Merge branch 'master' into skylight

This commit is contained in:
Eugen Rochko 2017-04-09 15:01:17 +02:00
commit 47910f57da
34 changed files with 425 additions and 149 deletions

View file

@ -63,3 +63,7 @@ SMTP_FROM_ADDRESS=notifications@example.com
# Streaming API integration
# STREAMING_API_BASE_URL=
# Advanced settings
# If you need to use pgBouncer, you need to disable prepared statements:
# PREPARED_STATEMENTS=false

View file

@ -25,7 +25,7 @@ const ClearColumnButton = React.createClass({
const { intl } = this.props;
return (
<div title={intl.formatMessage(messages.clear)} className='column-icon' tabIndex='0' style={iconStyle} onClick={this.onClick}>
<div title={intl.formatMessage(messages.clear)} className='column-icon' tabIndex='0' style={iconStyle} onClick={this.props.onClick}>
<i className='fa fa-eraser' />
</div>
);

View file

@ -14,6 +14,7 @@ const fr = {
"status.show_less": "Replier",
"status.open": "Déplier ce status",
"status.report": "Signaler @{name}",
"status.load_more": "Charger plus",
"video_player.toggle_sound": "Mettre/Couper le son",
"account.mention": "Mentionner",
"account.edit_profile": "Modifier le profil",
@ -41,6 +42,7 @@ const fr = {
"column.notifications": "Notifications",
"column.blocks": "Utilisateurs bloqués",
"column.favourites": "Favoris",
"empty_column.notifications": "Vous navez pas encore de notification. Interagissez avec dautres utilisateurs⋅trices pour débuter la conversation.",
"tabs_bar.compose": "Composer",
"tabs_bar.home": "Accueil",
"tabs_bar.mentions": "Mentions",

View file

@ -714,7 +714,15 @@ a.status__content__spoiler-link {
@media screen and (min-width: 360px) {
.columns-area {
margin: 10px;
margin: 0;
}
.column:first-child, .drawer:first-child {
margin-left: 0;
}
.column:last-child, .drawer:last-child {
margin-right: 0;
}
}
@ -816,6 +824,7 @@ a.status__content__spoiler-link {
}
.column, .drawer {
margin: 10px;
margin-left: 5px;
margin-right: 5px;
flex: 0 0 auto;
@ -823,11 +832,11 @@ a.status__content__spoiler-link {
}
.column:first-child, .drawer:first-child {
margin-left: 0;
margin-left: 10px;
}
.column:last-child, .drawer:last-child {
margin-right: 0;
margin-right: 10px;
}
@media screen and (max-width: 1024px) {
@ -885,6 +894,10 @@ a.status__content__spoiler-link {
}
@media screen and (min-width: 360px) {
.columns-area {
margin: 10px;
}
.tabs-bar {
margin: 10px;
margin-bottom: 0;
@ -895,6 +908,12 @@ a.status__content__spoiler-link {
}
}
@media screen and (min-width: 1024px) {
.columns-area {
margin: 0;
}
}
@media screen and (min-width: 600px) {
.tabs-bar__link {
.fa {

View file

@ -2,30 +2,25 @@
class AboutController < ApplicationController
before_action :set_body_classes
before_action :set_instance_presenter, only: [:show, :more]
def index
@description = Setting.site_description
@open_registrations = Setting.open_registrations
@closed_registrations_message = Setting.closed_registrations_message
def show; end
@user = User.new
@user.build_account
end
def more
@description = Setting.site_description
@extended_description = Setting.site_extended_description
@contact_account = Account.find_local(Setting.site_contact_username)
@contact_email = Setting.site_contact_email
@user_count = Rails.cache.fetch('user_count') { User.count }
@status_count = Rails.cache.fetch('local_status_count') { Status.local.count }
@domain_count = Rails.cache.fetch('distinct_domain_count') { Account.distinct.count(:domain) }
end
def more; end
def terms; end
private
def new_user
User.new.tap(&:build_account)
end
helper_method :new_user
def set_instance_presenter
@instance_presenter = InstancePresenter.new
end
def set_body_classes
@body_classes = 'about-body'
end

View file

@ -20,10 +20,8 @@ class Api::V1::AccountsController < ApiController
accounts = Account.where(id: results.map(&:target_account_id)).map { |a| [a.id, a] }.to_h
@accounts = results.map { |f| accounts[f.target_account_id] }
# set_account_counters_maps(@accounts)
next_path = following_api_v1_account_url(max_id: results.last.id) if results.size == limit_param(DEFAULT_ACCOUNTS_LIMIT)
prev_path = following_api_v1_account_url(since_id: results.first.id) unless results.empty?
next_path = following_api_v1_account_url(pagination_params(max_id: results.last.id)) if results.size == limit_param(DEFAULT_ACCOUNTS_LIMIT)
prev_path = following_api_v1_account_url(pagination_params(since_id: results.first.id)) unless results.empty?
set_pagination_headers(next_path, prev_path)
@ -35,10 +33,8 @@ class Api::V1::AccountsController < ApiController
accounts = Account.where(id: results.map(&:account_id)).map { |a| [a.id, a] }.to_h
@accounts = results.map { |f| accounts[f.account_id] }
# set_account_counters_maps(@accounts)
next_path = followers_api_v1_account_url(max_id: results.last.id) if results.size == limit_param(DEFAULT_ACCOUNTS_LIMIT)
prev_path = followers_api_v1_account_url(since_id: results.first.id) unless results.empty?
next_path = followers_api_v1_account_url(pagination_params(max_id: results.last.id)) if results.size == limit_param(DEFAULT_ACCOUNTS_LIMIT)
prev_path = followers_api_v1_account_url(pagination_params(since_id: results.first.id)) unless results.empty?
set_pagination_headers(next_path, prev_path)
@ -52,11 +48,9 @@ class Api::V1::AccountsController < ApiController
@statuses = cache_collection(@statuses, Status)
set_maps(@statuses)
# set_counters_maps(@statuses)
# set_account_counters_maps(@statuses.flat_map { |s| [s.account, s.reblog? ? s.reblog.account : nil] }.compact.uniq)
next_path = statuses_api_v1_account_url(max_id: @statuses.last.id) unless @statuses.empty?
prev_path = statuses_api_v1_account_url(since_id: @statuses.first.id) unless @statuses.empty?
next_path = statuses_api_v1_account_url(statuses_pagination_params(max_id: @statuses.last.id)) unless @statuses.empty?
prev_path = statuses_api_v1_account_url(statuses_pagination_params(since_id: @statuses.first.id)) unless @statuses.empty?
set_pagination_headers(next_path, prev_path)
end
@ -117,8 +111,6 @@ class Api::V1::AccountsController < ApiController
def search
@accounts = AccountSearchService.new.call(params[:q], limit_param(DEFAULT_ACCOUNTS_LIMIT), params[:resolve] == 'true', current_account)
# set_account_counters_maps(@accounts) unless @accounts.nil?
render action: :index
end
@ -135,4 +127,12 @@ class Api::V1::AccountsController < ApiController
@muting = Account.muting_map([@account.id], current_user.account_id)
@requested = Account.requested_map([@account.id], current_user.account_id)
end
def pagination_params(core_params)
params.permit(:limit).merge(core_params)
end
def statuses_pagination_params(core_params)
params.permit(:limit, :only_media, :exclude_replies).merge(core_params)
end
end

View file

@ -11,11 +11,15 @@ class Api::V1::BlocksController < ApiController
accounts = Account.where(id: results.map(&:target_account_id)).map { |a| [a.id, a] }.to_h
@accounts = results.map { |f| accounts[f.target_account_id] }.compact
# set_account_counters_maps(@accounts)
next_path = api_v1_blocks_url(max_id: results.last.id) if results.size == limit_param(DEFAULT_ACCOUNTS_LIMIT)
prev_path = api_v1_blocks_url(since_id: results.first.id) unless results.empty?
next_path = api_v1_blocks_url(pagination_params(max_id: results.last.id)) if results.size == limit_param(DEFAULT_ACCOUNTS_LIMIT)
prev_path = api_v1_blocks_url(pagination_params(since_id: results.first.id)) unless results.empty?
set_pagination_headers(next_path, prev_path)
end
private
def pagination_params(core_params)
params.permit(:limit).merge(core_params)
end
end

View file

@ -11,11 +11,16 @@ class Api::V1::FavouritesController < ApiController
@statuses = cache_collection(Status.where(id: results.map(&:status_id)), Status)
set_maps(@statuses)
# set_counters_maps(@statuses)
next_path = api_v1_favourites_url(max_id: results.last.id) if results.size == limit_param(DEFAULT_STATUSES_LIMIT)
prev_path = api_v1_favourites_url(since_id: results.first.id) unless results.empty?
next_path = api_v1_favourites_url(pagination_params(max_id: results.last.id)) if results.size == limit_param(DEFAULT_STATUSES_LIMIT)
prev_path = api_v1_favourites_url(pagination_params(since_id: results.first.id)) unless results.empty?
set_pagination_headers(next_path, prev_path)
end
private
def pagination_params(core_params)
params.permit(:limit).merge(core_params)
end
end

View file

@ -9,10 +9,8 @@ class Api::V1::FollowRequestsController < ApiController
accounts = Account.where(id: results.map(&:account_id)).map { |a| [a.id, a] }.to_h
@accounts = results.map { |f| accounts[f.account_id] }
# set_account_counters_maps(@accounts)
next_path = api_v1_follow_requests_url(max_id: results.last.id) if results.size == DEFAULT_ACCOUNTS_LIMIT
prev_path = api_v1_follow_requests_url(since_id: results.first.id) unless results.empty?
next_path = api_v1_follow_requests_url(pagination_params(max_id: results.last.id)) if results.size == DEFAULT_ACCOUNTS_LIMIT
prev_path = api_v1_follow_requests_url(pagination_params(since_id: results.first.id)) unless results.empty?
set_pagination_headers(next_path, prev_path)
end
@ -26,4 +24,10 @@ class Api::V1::FollowRequestsController < ApiController
RejectFollowService.new.call(Account.find(params[:id]), current_account)
render_empty
end
private
def pagination_params(core_params)
params.permit(:limit).merge(core_params)
end
end

View file

@ -11,11 +11,15 @@ class Api::V1::MutesController < ApiController
accounts = Account.where(id: results.map(&:target_account_id)).map { |a| [a.id, a] }.to_h
@accounts = results.map { |f| accounts[f.target_account_id] }
# set_account_counters_maps(@accounts)
next_path = api_v1_mutes_url(max_id: results.last.id) if results.size == limit_param(DEFAULT_ACCOUNTS_LIMIT)
prev_path = api_v1_mutes_url(since_id: results.first.id) unless results.empty?
next_path = api_v1_mutes_url(pagination_params(max_id: results.last.id)) if results.size == limit_param(DEFAULT_ACCOUNTS_LIMIT)
prev_path = api_v1_mutes_url(pagination_params(since_id: results.first.id)) unless results.empty?
set_pagination_headers(next_path, prev_path)
end
private
def pagination_params(core_params)
params.permit(:limit).merge(core_params)
end
end

View file

@ -14,11 +14,9 @@ class Api::V1::NotificationsController < ApiController
statuses = @notifications.select { |n| !n.target_status.nil? }.map(&:target_status)
set_maps(statuses)
# set_counters_maps(statuses)
# set_account_counters_maps(@notifications.map(&:from_account))
next_path = api_v1_notifications_url(max_id: @notifications.last.id) unless @notifications.empty?
prev_path = api_v1_notifications_url(since_id: @notifications.first.id) unless @notifications.empty?
next_path = api_v1_notifications_url(pagination_params(max_id: @notifications.last.id)) unless @notifications.empty?
prev_path = api_v1_notifications_url(pagination_params(since_id: @notifications.first.id)) unless @notifications.empty?
set_pagination_headers(next_path, prev_path)
end
@ -31,4 +29,10 @@ class Api::V1::NotificationsController < ApiController
Notification.where(account: current_account).delete_all
render_empty
end
private
def pagination_params(core_params)
params.permit(:limit).merge(core_params)
end
end

View file

@ -23,7 +23,6 @@ class Api::V1::StatusesController < ApiController
statuses = [@status] + @context[:ancestors] + @context[:descendants]
set_maps(statuses)
# set_counters_maps(statuses)
end
def card
@ -36,10 +35,8 @@ class Api::V1::StatusesController < ApiController
accounts = Account.where(id: results.map(&:account_id)).map { |a| [a.id, a] }.to_h
@accounts = results.map { |r| accounts[r.account_id] }
# set_account_counters_maps(@accounts)
next_path = reblogged_by_api_v1_status_url(max_id: results.last.id) if results.size == limit_param(DEFAULT_ACCOUNTS_LIMIT)
prev_path = reblogged_by_api_v1_status_url(since_id: results.first.id) unless results.empty?
next_path = reblogged_by_api_v1_status_url(pagination_params(max_id: results.last.id)) if results.size == limit_param(DEFAULT_ACCOUNTS_LIMIT)
prev_path = reblogged_by_api_v1_status_url(pagination_params(since_id: results.first.id)) unless results.empty?
set_pagination_headers(next_path, prev_path)
@ -51,10 +48,8 @@ class Api::V1::StatusesController < ApiController
accounts = Account.where(id: results.map(&:account_id)).map { |a| [a.id, a] }.to_h
@accounts = results.map { |f| accounts[f.account_id] }
# set_account_counters_maps(@accounts)
next_path = favourited_by_api_v1_status_url(max_id: results.last.id) if results.size == limit_param(DEFAULT_ACCOUNTS_LIMIT)
prev_path = favourited_by_api_v1_status_url(since_id: results.first.id) unless results.empty?
next_path = favourited_by_api_v1_status_url(pagination_params(max_id: results.last.id)) if results.size == limit_param(DEFAULT_ACCOUNTS_LIMIT)
prev_path = favourited_by_api_v1_status_url(pagination_params(since_id: results.first.id)) unless results.empty?
set_pagination_headers(next_path, prev_path)
@ -115,4 +110,8 @@ class Api::V1::StatusesController < ApiController
def status_params
params.permit(:status, :in_reply_to_id, :sensitive, :spoiler_text, :visibility, media_ids: [])
end
def pagination_params(core_params)
params.permit(:limit).merge(core_params)
end
end

View file

@ -1,8 +1,8 @@
# frozen_string_literal: true
class Api::V1::TimelinesController < ApiController
before_action -> { doorkeeper_authorize! :read }
before_action :require_user!, only: [:home, :mentions]
before_action -> { doorkeeper_authorize! :read }, only: [:home]
before_action :require_user!, only: [:home]
respond_to :json
@ -11,11 +11,9 @@ class Api::V1::TimelinesController < ApiController
@statuses = cache_collection(@statuses)
set_maps(@statuses)
# set_counters_maps(@statuses)
# set_account_counters_maps(@statuses.flat_map { |s| [s.account, s.reblog? ? s.reblog.account : nil] }.compact.uniq)
next_path = api_v1_home_timeline_url(max_id: @statuses.last.id) unless @statuses.empty?
prev_path = api_v1_home_timeline_url(since_id: @statuses.first.id) unless @statuses.empty?
next_path = api_v1_home_timeline_url(pagination_params(max_id: @statuses.last.id)) unless @statuses.empty?
prev_path = api_v1_home_timeline_url(pagination_params(since_id: @statuses.first.id)) unless @statuses.empty?
set_pagination_headers(next_path, prev_path)
@ -27,11 +25,9 @@ class Api::V1::TimelinesController < ApiController
@statuses = cache_collection(@statuses)
set_maps(@statuses)
# set_counters_maps(@statuses)
# set_account_counters_maps(@statuses.flat_map { |s| [s.account, s.reblog? ? s.reblog.account : nil] }.compact.uniq)
next_path = api_v1_public_timeline_url(max_id: @statuses.last.id) unless @statuses.empty?
prev_path = api_v1_public_timeline_url(since_id: @statuses.first.id) unless @statuses.empty?
next_path = api_v1_public_timeline_url(pagination_params(max_id: @statuses.last.id)) unless @statuses.empty?
prev_path = api_v1_public_timeline_url(pagination_params(since_id: @statuses.first.id)) unless @statuses.empty?
set_pagination_headers(next_path, prev_path)
@ -44,11 +40,9 @@ class Api::V1::TimelinesController < ApiController
@statuses = cache_collection(@statuses)
set_maps(@statuses)
# set_counters_maps(@statuses)
# set_account_counters_maps(@statuses.flat_map { |s| [s.account, s.reblog? ? s.reblog.account : nil] }.compact.uniq)
next_path = api_v1_hashtag_timeline_url(params[:id], max_id: @statuses.last.id) unless @statuses.empty?
prev_path = api_v1_hashtag_timeline_url(params[:id], since_id: @statuses.first.id) unless @statuses.empty?
next_path = api_v1_hashtag_timeline_url(params[:id], pagination_params(max_id: @statuses.last.id)) unless @statuses.empty?
prev_path = api_v1_hashtag_timeline_url(params[:id], pagination_params(since_id: @statuses.first.id)) unless @statuses.empty?
set_pagination_headers(next_path, prev_path)
@ -60,4 +54,8 @@ class Api::V1::TimelinesController < ApiController
def cache_collection(raw)
super(raw, Status)
end
def pagination_params(core_params)
params.permit(:local, :limit).merge(core_params)
end
end

View file

@ -203,7 +203,7 @@ class Account < ApplicationRecord
end
def triadic_closures(account, limit = 5)
sql = <<SQL
sql = <<-SQL.squish
WITH first_degree AS (
SELECT target_account_id
FROM follows
@ -216,7 +216,7 @@ class Account < ApplicationRecord
GROUP BY target_account_id, accounts.id
ORDER BY count(account_id) DESC
LIMIT ?
SQL
SQL
Account.find_by_sql([sql, account.id, account.id, limit])
end
@ -226,7 +226,7 @@ SQL
textsearch = '(setweight(to_tsvector(\'simple\', accounts.display_name), \'A\') || setweight(to_tsvector(\'simple\', accounts.username), \'B\') || setweight(to_tsvector(\'simple\', coalesce(accounts.domain, \'\')), \'C\'))'
query = 'to_tsquery(\'simple\', \'\'\' \' || ' + terms + ' || \' \'\'\' || \':*\')'
sql = <<SQL
sql = <<-SQL.squish
SELECT
accounts.*,
ts_rank_cd(#{textsearch}, #{query}, 32) AS rank
@ -234,7 +234,7 @@ SQL
WHERE #{query} @@ #{textsearch}
ORDER BY rank DESC
LIMIT ?
SQL
SQL
Account.find_by_sql([sql, limit])
end
@ -244,7 +244,7 @@ SQL
textsearch = '(setweight(to_tsvector(\'simple\', accounts.display_name), \'A\') || setweight(to_tsvector(\'simple\', accounts.username), \'B\') || setweight(to_tsvector(\'simple\', coalesce(accounts.domain, \'\')), \'C\'))'
query = 'to_tsquery(\'simple\', \'\'\' \' || ' + terms + ' || \' \'\'\' || \':*\')'
sql = <<SQL
sql = <<-SQL.squish
SELECT
accounts.*,
(count(f.id) + 1) * ts_rank_cd(#{textsearch}, #{query}, 32) AS rank
@ -254,7 +254,7 @@ SQL
GROUP BY accounts.id
ORDER BY rank DESC
LIMIT ?
SQL
SQL
Account.find_by_sql([sql, account.id, account.id, limit])
end

View file

@ -17,7 +17,7 @@ class Tag < ApplicationRecord
textsearch = 'to_tsvector(\'simple\', tags.name)'
query = 'to_tsquery(\'simple\', \'\'\' \' || ' + terms + ' || \' \'\'\' || \':*\')'
sql = <<SQL
sql = <<-SQL.squish
SELECT
tags.*,
ts_rank_cd(#{textsearch}, #{query}) AS rank
@ -25,7 +25,7 @@ class Tag < ApplicationRecord
WHERE #{query} @@ #{textsearch}
ORDER BY rank DESC
LIMIT ?
SQL
SQL
Tag.find_by_sql([sql, limit])
end

View file

@ -0,0 +1,28 @@
# frozen_string_literal: true
class InstancePresenter
delegate(
:closed_registrations_message,
:contact_email,
:open_registrations,
:site_description,
:site_extended_description,
to: Setting
)
def contact_account
Account.find_local(Setting.site_contact_username)
end
def user_count
Rails.cache.fetch('user_count') { User.count }
end
def status_count
Rails.cache.fetch('local_status_count') { Status.local.count }
end
def domain_count
Rails.cache.fetch('distinct_domain_count') { Account.distinct.count(:domain) }
end
end

View file

@ -0,0 +1,30 @@
= simple_form_for(new_user, url: user_registration_path) do |f|
= f.simple_fields_for :account do |account_fields|
= account_fields.input :username,
autofocus: true,
placeholder: t('simple_form.labels.defaults.username'),
required: true,
input_html: { 'aria-label' => t('simple_form.labels.defaults.username') }
= f.input :email,
placeholder: t('simple_form.labels.defaults.email'),
required: true,
input_html: { 'aria-label' => t('simple_form.labels.defaults.email') }
= f.input :password,
autocomplete: "off",
placeholder: t('simple_form.labels.defaults.password'),
required: true,
input_html: { 'aria-label' => t('simple_form.labels.defaults.password') }
= f.input :password_confirmation,
autocomplete: "off",
placeholder: t('simple_form.labels.defaults.confirm_password'),
required: true,
input_html: { 'aria-label' => t('simple_form.labels.defaults.confirm_password') }
.actions
= f.button :button, t('about.get_started'), type: :submit
.info
= link_to t('auth.login'), new_user_session_path, class: 'webapp-btn'
·
= link_to t('about.about_this'), about_more_path

View file

@ -7,42 +7,42 @@
.panel
%h2= Rails.configuration.x.local_domain
- unless @description.blank?
%p= @description.html_safe
- unless @instance_presenter.site_description.blank?
%p= @instance_presenter.site_description.html_safe
.information-board
.section
%span= t 'about.user_count_before'
%strong= number_with_delimiter @user_count
%strong= number_with_delimiter @instance_presenter.user_count
%span= t 'about.user_count_after'
.section
%span= t 'about.status_count_before'
%strong= number_with_delimiter @status_count
%strong= number_with_delimiter @instance_presenter.status_count
%span= t 'about.status_count_after'
.section
%span= t 'about.domain_count_before'
%strong= number_with_delimiter @domain_count
%strong= number_with_delimiter @instance_presenter.domain_count
%span= t 'about.domain_count_after'
- unless @extended_description.blank?
.panel= @extended_description.html_safe
- unless @instance_presenter.site_extended_description.blank?
.panel= @instance_presenter.site_extended_description.html_safe
.sidebar
.panel
.panel-header= t 'about.contact'
.panel-body
- if @contact_account
- if @instance_presenter.contact_account
.owner
.avatar= image_tag @contact_account.avatar.url
.avatar= image_tag @instance_presenter.contact_account.avatar.url
.name
= link_to TagManager.instance.url_for(@contact_account) do
%span.display_name.emojify= display_name(@contact_account)
%span.username= "@#{@contact_account.acct}"
= link_to TagManager.instance.url_for(@instance_presenter.contact_account) do
%span.display_name.emojify= display_name(@instance_presenter.contact_account)
%span.username= "@#{@instance_presenter.contact_account.acct}"
- unless @contact_email.blank?
- unless @instance_presenter.contact_email.blank?
.contact-email
= t 'about.business_email'
%strong= @contact_email
%strong= @instance_presenter.contact_email
.panel
.panel-header= t 'about.links'
.panel-list

View file

@ -8,7 +8,7 @@
%meta{ property: 'og:site_name', content: site_title }/
%meta{ property: 'og:type', content: 'website' }/
%meta{ property: 'og:title', content: Rails.configuration.x.local_domain }/
%meta{ property: 'og:description', content: @description.blank? ? "Mastodon is a free, open-source social network server. A decentralized alternative to commercial platforms, it avoids the risks of a single company monopolizing your communication. Anyone can run Mastodon and participate in the social network seamlessly" : strip_tags(@description) }/
%meta{ property: 'og:description', content: strip_tags(@instance_presenter.site_description.blank? ? t('about.about_mastodon') : @instance_presenter.site_description) }/
%meta{ property: 'og:image', content: asset_url('mastodon_small.jpg') }/
%meta{ property: 'og:image:width', content: '400' }/
%meta{ property: 'og:image:height', content: '400' }/
@ -24,28 +24,14 @@
.screenshot-with-signup
.mascot= image_tag 'fluffy-elephant-friend.png'
- if @open_registrations
= simple_form_for(@user, url: user_registration_path) do |f|
= f.simple_fields_for :account do |ff|
= ff.input :username, autofocus: true, placeholder: t('simple_form.labels.defaults.username'), required: true, input_html: { 'aria-label' => t('simple_form.labels.defaults.username') }
= f.input :email, placeholder: t('simple_form.labels.defaults.email'), required: true, input_html: { 'aria-label' => t('simple_form.labels.defaults.email') }
= f.input :password, autocomplete: "off", placeholder: t('simple_form.labels.defaults.password'), required: true, input_html: { 'aria-label' => t('simple_form.labels.defaults.password') }
= f.input :password_confirmation, autocomplete: "off", placeholder: t('simple_form.labels.defaults.confirm_password'), required: true, input_html: { 'aria-label' => t('simple_form.labels.defaults.confirm_password') }
.actions
= f.button :button, t('about.get_started'), type: :submit
.info
= link_to t('auth.login'), new_user_session_path, class: 'webapp-btn'
·
= link_to t('about.about_this'), about_more_path
- if @instance_presenter.open_registrations
= render 'registration'
- else
.closed-registrations-message
- if @closed_registrations_message.blank?
- if @instance_presenter.closed_registrations_message.blank?
%p= t('about.closed_registrations')
- else
= @closed_registrations_message.html_safe
= @instance_presenter.closed_registrations_message.html_safe
.info
= link_to t('auth.login'), new_user_session_path, class: 'webapp-btn'
·
@ -85,9 +71,9 @@
= fa_icon('li check-square')
= t 'about.features.api'
- unless @description.blank?
- unless @instance_presenter.site_description.blank?
%h3= t('about.description_headline', domain: Rails.configuration.x.local_domain)
%p= @description.html_safe
%p= @instance_presenter.site_description.html_safe
.actions
.info

View file

@ -1,5 +1,5 @@
<p>Bienvenue <%= @resource.email %>&nbsp;!</p>
<p>Vous pouvez confirmer l'email de votre compte Mastodon en cliquant sur le lien ci-dessous&nbsp;:</p>
<p>Vous pouvez confirmer le courriel de votre compte Mastodon en cliquant sur le lien ci-dessous&nbsp;:</p>
<p><%= link_to 'Confirmer mon compte', confirmation_url(@resource, confirmation_token: @token) %></p>

View file

@ -1,5 +1,5 @@
Bienvenue <%= @resource.email %> !
Vous pouvez confirmer l'email de votre compte Mastodon en cliquant sur le lien ci-dessous :
Vous pouvez confirmer le courriel de votre compte Mastodon en cliquant sur le lien ci-dessous :
<%= confirmation_url(@resource, confirmation_token: @token) %>

View file

@ -22,4 +22,4 @@ production:
password: <%= ENV['DB_PASS'] || '' %>
host: <%= ENV['DB_HOST'] || 'localhost' %>
port: <%= ENV['DB_PORT'] || 5432 %>
prepared_statements: false
prepared_statements: <%= ENV['PREPARED_STATEMENTS'] || 'true' %>

View file

@ -38,9 +38,9 @@ Rails.application.configure do
# Force all access to the app over SSL, use Strict-Transport-Security, and use secure cookies.
config.force_ssl = false
# Use the lowest log level to ensure availability of diagnostic information
# By default, use the lowest log level to ensure availability of diagnostic information
# when problems arise.
config.log_level = :debug
config.log_level = ENV.fetch('RAILS_LOG_LEVEL', 'debug').to_sym
# Prepend all log lines with the following tags.
config.log_tags = [:request_id]

View file

@ -58,4 +58,4 @@ fr:
not_locked: n'était pas verrouillé(e)
not_saved:
one: '1 erreur a empêché ce(tte) %{resource} d''être sauvegardé(e) :'
other: '%{count} erreurs ont empêché ce(tte) %{resource} d''être sauvegardé(e): '
other: '%{count} erreurs ont empêché ce(tte) %{resource} d''être sauvegardé(e) : '

View file

@ -23,11 +23,11 @@ fr:
edit: Modifier
submit: Envoyer
confirmations:
destroy: Êtes-vous certain?
destroy: Êtes-vous certain ?
edit:
title: Modifier l'application
form:
error: Oups! Vérifier votre formulaire pour des erreurs possibles
error: Oups ! Vérifier votre formulaire pour des erreurs possibles
help:
native_redirect_uri: Utiliser %{native_redirect_uri} pour les tests locaux
redirect_uri: Utiliser une ligne par URL
@ -54,7 +54,7 @@ fr:
title: Une erreur est survenue
new:
able_to: Cette application pourra
prompt: Autoriser %{client_name} à utiliser votre compte?
prompt: Autoriser %{client_name} à utiliser votre compte ?
title: Autorisation requise
show:
title: Code d'autorisation
@ -109,5 +109,5 @@ fr:
title: Autorisation OAuth requise
scopes:
follow: sabonner, se désabonner, bloquer, et débloquer des comptes
read: lire les données de votre compte
read: lire les données de votre compte
write: poster en tant que vous

View file

@ -4,7 +4,7 @@ fr:
about_mastodon: Mastodon est un serveur <em>libre</em> de réseautage social. Alternative <em>décentralisée</em> aux plateformes commerciales, la monopolisation de vos communications par une entreprise unique est évitée. Tout un chacun peut faire tourner Mastodon et participer au <em>réseau social</em> de manière transparente.
about_this: À propos de cette instance
apps: Applications
business_email: E-mail professionnel
business_email: Courriel professionnel
closed_registrations: Les inscriptions sont actuellement fermées sur cette instance.
contact: Contact
description_headline: Qu'est-ce que %{domain} ?
@ -40,9 +40,9 @@ fr:
remote_follow: Suivre à distance
unfollow: Ne plus suivre
application_mailer:
settings: 'Changer les préférences e-mail: ${link}'
settings: 'Changer les préférences courriel : ${link}'
signature: Notifications de Mastodon depuis %{instance}
view: 'Voir:'
view: 'Voir :'
applications:
invalid_url: L'URL fournie est invalide
auth:
@ -58,21 +58,27 @@ fr:
authorize_follow:
error: Malheureusement, il y a eu une erreur en cherchant les détails du compte distant
follow: Suivre
prompt_html: 'Vous (<strong>%{self}</strong>) avez demandé à suivre:'
prompt_html: 'Vous (<strong>%{self}</strong>) avez demandé à suivre :'
title: Suivre %{acct}
datetime:
distance_in_words:
about_x_hours: "%{count}h"
about_x_months: "%{count}mo"
about_x_years: "%{count}y"
almost_x_years: "%{count}y"
about_x_months: "%{count}mois"
about_x_years:
one: un an
other: "%{count} ans"
almost_x_years:
one: un an
other: "%{count} ans"
half_a_minute: A l'instant
less_than_x_minutes: "%{count}m"
less_than_x_minutes: "%{count}min"
less_than_x_seconds: A l'instant
over_x_years: "%{count}y"
x_days: "%{count}d"
x_minutes: "%{count}m"
x_months: "%{count}mo"
over_x_years:
one: un an
other: "%{count} ans"
x_days: "%{count}j"
x_minutes: "%{count}min"
x_months: "%{count}mois"
x_seconds: "%{count}s"
exports:
blocks: Vous bloquez
@ -96,7 +102,7 @@ fr:
landing_strip_html: <strong>%{name}</strong> utilise <strong>%{domain}</strong>. Vous pouvez le/la suivre et interagir si vous possédez un compte quelque part dans le "fediverse". Si ce n'est pas le cas, vous pouvez <a href="%{sign_up_path}">en créer un ici</a>.
notification_mailer:
digest:
body: 'Voici ce que vous avez raté sur ${instance} depuis votre dernière visite (%{}):'
body: 'Voici ce que vous avez raté sur ${instance} depuis votre dernière visite (%{}) :'
mention: '%{name} vous a mentionné⋅e'
new_followers_summary:
one: Vous avez un⋅e nouvel⋅le abonné⋅e ! Youpi !
@ -156,10 +162,10 @@ fr:
disable: Désactiver
enable: Activer
instructions_html: "<strong>Scannez ce QR code grâce à Google Authenticator, Authy ou une application similaire sur votre téléphone</strong>. Désormais, cette application générera des jetons que vous devrez saisir à chaque connexion."
plaintext_secret_html: 'Code secret en clair: <samp>%{secret}</samp>'
plaintext_secret_html: 'Code secret en clair : <samp>%{secret}</samp>'
warning: Si vous ne pouvez pas configurer une application d'authentification maintenant, vous devriez cliquer sur "Désactiver" pour ne pas bloquer l'accès à votre compte.
users:
invalid_email: L'adresse e-mail est invalide
invalid_email: L'adresse courriel est invalide
invalid_otp_token: Le code d'authentification à deux facteurs est invalide
will_paginate:
page_gap: "&hellip;"

View file

@ -188,11 +188,14 @@ Rails.application.routes.draw do
get '/web/(*any)', to: 'home#index', as: :web
get '/about', to: 'about#index'
get '/about', to: 'about#show'
get '/about/more', to: 'about#more'
get '/terms', to: 'about#terms'
root 'home#index'
match '*unmatched_route', via: :all, to: 'application#raise_not_found'
match '*unmatched_route',
via: :all,
to: 'application#raise_not_found',
format: false
end

View file

@ -35,3 +35,11 @@ You are able to set the following settings:
- Site extended description
You may wish to use the extended description (shown at https://yourmastodon.instance/about/more ) to display content guidelines or a user agreement (see https://mastodon.social/about/more for an example).
## Confirming Users Manually
The following rake task:
RAILS_ENV=production bundle exec rails mastodon:confirm_email USER_EMAIL=alice@alice.com
Will confirm a user manually, in case they don't have access to their confirmation email for whatever reason.

View file

@ -10,6 +10,15 @@ namespace :mastodon do
puts "Congrats! #{user.account.username} is now an admin. \\o/\nNavigate to #{admin_settings_url} to get started"
end
desc 'Manually confirms a user with associated user email address stored in USER_EMAIL environment variable.'
task confirm_email: :environment do
email = ENV.fetch('USER_EMAIL')
user = User.where(email: email)
user.update(confirmed_at: Time.now.utc)
puts "User #{email} confirmed."
end
namespace :media do
desc 'Removes media attachments that have not been assigned to any status for longer than a day'
task clear: :environment do

View file

@ -3,9 +3,16 @@ require 'rails_helper'
RSpec.describe AboutController, type: :controller do
render_views
describe 'GET #index' do
describe 'GET #show' do
it 'returns http success' do
get :index
get :show
expect(response).to have_http_status(:success)
end
end
describe 'GET #more' do
it 'returns http success' do
get :more
expect(response).to have_http_status(:success)
end
end

View file

@ -170,6 +170,61 @@ RSpec.describe Account, type: :model do
end
end
describe '.search_for' do
before do
@match = Fabricate(
:account,
display_name: "Display Name",
username: "username",
domain: "example.com"
)
_missing = Fabricate(
:account,
display_name: "Missing",
username: "missing",
domain: "missing.com"
)
end
it 'finds accounts with matching display_name' do
results = Account.search_for("display")
expect(results).to eq [@match]
end
it 'finds accounts with matching username' do
results = Account.search_for("username")
expect(results).to eq [@match]
end
it 'finds accounts with matching domain' do
results = Account.search_for("example")
expect(results).to eq [@match]
end
it 'ranks multiple matches higher' do
account = Fabricate(
:account,
username: "username",
display_name: "username"
)
results = Account.search_for("username")
expect(results).to eq [account, @match]
end
end
describe '.advanced_search_for' do
it 'ranks followed accounts higher' do
account = Fabricate(:account)
match = Fabricate(:account, username: "Matching")
followed_match = Fabricate(:account, username: "Matcher")
Fabricate(:follow, account: account, target_account: followed_match)
results = Account.advanced_search_for("match", account)
expect(results).to eq [followed_match, match]
expect(results.first.rank).to be > results.last.rank
end
end
describe '.find_local' do
before do
Fabricate(:account, username: 'Alice')

View file

@ -12,4 +12,15 @@ RSpec.describe Tag, type: :model do
expect(subject.match('https://en.wikipedia.org/wiki/Ghostbusters_(song)#Lawsuit')).to be_nil
end
end
describe '.search_for' do
it 'finds tag records with matching names' do
tag = Fabricate(:tag, name: "match")
_miss_tag = Fabricate(:tag, name: "miss")
results = Tag.search_for("match")
expect(results).to eq [tag]
end
end
end

View file

@ -0,0 +1,74 @@
require 'rails_helper'
describe InstancePresenter do
let(:instance_presenter) { InstancePresenter.new }
it "delegates site_description to Setting" do
Setting.site_description = "Site desc"
expect(instance_presenter.site_description).to eq "Site desc"
end
it "delegates site_extended_description to Setting" do
Setting.site_extended_description = "Extended desc"
expect(instance_presenter.site_extended_description).to eq "Extended desc"
end
it "delegates open_registrations to Setting" do
Setting.open_registrations = false
expect(instance_presenter.open_registrations).to eq false
end
it "delegates closed_registrations_message to Setting" do
Setting.closed_registrations_message = "Closed message"
expect(instance_presenter.closed_registrations_message).to eq "Closed message"
end
it "delegates contact_email to Setting" do
Setting.contact_email = "admin@example.com"
expect(instance_presenter.contact_email).to eq "admin@example.com"
end
describe "contact_account" do
it "returns the account for the site contact username" do
Setting.site_contact_username = "aaa"
account = Fabricate(:account, username: "aaa")
expect(instance_presenter.contact_account).to eq(account)
end
end
describe "user_count" do
it "returns the number of site users" do
cache = double
allow(Rails).to receive(:cache).and_return(cache)
allow(cache).to receive(:fetch).with("user_count").and_return(123)
expect(instance_presenter.user_count).to eq(123)
end
end
describe "status_count" do
it "returns the number of local statuses" do
cache = double
allow(Rails).to receive(:cache).and_return(cache)
allow(cache).to receive(:fetch).with("local_status_count").and_return(234)
expect(instance_presenter.status_count).to eq(234)
end
end
describe "domain_count" do
it "returns the number of known domains" do
cache = double
allow(Rails).to receive(:cache).and_return(cache)
allow(cache).to receive(:fetch).with("distinct_domain_count").and_return(345)
expect(instance_presenter.domain_count).to eq(345)
end
end
end

View file

@ -0,0 +1,21 @@
require "rails_helper"
describe "The catch all route" do
describe "with a simple value" do
it "returns a 404 page as html" do
get "/test"
expect(response.status).to eq 404
expect(response.content_type).to eq "text/html"
end
end
describe "with an implied format" do
it "returns a 404 page as html" do
get "/test.test"
expect(response.status).to eq 404
expect(response.content_type).to eq "text/html"
end
end
end