diff --git a/.env.production.sample b/.env.production.sample index d7c04e23..fbb28470 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 62c3e61e..71877fb2 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 fdd9c0e0..568422ff 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 696e8941..9aead00b 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 7fd43489..04e7ddac 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 da18474c..45487311 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 dadf2126..742717ba 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 8a5b81e6..22b93fe7 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 3b8e8c07..73cfaf10 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 6f48de04..cbd98732 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 7bbc5419..71c05433 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 4ece7e70..1976ce33 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 0446b9e4..e55e7d71 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 cbba8b5b..c59c7600 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 2de3bf98..8c12f57c 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 f6b0c166..8a0d00da 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 2665f1a2..6c45f1a2 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 9d33450f..dfa3f9f7 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 159973f0..810b8327 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 dc5dd4af..d299e4f4 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 ce44d041..3b46b01e 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 be109df9..cfc9083d 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 9727f3b7..9a9c1b6d 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 9cbecf07..b0a13aa7 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 09b0f1df..8bcfe7c9 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 79dcb722..5dc7f156 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 4282649e..f49de962 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 93a45459..0c3b2b04 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 360bbc16..7a5b8ec8 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 00000000..0f318d9c --- /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 00000000..22ce1cf5 --- /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