From b89f007862bb06bbf892c4f37dbc31ed83138b53 Mon Sep 17 00:00:00 2001 From: Eugen Date: Sat, 8 Apr 2017 23:39:31 +0200 Subject: [PATCH 01/13] Make public timelines API not require user context/app credentials (#1291) * Make /api/v1/timelines/public and /api/v1/timelines/tag/:id public Fix #1156 - respect query params when generating pagination links in API * Apply pagination fix to more APIs --- app/controllers/api/v1/accounts_controller.rb | 28 +++++++++---------- app/controllers/api/v1/blocks_controller.rb | 12 +++++--- .../api/v1/favourites_controller.rb | 11 ++++++-- .../api/v1/follow_requests_controller.rb | 12 +++++--- app/controllers/api/v1/mutes_controller.rb | 12 +++++--- .../api/v1/notifications_controller.rb | 12 +++++--- app/controllers/api/v1/statuses_controller.rb | 17 ++++++----- .../api/v1/timelines_controller.rb | 26 ++++++++--------- 8 files changed, 74 insertions(+), 56 deletions(-) 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 From 6e3925521d152808febf273312c73a09e3d3feb3 Mon Sep 17 00:00:00 2001 From: Ash Furrow Date: Sat, 8 Apr 2017 19:09:46 -0400 Subject: [PATCH 02/13] Adds user confirmation rake task (#1300) * Adds task to confirm user by email. * Adds documentation for manual confirmation. --- docs/Running-Mastodon/Administration-guide.md | 8 ++++++++ lib/tasks/mastodon.rake | 9 +++++++++ 2 files changed, 17 insertions(+) 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 From c3e7bac1ccd1b40724f4603052e3c50a1741f5f6 Mon Sep 17 00:00:00 2001 From: Ash Furrow Date: Sat, 8 Apr 2017 19:42:13 -0400 Subject: [PATCH 03/13] Allows setting log level in env variable (#1290) * Allows setting log level in env variable. * Made changes based on feedback in #1290. --- config/environments/production.rb | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) 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] From d5a675099a0ccf0c35ce1756f5015108cda1489a Mon Sep 17 00:00:00 2001 From: Eugen Date: Sun, 9 Apr 2017 05:46:32 +0200 Subject: [PATCH 04/13] Add env variable to disable prepared statements (#1293) --- .env.production.sample | 4 ++++ config/database.yml | 1 + 2 files changed, 5 insertions(+) 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/config/database.yml b/config/database.yml index 5ec342f9..810b8327 100644 --- a/config/database.yml +++ b/config/database.yml @@ -22,3 +22,4 @@ production: password: <%= ENV['DB_PASS'] || '' %> host: <%= ENV['DB_HOST'] || 'localhost' %> port: <%= ENV['DB_PORT'] || 5432 %> + prepared_statements: <%= ENV['PREPARED_STATEMENTS'] || 'true' %> From b1881a3d481bccfba984d42380ab2f3780bd0845 Mon Sep 17 00:00:00 2001 From: Rachel H Date: Sun, 9 Apr 2017 02:35:23 -0700 Subject: [PATCH 05/13] Fix nonworking clear notices button (#1316) --- .../features/notifications/components/clear_column_button.jsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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 ( -
+
); From 71706f21c28f5ae623ee69810fe26a34fb79b446 Mon Sep 17 00:00:00 2001 From: Matt Jankowski Date: Sun, 9 Apr 2017 08:39:41 -0400 Subject: [PATCH 06/13] Ignore implied formats for catch all route requests (#1340) A request to `/test` would show the custom 404 page, but a request to `/test.test` would return a 404 with an empty body. This change ignores the format on incoming catch all route requests, so that the html 404 page is returned on these requests. --- config/routes.rb | 5 ++++- spec/requests/catch_all_route_request_spec.rb | 21 +++++++++++++++++++ 2 files changed, 25 insertions(+), 1 deletion(-) create mode 100644 spec/requests/catch_all_route_request_spec.rb diff --git a/config/routes.rb b/config/routes.rb index 9cbecf07..66b0ed83 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -194,5 +194,8 @@ Rails.application.routes.draw do 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/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 From 388ec0d5b6d1549abed15802d6bdbfc8b1c05294 Mon Sep 17 00:00:00 2001 From: Matt Jankowski Date: Sun, 9 Apr 2017 08:45:01 -0400 Subject: [PATCH 07/13] Search cleanup (#1333) * Clean up SQL output in Tag and Account search methods * Add basic coverage for Tag.search_for * Add coverage for Account.search_for * Add coverage for Account.advanced_search_for --- app/models/account.rb | 12 ++++---- app/models/tag.rb | 4 +-- spec/models/account_spec.rb | 55 +++++++++++++++++++++++++++++++++++++ spec/models/tag_spec.rb | 11 ++++++++ 4 files changed, 74 insertions(+), 8 deletions(-) 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 = < 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 From 53eb31f124b8cb366f45ac0aec36e346115e334f Mon Sep 17 00:00:00 2001 From: Brian Mock Date: Sun, 9 Apr 2017 05:45:26 -0700 Subject: [PATCH 08/13] Fixes #1311 margin shouldn't stay fixed (#1312) --- app/assets/stylesheets/components.scss | 25 ++++++++++++++++++++++--- 1 file changed, 22 insertions(+), 3 deletions(-) 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 { From e5282e4ec0e9dab62dde9481284b0cfd30690fb9 Mon Sep 17 00:00:00 2001 From: Matt Jankowski Date: Sun, 9 Apr 2017 08:47:25 -0400 Subject: [PATCH 09/13] Clean up about page (#1282) * Add InstancePresenter to expose site details * Clean up about controller, use instance presenter --- app/controllers/about_controller.rb | 29 +++----- app/presenters/instance_presenter.rb | 28 +++++++ app/views/about/_registration.html.haml | 30 ++++++++ app/views/about/more.html.haml | 28 +++---- .../about/{index.html.haml => show.html.haml} | 28 ++----- config/routes.rb | 2 +- spec/controllers/about_controller_spec.rb | 11 ++- spec/presenters/instance_presenter_spec.rb | 74 +++++++++++++++++++ 8 files changed, 175 insertions(+), 55 deletions(-) create mode 100644 app/presenters/instance_presenter.rb create mode 100644 app/views/about/_registration.html.haml rename app/views/about/{index.html.haml => show.html.haml} (60%) create mode 100644 spec/presenters/instance_presenter_spec.rb 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/presenters/instance_presenter.rb b/app/presenters/instance_presenter.rb new file mode 100644 index 00000000..cd809566 --- /dev/null +++ b/app/presenters/instance_presenter.rb @@ -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 diff --git a/app/views/about/_registration.html.haml b/app/views/about/_registration.html.haml new file mode 100644 index 00000000..c7a9a488 --- /dev/null +++ b/app/views/about/_registration.html.haml @@ -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 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/config/routes.rb b/config/routes.rb index 66b0ed83..b0a13aa7 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -188,7 +188,7 @@ 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' 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/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 From ba2aea3a8046c4adeec4e75eab68538511f66656 Mon Sep 17 00:00:00 2001 From: Jonathan Klee Date: Sun, 9 Apr 2017 14:54:02 +0200 Subject: [PATCH 10/13] add empty notifications french translation (#1111) --- app/assets/javascripts/components/locales/fr.jsx | 1 + 1 file changed, 1 insertion(+) diff --git a/app/assets/javascripts/components/locales/fr.jsx b/app/assets/javascripts/components/locales/fr.jsx index fdd9c0e0..496d8bb6 100644 --- a/app/assets/javascripts/components/locales/fr.jsx +++ b/app/assets/javascripts/components/locales/fr.jsx @@ -41,6 +41,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", From e6b48a704885f6afe2d5cce4b46a31f91231ba5e Mon Sep 17 00:00:00 2001 From: spf Date: Sun, 9 Apr 2017 14:54:47 +0200 Subject: [PATCH 11/13] French typo (#1257) * French typo * Datetime french translation --- config/locales/devise.fr.yml | 2 +- config/locales/doorkeeper.fr.yml | 8 ++++---- config/locales/fr.yml | 32 +++++++++++++++++++------------- 3 files changed, 24 insertions(+), 18 deletions(-) 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..e30aab6f 100644 --- a/config/locales/fr.yml +++ b/config/locales/fr.yml @@ -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 e-mail : ${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,7 +162,7 @@ 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 From 082bef30277308a214c93c54120064066ec00e2e Mon Sep 17 00:00:00 2001 From: Olivier Humbert Date: Sun, 9 Apr 2017 14:55:58 +0200 Subject: [PATCH 12/13] French translation update (#1271) * Update confirmation_instructions.fr.html.erb consistency across the French translation * Update consistency across the French translation * Update fr.yml a bunch of consistency across the French translation + a few typos * Update doorkeeper.fr.yml consistency across the French translation (punctuation) --- app/views/user_mailer/confirmation_instructions.fr.html.erb | 2 +- app/views/user_mailer/confirmation_instructions.fr.text.erb | 2 +- config/locales/fr.yml | 6 +++--- 3 files changed, 5 insertions(+), 5 deletions(-) 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/locales/fr.yml b/config/locales/fr.yml index e30aab6f..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,7 +40,7 @@ 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 :' applications: @@ -165,7 +165,7 @@ fr: 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: "…" From 12e29c9660df3ce85f46715bb62a4a15ce38f6a5 Mon Sep 17 00:00:00 2001 From: StefOfficiel Date: Sun, 9 Apr 2017 14:58:08 +0200 Subject: [PATCH 13/13] Update fr.jsx (#1329) * Update fr.jsx * Remove duplicate translation --- app/assets/javascripts/components/locales/fr.jsx | 1 + 1 file changed, 1 insertion(+) diff --git a/app/assets/javascripts/components/locales/fr.jsx b/app/assets/javascripts/components/locales/fr.jsx index 496d8bb6..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",