Merge commit 'e2bc9be0e8
' into kb-draft-8.1
This commit is contained in:
commit
540565ba72
660 changed files with 10538 additions and 8049 deletions
|
@ -9,7 +9,6 @@ module.exports = {
|
|||
'plugin:import/recommended',
|
||||
'plugin:promise/recommended',
|
||||
'plugin:jsdoc/recommended',
|
||||
'plugin:prettier/recommended',
|
||||
],
|
||||
|
||||
env: {
|
||||
|
@ -63,7 +62,9 @@ module.exports = {
|
|||
'consistent-return': 'error',
|
||||
'dot-notation': 'error',
|
||||
eqeqeq: ['error', 'always', { 'null': 'ignore' }],
|
||||
'indent': ['error', 2],
|
||||
'jsx-quotes': ['error', 'prefer-single'],
|
||||
'semi': ['error', 'always'],
|
||||
'no-case-declarations': 'off',
|
||||
'no-catch-shadow': 'error',
|
||||
'no-console': [
|
||||
|
|
2
.github/ISSUE_TEMPLATE/1.bug_report.yml
vendored
2
.github/ISSUE_TEMPLATE/1.bug_report.yml
vendored
|
@ -1,5 +1,5 @@
|
|||
name: バグ報告
|
||||
description: kmyblueのバグ報告
|
||||
description: kmyblueのバグ報告(ただし情報改竄、秘密情報の漏洩、システムの破損などが発生するバグは、こちらではなく「Security」タブよりセキュリティインシデントとして報告してください)
|
||||
labels: [bug]
|
||||
body:
|
||||
- type: textarea
|
||||
|
|
|
@ -1,13 +1,13 @@
|
|||
# This configuration was generated by
|
||||
# `haml-lint --auto-gen-config`
|
||||
# on 2023-10-03 08:32:28 -0400 using Haml-Lint version 0.51.0.
|
||||
# on 2023-10-11 11:31:24 -0400 using Haml-Lint version 0.51.0.
|
||||
# The point is for the user to remove these configuration records
|
||||
# one by one as the lints are removed from the code base.
|
||||
# Note that changes in the inspected code, or installation of new
|
||||
# versions of Haml-Lint, may require this file to be generated again.
|
||||
|
||||
linters:
|
||||
# Offense count: 944
|
||||
# Offense count: 946
|
||||
LineLength:
|
||||
enabled: false
|
||||
|
||||
|
@ -30,10 +30,6 @@ linters:
|
|||
# Offense count: 15
|
||||
InstanceVariables:
|
||||
exclude:
|
||||
- 'app/views/admin/reports/_actions.html.haml'
|
||||
- 'app/views/auth/registrations/_status.html.haml'
|
||||
- 'app/views/auth/sessions/two_factor/_otp_authentication_form.html.haml'
|
||||
- 'app/views/relationships/_account.html.haml'
|
||||
- 'app/views/application/_sidebar.html.haml'
|
||||
|
||||
# Offense count: 2
|
||||
|
|
|
@ -178,9 +178,7 @@ RSpec/LetSetup:
|
|||
- 'spec/controllers/admin/reports/actions_controller_spec.rb'
|
||||
- 'spec/controllers/admin/statuses_controller_spec.rb'
|
||||
- 'spec/controllers/api/v1/accounts/statuses_controller_spec.rb'
|
||||
- 'spec/controllers/api/v1/admin/accounts_controller_spec.rb'
|
||||
- 'spec/controllers/api/v1/filters_controller_spec.rb'
|
||||
- 'spec/controllers/api/v1/followed_tags_controller_spec.rb'
|
||||
- 'spec/controllers/api/v2/admin/accounts_controller_spec.rb'
|
||||
- 'spec/controllers/api/v2/filters/keywords_controller_spec.rb'
|
||||
- 'spec/controllers/api/v2/filters/statuses_controller_spec.rb'
|
||||
|
@ -416,7 +414,6 @@ Rails/SkipsModelValidations:
|
|||
- 'lib/mastodon/cli/accounts.rb'
|
||||
- 'lib/mastodon/cli/main.rb'
|
||||
- 'lib/mastodon/cli/maintenance.rb'
|
||||
- 'spec/controllers/api/v1/admin/accounts_controller_spec.rb'
|
||||
- 'spec/lib/activitypub/activity/follow_spec.rb'
|
||||
- 'spec/services/follow_service_spec.rb'
|
||||
- 'spec/services/update_account_service_spec.rb'
|
||||
|
@ -526,12 +523,6 @@ Style/ClassVars:
|
|||
Exclude:
|
||||
- 'config/initializers/devise.rb'
|
||||
|
||||
# This cop supports unsafe autocorrection (--autocorrect-all).
|
||||
Style/CombinableLoops:
|
||||
Exclude:
|
||||
- 'app/models/form/custom_emoji_batch.rb'
|
||||
- 'app/models/form/ip_block_batch.rb'
|
||||
|
||||
# This cop supports safe autocorrection (--autocorrect).
|
||||
# Configuration parameters: AllowedVars.
|
||||
Style/FetchEnvVar:
|
||||
|
|
|
@ -16,6 +16,7 @@ All notable changes to this project will be documented in this file.
|
|||
|
||||
### Fixed
|
||||
|
||||
- Fix duplicate reports being sent when reporting some remote posts ([ClearlyClaire](https://github.com/mastodon/mastodon/pull/27355))
|
||||
- Fix clicking on already-opened thread post scrolling to the top of the thread ([ClearlyClaire](https://github.com/mastodon/mastodon/pull/27331), [ClearlyClaire](https://github.com/mastodon/mastodon/pull/27338), [ClearlyClaire](https://github.com/mastodon/mastodon/pull/27350))
|
||||
- Fix some remote posts getting truncated ([ClearlyClaire](https://github.com/mastodon/mastodon/pull/27307))
|
||||
- Fix some cases of infinite scroll code trying to fetch inaccessible posts in a loop ([ClearlyClaire](https://github.com/mastodon/mastodon/pull/27286))
|
||||
|
|
3
Gemfile
3
Gemfile
|
@ -106,6 +106,9 @@ group :test do
|
|||
# Used to split testing into chunks in CI
|
||||
gem 'rspec_chunked', '~> 0.6'
|
||||
|
||||
# Adds RSpec Error/Warning annotations to GitHub PRs on the Files tab
|
||||
gem 'rspec-github', '~> 2.4', require: false
|
||||
|
||||
# RSpec progress bar formatter
|
||||
gem 'fuubar', '~> 2.5'
|
||||
|
||||
|
|
49
Gemfile.lock
49
Gemfile.lock
|
@ -146,12 +146,12 @@ GEM
|
|||
net-http-persistent (~> 4.0)
|
||||
nokogiri (~> 1, >= 1.10.8)
|
||||
base64 (0.1.1)
|
||||
bcrypt (3.1.18)
|
||||
bcrypt (3.1.19)
|
||||
better_errors (2.10.1)
|
||||
erubi (>= 1.0.0)
|
||||
rack (>= 0.9.0)
|
||||
rouge (>= 1.0.0)
|
||||
better_html (2.0.1)
|
||||
better_html (2.0.2)
|
||||
actionview (>= 6.0)
|
||||
activesupport (>= 6.0)
|
||||
ast (~> 2.0)
|
||||
|
@ -210,17 +210,17 @@ GEM
|
|||
database_cleaner-core (2.0.1)
|
||||
date (3.3.3)
|
||||
debug_inspector (1.1.0)
|
||||
devise (4.9.2)
|
||||
devise (4.9.3)
|
||||
bcrypt (~> 3.0)
|
||||
orm_adapter (~> 0.1)
|
||||
railties (>= 4.1.0)
|
||||
responders
|
||||
warden (~> 1.2.3)
|
||||
devise-two-factor (4.1.0)
|
||||
activesupport (< 7.1)
|
||||
devise-two-factor (4.1.1)
|
||||
activesupport (~> 7.0)
|
||||
attr_encrypted (>= 1.3, < 5, != 2)
|
||||
devise (~> 4.0)
|
||||
railties (< 7.1)
|
||||
railties (~> 7.0)
|
||||
rotp (~> 6.0)
|
||||
devise_pam_authenticatable2 (9.2.0)
|
||||
devise (>= 4.0.0)
|
||||
|
@ -345,14 +345,14 @@ GEM
|
|||
rainbow (>= 2.0.0)
|
||||
i18n (1.14.1)
|
||||
concurrent-ruby (~> 1.0)
|
||||
i18n-tasks (1.0.12)
|
||||
i18n-tasks (1.0.13)
|
||||
activesupport (>= 4.0.2)
|
||||
ast (>= 2.1.0)
|
||||
better_html (>= 1.0, < 3.0)
|
||||
erubi
|
||||
highline (>= 2.0.0)
|
||||
i18n
|
||||
parser (>= 2.2.3.0)
|
||||
parser (>= 3.2.2.1)
|
||||
rails-i18n
|
||||
rainbow (>= 2.2.2, < 4.0)
|
||||
terminal-table (>= 1.5.1)
|
||||
|
@ -412,12 +412,12 @@ GEM
|
|||
llhttp-ffi (0.4.0)
|
||||
ffi-compiler (~> 1.0)
|
||||
rake (~> 13.0)
|
||||
lograge (0.13.0)
|
||||
lograge (0.14.0)
|
||||
actionpack (>= 4)
|
||||
activesupport (>= 4)
|
||||
railties (>= 4)
|
||||
request_store (~> 1.0)
|
||||
loofah (2.21.3)
|
||||
loofah (2.21.4)
|
||||
crass (~> 1.0.2)
|
||||
nokogiri (>= 1.12.0)
|
||||
mail (2.8.1)
|
||||
|
@ -440,7 +440,7 @@ GEM
|
|||
mime-types-data (3.2023.0808)
|
||||
mini_mime (1.1.5)
|
||||
mini_portile2 (2.8.4)
|
||||
minitest (5.19.0)
|
||||
minitest (5.20.0)
|
||||
msgpack (1.7.1)
|
||||
multi_json (1.15.0)
|
||||
multipart-post (2.3.0)
|
||||
|
@ -493,7 +493,7 @@ GEM
|
|||
orm_adapter (0.5.0)
|
||||
ox (2.14.17)
|
||||
parallel (1.23.0)
|
||||
parser (3.2.2.3)
|
||||
parser (3.2.2.4)
|
||||
ast (~> 2.4.1)
|
||||
racc
|
||||
parslet (2.0.0)
|
||||
|
@ -513,7 +513,7 @@ GEM
|
|||
premailer (~> 1.7, >= 1.7.9)
|
||||
private_address_check (0.5.0)
|
||||
public_suffix (5.0.3)
|
||||
puma (6.3.1)
|
||||
puma (6.4.0)
|
||||
nio4r (~> 2.0)
|
||||
pundit (2.3.0)
|
||||
activesupport (>= 3.0.0)
|
||||
|
@ -554,14 +554,14 @@ GEM
|
|||
actionpack (>= 5.0.1.rc1)
|
||||
actionview (>= 5.0.1.rc1)
|
||||
activesupport (>= 5.0.1.rc1)
|
||||
rails-dom-testing (2.1.1)
|
||||
rails-dom-testing (2.2.0)
|
||||
activesupport (>= 5.0.0)
|
||||
minitest
|
||||
nokogiri (>= 1.6)
|
||||
rails-html-sanitizer (1.6.0)
|
||||
loofah (~> 2.21)
|
||||
nokogiri (~> 1.14)
|
||||
rails-i18n (7.0.7)
|
||||
rails-i18n (7.0.8)
|
||||
i18n (>= 0.7, < 2)
|
||||
railties (>= 6.0.0, < 8)
|
||||
railties (7.0.8)
|
||||
|
@ -583,10 +583,10 @@ GEM
|
|||
redis (>= 4)
|
||||
redlock (1.3.2)
|
||||
redis (>= 3.0.0, < 6.0)
|
||||
regexp_parser (2.8.1)
|
||||
regexp_parser (2.8.2)
|
||||
request_store (1.5.1)
|
||||
rack (>= 1.4)
|
||||
responders (3.1.0)
|
||||
responders (3.1.1)
|
||||
actionpack (>= 5.2)
|
||||
railties (>= 5.2)
|
||||
rexml (3.2.6)
|
||||
|
@ -602,6 +602,8 @@ GEM
|
|||
rspec-expectations (3.12.3)
|
||||
diff-lcs (>= 1.2.0, < 2.0)
|
||||
rspec-support (~> 3.12.0)
|
||||
rspec-github (2.4.0)
|
||||
rspec-core (~> 3.0)
|
||||
rspec-mocks (3.12.5)
|
||||
diff-lcs (>= 1.2.0, < 2.0)
|
||||
rspec-support (~> 3.12.0)
|
||||
|
@ -622,12 +624,12 @@ GEM
|
|||
sidekiq (>= 5, < 8)
|
||||
rspec-support (3.12.1)
|
||||
rspec_chunked (0.6)
|
||||
rubocop (1.56.4)
|
||||
rubocop (1.57.1)
|
||||
base64 (~> 0.1.1)
|
||||
json (~> 2.3)
|
||||
language_server-protocol (>= 3.17.0)
|
||||
parallel (~> 1.10)
|
||||
parser (>= 3.2.2.3)
|
||||
parser (>= 3.2.2.4)
|
||||
rainbow (>= 2.2.2, < 4.0)
|
||||
regexp_parser (>= 1.8, < 3.0)
|
||||
rexml (>= 3.2.5, < 4.0)
|
||||
|
@ -636,11 +638,11 @@ GEM
|
|||
unicode-display_width (>= 2.4.0, < 3.0)
|
||||
rubocop-ast (1.29.0)
|
||||
parser (>= 3.2.1.0)
|
||||
rubocop-capybara (2.18.0)
|
||||
rubocop-capybara (2.19.0)
|
||||
rubocop (~> 1.41)
|
||||
rubocop-factory_bot (2.23.1)
|
||||
rubocop (~> 1.33)
|
||||
rubocop-performance (1.19.0)
|
||||
rubocop-performance (1.19.1)
|
||||
rubocop (>= 1.7.0, < 2.0)
|
||||
rubocop-ast (>= 0.4.0)
|
||||
rubocop-rails (2.20.2)
|
||||
|
@ -673,7 +675,7 @@ GEM
|
|||
rubyzip (>= 1.2.2, < 3.0)
|
||||
websocket (~> 1.0)
|
||||
semantic_range (3.0.0)
|
||||
sidekiq (6.5.10)
|
||||
sidekiq (6.5.12)
|
||||
connection_pool (>= 2.2.5, < 3)
|
||||
rack (~> 2.0)
|
||||
redis (>= 4.5.0, < 5)
|
||||
|
@ -791,7 +793,7 @@ GEM
|
|||
xorcist (1.1.3)
|
||||
xpath (3.2.0)
|
||||
nokogiri (~> 1.8)
|
||||
zeitwerk (2.6.11)
|
||||
zeitwerk (2.6.12)
|
||||
|
||||
PLATFORMS
|
||||
ruby
|
||||
|
@ -887,6 +889,7 @@ DEPENDENCIES
|
|||
redis (~> 4.5)
|
||||
redis-namespace (~> 1.10)
|
||||
rqrcode (~> 2.2)
|
||||
rspec-github (~> 2.4)
|
||||
rspec-rails (~> 6.0)
|
||||
rspec-retry (>= 0.6.2)
|
||||
rspec-sidekiq (~> 4.0)
|
||||
|
|
33
SECURITY.md
33
SECURITY.md
|
@ -1,22 +1,25 @@
|
|||
# Security Policy
|
||||
# セキュリティポリシー
|
||||
|
||||
If you believe you've identified a security vulnerability in Mastodon (a bug that allows something to happen that shouldn't be possible), you can either:
|
||||
kmyblueのプログラムにおいてセキュリティインシデントを発見した場合、kmyblueに報告してください。
|
||||
|
||||
- open a [Github security issue on the Mastodon project](https://github.com/mastodon/mastodon/security/advisories/new)
|
||||
- reach us at <security@joinmastodon.org>
|
||||
kmyblueにセキュリティインシデントを報告する場合、以下の手順を踏んでください。
|
||||
|
||||
You should _not_ report such issues on public GitHub issues or in other public spaces to give us time to publish a fix for the issue without exposing Mastodon's users to increased risk.
|
||||
- [こちらのリンクから新規インシデントを起票してください](https://github.com/kmycode/mastodon/security/advisories/new)
|
||||
- メール <tt@kmycode.net>、または[@askyq@kmy.blue](https://kmy.blue/@askyq)宛に、**セキュリティインシデントを起票したことだけ**を連絡してください。セキュリティインシデントの内容は、絶対に連絡に含めないでください(リンクくらいなら含めていいかな)
|
||||
|
||||
## Scope
|
||||
他のkmyblueフォークの利用者の安全のために少しでも時間稼ぎをしなければいけないので、この問題をIssueを含む公開された場所で記述しないでください。
|
||||
|
||||
A "vulnerability in Mastodon" is a vulnerability in the code distributed through our main source code repository on GitHub. Vulnerabilities that are specific to a given installation (e.g. misconfiguration) should be reported to the owner of that installation and not us.
|
||||
## 範囲
|
||||
|
||||
## Supported Versions
|
||||
こちらが対応できる範囲は、当リポジトリで公開しているソースコードのみとなります。当リポジトリの依存パッケージ内に問題がある場合は、そちらに報告してください。
|
||||
|
||||
| Version | Supported |
|
||||
| ------- | ---------------- |
|
||||
| 4.2.x | Yes |
|
||||
| 4.1.x | Yes |
|
||||
| 4.0.x | Until 2023-10-31 |
|
||||
| 3.5.x | Until 2023-12-31 |
|
||||
| < 3.5 | No |
|
||||
もしあなたに専門知識があり、それが本家Mastodon由来の問題であると信じるに足る根拠がある場合、kmyblueではなくMastodonのほうに報告してください。kmyblueに報告されても、Mastodonより先に修正してしまうことでMastodonにセキュリティリスクを発生させる可能性がありますし、本家Mastodonの対応を待つにしてもkmyblueのほうに来てしまったセキュリティインシデントの対応に困ります(本家がなかなか対応してくれない可能性を考えると削除しづらい)。もし間違ってkmyblueに来た場合、kmyblue開発者の責任で振り分けを行います。
|
||||
|
||||
## サポートするバージョン
|
||||
|
||||
下記以外のバージョンは、セキュリティインシデントを起票されても対応しません。
|
||||
|
||||
- 最新メジャーバージョン、かつ、最新マイナーバージョン
|
||||
- 最新メジャーバージョンのサポートは、次のメジャーバージョンが出た時点で終了します
|
||||
- LTS
|
||||
- LTSのサポートは、次のLTSが出た時点で終了します(ただし移行期間があってもいいと思ってるので、1〜3ヶ月以内ならセキュリティインシデントの程度に応じて対応する可能性があります)
|
||||
|
|
|
@ -2,6 +2,8 @@
|
|||
|
||||
module Admin
|
||||
class CustomEmojisController < BaseController
|
||||
before_action :set_custom_emoji, only: [:edit, :update]
|
||||
|
||||
def index
|
||||
authorize :custom_emoji, :index?
|
||||
|
||||
|
@ -15,6 +17,10 @@ module Admin
|
|||
@custom_emoji = CustomEmoji.new
|
||||
end
|
||||
|
||||
def edit
|
||||
authorize :custom_emoji, :create?
|
||||
end
|
||||
|
||||
def create
|
||||
authorize :custom_emoji, :create?
|
||||
|
||||
|
@ -28,6 +34,19 @@ module Admin
|
|||
end
|
||||
end
|
||||
|
||||
def update
|
||||
authorize :custom_emoji, :create?
|
||||
|
||||
@custom_emoji.assign_attributes(update_params)
|
||||
|
||||
if @custom_emoji.save
|
||||
log_action :create, @custom_emoji
|
||||
redirect_to admin_custom_emojis_path(filter_params), notice: I18n.t('admin.custom_emojis.updated_msg')
|
||||
else
|
||||
render :new
|
||||
end
|
||||
end
|
||||
|
||||
def batch
|
||||
authorize :custom_emoji, :index?
|
||||
|
||||
|
@ -43,8 +62,16 @@ module Admin
|
|||
|
||||
private
|
||||
|
||||
def set_custom_emoji
|
||||
@custom_emoji = CustomEmoji.find(params[:id])
|
||||
end
|
||||
|
||||
def resource_params
|
||||
params.require(:custom_emoji).permit(:shortcode, :image, :visible_in_picker)
|
||||
params.require(:custom_emoji).permit(:shortcode, :image, :visible_in_picker, :aliases_raw, :license)
|
||||
end
|
||||
|
||||
def update_params
|
||||
params.require(:custom_emoji).permit(:visible_in_picker, :aliases_raw, :license)
|
||||
end
|
||||
|
||||
def filtered_custom_emojis
|
||||
|
@ -74,7 +101,7 @@ module Admin
|
|||
end
|
||||
|
||||
def form_custom_emoji_batch_params
|
||||
params.require(:form_custom_emoji_batch).permit(:action, :category_id, :category_name, :aliases_raw, custom_emoji_ids: [])
|
||||
params.require(:form_custom_emoji_batch).permit(:action, :category_id, :category_name, custom_emoji_ids: [])
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
@ -32,7 +32,7 @@ module Admin
|
|||
private
|
||||
|
||||
def test_words
|
||||
ng_words = settings_params['ng_words'].split(/\r\n|\r|\n/)
|
||||
ng_words = "#{settings_params['ng_words']}\n#{settings_params['ng_words_for_stranger_mention']}".split(/\r\n|\r|\n/).filter(&:present?)
|
||||
Admin::NgWord.reject_with_custom_words?('Sample text', ng_words)
|
||||
end
|
||||
|
||||
|
|
|
@ -3,9 +3,9 @@
|
|||
class Api::V1::Statuses::EmojiReactionsController < Api::BaseController
|
||||
include Authorization
|
||||
|
||||
before_action -> { doorkeeper_authorize! :write, :'write:emoji_reactions' }
|
||||
before_action -> { doorkeeper_authorize! :write, :'write:favourites' }
|
||||
before_action :require_user!
|
||||
before_action :set_status, only: %i(create update destroy)
|
||||
before_action :set_status, only: %i(create update)
|
||||
before_action :set_status_without_authorize, only: [:destroy]
|
||||
|
||||
def create
|
||||
|
@ -28,6 +28,8 @@ class Api::V1::Statuses::EmojiReactionsController < Api::BaseController
|
|||
authorize @status, :show? if emoji_reaction.nil?
|
||||
|
||||
UnEmojiReactService.new.call(current_account.id, @status.id, emoji_reaction) if emoji_reaction.present?
|
||||
else
|
||||
authorize @status, :show?
|
||||
end
|
||||
|
||||
render json: @status, serializer: REST::StatusSerializer, relationships: StatusRelationshipsPresenter.new(
|
||||
|
|
|
@ -5,6 +5,7 @@ module TwoFactorAuthenticationConcern
|
|||
|
||||
included do
|
||||
prepend_before_action :authenticate_with_two_factor, if: :two_factor_enabled?, only: [:create]
|
||||
helper_method :webauthn_enabled?
|
||||
end
|
||||
|
||||
def two_factor_enabled?
|
||||
|
@ -87,4 +88,10 @@ module TwoFactorAuthenticationConcern
|
|||
|
||||
set_locale { render :two_factor }
|
||||
end
|
||||
|
||||
protected
|
||||
|
||||
def webauthn_enabled?
|
||||
@webauthn_enabled
|
||||
end
|
||||
end
|
||||
|
|
|
@ -30,6 +30,8 @@ module ContextHelper
|
|||
other_setting: { 'fedibird' => 'http://fedibird.com/ns#', 'otherSetting' => 'fedibird:otherSetting' },
|
||||
references: { 'fedibird' => 'http://fedibird.com/ns#', 'references' => { '@id' => 'fedibird:references', '@type' => '@id' } },
|
||||
quote_uri: { 'fedibird' => 'http://fedibird.com/ns#', 'quoteUri' => 'fedibird:quoteUri' },
|
||||
keywords: { 'schema' => 'http://schema.org#', 'keywords' => 'schema:keywords' },
|
||||
license: { 'schema' => 'http://schema.org#', 'license' => 'schema:license' },
|
||||
olm: {
|
||||
'toot' => 'http://joinmastodon.org/ns#', 'Device' => 'toot:Device', 'Ed25519Signature' => 'toot:Ed25519Signature', 'Ed25519Key' => 'toot:Ed25519Key', 'Curve25519Key' => 'toot:Curve25519Key', 'EncryptedMessage' => 'toot:EncryptedMessage', 'publicKeyBase64' => 'toot:publicKeyBase64', 'deviceId' => 'toot:deviceId',
|
||||
'claim' => { '@type' => '@id', '@id' => 'toot:claim' },
|
||||
|
|
|
@ -37,6 +37,10 @@ module KmyblueCapabilitiesHelper
|
|||
status_reference
|
||||
quote
|
||||
kmyblue_quote
|
||||
kmyblue_subscribable
|
||||
kmyblue_translation
|
||||
kmyblue_link_preview
|
||||
kmyblue_emoji_reaction_policy
|
||||
searchability
|
||||
kmyblue_searchability
|
||||
visibility_mutual
|
||||
|
@ -45,6 +49,8 @@ module KmyblueCapabilitiesHelper
|
|||
kmyblue_bookmark_category
|
||||
kmyblue_searchability_limited
|
||||
kmyblue_circle_history
|
||||
kmyblue_emoji_license
|
||||
emoji_keywords
|
||||
)
|
||||
|
||||
capabilities << :full_text_search if Chewy.enabled?
|
||||
|
|
|
@ -56,4 +56,4 @@ export const showAlertForError = (error, skipNotFound = false) => {
|
|||
title: messages.unexpectedTitle,
|
||||
message: messages.unexpectedMessage,
|
||||
});
|
||||
}
|
||||
};
|
||||
|
|
|
@ -213,9 +213,10 @@ export const deleteBookmarkCategoryFail = (id, error) => ({
|
|||
export const fetchBookmarkCategoryStatuses = bookmarkCategoryId => (dispatch, getState) => {
|
||||
dispatch(fetchBookmarkCategoryStatusesRequest(bookmarkCategoryId));
|
||||
|
||||
api(getState).get(`/api/v1/bookmark_categories/${bookmarkCategoryId}/statuses`, { params: { limit: 0 } }).then(({ data }) => {
|
||||
dispatch(importFetchedStatuses(data));
|
||||
dispatch(fetchBookmarkCategoryStatusesSuccess(bookmarkCategoryId, data));
|
||||
api(getState).get(`/api/v1/bookmark_categories/${bookmarkCategoryId}/statuses`).then((response) => {
|
||||
const next = getLinks(response).refs.find(link => link.rel === 'next');
|
||||
dispatch(importFetchedStatuses(response.data));
|
||||
dispatch(fetchBookmarkCategoryStatusesSuccess(bookmarkCategoryId, response.data, next ? next.uri : null));
|
||||
}).catch(err => dispatch(fetchBookmarkCategoryStatusesFail(bookmarkCategoryId, err)));
|
||||
};
|
||||
|
||||
|
|
|
@ -380,7 +380,11 @@ export const removeFromCircleAdder = circleId => (dispatch, getState) => {
|
|||
|
||||
export function fetchCircleStatuses(circleId) {
|
||||
return (dispatch, getState) => {
|
||||
if (getState().getIn(['circles', circleId, 'statuses', 'isLoading'])) {
|
||||
if (getState().getIn(['circles', circleId, 'isLoading'])) {
|
||||
return;
|
||||
}
|
||||
const items = getState().getIn(['circles', circleId, 'items']);
|
||||
if (items && items.size > 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
|
@ -422,9 +426,9 @@ export function fetchCircleStatusesFail(id, error) {
|
|||
|
||||
export function expandCircleStatuses(circleId) {
|
||||
return (dispatch, getState) => {
|
||||
const url = getState().getIn(['circles', circleId, 'statuses', 'next'], null);
|
||||
const url = getState().getIn(['circles', circleId, 'next'], null);
|
||||
|
||||
if (url === null || getState().getIn(['circles', circleId, 'statuses', 'isLoading'])) {
|
||||
if (url === null || getState().getIn(['circles', circleId, 'isLoading'])) {
|
||||
return;
|
||||
}
|
||||
|
||||
|
|
|
@ -290,7 +290,7 @@ export function submitComposeWithCircleSuccess(status, circleId) {
|
|||
type: COMPOSE_WITH_CIRCLE_SUCCESS,
|
||||
status,
|
||||
circleId,
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
export function submitComposeFail(error) {
|
||||
|
|
|
@ -121,7 +121,7 @@ export function normalizeStatus(status, normalOldStatus) {
|
|||
normalStatus.media_attachments.forEach(item => {
|
||||
const oldItem = list.find(i => i.get('id') === item.id);
|
||||
if (oldItem && oldItem.get('description') === item.description) {
|
||||
item.translation = oldItem.get('translation')
|
||||
item.translation = oldItem.get('translation');
|
||||
}
|
||||
});
|
||||
}
|
||||
|
@ -165,13 +165,13 @@ export function normalizePoll(poll, normalOldPoll) {
|
|||
...option,
|
||||
voted: poll.own_votes && poll.own_votes.includes(index),
|
||||
titleHtml: emojify(escapeTextContentForBrowser(option.title), emojiMap),
|
||||
}
|
||||
};
|
||||
|
||||
if (normalOldPoll && normalOldPoll.getIn(['options', index, 'title']) === option.title) {
|
||||
normalOption.translation = normalOldPoll.getIn(['options', index, 'translation']);
|
||||
}
|
||||
|
||||
return normalOption
|
||||
return normalOption;
|
||||
});
|
||||
|
||||
return normalPoll;
|
||||
|
|
|
@ -192,4 +192,4 @@ export const connectListStream = listId =>
|
|||
* @returns {function(): void}
|
||||
*/
|
||||
export const connectAntennaStream = antennaId =>
|
||||
connectTimelineStream(`antenna:${antennaId}`, 'antenna', { antenna: antennaId }, { fillGaps: () => fillAntennaTimelineGaps(antennaId) });
|
||||
connectTimelineStream(`antenna:${antennaId}`, 'antenna', { antenna: antennaId }, { fillGaps: () => fillAntennaTimelineGaps(antennaId) });
|
||||
|
|
|
@ -22,7 +22,7 @@ export default class Column extends PureComponent {
|
|||
scrollable = document.scrollingElement;
|
||||
} else {
|
||||
scrollable = this.node.querySelector('.scrollable');
|
||||
}
|
||||
}
|
||||
|
||||
if (!scrollable) {
|
||||
return;
|
||||
|
|
|
@ -184,7 +184,7 @@ class CompactedStatus extends ImmutablePureComponent {
|
|||
} else if (attachments.getIn([0, 'type']) === 'audio') {
|
||||
return '16 / 9';
|
||||
} else {
|
||||
return (attachments.size === 1 && attachments.getIn([0, 'meta', 'small', 'aspect'])) ? attachments.getIn([0, 'meta', 'small', 'aspect']) : '3 / 2'
|
||||
return (attachments.size === 1 && attachments.getIn([0, 'meta', 'small', 'aspect'])) ? attachments.getIn([0, 'meta', 'small', 'aspect']) : '3 / 2';
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -356,10 +356,10 @@ class MediaGallery extends PureComponent {
|
|||
|
||||
const rowClass = (size === 5 || size === 6 || size === 9 || size === 10 || size === 11 || size === 12) ? 'media-gallery--row3' :
|
||||
(size === 7 || size === 8 || size === 13 || size === 14 || size === 15 || size === 16) ? 'media-gallery--row4' :
|
||||
'media-gallery--row2';
|
||||
'media-gallery--row2';
|
||||
const columnClass = (size === 9) ? 'media-gallery--column3' :
|
||||
(size === 10 || size === 11 || size === 12 || size === 13 || size === 14 || size === 15 || size === 16) ? 'media-gallery--column4' :
|
||||
'media-gallery--column2';
|
||||
'media-gallery--column2';
|
||||
const compactClass = compact ? 'media-gallery__compact' : null;
|
||||
|
||||
return (
|
||||
|
|
|
@ -132,7 +132,7 @@ class Poll extends ImmutablePureComponent {
|
|||
|
||||
handleReveal = () => {
|
||||
this.setState({ revealed: true });
|
||||
}
|
||||
};
|
||||
|
||||
renderOption (option, optionIndex, showResults) {
|
||||
const { poll, lang, disabled, intl } = this.props;
|
||||
|
|
|
@ -13,7 +13,7 @@ import AttachmentList from 'mastodon/components/attachment_list';
|
|||
import { Icon } from 'mastodon/components/icon';
|
||||
import PictureInPicturePlaceholder from 'mastodon/components/picture_in_picture_placeholder';
|
||||
|
||||
import CompactedStatusContainer from '../containers/compacted_status_container'
|
||||
import CompactedStatusContainer from '../containers/compacted_status_container';
|
||||
import Card from '../features/status/components/card';
|
||||
// We use the component (and not the container) since we do not want
|
||||
// to use the progress bar to show download progress
|
||||
|
@ -213,7 +213,7 @@ class Status extends ImmutablePureComponent {
|
|||
} else if (attachments.getIn([0, 'type']) === 'audio') {
|
||||
return '16 / 9';
|
||||
} else {
|
||||
return (attachments.size === 1 && attachments.getIn([0, 'meta', 'small', 'aspect'])) ? attachments.getIn([0, 'meta', 'small', 'aspect']) : '3 / 2'
|
||||
return (attachments.size === 1 && attachments.getIn([0, 'meta', 'small', 'aspect'])) ? attachments.getIn([0, 'meta', 'small', 'aspect']) : '3 / 2';
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -489,12 +489,12 @@ class Status extends ImmutablePureComponent {
|
|||
</div>
|
||||
|
||||
<div >
|
||||
<FormattedMessage id='status.filtered' defaultMessage='Filtered' />: {matchedFilters.join(', ')}.
|
||||
{' '}
|
||||
<button className='status__wrapper--filtered__button' onClick={this.handleUnfilterClick}>
|
||||
<FormattedMessage id='status.show_filter_reason' defaultMessage='Show anyway' />
|
||||
</button>
|
||||
</div>
|
||||
<FormattedMessage id='status.filtered' defaultMessage='Filtered' />: {matchedFilters.join(', ')}.
|
||||
{' '}
|
||||
<button className='status__wrapper--filtered__button' onClick={this.handleUnfilterClick}>
|
||||
<FormattedMessage id='status.show_filter_reason' defaultMessage='Show anyway' />
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</HotKeys>
|
||||
);
|
||||
|
@ -628,7 +628,7 @@ class Status extends ImmutablePureComponent {
|
|||
const withReference = (!withQuote && status.get('status_references_count') > 0) ? <span className='status__visibility-icon'><Icon id='link' title='Reference' /></span> : null;
|
||||
const withExpiration = status.get('expires_at') ? <span className='status__visibility-icon'><Icon id='clock-o' title='Expiration' /></span> : null;
|
||||
|
||||
const quote = !muted && status.get('quote_id') && (['public', 'community'].includes(contextType) ? showQuoteInPublic : showQuoteInHome) && <CompactedStatusContainer id={status.get('quote_id')} />
|
||||
const quote = !muted && status.get('quote_id') && (['public', 'community'].includes(contextType) ? showQuoteInPublic : showQuoteInHome) && <CompactedStatusContainer id={status.get('quote_id')} />;
|
||||
|
||||
return (
|
||||
<HotKeys handlers={handlers}>
|
||||
|
@ -669,12 +669,12 @@ class Status extends ImmutablePureComponent {
|
|||
{...statusContentProps}
|
||||
/>
|
||||
|
||||
{(!status.get('spoiler_text') || expanded) && quote}
|
||||
|
||||
{(!isCardMediaWithSensitive || !status.get('hidden')) && media}
|
||||
|
||||
{(!status.get('spoiler_text') || expanded) && hashtagBar}
|
||||
|
||||
{(!status.get('spoiler_text') || expanded) && quote}
|
||||
|
||||
{emojiReactionsBar}
|
||||
|
||||
<StatusActionBar scrollKey={scrollKey} status={status} account={account} onFilter={matchedFilters ? this.handleFilterClick : null} {...other} />
|
||||
|
|
|
@ -127,7 +127,7 @@ class About extends PureComponent {
|
|||
const { multiColumn, intl, server, extendedDescription, domainBlocks } = this.props;
|
||||
const isLoading = server.get('isLoading');
|
||||
|
||||
const fedibirdCapabilities = server.get('fedibird_capabilities');
|
||||
const fedibirdCapabilities = server.get('fedibird_capabilities') || []; // thinking about isLoading is true
|
||||
const isPublicUnlistedVisibility = fedibirdCapabilities.includes('kmyblue_visibility_public_unlisted');
|
||||
const isEmojiReaction = fedibirdCapabilities.includes('emoji_reaction');
|
||||
|
||||
|
@ -192,7 +192,7 @@ class About extends PureComponent {
|
|||
</Section>
|
||||
|
||||
<Section title={intl.formatMessage(messages.capabilities)}>
|
||||
<p><FormattedMessage id='about.kmyblue_capability' defaultMessage='This server is using kmyblue, a fork of Mastodon. On this server, kmyblues unique features are configured as follows.' /></p>
|
||||
<p><FormattedMessage id='about.kmyblue_capability' defaultMessage='This server is using kmyblue, a fork of Mastodon. On this server, kmyblues unique features are configured as follows.' /></p>
|
||||
{!isLoading && (
|
||||
<ol className='rules-list'>
|
||||
<li>
|
||||
|
|
|
@ -33,7 +33,7 @@ class RadioPanel extends PureComponent {
|
|||
<div className='setting-radio-panel'>
|
||||
{values.map((val) => (
|
||||
<button className={classNames('setting-radio-panel__item', {'setting-radio-panel__item__active': value.get('value') === val.get('value')})}
|
||||
key={val.get('value')} onClick={this.handleChange} data-value={val.get('value')}>
|
||||
key={val.get('value')} onClick={this.handleChange} data-value={val.get('value')}>
|
||||
{val.get('label')}
|
||||
</button>
|
||||
))}
|
||||
|
|
|
@ -167,7 +167,7 @@ class AntennaSetting extends PureComponent {
|
|||
|
||||
handleEditAntennaClick = () => {
|
||||
window.open(`/antennas/${this.props.params.id}/edit`, '_blank');
|
||||
}
|
||||
};
|
||||
|
||||
handleDeleteClick = () => {
|
||||
const { dispatch, columnId, intl } = this.props;
|
||||
|
@ -193,7 +193,7 @@ class AntennaSetting extends PureComponent {
|
|||
|
||||
handleTimelineClick = () => {
|
||||
this.context.router.history.push(`/antennast/${this.props.params.id}`);
|
||||
}
|
||||
};
|
||||
|
||||
onStlToggle = ({ target }) => {
|
||||
const { dispatch } = this.props;
|
||||
|
@ -336,7 +336,7 @@ class AntennaSetting extends PureComponent {
|
|||
</label>
|
||||
</div>
|
||||
</>
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
let stlAlert;
|
||||
|
@ -369,7 +369,7 @@ class AntennaSetting extends PureComponent {
|
|||
const contentRadioAlert = antenna.get(contentRadioValue.get('value') === 'tags' ? 'keywords_count' : 'tags_count') > 0;
|
||||
|
||||
const listOptions = lists.toArray().filter((list) => list.length >= 2 && list[1]).map((list) => {
|
||||
return { value: list[1].get('id'), label: list[1].get('title') }
|
||||
return { value: list[1].get('id'), label: list[1].get('title') };
|
||||
});
|
||||
|
||||
return (
|
||||
|
@ -470,7 +470,7 @@ class AntennaSetting extends PureComponent {
|
|||
icon='sitemap'
|
||||
label={intl.formatMessage(messages.addDomainLabel)}
|
||||
title={intl.formatMessage(messages.addDomainTitle)}
|
||||
/>
|
||||
/>
|
||||
)}
|
||||
|
||||
{rangeRadioAlert && <div className='alert'><FormattedMessage id='antennas.warnings.range_radio' defaultMessage='Simultaneous account and domain designation is not recommended.' /></div>}
|
||||
|
@ -487,7 +487,7 @@ class AntennaSetting extends PureComponent {
|
|||
icon='hashtag'
|
||||
label={intl.formatMessage(messages.addTagLabel)}
|
||||
title={intl.formatMessage(messages.addTagTitle)}
|
||||
/>
|
||||
/>
|
||||
)}
|
||||
|
||||
{contentRadioValue.get('value') === 'keywords' && (
|
||||
|
@ -500,7 +500,7 @@ class AntennaSetting extends PureComponent {
|
|||
icon='paragraph'
|
||||
label={intl.formatMessage(messages.addKeywordLabel)}
|
||||
title={intl.formatMessage(messages.addKeywordTitle)}
|
||||
/>
|
||||
/>
|
||||
)}
|
||||
|
||||
{contentRadioAlert && <div className='alert'><FormattedMessage id='antennas.warnings.content_radio' defaultMessage='Simultaneous keyword and tag designation is not recommended.' /></div>}
|
||||
|
@ -518,7 +518,7 @@ class AntennaSetting extends PureComponent {
|
|||
icon='sitemap'
|
||||
label={intl.formatMessage(messages.addDomainLabel)}
|
||||
title={intl.formatMessage(messages.addDomainTitle)}
|
||||
/>
|
||||
/>
|
||||
<h3><FormattedMessage id='antennas.exclude_keywords' defaultMessage='Exclude keywords' /></h3>
|
||||
<TextList
|
||||
onChange={this.onExcludeKeywordNameChanged}
|
||||
|
@ -529,18 +529,18 @@ class AntennaSetting extends PureComponent {
|
|||
icon='paragraph'
|
||||
label={intl.formatMessage(messages.addKeywordLabel)}
|
||||
title={intl.formatMessage(messages.addKeywordTitle)}
|
||||
/>
|
||||
<h3><FormattedMessage id='antennas.exclude_tags' defaultMessage='Exclude tags' /></h3>
|
||||
<TextList
|
||||
onChange={this.onExcludeTagNameChanged}
|
||||
onAdd={this.onExcludeTagAdd}
|
||||
onRemove={this.onExcludeTagRemove}
|
||||
value={this.state.excludeTagName}
|
||||
values={tags.get('exclude_tags') || ImmutableList()}
|
||||
icon='hashtag'
|
||||
label={intl.formatMessage(messages.addTagLabel)}
|
||||
title={intl.formatMessage(messages.addTagTitle)}
|
||||
/>
|
||||
/>
|
||||
<h3><FormattedMessage id='antennas.exclude_tags' defaultMessage='Exclude tags' /></h3>
|
||||
<TextList
|
||||
onChange={this.onExcludeTagNameChanged}
|
||||
onAdd={this.onExcludeTagAdd}
|
||||
onRemove={this.onExcludeTagRemove}
|
||||
value={this.state.excludeTagName}
|
||||
values={tags.get('exclude_tags') || ImmutableList()}
|
||||
icon='hashtag'
|
||||
label={intl.formatMessage(messages.addTagLabel)}
|
||||
title={intl.formatMessage(messages.addTagTitle)}
|
||||
/>
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
|
|
|
@ -79,7 +79,7 @@ class Antennas extends ImmutablePureComponent {
|
|||
>
|
||||
{antennas.map(antenna => (
|
||||
<ColumnLink key={antenna.get('id')} to={`/antennast/${antenna.get('id')}`} icon='wifi' text={antenna.get('title')}
|
||||
badge={antenna.get('insert_feeds') ? intl.formatMessage(antenna.get('list') ? messages.insert_list : messages.insert_home) : undefined} />
|
||||
badge={antenna.get('insert_feeds') ? intl.formatMessage(antenna.get('list') ? messages.insert_list : messages.insert_home) : undefined} />
|
||||
))}
|
||||
</ScrollableList>
|
||||
|
||||
|
|
|
@ -13,12 +13,12 @@ import { debounce } from 'lodash';
|
|||
import { deleteBookmarkCategory, expandBookmarkCategoryStatuses, fetchBookmarkCategory, fetchBookmarkCategoryStatuses , setupBookmarkCategoryEditor } from 'mastodon/actions/bookmark_categories';
|
||||
import { addColumn, removeColumn, moveColumn } from 'mastodon/actions/columns';
|
||||
import { openModal } from 'mastodon/actions/modal';
|
||||
import Column from 'mastodon/components/column';
|
||||
import ColumnHeader from 'mastodon/components/column_header';
|
||||
import { Icon } from 'mastodon/components/icon';
|
||||
import { LoadingIndicator } from 'mastodon/components/loading_indicator';
|
||||
import StatusList from 'mastodon/components/status_list';
|
||||
import BundleColumnError from 'mastodon/features/ui/components/bundle_column_error';
|
||||
import Column from 'mastodon/features/ui/components/column';
|
||||
import { getBookmarkCategoryStatusList } from 'mastodon/selectors';
|
||||
|
||||
import EditBookmarkCategoryForm from './components/edit_bookmark_category_form';
|
||||
|
@ -113,7 +113,7 @@ class BookmarkCategoryStatuses extends ImmutablePureComponent {
|
|||
};
|
||||
|
||||
handleLoadMore = debounce(() => {
|
||||
this.props.dispatch(expandBookmarkCategoryStatuses());
|
||||
this.props.dispatch(expandBookmarkCategoryStatuses(this.props.params.id));
|
||||
}, 300, { leading: true });
|
||||
|
||||
render () {
|
||||
|
|
|
@ -12,9 +12,9 @@ import { debounce } from 'lodash';
|
|||
|
||||
import { fetchBookmarkedStatuses, expandBookmarkedStatuses } from 'mastodon/actions/bookmarks';
|
||||
import { addColumn, removeColumn, moveColumn } from 'mastodon/actions/columns';
|
||||
import Column from 'mastodon/components/column';
|
||||
import ColumnHeader from 'mastodon/components/column_header';
|
||||
import StatusList from 'mastodon/components/status_list';
|
||||
import Column from 'mastodon/features/ui/components/column';
|
||||
import { getStatusList } from 'mastodon/selectors';
|
||||
|
||||
const messages = defineMessages({
|
||||
|
|
|
@ -13,12 +13,12 @@ import { debounce } from 'lodash';
|
|||
import { deleteCircle, expandCircleStatuses, fetchCircle, fetchCircleStatuses } from 'mastodon/actions/circles';
|
||||
import { addColumn, removeColumn, moveColumn } from 'mastodon/actions/columns';
|
||||
import { openModal } from 'mastodon/actions/modal';
|
||||
import Column from 'mastodon/components/column';
|
||||
import ColumnHeader from 'mastodon/components/column_header';
|
||||
import { Icon } from 'mastodon/components/icon';
|
||||
import { LoadingIndicator } from 'mastodon/components/loading_indicator';
|
||||
import StatusList from 'mastodon/components/status_list';
|
||||
import BundleColumnError from 'mastodon/features/ui/components/bundle_column_error';
|
||||
import Column from 'mastodon/features/ui/components/column';
|
||||
import { getCircleStatusList } from 'mastodon/selectors';
|
||||
|
||||
|
||||
|
@ -113,7 +113,7 @@ class CircleStatuses extends ImmutablePureComponent {
|
|||
};
|
||||
|
||||
handleLoadMore = debounce(() => {
|
||||
this.props.dispatch(expandCircleStatuses());
|
||||
this.props.dispatch(expandCircleStatuses(this.props.params.id));
|
||||
}, 300, { leading: true });
|
||||
|
||||
render () {
|
||||
|
|
|
@ -32,7 +32,7 @@ class CircleSelect extends PureComponent {
|
|||
}
|
||||
|
||||
const listOptions = circles.toArray().filter((circle) => circle).map((circle) => {
|
||||
return { value: circle.get('id'), label: circle.get('title') };
|
||||
return { value: circle[1].get('id'), label: circle[1].get('title') };
|
||||
});
|
||||
const listValue = listOptions.find((opt) => opt.value === circleId);
|
||||
|
||||
|
|
|
@ -20,7 +20,7 @@ export default class NavigationBar extends ImmutablePureComponent {
|
|||
};
|
||||
|
||||
render () {
|
||||
const username = this.props.account.get('acct')
|
||||
const username = this.props.account.get('acct');
|
||||
return (
|
||||
<div className='navigation-bar'>
|
||||
<Link to={`/@${username}`}>
|
||||
|
|
|
@ -57,17 +57,17 @@ class Search extends PureComponent {
|
|||
};
|
||||
|
||||
defaultOptions = [
|
||||
{ label: <><mark>has:</mark> <FormattedList type='disjunction' value={['media', 'poll', 'embed']} /></>, action: e => { e.preventDefault(); this._insertText('has:') } },
|
||||
{ label: <><mark>is:</mark> <FormattedList type='disjunction' value={['reply', 'sensitive']} /></>, action: e => { e.preventDefault(); this._insertText('is:') } },
|
||||
{ label: <><mark>my:</mark> <FormattedList type='disjunction' value={['favourited', 'bookmarked', 'boosted']} /></>, action: e => { e.preventDefault(); this._insertText('my:') } },
|
||||
{ label: <><mark>language:</mark> <FormattedMessage id='search_popout.language_code' defaultMessage='ISO language code' /></>, action: e => { e.preventDefault(); this._insertText('language:') } },
|
||||
{ label: <><mark>from:</mark> <FormattedMessage id='search_popout.user' defaultMessage='user' /></>, action: e => { e.preventDefault(); this._insertText('from:') } },
|
||||
{ label: <><mark>domain:</mark> <FormattedMessage id='search_popout.domain' defaultMessage='domain' /></>, action: e => { e.preventDefault(); this._insertText('domain:') } },
|
||||
{ label: <><mark>before:</mark> <FormattedMessage id='search_popout.specific_date' defaultMessage='specific date' /></>, action: e => { e.preventDefault(); this._insertText('before:') } },
|
||||
{ label: <><mark>during:</mark> <FormattedMessage id='search_popout.specific_date' defaultMessage='specific date' /></>, action: e => { e.preventDefault(); this._insertText('during:') } },
|
||||
{ label: <><mark>after:</mark> <FormattedMessage id='search_popout.specific_date' defaultMessage='specific date' /></>, action: e => { e.preventDefault(); this._insertText('after:') } },
|
||||
{ label: <><mark>in:</mark> <FormattedList type='disjunction' value={['all', 'library']} /></>, action: e => { e.preventDefault(); this._insertText('in:') } },
|
||||
{ label: <><mark>order:</mark> <FormattedList type='disjunction' value={['desc', 'asc']} /></>, action: e => { e.preventDefault(); this._insertText('order:') } },
|
||||
{ label: <><mark>has:</mark> <FormattedList type='disjunction' value={['media', 'poll', 'embed']} /></>, action: e => { e.preventDefault(); this._insertText('has:'); } },
|
||||
{ label: <><mark>is:</mark> <FormattedList type='disjunction' value={['reply', 'sensitive']} /></>, action: e => { e.preventDefault(); this._insertText('is:'); } },
|
||||
{ label: <><mark>my:</mark> <FormattedList type='disjunction' value={['favourited', 'bookmarked', 'boosted']} /></>, action: e => { e.preventDefault(); this._insertText('my:'); } },
|
||||
{ label: <><mark>language:</mark> <FormattedMessage id='search_popout.language_code' defaultMessage='ISO language code' /></>, action: e => { e.preventDefault(); this._insertText('language:'); } },
|
||||
{ label: <><mark>from:</mark> <FormattedMessage id='search_popout.user' defaultMessage='user' /></>, action: e => { e.preventDefault(); this._insertText('from:'); } },
|
||||
{ label: <><mark>domain:</mark> <FormattedMessage id='search_popout.domain' defaultMessage='domain' /></>, action: e => { e.preventDefault(); this._insertText('domain:'); } },
|
||||
{ label: <><mark>before:</mark> <FormattedMessage id='search_popout.specific_date' defaultMessage='specific date' /></>, action: e => { e.preventDefault(); this._insertText('before:'); } },
|
||||
{ label: <><mark>during:</mark> <FormattedMessage id='search_popout.specific_date' defaultMessage='specific date' /></>, action: e => { e.preventDefault(); this._insertText('during:'); } },
|
||||
{ label: <><mark>after:</mark> <FormattedMessage id='search_popout.specific_date' defaultMessage='specific date' /></>, action: e => { e.preventDefault(); this._insertText('after:'); } },
|
||||
{ label: <><mark>in:</mark> <FormattedList type='disjunction' value={['all', 'library', 'public']} /></>, action: e => { e.preventDefault(); this._insertText('in:'); } },
|
||||
{ label: <><mark>order:</mark> <FormattedList type='disjunction' value={['desc', 'asc']} /></>, action: e => { e.preventDefault(); this._insertText('order:'); } },
|
||||
];
|
||||
|
||||
setRef = c => {
|
||||
|
|
|
@ -12,9 +12,9 @@ import { debounce } from 'lodash';
|
|||
|
||||
import { addColumn, removeColumn, moveColumn } from 'mastodon/actions/columns';
|
||||
import { fetchEmojiReactedStatuses, expandEmojiReactedStatuses } from 'mastodon/actions/emoji_reactions';
|
||||
import Column from 'mastodon/components/column';
|
||||
import ColumnHeader from 'mastodon/components/column_header';
|
||||
import StatusList from 'mastodon/components/status_list';
|
||||
import Column from 'mastodon/features/ui/components/column';
|
||||
|
||||
const messages = defineMessages({
|
||||
heading: { id: 'column.emoji_reactions', defaultMessage: 'Stamps' },
|
||||
|
|
|
@ -80,7 +80,7 @@ class Results extends PureComponent {
|
|||
}
|
||||
|
||||
return null;
|
||||
};
|
||||
}
|
||||
|
||||
handleSelectAll = () => {
|
||||
const { submittedType, dispatch } = this.props;
|
||||
|
@ -116,7 +116,7 @@ class Results extends PureComponent {
|
|||
}
|
||||
|
||||
this.setState({ type: 'hashtags' });
|
||||
}
|
||||
};
|
||||
|
||||
handleSelectStatuses = () => {
|
||||
const { submittedType, dispatch } = this.props;
|
||||
|
@ -128,7 +128,7 @@ class Results extends PureComponent {
|
|||
}
|
||||
|
||||
this.setState({ type: 'statuses' });
|
||||
}
|
||||
};
|
||||
|
||||
handleLoadMoreAccounts = () => this._loadMore('accounts');
|
||||
handleLoadMoreStatuses = () => this._loadMore('statuses');
|
||||
|
|
|
@ -12,9 +12,9 @@ import { debounce } from 'lodash';
|
|||
|
||||
import { addColumn, removeColumn, moveColumn } from 'mastodon/actions/columns';
|
||||
import { fetchFavouritedStatuses, expandFavouritedStatuses } from 'mastodon/actions/favourites';
|
||||
import Column from 'mastodon/components/column';
|
||||
import ColumnHeader from 'mastodon/components/column_header';
|
||||
import StatusList from 'mastodon/components/status_list';
|
||||
import Column from 'mastodon/features/ui/components/column';
|
||||
import { getStatusList } from 'mastodon/selectors';
|
||||
|
||||
const messages = defineMessages({
|
||||
|
|
|
@ -199,7 +199,7 @@ const Firehose = ({ feedType, multiColumn }) => {
|
|||
</Helmet>
|
||||
</Column>
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
Firehose.propTypes = {
|
||||
multiColumn: PropTypes.bool,
|
||||
|
|
|
@ -27,9 +27,9 @@ const mapStateToProps = (state, { accountId }) => ({
|
|||
const mapDispatchToProps = (dispatch) => ({
|
||||
onSignupClick() {
|
||||
dispatch(closeModal({
|
||||
modalType: undefined,
|
||||
ignoreFocus: false,
|
||||
}));
|
||||
modalType: undefined,
|
||||
ignoreFocus: false,
|
||||
}));
|
||||
dispatch(openModal({ modalType: 'CLOSED_REGISTRATIONS' }));
|
||||
},
|
||||
});
|
||||
|
@ -187,7 +187,7 @@ class LoginForm extends React.PureComponent {
|
|||
|
||||
setIFrameRef = (iframe) => {
|
||||
this.iframeRef = iframe;
|
||||
}
|
||||
};
|
||||
|
||||
handleFocus = () => {
|
||||
this.setState({ expanded: true });
|
||||
|
|
|
@ -78,7 +78,7 @@ class Lists extends ImmutablePureComponent {
|
|||
>
|
||||
{lists.map(list =>
|
||||
(<ColumnLink key={list.get('id')} to={`/lists/${list.get('id')}`} icon='list-ul' text={list.get('title')}
|
||||
badge={(list.get('antennas') && list.get('antennas').size > 0) ? intl.formatMessage(messages.with_antenna) : undefined} />),
|
||||
badge={(list.get('antennas') && list.get('antennas').size > 0) ? intl.formatMessage(messages.with_antenna) : undefined} />),
|
||||
)}
|
||||
</ScrollableList>
|
||||
|
||||
|
|
|
@ -59,7 +59,7 @@ class ReactionEmoji extends ImmutablePureComponent {
|
|||
const html = { __html: emojify(emoji) };
|
||||
content = (
|
||||
<span dangerouslySetInnerHTML={html} />
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
|
|
|
@ -100,7 +100,7 @@ class ReactionDeck extends ImmutablePureComponent {
|
|||
const newDeck = this.deckToArray();
|
||||
newDeck.push('👍');
|
||||
this.props.onChange(newDeck);
|
||||
}
|
||||
};
|
||||
|
||||
render () {
|
||||
const { intl, deck, emojiMap, multiColumn } = this.props;
|
||||
|
@ -123,38 +123,38 @@ class ReactionDeck extends ImmutablePureComponent {
|
|||
showBackButton
|
||||
/>
|
||||
|
||||
<ScrollableList
|
||||
scrollKey='reaction_deck'
|
||||
bindToDocument={!multiColumn}
|
||||
>
|
||||
<DragDropContext onDragEnd={this.handleReorder}>
|
||||
<StrictModeDroppable droppableId='deckitems'>
|
||||
{(provided) => (
|
||||
<div className='deckitems reaction_deck_container' {...provided.droppableProps} ref={provided.innerRef}>
|
||||
{deck.map((emoji, index) => (
|
||||
<Draggable key={index} draggableId={'' + index} index={index}>
|
||||
{(provided2) => (
|
||||
<div className='reaction_deck_container__row' ref={provided2.innerRef} {...provided2.draggableProps}>
|
||||
<Icon id='bars' className='handle' {...provided2.dragHandleProps} />
|
||||
<ReactionEmoji emojiMap={emojiMap}
|
||||
emoji={emoji.get('name')}
|
||||
index={index}
|
||||
onChange={this.handleChange}
|
||||
onRemove={this.handleRemove}
|
||||
className='reaction_emoji'
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
</Draggable>
|
||||
))}
|
||||
{provided.placeholder}
|
||||
<ScrollableList
|
||||
scrollKey='reaction_deck'
|
||||
bindToDocument={!multiColumn}
|
||||
>
|
||||
<DragDropContext onDragEnd={this.handleReorder}>
|
||||
<StrictModeDroppable droppableId='deckitems'>
|
||||
{(provided) => (
|
||||
<div className='deckitems reaction_deck_container' {...provided.droppableProps} ref={provided.innerRef}>
|
||||
{deck.map((emoji, index) => (
|
||||
<Draggable key={index} draggableId={'' + index} index={index}>
|
||||
{(provided2) => (
|
||||
<div className='reaction_deck_container__row' ref={provided2.innerRef} {...provided2.draggableProps}>
|
||||
<Icon id='bars' className='handle' {...provided2.dragHandleProps} />
|
||||
<ReactionEmoji emojiMap={emojiMap}
|
||||
emoji={emoji.get('name')}
|
||||
index={index}
|
||||
onChange={this.handleChange}
|
||||
onRemove={this.handleRemove}
|
||||
className='reaction_emoji'
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
</Draggable>
|
||||
))}
|
||||
{provided.placeholder}
|
||||
|
||||
<Button text={intl.formatMessage(messages.reaction_deck_add)} onClick={this.handleAdd} />
|
||||
</div>
|
||||
)}
|
||||
</StrictModeDroppable>
|
||||
</DragDropContext>
|
||||
</ScrollableList>
|
||||
<Button text={intl.formatMessage(messages.reaction_deck_add)} onClick={this.handleAdd} />
|
||||
</div>
|
||||
)}
|
||||
</StrictModeDroppable>
|
||||
</DragDropContext>
|
||||
</ScrollableList>
|
||||
|
||||
<Helmet>
|
||||
<meta name='robots' content='noindex' />
|
||||
|
|
|
@ -45,7 +45,7 @@ class Reblogs extends ImmutablePureComponent {
|
|||
if (!this.props.accountIds) {
|
||||
this.props.dispatch(fetchReblogs(this.props.params.statusId));
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
handleRefresh = () => {
|
||||
this.props.dispatch(fetchReblogs(this.props.params.statusId));
|
||||
|
|
|
@ -104,7 +104,7 @@ const Comment = ({ comment, domain, statusIds, isRemote, isSubmitting, selectedD
|
|||
</div>
|
||||
</>
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
Comment.propTypes = {
|
||||
comment: PropTypes.string.isRequired,
|
||||
|
|
|
@ -147,7 +147,7 @@ class DetailedStatus extends ImmutablePureComponent {
|
|||
} else if (attachments.getIn([0, 'type']) === 'audio') {
|
||||
return '16 / 9';
|
||||
} else {
|
||||
return (attachments.size === 1 && attachments.getIn([0, 'meta', 'small', 'aspect'])) ? attachments.getIn([0, 'meta', 'small', 'aspect']) : '3 / 2'
|
||||
return (attachments.size === 1 && attachments.getIn([0, 'meta', 'small', 'aspect'])) ? attachments.getIn([0, 'meta', 'small', 'aspect']) : '3 / 2';
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -83,8 +83,8 @@ class Header extends PureComponent {
|
|||
|
||||
if (sso_redirect) {
|
||||
content = (
|
||||
<a href={sso_redirect} data-method='post' className='button button--block button-tertiary'><FormattedMessage id='sign_in_banner.sso_redirect' defaultMessage='Login or Register' /></a>
|
||||
)
|
||||
<a href={sso_redirect} data-method='post' className='button button--block button-tertiary'><FormattedMessage id='sign_in_banner.sso_redirect' defaultMessage='Login or Register' /></a>
|
||||
);
|
||||
} else {
|
||||
let signupButton;
|
||||
|
||||
|
|
|
@ -127,7 +127,7 @@ export default class ModalRoot extends PureComponent {
|
|||
<BundleContainer fetchComponent={MODAL_COMPONENTS[type]} loading={this.renderLoading(type)} error={this.renderError} renderDelay={200}>
|
||||
{(SpecificComponent) => {
|
||||
const ref = typeof SpecificComponent !== 'function' ? this.setModalRef : undefined;
|
||||
return <SpecificComponent {...props} onChangeBackgroundColor={this.setBackgroundColor} onClose={this.handleClose} ref={ref} />
|
||||
return <SpecificComponent {...props} onChangeBackgroundColor={this.setBackgroundColor} onClose={this.handleClose} ref={ref} />;
|
||||
}}
|
||||
</BundleContainer>
|
||||
|
||||
|
|
|
@ -25,7 +25,7 @@ const SignInBanner = () => {
|
|||
<p><FormattedMessage id='sign_in_banner.text' defaultMessage='Login to follow profiles or hashtags, favorite, share and reply to posts. You can also interact from your account on a different server.' /></p>
|
||||
<a href={sso_redirect} data-method='post' className='button button--block button-tertiary'><FormattedMessage id='sign_in_banner.sso_redirect' defaultMessage='Login or Register' /></a>
|
||||
</div>
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
if (registrationsOpen) {
|
||||
|
|
|
@ -198,7 +198,9 @@ class SwitchingColumnsArea extends PureComponent {
|
|||
|
||||
{singleColumn ? <Redirect from='/deck' to='/home' exact /> : null}
|
||||
{singleColumn && pathName.startsWith('/deck/') ? <Redirect from={pathName} to={pathName.slice(5)} /> : null}
|
||||
{/* Redirect old bookmarks (without /deck) with home-like routes to the advanced interface */}
|
||||
{!singleColumn && pathName === '/getting-started' ? <Redirect from='/getting-started' to='/deck/getting-started' exact /> : null}
|
||||
{!singleColumn && pathName === '/home' ? <Redirect from='/home' to='/deck/getting-started' exact /> : null}
|
||||
|
||||
<WrappedRoute path='/getting-started' component={GettingStarted} content={children} />
|
||||
<WrappedRoute path='/keyboard-shortcuts' component={KeyboardShortcuts} content={children} />
|
||||
|
|
|
@ -113,6 +113,7 @@ const initialPath = document.querySelector("head meta[name=initialPath]")?.getAt
|
|||
/** @type {boolean} */
|
||||
export const hasMultiColumnPath = initialPath === '/'
|
||||
|| initialPath === '/getting-started'
|
||||
|| initialPath === '/home'
|
||||
|| initialPath.startsWith('/deck');
|
||||
|
||||
/**
|
||||
|
|
|
@ -613,7 +613,7 @@
|
|||
"sign_in_banner.create_account": "Sortu kontua",
|
||||
"sign_in_banner.sign_in": "Hasi saioa",
|
||||
"sign_in_banner.sso_redirect": "Hasi saioa edo izena eman",
|
||||
"sign_in_banner.text": "Hasi saioa profilak edo traolak jarraitzeko, bidalketak gogokoetara gehitzeko, partekatzeko edo erantzuteko. Zure kontutik ere komunika zaitezke beste zerbitzari ezberdin vatean.",
|
||||
"sign_in_banner.text": "Hasi saioa profilak edo traolak jarraitzeko, bidalketak gogokoetara gehitzeko, partekatzeko edo erantzuteko. Zure kontutik ere komunika zaitezke beste zerbitzari ezberdin batean.",
|
||||
"status.admin_account": "Ireki @{name} erabiltzailearen moderazio interfazea",
|
||||
"status.admin_domain": "{domain}-(r)en moderazio-interfazea ireki",
|
||||
"status.admin_status": "Ireki bidalketa hau moderazio interfazean",
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
{
|
||||
"about.blocks": "Valvotut palvelimet",
|
||||
"about.contact": "Yhteydenotto:",
|
||||
"about.contact": "Ota yhteyttä:",
|
||||
"about.disclaimer": "Mastodon on vapaa avoimen lähdekoodin ohjelmisto ja Mastodon gGmbH:n tavaramerkki.",
|
||||
"about.domain_blocks.no_reason_available": "Syytä ei ole ilmoitettu",
|
||||
"about.domain_blocks.preamble": "Mastodonin avulla voidaan yleensä tarkastella minkä tahansa fediversumiin kuuluvan palvelimen sisältöä ja vuorovaikuttaa eri palvelinten käyttäjien kanssa. Nämä ovat tälle palvelimelle määritetyt poikkeukset.",
|
||||
|
@ -26,15 +26,15 @@
|
|||
"account.domain_blocked": "Verkkotunnus estetty",
|
||||
"account.edit_profile": "Muokkaa profiilia",
|
||||
"account.enable_notifications": "Ilmoita minulle, kun @{name} julkaisee",
|
||||
"account.endorse": "Suosittele profiilissasi",
|
||||
"account.endorse": "Pidä esillä profiilissa",
|
||||
"account.featured_tags.last_status_at": "Viimeisin julkaisu {date}",
|
||||
"account.featured_tags.last_status_never": "Ei julkaisuja",
|
||||
"account.featured_tags.title": "Käyttäjän {name} esille nostetut aihetunnisteet",
|
||||
"account.featured_tags.title": "Käyttäjän {name} esillä pidettävät aihetunnisteet",
|
||||
"account.follow": "Seuraa",
|
||||
"account.followers": "seuraaja(t)",
|
||||
"account.followers": "Seuraajat",
|
||||
"account.followers.empty": "Kukaan ei seuraa tätä käyttäjää vielä.",
|
||||
"account.followers_counter": "{count, plural, one {{counter} seuraaja} other {{counter} seuraajaa}}",
|
||||
"account.following": "Seurataan",
|
||||
"account.following": "Seuratut",
|
||||
"account.following_counter": "{count, plural, one {{counter} seurattu} other {{counter} seurattua}}",
|
||||
"account.follows.empty": "Tämä käyttäjä ei vielä seuraa ketään.",
|
||||
"account.follows_you": "Seuraa sinua",
|
||||
|
@ -62,17 +62,17 @@
|
|||
"account.share": "Jaa käyttäjän @{name} profiili",
|
||||
"account.show_reblogs": "Näytä käyttäjän @{name} tehostukset",
|
||||
"account.statuses_counter": "{count, plural, one {{counter} julkaisu} other {{counter} julkaisua}}",
|
||||
"account.unblock": "Poista esto: @{name}",
|
||||
"account.unblock_domain": "Salli palvelu {domain}",
|
||||
"account.unblock": "Poista käyttäjän @{name} esto",
|
||||
"account.unblock_domain": "Poista verkkotunnuksen {domain} esto",
|
||||
"account.unblock_short": "Poista esto",
|
||||
"account.unendorse": "Poista suosittelu profiilistasi",
|
||||
"account.unendorse": "Älä pidä esillä profiilissa",
|
||||
"account.unfollow": "Lopeta seuraaminen",
|
||||
"account.unmute": "Poista käyttäjän @{name} mykistys",
|
||||
"account.unmute_notifications_short": "Poista ilmoitusten mykistys",
|
||||
"account.unmute_short": "Poista mykistys",
|
||||
"account_note.placeholder": "Lisää muistiinpano napsauttamalla",
|
||||
"admin.dashboard.daily_retention": "Käyttäjän säilyminen rekisteröitymisen jälkeiseen päivään mennessä",
|
||||
"admin.dashboard.monthly_retention": "Käyttäjän säilyminen rekisteröitymisen jälkeiseen kuukauteen mennessä",
|
||||
"admin.dashboard.daily_retention": "Käyttäjän pysyminen rekisteröitymisen jälkeiseen päivään mennessä",
|
||||
"admin.dashboard.monthly_retention": "Käyttäjän pysyminen rekisteröitymisen jälkeiseen kuukauteen mennessä",
|
||||
"admin.dashboard.retention.average": "Keskimäärin",
|
||||
"admin.dashboard.retention.cohort": "Kirjautumiset",
|
||||
"admin.dashboard.retention.cohort_size": "Uudet käyttäjät",
|
||||
|
@ -101,7 +101,7 @@
|
|||
"bundle_modal_error.close": "Sulje",
|
||||
"bundle_modal_error.message": "Jotain meni pieleen komponenttia ladattaessa.",
|
||||
"bundle_modal_error.retry": "Yritä uudelleen",
|
||||
"closed_registrations.other_server_instructions": "Koska Mastodon on hajautettu, voit luoda tilin toiselle palvelimelle ja silti olla vuorovaikutuksessa tämän kanssa.",
|
||||
"closed_registrations.other_server_instructions": "Koska Mastodon on hajautettu, voit luoda tilin toiselle palvelimelle ja olla silti vuorovaikutuksessa tämän kanssa.",
|
||||
"closed_registrations_modal.description": "Tilin luonti palveluun {domain} ei tällä hetkellä ole mahdollista, mutta huomioi, ettei Mastodonin käyttö edellytä juuri kyseisen palvelun tiliä.",
|
||||
"closed_registrations_modal.find_another_server": "Etsi toinen palvelin",
|
||||
"closed_registrations_modal.preamble": "Mastodon on hajautettu, joten riippumatta siitä, missä luot tilisi, voit seurata ja olla vuorovaikutuksessa kenen tahansa kanssa tällä palvelimella. Voit jopa isännöidä palvelinta!",
|
||||
|
@ -154,9 +154,9 @@
|
|||
"compose_form.publish_form": "Uusi julkaisu",
|
||||
"compose_form.publish_loud": "{publish}!",
|
||||
"compose_form.save_changes": "Tallenna muutokset",
|
||||
"compose_form.sensitive.hide": "{count, plural, one {Merkitse media arkaluontoiseksi} other {Merkitse mediat arkaluontoiseksi}}",
|
||||
"compose_form.sensitive.marked": "{count, plural, one {Media on merkitty arkaluontoiseksi} other {Mediat on merkitty arkaluontoiseksi}}",
|
||||
"compose_form.sensitive.unmarked": "{count, plural, one {Mediaa ei ole merkitty arkaluontoiseksi} other {Medioja ei ole merkitty arkaluontoiseksi}}",
|
||||
"compose_form.sensitive.hide": "{count, plural, one {Merkitse media arkaluonteiseksi} other {Merkitse mediat arkaluonteisiksi}}",
|
||||
"compose_form.sensitive.marked": "{count, plural, one {Media on merkitty arkaluonteiseksi} other {Mediat on merkitty arkaluonteisiksi}}",
|
||||
"compose_form.sensitive.unmarked": "{count, plural, one {Mediaa ei ole merkitty arkaluonteiseksi} other {Medioita ei ole merkitty arkaluonteisiksi}}",
|
||||
"compose_form.spoiler.marked": "Poista sisältövaroitus",
|
||||
"compose_form.spoiler.unmarked": "Lisää sisältövaroitus",
|
||||
"compose_form.spoiler_placeholder": "Kirjoita varoituksesi tähän",
|
||||
|
@ -194,7 +194,7 @@
|
|||
"copypaste.copied": "Kopioitu",
|
||||
"copypaste.copy_to_clipboard": "Kopioi leikepöydälle",
|
||||
"directory.federated": "Koko tunnettu fediversumi",
|
||||
"directory.local": "Vain palvelusta {domain}",
|
||||
"directory.local": "Vain palvelimelta {domain}",
|
||||
"directory.new_arrivals": "Äskettäin saapuneet",
|
||||
"directory.recently_active": "Hiljattain aktiiviset",
|
||||
"disabled_account_banner.account_settings": "Tilin asetukset",
|
||||
|
@ -225,12 +225,12 @@
|
|||
"empty_column.account_suspended": "Tili jäädytetty",
|
||||
"empty_column.account_timeline": "Ei viestejä täällä.",
|
||||
"empty_column.account_unavailable": "Profiilia ei löydy",
|
||||
"empty_column.blocks": "Et ole estänyt käyttäjiä.",
|
||||
"empty_column.blocks": "Et ole vielä estänyt käyttäjiä.",
|
||||
"empty_column.bookmarked_statuses": "Et ole vielä lisännyt julkaisuja kirjanmerkkeihisi. Kun lisäät yhden, se näkyy tässä.",
|
||||
"empty_column.community": "Paikallinen aikajana on tyhjä. Kirjoita jotain julkista, niin homma lähtee käyntiin!",
|
||||
"empty_column.direct": "Yksityisiä mainintoja ei vielä ole. Jos lähetät tai sinulle lähetetään sellaisia, näet ne täällä.",
|
||||
"empty_column.domain_blocks": "Palveluita ei ole vielä estetty.",
|
||||
"empty_column.explore_statuses": "Mikään ei trendaa nyt. Tarkista myöhemmin uudelleen!",
|
||||
"empty_column.domain_blocks": "Verkkotunnuksia ei ole vielä estetty.",
|
||||
"empty_column.explore_statuses": "Mikään ei ole nyt suosittua. Tarkista myöhemmin uudelleen!",
|
||||
"empty_column.favourited_statuses": "Sinulla ei ole vielä yhtään suosikkijulkaisua. Kun lisäät sellaisen, näkyy se tässä.",
|
||||
"empty_column.favourites": "Kukaan ei ole vielä lisännyt tätä julkaisua suosikkeihinsa. Kun joku tekee niin, tulee hän tähän näkyviin.",
|
||||
"empty_column.follow_requests": "Et ole vielä vastaanottanut seuraamispyyntöjä. Saamasi pyynnöt näkyvät täällä.",
|
||||
|
@ -266,7 +266,7 @@
|
|||
"filter_modal.select_filter.context_mismatch": "ei sovellu tähän kontekstiin",
|
||||
"filter_modal.select_filter.expired": "vanhentunut",
|
||||
"filter_modal.select_filter.prompt_new": "Uusi luokka: {name}",
|
||||
"filter_modal.select_filter.search": "Etsi tai luo",
|
||||
"filter_modal.select_filter.search": "Hae tai luo",
|
||||
"filter_modal.select_filter.subtitle": "Käytä olemassa olevaa luokkaa tai luo uusi",
|
||||
"filter_modal.select_filter.title": "Suodata tämä julkaisu",
|
||||
"filter_modal.title.status": "Suodata julkaisu",
|
||||
|
@ -336,9 +336,9 @@
|
|||
"keyboard_shortcuts.blocked": "Avaa estettyjen käyttäjien luettelo",
|
||||
"keyboard_shortcuts.boost": "Tehosta julkaisua",
|
||||
"keyboard_shortcuts.column": "Kohdista sarakkeeseen",
|
||||
"keyboard_shortcuts.compose": "siirry tekstinsyöttöön",
|
||||
"keyboard_shortcuts.compose": "Kohdista kirjoituskenttään",
|
||||
"keyboard_shortcuts.description": "Kuvaus",
|
||||
"keyboard_shortcuts.direct": "avataksesi yksityisten mainintojen sarakkeen",
|
||||
"keyboard_shortcuts.direct": "Avaa yksityisten mainintojen sarake",
|
||||
"keyboard_shortcuts.down": "Siirry listassa alaspäin",
|
||||
"keyboard_shortcuts.enter": "Avaa julkaisu",
|
||||
"keyboard_shortcuts.favourite": "Lisää julkaisu suosikkeihin",
|
||||
|
@ -347,22 +347,22 @@
|
|||
"keyboard_shortcuts.heading": "Pikanäppäimet",
|
||||
"keyboard_shortcuts.home": "Avaa kotiaikajana",
|
||||
"keyboard_shortcuts.hotkey": "Pikanäppäin",
|
||||
"keyboard_shortcuts.legend": "Näytä tämä selite",
|
||||
"keyboard_shortcuts.legend": "Näytä tämä ohje",
|
||||
"keyboard_shortcuts.local": "Avaa paikallinen aikajana",
|
||||
"keyboard_shortcuts.mention": "Mainitse julkaisija",
|
||||
"keyboard_shortcuts.muted": "Avaa lista mykistetyistä käyttäjistä",
|
||||
"keyboard_shortcuts.mention": "Mainitse kirjoittaja",
|
||||
"keyboard_shortcuts.muted": "Avaa mykistettyjen käyttäjien luettelo",
|
||||
"keyboard_shortcuts.my_profile": "Avaa profiilisi",
|
||||
"keyboard_shortcuts.notifications": "Avaa ilmoitukset-valikko",
|
||||
"keyboard_shortcuts.notifications": "Avaa ilmoitussarake",
|
||||
"keyboard_shortcuts.open_media": "Avaa media",
|
||||
"keyboard_shortcuts.pinned": "Avaa kiinnitettyjen julkaisujen luettelo",
|
||||
"keyboard_shortcuts.profile": "Avaa kirjoittajan profiili",
|
||||
"keyboard_shortcuts.reply": "Vastaa julkaisuun",
|
||||
"keyboard_shortcuts.requests": "Avaa seuraamispyyntöjen luettelo",
|
||||
"keyboard_shortcuts.search": "siirry hakukenttään",
|
||||
"keyboard_shortcuts.search": "Kohdista hakukenttään",
|
||||
"keyboard_shortcuts.spoilers": "Näytä/piilota sisältövaroituskenttä",
|
||||
"keyboard_shortcuts.start": "avaa \"Aloitus\"",
|
||||
"keyboard_shortcuts.toggle_hidden": "näytä/piilota sisältövaroituksella merkitty teksti",
|
||||
"keyboard_shortcuts.toggle_sensitivity": "näytä/piilota media",
|
||||
"keyboard_shortcuts.start": "Avaa Näin pääset alkuun -sarake",
|
||||
"keyboard_shortcuts.toggle_hidden": "Näytä/piilota sisältövaroituksella merkitty teksti",
|
||||
"keyboard_shortcuts.toggle_sensitivity": "Näytä/piilota media",
|
||||
"keyboard_shortcuts.toot": "Luo uusi julkaisu",
|
||||
"keyboard_shortcuts.unfocus": "Poistu teksti-/hakukentästä",
|
||||
"keyboard_shortcuts.up": "Siirry listassa ylöspäin",
|
||||
|
@ -414,12 +414,12 @@
|
|||
"navigation_bar.lists": "Listat",
|
||||
"navigation_bar.logout": "Kirjaudu ulos",
|
||||
"navigation_bar.mutes": "Mykistetyt käyttäjät",
|
||||
"navigation_bar.opened_in_classic_interface": "Julkaisut, profiilit ja tietyt muut sivut avautuvat oletuksena perinteiseen web-käyttöliittymään.",
|
||||
"navigation_bar.opened_in_classic_interface": "Julkaisut, profiilit ja tietyt muut sivut avautuvat oletuksena perinteiseen selainkäyttöliittymään.",
|
||||
"navigation_bar.personal": "Henkilökohtainen",
|
||||
"navigation_bar.pins": "Kiinnitetyt julkaisut",
|
||||
"navigation_bar.preferences": "Asetukset",
|
||||
"navigation_bar.public_timeline": "Yleinen aikajana",
|
||||
"navigation_bar.search": "Haku",
|
||||
"navigation_bar.search": "Hae",
|
||||
"navigation_bar.security": "Turvallisuus",
|
||||
"not_signed_in_indicator.not_signed_in": "Sinun on kirjauduttava sisään käyttääksesi resurssia.",
|
||||
"notification.admin.report": "{name} teki ilmoituksen käytäjästä {target}",
|
||||
|
@ -472,14 +472,14 @@
|
|||
"notifications_permission_banner.title": "Älä anna minkään mennä ohi",
|
||||
"onboarding.action.back": "Palaa takaisin",
|
||||
"onboarding.actions.back": "Palaa takaisin",
|
||||
"onboarding.actions.go_to_explore": "Siirry suosituimpien aiheiden syötteeseen",
|
||||
"onboarding.actions.go_to_explore": "Siirry suosittujen aiheiden syötteeseen",
|
||||
"onboarding.actions.go_to_home": "Siirry kotisyötteeseeni",
|
||||
"onboarding.compose.template": "Tervehdys #Mastodon!",
|
||||
"onboarding.follows.empty": "Valitettavasti tuloksia ei voida näyttää juuri nyt. Voit kokeilla hakua tai selata tutustumissivua löytääksesi seurattavaa, tai yrittää myöhemmin uudelleen.",
|
||||
"onboarding.follows.empty": "Valitettavasti tuloksia ei voida näyttää juuri nyt. Voit kokeilla hakua tai selata tutustumissivua löytääksesi seurattavaa tai yrittää myöhemmin uudelleen.",
|
||||
"onboarding.follows.lead": "Kokoat oman kotisyötteesi itse. Mitä enemmän ihmisiä seuraat, sitä aktiivisempi ja kiinnostavampi syöte on. Nämä profiilit voivat olla alkuun hyvä lähtökohta — voit aina lopettaa niiden seuraamisen myöhemmin!",
|
||||
"onboarding.follows.title": "Mukauta kotisyötettäsi",
|
||||
"onboarding.share.lead": "Kerro ihmisille, kuinka he voivat löytää sinut Mastodonista!",
|
||||
"onboarding.share.message": "Olen {username} #Mastodon'issa! Seuraa minua osoitteessa {url}",
|
||||
"onboarding.share.message": "Olen {username} #Mastodonissa! Seuraa minua osoitteessa {url}",
|
||||
"onboarding.share.next_steps": "Mahdolliset seuraavat vaiheet:",
|
||||
"onboarding.share.title": "Jaa profiilisi",
|
||||
"onboarding.start.lead": "Uusi Mastodon-tilisi on nyt valmiina käyttöön. Kyseessä on ainutlaatuinen, hajautettu sosiaalisen median alusta, jolla sinä itse – algoritmin sijaan – määrität käyttökokemuksesi. Näin hyödyt Mastodonista eniten:",
|
||||
|
@ -537,7 +537,7 @@
|
|||
"relative_time.today": "tänään",
|
||||
"reply_indicator.cancel": "Peruuta",
|
||||
"report.block": "Estä",
|
||||
"report.block_explanation": "Et näe hänen viestejään, eikä hän voi nähdä viestejäsi tai seurata sinua. Hän näkevät, että olet estänyt hänet.",
|
||||
"report.block_explanation": "Et näe hänen viestejään, eikä hän voi nähdä viestejäsi tai seurata sinua. Hän näkee, että olet estänyt hänet.",
|
||||
"report.categories.legal": "Lakiasiat",
|
||||
"report.categories.other": "Muu",
|
||||
"report.categories.spam": "Roskaposti",
|
||||
|
@ -584,17 +584,17 @@
|
|||
"report_notification.open": "Avaa raportti",
|
||||
"search.no_recent_searches": "Ei viimeaikaisia hakuja",
|
||||
"search.placeholder": "Hae",
|
||||
"search.quick_action.account_search": "Profiilit, jotka vastaavat hakua {x}",
|
||||
"search.quick_action.go_to_account": "Avaa profiili {x}",
|
||||
"search.quick_action.account_search": "Profiilit haulla {x}",
|
||||
"search.quick_action.go_to_account": "Siirry profiiliin {x}",
|
||||
"search.quick_action.go_to_hashtag": "Siirry aihetunnisteeseen {x}",
|
||||
"search.quick_action.open_url": "Avaa URL-osoite Mastodonissa",
|
||||
"search.quick_action.status_search": "Julkaisut haulla {x}",
|
||||
"search.search_or_paste": "Etsi tai kirjoita URL-osoite",
|
||||
"search.search_or_paste": "Hae tai kirjoita URL-osoite",
|
||||
"search_popout.full_text_search_disabled_message": "Ei saatavilla palvelimella {domain}.",
|
||||
"search_popout.language_code": "ISO-kielikoodi",
|
||||
"search_popout.options": "Haun asetukset",
|
||||
"search_popout.options": "Hakuvalinnat",
|
||||
"search_popout.quick_actions": "Pikatoiminnot",
|
||||
"search_popout.recent": "Viime haut",
|
||||
"search_popout.recent": "Viimeaikaiset haut",
|
||||
"search_popout.specific_date": "tietty päivämäärä",
|
||||
"search_popout.user": "käyttäjä",
|
||||
"search_results.accounts": "Profiilit",
|
||||
|
@ -637,7 +637,7 @@
|
|||
"status.history.created": "{name} luotu {date}",
|
||||
"status.history.edited": "{name} muokkasi {date}",
|
||||
"status.load_more": "Lataa lisää",
|
||||
"status.media.open": "Napsauta avataksesi",
|
||||
"status.media.open": "Avaa napsauttamalla",
|
||||
"status.media.show": "Napsauta näyttääksesi",
|
||||
"status.media_hidden": "Media piilotettu",
|
||||
"status.mention": "Mainitse @{name}",
|
||||
|
@ -654,7 +654,7 @@
|
|||
"status.reblogs.empty": "Kukaan ei ole vielä tehostanut tätä julkaisua. Kun joku tekee niin, tulee hän tähän näkyviin.",
|
||||
"status.redraft": "Poista ja palauta muokattavaksi",
|
||||
"status.remove_bookmark": "Poista kirjanmerkki",
|
||||
"status.replied_to": "Vastattu {name}",
|
||||
"status.replied_to": "Vastaus käyttäjälle {name}",
|
||||
"status.reply": "Vastaa",
|
||||
"status.replyAll": "Vastaa ketjuun",
|
||||
"status.report": "Raportoi @{name}",
|
||||
|
@ -686,7 +686,7 @@
|
|||
"timeline_hint.resources.followers": "Seuraajat",
|
||||
"timeline_hint.resources.follows": "seurattua",
|
||||
"timeline_hint.resources.statuses": "Vanhemmat julkaisut",
|
||||
"trends.counter_by_accounts": "{count, plural, one {{counter} henkilö} other {{counter} henkilöä}} {days, plural, one {viimeisen päivän} other {viimeisten {days} päivän}} aikana",
|
||||
"trends.counter_by_accounts": "{count, plural, one {{counter} henkilö} other {{counter} henkilöä}} {days, plural, one {viime päivänä} other {viimeisenä {days} päivänä}}",
|
||||
"trends.trending_now": "Suosittua nyt",
|
||||
"ui.beforeunload": "Luonnos häviää, jos poistut Mastodonista.",
|
||||
"units.short.billion": "{count} mrd.",
|
||||
|
@ -694,7 +694,7 @@
|
|||
"units.short.thousand": "{count} t.",
|
||||
"upload_area.title": "Lataa raahaamalla ja pudottamalla tähän",
|
||||
"upload_button.label": "Lisää kuvia, video tai äänitiedosto",
|
||||
"upload_error.limit": "Tiedostolatauksien raja ylitetty.",
|
||||
"upload_error.limit": "Tiedostolatauksien rajoitus ylitetty.",
|
||||
"upload_error.poll": "Tiedoston lataaminen ei ole sallittua äänestyksissä.",
|
||||
"upload_form.audio_description": "Kuvaile sisältöä kuuroille ja kuulorajoitteisille",
|
||||
"upload_form.description": "Kuvaile sisältöä sokeille ja näkörajoitteisille",
|
||||
|
@ -715,7 +715,7 @@
|
|||
"upload_modal.preview_label": "Esikatselu ({ratio})",
|
||||
"upload_progress.label": "Ladataan...",
|
||||
"upload_progress.processing": "Käsitellään…",
|
||||
"username.taken": "Käyttäjätunnus on jo varattu. Kokeile toista",
|
||||
"username.taken": "Käyttäjänimi on jo varattu. Kokeile toista",
|
||||
"video.close": "Sulje video",
|
||||
"video.download": "Lataa tiedosto",
|
||||
"video.exit_fullscreen": "Poistu koko näytön tilasta",
|
||||
|
|
|
@ -7,7 +7,7 @@
|
|||
"about.domain_blocks.silenced.explanation": "San fharsaingeachd, chan fhaic thu pròifilean agus susbaint an fhrithealaiche seo ach ma nì thu lorg no ma tha thu ga leantainn.",
|
||||
"about.domain_blocks.silenced.title": "Cuingichte",
|
||||
"about.domain_blocks.suspended.explanation": "Cha dèid dàta sam bith on fhrithealaiche seo a phròiseasadh, a stòradh no iomlaid agus chan urrainn do na cleachdaichean on fhrithealaiche sin conaltradh no eadar-ghnìomh a ghabhail an-seo.",
|
||||
"about.domain_blocks.suspended.title": "’Na dhàil",
|
||||
"about.domain_blocks.suspended.title": "À rèim",
|
||||
"about.not_available": "Cha deach am fiosrachadh seo a sholar air an fhrithealaiche seo.",
|
||||
"about.powered_by": "Lìonra sòisealta sgaoilte le cumhachd {mastodon}",
|
||||
"about.rules": "Riaghailtean an fhrithealaiche",
|
||||
|
|
|
@ -55,7 +55,7 @@
|
|||
"account.locked_info": "このアカウントは承認制アカウントです。相手が承認するまでフォローは完了しません。",
|
||||
"account.media": "メディア",
|
||||
"account.mention": "@{name}さんにメンション",
|
||||
"account.moved_to": "{name}さんがアカウントを引っ越しました:",
|
||||
"account.moved_to": "{name}さんはこちらのアカウントに引っ越しました:",
|
||||
"account.mute": "@{name}さんをミュート",
|
||||
"account.mute_notifications_short": "通知をオフにする",
|
||||
"account.mute_short": "ミュート",
|
||||
|
@ -340,7 +340,7 @@
|
|||
"errors.unexpected_crash.report_issue": "問題を報告",
|
||||
"explore.search_results": "検索結果",
|
||||
"explore.suggested_follows": "ユーザー",
|
||||
"explore.title": "エクスプローラー",
|
||||
"explore.title": "探索する",
|
||||
"explore.trending_links": "ニュース",
|
||||
"explore.trending_statuses": "投稿",
|
||||
"explore.trending_tags": "ハッシュタグ",
|
||||
|
|
|
@ -65,6 +65,7 @@
|
|||
"account.unendorse": "Mostrar pas pel perfil",
|
||||
"account.unfollow": "Quitar de sègre",
|
||||
"account.unmute": "Quitar de rescondre @{name}",
|
||||
"account.unmute_notifications_short": "Restablir las notificacions",
|
||||
"account.unmute_short": "Tornar afichar",
|
||||
"account_note.placeholder": "Clicar per ajustar una nòta",
|
||||
"admin.dashboard.retention.average": "Mejana",
|
||||
|
@ -97,6 +98,8 @@
|
|||
"column.direct": "Mencions privadas",
|
||||
"column.directory": "Percórrer los perfils",
|
||||
"column.domain_blocks": "Domenis resconduts",
|
||||
"column.favourites": "Favorits",
|
||||
"column.firehose": "Tuts en dirèct",
|
||||
"column.follow_requests": "Demandas d’abonament",
|
||||
"column.home": "Acuèlh",
|
||||
"column.lists": "Listas",
|
||||
|
@ -117,6 +120,7 @@
|
|||
"community.column_settings.remote_only": "Sonque alonhat",
|
||||
"compose.language.change": "Cambiar de lenga",
|
||||
"compose.language.search": "Recercar de lengas...",
|
||||
"compose.published.body": "Tut publicat.",
|
||||
"compose.published.open": "Dobrir",
|
||||
"compose.saved.body": "Publicacion enregistrada.",
|
||||
"compose_form.direct_message_warning_learn_more": "Ne saber mai",
|
||||
|
@ -170,6 +174,7 @@
|
|||
"conversation.open": "Veire la conversacion",
|
||||
"conversation.with": "Amb {names}",
|
||||
"copypaste.copied": "Copiat",
|
||||
"copypaste.copy_to_clipboard": "Copiar al quichapapièr",
|
||||
"directory.federated": "Del fediverse conegut",
|
||||
"directory.local": "Solament de {domain}",
|
||||
"directory.new_arrivals": "Nòus-venguts",
|
||||
|
@ -220,6 +225,7 @@
|
|||
"errors.unexpected_crash.copy_stacktrace": "Copiar las traças al quichapapièrs",
|
||||
"errors.unexpected_crash.report_issue": "Senhalar un problèma",
|
||||
"explore.search_results": "Resultats de recèrca",
|
||||
"explore.suggested_follows": "Personas",
|
||||
"explore.title": "Explorar",
|
||||
"explore.trending_links": "Novèlas",
|
||||
"explore.trending_statuses": "Publicacions",
|
||||
|
@ -234,6 +240,7 @@
|
|||
"filter_modal.select_filter.search": "Cercar o crear",
|
||||
"filter_modal.select_filter.title": "Filtrar aquesta publicacion",
|
||||
"filter_modal.title.status": "Filtrar una publicacion",
|
||||
"firehose.local": "Aqueste servidor",
|
||||
"follow_request.authorize": "Acceptar",
|
||||
"follow_request.reject": "Regetar",
|
||||
"follow_requests.unlocked_explanation": "Encara que vòstre compte siasque pas verrolhat, la còla de {domain} pensèt que volriatz benlèu repassar las demandas d’abonament d’aquestes comptes.",
|
||||
|
@ -257,12 +264,19 @@
|
|||
"hashtag.column_settings.tag_mode.any": "Un d’aquestes",
|
||||
"hashtag.column_settings.tag_mode.none": "Cap d’aquestes",
|
||||
"hashtag.column_settings.tag_toggle": "Inclure las etiquetas suplementàrias dins aquesta colomna",
|
||||
"hashtag.counter_by_accounts": "{count, plural, one {{counter} participant} other {{counter} participants}}",
|
||||
"hashtag.counter_by_uses": "{count, plural, one {{counter} tut} other {{counter} tuts}}",
|
||||
"hashtag.counter_by_uses_today": "{count, plural, one {{counter} tut} other {{counter} tuts}} uèi",
|
||||
"hashtag.follow": "Sègre l’etiqueta",
|
||||
"hashtag.unfollow": "Quitar de sègre l’etiqueta",
|
||||
"hashtags.and_other": "…e {count, plural, one {}other {# de mai}}",
|
||||
"home.actions.go_to_explore": "Agachatz las tendéncias",
|
||||
"home.actions.go_to_suggestions": "Trobatz de monde de sègre",
|
||||
"home.column_settings.basic": "Basic",
|
||||
"home.column_settings.show_reblogs": "Mostrar los partatges",
|
||||
"home.column_settings.show_replies": "Mostrar las responsas",
|
||||
"home.hide_announcements": "Rescondre las anóncias",
|
||||
"home.pending_critical_update.link": "Veire las mesas a jorn",
|
||||
"home.show_announcements": "Mostrar las anóncias",
|
||||
"interaction_modal.on_another_server": "Sus un autre servidor",
|
||||
"interaction_modal.on_this_server": "Sus aqueste servidor",
|
||||
|
@ -332,14 +346,17 @@
|
|||
"mute_modal.hide_notifications": "Rescondre las notificacions d’aquesta persona ?",
|
||||
"mute_modal.indefinite": "Cap de data de fin",
|
||||
"navigation_bar.about": "A prepaus",
|
||||
"navigation_bar.advanced_interface": "Dobrir l’interfàcia web avançada",
|
||||
"navigation_bar.blocks": "Personas blocadas",
|
||||
"navigation_bar.bookmarks": "Marcadors",
|
||||
"navigation_bar.community_timeline": "Flux public local",
|
||||
"navigation_bar.compose": "Escriure un nòu tut",
|
||||
"navigation_bar.direct": "Mencions privadas",
|
||||
"navigation_bar.discover": "Trobar",
|
||||
"navigation_bar.domain_blocks": "Domenis resconduts",
|
||||
"navigation_bar.edit_profile": "Modificar lo perfil",
|
||||
"navigation_bar.explore": "Explorar",
|
||||
"navigation_bar.favourites": "Favorits",
|
||||
"navigation_bar.filters": "Mots ignorats",
|
||||
"navigation_bar.follow_requests": "Demandas d’abonament",
|
||||
"navigation_bar.followed_tags": "Etiquetas seguidas",
|
||||
|
@ -369,6 +386,7 @@
|
|||
"notifications.column_settings.admin.report": "Senhalaments novèls :",
|
||||
"notifications.column_settings.admin.sign_up": "Nòus inscrits :",
|
||||
"notifications.column_settings.alert": "Notificacions localas",
|
||||
"notifications.column_settings.favourite": "Favorits :",
|
||||
"notifications.column_settings.filter_bar.advanced": "Mostrar totas las categorias",
|
||||
"notifications.column_settings.filter_bar.category": "Barra de recèrca rapida",
|
||||
"notifications.column_settings.filter_bar.show_bar": "Afichar la barra de filtres",
|
||||
|
@ -386,6 +404,7 @@
|
|||
"notifications.column_settings.update": "Modificacions :",
|
||||
"notifications.filter.all": "Totas",
|
||||
"notifications.filter.boosts": "Partages",
|
||||
"notifications.filter.favourites": "Favorits",
|
||||
"notifications.filter.follows": "Seguiments",
|
||||
"notifications.filter.mentions": "Mencions",
|
||||
"notifications.filter.polls": "Resultats del sondatge",
|
||||
|
@ -399,15 +418,21 @@
|
|||
"notifications_permission_banner.enable": "Activar las notificacions burèu",
|
||||
"notifications_permission_banner.how_to_control": "Per recebre las notificacions de Mastodon quand es pas dobèrt, activatz las notificacions de burèu. Podètz precisar quin tipe de notificacion generarà una notificacion de burèu via lo boton {icon} dessús un còp activadas.",
|
||||
"notifications_permission_banner.title": "Manquetz pas jamai res",
|
||||
"onboarding.action.back": "Tornar en rèire",
|
||||
"onboarding.actions.back": "Tornar en rèire",
|
||||
"onboarding.actions.go_to_explore": "See what's trending",
|
||||
"onboarding.actions.go_to_home": "Go to your home feed",
|
||||
"onboarding.compose.template": "Adiu #Mastodon !",
|
||||
"onboarding.follows.lead": "You curate your own home feed. The more people you follow, the more active and interesting it will be. These profiles may be a good starting point—you can always unfollow them later!",
|
||||
"onboarding.follows.title": "Popular on Mastodon",
|
||||
"onboarding.share.title": "Partejar vòstre perfil",
|
||||
"onboarding.start.lead": "Your new Mastodon account is ready to go. Here's how you can make the most of it:",
|
||||
"onboarding.start.skip": "Want to skip right ahead?",
|
||||
"onboarding.start.title": "Tot es prèst !",
|
||||
"onboarding.steps.follow_people.body": "You curate your own feed. Lets fill it with interesting people.",
|
||||
"onboarding.steps.follow_people.title": "Follow {count, plural, one {one person} other {# people}}",
|
||||
"onboarding.steps.publish_status.body": "Say hello to the world.",
|
||||
"onboarding.steps.publish_status.title": "Escrivètz vòstre primièr tut",
|
||||
"onboarding.steps.setup_profile.body": "Others are more likely to interact with you with a filled out profile.",
|
||||
"onboarding.steps.setup_profile.title": "Customize your profile",
|
||||
"onboarding.steps.share_profile.body": "Let your friends know how to find you on Mastodon!",
|
||||
|
@ -415,6 +440,7 @@
|
|||
"picture_in_picture.restore": "Lo tornar",
|
||||
"poll.closed": "Tampat",
|
||||
"poll.refresh": "Actualizar",
|
||||
"poll.reveal": "Veire los resultats",
|
||||
"poll.total_people": "{count, plural, one {# persona} other {# personas}}",
|
||||
"poll.total_votes": "{count, plural, one {# vòte} other {# vòtes}}",
|
||||
"poll.vote": "Votar",
|
||||
|
@ -482,11 +508,17 @@
|
|||
"report_notification.open": "Dobrir lo senhalament",
|
||||
"search.placeholder": "Recercar",
|
||||
"search.search_or_paste": "Recercar o picar una URL",
|
||||
"search_popout.language_code": "Còdi ISO de lenga",
|
||||
"search_popout.options": "Opcions de recèrca",
|
||||
"search_popout.quick_actions": "Accions rapidas",
|
||||
"search_popout.recent": "Recèrcas recentas",
|
||||
"search_popout.specific_date": "data especifica",
|
||||
"search_popout.user": "utilizaire",
|
||||
"search_results.accounts": "Perfils",
|
||||
"search_results.all": "Tot",
|
||||
"search_results.hashtags": "Etiquetas",
|
||||
"search_results.nothing_found": "Cap de resultat per aquestes tèrmes de recèrca",
|
||||
"search_results.see_all": "O veire tot",
|
||||
"search_results.statuses": "Tuts",
|
||||
"search_results.title": "Recèrca : {q}",
|
||||
"server_banner.active_users": "utilizaires actius",
|
||||
|
@ -506,16 +538,20 @@
|
|||
"status.copy": "Copiar lo ligam de l’estatut",
|
||||
"status.delete": "Escafar",
|
||||
"status.detailed_status": "Vista detalhada de la convèrsa",
|
||||
"status.direct_indicator": "Mencion privada",
|
||||
"status.edit": "Modificar",
|
||||
"status.edited": "Modificat {date}",
|
||||
"status.edited_x_times": "Modificat {count, plural, un {{count} còp} other {{count} còps}}",
|
||||
"status.embed": "Embarcar",
|
||||
"status.favourite": "Apondre als favorits",
|
||||
"status.filter": "Filtrar aquesta publicacion",
|
||||
"status.filtered": "Filtrat",
|
||||
"status.hide": "Amagar la publicacion",
|
||||
"status.history.created": "{name} o creèt lo {date}",
|
||||
"status.history.edited": "{name} o modifiquèt lo {date}",
|
||||
"status.load_more": "Cargar mai",
|
||||
"status.media.open": "Clicar per dobrir",
|
||||
"status.media.show": "Clicar per mostar",
|
||||
"status.media_hidden": "Mèdia rescondut",
|
||||
"status.mention": "Mencionar",
|
||||
"status.more": "Mai",
|
||||
|
@ -546,6 +582,7 @@
|
|||
"status.title.with_attachments": "{user} posted {attachmentCount, plural, one {an attachment} other {# attachments}}",
|
||||
"status.translate": "Traduire",
|
||||
"status.translated_from_with": "Traduch del {lang} amb {provider}",
|
||||
"status.uncached_media_warning": "Apercebut indisponible",
|
||||
"status.unmute_conversation": "Tornar mostrar la conversacion",
|
||||
"status.unpin": "Tirar del perfil",
|
||||
"subscribed_languages.lead": "Sonque las publicacions dins las lengas seleccionadas apreissaràn dins vòstre acuèlh e linha cronologica aprèp aqueste cambiament. Seleccionatz pas res per recebre las publicacions en quina lenga que siá.",
|
||||
|
|
|
@ -218,7 +218,10 @@
|
|||
"home.hide_announcements": "නිවේදන සඟවන්න",
|
||||
"home.pending_critical_update.link": "යාවත්කාල බලන්න",
|
||||
"home.show_announcements": "නිවේදන පෙන්වන්න",
|
||||
"interaction_modal.login.action": "මුලට ගෙනයන්න",
|
||||
"interaction_modal.on_this_server": "මෙම සේවාදායකයෙහි",
|
||||
"interaction_modal.title.favourite": "{name}ගේ ලිපිය ප්රිය කරන්න",
|
||||
"interaction_modal.title.follow": "{name} අනුගමනය",
|
||||
"intervals.full.days": "{number, plural, one {දවස් #} other {දවස් #}}",
|
||||
"intervals.full.hours": "{number, plural, one {පැය #} other {පැය #}}",
|
||||
"intervals.full.minutes": "{number, plural, one {විනාඩි #} other {විනාඩි #}}",
|
||||
|
@ -319,6 +322,7 @@
|
|||
"notifications.mark_as_read": "සියළු දැනුම්දීම් කියවූ බව යොදන්න",
|
||||
"notifications_permission_banner.enable": "වැඩතල දැනුම්දීම් සබල කරන්න",
|
||||
"notifications_permission_banner.title": "කිසිවක් අතපසු නොකරන්න",
|
||||
"onboarding.actions.go_to_explore": "නැගී එන දෑ වෙත ගෙනයන්න",
|
||||
"onboarding.compose.template": "ආයුබෝ #මාස්ටඩන්!",
|
||||
"onboarding.share.title": "ඔබගේ පැතිකඩ බෙදාගන්න",
|
||||
"onboarding.steps.publish_status.title": "පළමු ලිපිය පළ කරන්න",
|
||||
|
@ -358,6 +362,7 @@
|
|||
"report.categories.other": "වෙනත්",
|
||||
"report.categories.spam": "ආයාචිත",
|
||||
"report.categories.violation": "අන්තර්ගතය නිසා සේවාදායකයේ නීතියක් හෝ කිහිපයක් කඩ වේ",
|
||||
"report.category.subtitle": "හොඳම ගැලපීම තෝරන්න",
|
||||
"report.category.title": "මෙම {type}සමඟ සිදුවන්නේ කුමක්දැයි අපට කියන්න",
|
||||
"report.category.title_account": "පැතිකඩ",
|
||||
"report.category.title_status": "ලිපිය",
|
||||
|
@ -394,14 +399,25 @@
|
|||
"report_notification.categories.spam": "ආයාචිත",
|
||||
"report_notification.categories.violation": "නීතිය කඩ කිරීම",
|
||||
"report_notification.open": "විවෘත වාර්තාව",
|
||||
"search.no_recent_searches": "මෑත සෙවීම් නැත",
|
||||
"search.placeholder": "සොයන්න",
|
||||
"search.quick_action.account_search": "ගැළපෙන පැතිකඩ {x}",
|
||||
"search.quick_action.go_to_account": "{x} පැතිකඩ වෙත යන්න",
|
||||
"search.quick_action.open_url": "ලිපිනය මාස්ටඩන්හි අරින්න",
|
||||
"search.quick_action.status_search": "ගැළපෙන ලිපි {x}",
|
||||
"search.search_or_paste": "සොයන්න හෝ ඒ.ස.නි. අලවන්න",
|
||||
"search_popout.options": "සෙවුම් විකල්ප",
|
||||
"search_popout.quick_actions": "ඉක්මන් ක්රියාමාර්ග",
|
||||
"search_popout.recent": "මෑත සෙවීම්",
|
||||
"search_popout.specific_date": "නිශ්චිත දිනයකට",
|
||||
"search_popout.user": "පරිශ්රීලකයා",
|
||||
"search_results.accounts": "පැතිකඩ",
|
||||
"search_results.all": "සියල්ල",
|
||||
"search_results.nothing_found": "මෙම සෙවුම් පද සඳහා කිසිවක් සොයාගත නොහැකි විය",
|
||||
"search_results.see_all": "සියල්ල බලන්න",
|
||||
"search_results.statuses": "ලිපි",
|
||||
"search_results.title": "{q} සොයන්න",
|
||||
"server_banner.active_users": "සක්රිය පරිශ්රීලකයින්",
|
||||
"server_banner.learn_more": "තව දැනගන්න",
|
||||
"sign_in_banner.create_account": "ගිණුමක් සාදන්න",
|
||||
"sign_in_banner.sign_in": "පිවිසෙන්න",
|
||||
|
|
|
@ -61,7 +61,7 @@
|
|||
"account.requested_follow": "{name} ti poslal žiadosť na sledovanie",
|
||||
"account.share": "Zdieľaj @{name} profil",
|
||||
"account.show_reblogs": "Ukáž vyzdvihnutia od @{name}",
|
||||
"account.statuses_counter": "{count, plural, one {{counter} Toot} other {{counter} Toots}}",
|
||||
"account.statuses_counter": "{count, plural, one {{counter} príspevok} other {{counter} príspevkov}}",
|
||||
"account.unblock": "Odblokuj @{name}",
|
||||
"account.unblock_domain": "Prestaň skrývať {domain}",
|
||||
"account.unblock_short": "Odblokuj",
|
||||
|
|
|
@ -204,7 +204,7 @@
|
|||
"dismissable_banner.explore_links": "這些新聞故事正在被此伺服器以及去中心化網路上的人們熱烈討論著。越多不同人所嘟出的新聞排名更高。",
|
||||
"dismissable_banner.explore_statuses": "這些於此伺服器以及去中心化網路中其他伺服器發出的嘟文正在被此伺服器上的人們熱烈討論著。越多不同人轉嘟及最愛排名更高。",
|
||||
"dismissable_banner.explore_tags": "這些主題標籤正在被此伺服器以及去中心化網路上的人們熱烈討論著。越多不同人所嘟出的主題標籤排名更高。",
|
||||
"dismissable_banner.public_timeline": "這些是來自 {domain} 上人們於社群網站中跟隨者所發表之最近公開嘟文。",
|
||||
"dismissable_banner.public_timeline": "這些是來自 {domain} 使用者們跟隨中帳號所發表之最新公開嘟文。",
|
||||
"embed.instructions": "要在您的網站嵌入此嘟文,請複製以下程式碼。",
|
||||
"embed.preview": "它將顯示成這樣:",
|
||||
"emoji_button.activity": "活動",
|
||||
|
@ -271,8 +271,8 @@
|
|||
"filter_modal.select_filter.title": "過濾此嘟文",
|
||||
"filter_modal.title.status": "過濾一則嘟文",
|
||||
"firehose.all": "全部",
|
||||
"firehose.local": "此伺服器",
|
||||
"firehose.remote": "其他伺服器",
|
||||
"firehose.local": "本站",
|
||||
"firehose.remote": "聯邦宇宙",
|
||||
"follow_request.authorize": "授權",
|
||||
"follow_request.reject": "拒絕",
|
||||
"follow_requests.unlocked_explanation": "即便您的帳號未被鎖定,{domain} 的管理員認為您可能想要自己審核這些帳號的跟隨請求。",
|
||||
|
@ -409,7 +409,7 @@
|
|||
"navigation_bar.favourites": "最愛",
|
||||
"navigation_bar.filters": "已靜音的關鍵字",
|
||||
"navigation_bar.follow_requests": "跟隨請求",
|
||||
"navigation_bar.followed_tags": "已跟隨的主題標籤",
|
||||
"navigation_bar.followed_tags": "已跟隨主題標籤",
|
||||
"navigation_bar.follows_and_followers": "跟隨中與跟隨者",
|
||||
"navigation_bar.lists": "列表",
|
||||
"navigation_bar.logout": "登出",
|
||||
|
|
|
@ -38,10 +38,10 @@ export default function antennaAdderReducer(state = initialState, action) {
|
|||
return state.setIn(['antennas', 'isLoading'], true);
|
||||
case ANTENNA_ADDER_ANTENNAS_FETCH_FAIL:
|
||||
case ANTENNA_ADDER_EXCLUDE_ANTENNAS_FETCH_FAIL:
|
||||
return state.setIn(['antennas', 'isLoading'], false);
|
||||
return state.setIn(['antennas', 'isLoading'], false);
|
||||
case ANTENNA_ADDER_ANTENNAS_FETCH_SUCCESS:
|
||||
case ANTENNA_ADDER_EXCLUDE_ANTENNAS_FETCH_SUCCESS:
|
||||
return state.update('antennas', antennas => antennas.withMutations(map => {
|
||||
return state.update('antennas', antennas => antennas.withMutations(map => {
|
||||
map.set('isLoading', false);
|
||||
map.set('loaded', true);
|
||||
map.set('items', ImmutableList(action.antennas.map(item => item.id)));
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
import { List as ImmutableList, Map as ImmutableMap, OrderedSet as ImmutableOrderedSet, fromJS } from 'immutable';
|
||||
import { Map as ImmutableMap, OrderedSet as ImmutableOrderedSet, fromJS } from 'immutable';
|
||||
|
||||
import {
|
||||
CIRCLE_FETCH_SUCCESS,
|
||||
|
@ -18,14 +18,7 @@ import {
|
|||
COMPOSE_WITH_CIRCLE_SUCCESS,
|
||||
} from '../actions/compose';
|
||||
|
||||
const initialState = ImmutableList();
|
||||
|
||||
const initialStatusesState = ImmutableMap({
|
||||
items: ImmutableList(),
|
||||
isLoading: false,
|
||||
loaded: true,
|
||||
next: null,
|
||||
});
|
||||
const initialState = ImmutableMap();
|
||||
|
||||
const normalizeCircle = (state, circle) => {
|
||||
const old = state.get(circle.id);
|
||||
|
@ -33,13 +26,11 @@ const normalizeCircle = (state, circle) => {
|
|||
return state;
|
||||
}
|
||||
|
||||
let s = state.set(circle.id, fromJS(circle));
|
||||
state = state.set(circle.id, fromJS(circle));
|
||||
if (old) {
|
||||
s = s.setIn([circle.id, 'statuses'], old.get('statuses'));
|
||||
} else {
|
||||
s = s.setIn([circle.id, 'statuses'], initialStatusesState);
|
||||
state = state.setIn([circle.id, 'items'], old.get('items'));
|
||||
}
|
||||
return s.setIn([circle.id, 'isLoading'], false).setIn([circle.id, 'isLoaded'], true);
|
||||
return state.setIn([circle.id, 'isLoading'], false).setIn([circle.id, 'isLoaded'], true);
|
||||
};
|
||||
|
||||
const normalizeCircles = (state, circles) => {
|
||||
|
@ -51,7 +42,7 @@ const normalizeCircles = (state, circles) => {
|
|||
};
|
||||
|
||||
const normalizeCircleStatuses = (state, circleId, statuses, next) => {
|
||||
return state.updateIn([circleId, 'statuses'], listMap => listMap.withMutations(map => {
|
||||
return state.update(circleId, listMap => listMap.withMutations(map => {
|
||||
map.set('next', next);
|
||||
map.set('loaded', true);
|
||||
map.set('isLoading', false);
|
||||
|
@ -64,7 +55,7 @@ const appendToCircleStatuses = (state, circleId, statuses, next) => {
|
|||
};
|
||||
|
||||
const appendToCircleStatusesById = (state, circleId, statuses, next) => {
|
||||
return state.updateIn([circleId, 'statuses'], listMap => listMap.withMutations(map => {
|
||||
return state.update(circleId, listMap => listMap.withMutations(map => {
|
||||
if (typeof next !== 'undefined') {
|
||||
map.set('next', next);
|
||||
}
|
||||
|
@ -79,11 +70,11 @@ const prependToCircleStatusById = (state, circleId, statusId) => {
|
|||
if (!state.get(circleId)) return state;
|
||||
|
||||
return state.updateIn([circleId], circle => circle.withMutations(map => {
|
||||
if (map.getIn(['statuses', 'items'])) {
|
||||
map.updateIn(['statuses', 'items'], list => ImmutableOrderedSet([statusId]).union(list));
|
||||
if (map.get('items')) {
|
||||
map.update('items', list => ImmutableOrderedSet([statusId]).union(list));
|
||||
}
|
||||
}));
|
||||
}
|
||||
};
|
||||
|
||||
export default function circles(state = initialState, action) {
|
||||
switch(action.type) {
|
||||
|
@ -98,10 +89,10 @@ export default function circles(state = initialState, action) {
|
|||
return state.set(action.id, false);
|
||||
case CIRCLE_STATUSES_FETCH_REQUEST:
|
||||
case CIRCLE_STATUSES_EXPAND_REQUEST:
|
||||
return state.setIn([action.id, 'statuses', 'isLoading'], true);
|
||||
return state.setIn([action.id, 'isLoading'], true);
|
||||
case CIRCLE_STATUSES_FETCH_FAIL:
|
||||
case CIRCLE_STATUSES_EXPAND_FAIL:
|
||||
return state.setIn([action.id, 'statuses', 'isLoading'], false);
|
||||
return state.setIn([action.id, 'isLoading'], false);
|
||||
case CIRCLE_STATUSES_FETCH_SUCCESS:
|
||||
return normalizeCircleStatuses(state, action.id, action.statuses, action.next);
|
||||
case CIRCLE_STATUSES_EXPAND_SUCCESS:
|
||||
|
|
|
@ -156,5 +156,5 @@ export const getBookmarkCategoryStatusList = createSelector([
|
|||
], (items) => items ? items.toList() : ImmutableList());
|
||||
|
||||
export const getCircleStatusList = createSelector([
|
||||
(state, circleId) => state.getIn(['circles', circleId, 'statuses', 'items']),
|
||||
(state, circleId) => state.getIn(['circles', circleId, 'items']),
|
||||
], (items) => items ? items.toList() : ImmutableList());
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
import './public-path';
|
||||
import main from "mastodon/main"
|
||||
import main from "mastodon/main";
|
||||
|
||||
import { start } from '../mastodon/common';
|
||||
import { loadLocale } from '../mastodon/locales';
|
||||
|
|
|
@ -84,7 +84,7 @@ class ActivityPub::Activity::Create < ActivityPub::Activity
|
|||
|
||||
return nil unless valid_status?
|
||||
return nil if (reply_to_local? || reply_to_local_account? || reply_to_local_from_tags?) && reject_reply_to_local?
|
||||
return nil if (!reply_to_local_account_following? || !reply_to_local_status_following? || !reply_to_local_from_tags_following?) && reject_reply_exclude_followers?
|
||||
return nil if mention_to_local_but_not_followed? && reject_reply_exclude_followers?
|
||||
|
||||
ApplicationRecord.transaction do
|
||||
@status = Status.create!(@params)
|
||||
|
@ -139,7 +139,11 @@ class ActivityPub::Activity::Create < ActivityPub::Activity
|
|||
end
|
||||
|
||||
def valid_status?
|
||||
!Admin::NgWord.reject?("#{@params[:spoiler_text]}\n#{@params[:text]}") && !Admin::NgWord.hashtag_reject?(@tags.size)
|
||||
valid = !Admin::NgWord.reject?("#{@params[:spoiler_text]}\n#{@params[:text]}") && !Admin::NgWord.hashtag_reject?(@tags.size)
|
||||
|
||||
valid = !Admin::NgWord.stranger_mention_reject?("#{@params[:spoiler_text]}\n#{@params[:text]}") if valid && mention_to_local_but_not_followed?
|
||||
|
||||
valid
|
||||
end
|
||||
|
||||
def accounts_in_audience
|
||||
|
@ -270,6 +274,7 @@ class ActivityPub::Activity::Create < ActivityPub::Activity
|
|||
emoji.image_remote_url = custom_emoji_parser.image_remote_url
|
||||
emoji.license = custom_emoji_parser.license
|
||||
emoji.is_sensitive = custom_emoji_parser.is_sensitive
|
||||
emoji.aliases = custom_emoji_parser.aliases
|
||||
emoji.save
|
||||
rescue Seahorse::Client::NetworkingError => e
|
||||
Rails.logger.warn "Error storing emoji: #{e}"
|
||||
|
@ -418,11 +423,11 @@ class ActivityPub::Activity::Create < ActivityPub::Activity
|
|||
end
|
||||
|
||||
def reply_to_local_from_tags?
|
||||
(@mentions.present? && @mentions.any? { |m| m.account.local? })
|
||||
@mentions.present? && @mentions.any? { |m| m.account.local? }
|
||||
end
|
||||
|
||||
def reply_to_local_from_tags_following?
|
||||
(@mentions.present? && @mentions.none? { |m| m.account.local? && !m.account.following?(@account) })
|
||||
@mentions.nil? || @mentions.none? { |m| m.account.local? && !m.account.following?(@account) }
|
||||
end
|
||||
|
||||
def reply_to_local?
|
||||
|
@ -433,6 +438,10 @@ class ActivityPub::Activity::Create < ActivityPub::Activity
|
|||
!reply_to_local? || replied_to_status.account.following?(@account)
|
||||
end
|
||||
|
||||
def mention_to_local_but_not_followed?
|
||||
!reply_to_local_account_following? || !reply_to_local_status_following? || !reply_to_local_from_tags_following?
|
||||
end
|
||||
|
||||
def reject_reply_to_local?
|
||||
@reject_reply_to_local ||= DomainBlock.reject_reply?(@account.domain)
|
||||
end
|
||||
|
|
|
@ -3,6 +3,7 @@
|
|||
class ActivityPub::Activity::Like < ActivityPub::Activity
|
||||
include Redisable
|
||||
include Lockable
|
||||
include JsonLdHelper
|
||||
|
||||
def perform
|
||||
@original_status = status_from_uri(object_uri)
|
||||
|
@ -102,7 +103,7 @@ class ActivityPub::Activity::Like < ActivityPub::Activity
|
|||
|
||||
return if custom_emoji_parser.shortcode.blank? || custom_emoji_parser.image_remote_url.blank?
|
||||
|
||||
domain = tag['domain'] || URI.split(custom_emoji_parser.uri)[2] || @account.domain
|
||||
domain = URI.split(custom_emoji_parser.uri)[2] || @account.domain
|
||||
|
||||
if domain == Rails.configuration.x.local_domain || domain == Rails.configuration.x.web_domain
|
||||
# Block overwriting remote-but-local data
|
||||
|
@ -118,6 +119,9 @@ class ActivityPub::Activity::Like < ActivityPub::Activity
|
|||
(custom_emoji_parser.updated_at && custom_emoji_parser.updated_at >= emoji.updated_at) ||
|
||||
custom_emoji_parser.license != emoji.license
|
||||
|
||||
custom_emoji_parser = original_emoji_parser(custom_emoji_parser) if @account.domain != domain
|
||||
return if custom_emoji_parser.nil?
|
||||
|
||||
begin
|
||||
emoji ||= CustomEmoji.new(
|
||||
domain: domain,
|
||||
|
@ -127,6 +131,7 @@ class ActivityPub::Activity::Like < ActivityPub::Activity
|
|||
emoji.image_remote_url = custom_emoji_parser.image_remote_url
|
||||
emoji.license = custom_emoji_parser.license
|
||||
emoji.is_sensitive = custom_emoji_parser.is_sensitive
|
||||
emoji.aliases = custom_emoji_parser.aliases
|
||||
emoji.save
|
||||
rescue Seahorse::Client::NetworkingError => e
|
||||
Rails.logger.warn "Error storing emoji: #{e}"
|
||||
|
@ -135,6 +140,17 @@ class ActivityPub::Activity::Like < ActivityPub::Activity
|
|||
emoji
|
||||
end
|
||||
|
||||
def original_emoji_parser(custom_emoji_parser)
|
||||
uri = custom_emoji_parser.uri
|
||||
emoji = fetch_resource_without_id_validation(uri)
|
||||
return nil unless emoji
|
||||
|
||||
parser = ActivityPub::Parser::CustomEmojiParser.new(emoji)
|
||||
return nil unless parser.uri == uri && custom_emoji_parser.shortcode == parser.shortcode
|
||||
|
||||
parser
|
||||
end
|
||||
|
||||
def skip_download?(domain)
|
||||
DomainBlock.reject_media?(domain)
|
||||
end
|
||||
|
|
|
@ -135,7 +135,12 @@ class ActivityPub::Activity::Undo < ActivityPub::Activity
|
|||
if shortcode.present?
|
||||
emoji_tag = @object['tag'].is_a?(Array) ? @object['tag']&.first : @object['tag']
|
||||
|
||||
emoji = CustomEmoji.find_by(shortcode: shortcode, domain: @account.domain) if emoji_tag.present? && emoji_tag['id'].present?
|
||||
emoji = nil
|
||||
if emoji_tag.present? && emoji_tag['id'].present?
|
||||
domain = URI.split(emoji_tag['id'])[2]
|
||||
domain = nil if domain == Rails.configuration.x.local_domain || domain == Rails.configuration.x.web_domain
|
||||
emoji = CustomEmoji.find_by(shortcode: shortcode, domain: domain) if emoji_tag.present? && emoji_tag['id'].present?
|
||||
end
|
||||
|
||||
emoji_reaction = @original_status.emoji_reactions.where(account: @account, name: shortcode, custom_emoji: emoji).first
|
||||
|
||||
|
|
|
@ -15,6 +15,10 @@ class ActivityPub::Parser::CustomEmojiParser
|
|||
@json['name']&.delete(':')
|
||||
end
|
||||
|
||||
def aliases
|
||||
as_array(@json['keywords'])
|
||||
end
|
||||
|
||||
def image_remote_url
|
||||
@json.dig('icon', 'url')
|
||||
end
|
||||
|
|
|
@ -36,7 +36,8 @@ class LinkDetailsExtractor
|
|||
end
|
||||
|
||||
def language
|
||||
json['inLanguage']
|
||||
lang = json['inLanguage']
|
||||
lang.is_a?(Hash) ? (lang['alternateName'] || lang['name']) : lang
|
||||
end
|
||||
|
||||
def type
|
||||
|
|
|
@ -79,6 +79,8 @@ class SearchQueryTransformer < Parslet::Transform
|
|||
case @flags['in']
|
||||
when 'library'
|
||||
[StatusesIndex]
|
||||
when 'public'
|
||||
[PublicStatusesIndex]
|
||||
else
|
||||
@options[:current_account].user&.setting_use_public_index ? [PublicStatusesIndex, StatusesIndex] : [StatusesIndex]
|
||||
end
|
||||
|
|
|
@ -25,6 +25,10 @@ class StatusReachFinder
|
|||
(reached_account_inboxes_for_friend + followers_inboxes_for_friend + friend_inboxes).uniq
|
||||
end
|
||||
|
||||
def all_inboxes
|
||||
(inboxes + inboxes_for_misskey + inboxes_for_friend).uniq
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def reached_account_inboxes
|
||||
|
|
|
@ -399,6 +399,7 @@ class Account < ApplicationRecord
|
|||
|
||||
def allow_emoji_reaction?(account)
|
||||
return false if account.nil?
|
||||
return true unless local? || account.local?
|
||||
|
||||
show_emoji_reaction?(account)
|
||||
end
|
||||
|
|
|
@ -18,6 +18,10 @@ class Admin::NgWord
|
|||
hashtag_reject?(Extractor.extract_hashtags(text)&.size || 0)
|
||||
end
|
||||
|
||||
def stranger_mention_reject?(text)
|
||||
ng_words_for_stranger_mention.any? { |word| include?(text, word) }
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def include?(text, word)
|
||||
|
@ -32,6 +36,10 @@ class Admin::NgWord
|
|||
Setting.ng_words || []
|
||||
end
|
||||
|
||||
def ng_words_for_stranger_mention
|
||||
Setting.ng_words_for_stranger_mention || []
|
||||
end
|
||||
|
||||
def post_hash_tags_max
|
||||
value = Setting.post_hash_tags_max
|
||||
value.is_a?(Integer) && value.positive? ? value : 0
|
||||
|
|
|
@ -247,8 +247,16 @@ module AccountInteractions
|
|||
status.proper.favourites.where(account: self).exists?
|
||||
end
|
||||
|
||||
def emoji_reactioned?(status)
|
||||
status.proper.emoji_reactions.where(account: self).exists?
|
||||
def emoji_reacted?(status, shortcode = nil, domain = nil, domain_force = false) # rubocop:disable Style/OptionalBooleanParameter
|
||||
if shortcode.present?
|
||||
if domain.present? || domain_force
|
||||
status.proper.emoji_reactions.joins(:custom_emoji).where(account: self, name: shortcode, custom_emoji: { domain: domain }).exists?
|
||||
else
|
||||
status.proper.emoji_reactions.where(account: self, name: shortcode).exists?
|
||||
end
|
||||
else
|
||||
status.proper.emoji_reactions.where(account: self).exists?
|
||||
end
|
||||
end
|
||||
|
||||
def bookmarked?(status)
|
||||
|
|
|
@ -70,7 +70,13 @@ class CustomEmoji < ApplicationRecord
|
|||
end
|
||||
|
||||
def copy!
|
||||
copy = self.class.find_or_initialize_by(domain: nil, shortcode: shortcode)
|
||||
copy = self.class.find_or_initialize_by(
|
||||
domain: nil,
|
||||
shortcode: shortcode,
|
||||
license: license,
|
||||
aliases: aliases,
|
||||
is_sensitive: is_sensitive
|
||||
)
|
||||
copy.image = image
|
||||
copy.tap(&:save!)
|
||||
end
|
||||
|
|
|
@ -38,6 +38,8 @@ class Form::AdminSettings
|
|||
status_page_url
|
||||
captcha_enabled
|
||||
ng_words
|
||||
ng_words_for_stranger_mention
|
||||
stranger_mention_from_local_ng
|
||||
hide_local_users_for_anonymous
|
||||
post_hash_tags_max
|
||||
sensitive_words
|
||||
|
@ -78,6 +80,7 @@ class Form::AdminSettings
|
|||
check_lts_version_only
|
||||
enable_public_unlisted_visibility
|
||||
unlocked_friend
|
||||
stranger_mention_from_local_ng
|
||||
).freeze
|
||||
|
||||
UPLOAD_KEYS = %i(
|
||||
|
@ -91,6 +94,7 @@ class Form::AdminSettings
|
|||
|
||||
STRING_ARRAY_KEYS = %i(
|
||||
ng_words
|
||||
ng_words_for_stranger_mention
|
||||
sensitive_words
|
||||
sensitive_words_for_full
|
||||
).freeze
|
||||
|
|
|
@ -6,7 +6,7 @@ class Form::CustomEmojiBatch
|
|||
include AccountableConcern
|
||||
|
||||
attr_accessor :custom_emoji_ids, :action, :current_account,
|
||||
:category_id, :category_name, :aliases_raw, :visible_in_picker
|
||||
:category_id, :category_name, :visible_in_picker
|
||||
|
||||
def save
|
||||
case action
|
||||
|
@ -34,7 +34,7 @@ class Form::CustomEmojiBatch
|
|||
end
|
||||
|
||||
def update!
|
||||
custom_emojis.each { |custom_emoji| authorize(custom_emoji, :update?) }
|
||||
verify_authorization(:update?)
|
||||
|
||||
category = if category_id.present?
|
||||
CustomEmojiCategory.find(category_id)
|
||||
|
@ -43,14 +43,13 @@ class Form::CustomEmojiBatch
|
|||
end
|
||||
|
||||
custom_emojis.each do |custom_emoji|
|
||||
new_aliases_raw = (aliases_raw.presence || custom_emoji.aliases_raw)
|
||||
custom_emoji.update(category_id: category&.id, aliases_raw: new_aliases_raw)
|
||||
custom_emoji.update(category_id: category&.id)
|
||||
log_action :update, custom_emoji
|
||||
end
|
||||
end
|
||||
|
||||
def list!
|
||||
custom_emojis.each { |custom_emoji| authorize(custom_emoji, :update?) }
|
||||
verify_authorization(:update?)
|
||||
|
||||
custom_emojis.each do |custom_emoji|
|
||||
custom_emoji.update(visible_in_picker: true)
|
||||
|
@ -59,7 +58,7 @@ class Form::CustomEmojiBatch
|
|||
end
|
||||
|
||||
def unlist!
|
||||
custom_emojis.each { |custom_emoji| authorize(custom_emoji, :update?) }
|
||||
verify_authorization(:update?)
|
||||
|
||||
custom_emojis.each do |custom_emoji|
|
||||
custom_emoji.update(visible_in_picker: false)
|
||||
|
@ -68,7 +67,7 @@ class Form::CustomEmojiBatch
|
|||
end
|
||||
|
||||
def enable!
|
||||
custom_emojis.each { |custom_emoji| authorize(custom_emoji, :enable?) }
|
||||
verify_authorization(:enable?)
|
||||
|
||||
custom_emojis.each do |custom_emoji|
|
||||
custom_emoji.update(disabled: false)
|
||||
|
@ -77,7 +76,7 @@ class Form::CustomEmojiBatch
|
|||
end
|
||||
|
||||
def disable!
|
||||
custom_emojis.each { |custom_emoji| authorize(custom_emoji, :disable?) }
|
||||
verify_authorization(:disable?)
|
||||
|
||||
custom_emojis.each do |custom_emoji|
|
||||
custom_emoji.update(disabled: true)
|
||||
|
@ -86,7 +85,7 @@ class Form::CustomEmojiBatch
|
|||
end
|
||||
|
||||
def copy!
|
||||
custom_emojis.each { |custom_emoji| authorize(custom_emoji, :copy?) }
|
||||
verify_authorization(:copy?)
|
||||
|
||||
custom_emojis.each do |custom_emoji|
|
||||
copied_custom_emoji = custom_emoji.copy!
|
||||
|
@ -95,11 +94,15 @@ class Form::CustomEmojiBatch
|
|||
end
|
||||
|
||||
def delete!
|
||||
custom_emojis.each { |custom_emoji| authorize(custom_emoji, :destroy?) }
|
||||
verify_authorization(:destroy?)
|
||||
|
||||
custom_emojis.each do |custom_emoji|
|
||||
custom_emoji.destroy
|
||||
log_action :destroy, custom_emoji
|
||||
end
|
||||
end
|
||||
|
||||
def verify_authorization(permission)
|
||||
custom_emojis.each { |custom_emoji| authorize(custom_emoji, permission) }
|
||||
end
|
||||
end
|
||||
|
|
|
@ -21,11 +21,15 @@ class Form::IpBlockBatch
|
|||
end
|
||||
|
||||
def delete!
|
||||
ip_blocks.each { |ip_block| authorize(ip_block, :destroy?) }
|
||||
verify_authorization(:destroy?)
|
||||
|
||||
ip_blocks.each do |ip_block|
|
||||
ip_block.destroy
|
||||
log_action :destroy, ip_block
|
||||
end
|
||||
end
|
||||
|
||||
def verify_authorization(permission)
|
||||
ip_blocks.each { |ip_block| authorize(ip_block, permission) }
|
||||
end
|
||||
end
|
||||
|
|
|
@ -78,6 +78,7 @@ class Status < ApplicationRecord
|
|||
has_many :reblogs, foreign_key: 'reblog_of_id', class_name: 'Status', inverse_of: :reblog, dependent: :destroy
|
||||
has_many :reblogged_by_accounts, through: :reblogs, class_name: 'Account', source: :account
|
||||
has_many :quotes, foreign_key: 'quote_of_id', class_name: 'Status', inverse_of: :quote
|
||||
has_many :quoted_by_accounts, through: :quotes, class_name: 'Account', source: :account
|
||||
has_many :replies, foreign_key: 'in_reply_to_id', class_name: 'Status', inverse_of: :thread
|
||||
has_many :mentions, dependent: :destroy, inverse_of: :status
|
||||
has_many :mentioned_accounts, through: :mentions, source: :account, class_name: 'Account'
|
||||
|
@ -631,12 +632,10 @@ class Status < ApplicationRecord
|
|||
|
||||
self.searchability = if %w(public public_unlisted login unlisted).include?(visibility)
|
||||
searchability
|
||||
elsif visibility == 'limited'
|
||||
:limited
|
||||
elsif visibility == 'limited' || visibility == 'direct'
|
||||
searchability == 'limited' ? :limited : :direct
|
||||
elsif visibility == 'private'
|
||||
searchability == 'public' || searchability == 'public_unlisted' ? :private : searchability
|
||||
elsif visibility == 'direct'
|
||||
searchability == 'limited' ? :limited : :direct
|
||||
else
|
||||
:direct
|
||||
end
|
||||
|
|
|
@ -3,9 +3,9 @@
|
|||
class ActivityPub::EmojiSerializer < ActivityPub::Serializer
|
||||
include RoutingHelper
|
||||
|
||||
context_extensions :emoji
|
||||
context_extensions :emoji, :license, :keywords
|
||||
|
||||
attributes :id, :type, :domain, :name, :is_sensitive, :updated
|
||||
attributes :id, :type, :name, :keywords, :is_sensitive, :updated
|
||||
|
||||
attribute :license, if: -> { object.license.present? }
|
||||
|
||||
|
@ -19,8 +19,8 @@ class ActivityPub::EmojiSerializer < ActivityPub::Serializer
|
|||
'Emoji'
|
||||
end
|
||||
|
||||
def domain
|
||||
object.domain.presence || Rails.configuration.x.local_domain
|
||||
def keywords
|
||||
object.aliases
|
||||
end
|
||||
|
||||
def icon
|
||||
|
|
|
@ -16,11 +16,18 @@ class ManifestSerializer < ActiveModel::Serializer
|
|||
512
|
||||
).freeze
|
||||
|
||||
attributes :name, :short_name,
|
||||
attributes :id, :name, :short_name,
|
||||
:icons, :theme_color, :background_color,
|
||||
:display, :start_url, :scope,
|
||||
:share_target, :shortcuts
|
||||
|
||||
def id
|
||||
# This is set to `/home` because that was the old value of `start_url` and
|
||||
# thus the fallback ID computed by Chrome:
|
||||
# https://developer.chrome.com/blog/pwa-manifest-id/
|
||||
'/home'
|
||||
end
|
||||
|
||||
def name
|
||||
object.title
|
||||
end
|
||||
|
@ -53,7 +60,7 @@ class ManifestSerializer < ActiveModel::Serializer
|
|||
end
|
||||
|
||||
def start_url
|
||||
'/home'
|
||||
'/'
|
||||
end
|
||||
|
||||
def scope
|
||||
|
|
|
@ -5,7 +5,7 @@ class REST::Admin::DomainBlockSerializer < ActiveModel::Serializer
|
|||
:reject_media, :reject_favourite, :reject_reply, :reject_reports,
|
||||
:reject_send_not_public_searchability, :reject_reply_exclude_followers,
|
||||
:reject_send_public_unlisted, :reject_send_dissubscribable, :reject_send_media, :reject_send_sensitive,
|
||||
:reject_hashtag, :reject_straight_follow, :reject_new_follow, :detect_invalid_subscription,
|
||||
:reject_hashtag, :reject_straight_follow, :reject_new_follow, :reject_friend, :detect_invalid_subscription,
|
||||
:private_comment, :public_comment, :obfuscate
|
||||
|
||||
def id
|
||||
|
|
|
@ -5,6 +5,8 @@ class ActivityPub::ProcessStatusUpdateService < BaseService
|
|||
include Redisable
|
||||
include Lockable
|
||||
|
||||
class AbortError < ::StandardError; end
|
||||
|
||||
def call(status, activity_json, object_json, request_id: nil)
|
||||
raise ArgumentError, 'Status has unsaved changes' if status.changed?
|
||||
|
||||
|
@ -30,6 +32,9 @@ class ActivityPub::ProcessStatusUpdateService < BaseService
|
|||
handle_implicit_update!
|
||||
end
|
||||
|
||||
@status
|
||||
rescue AbortError
|
||||
@status.reload
|
||||
@status
|
||||
end
|
||||
|
||||
|
@ -46,6 +51,7 @@ class ActivityPub::ProcessStatusUpdateService < BaseService
|
|||
update_poll!
|
||||
update_immediate_attributes!
|
||||
update_metadata!
|
||||
validate_status_mentions!
|
||||
create_edits!
|
||||
end
|
||||
|
||||
|
@ -160,6 +166,15 @@ class ActivityPub::ProcessStatusUpdateService < BaseService
|
|||
!Admin::NgWord.reject?("#{@status_parser.spoiler_text}\n#{@status_parser.text}") && !Admin::NgWord.hashtag_reject?(@raw_tags.size)
|
||||
end
|
||||
|
||||
def validate_status_mentions!
|
||||
raise AbortError if mention_to_stranger? && Admin::NgWord.stranger_mention_reject?("#{@status.spoiler_text}\n#{@status.text}")
|
||||
end
|
||||
|
||||
def mention_to_stranger?
|
||||
@status.mentions.map(&:account).to_a.any? { |mentioned_account| mentioned_account.id != @status.account.id && !mentioned_account.following?(@status.account) } ||
|
||||
(@status.thread.present? && @status.thread.account.id != @status.account.id && !@status.thread.account.following?(@status.account))
|
||||
end
|
||||
|
||||
def update_immediate_attributes!
|
||||
@status.text = @status_parser.text || ''
|
||||
@status.spoiler_text = @status_parser.spoiler_text || ''
|
||||
|
@ -249,6 +264,7 @@ class ActivityPub::ProcessStatusUpdateService < BaseService
|
|||
emoji.image_remote_url = custom_emoji_parser.image_remote_url
|
||||
emoji.license = custom_emoji_parser.license
|
||||
emoji.is_sensitive = custom_emoji_parser.is_sensitive
|
||||
emoji.aliases = custom_emoji_parser.aliases
|
||||
emoji.save
|
||||
rescue Seahorse::Client::NetworkingError => e
|
||||
Rails.logger.warn "Error storing emoji: #{e}"
|
||||
|
|
|
@ -16,8 +16,9 @@ module Payloadable
|
|||
always_sign = options.delete(:always_sign)
|
||||
payload = ActiveModelSerializers::SerializableResource.new(record, options.merge(serializer: serializer, adapter: ActivityPub::Adapter)).as_json
|
||||
object = record.respond_to?(:virtual_object) ? record.virtual_object : record
|
||||
bearcap = object.is_a?(String) && record.respond_to?(:type) && (record.type == 'Create' || record.type == 'Update')
|
||||
|
||||
if ((object.respond_to?(:sign?) && object.sign?) && signer && (always_sign || signing_enabled?)) || object.is_a?(String)
|
||||
if ((object.respond_to?(:sign?) && object.sign?) && signer && (always_sign || signing_enabled?)) || bearcap
|
||||
ActivityPub::LinkedDataSignature.new(payload).sign!(signer, sign_with: sign_with)
|
||||
else
|
||||
payload
|
||||
|
|
|
@ -16,95 +16,81 @@ class EmojiReactService < BaseService
|
|||
authorize_with account, status, :emoji_reaction?
|
||||
@status = status
|
||||
|
||||
emoji_reaction = nil
|
||||
|
||||
with_redis_lock("emoji_reaction:#{status.id}") do
|
||||
emoji_reaction = EmojiReaction.find_by(account: account, status: status, name: name)
|
||||
raise Mastodon::ValidationError, I18n.t('reactions.errors.duplication') unless emoji_reaction.nil?
|
||||
|
||||
shortcode, domain = name.split('@')
|
||||
domain = nil if TagManager.instance.local_domain?(domain)
|
||||
custom_emoji = CustomEmoji.find_by(shortcode: shortcode, domain: domain)
|
||||
return if domain.present? && !EmojiReaction.exists?(status: status, custom_emoji: custom_emoji)
|
||||
|
||||
emoji_reaction = EmojiReaction.create!(account: account, status: status, name: shortcode, custom_emoji: custom_emoji)
|
||||
@emoji_reaction = EmojiReaction.find_by(account: account, status: status, name: shortcode, custom_emoji: custom_emoji)
|
||||
raise Mastodon::ValidationError, I18n.t('reactions.errors.duplication') unless @emoji_reaction.nil?
|
||||
|
||||
@emoji_reaction = EmojiReaction.create!(account: account, status: status, name: shortcode, custom_emoji: custom_emoji)
|
||||
|
||||
status.touch # rubocop:disable Rails/SkipsModelValidations
|
||||
end
|
||||
|
||||
raise Mastodon::ValidationError, I18n.t('reactions.errors.duplication') if emoji_reaction.nil?
|
||||
raise Mastodon::ValidationError, I18n.t('reactions.errors.duplication') if @emoji_reaction.nil?
|
||||
|
||||
Trends.statuses.register(status)
|
||||
|
||||
create_notification(emoji_reaction)
|
||||
notify_to_followers(emoji_reaction)
|
||||
bump_potential_friendship(account, status)
|
||||
write_stream(emoji_reaction)
|
||||
relay_for_emoji_reaction!(emoji_reaction)
|
||||
relay_friend_for_emoji_reaction!(emoji_reaction)
|
||||
create_notification
|
||||
notify_to_followers
|
||||
bump_potential_friendship!
|
||||
write_stream!
|
||||
|
||||
emoji_reaction
|
||||
@emoji_reaction
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def create_notification(emoji_reaction)
|
||||
status = emoji_reaction.status
|
||||
def create_notification
|
||||
status = @emoji_reaction.status
|
||||
|
||||
if status.account.local?
|
||||
if status.account.user&.setting_enable_emoji_reaction
|
||||
LocalNotificationWorker.perform_async(status.account_id, emoji_reaction.id, 'EmojiReaction', 'reaction') if status.account.user&.setting_emoji_reaction_streaming_notify_impl2
|
||||
LocalNotificationWorker.perform_async(status.account_id, emoji_reaction.id, 'EmojiReaction', 'emoji_reaction')
|
||||
LocalNotificationWorker.perform_async(status.account_id, @emoji_reaction.id, 'EmojiReaction', 'reaction') if status.account.user&.setting_emoji_reaction_streaming_notify_impl2
|
||||
LocalNotificationWorker.perform_async(status.account_id, @emoji_reaction.id, 'EmojiReaction', 'emoji_reaction')
|
||||
end
|
||||
elsif status.account.activitypub?
|
||||
ActivityPub::DeliveryWorker.perform_async(build_json(emoji_reaction), emoji_reaction.account_id, status.account.inbox_url)
|
||||
ActivityPub::DeliveryWorker.perform_async(payload, @emoji_reaction.account_id, status.account.inbox_url)
|
||||
end
|
||||
end
|
||||
|
||||
def notify_to_followers(emoji_reaction)
|
||||
status = emoji_reaction.status
|
||||
def notify_to_followers
|
||||
status = @emoji_reaction.status
|
||||
|
||||
return unless status.account.local?
|
||||
|
||||
ActivityPub::RawDistributionWorker.perform_async(build_json(emoji_reaction), status.account_id)
|
||||
ActivityPub::DeliveryWorker.push_bulk(inboxes, limit: 1_000) do |inbox_url|
|
||||
[payload, @status.account.id, inbox_url]
|
||||
end
|
||||
end
|
||||
|
||||
def write_stream(emoji_reaction)
|
||||
emoji_group = emoji_reaction.status.emoji_reactions_grouped_by_name(nil, force: true)
|
||||
.find { |reaction_group| reaction_group['name'] == emoji_reaction.name && (!reaction_group.key?(:domain) || reaction_group['domain'] == emoji_reaction.custom_emoji&.domain) }
|
||||
emoji_group['status_id'] = emoji_reaction.status_id.to_s
|
||||
DeliveryEmojiReactionWorker.perform_async(render_emoji_reaction(emoji_group), emoji_reaction.status_id, emoji_reaction.account_id)
|
||||
def inboxes
|
||||
StatusReachFinder.new(@status).all_inboxes
|
||||
end
|
||||
|
||||
def bump_potential_friendship(account, status)
|
||||
def write_stream!
|
||||
emoji_group = @emoji_reaction.status.emoji_reactions_grouped_by_name(nil, force: true)
|
||||
.find { |reaction_group| reaction_group['name'] == @emoji_reaction.name && (!reaction_group.key?(:domain) || reaction_group['domain'] == @emoji_reaction.custom_emoji&.domain) }
|
||||
emoji_group['status_id'] = @emoji_reaction.status_id.to_s
|
||||
DeliveryEmojiReactionWorker.perform_async(render_emoji_reaction(emoji_group), @emoji_reaction.status_id, @emoji_reaction.account_id)
|
||||
end
|
||||
|
||||
def bump_potential_friendship!
|
||||
ActivityTracker.increment('activity:interactions')
|
||||
return if account.following?(status.account_id)
|
||||
return if @emoji_reaction.account.following?(@emoji_reaction.status.account_id)
|
||||
|
||||
PotentialFriendshipTracker.record(account.id, status.account_id, :emoji_reaction)
|
||||
PotentialFriendshipTracker.record(@emoji_reaction.account.id, @emoji_reaction.status.account_id, :emoji_reaction)
|
||||
end
|
||||
|
||||
def build_json(emoji_reaction)
|
||||
@build_json = Oj.dump(serialize_payload(emoji_reaction, ActivityPub::EmojiReactionSerializer, signer: emoji_reaction.account))
|
||||
def payload
|
||||
@payload = Oj.dump(serialize_payload(@emoji_reaction, ActivityPub::EmojiReactionSerializer, signer: @emoji_reaction.account))
|
||||
end
|
||||
|
||||
def render_emoji_reaction(emoji_group)
|
||||
# @rendered_emoji_reaction ||= InlineRenderer.render(HashObject.new(emoji_group), nil, :emoji_reaction)
|
||||
@render_emoji_reaction ||= Oj.dump(event: :emoji_reaction, payload: emoji_group.to_json)
|
||||
end
|
||||
|
||||
def relay_for_emoji_reaction!(emoji_reaction)
|
||||
return unless @status.local? && @status.public_visibility?
|
||||
|
||||
ActivityPub::DeliveryWorker.push_bulk(Relay.enabled.pluck(:inbox_url)) do |inbox_url|
|
||||
[build_json(emoji_reaction), @status.account.id, inbox_url]
|
||||
end
|
||||
end
|
||||
|
||||
def relay_friend_for_emoji_reaction!(emoji_reaction)
|
||||
return unless @status.local? && @status.distributable_friend?
|
||||
|
||||
ActivityPub::DeliveryWorker.push_bulk(FriendDomain.distributables.pluck(:inbox_url)) do |inbox_url|
|
||||
[build_json(emoji_reaction), @status.account.id, inbox_url]
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
@ -89,7 +89,7 @@ class FanOutOnWriteService < BaseService
|
|||
end
|
||||
|
||||
def notify_about_update!
|
||||
@status.reblogged_by_accounts.merge(Account.local).select(:id).reorder(nil).find_in_batches do |accounts|
|
||||
@status.reblogged_by_accounts.or(@status.quoted_by_accounts).merge(Account.local).select(:id).reorder(nil).find_in_batches do |accounts|
|
||||
LocalNotificationWorker.push_bulk(accounts) do |account|
|
||||
[account.id, @status.id, 'Status', 'update']
|
||||
end
|
||||
|
|
|
@ -146,6 +146,7 @@ class PostStatusService < BaseService
|
|||
@status = @account.statuses.new(status_attributes)
|
||||
process_mentions_service.call(@status, limited_type: @status.limited_visibility? ? @limited_scope : '', circle: @circle, save_records: false)
|
||||
safeguard_mentions!(@status)
|
||||
validate_status_mentions!
|
||||
|
||||
@status.limited_scope = :personal if @status.limited_visibility? && !process_mentions_service.mentions?
|
||||
|
||||
|
@ -208,6 +209,15 @@ class PostStatusService < BaseService
|
|||
raise Mastodon::ValidationError, I18n.t('statuses.too_many_hashtags') if Admin::NgWord.hashtag_reject_with_extractor?(@options[:text])
|
||||
end
|
||||
|
||||
def validate_status_mentions!
|
||||
raise Mastodon::ValidationError, I18n.t('statuses.contains_ng_words') if mention_to_stranger? && Setting.stranger_mention_from_local_ng && Admin::NgWord.stranger_mention_reject?("#{@options[:spoiler_text]}\n#{@options[:text]}")
|
||||
end
|
||||
|
||||
def mention_to_stranger?
|
||||
@status.mentions.map(&:account).to_a.any? { |mentioned_account| mentioned_account.id != @account.id && !mentioned_account.following?(@account) } ||
|
||||
(@in_reply_to && @in_reply_to.account.id != @account.id && !@in_reply_to.account.following?(@account))
|
||||
end
|
||||
|
||||
def validate_media!
|
||||
if @options[:media_ids].blank? || !@options[:media_ids].is_a?(Enumerable)
|
||||
@media = []
|
||||
|
|
|
@ -38,7 +38,13 @@ class UpdateAccountService < BaseService
|
|||
end
|
||||
|
||||
def check_links(account)
|
||||
VerifyAccountLinksWorker.perform_async(account.id) if account.fields.any?(&:requires_verification?)
|
||||
return unless account.fields.any?(&:requires_verification?)
|
||||
|
||||
if account.local?
|
||||
VerifyAccountLinksWorker.perform_async(account.id)
|
||||
else
|
||||
VerifyAccountLinksWorker.perform_in(rand(10.minutes.to_i), account.id)
|
||||
end
|
||||
end
|
||||
|
||||
def process_hashtags(account)
|
||||
|
|
|
@ -35,10 +35,13 @@ class UpdateStatusService < BaseService
|
|||
update_poll! if @options.key?(:poll)
|
||||
update_immediate_attributes!
|
||||
create_edit! unless @options[:no_history]
|
||||
|
||||
reset_preview_card!
|
||||
process_mentions_service.call(@status)
|
||||
validate_status_mentions!
|
||||
end
|
||||
|
||||
queue_poll_notifications!
|
||||
reset_preview_card!
|
||||
update_metadata!
|
||||
update_references!
|
||||
broadcast_updates!
|
||||
|
@ -81,6 +84,15 @@ class UpdateStatusService < BaseService
|
|||
raise Mastodon::ValidationError, I18n.t('statuses.too_many_hashtags') if Admin::NgWord.hashtag_reject_with_extractor?(@options[:text])
|
||||
end
|
||||
|
||||
def validate_status_mentions!
|
||||
raise Mastodon::ValidationError, I18n.t('statuses.contains_ng_words') if mention_to_stranger? && Setting.stranger_mention_from_local_ng && Admin::NgWord.stranger_mention_reject?("#{@options[:spoiler_text]}\n#{@options[:text]}")
|
||||
end
|
||||
|
||||
def mention_to_stranger?
|
||||
@status.mentions.map(&:account).to_a.any? { |mentioned_account| mentioned_account.id != @status.account.id && !mentioned_account.following?(@status.account) } ||
|
||||
(@status.thread.present? && @status.thread.account.id != @status.account.id && !@status.thread.account.following?(@status.account))
|
||||
end
|
||||
|
||||
def validate_media!
|
||||
return [] if @options[:media_ids].blank? || !@options[:media_ids].is_a?(Enumerable)
|
||||
|
||||
|
@ -167,7 +179,6 @@ class UpdateStatusService < BaseService
|
|||
|
||||
def update_metadata!
|
||||
ProcessHashtagsService.new.call(@status)
|
||||
process_mentions_service.call(@status)
|
||||
|
||||
@status.update(limited_scope: :circle) if process_mentions_service.mentions?
|
||||
end
|
||||
|
|
|
@ -2,25 +2,40 @@
|
|||
|
||||
class ExistingUsernameValidator < ActiveModel::EachValidator
|
||||
def validate_each(record, attribute, value)
|
||||
return if value.blank?
|
||||
@value = value
|
||||
return if @value.blank?
|
||||
|
||||
usernames_and_domains = value.split(',').filter_map do |str|
|
||||
username, domain = str.strip.gsub(/\A@/, '').split('@', 2)
|
||||
if options[:multiple]
|
||||
record.errors.add(attribute, not_found_multiple_message) if usernames_with_no_accounts.any?
|
||||
elsif usernames_with_no_accounts.any? || usernames_and_domains.size > 1
|
||||
record.errors.add(attribute, not_found_message)
|
||||
end
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def usernames_and_domains
|
||||
@value.split(',').filter_map do |string|
|
||||
username, domain = string.strip.gsub(/\A@/, '').split('@', 2)
|
||||
domain = nil if TagManager.instance.local_domain?(domain)
|
||||
|
||||
next if username.blank?
|
||||
|
||||
[str, username, domain]
|
||||
end
|
||||
|
||||
usernames_with_no_accounts = usernames_and_domains.filter_map do |(str, username, domain)|
|
||||
str unless Account.find_remote(username, domain)
|
||||
end
|
||||
|
||||
if options[:multiple]
|
||||
record.errors.add(attribute, I18n.t('existing_username_validator.not_found_multiple', usernames: usernames_with_no_accounts.join(', '))) if usernames_with_no_accounts.any?
|
||||
elsif usernames_with_no_accounts.any? || usernames_and_domains.size > 1
|
||||
record.errors.add(attribute, I18n.t('existing_username_validator.not_found'))
|
||||
[string, username, domain]
|
||||
end
|
||||
end
|
||||
|
||||
def usernames_with_no_accounts
|
||||
usernames_and_domains.filter_map do |(string, username, domain)|
|
||||
string unless Account.find_remote(username, domain)
|
||||
end
|
||||
end
|
||||
|
||||
def not_found_multiple_message
|
||||
I18n.t('existing_username_validator.not_found_multiple', usernames: usernames_with_no_accounts.join(', '))
|
||||
end
|
||||
|
||||
def not_found_message
|
||||
I18n.t('existing_username_validator.not_found')
|
||||
end
|
||||
end
|
||||
|
|
|
@ -11,16 +11,31 @@ class UnreservedUsernameValidator < ActiveModel::Validator
|
|||
|
||||
private
|
||||
|
||||
def pam_controlled?
|
||||
return false unless Devise.pam_authentication && Devise.pam_controlled_service
|
||||
|
||||
Rpam2.account(Devise.pam_controlled_service, @username).present?
|
||||
def reserved_username?
|
||||
pam_username_reserved? || settings_username_reserved?
|
||||
end
|
||||
|
||||
def reserved_username?
|
||||
return true if pam_controlled?
|
||||
return false unless Setting.reserved_usernames
|
||||
def pam_username_reserved?
|
||||
pam_controlled? && pam_reserves_username?
|
||||
end
|
||||
|
||||
def pam_controlled?
|
||||
Devise.pam_authentication && Devise.pam_controlled_service
|
||||
end
|
||||
|
||||
def pam_reserves_username?
|
||||
Rpam2.account(Devise.pam_controlled_service, @username)
|
||||
end
|
||||
|
||||
def settings_username_reserved?
|
||||
settings_has_reserved_usernames? && settings_reserves_username?
|
||||
end
|
||||
|
||||
def settings_has_reserved_usernames?
|
||||
Setting.reserved_usernames.present?
|
||||
end
|
||||
|
||||
def settings_reserves_username?
|
||||
Setting.reserved_usernames.include?(@username.downcase)
|
||||
end
|
||||
end
|
||||
|
|
41
app/views/admin/accounts/_buttons.html.haml
Normal file
41
app/views/admin/accounts/_buttons.html.haml
Normal file
|
@ -0,0 +1,41 @@
|
|||
- if account.suspended?
|
||||
%hr.spacer/
|
||||
- if account.suspension_origin_remote?
|
||||
%p.muted-hint= deletion_request.present? ? t('admin.accounts.remote_suspension_reversible_hint_html', date: content_tag(:strong, l(deletion_request.due_at.to_date))) : t('admin.accounts.remote_suspension_irreversible')
|
||||
- else
|
||||
%p.muted-hint= deletion_request.present? ? t('admin.accounts.suspension_reversible_hint_html', date: content_tag(:strong, l(deletion_request.due_at.to_date))) : t('admin.accounts.suspension_irreversible')
|
||||
= link_to t('admin.accounts.undo_suspension'), unsuspend_admin_account_path(account.id), method: :post, class: 'button' if can?(:unsuspend, account)
|
||||
= link_to t('admin.accounts.redownload'), redownload_admin_account_path(account.id), method: :post, class: 'button' if can?(:redownload, account) && account.suspension_origin_remote?
|
||||
- if deletion_request.present?
|
||||
= link_to t('admin.accounts.delete'), admin_account_path(account.id), method: :delete, class: 'button button--destructive', data: { confirm: t('admin.accounts.are_you_sure') } if can?(:destroy, account)
|
||||
- else
|
||||
.action-buttons
|
||||
%div
|
||||
- if account.local? && account.user_approved?
|
||||
= link_to t('admin.accounts.warn'), new_admin_account_action_path(account.id, type: 'none'), class: 'button' if can?(:warn, account)
|
||||
- if account.user_disabled?
|
||||
= link_to t('admin.accounts.enable'), enable_admin_account_path(account.id), method: :post, class: 'button' if can?(:enable, account.user)
|
||||
- else
|
||||
= link_to t('admin.accounts.disable'), new_admin_account_action_path(account.id, type: 'disable'), class: 'button' if can?(:disable, account.user)
|
||||
- if account.sensitized?
|
||||
= link_to t('admin.accounts.undo_sensitized'), unsensitive_admin_account_path(account.id), method: :post, class: 'button' if can?(:unsensitive, account)
|
||||
- elsif !account.local? || account.user_approved?
|
||||
= link_to t('admin.accounts.sensitive'), new_admin_account_action_path(account.id, type: 'sensitive'), class: 'button' if can?(:sensitive, account)
|
||||
- if account.silenced?
|
||||
= link_to t('admin.accounts.undo_silenced'), unsilence_admin_account_path(account.id), method: :post, class: 'button' if can?(:unsilence, account)
|
||||
- elsif !account.local? || account.user_approved?
|
||||
= link_to t('admin.accounts.silence'), new_admin_account_action_path(account.id, type: 'silence'), class: 'button' if can?(:silence, account)
|
||||
- if account.local?
|
||||
- if account.user_pending?
|
||||
= link_to t('admin.accounts.approve'), approve_admin_account_path(account.id), method: :post, data: { confirm: t('admin.accounts.are_you_sure') }, class: 'button' if can?(:approve, account.user)
|
||||
= link_to t('admin.accounts.reject'), reject_admin_account_path(account.id), method: :post, data: { confirm: t('admin.accounts.are_you_sure') }, class: 'button button--destructive' if can?(:reject, account.user)
|
||||
- unless account.user_confirmed?
|
||||
= link_to t('admin.accounts.confirm'), admin_account_confirmation_path(account.id), method: :post, class: 'button' if can?(:confirm, account.user)
|
||||
- if !account.local? || account.user_approved?
|
||||
= link_to t('admin.accounts.perform_full_suspension'), new_admin_account_action_path(account.id, type: 'suspend'), class: 'button' if can?(:suspend, account)
|
||||
%div
|
||||
- if account.local?
|
||||
- if !account.memorial? && account.user_approved?
|
||||
= link_to t('admin.accounts.memorialize'), memorialize_admin_account_path(account.id), method: :post, data: { confirm: t('admin.accounts.are_you_sure') }, class: 'button button--destructive' if can?(:memorialize, account)
|
||||
- else
|
||||
= link_to t('admin.accounts.redownload'), redownload_admin_account_path(account.id), method: :post, class: 'button' if can?(:redownload, account)
|
43
app/views/admin/accounts/_counters.html.haml
Normal file
43
app/views/admin/accounts/_counters.html.haml
Normal file
|
@ -0,0 +1,43 @@
|
|||
.dashboard__counters.admin-account-counters
|
||||
%div
|
||||
= link_to admin_account_statuses_path(account.id) do
|
||||
.dashboard__counters__num= number_with_delimiter account.statuses_count
|
||||
.dashboard__counters__label= t 'admin.accounts.statuses'
|
||||
%div
|
||||
= link_to admin_account_statuses_path(account.id, { media: true }) do
|
||||
.dashboard__counters__num= number_to_human_size account.media_attachments.sum('file_file_size')
|
||||
.dashboard__counters__label= t 'admin.accounts.media_attachments'
|
||||
%div
|
||||
= link_to admin_account_relationships_path(account.id, location: account.local? ? nil : 'local', relationship: 'followed_by') do
|
||||
.dashboard__counters__num= number_with_delimiter account.local_followers_count
|
||||
.dashboard__counters__label= t 'admin.accounts.followers'
|
||||
%div
|
||||
= link_to admin_reports_path(account_id: account.id) do
|
||||
.dashboard__counters__num= number_with_delimiter account.reports.count
|
||||
.dashboard__counters__label= t 'admin.accounts.show.created_reports'
|
||||
%div
|
||||
= link_to admin_reports_path(target_account_id: account.id) do
|
||||
.dashboard__counters__num= number_with_delimiter account.targeted_reports.count
|
||||
.dashboard__counters__label= t 'admin.accounts.show.targeted_reports'
|
||||
%div
|
||||
= link_to admin_action_logs_path(target_account_id: account.id) do
|
||||
.dashboard__counters__text
|
||||
- if account.local? && account.user.nil?
|
||||
= t('admin.accounts.deleted')
|
||||
- elsif account.memorial?
|
||||
= t('admin.accounts.memorialized')
|
||||
- elsif account.suspended?
|
||||
= t('admin.accounts.suspended')
|
||||
- elsif account.silenced?
|
||||
= t('admin.accounts.silenced')
|
||||
- elsif account.local? && account.user&.disabled?
|
||||
= t('admin.accounts.disabled')
|
||||
- elsif account.local? && !account.user&.confirmed?
|
||||
= t('admin.accounts.confirming')
|
||||
- elsif account.local? && !account.user_approved?
|
||||
= t('admin.accounts.pending')
|
||||
- elsif account.sensitized?
|
||||
= t('admin.accounts.sensitive')
|
||||
- else
|
||||
= t('admin.accounts.no_limits_imposed')
|
||||
.dashboard__counters__label= t 'admin.accounts.login_status'
|
82
app/views/admin/accounts/_local_account.html.haml
Normal file
82
app/views/admin/accounts/_local_account.html.haml
Normal file
|
@ -0,0 +1,82 @@
|
|||
- if account.avatar?
|
||||
%tr
|
||||
%th= t('admin.accounts.avatar')
|
||||
%td= table_link_to 'trash', t('admin.accounts.remove_avatar'), remove_avatar_admin_account_path(account.id), method: :post, data: { confirm: t('admin.accounts.are_you_sure') } if can?(:remove_avatar, account)
|
||||
%td
|
||||
- if account.header?
|
||||
%tr
|
||||
%th= t('admin.accounts.header')
|
||||
%td= table_link_to 'trash', t('admin.accounts.remove_header'), remove_header_admin_account_path(account.id), method: :post, data: { confirm: t('admin.accounts.are_you_sure') } if can?(:remove_header, account)
|
||||
%td
|
||||
%tr
|
||||
%th= t('admin.accounts.role')
|
||||
%td
|
||||
- if account.user_role&.everyone?
|
||||
= t('admin.accounts.no_role_assigned')
|
||||
- else
|
||||
= account.user_role&.name
|
||||
%td
|
||||
= table_link_to 'vcard', t('admin.accounts.change_role.label'), admin_user_role_path(account.user) if can?(:change_role, account.user)
|
||||
%tr
|
||||
%th{ rowspan: can?(:create, :email_domain_block) ? 3 : 2 }= t('admin.accounts.email')
|
||||
%td{ rowspan: can?(:create, :email_domain_block) ? 3 : 2 }= account.user_email
|
||||
%td= table_link_to 'edit', t('admin.accounts.change_email.label'), admin_account_change_email_path(account.id) if can?(:change_email, account.user)
|
||||
%tr
|
||||
%td= table_link_to 'search', t('admin.accounts.search_same_email_domain'), admin_accounts_path(email: "%@#{account.user_email.split('@').last}")
|
||||
- if can?(:create, :email_domain_block)
|
||||
%tr
|
||||
%td= table_link_to 'ban', t('admin.accounts.add_email_domain_block'), new_admin_email_domain_block_path(_domain: account.user_email.split('@').last)
|
||||
- if account.user_unconfirmed_email.present?
|
||||
%tr
|
||||
%th= t('admin.accounts.unconfirmed_email')
|
||||
%td= account.user_unconfirmed_email
|
||||
%td
|
||||
%tr
|
||||
%th= t('admin.accounts.email_status')
|
||||
%td
|
||||
- if account.user&.confirmed?
|
||||
= t('admin.accounts.confirmed')
|
||||
- else
|
||||
= t('admin.accounts.confirming')
|
||||
%td= table_link_to 'refresh', t('admin.accounts.resend_confirmation.send'), resend_admin_account_confirmation_path(account.id), method: :post if can?(:confirm, account.user)
|
||||
%tr
|
||||
%th{ rowspan: can?(:reset_password, account.user) ? 2 : 1 }= t('admin.accounts.security')
|
||||
%td{ rowspan: can?(:reset_password, account.user) ? 2 : 1 }
|
||||
- if account.user&.two_factor_enabled?
|
||||
= t 'admin.accounts.security_measures.password_and_2fa'
|
||||
- else
|
||||
= t 'admin.accounts.security_measures.only_password'
|
||||
%td
|
||||
- if account.user&.two_factor_enabled?
|
||||
= table_link_to 'unlock', t('admin.accounts.disable_two_factor_authentication'), admin_user_two_factor_authentication_path(account.user.id), method: :delete if can?(:disable_2fa, account.user)
|
||||
- if can?(:reset_password, account.user)
|
||||
%tr
|
||||
%td
|
||||
= table_link_to 'key', t('admin.accounts.reset_password'), admin_account_reset_path(account.id), method: :create, data: { confirm: t('admin.accounts.are_you_sure') }
|
||||
%tr
|
||||
%th= t('simple_form.labels.defaults.locale')
|
||||
%td= standard_locale_name(account.user_locale)
|
||||
%td
|
||||
%tr
|
||||
%th= t('admin.accounts.joined')
|
||||
%td
|
||||
%time.formatted{ datetime: account.created_at.iso8601, title: l(account.created_at) }= l account.created_at
|
||||
%td
|
||||
- recent_ips = account.user.ips.order(used_at: :desc).to_a
|
||||
- recent_ips.each_with_index do |recent_ip, i|
|
||||
%tr
|
||||
- if i.zero?
|
||||
%th{ rowspan: recent_ips.size }= t('admin.accounts.most_recent_ip')
|
||||
%td= recent_ip.ip
|
||||
%td= table_link_to 'search', t('admin.accounts.search_same_ip'), admin_accounts_path(ip: recent_ip.ip)
|
||||
%tr
|
||||
%th= t('admin.accounts.most_recent_activity')
|
||||
%td
|
||||
- if account.user_current_sign_in_at
|
||||
%time.formatted{ datetime: account.user_current_sign_in_at.iso8601, title: l(account.user_current_sign_in_at) }= l account.user_current_sign_in_at
|
||||
%td
|
||||
- if account.user&.invited?
|
||||
%tr
|
||||
%th= t('admin.accounts.invited_by')
|
||||
%td= admin_account_link_to account.user.invite.user.account
|
||||
%td
|
15
app/views/admin/accounts/_remote_account.html.haml
Normal file
15
app/views/admin/accounts/_remote_account.html.haml
Normal file
|
@ -0,0 +1,15 @@
|
|||
%tr
|
||||
%th= t('admin.accounts.inbox_url')
|
||||
%td
|
||||
= account.inbox_url
|
||||
= fa_icon DeliveryFailureTracker.available?(account.inbox_url) ? 'check' : 'times'
|
||||
%td
|
||||
= table_link_to 'search', domain_block.present? ? t('admin.domain_blocks.view') : t('admin.accounts.view_domain'), admin_instance_path(account.domain)
|
||||
%tr
|
||||
%th= t('admin.accounts.shared_inbox_url')
|
||||
%td
|
||||
= account.shared_inbox_url
|
||||
= fa_icon DeliveryFailureTracker.available?(account.shared_inbox_url) ? 'check' : 'times'
|
||||
%td
|
||||
- if domain_block.nil?
|
||||
= table_link_to 'ban', t('admin.domain_blocks.add_new'), new_admin_domain_block_path(_domain: account.domain)
|
|
@ -27,49 +27,7 @@
|
|||
%div
|
||||
.account__header__content.emojify= prerender_custom_emojis(account_bio_format(account), account.emojis)
|
||||
|
||||
.dashboard__counters.admin-account-counters
|
||||
%div
|
||||
= link_to admin_account_statuses_path(@account.id) do
|
||||
.dashboard__counters__num= number_with_delimiter @account.statuses_count
|
||||
.dashboard__counters__label= t 'admin.accounts.statuses'
|
||||
%div
|
||||
= link_to admin_account_statuses_path(@account.id, { media: true }) do
|
||||
.dashboard__counters__num= number_to_human_size @account.media_attachments.sum('file_file_size')
|
||||
.dashboard__counters__label= t 'admin.accounts.media_attachments'
|
||||
%div
|
||||
= link_to admin_account_relationships_path(@account.id, location: @account.local? ? nil : 'local', relationship: 'followed_by') do
|
||||
.dashboard__counters__num= number_with_delimiter @account.local_followers_count
|
||||
.dashboard__counters__label= t 'admin.accounts.followers'
|
||||
%div
|
||||
= link_to admin_reports_path(account_id: @account.id) do
|
||||
.dashboard__counters__num= number_with_delimiter @account.reports.count
|
||||
.dashboard__counters__label= t '.created_reports'
|
||||
%div
|
||||
= link_to admin_reports_path(target_account_id: @account.id) do
|
||||
.dashboard__counters__num= number_with_delimiter @account.targeted_reports.count
|
||||
.dashboard__counters__label= t '.targeted_reports'
|
||||
%div
|
||||
= link_to admin_action_logs_path(target_account_id: @account.id) do
|
||||
.dashboard__counters__text
|
||||
- if @account.local? && @account.user.nil?
|
||||
= t('admin.accounts.deleted')
|
||||
- elsif @account.memorial?
|
||||
= t('admin.accounts.memorialized')
|
||||
- elsif @account.suspended?
|
||||
= t('admin.accounts.suspended')
|
||||
- elsif @account.silenced?
|
||||
= t('admin.accounts.silenced')
|
||||
- elsif @account.local? && @account.user&.disabled?
|
||||
= t('admin.accounts.disabled')
|
||||
- elsif @account.local? && !@account.user&.confirmed?
|
||||
= t('admin.accounts.confirming')
|
||||
- elsif @account.local? && !@account.user_approved?
|
||||
= t('admin.accounts.pending')
|
||||
- elsif @account.sensitized?
|
||||
= t('admin.accounts.sensitive')
|
||||
- else
|
||||
= t('admin.accounts.no_limits_imposed')
|
||||
.dashboard__counters__label= t 'admin.accounts.login_status'
|
||||
= render 'admin/accounts/counters', account: @account
|
||||
|
||||
- if @account.local? && @account.user.nil?
|
||||
= link_to t('admin.accounts.unblock_email'), unblock_email_admin_account_path(@account.id), method: :post, class: 'button' if can?(:unblock_email, @account) && CanonicalEmailBlock.exists?(reference_account_id: @account.id)
|
||||
|
@ -78,171 +36,11 @@
|
|||
%table.table.inline-table
|
||||
%tbody
|
||||
- if @account.local?
|
||||
- if @account.avatar?
|
||||
%tr
|
||||
%th= t('admin.accounts.avatar')
|
||||
%td= table_link_to 'trash', t('admin.accounts.remove_avatar'), remove_avatar_admin_account_path(@account.id), method: :post, data: { confirm: t('admin.accounts.are_you_sure') } if can?(:remove_avatar, @account)
|
||||
%td
|
||||
|
||||
- if @account.header?
|
||||
%tr
|
||||
%th= t('admin.accounts.header')
|
||||
%td= table_link_to 'trash', t('admin.accounts.remove_header'), remove_header_admin_account_path(@account.id), method: :post, data: { confirm: t('admin.accounts.are_you_sure') } if can?(:remove_header, @account)
|
||||
%td
|
||||
|
||||
%tr
|
||||
%th= t('admin.accounts.role')
|
||||
%td
|
||||
- if @account.user_role&.everyone?
|
||||
= t('admin.accounts.no_role_assigned')
|
||||
- else
|
||||
= @account.user_role&.name
|
||||
%td
|
||||
= table_link_to 'vcard', t('admin.accounts.change_role.label'), admin_user_role_path(@account.user) if can?(:change_role, @account.user)
|
||||
|
||||
%tr
|
||||
%th{ rowspan: can?(:create, :email_domain_block) ? 3 : 2 }= t('admin.accounts.email')
|
||||
%td{ rowspan: can?(:create, :email_domain_block) ? 3 : 2 }= @account.user_email
|
||||
%td= table_link_to 'edit', t('admin.accounts.change_email.label'), admin_account_change_email_path(@account.id) if can?(:change_email, @account.user)
|
||||
|
||||
%tr
|
||||
%td= table_link_to 'search', t('admin.accounts.search_same_email_domain'), admin_accounts_path(email: "%@#{@account.user_email.split('@').last}")
|
||||
|
||||
- if can?(:create, :email_domain_block)
|
||||
%tr
|
||||
%td= table_link_to 'ban', t('admin.accounts.add_email_domain_block'), new_admin_email_domain_block_path(_domain: @account.user_email.split('@').last)
|
||||
|
||||
- if @account.user_unconfirmed_email.present?
|
||||
%tr
|
||||
%th= t('admin.accounts.unconfirmed_email')
|
||||
%td= @account.user_unconfirmed_email
|
||||
%td
|
||||
|
||||
%tr
|
||||
%th= t('admin.accounts.email_status')
|
||||
%td
|
||||
- if @account.user&.confirmed?
|
||||
= t('admin.accounts.confirmed')
|
||||
- else
|
||||
= t('admin.accounts.confirming')
|
||||
%td= table_link_to 'refresh', t('admin.accounts.resend_confirmation.send'), resend_admin_account_confirmation_path(@account.id), method: :post if can?(:confirm, @account.user)
|
||||
%tr
|
||||
%th{ rowspan: can?(:reset_password, @account.user) ? 2 : 1 }= t('admin.accounts.security')
|
||||
%td{ rowspan: can?(:reset_password, @account.user) ? 2 : 1 }
|
||||
- if @account.user&.two_factor_enabled?
|
||||
= t 'admin.accounts.security_measures.password_and_2fa'
|
||||
- else
|
||||
= t 'admin.accounts.security_measures.only_password'
|
||||
%td
|
||||
- if @account.user&.two_factor_enabled?
|
||||
= table_link_to 'unlock', t('admin.accounts.disable_two_factor_authentication'), admin_user_two_factor_authentication_path(@account.user.id), method: :delete if can?(:disable_2fa, @account.user)
|
||||
|
||||
- if can?(:reset_password, @account.user)
|
||||
%tr
|
||||
%td
|
||||
= table_link_to 'key', t('admin.accounts.reset_password'), admin_account_reset_path(@account.id), method: :create, data: { confirm: t('admin.accounts.are_you_sure') }
|
||||
|
||||
%tr
|
||||
%th= t('simple_form.labels.defaults.locale')
|
||||
%td= standard_locale_name(@account.user_locale)
|
||||
%td
|
||||
|
||||
%tr
|
||||
%th= t('admin.accounts.joined')
|
||||
%td
|
||||
%time.formatted{ datetime: @account.created_at.iso8601, title: l(@account.created_at) }= l @account.created_at
|
||||
%td
|
||||
|
||||
- recent_ips = @account.user.ips.order(used_at: :desc).to_a
|
||||
|
||||
- recent_ips.each_with_index do |recent_ip, i|
|
||||
%tr
|
||||
- if i.zero?
|
||||
%th{ rowspan: recent_ips.size }= t('admin.accounts.most_recent_ip')
|
||||
%td= recent_ip.ip
|
||||
%td= table_link_to 'search', t('admin.accounts.search_same_ip'), admin_accounts_path(ip: recent_ip.ip)
|
||||
|
||||
%tr
|
||||
%th= t('admin.accounts.most_recent_activity')
|
||||
%td
|
||||
- if @account.user_current_sign_in_at
|
||||
%time.formatted{ datetime: @account.user_current_sign_in_at.iso8601, title: l(@account.user_current_sign_in_at) }= l @account.user_current_sign_in_at
|
||||
%td
|
||||
|
||||
- if @account.user&.invited?
|
||||
%tr
|
||||
%th= t('admin.accounts.invited_by')
|
||||
%td= admin_account_link_to @account.user.invite.user.account
|
||||
%td
|
||||
|
||||
= render 'admin/accounts/local_account', account: @account
|
||||
- else
|
||||
%tr
|
||||
%th= t('admin.accounts.inbox_url')
|
||||
%td
|
||||
= @account.inbox_url
|
||||
= fa_icon DeliveryFailureTracker.available?(@account.inbox_url) ? 'check' : 'times'
|
||||
%td
|
||||
= table_link_to 'search', @domain_block.present? ? t('admin.domain_blocks.view') : t('admin.accounts.view_domain'), admin_instance_path(@account.domain)
|
||||
%tr
|
||||
%th= t('admin.accounts.shared_inbox_url')
|
||||
%td
|
||||
= @account.shared_inbox_url
|
||||
= fa_icon DeliveryFailureTracker.available?(@account.shared_inbox_url) ? 'check' : 'times'
|
||||
%td
|
||||
- if @domain_block.nil?
|
||||
= table_link_to 'ban', t('admin.domain_blocks.add_new'), new_admin_domain_block_path(_domain: @account.domain)
|
||||
= render 'admin/accounts/remote_account', account: @account, domain_block: @domain_block
|
||||
|
||||
- if @account.suspended?
|
||||
%hr.spacer/
|
||||
|
||||
- if @account.suspension_origin_remote?
|
||||
%p.muted-hint= @deletion_request.present? ? t('admin.accounts.remote_suspension_reversible_hint_html', date: content_tag(:strong, l(@deletion_request.due_at.to_date))) : t('admin.accounts.remote_suspension_irreversible')
|
||||
- else
|
||||
%p.muted-hint= @deletion_request.present? ? t('admin.accounts.suspension_reversible_hint_html', date: content_tag(:strong, l(@deletion_request.due_at.to_date))) : t('admin.accounts.suspension_irreversible')
|
||||
|
||||
= link_to t('admin.accounts.undo_suspension'), unsuspend_admin_account_path(@account.id), method: :post, class: 'button' if can?(:unsuspend, @account)
|
||||
= link_to t('admin.accounts.redownload'), redownload_admin_account_path(@account.id), method: :post, class: 'button' if can?(:redownload, @account) && @account.suspension_origin_remote?
|
||||
|
||||
- if @deletion_request.present?
|
||||
= link_to t('admin.accounts.delete'), admin_account_path(@account.id), method: :delete, class: 'button button--destructive', data: { confirm: t('admin.accounts.are_you_sure') } if can?(:destroy, @account)
|
||||
- else
|
||||
.action-buttons
|
||||
%div
|
||||
- if @account.local? && @account.user_approved?
|
||||
= link_to t('admin.accounts.warn'), new_admin_account_action_path(@account.id, type: 'none'), class: 'button' if can?(:warn, @account)
|
||||
|
||||
- if @account.user_disabled?
|
||||
= link_to t('admin.accounts.enable'), enable_admin_account_path(@account.id), method: :post, class: 'button' if can?(:enable, @account.user)
|
||||
- else
|
||||
= link_to t('admin.accounts.disable'), new_admin_account_action_path(@account.id, type: 'disable'), class: 'button' if can?(:disable, @account.user)
|
||||
|
||||
- if @account.sensitized?
|
||||
= link_to t('admin.accounts.undo_sensitized'), unsensitive_admin_account_path(@account.id), method: :post, class: 'button' if can?(:unsensitive, @account)
|
||||
- elsif !@account.local? || @account.user_approved?
|
||||
= link_to t('admin.accounts.sensitive'), new_admin_account_action_path(@account.id, type: 'sensitive'), class: 'button' if can?(:sensitive, @account)
|
||||
|
||||
- if @account.silenced?
|
||||
= link_to t('admin.accounts.undo_silenced'), unsilence_admin_account_path(@account.id), method: :post, class: 'button' if can?(:unsilence, @account)
|
||||
- elsif !@account.local? || @account.user_approved?
|
||||
= link_to t('admin.accounts.silence'), new_admin_account_action_path(@account.id, type: 'silence'), class: 'button' if can?(:silence, @account)
|
||||
|
||||
- if @account.local?
|
||||
- if @account.user_pending?
|
||||
= link_to t('admin.accounts.approve'), approve_admin_account_path(@account.id), method: :post, data: { confirm: t('admin.accounts.are_you_sure') }, class: 'button' if can?(:approve, @account.user)
|
||||
= link_to t('admin.accounts.reject'), reject_admin_account_path(@account.id), method: :post, data: { confirm: t('admin.accounts.are_you_sure') }, class: 'button button--destructive' if can?(:reject, @account.user)
|
||||
|
||||
- unless @account.user_confirmed?
|
||||
= link_to t('admin.accounts.confirm'), admin_account_confirmation_path(@account.id), method: :post, class: 'button' if can?(:confirm, @account.user)
|
||||
|
||||
- if !@account.local? || @account.user_approved?
|
||||
= link_to t('admin.accounts.perform_full_suspension'), new_admin_account_action_path(@account.id, type: 'suspend'), class: 'button' if can?(:suspend, @account)
|
||||
|
||||
%div
|
||||
- if @account.local?
|
||||
- if !@account.memorial? && @account.user_approved?
|
||||
= link_to t('admin.accounts.memorialize'), memorialize_admin_account_path(@account.id), method: :post, data: { confirm: t('admin.accounts.are_you_sure') }, class: 'button button--destructive' if can?(:memorialize, @account)
|
||||
- else
|
||||
= link_to t('admin.accounts.redownload'), redownload_admin_account_path(@account.id), method: :post, class: 'button' if can?(:redownload, @account)
|
||||
= render 'admin/accounts/buttons', account: @account, deletion_request: @deletion_request
|
||||
|
||||
%hr.spacer/
|
||||
|
||||
|
|
|
@ -7,11 +7,16 @@
|
|||
|
||||
.batch-table__row__content__text
|
||||
%samp= ":#{custom_emoji.shortcode}:"
|
||||
= link_to safe_join([fa_icon('pencil'), t('admin.custom_emojis.edit.label')]), edit_admin_custom_emoji_path(custom_emoji, local: params[:local], remote: params[:remote], shortcode: params[:shortcode], by_domain: params[:by_domain]), method: :get, class: 'table-action-link'
|
||||
|
||||
- if custom_emoji.local?
|
||||
%span.information-badge= custom_emoji.category&.name || t('admin.custom_emojis.uncategorized')
|
||||
- if custom_emoji.aliases_raw.present?
|
||||
%br/
|
||||
%span= custom_emoji.aliases_raw
|
||||
%span.neutral-hint= custom_emoji.aliases_raw
|
||||
- if custom_emoji.license.present?
|
||||
%br/
|
||||
%span= custom_emoji.license
|
||||
|
||||
.batch-table__row__content__extra
|
||||
- if custom_emoji.local?
|
||||
|
|
42
app/views/admin/custom_emojis/edit.html.haml
Normal file
42
app/views/admin/custom_emojis/edit.html.haml
Normal file
|
@ -0,0 +1,42 @@
|
|||
- content_for :page_title do
|
||||
= t('.title')
|
||||
|
||||
= simple_form_for @custom_emoji, url: admin_custom_emoji_path(@custom_emoji.id), method: :put do |f|
|
||||
= render 'shared/error_messages', object: @custom_emoji
|
||||
- CustomEmojiFilter::KEYS.each do |key|
|
||||
= hidden_field_tag key, params[key] if params[key].present?
|
||||
|
||||
.fields-group
|
||||
= custom_emoji_tag(@custom_emoji)
|
||||
|
||||
%h4= t('admin.custom_emojis.shortcode')
|
||||
|
||||
.fields-group
|
||||
%samp= @custom_emoji.shortcode
|
||||
|
||||
- if !@custom_emoji.local?
|
||||
%h4= t('admin.custom_emojis.domain')
|
||||
|
||||
.fields-group
|
||||
%samp= @custom_emoji.domain
|
||||
|
||||
- if @custom_emoji.local?
|
||||
%h4= t('admin.custom_emojis.edit.label')
|
||||
|
||||
.fields-group
|
||||
= f.input :visible_in_picker, as: :boolean, wrapper: :with_label, label: t('admin.custom_emojis.visible_in_picker')
|
||||
|
||||
.fields-group
|
||||
= f.input :aliases_raw, wrapper: :with_label, kmyblue: true, label: t('admin.custom_emojis.aliases'), hint: t('admin.custom_emojis.aliases_hint')
|
||||
|
||||
.fields-group
|
||||
= f.input :license, wrapper: :with_label, kmyblue: true, label: t('admin.custom_emojis.license'), hint: t('admin.custom_emojis.license_hint')
|
||||
|
||||
.actions
|
||||
= f.button :button, t('generic.save_changes'), type: :submit
|
||||
|
||||
- elsif @custom_emoji.license.present?
|
||||
%h4= t('admin.custom_emojis.license')
|
||||
|
||||
.fields-group
|
||||
%p= @custom_emoji.license
|
|
@ -75,12 +75,6 @@
|
|||
.label_input
|
||||
= f.text_field :category_name, class: 'string optional', placeholder: t('admin.custom_emojis.create_new_category'), 'aria-label': t('admin.custom_emojis.create_new_category')
|
||||
|
||||
.fields-row
|
||||
.fields-group.fields-row__column
|
||||
.input.string.optional
|
||||
.label_input
|
||||
= f.text_field :aliases_raw, class: 'string optional', placeholder: 'Alias names', 'aria-label': 'Alias names'
|
||||
|
||||
.batch-table__body
|
||||
- if @custom_emojis.empty?
|
||||
= nothing_here 'nothing-here--under-tabs'
|
||||
|
|
|
@ -6,8 +6,18 @@
|
|||
|
||||
.fields-group
|
||||
= f.input :shortcode, wrapper: :with_label, label: t('admin.custom_emojis.shortcode'), hint: t('admin.custom_emojis.shortcode_hint')
|
||||
|
||||
.fields-group
|
||||
= f.input :image, wrapper: :with_label, input_html: { accept: CustomEmoji::IMAGE_MIME_TYPES.join(' ') }, hint: t('admin.custom_emojis.image_hint', size: number_to_human_size(CustomEmoji::LIMIT))
|
||||
|
||||
.fields-group
|
||||
= f.input :visible_in_picker, as: :boolean, wrapper: :with_label, label: t('admin.custom_emojis.visible_in_picker')
|
||||
|
||||
.fields-group
|
||||
= f.input :aliases_raw, wrapper: :with_label, kmyblue: true, label: t('admin.custom_emojis.aliases'), hint: t('admin.custom_emojis.aliases_hint')
|
||||
|
||||
.fields-group
|
||||
= f.input :license, wrapper: :with_label, kmyblue: true, label: t('admin.custom_emojis.license'), hint: t('admin.custom_emojis.license_hint')
|
||||
|
||||
.actions
|
||||
= f.button :button, t('admin.custom_emojis.upload'), type: :submit
|
||||
|
|
44
app/views/admin/domain_blocks/_domain_block_list.html.haml
Normal file
44
app/views/admin/domain_blocks/_domain_block_list.html.haml
Normal file
|
@ -0,0 +1,44 @@
|
|||
%h4= I18n.t('admin.domain_blocks.headers.harassment')
|
||||
|
||||
.fields-group
|
||||
= f.input :reject_favourite, as: :boolean, kmyblue: true, wrapper: :with_label, label: I18n.t('admin.domain_blocks.reject_favourite'), hint: I18n.t('admin.domain_blocks.reject_favourite_hint')
|
||||
|
||||
.fields-group
|
||||
= f.input :reject_reply, as: :boolean, kmyblue: true, wrapper: :with_label, label: I18n.t('admin.domain_blocks.reject_reply'), hint: I18n.t('admin.domain_blocks.reject_reply_hint')
|
||||
|
||||
.fields-group
|
||||
= f.input :reject_reply_exclude_followers, as: :boolean, kmyblue: true, wrapper: :with_label, label: I18n.t('admin.domain_blocks.reject_reply_exclude_followers'), hint: I18n.t('admin.domain_blocks.reject_reply_exclude_followers_hint')
|
||||
|
||||
.fields-group
|
||||
= f.input :reject_hashtag, as: :boolean, kmyblue: true, wrapper: :with_label, label: I18n.t('admin.domain_blocks.reject_hashtag'), hint: I18n.t('admin.domain_blocks.reject_hashtag_hint')
|
||||
|
||||
.fields-group
|
||||
= f.input :reject_straight_follow, as: :boolean, kmyblue: true, wrapper: :with_label, label: I18n.t('admin.domain_blocks.reject_straight_follow'), hint: I18n.t('admin.domain_blocks.reject_straight_follow_hint')
|
||||
|
||||
.fields-group
|
||||
= f.input :reject_new_follow, as: :boolean, kmyblue: true, wrapper: :with_label, label: I18n.t('admin.domain_blocks.reject_new_follow'), hint: I18n.t('admin.domain_blocks.reject_new_follow_hint')
|
||||
|
||||
.fields-group
|
||||
= f.input :reject_friend, as: :boolean, kmyblue: true, wrapper: :with_label, label: I18n.t('admin.domain_blocks.reject_friend'), hint: I18n.t('admin.domain_blocks.reject_friend_hint')
|
||||
|
||||
%h4= I18n.t('admin.domain_blocks.headers.invalid_privacy')
|
||||
|
||||
.fields-group
|
||||
= f.input :reject_send_not_public_searchability, as: :boolean, kmyblue: true, wrapper: :with_label, label: I18n.t('admin.domain_blocks.reject_send_not_public_searchability'), hint: I18n.t('admin.domain_blocks.reject_send_not_public_searchability_hint')
|
||||
|
||||
.fields-group
|
||||
= f.input :reject_send_dissubscribable, as: :boolean, kmyblue: true, wrapper: :with_label, label: I18n.t('admin.domain_blocks.reject_send_dissubscribable'), hint: I18n.t('admin.domain_blocks.reject_send_dissubscribable_hint')
|
||||
|
||||
.fields-group
|
||||
= f.input :detect_invalid_subscription, as: :boolean, kmyblue: true, wrapper: :with_label, label: I18n.t('admin.domain_blocks.detect_invalid_subscription'), hint: I18n.t('admin.domain_blocks.detect_invalid_subscription_hint')
|
||||
|
||||
%h4= I18n.t('admin.domain_blocks.headers.disagreement')
|
||||
|
||||
.fields-group
|
||||
= f.input :reject_send_public_unlisted, as: :boolean, kmyblue: true, wrapper: :with_label, label: I18n.t('admin.domain_blocks.reject_send_public_unlisted'), hint: I18n.t('admin.domain_blocks.reject_send_public_unlisted_hint')
|
||||
|
||||
.fields-group
|
||||
= f.input :reject_send_media, as: :boolean, kmyblue: true, wrapper: :with_label, label: I18n.t('admin.domain_blocks.reject_send_media'), hint: I18n.t('admin.domain_blocks.reject_send_media_hint')
|
||||
|
||||
.fields-group
|
||||
= f.input :reject_send_sensitive, as: :boolean, kmyblue: true, wrapper: :with_label, label: I18n.t('admin.domain_blocks.reject_send_sensitive'), hint: I18n.t('admin.domain_blocks.reject_send_sensitive_hint')
|
Some files were not shown because too many files have changed in this diff Show more
Loading…
Add table
Add a link
Reference in a new issue