diff options
29 files changed, 247 insertions, 160 deletions
diff --git a/Gemfile.lock b/Gemfile.lock index 58c59ad35..f6986cb4d 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -299,13 +299,11 @@ GEM sidekiq (>= 3.5.0) statsd-ruby (~> 1.2.0) oj (3.3.9) - openssl (2.0.6) orm_adapter (0.5.0) - ostatus2 (2.0.1) + ostatus2 (2.0.2) addressable (~> 2.4) http (~> 2.0) nokogiri (~> 1.6) - openssl (~> 2.0) ox (2.8.2) paperclip (5.1.0) activemodel (>= 4.2.0) diff --git a/app/controllers/accounts_controller.rb b/app/controllers/accounts_controller.rb index 31144fe05..c9725ed00 100644 --- a/app/controllers/accounts_controller.rb +++ b/app/controllers/accounts_controller.rb @@ -2,7 +2,8 @@ class AccountsController < ApplicationController include AccountControllerConcern - include SignatureVerification + + before_action :set_cache_headers def show respond_to do |format| @@ -27,10 +28,11 @@ class AccountsController < ApplicationController end format.json do - render json: @account, - serializer: ActivityPub::ActorSerializer, - adapter: ActivityPub::Adapter, - content_type: 'application/activity+json' + skip_session! + + render_cached_json(['activitypub', 'actor', @account.cache_key], content_type: 'application/activity+json') do + ActiveModelSerializers::SerializableResource.new(@account, serializer: ActivityPub::ActorSerializer, adapter: ActivityPub::Adapter) + end end end end diff --git a/app/controllers/activitypub/follows_controller.rb b/app/controllers/activitypub/follows_controller.rb index 8b1cddeb4..038bcbabc 100644 --- a/app/controllers/activitypub/follows_controller.rb +++ b/app/controllers/activitypub/follows_controller.rb @@ -4,15 +4,19 @@ class ActivityPub::FollowsController < Api::BaseController include SignatureVerification def show - render( - json: FollowRequest.includes(:account).references(:account).find_by!( - id: params.require(:id), - accounts: { domain: nil, username: params.require(:account_username) }, - target_account: signed_request_account - ), - serializer: ActivityPub::FollowSerializer, - adapter: ActivityPub::Adapter, - content_type: 'application/activity+json' + render json: follow_request, + serializer: ActivityPub::FollowSerializer, + adapter: ActivityPub::Adapter, + content_type: 'application/activity+json' + end + + private + + def follow_request + FollowRequest.includes(:account).references(:account).find_by!( + id: params.require(:id), + accounts: { domain: nil, username: params.require(:account_username) }, + target_account: signed_request_account ) end end diff --git a/app/controllers/application_controller.rb b/app/controllers/application_controller.rb index 46367f202..679b94f1e 100644 --- a/app/controllers/application_controller.rb +++ b/app/controllers/application_controller.rb @@ -198,11 +198,24 @@ class ApplicationController < ActionController::Base end def render_cached_json(cache_key, **options) + options[:expires_in] ||= 3.minutes + options[:public] ||= true + cache_key = cache_key.join(':') if cache_key.is_a?(Enumerable) + content_type = options.delete(:content_type) || 'application/json' + data = Rails.cache.fetch(cache_key, { raw: true }.merge(options)) do yield.to_json end - expires_in options[:expires_in], public: true - render json: data + expires_in options[:expires_in], public: options[:public] + render json: data, content_type: content_type + end + + def set_cache_headers + response.headers['Vary'] = 'Accept' + end + + def skip_session! + request.session_options[:skip] = true end end diff --git a/app/controllers/emojis_controller.rb b/app/controllers/emojis_controller.rb index a82b9340b..c9725ccc0 100644 --- a/app/controllers/emojis_controller.rb +++ b/app/controllers/emojis_controller.rb @@ -2,14 +2,16 @@ class EmojisController < ApplicationController before_action :set_emoji + before_action :set_cache_headers def show respond_to do |format| format.json do - render json: @emoji, - serializer: ActivityPub::EmojiSerializer, - adapter: ActivityPub::Adapter, - content_type: 'application/activity+json' + skip_session! + + render_cached_json(['activitypub', 'emoji', @emoji.cache_key], content_type: 'application/activity+json') do + ActiveModelSerializers::SerializableResource.new(@emoji, serializer: ActivityPub::EmojiSerializer, adapter: ActivityPub::Adapter) + end end end end diff --git a/app/controllers/statuses_controller.rb b/app/controllers/statuses_controller.rb index 6a635fba2..d67fac0e5 100644 --- a/app/controllers/statuses_controller.rb +++ b/app/controllers/statuses_controller.rb @@ -10,7 +10,7 @@ class StatusesController < ApplicationController before_action :set_link_headers before_action :check_account_suspension before_action :redirect_to_original, only: [:show] - before_action { response.headers['Vary'] = 'Accept' } + before_action :set_cache_headers def show respond_to do |format| @@ -23,25 +23,21 @@ class StatusesController < ApplicationController end format.json do - render json: @status, - serializer: ActivityPub::NoteSerializer, - adapter: ActivityPub::Adapter, - content_type: 'application/activity+json' - - # Allow HTTP caching for 3 minutes if the status is public - unless @stream_entry.hidden? - request.session_options[:skip] = true - expires_in(3.minutes, public: true) + skip_session! unless @stream_entry.hidden? + + render_cached_json(['activitypub', 'note', @status.cache_key], content_type: 'application/activity+json', public: !@stream_entry.hidden?) do + ActiveModelSerializers::SerializableResource.new(@status, serializer: ActivityPub::NoteSerializer, adapter: ActivityPub::Adapter) end end end end def activity - render json: @status, - serializer: ActivityPub::ActivitySerializer, - adapter: ActivityPub::Adapter, - content_type: 'application/activity+json' + skip_session! + + render_cached_json(['activitypub', 'activity', @status.cache_key], content_type: 'application/activity+json', public: !@stream_entry.hidden?) do + ActiveModelSerializers::SerializableResource.new(@status, serializer: ActivityPub::ActivitySerializer, adapter: ActivityPub::Adapter) + end end def embed diff --git a/app/helpers/admin/action_logs_helper.rb b/app/helpers/admin/action_logs_helper.rb index e85243e57..4475034a5 100644 --- a/app/helpers/admin/action_logs_helper.rb +++ b/app/helpers/admin/action_logs_helper.rb @@ -34,7 +34,7 @@ module Admin::ActionLogsHelper link_to attributes['domain'], "https://#{attributes['domain']}" when 'Status' tmp_status = Status.new(attributes) - link_to tmp_status.account.acct, TagManager.instance.url_for(tmp_status) + link_to tmp_status.account&.acct || "##{tmp_status.account_id}", TagManager.instance.url_for(tmp_status) end end diff --git a/app/helpers/routing_helper.rb b/app/helpers/routing_helper.rb index 11894a895..998b7566f 100644 --- a/app/helpers/routing_helper.rb +++ b/app/helpers/routing_helper.rb @@ -4,6 +4,7 @@ module RoutingHelper extend ActiveSupport::Concern include Rails.application.routes.url_helpers include ActionView::Helpers::AssetTagHelper + include Webpacker::Helper included do def default_url_options @@ -17,6 +18,10 @@ module RoutingHelper URI.join(root_url, source).to_s end + def full_pack_url(source, **options) + full_asset_url(asset_pack_path(source, options)) + end + private def use_storage? diff --git a/app/javascript/mastodon/features/getting_started/index.js b/app/javascript/mastodon/features/getting_started/index.js index 3c1619c24..ee789e180 100644 --- a/app/javascript/mastodon/features/getting_started/index.js +++ b/app/javascript/mastodon/features/getting_started/index.js @@ -70,30 +70,28 @@ export default class GettingStarted extends ImmutablePureComponent { navItems.push( <ColumnLink key='4' icon='star' text={intl.formatMessage(messages.favourites)} to='/favourites' />, - <ColumnLink key='5' icon='thumb-tack' text={intl.formatMessage(messages.pins)} to='/pinned' />, - <ColumnLink key='6' icon='bars' text={intl.formatMessage(messages.lists)} to='/lists' /> + <ColumnLink key='5' icon='bars' text={intl.formatMessage(messages.lists)} to='/lists' /> ); if (myAccount.get('locked')) { - navItems.push(<ColumnLink key='7' icon='users' text={intl.formatMessage(messages.follow_requests)} to='/follow_requests' />); + navItems.push(<ColumnLink key='6' icon='users' text={intl.formatMessage(messages.follow_requests)} to='/follow_requests' />); } - navItems.push( - <ColumnLink key='8' icon='volume-off' text={intl.formatMessage(messages.mutes)} to='/mutes' />, - <ColumnLink key='9' icon='ban' text={intl.formatMessage(messages.blocks)} to='/blocks' /> - ); - if (multiColumn) { - navItems.push(<ColumnLink key='10' icon='question' text={intl.formatMessage(messages.keyboard_shortcuts)} to='/keyboard-shortcuts' />); + navItems.push(<ColumnLink key='7' icon='question' text={intl.formatMessage(messages.keyboard_shortcuts)} to='/keyboard-shortcuts' />); } + navItems.push(<ColumnLink key='8' icon='book' text={intl.formatMessage(messages.info)} href='/about/more' />); + return ( <Column icon='asterisk' heading={intl.formatMessage(messages.heading)} hideHeadingOnMobile> <div className='getting-started__wrapper'> <ColumnSubheading text={intl.formatMessage(messages.navigation_subheading)} /> {navItems} <ColumnSubheading text={intl.formatMessage(messages.settings_subheading)} /> - <ColumnLink icon='book' text={intl.formatMessage(messages.info)} href='/about/more' /> + <ColumnLink icon='thumb-tack' text={intl.formatMessage(messages.pins)} to='/pinned' /> + <ColumnLink icon='volume-off' text={intl.formatMessage(messages.mutes)} to='/mutes' /> + <ColumnLink icon='ban' text={intl.formatMessage(messages.blocks)} to='/blocks' /> <ColumnLink icon='cog' text={intl.formatMessage(messages.preferences)} href='/settings/preferences' /> <ColumnLink icon='sign-out' text={intl.formatMessage(messages.sign_out)} href='/auth/sign_out' method='delete' /> </div> diff --git a/app/javascript/mastodon/locales/sr-Latn.json b/app/javascript/mastodon/locales/sr-Latn.json index 971ffb5c5..88631e332 100644 --- a/app/javascript/mastodon/locales/sr-Latn.json +++ b/app/javascript/mastodon/locales/sr-Latn.json @@ -11,7 +11,7 @@ "account.media": "Mediji", "account.mention": "Pomeni korisnika @{name}", "account.moved_to": "{name} se pomerio na:", - "account.mute": "Mutiraj @{name}", + "account.mute": "Ućutkaj korisnika @{name}", "account.mute_notifications": "Isključi obaveštenja od korisnika @{name}", "account.posts": "Statusa", "account.report": "Prijavi @{name}", @@ -21,7 +21,7 @@ "account.unblock": "Odblokiraj korisnika @{name}", "account.unblock_domain": "Odblokiraj domen {domain}", "account.unfollow": "Otprati", - "account.unmute": "Odmutiraj @{name}", + "account.unmute": "Ukloni ućutkavanje korisniku @{name}", "account.unmute_notifications": "Uključi nazad obaveštenja od korisnika @{name}", "account.view_full_profile": "Vidi ceo profil", "boost_modal.combo": "Možete pritisnuti {combo} da preskočite ovo sledeći put", @@ -37,10 +37,10 @@ "column.follow_requests": "Zahtevi za praćenje", "column.home": "Početna", "column.lists": "Liste", - "column.mutes": "Mutirani korisnici", + "column.mutes": "Ućutkani korisnici", "column.notifications": "Obaveštenja", "column.pins": "Prikačeni tutovi", - "column.public": "Združena lajna", + "column.public": "Federisana lajna", "column_back_button.label": "Nazad", "column_header.hide_settings": "Sakrij postavke", "column_header.moveLeft_settings": "Pomeri kolonu ulevo", @@ -50,6 +50,7 @@ "column_header.unpin": "Otkači", "column_subheading.navigation": "Navigacija", "column_subheading.settings": "Postavke", + "compose_form.hashtag_warning": "This toot won't be listed under any hashtag as it is unlisted. Only public toots can be searched by hashtag.", "compose_form.lock_disclaimer": "Vaš nalog nije {locked}. Svako može da Vas zaprati i da vidi objave namenjene samo Vašim pratiocima.", "compose_form.lock_disclaimer.lock": "zaključan", "compose_form.placeholder": "Šta Vam je na umu?", @@ -66,9 +67,9 @@ "confirmations.delete_list.confirm": "Obriši", "confirmations.delete_list.message": "Da li ste sigurni da želite da bespovratno obrišete ovu listu?", "confirmations.domain_block.confirm": "Sakrij ceo domen", - "confirmations.domain_block.message": "Da li ste stvarno, stvarno sigurno da želite da blokirate ceo domen {domain}? U većini slučajeva, par dobrih blokiranja ili mutiranja su dovoljna i preporučljiva.", - "confirmations.mute.confirm": "Mutiraj", - "confirmations.mute.message": "Da li stvarno želite da mutirate korisnika {name}?", + "confirmations.domain_block.message": "Da li ste stvarno, stvarno sigurno da želite da blokirate ceo domen {domain}? U većini slučajeva, par dobrih blokiranja ili ućutkavanja su dovoljna i preporučljiva.", + "confirmations.mute.confirm": "Ućutkaj", + "confirmations.mute.message": "Da li stvarno želite da ućutkate korisnika {name}?", "confirmations.unfollow.confirm": "Otprati", "confirmations.unfollow.message": "Da li ste sigurni da želite da otpratite korisnika {name}?", "embed.instructions": "Ugradi ovaj status na Vaš veb sajt kopiranjem koda ispod.", @@ -148,10 +149,10 @@ "navigation_bar.keyboard_shortcuts": "Prečice na tastaturi", "navigation_bar.lists": "Liste", "navigation_bar.logout": "Odjava", - "navigation_bar.mutes": "Mutirani korisnici", + "navigation_bar.mutes": "Ućutkani korisnici", "navigation_bar.pins": "Prikačeni tutovi", "navigation_bar.preferences": "Podešavanja", - "navigation_bar.public_timeline": "Združena lajna", + "navigation_bar.public_timeline": "Federisana lajna", "notification.favourite": "{name} je stavio Vaš status kao omiljeni", "notification.follow": "{name} Vas je zapratio", "notification.mention": "{name} Vas je pomenuo", @@ -169,7 +170,7 @@ "notifications.column_settings.sound": "Puštaj zvuk", "onboarding.done": "Gotovo", "onboarding.next": "Sledeće", - "onboarding.page_five.public_timelines": "Lokalna lajna prikazuje sve javne statuse od svih na domenu {domain}. Združena lajna prikazuje javne statuse od svih ljudi koje prate korisnici sa domena {domain}. Ovo su javne lajne, sjajan način da otkrijete nove ljude.", + "onboarding.page_five.public_timelines": "Lokalna lajna prikazuje sve javne statuse od svih na domenu {domain}. Federisana lajna prikazuje javne statuse od svih ljudi koje prate korisnici sa domena {domain}. Ovo su javne lajne, sjajan način da otkrijete nove ljude.", "onboarding.page_four.home": "Početna lajna prikazuje statuse ljudi koje Vi pratite.", "onboarding.page_four.notifications": "Kolona sa obaveštenjima Vam prikazuje kada neko priča sa Vama.", "onboarding.page_one.federation": "Mastodont je mreža nezavisnih servera koji se uvezuju da naprave jednu veću društvenu mrežu. Ove servere zovemo instancama.", @@ -213,6 +214,7 @@ "search_popout.tips.user": "korisnik", "search_results.total": "{count, number} {count, plural, one {rezultat} few {rezultata} other {rezultata}}", "standalone.public_title": "Pogled iznutra...", + "status.block": "Block @{name}", "status.cannot_reblog": "Ovaj status ne može da se podrži", "status.delete": "Obriši", "status.embed": "Ugradi na sajt", @@ -221,7 +223,8 @@ "status.media_hidden": "Multimedija sakrivena", "status.mention": "Pomeni korisnika @{name}", "status.more": "Još", - "status.mute_conversation": "Mutiraj prepisku", + "status.mute": "Mute @{name}", + "status.mute_conversation": "Ućutkaj prepisku", "status.open": "Proširi ovaj status", "status.pin": "Prikači na profil", "status.reblog": "Podrži", @@ -237,7 +240,7 @@ "status.unmute_conversation": "Uključi prepisku", "status.unpin": "Otkači sa profila", "tabs_bar.compose": "Napiši", - "tabs_bar.federated_timeline": "Združeno", + "tabs_bar.federated_timeline": "Federisano", "tabs_bar.home": "Početna", "tabs_bar.local_timeline": "Lokalno", "tabs_bar.notifications": "Obaveštenja", diff --git a/app/javascript/mastodon/locales/sr.json b/app/javascript/mastodon/locales/sr.json index 7824be2b2..e65c02ab7 100644 --- a/app/javascript/mastodon/locales/sr.json +++ b/app/javascript/mastodon/locales/sr.json @@ -11,7 +11,7 @@ "account.media": "Медији", "account.mention": "Помени корисника @{name}", "account.moved_to": "{name} се померио на:", - "account.mute": "Мутирај @{name}", + "account.mute": "Ућуткај корисника @{name}", "account.mute_notifications": "Искључи обавештења од корисника @{name}", "account.posts": "Статуса", "account.report": "Пријави @{name}", @@ -21,7 +21,7 @@ "account.unblock": "Одблокирај корисника @{name}", "account.unblock_domain": "Одблокирај домен {domain}", "account.unfollow": "Отпрати", - "account.unmute": "Одмутирај @{name}", + "account.unmute": "Уклони ућуткавање кориснику @{name}", "account.unmute_notifications": "Укључи назад обавештења од корисника @{name}", "account.view_full_profile": "Види цео профил", "boost_modal.combo": "Можете притиснути {combo} да прескочите ово следећи пут", @@ -37,10 +37,10 @@ "column.follow_requests": "Захтеви за праћење", "column.home": "Почетна", "column.lists": "Листе", - "column.mutes": "Мутирани корисници", + "column.mutes": "Ућуткани корисници", "column.notifications": "Обавештења", "column.pins": "Прикачени тутови", - "column.public": "Здружена лајна", + "column.public": "Федерисана лајна", "column_back_button.label": "Назад", "column_header.hide_settings": "Сакриј поставке", "column_header.moveLeft_settings": "Помери колону улево", @@ -67,9 +67,9 @@ "confirmations.delete_list.confirm": "Обриши", "confirmations.delete_list.message": "Да ли сте сигурни да желите да бесповратно обришете ову листу?", "confirmations.domain_block.confirm": "Сакриј цео домен", - "confirmations.domain_block.message": "Да ли сте стварно, стварно сигурно да желите да блокирате цео домен {domain}? У већини случајева, пар добрих блокирања или мутирања су довољна и препоручљива.", - "confirmations.mute.confirm": "Мутирај", - "confirmations.mute.message": "Да ли стварно желите да мутирате корисника {name}?", + "confirmations.domain_block.message": "Да ли сте стварно, стварно сигурно да желите да блокирате цео домен {domain}? У већини случајева, пар добрих блокирања или ућуткавања су довољна и препоручљива.", + "confirmations.mute.confirm": "Ућуткај", + "confirmations.mute.message": "Да ли стварно желите да ућуткате корисника {name}?", "confirmations.unfollow.confirm": "Отпрати", "confirmations.unfollow.message": "Да ли сте сигурни да желите да отпратите корисника {name}?", "embed.instructions": "Угради овај статус на Ваш веб сајт копирањем кода испод.", @@ -149,10 +149,10 @@ "navigation_bar.keyboard_shortcuts": "Пречице на тастатури", "navigation_bar.lists": "Листе", "navigation_bar.logout": "Одјава", - "navigation_bar.mutes": "Мутирани корисници", + "navigation_bar.mutes": "Ућуткани корисници", "navigation_bar.pins": "Прикачени тутови", "navigation_bar.preferences": "Подешавања", - "navigation_bar.public_timeline": "Здружена лајна", + "navigation_bar.public_timeline": "Федерисана лајна", "notification.favourite": "{name} је ставио Ваш статус као омиљени", "notification.follow": "{name} Вас је запратио", "notification.mention": "{name} Вас је поменуо", @@ -170,7 +170,7 @@ "notifications.column_settings.sound": "Пуштај звук", "onboarding.done": "Готово", "onboarding.next": "Следеће", - "onboarding.page_five.public_timelines": "Локална лајна приказује све јавне статусе од свих на домену {domain}. Здружена лајна приказује јавне статусе од свих људи које прате корисници са домена {domain}. Ово су јавне лајне, сјајан начин да откријете нове људе.", + "onboarding.page_five.public_timelines": "Локална лајна приказује све јавне статусе од свих на домену {domain}. Федерисана лајна приказује јавне статусе од свих људи које прате корисници са домена {domain}. Ово су јавне лајне, сјајан начин да откријете нове људе.", "onboarding.page_four.home": "Почетна лајна приказује статусе људи које Ви пратите.", "onboarding.page_four.notifications": "Колона са обавештењима Вам приказује када неко прича са Вама.", "onboarding.page_one.federation": "Мастодонт је мрежа независних сервера који се увезују да направе једну већу друштвену мрежу. Ове сервере зовемо инстанцама.", @@ -224,7 +224,7 @@ "status.mention": "Помени корисника @{name}", "status.more": "Још", "status.mute": "Mute @{name}", - "status.mute_conversation": "Мутирај преписку", + "status.mute_conversation": "Ућуткај преписку", "status.open": "Прошири овај статус", "status.pin": "Прикачи на профил", "status.reblog": "Подржи", @@ -240,7 +240,7 @@ "status.unmute_conversation": "Укључи преписку", "status.unpin": "Откачи са профила", "tabs_bar.compose": "Напиши", - "tabs_bar.federated_timeline": "Здружено", + "tabs_bar.federated_timeline": "Федерисано", "tabs_bar.home": "Почетна", "tabs_bar.local_timeline": "Локално", "tabs_bar.notifications": "Обавештења", diff --git a/app/javascript/mastodon/locales/zh-TW.json b/app/javascript/mastodon/locales/zh-TW.json index 876fb0e13..65b174ab5 100644 --- a/app/javascript/mastodon/locales/zh-TW.json +++ b/app/javascript/mastodon/locales/zh-TW.json @@ -64,8 +64,8 @@ "confirmations.block.message": "你確定要封鎖 {name} ?", "confirmations.delete.confirm": "刪除", "confirmations.delete.message": "你確定要刪除這個狀態?", - "confirmations.delete_list.confirm": "Delete", - "confirmations.delete_list.message": "Are you sure you want to permanently delete this list?", + "confirmations.delete_list.confirm": "刪除", + "confirmations.delete_list.message": "確定要永久性地刪除這個名單嗎?", "confirmations.domain_block.confirm": "隱藏整個網域", "confirmations.domain_block.message": "你真的真的確定要隱藏整個 {domain} ?多數情況下,比較推薦封鎖或消音幾個特定目標就好。", "confirmations.mute.confirm": "消音", @@ -128,14 +128,14 @@ "lightbox.close": "關閉", "lightbox.next": "繼續", "lightbox.previous": "回退", - "lists.account.add": "Add to list", - "lists.account.remove": "Remove from list", - "lists.delete": "Delete list", - "lists.edit": "Edit list", - "lists.new.create": "Add list", - "lists.new.title_placeholder": "New list title", - "lists.search": "Search among people you follow", - "lists.subheading": "Your lists", + "lists.account.add": "加到名單裡", + "lists.account.remove": "從名單中移除", + "lists.delete": "刪除名單", + "lists.edit": "修改名單", + "lists.new.create": "新增名單", + "lists.new.title_placeholder": "名單名稱", + "lists.search": "搜尋您關注的使用者", + "lists.subheading": "您的名單", "loading_indicator.label": "讀取中...", "media_gallery.toggle_visible": "切換可見性", "missing_indicator.label": "找不到", @@ -146,8 +146,8 @@ "navigation_bar.favourites": "最愛", "navigation_bar.follow_requests": "關注請求", "navigation_bar.info": "關於本站", - "navigation_bar.keyboard_shortcuts": "Keyboard shortcuts", - "navigation_bar.lists": "Lists", + "navigation_bar.keyboard_shortcuts": "快速鍵", + "navigation_bar.lists": "名單", "navigation_bar.logout": "登出", "navigation_bar.mutes": "消音的使用者", "navigation_bar.pins": "置頂貼文", diff --git a/app/javascript/styles/mastodon/admin.scss b/app/javascript/styles/mastodon/admin.scss index bddea557b..0c343e1df 100644 --- a/app/javascript/styles/mastodon/admin.scss +++ b/app/javascript/styles/mastodon/admin.scss @@ -398,10 +398,12 @@ } } + &__content { + max-width: calc(100% - 90px); + } + &__title { - overflow: hidden; - text-overflow: ellipsis; - white-space: nowrap; + word-wrap: break-word; } &__timestamp { @@ -415,7 +417,7 @@ color: $ui-primary-color; font-family: 'mastodon-font-monospace', monospace; font-size: 12px; - white-space: nowrap; + word-wrap: break-word; min-height: 20px; } diff --git a/app/models/user.rb b/app/models/user.rb index 231271f73..855ae908d 100644 --- a/app/models/user.rb +++ b/app/models/user.rb @@ -126,18 +126,18 @@ class User < ApplicationRecord end def confirm - return if confirmed? + new_user = !confirmed? super - update_statistics! + update_statistics! if new_user end def confirm! - return if confirmed? + new_user = !confirmed? skip_confirmation! save! - update_statistics! + update_statistics! if new_user end def promote! diff --git a/app/serializers/activitypub/delete_actor_serializer.rb b/app/serializers/activitypub/delete_actor_serializer.rb new file mode 100644 index 000000000..dfea9db4a --- /dev/null +++ b/app/serializers/activitypub/delete_actor_serializer.rb @@ -0,0 +1,22 @@ +# frozen_string_literal: true + +class ActivityPub::DeleteActorSerializer < ActiveModel::Serializer + attributes :id, :type, :actor + attribute :virtual_object, key: :object + + def id + [ActivityPub::TagManager.instance.uri_for(object), '#delete'].join + end + + def type + 'Delete' + end + + def actor + ActivityPub::TagManager.instance.uri_for(object) + end + + def virtual_object + actor + end +end diff --git a/app/serializers/rest/instance_serializer.rb b/app/serializers/rest/instance_serializer.rb index abbacc374..65907dad2 100644 --- a/app/serializers/rest/instance_serializer.rb +++ b/app/serializers/rest/instance_serializer.rb @@ -27,7 +27,7 @@ class REST::InstanceSerializer < ActiveModel::Serializer end def thumbnail - full_asset_url(instance_presenter.thumbnail.file.url) if instance_presenter.thumbnail + instance_presenter.thumbnail ? full_asset_url(instance_presenter.thumbnail.file.url) : full_pack_url('preview.jpg') end def max_toot_chars diff --git a/app/services/batched_remove_status_service.rb b/app/services/batched_remove_status_service.rb index 21c775208..cb65a2256 100644 --- a/app/services/batched_remove_status_service.rb +++ b/app/services/batched_remove_status_service.rb @@ -17,9 +17,7 @@ class BatchedRemoveStatusService < BaseService @stream_entry_batches = [] @salmon_batches = [] - @activity_json_batches = [] @json_payloads = statuses.map { |s| [s.id, Oj.dump(event: :delete, payload: s.id.to_s)] }.to_h - @activity_json = {} @activity_xml = {} # Ensure that rendered XML reflects destroyed state @@ -32,10 +30,7 @@ class BatchedRemoveStatusService < BaseService unpush_from_home_timelines(account, account_statuses) unpush_from_list_timelines(account, account_statuses) - if account.local? - batch_stream_entries(account, account_statuses) - batch_activity_json(account, account_statuses) - end + batch_stream_entries(account, account_statuses) if account.local? end # Cannot be batched @@ -47,7 +42,6 @@ class BatchedRemoveStatusService < BaseService Pubsubhubbub::RawDistributionWorker.push_bulk(@stream_entry_batches) { |batch| batch } NotificationWorker.push_bulk(@salmon_batches) { |batch| batch } - ActivityPub::DeliveryWorker.push_bulk(@activity_json_batches) { |batch| batch } end private @@ -58,22 +52,6 @@ class BatchedRemoveStatusService < BaseService end end - def batch_activity_json(account, statuses) - account.followers.inboxes.each do |inbox_url| - statuses.each do |status| - @activity_json_batches << [build_json(status), account.id, inbox_url] - end - end - - statuses.each do |status| - other_recipients = (status.mentions + status.reblogs).map(&:account).reject(&:local?).select(&:activitypub?).uniq(&:id) - - other_recipients.each do |target_account| - @activity_json_batches << [build_json(status), account.id, target_account.inbox_url] - end - end - end - def unpush_from_home_timelines(account, statuses) recipients = account.followers.local.to_a @@ -134,23 +112,9 @@ class BatchedRemoveStatusService < BaseService Redis.current end - def build_json(status) - return @activity_json[status.id] if @activity_json.key?(status.id) - - @activity_json[status.id] = sign_json(status, ActiveModelSerializers::SerializableResource.new( - status, - serializer: status.reblog? ? ActivityPub::UndoAnnounceSerializer : ActivityPub::DeleteSerializer, - adapter: ActivityPub::Adapter - ).as_json) - end - def build_xml(stream_entry) return @activity_xml[stream_entry.id] if @activity_xml.key?(stream_entry.id) @activity_xml[stream_entry.id] = stream_entry_to_xml(stream_entry) end - - def sign_json(status, json) - Oj.dump(ActivityPub::LinkedDataSignature.new(json).sign!(status.account)) - end end diff --git a/app/services/fetch_atom_service.rb b/app/services/fetch_atom_service.rb index 1c47a22da..c01e8d071 100644 --- a/app/services/fetch_atom_service.rb +++ b/app/services/fetch_atom_service.rb @@ -50,7 +50,7 @@ class FetchAtomService < BaseService @unsupported_activity = true nil end - elsif @response['Link'] && !terminal + elsif @response['Link'] && !terminal && link_header.find_link(%w(rel alternate)) process_headers elsif @response.mime_type == 'text/html' && !terminal process_html @@ -70,8 +70,6 @@ class FetchAtomService < BaseService end def process_headers - link_header = LinkHeader.parse(@response['Link'].is_a?(Array) ? @response['Link'].first : @response['Link']) - json_link = link_header.find_link(%w(rel alternate), %w(type application/activity+json)) || link_header.find_link(%w(rel alternate), ['type', 'application/ld+json; profile="https://www.w3.org/ns/activitystreams"']) atom_link = link_header.find_link(%w(rel alternate), %w(type application/atom+xml)) @@ -80,4 +78,8 @@ class FetchAtomService < BaseService result end + + def link_header + @link_header ||= LinkHeader.parse(@response['Link'].is_a?(Array) ? @response['Link'].first : @response['Link']) + end end diff --git a/app/services/suspend_account_service.rb b/app/services/suspend_account_service.rb index 958b28cdc..56fa2d8dd 100644 --- a/app/services/suspend_account_service.rb +++ b/app/services/suspend_account_service.rb @@ -22,6 +22,8 @@ class SuspendAccountService < BaseService end def purge_content! + ActivityPub::RawDistributionWorker.perform_async(delete_actor_json, @account.id) if @account.local? + @account.statuses.reorder(nil).find_in_batches do |statuses| BatchedRemoveStatusService.new.call(statuses) end @@ -54,4 +56,14 @@ class SuspendAccountService < BaseService def destroy_all(association) association.in_batches.destroy_all end + + def delete_actor_json + payload = ActiveModelSerializers::SerializableResource.new( + @account, + serializer: ActivityPub::DeleteActorSerializer, + adapter: ActivityPub::Adapter + ).as_json + + Oj.dump(ActivityPub::LinkedDataSignature.new(payload).sign!(@account)) + end end diff --git a/config/locales/pl.yml b/config/locales/pl.yml index 8b6d97b94..0d24367d9 100644 --- a/config/locales/pl.yml +++ b/config/locales/pl.yml @@ -160,6 +160,7 @@ pl: update_status: "%{name} zaktualizował wpis użytkownika %{target}" title: Dziennik działań administracyjnych custom_emojis: + by_domain: Według domeny copied_msg: Pomyślnie utworzono lokalną kopię emoji copy: Kopiuj copy_failed_msg: Nie udało się utworzyć lokalnej kopii emoji @@ -620,6 +621,7 @@ pl: private: Nie możesz przypiąć niepublicznego wpisu reblog: Nie możesz przypiąć podbicia wpisu show_more: Pokaż więcej + title: '%{name}: "%{quote}"' visibilities: private: Tylko dla śledzących private_long: Widoczne tylko dla osób, które Cię śledzą diff --git a/config/locales/simple_form.zh-TW.yml b/config/locales/simple_form.zh-TW.yml index c82f07e2d..b5edaea72 100644 --- a/config/locales/simple_form.zh-TW.yml +++ b/config/locales/simple_form.zh-TW.yml @@ -21,7 +21,7 @@ zh-TW: data: 資料 display_name: 顯示名稱 email: 電子信箱 - filtered_languages: 封鎖下面语言的文章 + filtered_languages: 封鎖下面語言的文章 header: 個人頁面頂部 locale: 語言 locked: 將帳號轉為「私密」 @@ -29,7 +29,16 @@ zh-TW: note: 簡介 otp_attempt: 雙因子驗證碼 password: 密碼 + setting_auto_play_gif: 自動播放 GIFs + setting_boost_modal: 轉推前跳出確認視窗 setting_default_privacy: 文章預設隱私度 + setting_default_sensitive: 預設我的內容為敏感內容 + setting_delete_modal: 刪推前跳出確認視窗 + setting_noindex: 不被搜尋引擎檢索 + setting_reduce_motion: 減低動畫效果 + setting_system_font_ui: 使用系統預設字體 + setting_theme: 網站主題 + setting_unfollow_modal: 取消關注前跳出確認視窗 type: 匯入資料類型 username: 使用者名稱 interactions: diff --git a/config/locales/sr-Latn.yml b/config/locales/sr-Latn.yml index 45fb1c1ba..964a82d64 100644 --- a/config/locales/sr-Latn.yml +++ b/config/locales/sr-Latn.yml @@ -409,8 +409,8 @@ sr-Latn: exports: blocks: Blokirali ste csv: CSV - follows: PRatite - mutes: Mutirali ste + follows: Pratite + mutes: Ućutkali ste storage: Multimedijalno skladište followers: domain: Domen @@ -441,7 +441,7 @@ sr-Latn: types: blocking: Lista blokiranja following: Lista pratilaca - muting: Lista mutiranih + muting: Lista ućutkanih upload: Otpremi in_memoriam_html: In Memoriam. invites: diff --git a/config/locales/sr.yml b/config/locales/sr.yml index 6961ff841..57ccf2008 100644 --- a/config/locales/sr.yml +++ b/config/locales/sr.yml @@ -409,8 +409,8 @@ sr: exports: blocks: Блокирали сте csv: CSV - follows: ПРатите - mutes: Мутирали сте + follows: Пратите + mutes: Ућуткали сте storage: Мултимедијално складиште followers: domain: Домен @@ -441,7 +441,7 @@ sr: types: blocking: Листа блокирања following: Листа пратилаца - muting: Листа мутираних + muting: Листа ућутканих upload: Отпреми in_memoriam_html: In Memoriam. invites: diff --git a/config/locales/zh-TW.yml b/config/locales/zh-TW.yml index 7a66a64ca..e73dbf9cc 100644 --- a/config/locales/zh-TW.yml +++ b/config/locales/zh-TW.yml @@ -55,7 +55,7 @@ zh-TW: perform_full_suspension: 進行停權 profile_url: 個人檔案網址 public: 公開 - push_subscription_expires: PuSH 訂閱逾期 + push_subscription_expires: 推播訂閱過期 salmon_url: Salmon URL silence: 靜音 statuses: 狀態 @@ -133,12 +133,14 @@ zh-TW: forgot_password: 忘記密碼? login: 登入 logout: 登出 + migrate_account: 轉移到另一個帳號 + migrate_account_html: 想要將這個帳號指向另一個帳號可到<a href="%{path}">到這裡設定</a>。 register: 註冊 resend_confirmation: 重寄驗證信 reset_password: 重設密碼 set_new_password: 設定新密碼 authorize_follow: - error: 對不起,尋找這個跨站使用者的過程發生錯誤 + error: 對不起,搜尋遠端使用者出現錯誤 follow: 關注 title: 關注 %{acct} datetime: @@ -165,7 +167,16 @@ zh-TW: blocks: 您封鎖的使用者 csv: CSV follows: 您關注的使用者 + mutes: 您靜音的使用者 storage: 儲存空間大小 + followers: + domain: 網域 + explanation_html: 為確保個人隱私,您必須知道有哪些使用者正關注你。<strong>您的私密內容會被發送到所有您有被關注的服務站上</strong>。如果您不信任這些服務站的管理者,您可以選擇檢查或刪除您的關注者。 + followers_count: 關注者數 + lock_link: 鎖住你的帳號 + purge: 移除關注者 + unlocked_warning_html: 所有人都可以關注並檢索你的隱藏狀態。%{lock_link}以檢查或拒絕關注。 + unlocked_warning_title: 你的帳號是公開的 generic: changes_saved_msg: 已成功儲存修改 powered_by: 網站由 %{link} 開發 @@ -179,6 +190,7 @@ zh-TW: types: blocking: 您封鎖的使用者名單 following: 您關注的使用者名單 + muting: 您靜音的使用者名單 upload: 上傳 landing_strip_html: "<strong>%{name}</strong> 是一個在 %{link_to_root_path} 的使用者。只要您有任何 Mastodon 服務站、或者聯盟網站的帳號,便可以跨站關注此站使用者,或者與他們互動。" landing_strip_signup_html: 如果您沒有這些帳號,歡迎在<a href="%{sign_up_path}">這裡註冊</a>。 @@ -231,15 +243,26 @@ zh-TW: missing_resource: 無法找到資源 proceed: 下一步 prompt: 您希望關注︰ + sessions: + activity: 最近活動 + browser: 瀏覽器 + current_session: 目前的 session + description: "%{platform} 上的 %{browser}" + explanation: 這些是現在正登入於你的 Mastodon 帳號的瀏覽器。 + revoke: 取消 + revoke_success: Session 取消成功。 settings: authorized_apps: 已授權應用程式 back: 回到 Mastodon + development: 開發 edit_profile: 修改個人資料 export: 匯出 + followers: 授權追蹤者 import: 匯入 + notifications: 通知 preferences: 偏好設定 settings: 設定 - two_factor_authentication: 雙因子認證 + two_factor_authentication: 兩階段認證 statuses: open_in_web: 以網頁開啟 over_character_limit: 超過了 %{max} 字的限制 @@ -257,14 +280,14 @@ zh-TW: default: "%Y年%-m月%d日 %H:%M" two_factor_authentication: code_hint: 請輸入您認證器產生的代碼,以進行認證 - description_html: 當您啟用<strong>雙因子認證</strong>後,您登入時將需要使您手機、或其他種類認證器產生的代碼。 + description_html: 啟用<strong>兩階段認證</strong>後,登入時將需要使手機、或其他種類認證器產生的代碼。 disable: 停用 enable: 啟用 - enabled_success: 已成功啟用雙因子認證 - instructions_html: "<strong>請用您手機的認證器應用程式(如 Google Authenticator、Authy),掃描這裡的 QR 圖形碼</strong>。在雙因子認證啟用後,您登入時將須要使用此應用程式產生的認證碼。" + enabled_success: 已成功啟用兩階段認證 + instructions_html: "<strong>請用您手機的認證器應用程式(如 Google Authenticator、Authy),掃描這裡的 QR 圖形碼</strong>。在兩階段認證啟用後,您登入時將須要使用此應用程式產生的認證碼。" manual_instructions: 如果您無法掃描 QR 圖形碼,請手動輸入︰ setup: 設定 wrong_code: 您輸入的認證碼並不正確!可能伺服器時間和您手機不一致,請檢查您手機的時間,或與本站管理員聯絡。 users: invalid_email: 信箱地址格式不正確 - invalid_otp_token: 雙因子認證碼不正確 + invalid_otp_token: 兩階段認證碼不正確 diff --git a/db/migrate/20171129172043_add_index_on_stream_entries.rb b/db/migrate/20171129172043_add_index_on_stream_entries.rb index 478530c7f..181c4f288 100644 --- a/db/migrate/20171129172043_add_index_on_stream_entries.rb +++ b/db/migrate/20171129172043_add_index_on_stream_entries.rb @@ -1,6 +1,7 @@ class AddIndexOnStreamEntries < ActiveRecord::Migration[5.1] + disable_ddl_transaction! + def change - commit_db_transaction add_index :stream_entries, [:account_id, :activity_type, :id], algorithm: :concurrently remove_index :stream_entries, name: :index_stream_entries_on_account_id end diff --git a/db/migrate/20171226094803_more_faster_index_on_notifications.rb b/db/migrate/20171226094803_more_faster_index_on_notifications.rb index b2e53b82d..0273a4e7c 100644 --- a/db/migrate/20171226094803_more_faster_index_on_notifications.rb +++ b/db/migrate/20171226094803_more_faster_index_on_notifications.rb @@ -1,6 +1,7 @@ class MoreFasterIndexOnNotifications < ActiveRecord::Migration[5.1] + disable_ddl_transaction! + def change - commit_db_transaction add_index :notifications, [:account_id, :id], order: { id: :desc }, algorithm: :concurrently remove_index :notifications, name: :index_notifications_on_id_and_account_id_and_activity_type end diff --git a/lib/mastodon/version.rb b/lib/mastodon/version.rb index ca129da80..007768769 100644 --- a/lib/mastodon/version.rb +++ b/lib/mastodon/version.rb @@ -13,7 +13,7 @@ module Mastodon end def patch - 0 + 1 end def pre diff --git a/spec/controllers/auth/confirmations_controller_spec.rb b/spec/controllers/auth/confirmations_controller_spec.rb index 2ec36c060..80a06c43a 100644 --- a/spec/controllers/auth/confirmations_controller_spec.rb +++ b/spec/controllers/auth/confirmations_controller_spec.rb @@ -12,20 +12,40 @@ describe Auth::ConfirmationsController, type: :controller do end describe 'GET #show' do - let!(:user) { Fabricate(:user, confirmation_token: 'foobar', confirmed_at: nil) } + context 'when user is unconfirmed' do + let!(:user) { Fabricate(:user, confirmation_token: 'foobar', confirmed_at: nil) } - before do - allow(BootstrapTimelineWorker).to receive(:perform_async) - @request.env['devise.mapping'] = Devise.mappings[:user] - get :show, params: { confirmation_token: 'foobar' } - end + before do + allow(BootstrapTimelineWorker).to receive(:perform_async) + @request.env['devise.mapping'] = Devise.mappings[:user] + get :show, params: { confirmation_token: 'foobar' } + end + + it 'redirects to login' do + expect(response).to redirect_to(new_user_session_path) + end - it 'redirects to login' do - expect(response).to redirect_to(new_user_session_path) + it 'queues up bootstrapping of home timeline' do + expect(BootstrapTimelineWorker).to have_received(:perform_async).with(user.account_id) + end end - it 'queues up bootstrapping of home timeline' do - expect(BootstrapTimelineWorker).to have_received(:perform_async).with(user.account_id) + context 'when user is updating email' do + let!(:user) { Fabricate(:user, confirmation_token: 'foobar', unconfirmed_email: 'new-email@example.com') } + + before do + allow(BootstrapTimelineWorker).to receive(:perform_async) + @request.env['devise.mapping'] = Devise.mappings[:user] + get :show, params: { confirmation_token: 'foobar' } + end + + it 'redirects to login' do + expect(response).to redirect_to(new_user_session_path) + end + + it 'does not queue up bootstrapping of home timeline' do + expect(BootstrapTimelineWorker).to_not have_received(:perform_async) + end end end end diff --git a/spec/models/user_spec.rb b/spec/models/user_spec.rb index 5ed7ed88b..8171c939a 100644 --- a/spec/models/user_spec.rb +++ b/spec/models/user_spec.rb @@ -148,6 +148,14 @@ RSpec.describe User, type: :model do end end + describe '#confirm' do + it 'sets email to unconfirmed_email' do + user = Fabricate.build(:user, confirmed_at: Time.now.utc, unconfirmed_email: 'new-email@example.com') + user.confirm + expect(user.email).to eq 'new-email@example.com' + end + end + describe '#disable_two_factor!' do it 'saves false for otp_required_for_login' do user = Fabricate.build(:user, otp_required_for_login: true) |