diff --git a/.env.production.sample b/.env.production.sample index d7c04e2354..fbb2847078 100644 --- a/.env.production.sample +++ b/.env.production.sample @@ -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 diff --git a/app/assets/javascripts/components/features/notifications/components/clear_column_button.jsx b/app/assets/javascripts/components/features/notifications/components/clear_column_button.jsx index 62c3e61e07..71877fb2bc 100644 --- a/app/assets/javascripts/components/features/notifications/components/clear_column_button.jsx +++ b/app/assets/javascripts/components/features/notifications/components/clear_column_button.jsx @@ -25,7 +25,7 @@ const ClearColumnButton = React.createClass({ const { intl } = this.props; return ( -
+
); diff --git a/app/assets/javascripts/components/locales/fr.jsx b/app/assets/javascripts/components/locales/fr.jsx index fdd9c0e007..568422ff35 100644 --- a/app/assets/javascripts/components/locales/fr.jsx +++ b/app/assets/javascripts/components/locales/fr.jsx @@ -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 n’avez pas encore de notification. Interagissez avec d’autres utilisateurs⋅trices pour débuter la conversation.", "tabs_bar.compose": "Composer", "tabs_bar.home": "Accueil", "tabs_bar.mentions": "Mentions", diff --git a/app/assets/stylesheets/components.scss b/app/assets/stylesheets/components.scss index 696e89418a..9aead00b5f 100644 --- a/app/assets/stylesheets/components.scss +++ b/app/assets/stylesheets/components.scss @@ -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 { diff --git a/app/controllers/about_controller.rb b/app/controllers/about_controller.rb index 7fd43489f2..04e7ddacf7 100644 --- a/app/controllers/about_controller.rb +++ b/app/controllers/about_controller.rb @@ -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 diff --git a/app/controllers/api/v1/accounts_controller.rb b/app/controllers/api/v1/accounts_controller.rb index da18474cbd..454873116a 100644 --- a/app/controllers/api/v1/accounts_controller.rb +++ b/app/controllers/api/v1/accounts_controller.rb @@ -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 diff --git a/app/controllers/api/v1/blocks_controller.rb b/app/controllers/api/v1/blocks_controller.rb index dadf212656..742717ba2f 100644 --- a/app/controllers/api/v1/blocks_controller.rb +++ b/app/controllers/api/v1/blocks_controller.rb @@ -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 diff --git a/app/controllers/api/v1/favourites_controller.rb b/app/controllers/api/v1/favourites_controller.rb index 8a5b81e63b..22b93fe79a 100644 --- a/app/controllers/api/v1/favourites_controller.rb +++ b/app/controllers/api/v1/favourites_controller.rb @@ -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 diff --git a/app/controllers/api/v1/follow_requests_controller.rb b/app/controllers/api/v1/follow_requests_controller.rb index 3b8e8c0785..73cfaf10a6 100644 --- a/app/controllers/api/v1/follow_requests_controller.rb +++ b/app/controllers/api/v1/follow_requests_controller.rb @@ -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 diff --git a/app/controllers/api/v1/mutes_controller.rb b/app/controllers/api/v1/mutes_controller.rb index 6f48de0403..cbd98732bb 100644 --- a/app/controllers/api/v1/mutes_controller.rb +++ b/app/controllers/api/v1/mutes_controller.rb @@ -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 diff --git a/app/controllers/api/v1/notifications_controller.rb b/app/controllers/api/v1/notifications_controller.rb index 7bbc5419c5..71c054334e 100644 --- a/app/controllers/api/v1/notifications_controller.rb +++ b/app/controllers/api/v1/notifications_controller.rb @@ -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 diff --git a/app/controllers/api/v1/statuses_controller.rb b/app/controllers/api/v1/statuses_controller.rb index 4ece7e7028..1976ce330d 100644 --- a/app/controllers/api/v1/statuses_controller.rb +++ b/app/controllers/api/v1/statuses_controller.rb @@ -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 diff --git a/app/controllers/api/v1/timelines_controller.rb b/app/controllers/api/v1/timelines_controller.rb index 0446b9e4dd..e55e7d7181 100644 --- a/app/controllers/api/v1/timelines_controller.rb +++ b/app/controllers/api/v1/timelines_controller.rb @@ -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 diff --git a/app/models/account.rb b/app/models/account.rb index cbba8b5b6d..c59c760095 100644 --- a/app/models/account.rb +++ b/app/models/account.rb @@ -203,7 +203,7 @@ class Account < ApplicationRecord end def triadic_closures(account, limit = 5) - sql = < 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 diff --git a/app/views/about/more.html.haml b/app/views/about/more.html.haml index 2de3bf986f..8c12f57c10 100644 --- a/app/views/about/more.html.haml +++ b/app/views/about/more.html.haml @@ -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 diff --git a/app/views/about/index.html.haml b/app/views/about/show.html.haml similarity index 60% rename from app/views/about/index.html.haml rename to app/views/about/show.html.haml index f6b0c16685..8a0d00daa9 100644 --- a/app/views/about/index.html.haml +++ b/app/views/about/show.html.haml @@ -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 diff --git a/app/views/user_mailer/confirmation_instructions.fr.html.erb b/app/views/user_mailer/confirmation_instructions.fr.html.erb index 2665f1a20b..6c45f1a214 100644 --- a/app/views/user_mailer/confirmation_instructions.fr.html.erb +++ b/app/views/user_mailer/confirmation_instructions.fr.html.erb @@ -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 :

<%= link_to 'Confirmer mon compte', confirmation_url(@resource, confirmation_token: @token) %>

diff --git a/app/views/user_mailer/confirmation_instructions.fr.text.erb b/app/views/user_mailer/confirmation_instructions.fr.text.erb index 9d33450f80..dfa3f9f7c3 100644 --- a/app/views/user_mailer/confirmation_instructions.fr.text.erb +++ b/app/views/user_mailer/confirmation_instructions.fr.text.erb @@ -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) %> diff --git a/config/database.yml b/config/database.yml index 159973f0d3..810b832780 100644 --- a/config/database.yml +++ b/config/database.yml @@ -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' %> diff --git a/config/environments/production.rb b/config/environments/production.rb index dc5dd4afd4..d299e4f4ca 100644 --- a/config/environments/production.rb +++ b/config/environments/production.rb @@ -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] diff --git a/config/locales/devise.fr.yml b/config/locales/devise.fr.yml index ce44d041aa..3b46b01e3a 100644 --- a/config/locales/devise.fr.yml +++ b/config/locales/devise.fr.yml @@ -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) : ' diff --git a/config/locales/doorkeeper.fr.yml b/config/locales/doorkeeper.fr.yml index be109df9cc..cfc9083d74 100644 --- a/config/locales/doorkeeper.fr.yml +++ b/config/locales/doorkeeper.fr.yml @@ -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: s’abonner, 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 diff --git a/config/locales/fr.yml b/config/locales/fr.yml index 9727f3b7e3..9a9c1b6ded 100644 --- a/config/locales/fr.yml +++ b/config/locales/fr.yml @@ -4,7 +4,7 @@ fr: about_mastodon: Mastodon est un serveur libre de réseautage social. Alternative décentralisée 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 réseau social 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 (%{self}) avez demandé à suivre:' + prompt_html: 'Vous (%{self}) 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: %{name} utilise %{domain}. 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 en créer un ici. 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: "Scannez ce QR code grâce à Google Authenticator, Authy ou une application similaire sur votre téléphone. Désormais, cette application générera des jetons que vous devrez saisir à chaque connexion." - plaintext_secret_html: 'Code secret en clair: %{secret}' + plaintext_secret_html: 'Code secret en clair : %{secret}' 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: "…" diff --git a/config/routes.rb b/config/routes.rb index 9cbecf0779..b0a13aa78a 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -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 diff --git a/docs/Running-Mastodon/Administration-guide.md b/docs/Running-Mastodon/Administration-guide.md index 09b0f1df12..8bcfe7c9e1 100644 --- a/docs/Running-Mastodon/Administration-guide.md +++ b/docs/Running-Mastodon/Administration-guide.md @@ -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. diff --git a/lib/tasks/mastodon.rake b/lib/tasks/mastodon.rake index 79dcb722ae..5dc7f15678 100644 --- a/lib/tasks/mastodon.rake +++ b/lib/tasks/mastodon.rake @@ -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 diff --git a/spec/controllers/about_controller_spec.rb b/spec/controllers/about_controller_spec.rb index 4282649e1a..f49de96221 100644 --- a/spec/controllers/about_controller_spec.rb +++ b/spec/controllers/about_controller_spec.rb @@ -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 diff --git a/spec/models/account_spec.rb b/spec/models/account_spec.rb index 93a45459d9..0c3b2b0428 100644 --- a/spec/models/account_spec.rb +++ b/spec/models/account_spec.rb @@ -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') diff --git a/spec/models/tag_spec.rb b/spec/models/tag_spec.rb index 360bbc16de..7a5b8ec897 100644 --- a/spec/models/tag_spec.rb +++ b/spec/models/tag_spec.rb @@ -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 diff --git a/spec/presenters/instance_presenter_spec.rb b/spec/presenters/instance_presenter_spec.rb new file mode 100644 index 0000000000..0f318d9c31 --- /dev/null +++ b/spec/presenters/instance_presenter_spec.rb @@ -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 diff --git a/spec/requests/catch_all_route_request_spec.rb b/spec/requests/catch_all_route_request_spec.rb new file mode 100644 index 0000000000..22ce1cf59f --- /dev/null +++ b/spec/requests/catch_all_route_request_spec.rb @@ -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