From b253d3e0c2591700f597ad63a93bc62c5f48409d Mon Sep 17 00:00:00 2001 From: gol-cha <info@mevo.xyz> Date: Sat, 2 Feb 2019 09:26:49 +0900 Subject: [PATCH 01/32] Upgrade new Web Share Target API (#9963) * Update manifest.json for new Web Share Target API. * fix code formatting --- app/serializers/manifest_serializer.rb | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/app/serializers/manifest_serializer.rb b/app/serializers/manifest_serializer.rb index 859ef0d149..cc8b9a4d45 100644 --- a/app/serializers/manifest_serializer.rb +++ b/app/serializers/manifest_serializer.rb @@ -52,6 +52,14 @@ class ManifestSerializer < ActiveModel::Serializer end def share_target - { url_template: 'share?title={title}&text={text}&url={url}' } + { + url_template: 'share?title={title}&text={text}&url={url}', + action: 'share', + params: { + title: 'title', + text: 'text', + url: 'url', + }, + } end end From 5092d17f2936146fa26e5d8a9b9e391f77010f28 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Acid=20Chicken=20=28=E7=A1=AB=E9=85=B8=E9=B6=8F=29?= <root@acid-chicken.com> Date: Sat, 2 Feb 2019 20:25:04 +0900 Subject: [PATCH 02/32] Add WebP support (#9879) * Add WebP support * Remove the changes to the tooltip refs: https://github.com/tootsuite/mastodon/pull/9879#pullrequestreview-199312528 --- app/javascript/mastodon/utils/resize_image.js | 2 +- app/models/concerns/account_avatar.rb | 2 +- app/models/concerns/account_header.rb | 2 +- app/models/media_attachment.rb | 4 ++-- app/models/preview_card.rb | 2 +- 5 files changed, 6 insertions(+), 6 deletions(-) diff --git a/app/javascript/mastodon/utils/resize_image.js b/app/javascript/mastodon/utils/resize_image.js index d1608094f7..bbdbc865e9 100644 --- a/app/javascript/mastodon/utils/resize_image.js +++ b/app/javascript/mastodon/utils/resize_image.js @@ -31,7 +31,7 @@ const loadImage = inputFile => new Promise((resolve, reject) => { }); const getOrientation = (img, type = 'image/png') => new Promise(resolve => { - if (type !== 'image/jpeg') { + if (!['image/jpeg', 'image/webp'].includes(type)) { resolve(1); return; } diff --git a/app/models/concerns/account_avatar.rb b/app/models/concerns/account_avatar.rb index 2d5ebfca35..5fff3ef5d2 100644 --- a/app/models/concerns/account_avatar.rb +++ b/app/models/concerns/account_avatar.rb @@ -3,7 +3,7 @@ module AccountAvatar extend ActiveSupport::Concern - IMAGE_MIME_TYPES = ['image/jpeg', 'image/png', 'image/gif'].freeze + IMAGE_MIME_TYPES = ['image/jpeg', 'image/png', 'image/gif', 'image/webp'].freeze LIMIT = 2.megabytes class_methods do diff --git a/app/models/concerns/account_header.rb b/app/models/concerns/account_header.rb index 067e166eb6..a748fdff72 100644 --- a/app/models/concerns/account_header.rb +++ b/app/models/concerns/account_header.rb @@ -3,7 +3,7 @@ module AccountHeader extend ActiveSupport::Concern - IMAGE_MIME_TYPES = ['image/jpeg', 'image/png', 'image/gif'].freeze + IMAGE_MIME_TYPES = ['image/jpeg', 'image/png', 'image/gif', 'image/webp'].freeze LIMIT = 2.megabytes MAX_PIXELS = 750_000 # 1500x500px diff --git a/app/models/media_attachment.rb b/app/models/media_attachment.rb index 6b939124fa..2e0f98cc6f 100644 --- a/app/models/media_attachment.rb +++ b/app/models/media_attachment.rb @@ -25,10 +25,10 @@ class MediaAttachment < ApplicationRecord enum type: [:image, :gifv, :video, :unknown] - IMAGE_FILE_EXTENSIONS = ['.jpg', '.jpeg', '.png', '.gif'].freeze + IMAGE_FILE_EXTENSIONS = ['.jpg', '.jpeg', '.png', '.gif', '.webp'].freeze VIDEO_FILE_EXTENSIONS = ['.webm', '.mp4', '.m4v', '.mov'].freeze - IMAGE_MIME_TYPES = ['image/jpeg', 'image/png', 'image/gif'].freeze + IMAGE_MIME_TYPES = ['image/jpeg', 'image/png', 'image/gif', 'image/webp'].freeze VIDEO_MIME_TYPES = ['video/webm', 'video/mp4', 'video/quicktime'].freeze VIDEO_CONVERTIBLE_MIME_TYPES = ['video/webm', 'video/quicktime'].freeze diff --git a/app/models/preview_card.rb b/app/models/preview_card.rb index a792b352bd..f26ea0c748 100644 --- a/app/models/preview_card.rb +++ b/app/models/preview_card.rb @@ -25,7 +25,7 @@ # class PreviewCard < ApplicationRecord - IMAGE_MIME_TYPES = ['image/jpeg', 'image/png', 'image/gif'].freeze + IMAGE_MIME_TYPES = ['image/jpeg', 'image/png', 'image/gif', 'image/webp'].freeze LIMIT = 1.megabytes self.inheritance_column = false From 6a5e3da6b044e50635d293c2716883cc5627e4c8 Mon Sep 17 00:00:00 2001 From: Jakub Mendyk <jakubmendyk.szkola@gmail.com> Date: Sat, 2 Feb 2019 19:01:18 +0100 Subject: [PATCH 03/32] Allow most kinds of characters in URL query (fixes #8408) (#8447) * Allow unicode characters in URL query strings Fixes #8408 * Alternative approach to unicode support in urls Adds PoC/idea to approch this problem. --- app/lib/formatter.rb | 39 +++++++++++++++++++++++++++++++++++++- spec/lib/formatter_spec.rb | 32 ++++++++++++++++++++++++++++--- 2 files changed, 67 insertions(+), 4 deletions(-) diff --git a/app/lib/formatter.rb b/app/lib/formatter.rb index 05fd9eeb12..2e35871696 100644 --- a/app/lib/formatter.rb +++ b/app/lib/formatter.rb @@ -99,7 +99,7 @@ class Formatter end def encode_and_link_urls(html, accounts = nil, options = {}) - entities = Extractor.extract_entities_with_indices(html, extract_url_without_protocol: false) + entities = utf8_friendly_extractor(html, extract_url_without_protocol: false) if accounts.is_a?(Hash) options = accounts @@ -199,6 +199,43 @@ class Formatter result.flatten.join end + def utf8_friendly_extractor(text, options = {}) + old_to_new_index = [0] + + escaped = text.chars.map do |c| + output = c.ord.to_s(16).length > 2 ? CGI.escape(c) : c + old_to_new_index << old_to_new_index.last + output.length + output + end.join + + # Note: I couldn't obtain list_slug with @user/list-name format + # for mention so this requires additional check + special = Extractor.extract_entities_with_indices(escaped, options).map do |extract| + # exactly one of :url, :hashtag, :screen_name, :cashtag keys is present + key = (extract.keys & [:url, :hashtag, :screen_name, :cashtag]).first + + new_indices = [ + old_to_new_index.find_index(extract[:indices].first), + old_to_new_index.find_index(extract[:indices].last), + ] + + has_prefix_char = [:hashtag, :screen_name, :cashtag].include?(key) + value_indices = [ + new_indices.first + (has_prefix_char ? 1 : 0), # account for #, @ or $ + new_indices.last - 1, + ] + + next extract.merge( + :indices => new_indices, + key => text[value_indices.first..value_indices.last] + ) + end + + standard = Extractor.extract_entities_with_indices(text, options) + + Extractor.remove_overlapping_entities(special + standard) + end + def link_to_url(entity, options = {}) url = Addressable::URI.parse(entity[:url]) html_attrs = { target: '_blank', rel: 'nofollow noopener' } diff --git a/spec/lib/formatter_spec.rb b/spec/lib/formatter_spec.rb index 0c1efe7c3c..9872d37567 100644 --- a/spec/lib/formatter_spec.rb +++ b/spec/lib/formatter_spec.rb @@ -74,10 +74,36 @@ RSpec.describe Formatter do end context 'given a URL with a query string' do - let(:text) { 'https://www.ruby-toolbox.com/search?utf8=%E2%9C%93&q=autolink' } + context 'with escaped unicode character' do + let(:text) { 'https://www.ruby-toolbox.com/search?utf8=%E2%9C%93&q=autolink' } - it 'matches the full URL' do - is_expected.to include 'href="https://www.ruby-toolbox.com/search?utf8=%E2%9C%93&q=autolink"' + it 'matches the full URL' do + is_expected.to include 'href="https://www.ruby-toolbox.com/search?utf8=%E2%9C%93&q=autolink"' + end + end + + context 'with unicode character' do + let(:text) { 'https://www.ruby-toolbox.com/search?utf8=✓&q=autolink' } + + it 'matches the full URL' do + is_expected.to include 'href="https://www.ruby-toolbox.com/search?utf8=✓&q=autolink"' + end + end + + context 'with unicode character at the end' do + let(:text) { 'https://www.ruby-toolbox.com/search?utf8=✓' } + + it 'matches the full URL' do + is_expected.to include 'href="https://www.ruby-toolbox.com/search?utf8=✓"' + end + end + + context 'with escaped and not escaped unicode characters' do + let(:text) { 'https://www.ruby-toolbox.com/search?utf8=%E2%9C%93&utf81=✓&q=autolink' } + + it 'preserves escaped unicode characters' do + is_expected.to include 'href="https://www.ruby-toolbox.com/search?utf8=%E2%9C%93&utf81=✓&q=autolink"' + end end end From bcfff65195f557dc086470f91b4c90b15c004cf7 Mon Sep 17 00:00:00 2001 From: ysksn <bluewhale1982@gmail.com> Date: Sun, 3 Feb 2019 03:11:38 +0900 Subject: [PATCH 04/32] Create Redisable#redis (#9633) * Create Redisable * Use #redis instead of Redis.current --- app/lib/activity_tracker.rb | 6 ++---- app/lib/activitypub/activity.rb | 5 +---- app/lib/feed_manager.rb | 9 +++------ app/lib/ostatus/activity/base.rb | 6 ++---- app/lib/potential_friendship_tracker.rb | 8 ++------ app/models/concerns/redisable.rb | 11 +++++++++++ app/models/feed.rb | 6 ++---- app/models/trending_tags.rb | 6 ++---- app/services/batched_remove_status_service.rb | 5 +---- app/services/follow_service.rb | 6 ++---- app/services/post_status_service.rb | 6 ++---- app/services/remove_status_service.rb | 19 ++++++++----------- .../scheduler/feed_cleanup_scheduler.rb | 5 +---- 13 files changed, 39 insertions(+), 59 deletions(-) create mode 100644 app/models/concerns/redisable.rb diff --git a/app/lib/activity_tracker.rb b/app/lib/activity_tracker.rb index 5b49726747..ae3c11b6af 100644 --- a/app/lib/activity_tracker.rb +++ b/app/lib/activity_tracker.rb @@ -4,6 +4,8 @@ class ActivityTracker EXPIRE_AFTER = 90.days.seconds class << self + include Redisable + def increment(prefix) key = [prefix, current_week].join(':') @@ -20,10 +22,6 @@ class ActivityTracker private - def redis - Redis.current - end - def current_week Time.zone.today.cweek end diff --git a/app/lib/activitypub/activity.rb b/app/lib/activitypub/activity.rb index 87318fb1c5..919678618a 100644 --- a/app/lib/activitypub/activity.rb +++ b/app/lib/activitypub/activity.rb @@ -2,6 +2,7 @@ class ActivityPub::Activity include JsonLdHelper + include Redisable def initialize(json, account, **options) @json = json @@ -70,10 +71,6 @@ class ActivityPub::Activity @object_uri ||= value_or_id(@object) end - def redis - Redis.current - end - def distribute(status) crawl_links(status) diff --git a/app/lib/feed_manager.rb b/app/lib/feed_manager.rb index f99df33e54..d77cdb3a3c 100644 --- a/app/lib/feed_manager.rb +++ b/app/lib/feed_manager.rb @@ -4,6 +4,7 @@ require 'singleton' class FeedManager include Singleton + include Redisable MAX_ITEMS = 400 @@ -35,7 +36,7 @@ class FeedManager def unpush_from_home(account, status) return false unless remove_from_feed(:home, account.id, status) - Redis.current.publish("timeline:#{account.id}", Oj.dump(event: :delete, payload: status.id.to_s)) + redis.publish("timeline:#{account.id}", Oj.dump(event: :delete, payload: status.id.to_s)) true end @@ -53,7 +54,7 @@ class FeedManager def unpush_from_list(list, status) return false unless remove_from_feed(:list, list.id, status) - Redis.current.publish("timeline:list:#{list.id}", Oj.dump(event: :delete, payload: status.id.to_s)) + redis.publish("timeline:list:#{list.id}", Oj.dump(event: :delete, payload: status.id.to_s)) true end @@ -142,10 +143,6 @@ class FeedManager private - def redis - Redis.current - end - def push_update_required?(timeline_id) redis.exists("subscribed:#{timeline_id}") end diff --git a/app/lib/ostatus/activity/base.rb b/app/lib/ostatus/activity/base.rb index c5933f3adf..db70f19980 100644 --- a/app/lib/ostatus/activity/base.rb +++ b/app/lib/ostatus/activity/base.rb @@ -1,6 +1,8 @@ # frozen_string_literal: true class OStatus::Activity::Base + include Redisable + def initialize(xml, account = nil, **options) @xml = xml @account = account @@ -66,8 +68,4 @@ class OStatus::Activity::Base Status.find_by(uri: uri) end end - - def redis - Redis.current - end end diff --git a/app/lib/potential_friendship_tracker.rb b/app/lib/potential_friendship_tracker.rb index 017a9748d5..188aa4a275 100644 --- a/app/lib/potential_friendship_tracker.rb +++ b/app/lib/potential_friendship_tracker.rb @@ -11,6 +11,8 @@ class PotentialFriendshipTracker }.freeze class << self + include Redisable + def record(account_id, target_account_id, action) return if account_id == target_account_id @@ -31,11 +33,5 @@ class PotentialFriendshipTracker return [] if account_ids.empty? Account.searchable.where(id: account_ids) end - - private - - def redis - Redis.current - end end end diff --git a/app/models/concerns/redisable.rb b/app/models/concerns/redisable.rb new file mode 100644 index 0000000000..c6cf973596 --- /dev/null +++ b/app/models/concerns/redisable.rb @@ -0,0 +1,11 @@ +# frozen_string_literal: true + +module Redisable + extend ActiveSupport::Concern + + private + + def redis + Redis.current + end +end diff --git a/app/models/feed.rb b/app/models/feed.rb index 5bce88f255..0e8943ff86 100644 --- a/app/models/feed.rb +++ b/app/models/feed.rb @@ -1,6 +1,8 @@ # frozen_string_literal: true class Feed + include Redisable + def initialize(type, id) @type = type @id = id @@ -27,8 +29,4 @@ class Feed def key FeedManager.instance.key(@type, @id) end - - def redis - Redis.current - end end diff --git a/app/models/trending_tags.rb b/app/models/trending_tags.rb index 3a8be21649..148535c212 100644 --- a/app/models/trending_tags.rb +++ b/app/models/trending_tags.rb @@ -7,6 +7,8 @@ class TrendingTags THRESHOLD = 5 class << self + include Redisable + def record_use!(tag, account, at_time = Time.now.utc) return if disallowed_hashtags.include?(tag.name) || account.silenced? || account.bot? @@ -59,9 +61,5 @@ class TrendingTags @disallowed_hashtags = @disallowed_hashtags.split(' ') if @disallowed_hashtags.is_a? String @disallowed_hashtags = @disallowed_hashtags.map(&:downcase) end - - def redis - Redis.current - end end end diff --git a/app/services/batched_remove_status_service.rb b/app/services/batched_remove_status_service.rb index 2e61904fc7..cd3b14e08e 100644 --- a/app/services/batched_remove_status_service.rb +++ b/app/services/batched_remove_status_service.rb @@ -2,6 +2,7 @@ class BatchedRemoveStatusService < BaseService include StreamEntryRenderer + include Redisable # Delete given statuses and reblogs of them # Dispatch PuSH updates of the deleted statuses, but only local ones @@ -109,10 +110,6 @@ class BatchedRemoveStatusService < BaseService end end - def redis - Redis.current - end - def build_xml(stream_entry) return @activity_xml[stream_entry.id] if @activity_xml.key?(stream_entry.id) diff --git a/app/services/follow_service.rb b/app/services/follow_service.rb index 9d36a1449d..92d8c864a7 100644 --- a/app/services/follow_service.rb +++ b/app/services/follow_service.rb @@ -1,6 +1,8 @@ # frozen_string_literal: true class FollowService < BaseService + include Redisable + # Follow a remote user, notify remote user about the follow # @param [Account] source_account From which to follow # @param [String, Account] uri User URI to follow in the form of username@domain (or account record) @@ -67,10 +69,6 @@ class FollowService < BaseService follow end - def redis - Redis.current - end - def build_follow_request_xml(follow_request) OStatus::AtomSerializer.render(OStatus::AtomSerializer.new.follow_request_salmon(follow_request)) end diff --git a/app/services/post_status_service.rb b/app/services/post_status_service.rb index 9959bb1fbf..686b10c581 100644 --- a/app/services/post_status_service.rb +++ b/app/services/post_status_service.rb @@ -1,6 +1,8 @@ # frozen_string_literal: true class PostStatusService < BaseService + include Redisable + MIN_SCHEDULE_OFFSET = 5.minutes.freeze # Post a text status update, fetch and notify remote users mentioned @@ -110,10 +112,6 @@ class PostStatusService < BaseService ProcessHashtagsService.new end - def redis - Redis.current - end - def scheduled? @scheduled_at.present? end diff --git a/app/services/remove_status_service.rb b/app/services/remove_status_service.rb index 11d28e783d..28c5224b01 100644 --- a/app/services/remove_status_service.rb +++ b/app/services/remove_status_service.rb @@ -2,6 +2,7 @@ class RemoveStatusService < BaseService include StreamEntryRenderer + include Redisable def call(status, **options) @payload = Oj.dump(event: :delete, payload: status.id.to_s) @@ -55,7 +56,7 @@ class RemoveStatusService < BaseService def remove_from_affected @mentions.map(&:account).select(&:local?).each do |account| - Redis.current.publish("timeline:#{account.id}", @payload) + redis.publish("timeline:#{account.id}", @payload) end end @@ -133,26 +134,22 @@ class RemoveStatusService < BaseService return unless @status.public_visibility? @tags.each do |hashtag| - Redis.current.publish("timeline:hashtag:#{hashtag}", @payload) - Redis.current.publish("timeline:hashtag:#{hashtag}:local", @payload) if @status.local? + redis.publish("timeline:hashtag:#{hashtag}", @payload) + redis.publish("timeline:hashtag:#{hashtag}:local", @payload) if @status.local? end end def remove_from_public return unless @status.public_visibility? - Redis.current.publish('timeline:public', @payload) - Redis.current.publish('timeline:public:local', @payload) if @status.local? + redis.publish('timeline:public', @payload) + redis.publish('timeline:public:local', @payload) if @status.local? end def remove_from_media return unless @status.public_visibility? - Redis.current.publish('timeline:public:media', @payload) - Redis.current.publish('timeline:public:local:media', @payload) if @status.local? - end - - def redis - Redis.current + redis.publish('timeline:public:media', @payload) + redis.publish('timeline:public:local:media', @payload) if @status.local? end end diff --git a/app/workers/scheduler/feed_cleanup_scheduler.rb b/app/workers/scheduler/feed_cleanup_scheduler.rb index cd22734180..bf5e207570 100644 --- a/app/workers/scheduler/feed_cleanup_scheduler.rb +++ b/app/workers/scheduler/feed_cleanup_scheduler.rb @@ -2,6 +2,7 @@ class Scheduler::FeedCleanupScheduler include Sidekiq::Worker + include Redisable sidekiq_options unique: :until_executed, retry: 0 @@ -57,8 +58,4 @@ class Scheduler::FeedCleanupScheduler def feed_manager FeedManager.instance end - - def redis - Redis.current - end end From ed3011061896dfc4819d517a0f4f4947e56feac4 Mon Sep 17 00:00:00 2001 From: ThibG <thib@sitedethib.com> Date: Sat, 2 Feb 2019 19:18:15 +0100 Subject: [PATCH 05/32] Make displaying application used to toot opt-in (#9897) * Make storing and displaying application used to toot opt-in * Revert to storing application info, and display it to the author via API --- app/controllers/settings/preferences_controller.rb | 1 + app/lib/user_settings_decorator.rb | 5 +++++ app/models/account.rb | 1 + app/models/user.rb | 6 +++++- app/serializers/rest/status_serializer.rb | 6 +++++- app/views/settings/preferences/show.html.haml | 3 +++ app/views/stream_entries/_detailed_status.html.haml | 2 +- config/locales/simple_form.en.yml | 2 ++ config/settings.yml | 1 + 9 files changed, 24 insertions(+), 3 deletions(-) diff --git a/app/controllers/settings/preferences_controller.rb b/app/controllers/settings/preferences_controller.rb index 41df3bde2f..90967635da 100644 --- a/app/controllers/settings/preferences_controller.rb +++ b/app/controllers/settings/preferences_controller.rb @@ -48,6 +48,7 @@ class Settings::PreferencesController < Settings::BaseController :setting_theme, :setting_hide_network, :setting_aggregate_reblogs, + :setting_show_application, notification_emails: %i(follow follow_request reblog favourite mention digest report), interactions: %i(must_be_follower must_be_following) ) diff --git a/app/lib/user_settings_decorator.rb b/app/lib/user_settings_decorator.rb index 19b8544103..daeb3d936f 100644 --- a/app/lib/user_settings_decorator.rb +++ b/app/lib/user_settings_decorator.rb @@ -32,6 +32,7 @@ class UserSettingsDecorator user.settings['theme'] = theme_preference if change?('setting_theme') user.settings['hide_network'] = hide_network_preference if change?('setting_hide_network') user.settings['aggregate_reblogs'] = aggregate_reblogs_preference if change?('setting_aggregate_reblogs') + user.settings['show_application'] = show_application_preference if change?('setting_show_application') end def merged_notification_emails @@ -90,6 +91,10 @@ class UserSettingsDecorator boolean_cast_setting 'setting_hide_network' end + def show_application_preference + boolean_cast_setting 'setting_show_application' + end + def theme_preference settings['setting_theme'] end diff --git a/app/models/account.rb b/app/models/account.rb index 11a3c21fe4..12d7a747ee 100644 --- a/app/models/account.rb +++ b/app/models/account.rb @@ -109,6 +109,7 @@ class Account < ApplicationRecord :staff?, :locale, :hides_network?, + :shows_application?, to: :user, prefix: true, allow_nil: true diff --git a/app/models/user.rb b/app/models/user.rb index fdd2741c1d..7432e3da88 100644 --- a/app/models/user.rb +++ b/app/models/user.rb @@ -100,7 +100,7 @@ class User < ApplicationRecord delegate :auto_play_gif, :default_sensitive, :unfollow_modal, :boost_modal, :delete_modal, :reduce_motion, :system_font_ui, :noindex, :theme, :display_media, :hide_network, - :expand_spoilers, :default_language, :aggregate_reblogs, to: :settings, prefix: :setting, allow_nil: false + :expand_spoilers, :default_language, :aggregate_reblogs, :show_application, to: :settings, prefix: :setting, allow_nil: false attr_reader :invite_code @@ -244,6 +244,10 @@ class User < ApplicationRecord @aggregates_reblogs ||= settings.aggregate_reblogs end + def shows_application? + @shows_application ||= settings.shows_application + end + def token_for_app(a) return nil if a.nil? || a.owner != self Doorkeeper::AccessToken diff --git a/app/serializers/rest/status_serializer.rb b/app/serializers/rest/status_serializer.rb index bfc2d78b41..66e19be560 100644 --- a/app/serializers/rest/status_serializer.rb +++ b/app/serializers/rest/status_serializer.rb @@ -12,7 +12,7 @@ class REST::StatusSerializer < ActiveModel::Serializer attribute :pinned, if: :pinnable? belongs_to :reblog, serializer: REST::StatusSerializer - belongs_to :application + belongs_to :application, if: :show_application? belongs_to :account, serializer: REST::AccountSerializer has_many :media_attachments, serializer: REST::MediaAttachmentSerializer @@ -38,6 +38,10 @@ class REST::StatusSerializer < ActiveModel::Serializer !current_user.nil? end + def show_application? + object.account.user_shows_application? || (current_user? && current_user.account_id == object.account_id) + end + def visibility # This visibility is masked behind "private" # to avoid API changes because there are no diff --git a/app/views/settings/preferences/show.html.haml b/app/views/settings/preferences/show.html.haml index a2c61c9a68..3cb91631ed 100644 --- a/app/views/settings/preferences/show.html.haml +++ b/app/views/settings/preferences/show.html.haml @@ -34,6 +34,9 @@ .fields-group = f.input :setting_hide_network, as: :boolean, wrapper: :with_label + .fields-group + = f.input :setting_show_application, as: :boolean, wrapper: :with_label + %hr#settings_web/ .fields-row diff --git a/app/views/stream_entries/_detailed_status.html.haml b/app/views/stream_entries/_detailed_status.html.haml index 18265e1107..e123d657fa 100644 --- a/app/views/stream_entries/_detailed_status.html.haml +++ b/app/views/stream_entries/_detailed_status.html.haml @@ -39,7 +39,7 @@ = link_to TagManager.instance.url_for(status), class: 'detailed-status__datetime u-url u-uid', target: stream_link_target, rel: 'noopener' do %time.formatted{ datetime: status.created_at.iso8601, title: l(status.created_at) }= l(status.created_at) · - - if status.application + - if status.application && @account.user&.setting_show_application - if status.application.website.blank? %strong.detailed-status__application= status.application.name - else diff --git a/config/locales/simple_form.en.yml b/config/locales/simple_form.en.yml index 4363c59e42..325114755b 100644 --- a/config/locales/simple_form.en.yml +++ b/config/locales/simple_form.en.yml @@ -33,6 +33,7 @@ en: setting_display_media_show_all: Always show media marked as sensitive setting_hide_network: Who you follow and who follows you will not be shown on your profile setting_noindex: Affects your public profile and status pages + setting_show_application: The application you use to toot will be displayed in the detailed view of your toots setting_theme: Affects how Mastodon looks when you're logged in from any device. username: Your username will be unique on %{domain} whole_word: When the keyword or phrase is alphanumeric only, it will only be applied if it matches the whole word @@ -100,6 +101,7 @@ en: setting_hide_network: Hide your network setting_noindex: Opt-out of search engine indexing setting_reduce_motion: Reduce motion in animations + setting_show_application: Disclose application used to send toots setting_system_font_ui: Use system's default font setting_theme: Site theme setting_unfollow_modal: Show confirmation dialog before unfollowing someone diff --git a/config/settings.yml b/config/settings.yml index 4f7c2c8f32..2cf286a9e4 100644 --- a/config/settings.yml +++ b/config/settings.yml @@ -26,6 +26,7 @@ defaults: &defaults expand_spoilers: false preview_sensitive_media: false reduce_motion: false + show_application: false system_font_ui: false noindex: false theme: 'default' From 582f86ab32b4ce2b9d242cf6f5c3c6c0dce4e144 Mon Sep 17 00:00:00 2001 From: Jeong Arm <kjwonmail@gmail.com> Date: Sun, 3 Feb 2019 03:37:16 +0900 Subject: [PATCH 06/32] Re-enable ignored translations that is needed (#7842) Some of strings should be translated. --- config/i18n-tasks.yml | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/config/i18n-tasks.yml b/config/i18n-tasks.yml index eec8b6dbec..1bcac154b1 100644 --- a/config/i18n-tasks.yml +++ b/config/i18n-tasks.yml @@ -35,11 +35,8 @@ ignore_missing: - 'activemodel.errors.*' - 'activerecord.attributes.*' - 'activerecord.errors.*' - - '{devise,pagination,doorkeeper}.*' + - '{pagination,doorkeeper}.*' - '{date,datetime,time,number}.*' - - 'simple_form.{yes,no}' - - 'simple_form.{placeholders,hints,labels}.*' - - 'simple_form.{error_notification,required}.:' - 'errors.messages.*' - 'activerecord.errors.models.doorkeeper/*' - 'sessions.{browsers,platforms}.*' From 750c67660de753065ec160b4e389ba0dda2f81cc Mon Sep 17 00:00:00 2001 From: tmm576 <tmm576@users.noreply.github.com> Date: Sat, 2 Feb 2019 14:22:05 -0500 Subject: [PATCH 07/32] Allow multiple files upload through web UI, including drag & drop (#9856) * Allow drag and drop uploads of multiple files to compose * Calculate aggregate upload progress for single action * Allow multiple uploads to compose through traditional input, consolidate update file limit logic, provide file limit feedback --- app/javascript/mastodon/actions/alerts.js | 4 +-- app/javascript/mastodon/actions/compose.js | 36 ++++++++++++++----- .../compose/components/upload_button.js | 2 +- app/javascript/mastodon/features/ui/index.js | 2 +- .../mastodon/locales/defaultMessages.json | 11 +++++- app/javascript/mastodon/locales/en.json | 1 + 6 files changed, 42 insertions(+), 14 deletions(-) diff --git a/app/javascript/mastodon/actions/alerts.js b/app/javascript/mastodon/actions/alerts.js index 3f5d7ef46b..50cd48a9ed 100644 --- a/app/javascript/mastodon/actions/alerts.js +++ b/app/javascript/mastodon/actions/alerts.js @@ -22,7 +22,7 @@ export function clearAlert() { }; }; -export function showAlert(title, message) { +export function showAlert(title = messages.unexpectedTitle, message = messages.unexpectedMessage) { return { type: ALERT_SHOW, title, @@ -44,6 +44,6 @@ export function showAlertForError(error) { return showAlert(title, message); } else { console.error(error); - return showAlert(messages.unexpectedTitle, messages.unexpectedMessage); + return showAlert(); } } diff --git a/app/javascript/mastodon/actions/compose.js b/app/javascript/mastodon/actions/compose.js index a4352faaba..0be2a5cd46 100644 --- a/app/javascript/mastodon/actions/compose.js +++ b/app/javascript/mastodon/actions/compose.js @@ -8,6 +8,8 @@ import resizeImage from '../utils/resize_image'; import { importFetchedAccounts } from './importer'; import { updateTimeline } from './timelines'; import { showAlertForError } from './alerts'; +import { showAlert } from './alerts'; +import { defineMessages } from 'react-intl'; let cancelFetchComposeSuggestionsAccounts; @@ -49,6 +51,10 @@ export const COMPOSE_UPLOAD_CHANGE_REQUEST = 'COMPOSE_UPLOAD_UPDATE_REQUEST' export const COMPOSE_UPLOAD_CHANGE_SUCCESS = 'COMPOSE_UPLOAD_UPDATE_SUCCESS'; export const COMPOSE_UPLOAD_CHANGE_FAIL = 'COMPOSE_UPLOAD_UPDATE_FAIL'; +const messages = defineMessages({ + uploadErrorLimit: { id: 'upload_error.limit', defaultMessage: 'File upload limit exceeded.' }, +}); + export function changeCompose(text) { return { type: COMPOSE_CHANGE, @@ -184,20 +190,32 @@ export function submitComposeFail(error) { export function uploadCompose(files) { return function (dispatch, getState) { - if (getState().getIn(['compose', 'media_attachments']).size > 3) { + const uploadLimit = 4; + const media = getState().getIn(['compose', 'media_attachments']); + const total = Array.from(files).reduce((a, v) => a + v.size, 0); + const progress = new Array(files.length).fill(0); + + if (files.length + media.size > uploadLimit) { + dispatch(showAlert(undefined, messages.uploadErrorLimit)); return; } - dispatch(uploadComposeRequest()); - resizeImage(files[0]).then(file => { - const data = new FormData(); - data.append('file', file); + for (const [i, f] of Array.from(files).entries()) { + if (media.size + i > 3) break; - return api(getState).post('/api/v1/media', data, { - onUploadProgress: ({ loaded, total }) => dispatch(uploadComposeProgress(loaded, total)), - }).then(({ data }) => dispatch(uploadComposeSuccess(data))); - }).catch(error => dispatch(uploadComposeFail(error))); + resizeImage(f).then(file => { + const data = new FormData(); + data.append('file', file); + + return api(getState).post('/api/v1/media', data, { + onUploadProgress: function({ loaded }){ + progress[i] = loaded; + dispatch(uploadComposeProgress(progress.reduce((a, v) => a + v, 0), total)); + }, + }).then(({ data }) => dispatch(uploadComposeSuccess(data))); + }).catch(error => dispatch(uploadComposeFail(error))); + }; }; }; diff --git a/app/javascript/mastodon/features/compose/components/upload_button.js b/app/javascript/mastodon/features/compose/components/upload_button.js index b6fe770ead..db55ad70bf 100644 --- a/app/javascript/mastodon/features/compose/components/upload_button.js +++ b/app/javascript/mastodon/features/compose/components/upload_button.js @@ -63,7 +63,7 @@ class UploadButton extends ImmutablePureComponent { key={resetFileKey} ref={this.setRef} type='file' - multiple={false} + multiple accept={acceptContentTypes.toArray().join(',')} onChange={this.handleChange} disabled={disabled} diff --git a/app/javascript/mastodon/features/ui/index.js b/app/javascript/mastodon/features/ui/index.js index f01c2bf247..93e45678f2 100644 --- a/app/javascript/mastodon/features/ui/index.js +++ b/app/javascript/mastodon/features/ui/index.js @@ -263,7 +263,7 @@ class UI extends React.PureComponent { this.setState({ draggingOver: false }); this.dragTargets = []; - if (e.dataTransfer && e.dataTransfer.files.length === 1) { + if (e.dataTransfer && e.dataTransfer.files.length >= 1) { this.props.dispatch(uploadCompose(e.dataTransfer.files)); } } diff --git a/app/javascript/mastodon/locales/defaultMessages.json b/app/javascript/mastodon/locales/defaultMessages.json index 6ac8160ba4..89d4e6bbaa 100644 --- a/app/javascript/mastodon/locales/defaultMessages.json +++ b/app/javascript/mastodon/locales/defaultMessages.json @@ -12,6 +12,15 @@ ], "path": "app/javascript/mastodon/actions/alerts.json" }, + { + "descriptors": [ + { + "defaultMessage": "File upload limit exceeded.", + "id": "upload_error.limit" + } + ], + "path": "app/javascript/mastodon/actions/compose.json" + }, { "descriptors": [ { @@ -2285,4 +2294,4 @@ ], "path": "app/javascript/mastodon/features/video/index.json" } -] \ No newline at end of file +] diff --git a/app/javascript/mastodon/locales/en.json b/app/javascript/mastodon/locales/en.json index 1dbf280222..e840b17d84 100644 --- a/app/javascript/mastodon/locales/en.json +++ b/app/javascript/mastodon/locales/en.json @@ -342,6 +342,7 @@ "ui.beforeunload": "Your draft will be lost if you leave Mastodon.", "upload_area.title": "Drag & drop to upload", "upload_button.label": "Add media (JPEG, PNG, GIF, WebM, MP4, MOV)", + "upload_error.limit": "File upload limit exceeded.", "upload_form.description": "Describe for the visually impaired", "upload_form.focus": "Change preview", "upload_form.undo": "Delete", From 7e33d6d192128238768c976ba108a374ad073aa2 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <dependabot[bot]@users.noreply.github.com> Date: Sun, 3 Feb 2019 03:40:22 +0100 Subject: [PATCH 08/32] Bump httplog from 1.2.0 to 1.2.1 (#9942) Bumps [httplog](https://github.com/trusche/httplog) from 1.2.0 to 1.2.1. - [Release notes](https://github.com/trusche/httplog/releases) - [Changelog](https://github.com/trusche/httplog/blob/master/CHANGELOG.md) - [Commits](https://github.com/trusche/httplog/compare/v1.2.0...v1.2.1) Signed-off-by: dependabot[bot] <support@dependabot.com> --- Gemfile.lock | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Gemfile.lock b/Gemfile.lock index 8f661fc242..3b488710b0 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -266,7 +266,7 @@ GEM domain_name (~> 0.5) http-form_data (2.1.1) http_accept_language (2.1.1) - httplog (1.2.0) + httplog (1.2.1) rack (>= 1.0) rainbow (>= 2.0.0) i18n (1.5.3) From c5071f2d787e81251c2b3111074b20d94773ee44 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <dependabot[bot]@users.noreply.github.com> Date: Sun, 3 Feb 2019 03:40:40 +0100 Subject: [PATCH 09/32] Bump capybara from 3.12.0 to 3.13.2 (#9935) Bumps [capybara](https://github.com/teamcapybara/capybara) from 3.12.0 to 3.13.2. - [Release notes](https://github.com/teamcapybara/capybara/releases) - [Changelog](https://github.com/teamcapybara/capybara/blob/master/History.md) - [Commits](https://github.com/teamcapybara/capybara/compare/3.12.0...3.13.2) Signed-off-by: dependabot[bot] <support@dependabot.com> --- Gemfile | 2 +- Gemfile.lock | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/Gemfile b/Gemfile index 8266488f89..44cae11ff2 100644 --- a/Gemfile +++ b/Gemfile @@ -107,7 +107,7 @@ group :production, :test do end group :test do - gem 'capybara', '~> 3.12' + gem 'capybara', '~> 3.13' gem 'climate_control', '~> 0.2' gem 'faker', '~> 1.9' gem 'microformats', '~> 4.0' diff --git a/Gemfile.lock b/Gemfile.lock index 3b488710b0..5bec39a01f 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -126,7 +126,7 @@ GEM sshkit (~> 1.3) capistrano-yarn (2.0.2) capistrano (~> 3.0) - capybara (3.12.0) + capybara (3.13.2) addressable mini_mime (>= 0.1.3) nokogiri (~> 1.8) @@ -669,7 +669,7 @@ DEPENDENCIES capistrano-rails (~> 1.4) capistrano-rbenv (~> 2.1) capistrano-yarn (~> 2.0) - capybara (~> 3.12) + capybara (~> 3.13) charlock_holmes (~> 0.7.6) chewy (~> 5.0) cld3 (~> 3.2.3) From d14c276e58f0f223b0e4889d342a948c961081b2 Mon Sep 17 00:00:00 2001 From: Eugen Rochko <eugen@zeonfederated.com> Date: Sun, 3 Feb 2019 03:59:51 +0100 Subject: [PATCH 10/32] Add option to overwrite imported data (#9962) * Add option to overwrite imported data Fix #7465 * Add import for domain blocks --- app/models/account_domain_block.rb | 1 + app/models/concerns/domain_normalizable.rb | 2 +- app/models/export.rb | 1 + app/models/import.rb | 14 ++- app/services/import_service.rb | 90 +++++++++++++++++++ app/views/settings/imports/show.html.haml | 7 +- app/workers/import/relationship_worker.rb | 8 +- app/workers/import_worker.rb | 38 +------- config/locales/en.yml | 6 ++ ...20190201012802_add_overwrite_to_imports.rb | 17 ++++ db/schema.rb | 3 +- .../concerns/account_interactions_spec.rb | 4 +- 12 files changed, 148 insertions(+), 43 deletions(-) create mode 100644 app/services/import_service.rb create mode 100644 db/migrate/20190201012802_add_overwrite_to_imports.rb diff --git a/app/models/account_domain_block.rb b/app/models/account_domain_block.rb index e352000c3a..7c0d60379e 100644 --- a/app/models/account_domain_block.rb +++ b/app/models/account_domain_block.rb @@ -12,6 +12,7 @@ class AccountDomainBlock < ApplicationRecord include Paginable + include DomainNormalizable belongs_to :account validates :domain, presence: true, uniqueness: { scope: :account_id } diff --git a/app/models/concerns/domain_normalizable.rb b/app/models/concerns/domain_normalizable.rb index dff3e5414f..fb84058fc2 100644 --- a/app/models/concerns/domain_normalizable.rb +++ b/app/models/concerns/domain_normalizable.rb @@ -10,6 +10,6 @@ module DomainNormalizable private def normalize_domain - self.domain = TagManager.instance.normalize_domain(domain) + self.domain = TagManager.instance.normalize_domain(domain&.strip) end end diff --git a/app/models/export.rb b/app/models/export.rb index a2520e9c24..fc4bb69648 100644 --- a/app/models/export.rb +++ b/app/models/export.rb @@ -1,4 +1,5 @@ # frozen_string_literal: true + require 'csv' class Export diff --git a/app/models/import.rb b/app/models/import.rb index 55e970b0d8..a7a0d80657 100644 --- a/app/models/import.rb +++ b/app/models/import.rb @@ -13,20 +13,30 @@ # data_file_size :integer # data_updated_at :datetime # account_id :bigint(8) not null +# overwrite :boolean default(FALSE), not null # class Import < ApplicationRecord - FILE_TYPES = ['text/plain', 'text/csv'].freeze + FILE_TYPES = %w(text/plain text/csv).freeze + MODES = %i(merge overwrite).freeze self.inheritance_column = false belongs_to :account - enum type: [:following, :blocking, :muting] + enum type: [:following, :blocking, :muting, :domain_blocking] validates :type, presence: true has_attached_file :data validates_attachment_content_type :data, content_type: FILE_TYPES validates_attachment_presence :data + + def mode + overwrite? ? :overwrite : :merge + end + + def mode=(str) + self.overwrite = str.to_sym == :overwrite + end end diff --git a/app/services/import_service.rb b/app/services/import_service.rb new file mode 100644 index 0000000000..3f558626e6 --- /dev/null +++ b/app/services/import_service.rb @@ -0,0 +1,90 @@ +# frozen_string_literal: true + +require 'csv' + +class ImportService < BaseService + ROWS_PROCESSING_LIMIT = 20_000 + + def call(import) + @import = import + @account = @import.account + @data = CSV.new(import_data).reject(&:blank?) + + case @import.type + when 'following' + import_follows! + when 'blocking' + import_blocks! + when 'muting' + import_mutes! + when 'domain_blocking' + import_domain_blocks! + end + end + + private + + def import_follows! + import_relationships!('follow', 'unfollow', @account.following, follow_limit) + end + + def import_blocks! + import_relationships!('block', 'unblock', @account.blocking, ROWS_PROCESSING_LIMIT) + end + + def import_mutes! + import_relationships!('mute', 'unmute', @account.muting, ROWS_PROCESSING_LIMIT) + end + + def import_domain_blocks! + items = @data.take(ROWS_PROCESSING_LIMIT).map { |row| row.first.strip } + + if @import.overwrite? + presence_hash = items.each_with_object({}) { |id, mapping| mapping[id] = true } + + @account.domain_blocks.find_each do |domain_block| + if presence_hash[domain_block.domain] + items.delete(domain_block.domain) + else + @account.unblock_domain!(domain_block.domain) + end + end + end + + items.each do |domain| + @account.block_domain!(domain) + end + + AfterAccountDomainBlockWorker.push_bulk(items) do |domain| + [@account.id, domain] + end + end + + def import_relationships!(action, undo_action, overwrite_scope, limit) + items = @data.take(limit).map { |row| row.first.strip } + + if @import.overwrite? + presence_hash = items.each_with_object({}) { |id, mapping| mapping[id] = true } + + overwrite_scope.find_each do |target_account| + if presence_hash[target_account.acct] + items.delete(target_account.acct) + else + Import::RelationshipWorker.perform_async(@account.id, target_account.acct, undo_action) + end + end + end + + Import::RelationshipWorker.push_bulk(items) do |acct| + [@account.id, acct, action] + end + end + + def import_data + Paperclip.io_adapters.for(@import.data).read + end + + def follow_limit + FollowLimitValidator.limit_for_account(@account) + end +end diff --git a/app/views/settings/imports/show.html.haml b/app/views/settings/imports/show.html.haml index 4512fc714d..7bb4beb01c 100644 --- a/app/views/settings/imports/show.html.haml +++ b/app/views/settings/imports/show.html.haml @@ -5,8 +5,11 @@ .field-group = f.input :type, collection: Import.types.keys, wrapper: :with_block_label, include_blank: false, label_method: lambda { |type| I18n.t("imports.types.#{type}") }, hint: t('imports.preface') - .field-group - = f.input :data, wrapper: :with_block_label, hint: t('simple_form.hints.imports.data') + .fields-row + .fields-group.fields-row__column.fields-row__column-6 + = f.input :data, wrapper: :with_block_label, hint: t('simple_form.hints.imports.data') + .fields-group.fields-row__column.fields-row__column-6 + = f.input :mode, as: :radio_buttons, collection: Import::MODES, label_method: lambda { |mode| safe_join([I18n.t("imports.modes.#{mode}"), content_tag(:span, I18n.t("imports.modes.#{mode}_long"), class: 'hint')]) }, collection_wrapper_tag: 'ul', item_wrapper_tag: 'li' .actions = f.button :button, t('imports.upload'), type: :submit diff --git a/app/workers/import/relationship_worker.rb b/app/workers/import/relationship_worker.rb index 1dd8bf8fbb..e9db20a463 100644 --- a/app/workers/import/relationship_worker.rb +++ b/app/workers/import/relationship_worker.rb @@ -13,11 +13,17 @@ class Import::RelationshipWorker case relationship when 'follow' - FollowService.new.call(from_account, target_account.acct) + FollowService.new.call(from_account, target_account) + when 'unfollow' + UnfollowService.new.call(from_account, target_account) when 'block' BlockService.new.call(from_account, target_account) + when 'unblock' + UnblockService.new.call(from_account, target_account) when 'mute' MuteService.new.call(from_account, target_account) + when 'unmute' + UnmuteService.new.call(from_account, target_account) end rescue ActiveRecord::RecordNotFound true diff --git a/app/workers/import_worker.rb b/app/workers/import_worker.rb index aeb221cf68..dfa71b29ec 100644 --- a/app/workers/import_worker.rb +++ b/app/workers/import_worker.rb @@ -1,44 +1,14 @@ # frozen_string_literal: true -require 'csv' - class ImportWorker include Sidekiq::Worker sidekiq_options queue: 'pull', retry: false - attr_reader :import - def perform(import_id) - @import = Import.find(import_id) - - Import::RelationshipWorker.push_bulk(import_rows) do |row| - [@import.account_id, row.first, relationship_type] - end - - @import.destroy - end - - private - - def import_contents - Paperclip.io_adapters.for(@import.data).read - end - - def relationship_type - case @import.type - when 'following' - 'follow' - when 'blocking' - 'block' - when 'muting' - 'mute' - end - end - - def import_rows - rows = CSV.new(import_contents).reject(&:blank?) - rows = rows.take(FollowLimitValidator.limit_for_account(@import.account)) if @import.type == 'following' - rows + import = Import.find(import_id) + ImportService.new.call(import) + ensure + import&.destroy end end diff --git a/config/locales/en.yml b/config/locales/en.yml index c7ef36598a..f23f867e5c 100644 --- a/config/locales/en.yml +++ b/config/locales/en.yml @@ -628,10 +628,16 @@ en: one: Something isn't quite right yet! Please review the error below other: Something isn't quite right yet! Please review %{count} errors below imports: + modes: + merge: Merge + merge_long: Keep existing records and add new ones + overwrite: Overwrite + overwrite_long: Replace current records with the new ones preface: You can import data that you have exported from another instance, such as a list of the people you are following or blocking. success: Your data was successfully uploaded and will now be processed in due time types: blocking: Blocking list + domain_blocking: Domain blocking list following: Following list muting: Muting list upload: Upload diff --git a/db/migrate/20190201012802_add_overwrite_to_imports.rb b/db/migrate/20190201012802_add_overwrite_to_imports.rb new file mode 100644 index 0000000000..89b262cc72 --- /dev/null +++ b/db/migrate/20190201012802_add_overwrite_to_imports.rb @@ -0,0 +1,17 @@ +require Rails.root.join('lib', 'mastodon', 'migration_helpers') + +class AddOverwriteToImports < ActiveRecord::Migration[5.2] + include Mastodon::MigrationHelpers + + disable_ddl_transaction! + + def up + safety_assured do + add_column_with_default :imports, :overwrite, :boolean, default: false, allow_null: false + end + end + + def down + remove_column :imports, :overwrite, :boolean + end +end diff --git a/db/schema.rb b/db/schema.rb index 3487adf088..44e00df405 100644 --- a/db/schema.rb +++ b/db/schema.rb @@ -10,7 +10,7 @@ # # It's strongly recommended that you check this file into your version control system. -ActiveRecord::Schema.define(version: 2019_01_17_114553) do +ActiveRecord::Schema.define(version: 2019_02_01_012802) do # These are extensions that must be enabled in order to support this database enable_extension "plpgsql" @@ -290,6 +290,7 @@ ActiveRecord::Schema.define(version: 2019_01_17_114553) do t.integer "data_file_size" t.datetime "data_updated_at" t.bigint "account_id", null: false + t.boolean "overwrite", default: false, null: false end create_table "invites", force: :cascade do |t| diff --git a/spec/models/concerns/account_interactions_spec.rb b/spec/models/concerns/account_interactions_spec.rb index 8df52b7706..e8ef61f66c 100644 --- a/spec/models/concerns/account_interactions_spec.rb +++ b/spec/models/concerns/account_interactions_spec.rb @@ -237,9 +237,9 @@ describe AccountInteractions do end describe '#block_domain!' do - let(:domain_block) { Fabricate(:domain_block) } + let(:domain) { 'example.com' } - subject { account.block_domain!(domain_block) } + subject { account.block_domain!(domain) } it 'creates and returns AccountDomainBlock' do expect do From 364f2ff9aa2b4bf601d68a12bce758aeb5530467 Mon Sep 17 00:00:00 2001 From: Eugen Rochko <eugen@zeonfederated.com> Date: Mon, 4 Feb 2019 04:25:59 +0100 Subject: [PATCH 11/32] Add featured hashtags to profiles (#9755) * Add hashtag filter to profiles GET /@:username/tagged/:hashtag GET /api/v1/accounts/:id/statuses?tagged=:hashtag * Display featured hashtags on public profile * Use separate model for featured tags * Update featured hashtag counters on-write * Limit featured tags to 10 --- app/controllers/accounts_controller.rb | 14 ++++- .../api/v1/accounts/statuses_controller.rb | 5 ++ .../settings/featured_tags_controller.rb | 51 +++++++++++++++++++ .../settings/profiles_controller.rb | 2 +- .../settings/sessions_controller.rb | 1 + app/javascript/styles/mastodon/accounts.scss | 4 ++ app/javascript/styles/mastodon/admin.scss | 7 ++- app/javascript/styles/mastodon/widgets.scss | 7 ++- app/models/concerns/account_associations.rb | 1 + app/models/featured_tag.rb | 46 +++++++++++++++++ app/models/tag.rb | 2 + app/services/process_hashtags_service.rb | 12 ++++- app/services/remove_status_service.rb | 4 ++ app/views/accounts/show.html.haml | 13 +++++ .../settings/featured_tags/index.html.haml | 27 ++++++++++ config/locales/en.yml | 5 ++ config/locales/simple_form.en.yml | 4 ++ config/navigation.rb | 1 + config/routes.rb | 2 + ...5102658_create_account_moderation_notes.rb | 1 + .../20190203180359_create_featured_tags.rb | 12 +++++ db/schema.rb | 15 +++++- spec/fabricators/featured_tag_fabricator.rb | 6 +++ spec/models/featured_tag_spec.rb | 4 ++ 24 files changed, 238 insertions(+), 8 deletions(-) create mode 100644 app/controllers/settings/featured_tags_controller.rb create mode 100644 app/models/featured_tag.rb create mode 100644 app/views/settings/featured_tags/index.html.haml create mode 100644 db/migrate/20190203180359_create_featured_tags.rb create mode 100644 spec/fabricators/featured_tag_fabricator.rb create mode 100644 spec/models/featured_tag_spec.rb diff --git a/app/controllers/accounts_controller.rb b/app/controllers/accounts_controller.rb index f788a90789..6e3a23073b 100644 --- a/app/controllers/accounts_controller.rb +++ b/app/controllers/accounts_controller.rb @@ -57,6 +57,7 @@ class AccountsController < ApplicationController def filtered_statuses default_statuses.tap do |statuses| + statuses.merge!(hashtag_scope) if tag_requested? statuses.merge!(only_media_scope) if media_requested? statuses.merge!(no_replies_scope) unless replies_requested? end @@ -78,12 +79,15 @@ class AccountsController < ApplicationController Status.without_replies end + def hashtag_scope + Status.tagged_with(Tag.find_by(name: params[:tag].downcase)&.id) + end + def set_account @account = Account.find_local!(params[:username]) end def older_url - ::Rails.logger.info("older: max_id #{@statuses.last.id}, url #{pagination_url(max_id: @statuses.last.id)}") pagination_url(max_id: @statuses.last.id) end @@ -92,7 +96,9 @@ class AccountsController < ApplicationController end def pagination_url(max_id: nil, min_id: nil) - if media_requested? + if tag_requested? + short_account_tag_url(@account, params[:tag], max_id: max_id, min_id: min_id) + elsif media_requested? short_account_media_url(@account, max_id: max_id, min_id: min_id) elsif replies_requested? short_account_with_replies_url(@account, max_id: max_id, min_id: min_id) @@ -109,6 +115,10 @@ class AccountsController < ApplicationController request.path.ends_with?('/with_replies') end + def tag_requested? + request.path.ends_with?("/tagged/#{params[:tag]}") + end + def filtered_status_page(params) if params[:min_id].present? filtered_statuses.paginate_by_min_id(PAGE_SIZE, params[:min_id]).reverse diff --git a/app/controllers/api/v1/accounts/statuses_controller.rb b/app/controllers/api/v1/accounts/statuses_controller.rb index 6c2a5c1414..6fdc827cb2 100644 --- a/app/controllers/api/v1/accounts/statuses_controller.rb +++ b/app/controllers/api/v1/accounts/statuses_controller.rb @@ -33,6 +33,7 @@ class Api::V1::Accounts::StatusesController < Api::BaseController statuses.merge!(only_media_scope) if truthy_param?(:only_media) statuses.merge!(no_replies_scope) if truthy_param?(:exclude_replies) statuses.merge!(no_reblogs_scope) if truthy_param?(:exclude_reblogs) + statuses.merge!(hashtag_scope) if params[:tagged].present? statuses end @@ -67,6 +68,10 @@ class Api::V1::Accounts::StatusesController < Api::BaseController Status.without_reblogs end + def hashtag_scope + Status.tagged_with(Tag.find_by(name: params[:tagged])&.id) + end + def pagination_params(core_params) params.slice(:limit, :only_media, :exclude_replies).permit(:limit, :only_media, :exclude_replies).merge(core_params) end diff --git a/app/controllers/settings/featured_tags_controller.rb b/app/controllers/settings/featured_tags_controller.rb new file mode 100644 index 0000000000..19815e4168 --- /dev/null +++ b/app/controllers/settings/featured_tags_controller.rb @@ -0,0 +1,51 @@ +# frozen_string_literal: true + +class Settings::FeaturedTagsController < Settings::BaseController + layout 'admin' + + before_action :authenticate_user! + before_action :set_featured_tags, only: :index + before_action :set_featured_tag, except: [:index, :create] + before_action :set_most_used_tags, only: :index + + def index + @featured_tag = FeaturedTag.new + end + + def create + @featured_tag = current_account.featured_tags.new(featured_tag_params) + @featured_tag.reset_data + + if @featured_tag.save + redirect_to settings_featured_tags_path + else + set_featured_tags + set_most_used_tags + + render :index + end + end + + def destroy + @featured_tag.destroy! + redirect_to settings_featured_tags_path + end + + private + + def set_featured_tag + @featured_tag = current_account.featured_tags.find(params[:id]) + end + + def set_featured_tags + @featured_tags = current_account.featured_tags.reject(&:new_record?) + end + + def set_most_used_tags + @most_used_tags = Tag.most_used(current_account).where.not(id: @featured_tags.map(&:id)).limit(10) + end + + def featured_tag_params + params.require(:featured_tag).permit(:name) + end +end diff --git a/app/controllers/settings/profiles_controller.rb b/app/controllers/settings/profiles_controller.rb index db9081fdf0..8b640cdca1 100644 --- a/app/controllers/settings/profiles_controller.rb +++ b/app/controllers/settings/profiles_controller.rb @@ -32,6 +32,6 @@ class Settings::ProfilesController < Settings::BaseController end def set_account - @account = current_user.account + @account = current_account end end diff --git a/app/controllers/settings/sessions_controller.rb b/app/controllers/settings/sessions_controller.rb index 11b150ffd3..84ebb21f2c 100644 --- a/app/controllers/settings/sessions_controller.rb +++ b/app/controllers/settings/sessions_controller.rb @@ -1,6 +1,7 @@ # frozen_string_literal: true class Settings::SessionsController < Settings::BaseController + before_action :authenticate_user! before_action :set_session, only: :destroy def destroy diff --git a/app/javascript/styles/mastodon/accounts.scss b/app/javascript/styles/mastodon/accounts.scss index 63a5c61b8b..f4f458cf42 100644 --- a/app/javascript/styles/mastodon/accounts.scss +++ b/app/javascript/styles/mastodon/accounts.scss @@ -288,3 +288,7 @@ border-bottom: 0; } } + +.directory__tag .trends__item__current { + width: auto; +} diff --git a/app/javascript/styles/mastodon/admin.scss b/app/javascript/styles/mastodon/admin.scss index 177f8145fa..6d785707c9 100644 --- a/app/javascript/styles/mastodon/admin.scss +++ b/app/javascript/styles/mastodon/admin.scss @@ -153,10 +153,15 @@ $content-width: 840px; font-weight: 500; } - .directory__tag a { + .directory__tag > a, + .directory__tag > div { box-shadow: none; } + .directory__tag .table-action-link .fa { + color: inherit; + } + .directory__tag h4 { font-size: 18px; font-weight: 700; diff --git a/app/javascript/styles/mastodon/widgets.scss b/app/javascript/styles/mastodon/widgets.scss index c97337e4e3..1eaf30c5b3 100644 --- a/app/javascript/styles/mastodon/widgets.scss +++ b/app/javascript/styles/mastodon/widgets.scss @@ -269,7 +269,8 @@ box-sizing: border-box; margin-bottom: 10px; - a { + & > a, + & > div { display: flex; align-items: center; justify-content: space-between; @@ -279,7 +280,9 @@ text-decoration: none; color: inherit; box-shadow: 0 0 15px rgba($base-shadow-color, 0.2); + } + & > a { &:hover, &:active, &:focus { @@ -287,7 +290,7 @@ } } - &.active a { + &.active > a { background: $ui-highlight-color; cursor: default; } diff --git a/app/models/concerns/account_associations.rb b/app/models/concerns/account_associations.rb index 7dafeee34c..397ec4a22a 100644 --- a/app/models/concerns/account_associations.rb +++ b/app/models/concerns/account_associations.rb @@ -55,5 +55,6 @@ module AccountAssociations # Hashtags has_and_belongs_to_many :tags + has_many :featured_tags, -> { includes(:tag) }, dependent: :destroy, inverse_of: :account end end diff --git a/app/models/featured_tag.rb b/app/models/featured_tag.rb new file mode 100644 index 0000000000..b5a10ad2da --- /dev/null +++ b/app/models/featured_tag.rb @@ -0,0 +1,46 @@ +# frozen_string_literal: true +# == Schema Information +# +# Table name: featured_tags +# +# id :bigint(8) not null, primary key +# account_id :bigint(8) +# tag_id :bigint(8) +# statuses_count :bigint(8) default(0), not null +# last_status_at :datetime +# created_at :datetime not null +# updated_at :datetime not null +# + +class FeaturedTag < ApplicationRecord + belongs_to :account, inverse_of: :featured_tags, required: true + belongs_to :tag, inverse_of: :featured_tags, required: true + + delegate :name, to: :tag, allow_nil: true + + validates :name, presence: true + validate :validate_featured_tags_limit, on: :create + + def name=(str) + self.tag = Tag.find_or_initialize_by(name: str.delete('#').mb_chars.downcase.to_s) + end + + def increment(timestamp) + update(statuses_count: statuses_count + 1, last_status_at: timestamp) + end + + def decrement(deleted_status_id) + update(statuses_count: [0, statuses_count - 1].max, last_status_at: account.statuses.where(visibility: %i(public unlisted)).tagged_with(tag).where.not(id: deleted_status_id).select(:created_at).first&.created_at) + end + + def reset_data + self.statuses_count = account.statuses.where(visibility: %i(public unlisted)).tagged_with(tag).count + self.last_status_at = account.statuses.where(visibility: %i(public unlisted)).tagged_with(tag).select(:created_at).first&.created_at + end + + private + + def validate_featured_tags_limit + errors.add(:base, I18n.t('featured_tags.errors.limit')) if account.featured_tags.count >= 10 + end +end diff --git a/app/models/tag.rb b/app/models/tag.rb index 99830ae92c..4373e967b2 100644 --- a/app/models/tag.rb +++ b/app/models/tag.rb @@ -14,6 +14,7 @@ class Tag < ApplicationRecord has_and_belongs_to_many :accounts has_and_belongs_to_many :sample_accounts, -> { searchable.discoverable.popular.limit(3) }, class_name: 'Account' + has_many :featured_tags, dependent: :destroy, inverse_of: :tag has_one :account_tag_stat, dependent: :destroy HASHTAG_NAME_RE = '[[:word:]_]*[[:alpha:]_·][[:word:]_]*' @@ -23,6 +24,7 @@ class Tag < ApplicationRecord scope :discoverable, -> { joins(:account_tag_stat).where(AccountTagStat.arel_table[:accounts_count].gt(0)).where(account_tag_stats: { hidden: false }).order(Arel.sql('account_tag_stats.accounts_count desc')) } scope :hidden, -> { where(account_tag_stats: { hidden: true }) } + scope :most_used, ->(account) { joins(:statuses).where(statuses: { account: account }).group(:id).order(Arel.sql('count(*) desc')) } delegate :accounts_count, :accounts_count=, diff --git a/app/services/process_hashtags_service.rb b/app/services/process_hashtags_service.rb index cf7471c989..d5ec076a8b 100644 --- a/app/services/process_hashtags_service.rb +++ b/app/services/process_hashtags_service.rb @@ -2,12 +2,22 @@ class ProcessHashtagsService < BaseService def call(status, tags = []) - tags = Extractor.extract_hashtags(status.text) if status.local? + tags = Extractor.extract_hashtags(status.text) if status.local? + records = [] tags.map { |str| str.mb_chars.downcase }.uniq(&:to_s).each do |name| tag = Tag.where(name: name).first_or_create(name: name) + status.tags << tag + records << tag + TrendingTags.record_use!(tag, status.account, status.created_at) if status.public_visibility? end + + return unless status.public_visibility? || status.unlisted_visibility? + + status.account.featured_tags.where(tag_id: records.map(&:id)).each do |featured_tag| + featured_tag.increment(status.created_at) + end end end diff --git a/app/services/remove_status_service.rb b/app/services/remove_status_service.rb index 28c5224b01..2012f15fc7 100644 --- a/app/services/remove_status_service.rb +++ b/app/services/remove_status_service.rb @@ -131,6 +131,10 @@ class RemoveStatusService < BaseService end def remove_from_hashtags + @account.featured_tags.where(tag_id: @status.tags.pluck(:id)).each do |featured_tag| + featured_tag.decrement(@status.id) + end + return unless @status.public_visibility? @tags.each do |hashtag| diff --git a/app/views/accounts/show.html.haml b/app/views/accounts/show.html.haml index 0ee9dd7ded..23a595205c 100644 --- a/app/views/accounts/show.html.haml +++ b/app/views/accounts/show.html.haml @@ -63,4 +63,17 @@ - @endorsed_accounts.each do |account| = account_link_to account + - @account.featured_tags.each do |featured_tag| + .directory__tag{ class: params[:tag] == featured_tag.name ? 'active' : nil } + = link_to short_account_tag_path(@account, featured_tag.tag) do + %h4 + = fa_icon 'hashtag' + = featured_tag.name + %small + - if featured_tag.last_status_at.nil? + = t('accounts.nothing_here') + - else + %time{ datetime: featured_tag.last_status_at.iso8601, title: l(featured_tag.last_status_at) }= l featured_tag.last_status_at + .trends__item__current= number_to_human featured_tag.statuses_count, strip_insignificant_zeros: true + = render 'application/sidebar' diff --git a/app/views/settings/featured_tags/index.html.haml b/app/views/settings/featured_tags/index.html.haml new file mode 100644 index 0000000000..5f69517f3a --- /dev/null +++ b/app/views/settings/featured_tags/index.html.haml @@ -0,0 +1,27 @@ +- content_for :page_title do + = t('settings.featured_tags') + += simple_form_for @featured_tag, url: settings_featured_tags_path do |f| + = render 'shared/error_messages', object: @featured_tag + + .fields-group + = f.input :name, wrapper: :with_block_label, hint: safe_join([t('simple_form.hints.featured_tag.name'), safe_join(@most_used_tags.map { |tag| link_to("##{tag.name}", settings_featured_tags_path(featured_tag: { name: tag.name }), method: :post) }, ', ')], ' ') + + .actions + = f.button :button, t('featured_tags.add_new'), type: :submit + +%hr.spacer/ + +- @featured_tags.each do |featured_tag| + .directory__tag{ class: params[:tag] == featured_tag.name ? 'active' : nil } + %div + %h4 + = fa_icon 'hashtag' + = featured_tag.name + %small + - if featured_tag.last_status_at.nil? + = t('accounts.nothing_here') + - else + %time{ datetime: featured_tag.last_status_at.iso8601, title: l(featured_tag.last_status_at) }= l featured_tag.last_status_at + = table_link_to 'trash', t('filters.index.delete'), settings_featured_tag_path(featured_tag), method: :delete, data: { confirm: t('admin.accounts.are_you_sure') } + .trends__item__current= number_to_human featured_tag.statuses_count, strip_insignificant_zeros: true diff --git a/config/locales/en.yml b/config/locales/en.yml index f23f867e5c..c92fc781c8 100644 --- a/config/locales/en.yml +++ b/config/locales/en.yml @@ -588,6 +588,10 @@ en: lists: Lists mutes: You mute storage: Media storage + featured_tags: + add_new: Add new + errors: + limit: You have already featured the maximum amount of hashtags filters: contexts: home: Home timeline @@ -807,6 +811,7 @@ en: development: Development edit_profile: Edit profile export: Data export + featured_tags: Featured hashtags followers: Authorized followers import: Import migrate: Account migration diff --git a/config/locales/simple_form.en.yml b/config/locales/simple_form.en.yml index 325114755b..3a2746a539 100644 --- a/config/locales/simple_form.en.yml +++ b/config/locales/simple_form.en.yml @@ -37,6 +37,8 @@ en: setting_theme: Affects how Mastodon looks when you're logged in from any device. username: Your username will be unique on %{domain} whole_word: When the keyword or phrase is alphanumeric only, it will only be applied if it matches the whole word + featured_tag: + name: 'You might want to use one of these:' imports: data: CSV file exported from another Mastodon instance sessions: @@ -110,6 +112,8 @@ en: username: Username username_or_email: Username or Email whole_word: Whole word + featured_tag: + name: Hashtag interactions: must_be_follower: Block notifications from non-followers must_be_following: Block notifications from people you don't follow diff --git a/config/navigation.rb b/config/navigation.rb index a9521f956d..1be621ac24 100644 --- a/config/navigation.rb +++ b/config/navigation.rb @@ -6,6 +6,7 @@ SimpleNavigation::Configuration.run do |navigation| primary.item :settings, safe_join([fa_icon('cog fw'), t('settings.settings')]), settings_profile_url do |settings| settings.item :profile, safe_join([fa_icon('user fw'), t('settings.edit_profile')]), settings_profile_url, highlights_on: %r{/settings/profile|/settings/migration} + settings.item :featured_tags, safe_join([fa_icon('hashtag fw'), t('settings.featured_tags')]), settings_featured_tags_url settings.item :preferences, safe_join([fa_icon('sliders fw'), t('settings.preferences')]), settings_preferences_url settings.item :notifications, safe_join([fa_icon('bell fw'), t('settings.notifications')]), settings_notifications_url settings.item :password, safe_join([fa_icon('lock fw'), t('auth.security')]), edit_user_registration_url, highlights_on: %r{/auth/edit|/settings/delete} diff --git a/config/routes.rb b/config/routes.rb index af49845ccb..ded62981d4 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -74,6 +74,7 @@ Rails.application.routes.draw do get '/@:username', to: 'accounts#show', as: :short_account get '/@:username/with_replies', to: 'accounts#show', as: :short_account_with_replies get '/@:username/media', to: 'accounts#show', as: :short_account_media + get '/@:username/tagged/:tag', to: 'accounts#show', as: :short_account_tag get '/@:account_username/:id', to: 'statuses#show', as: :short_account_status get '/@:account_username/:id/embed', to: 'statuses#embed', as: :embed_short_account_status @@ -116,6 +117,7 @@ Rails.application.routes.draw do resource :migration, only: [:show, :update] resources :sessions, only: [:destroy] + resources :featured_tags, only: [:index, :create, :destroy] end resources :media, only: [:show] do diff --git a/db/migrate/20171005102658_create_account_moderation_notes.rb b/db/migrate/20171005102658_create_account_moderation_notes.rb index d1802b5b3c..974ed99403 100644 --- a/db/migrate/20171005102658_create_account_moderation_notes.rb +++ b/db/migrate/20171005102658_create_account_moderation_notes.rb @@ -7,6 +7,7 @@ class CreateAccountModerationNotes < ActiveRecord::Migration[5.1] t.timestamps end + add_foreign_key :account_moderation_notes, :accounts, column: :target_account_id end end diff --git a/db/migrate/20190203180359_create_featured_tags.rb b/db/migrate/20190203180359_create_featured_tags.rb new file mode 100644 index 0000000000..b08410a3a7 --- /dev/null +++ b/db/migrate/20190203180359_create_featured_tags.rb @@ -0,0 +1,12 @@ +class CreateFeaturedTags < ActiveRecord::Migration[5.2] + def change + create_table :featured_tags do |t| + t.references :account, foreign_key: { on_delete: :cascade } + t.references :tag, foreign_key: { on_delete: :cascade } + t.bigint :statuses_count, default: 0, null: false + t.datetime :last_status_at + + t.timestamps + end + end +end diff --git a/db/schema.rb b/db/schema.rb index 44e00df405..e9fb358f80 100644 --- a/db/schema.rb +++ b/db/schema.rb @@ -10,7 +10,7 @@ # # It's strongly recommended that you check this file into your version control system. -ActiveRecord::Schema.define(version: 2019_02_01_012802) do +ActiveRecord::Schema.define(version: 2019_02_03_180359) do # These are extensions that must be enabled in order to support this database enable_extension "plpgsql" @@ -250,6 +250,17 @@ ActiveRecord::Schema.define(version: 2019_02_01_012802) do t.index ["status_id"], name: "index_favourites_on_status_id" end + create_table "featured_tags", force: :cascade do |t| + t.bigint "account_id" + t.bigint "tag_id" + t.bigint "statuses_count", default: 0, null: false + t.datetime "last_status_at" + t.datetime "created_at", null: false + t.datetime "updated_at", null: false + t.index ["account_id"], name: "index_featured_tags_on_account_id" + t.index ["tag_id"], name: "index_featured_tags_on_tag_id" + end + create_table "follow_requests", force: :cascade do |t| t.datetime "created_at", null: false t.datetime "updated_at", null: false @@ -708,6 +719,8 @@ ActiveRecord::Schema.define(version: 2019_02_01_012802) do add_foreign_key "custom_filters", "accounts", on_delete: :cascade add_foreign_key "favourites", "accounts", name: "fk_5eb6c2b873", on_delete: :cascade add_foreign_key "favourites", "statuses", name: "fk_b0e856845e", on_delete: :cascade + add_foreign_key "featured_tags", "accounts", on_delete: :cascade + add_foreign_key "featured_tags", "tags", on_delete: :cascade add_foreign_key "follow_requests", "accounts", column: "target_account_id", name: "fk_9291ec025d", on_delete: :cascade add_foreign_key "follow_requests", "accounts", name: "fk_76d644b0e7", on_delete: :cascade add_foreign_key "follows", "accounts", column: "target_account_id", name: "fk_745ca29eac", on_delete: :cascade diff --git a/spec/fabricators/featured_tag_fabricator.rb b/spec/fabricators/featured_tag_fabricator.rb new file mode 100644 index 0000000000..25cbdaac0b --- /dev/null +++ b/spec/fabricators/featured_tag_fabricator.rb @@ -0,0 +1,6 @@ +Fabricator(:featured_tag) do + account + tag + statuses_count 1_337 + last_status_at Time.now.utc +end diff --git a/spec/models/featured_tag_spec.rb b/spec/models/featured_tag_spec.rb new file mode 100644 index 0000000000..07533e0b90 --- /dev/null +++ b/spec/models/featured_tag_spec.rb @@ -0,0 +1,4 @@ +require 'rails_helper' + +RSpec.describe FeaturedTag, type: :model do +end From f86413fce29e70532fbbe5eab9ee8ff053f5552c Mon Sep 17 00:00:00 2001 From: mike castleman <m@mlcastle.net> Date: Mon, 4 Feb 2019 01:45:29 -0200 Subject: [PATCH 12/32] change "finish tutorial" text to "finish toot-orial" (#9905) --- app/javascript/mastodon/features/introduction/index.js | 2 +- app/javascript/mastodon/locales/defaultMessages.json | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/app/javascript/mastodon/features/introduction/index.js b/app/javascript/mastodon/features/introduction/index.js index e712b2f7d4..754477bb99 100644 --- a/app/javascript/mastodon/features/introduction/index.js +++ b/app/javascript/mastodon/features/introduction/index.js @@ -89,7 +89,7 @@ const FrameInteractions = ({ onNext }) => ( </div> <div className='introduction__action'> - <button className='button' onClick={onNext}><FormattedMessage id='introduction.interactions.action' defaultMessage='Finish tutorial!' /></button> + <button className='button' onClick={onNext}><FormattedMessage id='introduction.interactions.action' defaultMessage='Finish toot-orial!' /></button> </div> </div> ); diff --git a/app/javascript/mastodon/locales/defaultMessages.json b/app/javascript/mastodon/locales/defaultMessages.json index 89d4e6bbaa..2dc755e0f5 100644 --- a/app/javascript/mastodon/locales/defaultMessages.json +++ b/app/javascript/mastodon/locales/defaultMessages.json @@ -1457,7 +1457,7 @@ "id": "introduction.interactions.favourite.text" }, { - "defaultMessage": "Finish tutorial!", + "defaultMessage": "Finish toot-orial!", "id": "introduction.interactions.action" } ], From c78d64d9c399d58a49aa92fbb2c607bb681ef5a0 Mon Sep 17 00:00:00 2001 From: rinsuki <428rinsuki+git@gmail.com> Date: Mon, 4 Feb 2019 12:46:05 +0900 Subject: [PATCH 13/32] Use video filesize limit with gifv (#9924) --- app/models/media_attachment.rb | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/app/models/media_attachment.rb b/app/models/media_attachment.rb index 2e0f98cc6f..a57ba0b2ea 100644 --- a/app/models/media_attachment.rb +++ b/app/models/media_attachment.rb @@ -87,8 +87,8 @@ class MediaAttachment < ApplicationRecord convert_options: { all: '-quality 90 -strip' } validates_attachment_content_type :file, content_type: IMAGE_MIME_TYPES + VIDEO_MIME_TYPES - validates_attachment_size :file, less_than: IMAGE_LIMIT, unless: :video? - validates_attachment_size :file, less_than: VIDEO_LIMIT, if: :video? + validates_attachment_size :file, less_than: IMAGE_LIMIT, unless: :video_or_gifv? + validates_attachment_size :file, less_than: VIDEO_LIMIT, if: :video_or_gifv? remotable_attachment :file, VIDEO_LIMIT include Attachmentable @@ -111,6 +111,10 @@ class MediaAttachment < ApplicationRecord file.blank? && remote_url.present? end + def video_or_gifv? + video? || gifv? + end + def to_param shortcode end From 88f35f339d19eed87bb3b55ebd5ed2303726aa78 Mon Sep 17 00:00:00 2001 From: rinsuki <428rinsuki+git@gmail.com> Date: Tue, 5 Feb 2019 06:25:42 +0900 Subject: [PATCH 14/32] Fix authorized applications list page design (#9969) --- app/controllers/oauth/authorized_applications_controller.rb | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/app/controllers/oauth/authorized_applications_controller.rb b/app/controllers/oauth/authorized_applications_controller.rb index 0c28d194bc..f3d2353669 100644 --- a/app/controllers/oauth/authorized_applications_controller.rb +++ b/app/controllers/oauth/authorized_applications_controller.rb @@ -5,6 +5,7 @@ class Oauth::AuthorizedApplicationsController < Doorkeeper::AuthorizedApplicatio before_action :store_current_location before_action :authenticate_resource_owner! + before_action :set_body_classes include Localized @@ -15,6 +16,10 @@ class Oauth::AuthorizedApplicationsController < Doorkeeper::AuthorizedApplicatio private + def set_body_classes + @body_classes = 'admin' + end + def store_current_location store_location_for(:user, request.url) end From e02a13f64e5c2c93fa73a67a4ce32a7d1df24760 Mon Sep 17 00:00:00 2001 From: rinsuki <428rinsuki+git@gmail.com> Date: Tue, 5 Feb 2019 07:14:57 +0900 Subject: [PATCH 15/32] Fix not showing custom emojis in share page emoji picker (#9970) --- app/javascript/mastodon/containers/compose_container.js | 3 +++ 1 file changed, 3 insertions(+) diff --git a/app/javascript/mastodon/containers/compose_container.js b/app/javascript/mastodon/containers/compose_container.js index 5ee1d2f141..7bc7bbaa4d 100644 --- a/app/javascript/mastodon/containers/compose_container.js +++ b/app/javascript/mastodon/containers/compose_container.js @@ -7,6 +7,7 @@ import { IntlProvider, addLocaleData } from 'react-intl'; import { getLocale } from '../locales'; import Compose from '../features/standalone/compose'; import initialState from '../initial_state'; +import { fetchCustomEmojis } from '../actions/custom_emojis'; const { localeData, messages } = getLocale(); addLocaleData(localeData); @@ -17,6 +18,8 @@ if (initialState) { store.dispatch(hydrateStore(initialState)); } +store.dispatch(fetchCustomEmojis()); + export default class TimelineContainer extends React.PureComponent { static propTypes = { From 2557cb2f9550f7ab0cd6e6f392642b5249589ed8 Mon Sep 17 00:00:00 2001 From: Eugen Rochko <eugen@zeonfederated.com> Date: Tue, 5 Feb 2019 00:27:18 +0100 Subject: [PATCH 16/32] Fix pinned statuses being shown in a featured hashtag (#9971) --- app/controllers/accounts_controller.rb | 2 +- app/controllers/settings/featured_tags_controller.rb | 2 +- app/views/accounts/show.html.haml | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/app/controllers/accounts_controller.rb b/app/controllers/accounts_controller.rb index 6e3a23073b..cbf1a8287b 100644 --- a/app/controllers/accounts_controller.rb +++ b/app/controllers/accounts_controller.rb @@ -52,7 +52,7 @@ class AccountsController < ApplicationController private def show_pinned_statuses? - [replies_requested?, media_requested?, params[:max_id].present?, params[:min_id].present?].none? + [replies_requested?, media_requested?, tag_requested?, params[:max_id].present?, params[:min_id].present?].none? end def filtered_statuses diff --git a/app/controllers/settings/featured_tags_controller.rb b/app/controllers/settings/featured_tags_controller.rb index 19815e4168..3a3241425d 100644 --- a/app/controllers/settings/featured_tags_controller.rb +++ b/app/controllers/settings/featured_tags_controller.rb @@ -38,7 +38,7 @@ class Settings::FeaturedTagsController < Settings::BaseController end def set_featured_tags - @featured_tags = current_account.featured_tags.reject(&:new_record?) + @featured_tags = current_account.featured_tags.order(statuses_count: :desc).reject(&:new_record?) end def set_most_used_tags diff --git a/app/views/accounts/show.html.haml b/app/views/accounts/show.html.haml index 23a595205c..0da69728f3 100644 --- a/app/views/accounts/show.html.haml +++ b/app/views/accounts/show.html.haml @@ -63,7 +63,7 @@ - @endorsed_accounts.each do |account| = account_link_to account - - @account.featured_tags.each do |featured_tag| + - @account.featured_tags.order(statuses_count: :desc).each do |featured_tag| .directory__tag{ class: params[:tag] == featured_tag.name ? 'active' : nil } = link_to short_account_tag_path(@account, featured_tag.tag) do %h4 From 76d41475a89ebdd19b08b9e8a81e5e3c7dbd4001 Mon Sep 17 00:00:00 2001 From: trwnh <a@trwnh.com> Date: Mon, 4 Feb 2019 21:46:18 -0600 Subject: [PATCH 17/32] [UI] Fix whitespace being applied to div instead of p (#9968) * fix large line breaks * fix ascii art posts --- app/javascript/styles/mastodon/components.scss | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/javascript/styles/mastodon/components.scss b/app/javascript/styles/mastodon/components.scss index 10e0946481..32fd773859 100644 --- a/app/javascript/styles/mastodon/components.scss +++ b/app/javascript/styles/mastodon/components.scss @@ -638,7 +638,6 @@ font-weight: 400; overflow: hidden; text-overflow: ellipsis; - white-space: pre-wrap; padding-top: 2px; color: $primary-text-color; @@ -662,6 +661,7 @@ p { margin-bottom: 20px; + white-space: pre-wrap; &:last-child { margin-bottom: 0; From 5c873a4ed7d4abb8c684e3a8ed3cdf9383e77be5 Mon Sep 17 00:00:00 2001 From: ashleyhull-versent <ashley.hull@versent.com.au> Date: Tue, 5 Feb 2019 15:11:51 +1100 Subject: [PATCH 18/32] Update Dockerfile (#9965) --- Dockerfile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Dockerfile b/Dockerfile index 1909053375..1e60045c01 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,5 +1,5 @@ FROM node:8.15-alpine as node -FROM ruby:2.6-alpine3.8 +FROM ruby:2.6-alpine3.9 LABEL maintainer="https://github.com/tootsuite/mastodon" \ description="Your self-hosted, globally interconnected microblogging community" From 5bffb53a763531f6ed1d8e87bacede3ff0938086 Mon Sep 17 00:00:00 2001 From: Takeshi Umeda <noel.yoshiba@gmail.com> Date: Tue, 5 Feb 2019 23:11:11 +0900 Subject: [PATCH 19/32] Fix it as tagged_request of accounts_controller is not addressable_uri (#9976) --- app/controllers/accounts_controller.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/controllers/accounts_controller.rb b/app/controllers/accounts_controller.rb index cbf1a8287b..cad2ecf3fe 100644 --- a/app/controllers/accounts_controller.rb +++ b/app/controllers/accounts_controller.rb @@ -116,7 +116,7 @@ class AccountsController < ApplicationController end def tag_requested? - request.path.ends_with?("/tagged/#{params[:tag]}") + request.path.ends_with?(Addressable::URI.parse("/tagged/#{params[:tag]}").normalize) end def filtered_status_page(params) From 1ac9a3f4bb8d1fb4bf521f9452ebc839d44315ba Mon Sep 17 00:00:00 2001 From: ThibG <thib@sitedethib.com> Date: Tue, 5 Feb 2019 15:11:35 +0100 Subject: [PATCH 20/32] =?UTF-8?q?Hide=20misleading=20=E2=80=9CYou=20will?= =?UTF-8?q?=20be=20sent=20a=20confirmation=20e-mail=E2=80=9D=20hint=20from?= =?UTF-8?q?=20admin=20view=20(#9973)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Thanks @wryk for noticing this issue. --- app/views/admin/change_emails/show.html.haml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/views/admin/change_emails/show.html.haml b/app/views/admin/change_emails/show.html.haml index 6febef9b1a..6ff0d785ed 100644 --- a/app/views/admin/change_emails/show.html.haml +++ b/app/views/admin/change_emails/show.html.haml @@ -3,7 +3,7 @@ = simple_form_for @user, url: admin_account_change_email_path(@account.id) do |f| .fields-group - = f.input :email, wrapper: :with_label, disabled: true, label: t('admin.accounts.change_email.current_email') + = f.input :email, wrapper: :with_label, hint: false, disabled: true, label: t('admin.accounts.change_email.current_email') .fields-group = f.input :unconfirmed_email, wrapper: :with_label, label: t('admin.accounts.change_email.new_email') From 11d150285302211def8015d0bc1b825f9f7e0e23 Mon Sep 17 00:00:00 2001 From: J0WI <J0WI@users.noreply.github.com> Date: Tue, 5 Feb 2019 15:12:45 +0100 Subject: [PATCH 21/32] Replace LibreSSL by OpenSSL (#9975) --- Dockerfile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Dockerfile b/Dockerfile index 1e60045c01..184e25ca2c 100644 --- a/Dockerfile +++ b/Dockerfile @@ -29,7 +29,7 @@ RUN apk -U upgrade \ build-base \ icu-dev \ libidn-dev \ - libressl \ + openssl \ libtool \ libxml2-dev \ libxslt-dev \ From 26c1aba658d04617caf33783306b4504ca41f63c Mon Sep 17 00:00:00 2001 From: J0WI <J0WI@users.noreply.github.com> Date: Tue, 5 Feb 2019 15:13:19 +0100 Subject: [PATCH 22/32] Do not use apk cache and upgrade (#9966) --- Dockerfile | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/Dockerfile b/Dockerfile index 184e25ca2c..2b15381ec5 100644 --- a/Dockerfile +++ b/Dockerfile @@ -24,8 +24,7 @@ COPY --from=node /usr/local/lib/node_modules /usr/local/lib/node_modules COPY --from=node /usr/local/bin/npm /usr/local/bin/npm COPY --from=node /opt/yarn-* /opt/yarn -RUN apk -U upgrade \ - && apk add -t build-dependencies \ +RUN apk add --no-cache -t build-dependencies \ build-base \ icu-dev \ libidn-dev \ @@ -36,7 +35,7 @@ RUN apk -U upgrade \ postgresql-dev \ protobuf-dev \ python \ - && apk add \ + && apk add --no-cache \ ca-certificates \ ffmpeg \ file \ @@ -64,7 +63,7 @@ RUN apk -U upgrade \ && make install \ && libtool --finish /usr/local/lib \ && cd /mastodon \ - && rm -rf /tmp/* /var/cache/apk/* + && rm -rf /tmp/* COPY Gemfile Gemfile.lock package.json yarn.lock .yarnclean /mastodon/ From 3eb17a3beafbfa998f4d09f6d3310658195638d6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?marcin=20miko=C5=82ajczak?= <me@m4sk.in> Date: Tue, 5 Feb 2019 15:13:34 +0100 Subject: [PATCH 23/32] =?UTF-8?q?i18n:=20Update=20Polish=20translation=20?= =?UTF-8?q?=F0=9F=87=B5=20(#9974)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Marcin Mikołajczak <me@m4sk.in> --- app/javascript/mastodon/locales/pl.json | 1 + config/locales/pl.yml | 11 +++++++++++ config/locales/simple_form.pl.yml | 6 ++++++ 3 files changed, 18 insertions(+) diff --git a/app/javascript/mastodon/locales/pl.json b/app/javascript/mastodon/locales/pl.json index 0d16f7cfc0..a1cda6c2d4 100644 --- a/app/javascript/mastodon/locales/pl.json +++ b/app/javascript/mastodon/locales/pl.json @@ -342,6 +342,7 @@ "ui.beforeunload": "Utracisz tworzony wpis, jeżeli opuścisz Mastodona.", "upload_area.title": "Przeciągnij i upuść aby wysłać", "upload_button.label": "Dodaj zawartość multimedialną (JPEG, PNG, GIF, WebM, MP4, MOV)", + "upload_error.limit": "Przekroczono limit plików do wysłania.", "upload_form.description": "Wprowadź opis dla niewidomych i niedowidzących", "upload_form.focus": "Dopasuj podgląd", "upload_form.undo": "Usuń", diff --git a/config/locales/pl.yml b/config/locales/pl.yml index 188f5c22b9..e110db50de 100644 --- a/config/locales/pl.yml +++ b/config/locales/pl.yml @@ -602,6 +602,10 @@ pl: lists: Listy mutes: Wyciszeni storage: Urządzenie przechowujące dane + featured_tags: + add_new: Dodaj nowy + errors: + limit: Już przekroczyłeś(-aś) maksymalną liczbę wyróżnionych hashtagów filters: contexts: home: Strona główna @@ -646,10 +650,16 @@ pl: one: Coś jest wciąż nie tak! Przyjrzyj się poniższemu błędowi other: Coś jest wciąż nie tak! Przejrzyj poniższe błędy (%{count}) imports: + modes: + merge: Połącz + merge_long: Zachowaj obecne wpisy i dodaj nowe + overwrite: Nadpisz + overwrite_long: Zastąp obecne wpisy nowymi preface: Możesz zaimportować pewne dane (np. lista kont, które śledzisz lub blokujesz) do swojego konta na tym serwerze, korzystając z danych wyeksportowanych z innego serwera. success: Twoje dane zostały załadowane i zostaną niebawem przetworzone types: blocking: Lista blokowanych + domain_blocking: Lista zablokowanych domen following: Lista śledzonych muting: Lista wyciszonych upload: Załaduj @@ -825,6 +835,7 @@ pl: development: Tworzenie aplikacji edit_profile: Edytuj profil export: Eksportowanie danych + featured_tags: Wyróżnione hashtagi followers: Autoryzowani śledzący import: Importowanie danych migrate: Migracja konta diff --git a/config/locales/simple_form.pl.yml b/config/locales/simple_form.pl.yml index bbc55e8a91..0ab045068e 100644 --- a/config/locales/simple_form.pl.yml +++ b/config/locales/simple_form.pl.yml @@ -33,9 +33,12 @@ pl: setting_display_media_show_all: Zawsze pokazuj zawartość multimedialną jako wrażliwą setting_hide_network: Informacje o tym, kto Cię śledzi i kogo śledzisz nie będą widoczne setting_noindex: Wpływa na widoczność strony profilu i Twoich wpisów + setting_show_application: W informacjach o wpisie będzie widoczna informacja o aplikacji, z której został wysłany setting_theme: Zmienia wygląd Mastodona po zalogowaniu z dowolnego urządzenia. username: Twoja nazwa użytkownika będzie niepowtarzalna na %{domain} whole_word: Jeśli słowo lub fraza składa się jedynie z liter lub cyfr, filtr będzie zastosowany tylko do pełnych wystąpień + featured_tag: + name: 'Sugerujemy użycie jednego z następujących:' imports: data: Plik CSV wyeksportowany z innej instancji Mastodona sessions: @@ -101,6 +104,7 @@ pl: setting_hide_network: Ukryj swoją sieć setting_noindex: Nie indeksuj mojego profilu w wyszukiwarkach internetowych setting_reduce_motion: Ogranicz ruch w animacjach + setting_show_application: Informuj o aplikacji z której wysłano wpisy setting_system_font_ui: Używaj domyślnej czcionki systemu setting_theme: Motyw strony setting_unfollow_modal: Pytaj o potwierdzenie przed cofnięciem śledzenia @@ -109,6 +113,8 @@ pl: username: Nazwa użytkownika username_or_email: Nazwa użytkownika lub adres e-mail whole_word: Całe słowo + featured_tag: + name: Hashtag interactions: must_be_follower: Nie wyświetlaj powiadomień od osób, które Cię nie śledzą must_be_following: Nie wyświetlaj powiadomień od osób, których nie śledzisz From 46e806cd2f14a5e45d66b4c23040855202818984 Mon Sep 17 00:00:00 2001 From: mayaeh <mayaeh@marimo-net.org> Date: Wed, 6 Feb 2019 03:11:24 +0900 Subject: [PATCH 24/32] Rename from instance to server. (#9938) --- .../features/getting_started/index.js | 2 +- .../features/public_timeline/index.js | 2 +- .../features/ui/components/report_modal.js | 2 +- .../mastodon/locales/defaultMessages.json | 6 +-- app/javascript/mastodon/locales/en.json | 6 +-- app/javascript/mastodon/locales/ja.json | 8 ++-- config/locales/devise.en.yml | 6 +-- config/locales/devise.ja.yml | 6 +-- config/locales/en.yml | 34 ++++++------- config/locales/ja.yml | 48 +++++++++---------- config/locales/simple_form.en.yml | 2 +- config/locales/simple_form.ja.yml | 2 +- 12 files changed, 62 insertions(+), 62 deletions(-) diff --git a/app/javascript/mastodon/features/getting_started/index.js b/app/javascript/mastodon/features/getting_started/index.js index e277a73c7a..e1f84de276 100644 --- a/app/javascript/mastodon/features/getting_started/index.js +++ b/app/javascript/mastodon/features/getting_started/index.js @@ -160,7 +160,7 @@ class GettingStarted extends ImmutablePureComponent { {invitesEnabled && <li><a href='/invites' target='_blank'><FormattedMessage id='getting_started.invite' defaultMessage='Invite people' /></a> · </li>} {multiColumn && <li><Link to='/keyboard-shortcuts'><FormattedMessage id='navigation_bar.keyboard_shortcuts' defaultMessage='Hotkeys' /></Link> · </li>} <li><a href='/auth/edit'><FormattedMessage id='getting_started.security' defaultMessage='Security' /></a> · </li> - <li><a href='/about/more' target='_blank'><FormattedMessage id='navigation_bar.info' defaultMessage='About this instance' /></a> · </li> + <li><a href='/about/more' target='_blank'><FormattedMessage id='navigation_bar.info' defaultMessage='About this server' /></a> · </li> <li><a href='https://joinmastodon.org/apps' target='_blank'><FormattedMessage id='navigation_bar.apps' defaultMessage='Mobile apps' /></a> · </li> <li><a href='/terms' target='_blank'><FormattedMessage id='getting_started.terms' defaultMessage='Terms of service' /></a> · </li> <li><a href='/settings/applications' target='_blank'><FormattedMessage id='getting_started.developers' defaultMessage='Developers' /></a> · </li> diff --git a/app/javascript/mastodon/features/public_timeline/index.js b/app/javascript/mastodon/features/public_timeline/index.js index d640033ebb..2b7d9c56f9 100644 --- a/app/javascript/mastodon/features/public_timeline/index.js +++ b/app/javascript/mastodon/features/public_timeline/index.js @@ -124,7 +124,7 @@ class PublicTimeline extends React.PureComponent { onLoadMore={this.handleLoadMore} trackScroll={!pinned} scrollKey={`public_timeline-${columnId}`} - emptyMessage={<FormattedMessage id='empty_column.public' defaultMessage='There is nothing here! Write something publicly, or manually follow users from other instances to fill it up' />} + emptyMessage={<FormattedMessage id='empty_column.public' defaultMessage='There is nothing here! Write something publicly, or manually follow users from other servers to fill it up' />} shouldUpdateScroll={shouldUpdateScroll} /> </Column> diff --git a/app/javascript/mastodon/features/ui/components/report_modal.js b/app/javascript/mastodon/features/ui/components/report_modal.js index bc6b18664a..2e41f784dd 100644 --- a/app/javascript/mastodon/features/ui/components/report_modal.js +++ b/app/javascript/mastodon/features/ui/components/report_modal.js @@ -97,7 +97,7 @@ class ReportModal extends ImmutablePureComponent { <div className='report-modal__container'> <div className='report-modal__comment'> - <p><FormattedMessage id='report.hint' defaultMessage='The report will be sent to your instance moderators. You can provide an explanation of why you are reporting this account below:' /></p> + <p><FormattedMessage id='report.hint' defaultMessage='The report will be sent to your server moderators. You can provide an explanation of why you are reporting this account below:' /></p> <textarea className='setting-text light' diff --git a/app/javascript/mastodon/locales/defaultMessages.json b/app/javascript/mastodon/locales/defaultMessages.json index 2dc755e0f5..5df071839a 100644 --- a/app/javascript/mastodon/locales/defaultMessages.json +++ b/app/javascript/mastodon/locales/defaultMessages.json @@ -1284,7 +1284,7 @@ "id": "getting_started.security" }, { - "defaultMessage": "About this instance", + "defaultMessage": "About this server", "id": "navigation_bar.info" }, { @@ -1837,7 +1837,7 @@ "id": "column.public" }, { - "defaultMessage": "There is nothing here! Write something publicly, or manually follow users from other instances to fill it up", + "defaultMessage": "There is nothing here! Write something publicly, or manually follow users from other servers to fill it up", "id": "empty_column.public" } ], @@ -2184,7 +2184,7 @@ "id": "report.target" }, { - "defaultMessage": "The report will be sent to your instance moderators. You can provide an explanation of why you are reporting this account below:", + "defaultMessage": "The report will be sent to your server moderators. You can provide an explanation of why you are reporting this account below:", "id": "report.hint" }, { diff --git a/app/javascript/mastodon/locales/en.json b/app/javascript/mastodon/locales/en.json index e840b17d84..03af57ba80 100644 --- a/app/javascript/mastodon/locales/en.json +++ b/app/javascript/mastodon/locales/en.json @@ -128,7 +128,7 @@ "empty_column.lists": "You don't have any lists yet. When you create one, it will show up here.", "empty_column.mutes": "You haven't muted any users yet.", "empty_column.notifications": "You don't have any notifications yet. Interact with others to start the conversation.", - "empty_column.public": "There is nothing here! Write something publicly, or manually follow users from other instances to fill it up", + "empty_column.public": "There is nothing here! Write something publicly, or manually follow users from other servers to fill it up", "follow_request.authorize": "Authorize", "follow_request.reject": "Reject", "getting_started.developers": "Developers", @@ -224,7 +224,7 @@ "navigation_bar.favourites": "Favourites", "navigation_bar.filters": "Muted words", "navigation_bar.follow_requests": "Follow requests", - "navigation_bar.info": "About this instance", + "navigation_bar.info": "About this server", "navigation_bar.keyboard_shortcuts": "Hotkeys", "navigation_bar.lists": "Lists", "navigation_bar.logout": "Logout", @@ -276,7 +276,7 @@ "reply_indicator.cancel": "Cancel", "report.forward": "Forward to {target}", "report.forward_hint": "The account is from another server. Send an anonymized copy of the report there as well?", - "report.hint": "The report will be sent to your instance moderators. You can provide an explanation of why you are reporting this account below:", + "report.hint": "The report will be sent to your server moderators. You can provide an explanation of why you are reporting this account below:", "report.placeholder": "Additional comments", "report.submit": "Submit", "report.target": "Reporting {target}", diff --git a/app/javascript/mastodon/locales/ja.json b/app/javascript/mastodon/locales/ja.json index cd4addd686..81b21a94a4 100644 --- a/app/javascript/mastodon/locales/ja.json +++ b/app/javascript/mastodon/locales/ja.json @@ -128,7 +128,7 @@ "empty_column.lists": "まだリストがありません。リストを作るとここに表示されます。", "empty_column.mutes": "まだ誰もミュートしていません。", "empty_column.notifications": "まだ通知がありません。他の人とふれ合って会話を始めましょう。", - "empty_column.public": "ここにはまだ何もありません! 公開で何かを投稿したり、他のインスタンスのユーザーをフォローしたりしていっぱいにしましょう", + "empty_column.public": "ここにはまだ何もありません! 公開で何かを投稿したり、他のサーバーのユーザーをフォローしたりしていっぱいにしましょう", "follow_request.authorize": "許可", "follow_request.reject": "拒否", "getting_started.developers": "開発", @@ -224,7 +224,7 @@ "navigation_bar.favourites": "お気に入り", "navigation_bar.filters": "フィルター設定", "navigation_bar.follow_requests": "フォローリクエスト", - "navigation_bar.info": "このインスタンスについて", + "navigation_bar.info": "このサーバーについて", "navigation_bar.keyboard_shortcuts": "ホットキー", "navigation_bar.lists": "リスト", "navigation_bar.logout": "ログアウト", @@ -275,8 +275,8 @@ "relative_time.seconds": "{number}秒前", "reply_indicator.cancel": "キャンセル", "report.forward": "{target} に転送する", - "report.forward_hint": "このアカウントは別のインスタンスに所属しています。通報内容を匿名で転送しますか?", - "report.hint": "通報内容はあなたのインスタンスのモデレーターへ送信されます。通報理由を入力してください。:", + "report.forward_hint": "このアカウントは別のサーバーに所属しています。通報内容を匿名で転送しますか?", + "report.hint": "通報内容はあなたのサーバーのモデレーターへ送信されます。通報理由を入力してください。:", "report.placeholder": "追加コメント", "report.submit": "通報する", "report.target": "{target}さんを通報する", diff --git a/config/locales/devise.en.yml b/config/locales/devise.en.yml index bd0642b251..726c0504ef 100644 --- a/config/locales/devise.en.yml +++ b/config/locales/devise.en.yml @@ -20,17 +20,17 @@ en: action: Verify email address action_with_app: Confirm and return to %{app} explanation: You have created an account on %{host} with this email address. You are one click away from activating it. If this wasn't you, please ignore this email. - extra_html: Please also check out <a href="%{terms_path}">the rules of the instance</a> and <a href="%{policy_path}">our terms of service</a>. + extra_html: Please also check out <a href="%{terms_path}">the rules of the server</a> and <a href="%{policy_path}">our terms of service</a>. subject: 'Mastodon: Confirmation instructions for %{instance}' title: Verify email address email_changed: explanation: 'The email address for your account is being changed to:' - extra: If you did not change your email, it is likely that someone has gained access to your account. Please change your password immediately or contact the instance admin if you're locked out of your account. + extra: If you did not change your email, it is likely that someone has gained access to your account. Please change your password immediately or contact the server admin if you're locked out of your account. subject: 'Mastodon: Email changed' title: New email address password_change: explanation: The password for your account has been changed. - extra: If you did not change your password, it is likely that someone has gained access to your account. Please change your password immediately or contact the instance admin if you're locked out of your account. + extra: If you did not change your password, it is likely that someone has gained access to your account. Please change your password immediately or contact the server admin if you're locked out of your account. subject: 'Mastodon: Password changed' title: Password changed reconfirmation_instructions: diff --git a/config/locales/devise.ja.yml b/config/locales/devise.ja.yml index cae76d4938..bd2bb71bb3 100644 --- a/config/locales/devise.ja.yml +++ b/config/locales/devise.ja.yml @@ -20,17 +20,17 @@ ja: action: メールアドレスの確認 action_with_app: 確認し %{app} に戻る explanation: このメールアドレスで%{host}にアカウントを作成しました。有効にするまであと一歩です。もし心当たりがない場合、申し訳ありませんがこのメールを無視してください。 - extra_html: また <a href="%{terms_path}">インスタンスのルール</a> と <a href="%{policy_path}">利用規約</a> もお読みください。 + extra_html: また <a href="%{terms_path}">サーバーのルール</a> と <a href="%{policy_path}">利用規約</a> もお読みください。 subject: 'Mastodon: メールアドレスの確認 %{instance}' title: メールアドレスの確認 email_changed: explanation: 'アカウントのメールアドレスは以下のように変更されます:' - extra: メールアドレスの変更を行っていない場合、他の誰かがあなたのアカウントにアクセスした可能性があります。すぐにパスワードを変更するか、アカウントがロックされている場合はインスタンス管理者に連絡してください。 + extra: メールアドレスの変更を行っていない場合、他の誰かがあなたのアカウントにアクセスした可能性があります。すぐにパスワードを変更するか、アカウントがロックされている場合はサーバー管理者に連絡してください。 subject: 'Mastodon: メールアドレスの変更' title: 新しいメールアドレス password_change: explanation: パスワードが変更されました。 - extra: パスワードの変更を行っていない場合、他の誰かがあなたのアカウントにアクセスした可能性があります。すぐにパスワードを変更するか、アカウントがロックされている場合はインスタンス管理者に連絡してください。 + extra: パスワードの変更を行っていない場合、他の誰かがあなたのアカウントにアクセスした可能性があります。すぐにパスワードを変更するか、アカウントがロックされている場合はサーバー管理者に連絡してください。 subject: 'Mastodon: パスワードが変更されました' title: パスワードの変更 reconfirmation_instructions: diff --git a/config/locales/en.yml b/config/locales/en.yml index c92fc781c8..dacf16d563 100644 --- a/config/locales/en.yml +++ b/config/locales/en.yml @@ -7,7 +7,7 @@ en: administered_by: 'Administered by:' api: API apps: Mobile apps - closed_registrations: Registrations are currently closed on this instance. However! You can find a different instance to make an account on and get access to the very same network from there. + closed_registrations: Registrations are currently closed on this server. However! You can find a different server to make an account on and get access to the very same network from there. contact: Contact contact_missing: Not set contact_unavailable: N/A @@ -27,7 +27,7 @@ en: generic_description: "%{domain} is one server in the network" hosted_on: Mastodon hosted on %{domain} learn_more: Learn more - other_instances: Instance list + other_instances: Server list privacy_policy: Privacy policy source_code: Source code status_count_after: @@ -386,14 +386,14 @@ en: desc_html: Modify the look with CSS loaded on every page title: Custom CSS hero: - desc_html: Displayed on the frontpage. At least 600x100px recommended. When not set, falls back to instance thumbnail + desc_html: Displayed on the frontpage. At least 600x100px recommended. When not set, falls back to server thumbnail title: Hero image mascot: desc_html: Displayed on multiple pages. At least 293×205px recommended. When not set, falls back to default mascot title: Mascot image peers_api_enabled: - desc_html: Domain names this instance has encountered in the fediverse - title: Publish list of discovered instances + desc_html: Domain names this server has encountered in the fediverse + title: Publish list of discovered servers preview_sensitive_media: desc_html: Link previews on other websites will display a thumbnail even if the media is marked as sensitive title: Show sensitive media in OpenGraph previews @@ -421,20 +421,20 @@ en: title: Show staff badge site_description: desc_html: Introductory paragraph on the frontpage. Describe what makes this Mastodon server special and anything else important. You can use HTML tags, in particular <code><a></code> and <code><em></code>. - title: Instance description + title: Server description site_description_extended: - desc_html: A good place for your code of conduct, rules, guidelines and other things that set your instance apart. You can use HTML tags + desc_html: A good place for your code of conduct, rules, guidelines and other things that set your server apart. You can use HTML tags title: Custom extended information site_short_description: - desc_html: Displayed in sidebar and meta tags. Describe what Mastodon is and what makes this server special in a single paragraph. If empty, defaults to instance description. - title: Short instance description + desc_html: Displayed in sidebar and meta tags. Describe what Mastodon is and what makes this server special in a single paragraph. If empty, defaults to server description. + title: Short server description site_terms: desc_html: You can write your own privacy policy, terms of service or other legalese. You can use HTML tags title: Custom terms of service - site_title: Instance name + site_title: Server name thumbnail: desc_html: Used for previews via OpenGraph and API. 1200x630px recommended - title: Instance thumbnail + title: Server thumbnail timeline_preview: desc_html: Display public timeline on landing page title: Timeline preview @@ -495,7 +495,7 @@ en: warning: Be very careful with this data. Never share it with anyone! your_token: Your access token auth: - agreement_html: By clicking "Sign up" below you agree to follow <a href="%{rules_path}">the rules of the instance</a> and <a href="%{terms_path}">our terms of service</a>. + agreement_html: By clicking "Sign up" below you agree to follow <a href="%{rules_path}">the rules of the server</a> and <a href="%{terms_path}">our terms of service</a>. change_password: Password confirm_email: Confirm email delete_account: Delete account @@ -549,7 +549,7 @@ en: description_html: This will <strong>permanently, irreversibly</strong> remove content from your account and deactivate it. Your username will remain reserved to prevent future impersonations. proceed: Delete account success_msg: Your account was successfully deleted - warning_html: Only deletion of content from this particular instance is guaranteed. Content that has been widely shared is likely to leave traces. Offline servers and servers that have unsubscribed from your updates will not update their databases. + warning_html: Only deletion of content from this particular server is guaranteed. Content that has been widely shared is likely to leave traces. Offline servers and servers that have unsubscribed from your updates will not update their databases. warning_title: Disseminated content availability directories: directory: Profile directory @@ -610,7 +610,7 @@ en: title: Add new filter followers: domain: Domain - explanation_html: If you want to ensure the privacy of your statuses, you must be aware of who is following you. <strong>Your private statuses are delivered to all instances where you have followers</strong>. You may wish to review them, and remove followers if you do not trust your privacy to be respected by the staff or software of those instances. + explanation_html: If you want to ensure the privacy of your statuses, you must be aware of who is following you. <strong>Your private statuses are delivered to all servers where you have followers</strong>. You may wish to review them, and remove followers if you do not trust your privacy to be respected by the staff or software of those servers. followers_count: Number of followers lock_link: Lock your account purge: Remove from followers @@ -637,7 +637,7 @@ en: merge_long: Keep existing records and add new ones overwrite: Overwrite overwrite_long: Replace current records with the new ones - preface: You can import data that you have exported from another instance, such as a list of the people you are following or blocking. + preface: You can import data that you have exported from another server, such as a list of the people you are following or blocking. success: Your data was successfully uploaded and will now be processed in due time types: blocking: Blocking list @@ -663,7 +663,7 @@ en: one: 1 use other: "%{count} uses" max_uses_prompt: No limit - prompt: Generate and share links with others to grant access to this instance + prompt: Generate and share links with others to grant access to this server table: expires_at: Expires uses: Uses @@ -991,7 +991,7 @@ en: final_action: Start posting final_step: 'Start posting! Even without followers your public messages may be seen by others, for example on the local timeline and in hashtags. You may want to introduce yourself on the #introductions hashtag.' full_handle: Your full handle - full_handle_hint: This is what you would tell your friends so they can message or follow you from another instance. + full_handle_hint: This is what you would tell your friends so they can message or follow you from another server. review_preferences_action: Change preferences review_preferences_step: Make sure to set your preferences, such as which emails you'd like to receive, or what privacy level you’d like your posts to default to. If you don’t have motion sickness, you could choose to enable GIF autoplay. subject: Welcome to Mastodon diff --git a/config/locales/ja.yml b/config/locales/ja.yml index 7ccb2f21c5..07458d07dd 100644 --- a/config/locales/ja.yml +++ b/config/locales/ja.yml @@ -7,7 +7,7 @@ ja: administered_by: '管理者:' api: API apps: アプリ - closed_registrations: 現在このインスタンスでの新規登録は受け付けていません。しかし、他のインスタンスにアカウントを作成しても全く同じネットワークに参加することができます。 + closed_registrations: 現在このサーバーでの新規登録は受け付けていません。しかし、他のサーバーにアカウントを作成しても全く同じネットワークに参加することができます。 contact: 連絡先 contact_missing: 未設定 contact_unavailable: N/A @@ -24,10 +24,10 @@ ja: real_conversation_title: 本当のコミュニケーションのために within_reach_body: デベロッパーフレンドリーな API により実現された、iOS や Android、その他様々なプラットフォームのためのアプリでどこでも友人とやりとりできます。 within_reach_title: いつでも身近に - generic_description: "%{domain} は、Mastodon インスタンスの一つです" + generic_description: "%{domain} は、Mastodon サーバーの一つです" hosted_on: Mastodon hosted on %{domain} learn_more: もっと詳しく - other_instances: 他のインスタンス + other_instances: 他のサーバー privacy_policy: プライバシーポリシー source_code: ソースコード status_count_after: @@ -310,7 +310,7 @@ ja: all: すべて limited: 制限あり title: モデレーション - title: 既知のインスタンス + title: 既知のサーバー total_blocked_by_us: ブロック合計 total_followed_by_them: 被フォロー合計 total_followed_by_us: フォロー合計 @@ -392,8 +392,8 @@ ja: desc_html: 複数のページに表示されます。サイズは293x205px以上推奨です。未設定の場合、標準のマスコットが使用されます title: マスコットイメージ peers_api_enabled: - desc_html: 連合内でこのインスタンスが遭遇したドメインの名前 - title: 接続しているインスタンスのリストを公開する + desc_html: 連合内でこのサーバーが遭遇したドメインの名前 + title: 接続しているサーバーのリストを公開する preview_sensitive_media: desc_html: 他のウェブサイトにリンクを貼った際、メディアが閲覧注意としてマークされていてもサムネイルが表示されます title: OpenGraphによるプレビューで閲覧注意のメディアも表示する @@ -420,21 +420,21 @@ ja: desc_html: ユーザーページにスタッフのバッジを表示します title: スタッフバッジを表示する site_description: - desc_html: フロントページへの表示に使用される紹介文です。このMastodonインスタンスを特徴付けることやその他重要なことを記述してください。HTMLタグ、特に<code><a></code> と <code><em></code>が使えます。 - title: インスタンスの説明 + desc_html: フロントページへの表示に使用される紹介文です。このMastodonサーバーを特徴付けることやその他重要なことを記述してください。HTMLタグ、特に<code><a></code> と <code><em></code>が使えます。 + title: サーバーの説明 site_description_extended: - desc_html: あなたのインスタンスにおける行動規範やルール、ガイドライン、そのほかの記述をする際に最適な場所です。HTMLタグが使えます + desc_html: あなたのサーバーにおける行動規範やルール、ガイドライン、そのほかの記述をする際に最適な場所です。HTMLタグが使えます title: カスタム詳細説明 site_short_description: - desc_html: サイドバーと meta タグに表示されます。Mastodon とは何か、そしてこのサーバーの特別な何かを1段落で記述してください。空欄の場合、インスタンスの説明が使用されます。 - title: 短いインスタンスの説明 + desc_html: サイドバーと meta タグに表示されます。Mastodon とは何か、そしてこのサーバーの特別な何かを1段落で記述してください。空欄の場合、サーバーの説明が使用されます。 + title: 短いサーバーの説明 site_terms: desc_html: あなたは独自のプライバシーポリシーや利用規約、そのほかの法的根拠を書くことができます。HTMLタグが使えます title: カスタム利用規約 - site_title: インスタンスの名前 + site_title: サーバーの名前 thumbnail: desc_html: OpenGraphとAPIによるプレビューに使用されます。サイズは1200×630px推奨です - title: インスタンスのサムネイル + title: サーバーのサムネイル timeline_preview: desc_html: ランディングページに公開タイムラインを表示します title: タイムラインプレビュー @@ -495,7 +495,7 @@ ja: warning: このデータは気をつけて取り扱ってください。他の人と共有しないでください! your_token: アクセストークン auth: - agreement_html: 登録するをクリックすると <a href="%{rules_path}">インスタンスのルール</a> と <a href="%{terms_path}">プライバシーポリシー</a> に従うことに同意したことになります。 + agreement_html: 登録するをクリックすると <a href="%{rules_path}">サーバーのルール</a> と <a href="%{terms_path}">プライバシーポリシー</a> に従うことに同意したことになります。 change_password: パスワード confirm_email: メールアドレスの確認 delete_account: アカウントの削除 @@ -513,7 +513,7 @@ ja: cas: CAS saml: SAML register: 登録する - register_elsewhere: 他のインスタンスで新規登録 + register_elsewhere: 他のサーバーで新規登録 resend_confirmation: 確認メールを再送する reset_password: パスワードを再発行 security: セキュリティ @@ -549,7 +549,7 @@ ja: description_html: あなたのアカウントに含まれるコンテンツは全て削除され、アカウントは無効化されます。これは恒久的なもので、<strong>取り消すことはできません</strong>。なりすましを防ぐために、同じユーザー名で再度登録することはできなくなります。 proceed: アカウントを削除する success_msg: アカウントは正常に削除されました - warning_html: 削除が保証されるのはこのインスタンス上のコンテンツのみです。他のインスタンス等、外部に広く共有されたコンテンツについては痕跡が残ることがあります。また、現在接続できないサーバーや、あなたの更新を受け取らなくなったサーバーに対しては、削除は反映されません。 + warning_html: 削除が保証されるのはこのサーバー上のコンテンツのみです。他のサーバー等、外部に広く共有されたコンテンツについては痕跡が残ることがあります。また、現在接続できないサーバーや、あなたの更新を受け取らなくなったサーバーに対しては、削除は反映されません。 warning_title: 共有されたコンテンツについて directories: directory: ディレクトリ @@ -606,7 +606,7 @@ ja: title: 新規フィルターを追加 followers: domain: ドメイン - explanation_html: あなたの投稿のプライバシーを確保したい場合、誰があなたをフォローしているのかを把握している必要があります。 <strong>プライベート投稿は、あなたのフォロワーがいる全てのインスタンスに配信されます</strong>。 フォロワーのインスタンスの管理者やソフトウェアがあなたのプライバシーを尊重してくれるかどうか怪しい場合は、そのフォロワーを削除した方がよいかもしれません。 + explanation_html: あなたの投稿のプライバシーを確保したい場合、誰があなたをフォローしているのかを把握している必要があります。 <strong>プライベート投稿は、あなたのフォロワーがいる全てのサーバーに配信されます</strong>。 フォロワーのサーバーの管理者やソフトウェアがあなたのプライバシーを尊重してくれるかどうか怪しい場合は、そのフォロワーを削除した方がよいかもしれません。 followers_count: フォロワー数 lock_link: 承認制アカウントにする purge: フォロワーから削除する @@ -628,7 +628,7 @@ ja: one: エラーが発生しました! 以下のエラーを確認してください other: エラーが発生しました! 以下の%{count}個のエラーを確認してください imports: - preface: 他のインスタンスでエクスポートされたファイルから、フォロー/ブロックした情報をこのインスタンス上のアカウントにインポートできます。 + preface: 他のサーバーでエクスポートされたファイルから、フォロー/ブロックした情報をこのサーバー上のアカウントにインポートできます。 success: ファイルは正常にアップロードされ、現在処理中です。しばらくしてから確認してください types: blocking: ブロックしたアカウントリスト @@ -653,7 +653,7 @@ ja: one: '1' other: "%{count}" max_uses_prompt: 無制限 - prompt: リンクを生成・共有してこのインスタンスへの新規登録を受け付けることができます + prompt: リンクを生成・共有してこのサーバーへの新規登録を受け付けることができます table: expires_at: 有効期限 uses: 使用 @@ -800,7 +800,7 @@ ja: development: 開発 edit_profile: プロフィールを編集 export: データのエクスポート - followers: 信頼済みのインスタンス + followers: 信頼済みのサーバー import: データのインポート migrate: アカウントの引っ越し notifications: 通知 @@ -979,13 +979,13 @@ ja: final_action: 始めましょう final_step: 'さあ始めましょう! たとえフォロワーがいなくても、あなたの公開した投稿はローカルタイムラインやハッシュタグなどで誰かの目に止まるかもしれません。自己紹介をしたい時は #introductions ハッシュタグを使うといいかもしれません。' full_handle: あなたの正式なユーザー名 - full_handle_hint: これは別のインスタンスからフォローしてもらったりメッセージのやり取りをする際に、友達に伝えるといいでしょう。 + full_handle_hint: これは別のサーバーからフォローしてもらったりメッセージのやり取りをする際に、友達に伝えるといいでしょう。 review_preferences_action: 設定の変更 review_preferences_step: 受け取りたいメールや投稿の公開範囲などの設定を必ず行ってください。不快でないならアニメーション GIF の自動再生を有効にすることもできます。 subject: Mastodon へようこそ - tip_federated_timeline: 連合タイムラインは Mastodon ネットワークの流れを見られるものです。ただしあなたと同じインスタンスの人がフォローしている人だけが含まれるので、それが全てではありません。 - tip_following: 標準では自動でインスタンスの管理者をフォローしています。もっと興味のある人たちを見つけるには、ローカルタイムラインと連合タイムラインを確認してください。 - tip_local_timeline: ローカルタイムラインは %{instance} にいる人々の流れを見られるものです。彼らはあなたと同じインスタンスにいる隣人のようなものです! + tip_federated_timeline: 連合タイムラインは Mastodon ネットワークの流れを見られるものです。ただしあなたと同じサーバーの人がフォローしている人だけが含まれるので、それが全てではありません。 + tip_following: 標準では自動でサーバーの管理者をフォローしています。もっと興味のある人たちを見つけるには、ローカルタイムラインと連合タイムラインを確認してください。 + tip_local_timeline: ローカルタイムラインは %{instance} にいる人々の流れを見られるものです。彼らはあなたと同じサーバーにいる隣人のようなものです! tip_mobile_webapp: もしモバイル端末のブラウザで Mastodon をホーム画面に追加できる場合、プッシュ通知を受け取ることができます。それはまるでネイティブアプリのように動作します! tips: 豆知識 title: ようこそ、%{name} ! diff --git a/config/locales/simple_form.en.yml b/config/locales/simple_form.en.yml index 3a2746a539..3faaa6ac7b 100644 --- a/config/locales/simple_form.en.yml +++ b/config/locales/simple_form.en.yml @@ -40,7 +40,7 @@ en: featured_tag: name: 'You might want to use one of these:' imports: - data: CSV file exported from another Mastodon instance + data: CSV file exported from another Mastodon server sessions: otp: 'Enter the two-factor code generated by your phone app or use one of your recovery codes:' user: diff --git a/config/locales/simple_form.ja.yml b/config/locales/simple_form.ja.yml index f9beedb7e1..c22b67314a 100644 --- a/config/locales/simple_form.ja.yml +++ b/config/locales/simple_form.ja.yml @@ -37,7 +37,7 @@ ja: username: あなたのユーザー名は %{domain} の中で重複していない必要があります whole_word: キーワードまたはフレーズが英数字のみの場合、単語全体と一致する場合のみ適用されるようになります imports: - data: 他の Mastodon インスタンスからエクスポートしたCSVファイルを選択して下さい + data: 他の Mastodon サーバーからエクスポートしたCSVファイルを選択して下さい sessions: otp: '携帯電話のアプリで生成された二段階認証コードを入力するか、リカバリーコードを使用してください:' user: From e186bd2fb0d2d70c0c401d35cd9f6a63bd20eeac Mon Sep 17 00:00:00 2001 From: abcang <abcang1015@gmail.com> Date: Wed, 6 Feb 2019 10:50:52 +0900 Subject: [PATCH 25/32] Fix Tombstone.delete_all ArgumentError (#9978) --- app/services/activitypub/process_account_service.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/services/activitypub/process_account_service.rb b/app/services/activitypub/process_account_service.rb index 487456f3af..5e33084282 100644 --- a/app/services/activitypub/process_account_service.rb +++ b/app/services/activitypub/process_account_service.rb @@ -212,7 +212,7 @@ class ActivityPub::ProcessAccountService < BaseService end def clear_tombstones! - Tombstone.delete_all(account_id: @account.id) + Tombstone.where(account_id: @account.id).delete_all end def protocol_changed? From e28fe2ef2639d06c0df8717216052720f1c3543f Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <dependabot[bot]@users.noreply.github.com> Date: Wed, 6 Feb 2019 16:46:37 +0100 Subject: [PATCH 26/32] Bump microformats from 4.0.7 to 4.1.0 (#9980) Bumps [microformats](https://github.com/microformats/microformats-ruby) from 4.0.7 to 4.1.0. - [Release notes](https://github.com/microformats/microformats-ruby/releases) - [Commits](https://github.com/microformats/microformats-ruby/compare/v4.0.7...v4.1.0) Signed-off-by: dependabot[bot] <support@dependabot.com> --- Gemfile | 2 +- Gemfile.lock | 8 ++++---- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/Gemfile b/Gemfile index 44cae11ff2..fc919af6ef 100644 --- a/Gemfile +++ b/Gemfile @@ -110,7 +110,7 @@ group :test do gem 'capybara', '~> 3.13' gem 'climate_control', '~> 0.2' gem 'faker', '~> 1.9' - gem 'microformats', '~> 4.0' + gem 'microformats', '~> 4.1' gem 'rails-controller-testing', '~> 1.0' gem 'rspec-sidekiq', '~> 3.0' gem 'simplecov', '~> 0.16', require: false diff --git a/Gemfile.lock b/Gemfile.lock index 5bec39a01f..af76296906 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -335,9 +335,9 @@ GEM redis (>= 3.0.5) memory_profiler (0.9.12) method_source (0.9.2) - microformats (4.0.7) - json - nokogiri + microformats (4.1.0) + json (~> 2.1) + nokogiri (~> 1.8, >= 1.8.3) mime-types (3.2.2) mime-types-data (~> 3.2015) mime-types-data (3.2018.0812) @@ -709,7 +709,7 @@ DEPENDENCIES makara (~> 0.4) mario-redis-lock (~> 1.2) memory_profiler - microformats (~> 4.0) + microformats (~> 4.1) mime-types (~> 3.2) net-ldap (~> 0.10) nokogiri (~> 1.10) From d09ce6d81b542705fcce8f5091d032471531c755 Mon Sep 17 00:00:00 2001 From: ThibG <thib@sitedethib.com> Date: Wed, 6 Feb 2019 23:36:43 +0100 Subject: [PATCH 27/32] Fix IntersectionObserverArticle not hiding some out-of-view items (#9982) IntersectionObserverArticle is made to save on RAM by avoiding fully rendering items that are far out of view. However, it did not work for items spawned outside the intersection observer. --- .../mastodon/components/intersection_observer_article.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/javascript/mastodon/components/intersection_observer_article.js b/app/javascript/mastodon/components/intersection_observer_article.js index de2203a4bc..e453730ba4 100644 --- a/app/javascript/mastodon/components/intersection_observer_article.js +++ b/app/javascript/mastodon/components/intersection_observer_article.js @@ -65,7 +65,7 @@ export default class IntersectionObserverArticle extends React.Component { } updateStateAfterIntersection = (prevState) => { - if (prevState.isIntersecting && !this.entry.isIntersecting) { + if (prevState.isIntersecting !== false && !this.entry.isIntersecting) { scheduleIdleTask(this.hideIfNotIntersecting); } return { From 157d3af46c1a94dd0365b6e33e82919dd3c15fde Mon Sep 17 00:00:00 2001 From: Hinaloe <hina@hinaloe.net> Date: Sat, 9 Feb 2019 11:39:38 +0900 Subject: [PATCH 28/32] Only URLs extract with pre-escaped text (#9991) * [test] add japanese hashtag testcase * Only URLs extract with pre-escaped text ( https://github.com/tootsuite/mastodon/issues/9989 ) --- app/lib/formatter.rb | 2 +- spec/lib/formatter_spec.rb | 8 ++++++++ 2 files changed, 9 insertions(+), 1 deletion(-) diff --git a/app/lib/formatter.rb b/app/lib/formatter.rb index 2e35871696..6603b8df17 100644 --- a/app/lib/formatter.rb +++ b/app/lib/formatter.rb @@ -210,7 +210,7 @@ class Formatter # Note: I couldn't obtain list_slug with @user/list-name format # for mention so this requires additional check - special = Extractor.extract_entities_with_indices(escaped, options).map do |extract| + special = Extractor.extract_urls_with_indices(escaped, options).map do |extract| # exactly one of :url, :hashtag, :screen_name, :cashtag keys is present key = (extract.keys & [:url, :hashtag, :screen_name, :cashtag]).first diff --git a/spec/lib/formatter_spec.rb b/spec/lib/formatter_spec.rb index 9872d37567..8fb6695a9b 100644 --- a/spec/lib/formatter_spec.rb +++ b/spec/lib/formatter_spec.rb @@ -194,6 +194,14 @@ RSpec.describe Formatter do is_expected.to include '/tags/hashtag" class="mention hashtag" rel="tag">#<span>hashtag</span></a>' end end + + context 'given text containing a hashtag with Unicode chars' do + let(:text) { '#hashtagタグ' } + + it 'creates a hashtag link' do + is_expected.to include '/tags/hashtag%E3%82%BF%E3%82%B0" class="mention hashtag" rel="tag">#<span>hashtagタグ</span></a>' + end + end end describe '#format_spoiler' do From a666d1e7edaa8a3da61ce23f648321f7aa61d03b Mon Sep 17 00:00:00 2001 From: rinsuki <428rinsuki+git@gmail.com> Date: Sun, 10 Feb 2019 01:33:41 +0900 Subject: [PATCH 29/32] Enable "displaying application used to post" setting by default (#9994) related: https://github.com/tootsuite/mastodon/pull/9897#issuecomment-461093615 --- config/settings.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/config/settings.yml b/config/settings.yml index 2cf286a9e4..33a03efccb 100644 --- a/config/settings.yml +++ b/config/settings.yml @@ -26,7 +26,7 @@ defaults: &defaults expand_spoilers: false preview_sensitive_media: false reduce_motion: false - show_application: false + show_application: true system_font_ui: false noindex: false theme: 'default' From 016ad37bc8c9ca8bf8f872b8fee704a0388f575e Mon Sep 17 00:00:00 2001 From: Eugen Rochko <eugen@zeonfederated.com> Date: Sat, 9 Feb 2019 20:13:11 +0100 Subject: [PATCH 30/32] Fix URL linkifier grabbing full-width spaces and quotations (#9997) Fix #9993 Fix #5654 --- app/lib/formatter.rb | 12 +++++++- config/initializers/twitter_regex.rb | 4 +-- spec/lib/formatter_spec.rb | 44 ++++++++++++++++++++++++++-- 3 files changed, 55 insertions(+), 5 deletions(-) diff --git a/app/lib/formatter.rb b/app/lib/formatter.rb index 6603b8df17..0653214f53 100644 --- a/app/lib/formatter.rb +++ b/app/lib/formatter.rb @@ -199,12 +199,22 @@ class Formatter result.flatten.join end + UNICODE_ESCAPE_BLACKLIST_RE = /\p{Z}|\p{P}/ + def utf8_friendly_extractor(text, options = {}) old_to_new_index = [0] escaped = text.chars.map do |c| - output = c.ord.to_s(16).length > 2 ? CGI.escape(c) : c + output = begin + if c.ord.to_s(16).length > 2 && UNICODE_ESCAPE_BLACKLIST_RE.match(c).nil? + CGI.escape(c) + else + c + end + end + old_to_new_index << old_to_new_index.last + output.length + output end.join diff --git a/config/initializers/twitter_regex.rb b/config/initializers/twitter_regex.rb index 0e8f5bfeb4..0ddbbee982 100644 --- a/config/initializers/twitter_regex.rb +++ b/config/initializers/twitter_regex.rb @@ -1,7 +1,7 @@ module Twitter class Regex - REGEXEN[:valid_general_url_path_chars] = /[^\p{White_Space}\(\)\?]/iou - REGEXEN[:valid_url_path_ending_chars] = /[^\p{White_Space}\(\)\?!\*';:=\,\.\$%\[\]~&\|@]|(?:#{REGEXEN[:valid_url_balanced_parens]})/iou + REGEXEN[:valid_general_url_path_chars] = /[^\p{White_Space}<>\(\)\?]/iou + REGEXEN[:valid_url_path_ending_chars] = /[^\p{White_Space}\(\)\?!\*"'「」<>;:=\,\.\$%\[\]~&\|@]|(?:#{REGEXEN[:valid_url_balanced_parens]})/iou REGEXEN[:valid_url_balanced_parens] = / \( (?: diff --git a/spec/lib/formatter_spec.rb b/spec/lib/formatter_spec.rb index 8fb6695a9b..96d2fc7e06 100644 --- a/spec/lib/formatter_spec.rb +++ b/spec/lib/formatter_spec.rb @@ -115,6 +115,22 @@ RSpec.describe Formatter do end end + context 'given a URL in quotation marks' do + let(:text) { '"https://example.com/"' } + + it 'does not match the quotation marks' do + is_expected.to include 'href="https://example.com/"' + end + end + + context 'given a URL in angle brackets' do + let(:text) { '<https://example.com/>' } + + it 'does not match the angle brackets' do + is_expected.to include 'href="https://example.com/"' + end + end + context 'given a URL with Japanese path string' do let(:text) { 'https://ja.wikipedia.org/wiki/日本' } @@ -131,6 +147,22 @@ RSpec.describe Formatter do end end + context 'given a URL with a full-width space' do + let(:text) { 'https://example.com/ abc123' } + + it 'does not match the full-width space' do + is_expected.to include 'href="https://example.com/"' + end + end + + context 'given a URL in Japanese quotation marks' do + let(:text) { '「[https://example.org/」' } + + it 'does not match the quotation marks' do + is_expected.to include 'href="https://example.org/"' + end + end + context 'given a URL with Simplified Chinese path string' do let(:text) { 'https://baike.baidu.com/item/中华人民共和国' } @@ -150,7 +182,11 @@ RSpec.describe Formatter do context 'given a URL containing unsafe code (XSS attack, visible part)' do let(:text) { %q{http://example.com/b<del>b</del>} } - it 'escapes the HTML in the URL' do + it 'does not include the HTML in the URL' do + is_expected.to include '"http://example.com/b"' + end + + it 'escapes the HTML' do is_expected.to include '<del>b</del>' end end @@ -158,7 +194,11 @@ RSpec.describe Formatter do context 'given a URL containing unsafe code (XSS attack, invisible part)' do let(:text) { %q{http://example.com/blahblahblahblah/a<script>alert("Hello")</script>} } - it 'escapes the HTML in the URL' do + it 'does not include the HTML in the URL' do + is_expected.to include '"http://example.com/blahblahblahblah/a"' + end + + it 'escapes the HTML' do is_expected.to include '<script>alert("Hello")</script>' end end From fb90ec894ed3ebaf31b860991bd2a79d21c45e13 Mon Sep 17 00:00:00 2001 From: mayaeh <mayaeh@marimo-net.org> Date: Sun, 10 Feb 2019 21:04:59 +0900 Subject: [PATCH 31/32] i18n: add Japanese translations (#10000) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Add Japanese translations. * Probably change to more general "統合" than "マージ" . --- app/javascript/mastodon/locales/ja.json | 1 + config/locales/ja.yml | 11 +++++++++++ config/locales/simple_form.ja.yml | 6 ++++++ 3 files changed, 18 insertions(+) diff --git a/app/javascript/mastodon/locales/ja.json b/app/javascript/mastodon/locales/ja.json index 81b21a94a4..92f9b0effe 100644 --- a/app/javascript/mastodon/locales/ja.json +++ b/app/javascript/mastodon/locales/ja.json @@ -342,6 +342,7 @@ "ui.beforeunload": "Mastodonから離れると送信前の投稿は失われます。", "upload_area.title": "ドラッグ&ドロップでアップロード", "upload_button.label": "メディアを追加 (JPEG, PNG, GIF, WebM, MP4, MOV)", + "upload_error.limit": "アップロードできる上限を超えています。", "upload_form.description": "視覚障害者のための説明", "upload_form.focus": "焦点", "upload_form.undo": "削除", diff --git a/config/locales/ja.yml b/config/locales/ja.yml index 07458d07dd..1a2591cb4e 100644 --- a/config/locales/ja.yml +++ b/config/locales/ja.yml @@ -588,6 +588,10 @@ ja: lists: リスト mutes: ミュート storage: メディア + featured_tags: + add_new: 追加 + errors: + limit: 注目のハッシュタグの上限に達しました filters: contexts: home: ホームタイムライン @@ -628,10 +632,16 @@ ja: one: エラーが発生しました! 以下のエラーを確認してください other: エラーが発生しました! 以下の%{count}個のエラーを確認してください imports: + modes: + merge: 統合 + merge_long: 現在のレコードを保持したまま新しいものを追加します + overwrite: 上書き + overwrite_long: 現在のレコードを新しいもので置き換えます preface: 他のサーバーでエクスポートされたファイルから、フォロー/ブロックした情報をこのサーバー上のアカウントにインポートできます。 success: ファイルは正常にアップロードされ、現在処理中です。しばらくしてから確認してください types: blocking: ブロックしたアカウントリスト + domain_blocking: 非表示にしたドメインリスト following: フォロー中のアカウントリスト muting: ミュートしたアカウントリスト upload: アップロード @@ -800,6 +810,7 @@ ja: development: 開発 edit_profile: プロフィールを編集 export: データのエクスポート + featured_tags: 注目のハッシュタグ followers: 信頼済みのサーバー import: データのインポート migrate: アカウントの引っ越し diff --git a/config/locales/simple_form.ja.yml b/config/locales/simple_form.ja.yml index c22b67314a..87ffe9d143 100644 --- a/config/locales/simple_form.ja.yml +++ b/config/locales/simple_form.ja.yml @@ -33,9 +33,12 @@ ja: setting_display_media_show_all: 閲覧注意としてマークされたメディアも常に表示する setting_hide_network: フォローとフォロワーの情報がプロフィールページで見られないようにします setting_noindex: 公開プロフィールおよび各投稿ページに影響します + setting_show_application: トゥートするのに使用したアプリがトゥートの詳細ビューに表示されるようになります setting_theme: ログインしている全てのデバイスで適用されるデザインです。 username: あなたのユーザー名は %{domain} の中で重複していない必要があります whole_word: キーワードまたはフレーズが英数字のみの場合、単語全体と一致する場合のみ適用されるようになります + featured_tag: + name: 'これらを使うといいかもしれません:' imports: data: 他の Mastodon サーバーからエクスポートしたCSVファイルを選択して下さい sessions: @@ -100,6 +103,7 @@ ja: setting_hide_network: 繋がりを隠す setting_noindex: 検索エンジンによるインデックスを拒否する setting_reduce_motion: アニメーションの動きを減らす + setting_show_application: トゥートの送信に使用したアプリを開示する setting_system_font_ui: システムのデフォルトフォントを使う setting_theme: サイトテーマ setting_unfollow_modal: フォローを解除する前に確認ダイアログを表示する @@ -108,6 +112,8 @@ ja: username: ユーザー名 username_or_email: ユーザー名またはメールアドレス whole_word: 単語全体にマッチ + featured_tag: + name: ハッシュタグ interactions: must_be_follower: フォロワー以外からの通知をブロック must_be_following: フォローしていないユーザーからの通知をブロック From 3031b8a8f22a74a1b691cfe8a31a816ae9cc480d Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <dependabot[bot]@users.noreply.github.com> Date: Sun, 10 Feb 2019 18:17:34 +0100 Subject: [PATCH 32/32] Bump parallel_tests from 2.27.1 to 2.28.0 (#9985) Bumps [parallel_tests](https://github.com/grosser/parallel_tests) from 2.27.1 to 2.28.0. - [Release notes](https://github.com/grosser/parallel_tests/releases) - [Commits](https://github.com/grosser/parallel_tests/compare/v2.27.1...v2.28.0) Signed-off-by: dependabot[bot] <support@dependabot.com> --- Gemfile | 2 +- Gemfile.lock | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/Gemfile b/Gemfile index fc919af6ef..53c8b4602f 100644 --- a/Gemfile +++ b/Gemfile @@ -115,7 +115,7 @@ group :test do gem 'rspec-sidekiq', '~> 3.0' gem 'simplecov', '~> 0.16', require: false gem 'webmock', '~> 3.5' - gem 'parallel_tests', '~> 2.27' + gem 'parallel_tests', '~> 2.28' end group :development do diff --git a/Gemfile.lock b/Gemfile.lock index af76296906..0c342064fc 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -390,7 +390,7 @@ GEM av (~> 0.9.0) paperclip (>= 2.5.2) parallel (1.13.0) - parallel_tests (2.27.1) + parallel_tests (2.28.0) parallel parser (2.6.0.0) ast (~> 2.4.0) @@ -722,7 +722,7 @@ DEPENDENCIES ox (~> 2.10) paperclip (~> 6.0) paperclip-av-transcoder (~> 0.6) - parallel_tests (~> 2.27) + parallel_tests (~> 2.28) pg (~> 1.1) pghero (~> 2.2) pkg-config (~> 1.3)