mirror of
https://github.com/glitch-soc/mastodon.git
synced 2025-12-16 09:18:46 +00:00
Compare commits
13 Commits
compose-re
...
fix-column
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
ac686d5a5d | ||
|
|
ec620ae486 | ||
|
|
3b016342c6 | ||
|
|
3c18964256 | ||
|
|
c61dd918a2 | ||
|
|
02ba03d6db | ||
|
|
3bee0996c5 | ||
|
|
89daeb43a8 | ||
|
|
7d4f4f9aab | ||
|
|
256c2b1de0 | ||
|
|
02e3e1ec09 | ||
|
|
ff924f95bb | ||
|
|
c10f4bdb03 |
@@ -299,13 +299,11 @@ GEM
|
|||||||
sidekiq (>= 3.5.0)
|
sidekiq (>= 3.5.0)
|
||||||
statsd-ruby (~> 1.2.0)
|
statsd-ruby (~> 1.2.0)
|
||||||
oj (3.3.9)
|
oj (3.3.9)
|
||||||
openssl (2.0.6)
|
|
||||||
orm_adapter (0.5.0)
|
orm_adapter (0.5.0)
|
||||||
ostatus2 (2.0.1)
|
ostatus2 (2.0.2)
|
||||||
addressable (~> 2.4)
|
addressable (~> 2.4)
|
||||||
http (~> 2.0)
|
http (~> 2.0)
|
||||||
nokogiri (~> 1.6)
|
nokogiri (~> 1.6)
|
||||||
openssl (~> 2.0)
|
|
||||||
ox (2.8.2)
|
ox (2.8.2)
|
||||||
paperclip (5.1.0)
|
paperclip (5.1.0)
|
||||||
activemodel (>= 4.2.0)
|
activemodel (>= 4.2.0)
|
||||||
|
|||||||
@@ -2,7 +2,8 @@
|
|||||||
|
|
||||||
class AccountsController < ApplicationController
|
class AccountsController < ApplicationController
|
||||||
include AccountControllerConcern
|
include AccountControllerConcern
|
||||||
include SignatureVerification
|
|
||||||
|
before_action :set_cache_headers
|
||||||
|
|
||||||
def show
|
def show
|
||||||
respond_to do |format|
|
respond_to do |format|
|
||||||
@@ -26,10 +27,11 @@ class AccountsController < ApplicationController
|
|||||||
end
|
end
|
||||||
|
|
||||||
format.json do
|
format.json do
|
||||||
render json: @account,
|
skip_session!
|
||||||
serializer: ActivityPub::ActorSerializer,
|
|
||||||
adapter: ActivityPub::Adapter,
|
render_cached_json(['activitypub', 'actor', @account.cache_key], content_type: 'application/activity+json') do
|
||||||
content_type: 'application/activity+json'
|
ActiveModelSerializers::SerializableResource.new(@account, serializer: ActivityPub::ActorSerializer, adapter: ActivityPub::Adapter)
|
||||||
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|||||||
@@ -4,15 +4,19 @@ class ActivityPub::FollowsController < Api::BaseController
|
|||||||
include SignatureVerification
|
include SignatureVerification
|
||||||
|
|
||||||
def show
|
def show
|
||||||
render(
|
render json: follow_request,
|
||||||
json: FollowRequest.includes(:account).references(:account).find_by!(
|
serializer: ActivityPub::FollowSerializer,
|
||||||
id: params.require(:id),
|
adapter: ActivityPub::Adapter,
|
||||||
accounts: { domain: nil, username: params.require(:account_username) },
|
content_type: 'application/activity+json'
|
||||||
target_account: signed_request_account
|
end
|
||||||
),
|
|
||||||
serializer: ActivityPub::FollowSerializer,
|
private
|
||||||
adapter: ActivityPub::Adapter,
|
|
||||||
content_type: 'application/activity+json'
|
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
|
||||||
end
|
end
|
||||||
|
|||||||
@@ -123,11 +123,24 @@ class ApplicationController < ActionController::Base
|
|||||||
end
|
end
|
||||||
|
|
||||||
def render_cached_json(cache_key, **options)
|
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
|
data = Rails.cache.fetch(cache_key, { raw: true }.merge(options)) do
|
||||||
yield.to_json
|
yield.to_json
|
||||||
end
|
end
|
||||||
|
|
||||||
expires_in options[:expires_in], public: true
|
expires_in options[:expires_in], public: options[:public]
|
||||||
render json: data
|
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
|
||||||
end
|
end
|
||||||
|
|||||||
@@ -2,14 +2,16 @@
|
|||||||
|
|
||||||
class EmojisController < ApplicationController
|
class EmojisController < ApplicationController
|
||||||
before_action :set_emoji
|
before_action :set_emoji
|
||||||
|
before_action :set_cache_headers
|
||||||
|
|
||||||
def show
|
def show
|
||||||
respond_to do |format|
|
respond_to do |format|
|
||||||
format.json do
|
format.json do
|
||||||
render json: @emoji,
|
skip_session!
|
||||||
serializer: ActivityPub::EmojiSerializer,
|
|
||||||
adapter: ActivityPub::Adapter,
|
render_cached_json(['activitypub', 'emoji', @emoji.cache_key], content_type: 'application/activity+json') do
|
||||||
content_type: 'application/activity+json'
|
ActiveModelSerializers::SerializableResource.new(@emoji, serializer: ActivityPub::EmojiSerializer, adapter: ActivityPub::Adapter)
|
||||||
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|||||||
@@ -10,7 +10,7 @@ class StatusesController < ApplicationController
|
|||||||
before_action :set_link_headers
|
before_action :set_link_headers
|
||||||
before_action :check_account_suspension
|
before_action :check_account_suspension
|
||||||
before_action :redirect_to_original, only: [:show]
|
before_action :redirect_to_original, only: [:show]
|
||||||
before_action { response.headers['Vary'] = 'Accept' }
|
before_action :set_cache_headers
|
||||||
|
|
||||||
def show
|
def show
|
||||||
respond_to do |format|
|
respond_to do |format|
|
||||||
@@ -22,25 +22,21 @@ class StatusesController < ApplicationController
|
|||||||
end
|
end
|
||||||
|
|
||||||
format.json do
|
format.json do
|
||||||
render json: @status,
|
skip_session! unless @stream_entry.hidden?
|
||||||
serializer: ActivityPub::NoteSerializer,
|
|
||||||
adapter: ActivityPub::Adapter,
|
|
||||||
content_type: 'application/activity+json'
|
|
||||||
|
|
||||||
# Allow HTTP caching for 3 minutes if the status is public
|
render_cached_json(['activitypub', 'note', @status.cache_key], content_type: 'application/activity+json', public: !@stream_entry.hidden?) do
|
||||||
unless @stream_entry.hidden?
|
ActiveModelSerializers::SerializableResource.new(@status, serializer: ActivityPub::NoteSerializer, adapter: ActivityPub::Adapter)
|
||||||
request.session_options[:skip] = true
|
|
||||||
expires_in(3.minutes, public: true)
|
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
def activity
|
def activity
|
||||||
render json: @status,
|
skip_session!
|
||||||
serializer: ActivityPub::ActivitySerializer,
|
|
||||||
adapter: ActivityPub::Adapter,
|
render_cached_json(['activitypub', 'activity', @status.cache_key], content_type: 'application/activity+json', public: !@stream_entry.hidden?) do
|
||||||
content_type: 'application/activity+json'
|
ActiveModelSerializers::SerializableResource.new(@status, serializer: ActivityPub::ActivitySerializer, adapter: ActivityPub::Adapter)
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
def embed
|
def embed
|
||||||
|
|||||||
@@ -34,7 +34,7 @@ module Admin::ActionLogsHelper
|
|||||||
link_to attributes['domain'], "https://#{attributes['domain']}"
|
link_to attributes['domain'], "https://#{attributes['domain']}"
|
||||||
when 'Status'
|
when 'Status'
|
||||||
tmp_status = Status.new(attributes)
|
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
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ module RoutingHelper
|
|||||||
extend ActiveSupport::Concern
|
extend ActiveSupport::Concern
|
||||||
include Rails.application.routes.url_helpers
|
include Rails.application.routes.url_helpers
|
||||||
include ActionView::Helpers::AssetTagHelper
|
include ActionView::Helpers::AssetTagHelper
|
||||||
|
include Webpacker::Helper
|
||||||
|
|
||||||
included do
|
included do
|
||||||
def default_url_options
|
def default_url_options
|
||||||
@@ -17,6 +18,10 @@ module RoutingHelper
|
|||||||
URI.join(root_url, source).to_s
|
URI.join(root_url, source).to_s
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def full_pack_url(source, **options)
|
||||||
|
full_asset_url(asset_pack_path(source, options))
|
||||||
|
end
|
||||||
|
|
||||||
private
|
private
|
||||||
|
|
||||||
def use_storage?
|
def use_storage?
|
||||||
|
|||||||
@@ -23,7 +23,6 @@ export default class ColumnHeader extends React.PureComponent {
|
|||||||
icon: PropTypes.string.isRequired,
|
icon: PropTypes.string.isRequired,
|
||||||
active: PropTypes.bool,
|
active: PropTypes.bool,
|
||||||
multiColumn: PropTypes.bool,
|
multiColumn: PropTypes.bool,
|
||||||
focusable: PropTypes.bool,
|
|
||||||
showBackButton: PropTypes.bool,
|
showBackButton: PropTypes.bool,
|
||||||
children: PropTypes.node,
|
children: PropTypes.node,
|
||||||
pinned: PropTypes.bool,
|
pinned: PropTypes.bool,
|
||||||
@@ -32,10 +31,6 @@ export default class ColumnHeader extends React.PureComponent {
|
|||||||
onClick: PropTypes.func,
|
onClick: PropTypes.func,
|
||||||
};
|
};
|
||||||
|
|
||||||
static defaultProps = {
|
|
||||||
focusable: true,
|
|
||||||
}
|
|
||||||
|
|
||||||
state = {
|
state = {
|
||||||
collapsed: true,
|
collapsed: true,
|
||||||
animating: false,
|
animating: false,
|
||||||
@@ -68,7 +63,7 @@ export default class ColumnHeader extends React.PureComponent {
|
|||||||
}
|
}
|
||||||
|
|
||||||
render () {
|
render () {
|
||||||
const { title, icon, active, children, pinned, onPin, multiColumn, focusable, showBackButton, intl: { formatMessage } } = this.props;
|
const { title, icon, active, children, pinned, onPin, multiColumn, showBackButton, intl: { formatMessage } } = this.props;
|
||||||
const { collapsed, animating } = this.state;
|
const { collapsed, animating } = this.state;
|
||||||
|
|
||||||
const wrapperClassName = classNames('column-header__wrapper', {
|
const wrapperClassName = classNames('column-header__wrapper', {
|
||||||
@@ -135,11 +130,13 @@ export default class ColumnHeader extends React.PureComponent {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<div className={wrapperClassName}>
|
<div className={wrapperClassName}>
|
||||||
<h1 tabIndex={focusable ? 0 : null} role='button' className={buttonClassName} aria-label={title} onClick={this.handleTitleClick}>
|
<h1 className={buttonClassName}>
|
||||||
<i className={`fa fa-fw fa-${icon} column-header__icon`} />
|
<button onClick={this.handleTitleClick}>
|
||||||
<span className='column-header__title'>
|
<i className={`fa fa-fw fa-${icon} column-header__icon`} />
|
||||||
{title}
|
<span className='column-header__title'>
|
||||||
</span>
|
{title}
|
||||||
|
</span>
|
||||||
|
</button>
|
||||||
|
|
||||||
<div className='column-header__buttons'>
|
<div className='column-header__buttons'>
|
||||||
{backButton}
|
{backButton}
|
||||||
|
|||||||
@@ -70,30 +70,28 @@ export default class GettingStarted extends ImmutablePureComponent {
|
|||||||
|
|
||||||
navItems.push(
|
navItems.push(
|
||||||
<ColumnLink key='4' icon='star' text={intl.formatMessage(messages.favourites)} to='/favourites' />,
|
<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='5' icon='bars' text={intl.formatMessage(messages.lists)} to='/lists' />
|
||||||
<ColumnLink key='6' icon='bars' text={intl.formatMessage(messages.lists)} to='/lists' />
|
|
||||||
);
|
);
|
||||||
|
|
||||||
if (myAccount.get('locked')) {
|
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) {
|
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 (
|
return (
|
||||||
<Column icon='asterisk' heading={intl.formatMessage(messages.heading)} hideHeadingOnMobile>
|
<Column icon='asterisk' heading={intl.formatMessage(messages.heading)} hideHeadingOnMobile>
|
||||||
<div className='getting-started__wrapper'>
|
<div className='getting-started__wrapper'>
|
||||||
<ColumnSubheading text={intl.formatMessage(messages.navigation_subheading)} />
|
<ColumnSubheading text={intl.formatMessage(messages.navigation_subheading)} />
|
||||||
{navItems}
|
{navItems}
|
||||||
<ColumnSubheading text={intl.formatMessage(messages.settings_subheading)} />
|
<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='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' />
|
<ColumnLink icon='sign-out' text={intl.formatMessage(messages.sign_out)} href='/auth/sign_out' method='delete' />
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -64,8 +64,8 @@
|
|||||||
"confirmations.block.message": "你確定要封鎖 {name} ?",
|
"confirmations.block.message": "你確定要封鎖 {name} ?",
|
||||||
"confirmations.delete.confirm": "刪除",
|
"confirmations.delete.confirm": "刪除",
|
||||||
"confirmations.delete.message": "你確定要刪除這個狀態?",
|
"confirmations.delete.message": "你確定要刪除這個狀態?",
|
||||||
"confirmations.delete_list.confirm": "Delete",
|
"confirmations.delete_list.confirm": "刪除",
|
||||||
"confirmations.delete_list.message": "Are you sure you want to permanently delete this list?",
|
"confirmations.delete_list.message": "確定要永久性地刪除這個名單嗎?",
|
||||||
"confirmations.domain_block.confirm": "隱藏整個網域",
|
"confirmations.domain_block.confirm": "隱藏整個網域",
|
||||||
"confirmations.domain_block.message": "你真的真的確定要隱藏整個 {domain} ?多數情況下,比較推薦封鎖或消音幾個特定目標就好。",
|
"confirmations.domain_block.message": "你真的真的確定要隱藏整個 {domain} ?多數情況下,比較推薦封鎖或消音幾個特定目標就好。",
|
||||||
"confirmations.mute.confirm": "消音",
|
"confirmations.mute.confirm": "消音",
|
||||||
@@ -128,14 +128,14 @@
|
|||||||
"lightbox.close": "關閉",
|
"lightbox.close": "關閉",
|
||||||
"lightbox.next": "繼續",
|
"lightbox.next": "繼續",
|
||||||
"lightbox.previous": "回退",
|
"lightbox.previous": "回退",
|
||||||
"lists.account.add": "Add to list",
|
"lists.account.add": "加到名單裡",
|
||||||
"lists.account.remove": "Remove from list",
|
"lists.account.remove": "從名單中移除",
|
||||||
"lists.delete": "Delete list",
|
"lists.delete": "刪除名單",
|
||||||
"lists.edit": "Edit list",
|
"lists.edit": "修改名單",
|
||||||
"lists.new.create": "Add list",
|
"lists.new.create": "新增名單",
|
||||||
"lists.new.title_placeholder": "New list title",
|
"lists.new.title_placeholder": "名單名稱",
|
||||||
"lists.search": "Search among people you follow",
|
"lists.search": "搜尋您關注的使用者",
|
||||||
"lists.subheading": "Your lists",
|
"lists.subheading": "您的名單",
|
||||||
"loading_indicator.label": "讀取中...",
|
"loading_indicator.label": "讀取中...",
|
||||||
"media_gallery.toggle_visible": "切換可見性",
|
"media_gallery.toggle_visible": "切換可見性",
|
||||||
"missing_indicator.label": "找不到",
|
"missing_indicator.label": "找不到",
|
||||||
@@ -146,8 +146,8 @@
|
|||||||
"navigation_bar.favourites": "最愛",
|
"navigation_bar.favourites": "最愛",
|
||||||
"navigation_bar.follow_requests": "關注請求",
|
"navigation_bar.follow_requests": "關注請求",
|
||||||
"navigation_bar.info": "關於本站",
|
"navigation_bar.info": "關於本站",
|
||||||
"navigation_bar.keyboard_shortcuts": "Keyboard shortcuts",
|
"navigation_bar.keyboard_shortcuts": "快速鍵",
|
||||||
"navigation_bar.lists": "Lists",
|
"navigation_bar.lists": "名單",
|
||||||
"navigation_bar.logout": "登出",
|
"navigation_bar.logout": "登出",
|
||||||
"navigation_bar.mutes": "消音的使用者",
|
"navigation_bar.mutes": "消音的使用者",
|
||||||
"navigation_bar.pins": "置頂貼文",
|
"navigation_bar.pins": "置頂貼文",
|
||||||
|
|||||||
@@ -2350,6 +2350,19 @@
|
|||||||
position: relative;
|
position: relative;
|
||||||
z-index: 2;
|
z-index: 2;
|
||||||
outline: 0;
|
outline: 0;
|
||||||
|
overflow: hidden;
|
||||||
|
|
||||||
|
& > button {
|
||||||
|
display: flex;
|
||||||
|
flex: auto;
|
||||||
|
margin: 0;
|
||||||
|
border: none;
|
||||||
|
padding: 0;
|
||||||
|
color: inherit;
|
||||||
|
background: transparent;
|
||||||
|
font: inherit;
|
||||||
|
text-align: left;
|
||||||
|
}
|
||||||
|
|
||||||
&.active {
|
&.active {
|
||||||
box-shadow: 0 1px 0 rgba($ui-highlight-color, 0.3);
|
box-shadow: 0 1px 0 rgba($ui-highlight-color, 0.3);
|
||||||
|
|||||||
22
app/serializers/activitypub/delete_actor_serializer.rb
Normal file
22
app/serializers/activitypub/delete_actor_serializer.rb
Normal file
@@ -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
|
||||||
@@ -27,7 +27,7 @@ class REST::InstanceSerializer < ActiveModel::Serializer
|
|||||||
end
|
end
|
||||||
|
|
||||||
def thumbnail
|
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
|
end
|
||||||
|
|
||||||
def stats
|
def stats
|
||||||
|
|||||||
@@ -17,9 +17,7 @@ class BatchedRemoveStatusService < BaseService
|
|||||||
|
|
||||||
@stream_entry_batches = []
|
@stream_entry_batches = []
|
||||||
@salmon_batches = []
|
@salmon_batches = []
|
||||||
@activity_json_batches = []
|
|
||||||
@json_payloads = statuses.map { |s| [s.id, Oj.dump(event: :delete, payload: s.id.to_s)] }.to_h
|
@json_payloads = statuses.map { |s| [s.id, Oj.dump(event: :delete, payload: s.id.to_s)] }.to_h
|
||||||
@activity_json = {}
|
|
||||||
@activity_xml = {}
|
@activity_xml = {}
|
||||||
|
|
||||||
# Ensure that rendered XML reflects destroyed state
|
# Ensure that rendered XML reflects destroyed state
|
||||||
@@ -32,10 +30,7 @@ class BatchedRemoveStatusService < BaseService
|
|||||||
unpush_from_home_timelines(account, account_statuses)
|
unpush_from_home_timelines(account, account_statuses)
|
||||||
unpush_from_list_timelines(account, account_statuses)
|
unpush_from_list_timelines(account, account_statuses)
|
||||||
|
|
||||||
if account.local?
|
batch_stream_entries(account, account_statuses) if account.local?
|
||||||
batch_stream_entries(account, account_statuses)
|
|
||||||
batch_activity_json(account, account_statuses)
|
|
||||||
end
|
|
||||||
end
|
end
|
||||||
|
|
||||||
# Cannot be batched
|
# Cannot be batched
|
||||||
@@ -46,7 +41,6 @@ class BatchedRemoveStatusService < BaseService
|
|||||||
|
|
||||||
Pubsubhubbub::RawDistributionWorker.push_bulk(@stream_entry_batches) { |batch| batch }
|
Pubsubhubbub::RawDistributionWorker.push_bulk(@stream_entry_batches) { |batch| batch }
|
||||||
NotificationWorker.push_bulk(@salmon_batches) { |batch| batch }
|
NotificationWorker.push_bulk(@salmon_batches) { |batch| batch }
|
||||||
ActivityPub::DeliveryWorker.push_bulk(@activity_json_batches) { |batch| batch }
|
|
||||||
end
|
end
|
||||||
|
|
||||||
private
|
private
|
||||||
@@ -57,22 +51,6 @@ class BatchedRemoveStatusService < BaseService
|
|||||||
end
|
end
|
||||||
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)
|
def unpush_from_home_timelines(account, statuses)
|
||||||
recipients = account.followers.local.to_a
|
recipients = account.followers.local.to_a
|
||||||
|
|
||||||
@@ -123,23 +101,9 @@ class BatchedRemoveStatusService < BaseService
|
|||||||
Redis.current
|
Redis.current
|
||||||
end
|
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)
|
def build_xml(stream_entry)
|
||||||
return @activity_xml[stream_entry.id] if @activity_xml.key?(stream_entry.id)
|
return @activity_xml[stream_entry.id] if @activity_xml.key?(stream_entry.id)
|
||||||
|
|
||||||
@activity_xml[stream_entry.id] = stream_entry_to_xml(stream_entry)
|
@activity_xml[stream_entry.id] = stream_entry_to_xml(stream_entry)
|
||||||
end
|
end
|
||||||
|
|
||||||
def sign_json(status, json)
|
|
||||||
Oj.dump(ActivityPub::LinkedDataSignature.new(json).sign!(status.account))
|
|
||||||
end
|
|
||||||
end
|
end
|
||||||
|
|||||||
@@ -50,7 +50,7 @@ class FetchAtomService < BaseService
|
|||||||
@unsupported_activity = true
|
@unsupported_activity = true
|
||||||
nil
|
nil
|
||||||
end
|
end
|
||||||
elsif @response['Link'] && !terminal
|
elsif @response['Link'] && !terminal && link_header.find_link(%w(rel alternate))
|
||||||
process_headers
|
process_headers
|
||||||
elsif @response.mime_type == 'text/html' && !terminal
|
elsif @response.mime_type == 'text/html' && !terminal
|
||||||
process_html
|
process_html
|
||||||
@@ -70,8 +70,6 @@ class FetchAtomService < BaseService
|
|||||||
end
|
end
|
||||||
|
|
||||||
def process_headers
|
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"'])
|
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))
|
atom_link = link_header.find_link(%w(rel alternate), %w(type application/atom+xml))
|
||||||
|
|
||||||
@@ -80,4 +78,8 @@ class FetchAtomService < BaseService
|
|||||||
|
|
||||||
result
|
result
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def link_header
|
||||||
|
@link_header ||= LinkHeader.parse(@response['Link'].is_a?(Array) ? @response['Link'].first : @response['Link'])
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|||||||
@@ -22,6 +22,8 @@ class SuspendAccountService < BaseService
|
|||||||
end
|
end
|
||||||
|
|
||||||
def purge_content!
|
def purge_content!
|
||||||
|
ActivityPub::RawDistributionWorker.perform_async(delete_actor_json, @account.id) if @account.local?
|
||||||
|
|
||||||
@account.statuses.reorder(nil).find_in_batches do |statuses|
|
@account.statuses.reorder(nil).find_in_batches do |statuses|
|
||||||
BatchedRemoveStatusService.new.call(statuses)
|
BatchedRemoveStatusService.new.call(statuses)
|
||||||
end
|
end
|
||||||
@@ -54,4 +56,14 @@ class SuspendAccountService < BaseService
|
|||||||
def destroy_all(association)
|
def destroy_all(association)
|
||||||
association.in_batches.destroy_all
|
association.in_batches.destroy_all
|
||||||
end
|
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
|
end
|
||||||
|
|||||||
@@ -160,6 +160,7 @@ pl:
|
|||||||
update_status: "%{name} zaktualizował wpis użytkownika %{target}"
|
update_status: "%{name} zaktualizował wpis użytkownika %{target}"
|
||||||
title: Dziennik działań administracyjnych
|
title: Dziennik działań administracyjnych
|
||||||
custom_emojis:
|
custom_emojis:
|
||||||
|
by_domain: Według domeny
|
||||||
copied_msg: Pomyślnie utworzono lokalną kopię emoji
|
copied_msg: Pomyślnie utworzono lokalną kopię emoji
|
||||||
copy: Kopiuj
|
copy: Kopiuj
|
||||||
copy_failed_msg: Nie udało się utworzyć lokalnej kopii emoji
|
copy_failed_msg: Nie udało się utworzyć lokalnej kopii emoji
|
||||||
@@ -612,6 +613,7 @@ pl:
|
|||||||
private: Nie możesz przypiąć niepublicznego wpisu
|
private: Nie możesz przypiąć niepublicznego wpisu
|
||||||
reblog: Nie możesz przypiąć podbicia wpisu
|
reblog: Nie możesz przypiąć podbicia wpisu
|
||||||
show_more: Pokaż więcej
|
show_more: Pokaż więcej
|
||||||
|
title: '%{name}: "%{quote}"'
|
||||||
visibilities:
|
visibilities:
|
||||||
private: Tylko dla śledzących
|
private: Tylko dla śledzących
|
||||||
private_long: Widoczne tylko dla osób, które Cię śledzą
|
private_long: Widoczne tylko dla osób, które Cię śledzą
|
||||||
|
|||||||
@@ -21,7 +21,7 @@ zh-TW:
|
|||||||
data: 資料
|
data: 資料
|
||||||
display_name: 顯示名稱
|
display_name: 顯示名稱
|
||||||
email: 電子信箱
|
email: 電子信箱
|
||||||
filtered_languages: 封鎖下面语言的文章
|
filtered_languages: 封鎖下面語言的文章
|
||||||
header: 個人頁面頂部
|
header: 個人頁面頂部
|
||||||
locale: 語言
|
locale: 語言
|
||||||
locked: 將帳號轉為「私密」
|
locked: 將帳號轉為「私密」
|
||||||
@@ -29,7 +29,16 @@ zh-TW:
|
|||||||
note: 簡介
|
note: 簡介
|
||||||
otp_attempt: 雙因子驗證碼
|
otp_attempt: 雙因子驗證碼
|
||||||
password: 密碼
|
password: 密碼
|
||||||
|
setting_auto_play_gif: 自動播放 GIFs
|
||||||
|
setting_boost_modal: 轉推前跳出確認視窗
|
||||||
setting_default_privacy: 文章預設隱私度
|
setting_default_privacy: 文章預設隱私度
|
||||||
|
setting_default_sensitive: 預設我的內容為敏感內容
|
||||||
|
setting_delete_modal: 刪推前跳出確認視窗
|
||||||
|
setting_noindex: 不被搜尋引擎檢索
|
||||||
|
setting_reduce_motion: 減低動畫效果
|
||||||
|
setting_system_font_ui: 使用系統預設字體
|
||||||
|
setting_theme: 網站主題
|
||||||
|
setting_unfollow_modal: 取消關注前跳出確認視窗
|
||||||
type: 匯入資料類型
|
type: 匯入資料類型
|
||||||
username: 使用者名稱
|
username: 使用者名稱
|
||||||
interactions:
|
interactions:
|
||||||
|
|||||||
@@ -55,7 +55,7 @@ zh-TW:
|
|||||||
perform_full_suspension: 進行停權
|
perform_full_suspension: 進行停權
|
||||||
profile_url: 個人檔案網址
|
profile_url: 個人檔案網址
|
||||||
public: 公開
|
public: 公開
|
||||||
push_subscription_expires: PuSH 訂閱逾期
|
push_subscription_expires: 推播訂閱過期
|
||||||
salmon_url: Salmon URL
|
salmon_url: Salmon URL
|
||||||
silence: 靜音
|
silence: 靜音
|
||||||
statuses: 狀態
|
statuses: 狀態
|
||||||
@@ -133,12 +133,14 @@ zh-TW:
|
|||||||
forgot_password: 忘記密碼?
|
forgot_password: 忘記密碼?
|
||||||
login: 登入
|
login: 登入
|
||||||
logout: 登出
|
logout: 登出
|
||||||
|
migrate_account: 轉移到另一個帳號
|
||||||
|
migrate_account_html: 想要將這個帳號指向另一個帳號可到<a href="%{path}">到這裡設定</a>。
|
||||||
register: 註冊
|
register: 註冊
|
||||||
resend_confirmation: 重寄驗證信
|
resend_confirmation: 重寄驗證信
|
||||||
reset_password: 重設密碼
|
reset_password: 重設密碼
|
||||||
set_new_password: 設定新密碼
|
set_new_password: 設定新密碼
|
||||||
authorize_follow:
|
authorize_follow:
|
||||||
error: 對不起,尋找這個跨站使用者的過程發生錯誤
|
error: 對不起,搜尋遠端使用者出現錯誤
|
||||||
follow: 關注
|
follow: 關注
|
||||||
title: 關注 %{acct}
|
title: 關注 %{acct}
|
||||||
datetime:
|
datetime:
|
||||||
@@ -165,7 +167,16 @@ zh-TW:
|
|||||||
blocks: 您封鎖的使用者
|
blocks: 您封鎖的使用者
|
||||||
csv: CSV
|
csv: CSV
|
||||||
follows: 您關注的使用者
|
follows: 您關注的使用者
|
||||||
|
mutes: 您靜音的使用者
|
||||||
storage: 儲存空間大小
|
storage: 儲存空間大小
|
||||||
|
followers:
|
||||||
|
domain: 網域
|
||||||
|
explanation_html: 為確保個人隱私,您必須知道有哪些使用者正關注你。<strong>您的私密內容會被發送到所有您有被關注的服務站上</strong>。如果您不信任這些服務站的管理者,您可以選擇檢查或刪除您的關注者。
|
||||||
|
followers_count: 關注者數
|
||||||
|
lock_link: 鎖住你的帳號
|
||||||
|
purge: 移除關注者
|
||||||
|
unlocked_warning_html: 所有人都可以關注並檢索你的隱藏狀態。%{lock_link}以檢查或拒絕關注。
|
||||||
|
unlocked_warning_title: 你的帳號是公開的
|
||||||
generic:
|
generic:
|
||||||
changes_saved_msg: 已成功儲存修改
|
changes_saved_msg: 已成功儲存修改
|
||||||
powered_by: 網站由 %{link} 開發
|
powered_by: 網站由 %{link} 開發
|
||||||
@@ -179,6 +190,7 @@ zh-TW:
|
|||||||
types:
|
types:
|
||||||
blocking: 您封鎖的使用者名單
|
blocking: 您封鎖的使用者名單
|
||||||
following: 您關注的使用者名單
|
following: 您關注的使用者名單
|
||||||
|
muting: 您靜音的使用者名單
|
||||||
upload: 上傳
|
upload: 上傳
|
||||||
landing_strip_html: "<strong>%{name}</strong> 是一個在 %{link_to_root_path} 的使用者。只要您有任何 Mastodon 服務站、或者聯盟網站的帳號,便可以跨站關注此站使用者,或者與他們互動。"
|
landing_strip_html: "<strong>%{name}</strong> 是一個在 %{link_to_root_path} 的使用者。只要您有任何 Mastodon 服務站、或者聯盟網站的帳號,便可以跨站關注此站使用者,或者與他們互動。"
|
||||||
landing_strip_signup_html: 如果您沒有這些帳號,歡迎在<a href="%{sign_up_path}">這裡註冊</a>。
|
landing_strip_signup_html: 如果您沒有這些帳號,歡迎在<a href="%{sign_up_path}">這裡註冊</a>。
|
||||||
@@ -231,15 +243,26 @@ zh-TW:
|
|||||||
missing_resource: 無法找到資源
|
missing_resource: 無法找到資源
|
||||||
proceed: 下一步
|
proceed: 下一步
|
||||||
prompt: 您希望關注︰
|
prompt: 您希望關注︰
|
||||||
|
sessions:
|
||||||
|
activity: 最近活動
|
||||||
|
browser: 瀏覽器
|
||||||
|
current_session: 目前的 session
|
||||||
|
description: "%{platform} 上的 %{browser}"
|
||||||
|
explanation: 這些是現在正登入於你的 Mastodon 帳號的瀏覽器。
|
||||||
|
revoke: 取消
|
||||||
|
revoke_success: Session 取消成功。
|
||||||
settings:
|
settings:
|
||||||
authorized_apps: 已授權應用程式
|
authorized_apps: 已授權應用程式
|
||||||
back: 回到 Mastodon
|
back: 回到 Mastodon
|
||||||
|
development: 開發
|
||||||
edit_profile: 修改個人資料
|
edit_profile: 修改個人資料
|
||||||
export: 匯出
|
export: 匯出
|
||||||
|
followers: 授權追蹤者
|
||||||
import: 匯入
|
import: 匯入
|
||||||
|
notifications: 通知
|
||||||
preferences: 偏好設定
|
preferences: 偏好設定
|
||||||
settings: 設定
|
settings: 設定
|
||||||
two_factor_authentication: 雙因子認證
|
two_factor_authentication: 兩階段認證
|
||||||
statuses:
|
statuses:
|
||||||
open_in_web: 以網頁開啟
|
open_in_web: 以網頁開啟
|
||||||
over_character_limit: 超過了 %{max} 字的限制
|
over_character_limit: 超過了 %{max} 字的限制
|
||||||
@@ -257,14 +280,14 @@ zh-TW:
|
|||||||
default: "%Y年%-m月%d日 %H:%M"
|
default: "%Y年%-m月%d日 %H:%M"
|
||||||
two_factor_authentication:
|
two_factor_authentication:
|
||||||
code_hint: 請輸入您認證器產生的代碼,以進行認證
|
code_hint: 請輸入您認證器產生的代碼,以進行認證
|
||||||
description_html: 當您啟用<strong>雙因子認證</strong>後,您登入時將需要使您手機、或其他種類認證器產生的代碼。
|
description_html: 啟用<strong>兩階段認證</strong>後,登入時將需要使手機、或其他種類認證器產生的代碼。
|
||||||
disable: 停用
|
disable: 停用
|
||||||
enable: 啟用
|
enable: 啟用
|
||||||
enabled_success: 已成功啟用雙因子認證
|
enabled_success: 已成功啟用兩階段認證
|
||||||
instructions_html: "<strong>請用您手機的認證器應用程式(如 Google Authenticator、Authy),掃描這裡的 QR 圖形碼</strong>。在雙因子認證啟用後,您登入時將須要使用此應用程式產生的認證碼。"
|
instructions_html: "<strong>請用您手機的認證器應用程式(如 Google Authenticator、Authy),掃描這裡的 QR 圖形碼</strong>。在兩階段認證啟用後,您登入時將須要使用此應用程式產生的認證碼。"
|
||||||
manual_instructions: 如果您無法掃描 QR 圖形碼,請手動輸入︰
|
manual_instructions: 如果您無法掃描 QR 圖形碼,請手動輸入︰
|
||||||
setup: 設定
|
setup: 設定
|
||||||
wrong_code: 您輸入的認證碼並不正確!可能伺服器時間和您手機不一致,請檢查您手機的時間,或與本站管理員聯絡。
|
wrong_code: 您輸入的認證碼並不正確!可能伺服器時間和您手機不一致,請檢查您手機的時間,或與本站管理員聯絡。
|
||||||
users:
|
users:
|
||||||
invalid_email: 信箱地址格式不正確
|
invalid_email: 信箱地址格式不正確
|
||||||
invalid_otp_token: 雙因子認證碼不正確
|
invalid_otp_token: 兩階段認證碼不正確
|
||||||
|
|||||||
Reference in New Issue
Block a user