Merge pull request #2522 from ClearlyClaire/glitch-soc/merge-upstream
Merge upstream changes up to b2c5b20ef2
This commit is contained in:
commit
4fdbffc57c
311 changed files with 20721 additions and 15251 deletions
|
@ -4,7 +4,7 @@ FROM mcr.microsoft.com/devcontainers/ruby:1-3.2-bullseye
|
||||||
# Install Rails
|
# Install Rails
|
||||||
# RUN gem install rails webdrivers
|
# RUN gem install rails webdrivers
|
||||||
|
|
||||||
ARG NODE_VERSION="16"
|
ARG NODE_VERSION="20"
|
||||||
RUN su vscode -c "source /usr/local/share/nvm/nvm.sh && nvm install ${NODE_VERSION} 2>&1"
|
RUN su vscode -c "source /usr/local/share/nvm/nvm.sh && nvm install ${NODE_VERSION} 2>&1"
|
||||||
|
|
||||||
# [Optional] Uncomment this section to install additional OS packages.
|
# [Optional] Uncomment this section to install additional OS packages.
|
||||||
|
@ -15,6 +15,6 @@ RUN apt-get update && export DEBIAN_FRONTEND=noninteractive \
|
||||||
RUN gem install foreman
|
RUN gem install foreman
|
||||||
|
|
||||||
# [Optional] Uncomment this line to install global node packages.
|
# [Optional] Uncomment this line to install global node packages.
|
||||||
RUN su vscode -c "source /usr/local/share/nvm/nvm.sh && npm install -g yarn" 2>&1
|
RUN su vscode -c "source /usr/local/share/nvm/nvm.sh && corepack enable" 2>&1
|
||||||
|
|
||||||
COPY welcome-message.txt /usr/local/etc/vscode-dev-containers/first-run-notice.txt
|
COPY welcome-message.txt /usr/local/etc/vscode-dev-containers/first-run-notice.txt
|
||||||
|
|
|
@ -11,7 +11,8 @@ bundle install
|
||||||
git checkout -- Gemfile.lock
|
git checkout -- Gemfile.lock
|
||||||
|
|
||||||
# Fetch Javascript dependencies
|
# Fetch Javascript dependencies
|
||||||
yarn --frozen-lockfile
|
corepack prepare
|
||||||
|
yarn install --immutable
|
||||||
|
|
||||||
# [re]create, migrate, and seed the test database
|
# [re]create, migrate, and seed the test database
|
||||||
RAILS_ENV=test ./bin/rails db:setup
|
RAILS_ENV=test ./bin/rails db:setup
|
||||||
|
|
27
.github/actions/setup-javascript/action.yml
vendored
27
.github/actions/setup-javascript/action.yml
vendored
|
@ -11,9 +11,32 @@ runs:
|
||||||
- name: Set up Node.js
|
- name: Set up Node.js
|
||||||
uses: actions/setup-node@v3
|
uses: actions/setup-node@v3
|
||||||
with:
|
with:
|
||||||
cache: yarn
|
|
||||||
node-version-file: '.nvmrc'
|
node-version-file: '.nvmrc'
|
||||||
|
|
||||||
|
# The following is needed because we can not use `cache: true` for `setup-node`, as it does not support Corepack yet and mess up with the cache location if ran after Node is installed
|
||||||
|
- name: Enable corepack
|
||||||
|
shell: bash
|
||||||
|
run: corepack enable
|
||||||
|
|
||||||
|
- name: Get yarn cache directory path
|
||||||
|
id: yarn-cache-dir-path
|
||||||
|
shell: bash
|
||||||
|
run: echo "dir=$(yarn config get cacheFolder)" >> $GITHUB_OUTPUT
|
||||||
|
|
||||||
|
- uses: actions/cache@v3
|
||||||
|
id: yarn-cache # use this to check for `cache-hit` (`steps.yarn-cache.outputs.cache-hit != 'true'`)
|
||||||
|
with:
|
||||||
|
path: ${{ steps.yarn-cache-dir-path.outputs.dir }}
|
||||||
|
key: ${{ runner.os }}-yarn-${{ hashFiles('**/yarn.lock') }}
|
||||||
|
restore-keys: |
|
||||||
|
${{ runner.os }}-yarn-
|
||||||
|
|
||||||
- name: Install all yarn packages
|
- name: Install all yarn packages
|
||||||
shell: bash
|
shell: bash
|
||||||
run: yarn --frozen-lockfile ${{ inputs.onlyProduction != 'false' && '--production' || '' }}
|
run: yarn install --immutable
|
||||||
|
if: inputs.onlyProduction == 'false'
|
||||||
|
|
||||||
|
- name: Install all production yarn packages
|
||||||
|
shell: bash
|
||||||
|
run: yarn workspaces focus --production
|
||||||
|
if: inputs.onlyProduction != 'false'
|
||||||
|
|
1
.github/renovate.json5
vendored
1
.github/renovate.json5
vendored
|
@ -12,6 +12,7 @@
|
||||||
// If we do not want a package to be grouped with others, we need to set its groupName
|
// If we do not want a package to be grouped with others, we need to set its groupName
|
||||||
// to `null` after any other rule set it to something.
|
// to `null` after any other rule set it to something.
|
||||||
dependencyDashboardHeader: 'This issue lists Renovate updates and detected dependencies. Read the [Dependency Dashboard](https://docs.renovatebot.com/key-concepts/dashboard/) docs to learn more. Before approving any upgrade: read the description and comments in the [`renovate.json5` file](https://github.com/mastodon/mastodon/blob/main/.github/renovate.json5).',
|
dependencyDashboardHeader: 'This issue lists Renovate updates and detected dependencies. Read the [Dependency Dashboard](https://docs.renovatebot.com/key-concepts/dashboard/) docs to learn more. Before approving any upgrade: read the description and comments in the [`renovate.json5` file](https://github.com/mastodon/mastodon/blob/main/.github/renovate.json5).',
|
||||||
|
postUpdateOptions: ['yarnDedupeHighest'],
|
||||||
packageRules: [
|
packageRules: [
|
||||||
{
|
{
|
||||||
// Require Dependency Dashboard Approval for major version bumps of these node packages
|
// Require Dependency Dashboard Approval for major version bumps of these node packages
|
||||||
|
|
9
.gitignore
vendored
9
.gitignore
vendored
|
@ -55,6 +55,15 @@ npm-debug.log
|
||||||
yarn-error.log
|
yarn-error.log
|
||||||
yarn-debug.log
|
yarn-debug.log
|
||||||
|
|
||||||
|
# From https://yarnpkg.com/getting-started/qa#which-files-should-be-gitignored
|
||||||
|
.pnp.*
|
||||||
|
.yarn/*
|
||||||
|
!.yarn/patches
|
||||||
|
!.yarn/plugins
|
||||||
|
!.yarn/releases
|
||||||
|
!.yarn/sdks
|
||||||
|
!.yarn/versions
|
||||||
|
|
||||||
# Ignore vagrant log files
|
# Ignore vagrant log files
|
||||||
*-cloudimg-console.log
|
*-cloudimg-console.log
|
||||||
|
|
||||||
|
|
|
@ -31,4 +31,3 @@ linters:
|
||||||
- 'app/views/admin/accounts/_buttons.html.haml'
|
- 'app/views/admin/accounts/_buttons.html.haml'
|
||||||
- 'app/views/admin/accounts/_local_account.html.haml'
|
- 'app/views/admin/accounts/_local_account.html.haml'
|
||||||
- 'app/views/admin/roles/_form.html.haml'
|
- 'app/views/admin/roles/_form.html.haml'
|
||||||
- 'app/views/layouts/application.html.haml'
|
|
||||||
|
|
|
@ -24,15 +24,6 @@ Lint/NonLocalExitFromIterator:
|
||||||
Exclude:
|
Exclude:
|
||||||
- 'app/helpers/jsonld_helper.rb'
|
- 'app/helpers/jsonld_helper.rb'
|
||||||
|
|
||||||
# This cop supports safe autocorrection (--autocorrect).
|
|
||||||
# Configuration parameters: IgnoreEmptyBlocks, AllowUnusedKeywordArguments.
|
|
||||||
Lint/UnusedBlockArgument:
|
|
||||||
Exclude:
|
|
||||||
- 'config/initializers/content_security_policy.rb'
|
|
||||||
- 'config/initializers/doorkeeper.rb'
|
|
||||||
- 'config/initializers/paperclip.rb'
|
|
||||||
- 'config/initializers/simple_form.rb'
|
|
||||||
|
|
||||||
# Configuration parameters: AllowedMethods, AllowedPatterns, CountRepeatedAttributes.
|
# Configuration parameters: AllowedMethods, AllowedPatterns, CountRepeatedAttributes.
|
||||||
Metrics/AbcSize:
|
Metrics/AbcSize:
|
||||||
Max: 144
|
Max: 144
|
||||||
|
@ -52,48 +43,10 @@ Metrics/CyclomaticComplexity:
|
||||||
Metrics/PerceivedComplexity:
|
Metrics/PerceivedComplexity:
|
||||||
Max: 27
|
Max: 27
|
||||||
|
|
||||||
RSpec/AnyInstance:
|
|
||||||
Exclude:
|
|
||||||
- 'spec/controllers/activitypub/inboxes_controller_spec.rb'
|
|
||||||
- 'spec/controllers/admin/accounts_controller_spec.rb'
|
|
||||||
- 'spec/controllers/admin/resets_controller_spec.rb'
|
|
||||||
- 'spec/controllers/admin/settings/branding_controller_spec.rb'
|
|
||||||
- 'spec/controllers/auth/sessions_controller_spec.rb'
|
|
||||||
- 'spec/controllers/settings/two_factor_authentication/confirmations_controller_spec.rb'
|
|
||||||
- 'spec/controllers/settings/two_factor_authentication/recovery_codes_controller_spec.rb'
|
|
||||||
- 'spec/lib/request_spec.rb'
|
|
||||||
- 'spec/lib/status_filter_spec.rb'
|
|
||||||
- 'spec/models/account_spec.rb'
|
|
||||||
- 'spec/models/setting_spec.rb'
|
|
||||||
- 'spec/services/activitypub/process_collection_service_spec.rb'
|
|
||||||
- 'spec/validators/follow_limit_validator_spec.rb'
|
|
||||||
- 'spec/workers/activitypub/delivery_worker_spec.rb'
|
|
||||||
- 'spec/workers/web/push_notification_worker_spec.rb'
|
|
||||||
|
|
||||||
# Configuration parameters: CountAsOne.
|
# Configuration parameters: CountAsOne.
|
||||||
RSpec/ExampleLength:
|
RSpec/ExampleLength:
|
||||||
Max: 22
|
Max: 22
|
||||||
|
|
||||||
# Configuration parameters: AssignmentOnly.
|
|
||||||
RSpec/InstanceVariable:
|
|
||||||
Exclude:
|
|
||||||
- 'spec/controllers/api/v1/streaming_controller_spec.rb'
|
|
||||||
- 'spec/controllers/auth/confirmations_controller_spec.rb'
|
|
||||||
- 'spec/controllers/auth/passwords_controller_spec.rb'
|
|
||||||
- 'spec/controllers/auth/sessions_controller_spec.rb'
|
|
||||||
- 'spec/controllers/concerns/export_controller_concern_spec.rb'
|
|
||||||
- 'spec/controllers/home_controller_spec.rb'
|
|
||||||
- 'spec/controllers/settings/two_factor_authentication/webauthn_credentials_controller_spec.rb'
|
|
||||||
- 'spec/controllers/statuses_cleanup_controller_spec.rb'
|
|
||||||
- 'spec/models/concerns/account_finder_concern_spec.rb'
|
|
||||||
- 'spec/models/concerns/account_interactions_spec.rb'
|
|
||||||
- 'spec/models/public_feed_spec.rb'
|
|
||||||
- 'spec/serializers/activitypub/note_serializer_spec.rb'
|
|
||||||
- 'spec/serializers/activitypub/update_poll_serializer_spec.rb'
|
|
||||||
- 'spec/services/remove_status_service_spec.rb'
|
|
||||||
- 'spec/services/search_service_spec.rb'
|
|
||||||
- 'spec/services/unblock_domain_service_spec.rb'
|
|
||||||
|
|
||||||
RSpec/LetSetup:
|
RSpec/LetSetup:
|
||||||
Exclude:
|
Exclude:
|
||||||
- 'spec/controllers/api/v1/accounts/statuses_controller_spec.rb'
|
- 'spec/controllers/api/v1/accounts/statuses_controller_spec.rb'
|
||||||
|
@ -136,12 +89,6 @@ RSpec/LetSetup:
|
||||||
- 'spec/services/unsuspend_account_service_spec.rb'
|
- 'spec/services/unsuspend_account_service_spec.rb'
|
||||||
- 'spec/workers/scheduler/user_cleanup_scheduler_spec.rb'
|
- 'spec/workers/scheduler/user_cleanup_scheduler_spec.rb'
|
||||||
|
|
||||||
RSpec/MessageChain:
|
|
||||||
Exclude:
|
|
||||||
- 'spec/models/concerns/remotable_spec.rb'
|
|
||||||
- 'spec/models/session_activation_spec.rb'
|
|
||||||
- 'spec/models/setting_spec.rb'
|
|
||||||
|
|
||||||
RSpec/MultipleExpectations:
|
RSpec/MultipleExpectations:
|
||||||
Max: 8
|
Max: 8
|
||||||
|
|
||||||
|
@ -181,11 +128,6 @@ Rails/HasManyOrHasOneDependent:
|
||||||
- 'app/models/user.rb'
|
- 'app/models/user.rb'
|
||||||
- 'app/models/web/push_subscription.rb'
|
- 'app/models/web/push_subscription.rb'
|
||||||
|
|
||||||
Rails/I18nLocaleTexts:
|
|
||||||
Exclude:
|
|
||||||
- 'lib/tasks/mastodon.rake'
|
|
||||||
- 'spec/helpers/flashes_helper_spec.rb'
|
|
||||||
|
|
||||||
# Configuration parameters: Include.
|
# Configuration parameters: Include.
|
||||||
# Include: app/controllers/**/*.rb, app/mailers/**/*.rb
|
# Include: app/controllers/**/*.rb, app/mailers/**/*.rb
|
||||||
Rails/LexicallyScopedActionFilter:
|
Rails/LexicallyScopedActionFilter:
|
||||||
|
@ -561,14 +503,6 @@ Style/SingleArgumentDig:
|
||||||
Exclude:
|
Exclude:
|
||||||
- 'lib/webpacker/manifest_extensions.rb'
|
- 'lib/webpacker/manifest_extensions.rb'
|
||||||
|
|
||||||
# This cop supports safe autocorrection (--autocorrect).
|
|
||||||
# Configuration parameters: EnforcedStyle.
|
|
||||||
# SupportedStyles: require_parentheses, require_no_parentheses
|
|
||||||
Style/StabbyLambdaParentheses:
|
|
||||||
Exclude:
|
|
||||||
- 'config/environments/production.rb'
|
|
||||||
- 'config/initializers/content_security_policy.rb'
|
|
||||||
|
|
||||||
# This cop supports safe autocorrection (--autocorrect).
|
# This cop supports safe autocorrection (--autocorrect).
|
||||||
Style/StderrPuts:
|
Style/StderrPuts:
|
||||||
Exclude:
|
Exclude:
|
||||||
|
@ -627,5 +561,3 @@ Style/TrailingCommaInHashLiteral:
|
||||||
Style/WordArray:
|
Style/WordArray:
|
||||||
Exclude:
|
Exclude:
|
||||||
- 'app/helpers/languages_helper.rb'
|
- 'app/helpers/languages_helper.rb'
|
||||||
- 'spec/controllers/settings/imports_controller_spec.rb'
|
|
||||||
- 'spec/models/form/import_spec.rb'
|
|
||||||
|
|
3
.watchmanconfig
Normal file
3
.watchmanconfig
Normal file
|
@ -0,0 +1,3 @@
|
||||||
|
{
|
||||||
|
"ignore_dirs": ["node_modules/", "public/"]
|
||||||
|
}
|
0
.yarn/.gitkeep
Normal file
0
.yarn/.gitkeep
Normal file
13
.yarn/patches/babel-plugin-lodash-npm-3.3.4-c7161075b6.patch
Normal file
13
.yarn/patches/babel-plugin-lodash-npm-3.3.4-c7161075b6.patch
Normal file
|
@ -0,0 +1,13 @@
|
||||||
|
diff --git a/lib/index.js b/lib/index.js
|
||||||
|
index 16ed6be8be8f555cc99096c2ff60954b42dc313d..d009c069770d066ad0db7ad02de1ea473a29334e 100644
|
||||||
|
--- a/lib/index.js
|
||||||
|
+++ b/lib/index.js
|
||||||
|
@@ -99,7 +99,7 @@ function lodash(_ref) {
|
||||||
|
|
||||||
|
var node = _ref3;
|
||||||
|
|
||||||
|
- if ((0, _types.isModuleDeclaration)(node)) {
|
||||||
|
+ if ((0, _types.isImportDeclaration)(node) || (0, _types.isExportDeclaration)(node)) {
|
||||||
|
isModule = true;
|
||||||
|
break;
|
||||||
|
}
|
|
@ -0,0 +1,22 @@
|
||||||
|
diff --git a/dist/index.js b/dist/index.js
|
||||||
|
index 57e375592d984e9a429bcd9f800fa2d15cd662e4..0c47d96df3608e23adfd77d887a8f72abbd501c0 100644
|
||||||
|
--- a/dist/index.js
|
||||||
|
+++ b/dist/index.js
|
||||||
|
@@ -5,7 +5,7 @@ Object.defineProperty(exports, "__esModule", {
|
||||||
|
});
|
||||||
|
exports.default = void 0;
|
||||||
|
|
||||||
|
-var _crypto = _interopRequireDefault(require("crypto"));
|
||||||
|
+var _createHash = _interopRequireDefault(require("webpack/lib/util/createHash"));
|
||||||
|
|
||||||
|
var _path = _interopRequireDefault(require("path"));
|
||||||
|
|
||||||
|
@@ -227,7 +227,7 @@ class CompressionPlugin {
|
||||||
|
originalAlgorithm: this.options.algorithm,
|
||||||
|
compressionOptions: this.options.compressionOptions,
|
||||||
|
name,
|
||||||
|
- contentHash: _crypto.default.createHash("md4").update(input).digest("hex")
|
||||||
|
+ contentHash: _createHash.default("md4").update(input).digest("hex")
|
||||||
|
};
|
||||||
|
} else {
|
||||||
|
cacheData.name = (0, _serializeJavascript.default)({
|
49
.yarnclean
49
.yarnclean
|
@ -1,49 +0,0 @@
|
||||||
# test directories
|
|
||||||
__tests__
|
|
||||||
test
|
|
||||||
tests
|
|
||||||
powered-test
|
|
||||||
|
|
||||||
# asset directories
|
|
||||||
docs
|
|
||||||
doc
|
|
||||||
website
|
|
||||||
images
|
|
||||||
# assets
|
|
||||||
|
|
||||||
# examples
|
|
||||||
example
|
|
||||||
examples
|
|
||||||
|
|
||||||
# code coverage directories
|
|
||||||
coverage
|
|
||||||
.nyc_output
|
|
||||||
|
|
||||||
# build scripts
|
|
||||||
Makefile
|
|
||||||
Gulpfile.js
|
|
||||||
Gruntfile.js
|
|
||||||
|
|
||||||
# configs
|
|
||||||
.tern-project
|
|
||||||
.gitattributes
|
|
||||||
.editorconfig
|
|
||||||
.*ignore
|
|
||||||
.eslintrc
|
|
||||||
.jshintrc
|
|
||||||
.flowconfig
|
|
||||||
.documentup.json
|
|
||||||
.yarn-metadata.json
|
|
||||||
.*.yml
|
|
||||||
*.yml
|
|
||||||
|
|
||||||
# misc
|
|
||||||
*.gz
|
|
||||||
*.md
|
|
||||||
|
|
||||||
# for specific ignore
|
|
||||||
!.svgo.yml
|
|
||||||
!sass-lint/**/*.yml
|
|
||||||
|
|
||||||
# breaks lint-staged or generally anything using https://github.com/eemeli/yaml/issues/384
|
|
||||||
!**/yaml/dist/**/doc
|
|
1
.yarnrc.yml
Normal file
1
.yarnrc.yml
Normal file
|
@ -0,0 +1 @@
|
||||||
|
nodeLinker: node-modules
|
14
Dockerfile
14
Dockerfile
|
@ -13,7 +13,6 @@ ENV DEBIAN_FRONTEND="noninteractive" \
|
||||||
SHELL ["/bin/bash", "-o", "pipefail", "-c"]
|
SHELL ["/bin/bash", "-o", "pipefail", "-c"]
|
||||||
|
|
||||||
WORKDIR /opt/mastodon
|
WORKDIR /opt/mastodon
|
||||||
COPY Gemfile* package.json yarn.lock /opt/mastodon/
|
|
||||||
|
|
||||||
# hadolint ignore=DL3008
|
# hadolint ignore=DL3008
|
||||||
RUN apt-get update && \
|
RUN apt-get update && \
|
||||||
|
@ -36,8 +35,14 @@ RUN apt-get update && \
|
||||||
bundle config set --local deployment 'true' && \
|
bundle config set --local deployment 'true' && \
|
||||||
bundle config set --local without 'development test' && \
|
bundle config set --local without 'development test' && \
|
||||||
bundle config set silence_root_warning true && \
|
bundle config set silence_root_warning true && \
|
||||||
bundle install -j"$(nproc)" && \
|
corepack enable
|
||||||
yarn install --pure-lockfile --production --network-timeout 600000 && \
|
|
||||||
|
COPY Gemfile* package.json yarn.lock .yarnrc.yml /opt/mastodon/
|
||||||
|
COPY .yarn /opt/mastodon/.yarn
|
||||||
|
|
||||||
|
RUN bundle install -j"$(nproc)"
|
||||||
|
|
||||||
|
RUN yarn workspaces focus --all --production && \
|
||||||
yarn cache clean
|
yarn cache clean
|
||||||
|
|
||||||
FROM node:${NODE_VERSION}
|
FROM node:${NODE_VERSION}
|
||||||
|
@ -78,7 +83,8 @@ RUN apt-get update && \
|
||||||
tzdata \
|
tzdata \
|
||||||
libreadline8 \
|
libreadline8 \
|
||||||
tini && \
|
tini && \
|
||||||
ln -s /opt/mastodon /mastodon
|
ln -s /opt/mastodon /mastodon && \
|
||||||
|
corepack enable
|
||||||
|
|
||||||
# Note: no, cleaning here since Debian does this automatically
|
# Note: no, cleaning here since Debian does this automatically
|
||||||
# See the file /etc/apt/apt.conf.d/docker-clean within the Docker image's filesystem
|
# See the file /etc/apt/apt.conf.d/docker-clean within the Docker image's filesystem
|
||||||
|
|
6
Gemfile
6
Gemfile
|
@ -16,7 +16,7 @@ gem 'dotenv-rails', '~> 2.8'
|
||||||
|
|
||||||
gem 'aws-sdk-s3', '~> 1.123', require: false
|
gem 'aws-sdk-s3', '~> 1.123', require: false
|
||||||
gem 'fog-core', '<= 2.4.0'
|
gem 'fog-core', '<= 2.4.0'
|
||||||
gem 'fog-openstack', '~> 0.3', require: false
|
gem 'fog-openstack', '~> 1.0', require: false
|
||||||
gem 'kt-paperclip', '~> 7.2'
|
gem 'kt-paperclip', '~> 7.2'
|
||||||
gem 'md-paperclip-azure', '~> 2.2', require: false
|
gem 'md-paperclip-azure', '~> 2.2', require: false
|
||||||
gem 'blurhash', '~> 0.1'
|
gem 'blurhash', '~> 0.1'
|
||||||
|
@ -88,7 +88,7 @@ gem 'simple-navigation', '~> 4.4'
|
||||||
gem 'simple_form', '~> 5.2'
|
gem 'simple_form', '~> 5.2'
|
||||||
gem 'sprockets-rails', '~> 3.4', require: 'sprockets/railtie'
|
gem 'sprockets-rails', '~> 3.4', require: 'sprockets/railtie'
|
||||||
gem 'stoplight', '~> 3.0.1'
|
gem 'stoplight', '~> 3.0.1'
|
||||||
gem 'strong_migrations', '1.3.0'
|
gem 'strong_migrations', '1.6.4'
|
||||||
gem 'tty-prompt', '~> 0.23', require: false
|
gem 'tty-prompt', '~> 0.23', require: false
|
||||||
gem 'twitter-text', '~> 3.1.0'
|
gem 'twitter-text', '~> 3.1.0'
|
||||||
gem 'tzinfo-data', '~> 1.2023'
|
gem 'tzinfo-data', '~> 1.2023'
|
||||||
|
@ -195,7 +195,7 @@ gem 'xorcist', '~> 1.1'
|
||||||
|
|
||||||
gem 'cocoon', '~> 1.2'
|
gem 'cocoon', '~> 1.2'
|
||||||
|
|
||||||
gem 'net-http', '~> 0.3.2'
|
gem 'net-http', '~> 0.4.0'
|
||||||
gem 'rubyzip', '~> 2.3'
|
gem 'rubyzip', '~> 2.3'
|
||||||
|
|
||||||
gem 'hcaptcha', '~> 7.1'
|
gem 'hcaptcha', '~> 7.1'
|
||||||
|
|
175
Gemfile.lock
175
Gemfile.lock
|
@ -39,50 +39,51 @@ GIT
|
||||||
GEM
|
GEM
|
||||||
remote: https://rubygems.org/
|
remote: https://rubygems.org/
|
||||||
specs:
|
specs:
|
||||||
actioncable (7.1.1)
|
actioncable (7.1.2)
|
||||||
actionpack (= 7.1.1)
|
actionpack (= 7.1.2)
|
||||||
activesupport (= 7.1.1)
|
activesupport (= 7.1.2)
|
||||||
nio4r (~> 2.0)
|
nio4r (~> 2.0)
|
||||||
websocket-driver (>= 0.6.1)
|
websocket-driver (>= 0.6.1)
|
||||||
zeitwerk (~> 2.6)
|
zeitwerk (~> 2.6)
|
||||||
actionmailbox (7.1.1)
|
actionmailbox (7.1.2)
|
||||||
actionpack (= 7.1.1)
|
actionpack (= 7.1.2)
|
||||||
activejob (= 7.1.1)
|
activejob (= 7.1.2)
|
||||||
activerecord (= 7.1.1)
|
activerecord (= 7.1.2)
|
||||||
activestorage (= 7.1.1)
|
activestorage (= 7.1.2)
|
||||||
activesupport (= 7.1.1)
|
activesupport (= 7.1.2)
|
||||||
mail (>= 2.7.1)
|
mail (>= 2.7.1)
|
||||||
net-imap
|
net-imap
|
||||||
net-pop
|
net-pop
|
||||||
net-smtp
|
net-smtp
|
||||||
actionmailer (7.1.1)
|
actionmailer (7.1.2)
|
||||||
actionpack (= 7.1.1)
|
actionpack (= 7.1.2)
|
||||||
actionview (= 7.1.1)
|
actionview (= 7.1.2)
|
||||||
activejob (= 7.1.1)
|
activejob (= 7.1.2)
|
||||||
activesupport (= 7.1.1)
|
activesupport (= 7.1.2)
|
||||||
mail (~> 2.5, >= 2.5.4)
|
mail (~> 2.5, >= 2.5.4)
|
||||||
net-imap
|
net-imap
|
||||||
net-pop
|
net-pop
|
||||||
net-smtp
|
net-smtp
|
||||||
rails-dom-testing (~> 2.2)
|
rails-dom-testing (~> 2.2)
|
||||||
actionpack (7.1.1)
|
actionpack (7.1.2)
|
||||||
actionview (= 7.1.1)
|
actionview (= 7.1.2)
|
||||||
activesupport (= 7.1.1)
|
activesupport (= 7.1.2)
|
||||||
nokogiri (>= 1.8.5)
|
nokogiri (>= 1.8.5)
|
||||||
|
racc
|
||||||
rack (>= 2.2.4)
|
rack (>= 2.2.4)
|
||||||
rack-session (>= 1.0.1)
|
rack-session (>= 1.0.1)
|
||||||
rack-test (>= 0.6.3)
|
rack-test (>= 0.6.3)
|
||||||
rails-dom-testing (~> 2.2)
|
rails-dom-testing (~> 2.2)
|
||||||
rails-html-sanitizer (~> 1.6)
|
rails-html-sanitizer (~> 1.6)
|
||||||
actiontext (7.1.1)
|
actiontext (7.1.2)
|
||||||
actionpack (= 7.1.1)
|
actionpack (= 7.1.2)
|
||||||
activerecord (= 7.1.1)
|
activerecord (= 7.1.2)
|
||||||
activestorage (= 7.1.1)
|
activestorage (= 7.1.2)
|
||||||
activesupport (= 7.1.1)
|
activesupport (= 7.1.2)
|
||||||
globalid (>= 0.6.0)
|
globalid (>= 0.6.0)
|
||||||
nokogiri (>= 1.8.5)
|
nokogiri (>= 1.8.5)
|
||||||
actionview (7.1.1)
|
actionview (7.1.2)
|
||||||
activesupport (= 7.1.1)
|
activesupport (= 7.1.2)
|
||||||
builder (~> 3.1)
|
builder (~> 3.1)
|
||||||
erubi (~> 1.11)
|
erubi (~> 1.11)
|
||||||
rails-dom-testing (~> 2.2)
|
rails-dom-testing (~> 2.2)
|
||||||
|
@ -92,22 +93,22 @@ GEM
|
||||||
activemodel (>= 4.1)
|
activemodel (>= 4.1)
|
||||||
case_transform (>= 0.2)
|
case_transform (>= 0.2)
|
||||||
jsonapi-renderer (>= 0.1.1.beta1, < 0.3)
|
jsonapi-renderer (>= 0.1.1.beta1, < 0.3)
|
||||||
activejob (7.1.1)
|
activejob (7.1.2)
|
||||||
activesupport (= 7.1.1)
|
activesupport (= 7.1.2)
|
||||||
globalid (>= 0.3.6)
|
globalid (>= 0.3.6)
|
||||||
activemodel (7.1.1)
|
activemodel (7.1.2)
|
||||||
activesupport (= 7.1.1)
|
activesupport (= 7.1.2)
|
||||||
activerecord (7.1.1)
|
activerecord (7.1.2)
|
||||||
activemodel (= 7.1.1)
|
activemodel (= 7.1.2)
|
||||||
activesupport (= 7.1.1)
|
activesupport (= 7.1.2)
|
||||||
timeout (>= 0.4.0)
|
timeout (>= 0.4.0)
|
||||||
activestorage (7.1.1)
|
activestorage (7.1.2)
|
||||||
actionpack (= 7.1.1)
|
actionpack (= 7.1.2)
|
||||||
activejob (= 7.1.1)
|
activejob (= 7.1.2)
|
||||||
activerecord (= 7.1.1)
|
activerecord (= 7.1.2)
|
||||||
activesupport (= 7.1.1)
|
activesupport (= 7.1.2)
|
||||||
marcel (~> 1.0)
|
marcel (~> 1.0)
|
||||||
activesupport (7.1.1)
|
activesupport (7.1.2)
|
||||||
base64
|
base64
|
||||||
bigdecimal
|
bigdecimal
|
||||||
concurrent-ruby (~> 1.0, >= 1.0.2)
|
concurrent-ruby (~> 1.0, >= 1.0.2)
|
||||||
|
@ -130,8 +131,8 @@ GEM
|
||||||
attr_required (1.0.1)
|
attr_required (1.0.1)
|
||||||
awrence (1.2.1)
|
awrence (1.2.1)
|
||||||
aws-eventstream (1.2.0)
|
aws-eventstream (1.2.0)
|
||||||
aws-partitions (1.809.0)
|
aws-partitions (1.828.0)
|
||||||
aws-sdk-core (3.181.0)
|
aws-sdk-core (3.183.1)
|
||||||
aws-eventstream (~> 1, >= 1.0.2)
|
aws-eventstream (~> 1, >= 1.0.2)
|
||||||
aws-partitions (~> 1, >= 1.651.0)
|
aws-partitions (~> 1, >= 1.651.0)
|
||||||
aws-sigv4 (~> 1.5)
|
aws-sigv4 (~> 1.5)
|
||||||
|
@ -139,7 +140,7 @@ GEM
|
||||||
aws-sdk-kms (1.71.0)
|
aws-sdk-kms (1.71.0)
|
||||||
aws-sdk-core (~> 3, >= 3.177.0)
|
aws-sdk-core (~> 3, >= 3.177.0)
|
||||||
aws-sigv4 (~> 1.1)
|
aws-sigv4 (~> 1.1)
|
||||||
aws-sdk-s3 (1.133.0)
|
aws-sdk-s3 (1.136.0)
|
||||||
aws-sdk-core (~> 3, >= 3.181.0)
|
aws-sdk-core (~> 3, >= 3.181.0)
|
||||||
aws-sdk-kms (~> 1)
|
aws-sdk-kms (~> 1)
|
||||||
aws-sigv4 (~> 1.6)
|
aws-sigv4 (~> 1.6)
|
||||||
|
@ -218,7 +219,7 @@ GEM
|
||||||
activerecord (>= 5.a)
|
activerecord (>= 5.a)
|
||||||
database_cleaner-core (~> 2.0.0)
|
database_cleaner-core (~> 2.0.0)
|
||||||
database_cleaner-core (2.0.1)
|
database_cleaner-core (2.0.1)
|
||||||
date (3.3.3)
|
date (3.3.4)
|
||||||
debug_inspector (1.1.0)
|
debug_inspector (1.1.0)
|
||||||
devise (4.9.3)
|
devise (4.9.3)
|
||||||
bcrypt (~> 3.0)
|
bcrypt (~> 3.0)
|
||||||
|
@ -263,7 +264,7 @@ GEM
|
||||||
erubi (1.12.0)
|
erubi (1.12.0)
|
||||||
et-orbi (1.2.7)
|
et-orbi (1.2.7)
|
||||||
tzinfo
|
tzinfo
|
||||||
excon (0.100.0)
|
excon (0.104.0)
|
||||||
fabrication (2.30.0)
|
fabrication (2.30.0)
|
||||||
faker (3.2.2)
|
faker (3.2.2)
|
||||||
i18n (>= 1.8.11, < 2)
|
i18n (>= 1.8.11, < 2)
|
||||||
|
@ -298,19 +299,18 @@ GEM
|
||||||
ffi-compiler (1.0.1)
|
ffi-compiler (1.0.1)
|
||||||
ffi (>= 1.0.0)
|
ffi (>= 1.0.0)
|
||||||
rake
|
rake
|
||||||
fog-core (2.1.0)
|
fog-core (2.3.0)
|
||||||
builder
|
builder
|
||||||
excon (~> 0.58)
|
excon (~> 0.71)
|
||||||
formatador (~> 0.2)
|
formatador (>= 0.2, < 2.0)
|
||||||
mime-types
|
mime-types
|
||||||
fog-json (1.2.0)
|
fog-json (1.2.0)
|
||||||
fog-core
|
fog-core
|
||||||
multi_json (~> 1.10)
|
multi_json (~> 1.10)
|
||||||
fog-openstack (0.3.10)
|
fog-openstack (1.1.0)
|
||||||
fog-core (>= 1.45, <= 2.1.0)
|
fog-core (~> 2.1)
|
||||||
fog-json (>= 1.0)
|
fog-json (>= 1.0)
|
||||||
ipaddress (>= 0.8)
|
formatador (1.1.0)
|
||||||
formatador (0.3.0)
|
|
||||||
fugit (1.8.1)
|
fugit (1.8.1)
|
||||||
et-orbi (~> 1, >= 1.2.7)
|
et-orbi (~> 1, >= 1.2.7)
|
||||||
raabro (~> 1.4)
|
raabro (~> 1.4)
|
||||||
|
@ -370,8 +370,7 @@ GEM
|
||||||
terminal-table (>= 1.5.1)
|
terminal-table (>= 1.5.1)
|
||||||
idn-ruby (0.1.5)
|
idn-ruby (0.1.5)
|
||||||
io-console (0.6.0)
|
io-console (0.6.0)
|
||||||
ipaddress (0.8.3)
|
irb (1.8.3)
|
||||||
irb (1.8.1)
|
|
||||||
rdoc
|
rdoc
|
||||||
reline (>= 0.3.8)
|
reline (>= 0.3.8)
|
||||||
jmespath (1.6.2)
|
jmespath (1.6.2)
|
||||||
|
@ -389,10 +388,10 @@ GEM
|
||||||
multi_json (~> 1.15)
|
multi_json (~> 1.15)
|
||||||
rack (>= 2.2, < 4)
|
rack (>= 2.2, < 4)
|
||||||
rdf (~> 3.3)
|
rdf (~> 3.3)
|
||||||
json-ld-preloaded (3.2.2)
|
json-ld-preloaded (3.3.0)
|
||||||
json-ld (~> 3.2)
|
json-ld (~> 3.3)
|
||||||
rdf (~> 3.2)
|
rdf (~> 3.3)
|
||||||
json-schema (4.0.0)
|
json-schema (4.1.1)
|
||||||
addressable (>= 2.8)
|
addressable (>= 2.8)
|
||||||
jsonapi-renderer (0.2.2)
|
jsonapi-renderer (0.2.2)
|
||||||
jwt (2.7.1)
|
jwt (2.7.1)
|
||||||
|
@ -452,25 +451,25 @@ GEM
|
||||||
memory_profiler (1.0.1)
|
memory_profiler (1.0.1)
|
||||||
mime-types (3.5.1)
|
mime-types (3.5.1)
|
||||||
mime-types-data (~> 3.2015)
|
mime-types-data (~> 3.2015)
|
||||||
mime-types-data (3.2023.0808)
|
mime-types-data (3.2023.1003)
|
||||||
mini_mime (1.1.5)
|
mini_mime (1.1.5)
|
||||||
mini_portile2 (2.8.4)
|
mini_portile2 (2.8.5)
|
||||||
minitest (5.20.0)
|
minitest (5.20.0)
|
||||||
msgpack (1.7.2)
|
msgpack (1.7.2)
|
||||||
multi_json (1.15.0)
|
multi_json (1.15.0)
|
||||||
multipart-post (2.3.0)
|
multipart-post (2.3.0)
|
||||||
mutex_m (0.1.2)
|
mutex_m (0.1.2)
|
||||||
net-http (0.3.2)
|
net-http (0.4.0)
|
||||||
uri
|
uri
|
||||||
net-http-persistent (4.0.2)
|
net-http-persistent (4.0.2)
|
||||||
connection_pool (~> 2.2)
|
connection_pool (~> 2.2)
|
||||||
net-imap (0.4.1)
|
net-imap (0.4.4)
|
||||||
date
|
date
|
||||||
net-protocol
|
net-protocol
|
||||||
net-ldap (0.18.0)
|
net-ldap (0.18.0)
|
||||||
net-pop (0.1.2)
|
net-pop (0.1.2)
|
||||||
net-protocol
|
net-protocol
|
||||||
net-protocol (0.2.1)
|
net-protocol (0.2.2)
|
||||||
timeout
|
timeout
|
||||||
net-smtp (0.4.0)
|
net-smtp (0.4.0)
|
||||||
net-protocol
|
net-protocol
|
||||||
|
@ -528,7 +527,7 @@ GEM
|
||||||
net-smtp
|
net-smtp
|
||||||
premailer (~> 1.7, >= 1.7.9)
|
premailer (~> 1.7, >= 1.7.9)
|
||||||
private_address_check (0.5.0)
|
private_address_check (0.5.0)
|
||||||
psych (5.1.1)
|
psych (5.1.1.1)
|
||||||
stringio
|
stringio
|
||||||
public_suffix (5.0.3)
|
public_suffix (5.0.3)
|
||||||
puma (6.4.0)
|
puma (6.4.0)
|
||||||
|
@ -559,20 +558,20 @@ GEM
|
||||||
rackup (1.0.0)
|
rackup (1.0.0)
|
||||||
rack (< 3)
|
rack (< 3)
|
||||||
webrick
|
webrick
|
||||||
rails (7.1.1)
|
rails (7.1.2)
|
||||||
actioncable (= 7.1.1)
|
actioncable (= 7.1.2)
|
||||||
actionmailbox (= 7.1.1)
|
actionmailbox (= 7.1.2)
|
||||||
actionmailer (= 7.1.1)
|
actionmailer (= 7.1.2)
|
||||||
actionpack (= 7.1.1)
|
actionpack (= 7.1.2)
|
||||||
actiontext (= 7.1.1)
|
actiontext (= 7.1.2)
|
||||||
actionview (= 7.1.1)
|
actionview (= 7.1.2)
|
||||||
activejob (= 7.1.1)
|
activejob (= 7.1.2)
|
||||||
activemodel (= 7.1.1)
|
activemodel (= 7.1.2)
|
||||||
activerecord (= 7.1.1)
|
activerecord (= 7.1.2)
|
||||||
activestorage (= 7.1.1)
|
activestorage (= 7.1.2)
|
||||||
activesupport (= 7.1.1)
|
activesupport (= 7.1.2)
|
||||||
bundler (>= 1.15.0)
|
bundler (>= 1.15.0)
|
||||||
railties (= 7.1.1)
|
railties (= 7.1.2)
|
||||||
rails-controller-testing (1.0.5)
|
rails-controller-testing (1.0.5)
|
||||||
actionpack (>= 5.0.1.rc1)
|
actionpack (>= 5.0.1.rc1)
|
||||||
actionview (>= 5.0.1.rc1)
|
actionview (>= 5.0.1.rc1)
|
||||||
|
@ -587,9 +586,9 @@ GEM
|
||||||
rails-i18n (7.0.8)
|
rails-i18n (7.0.8)
|
||||||
i18n (>= 0.7, < 2)
|
i18n (>= 0.7, < 2)
|
||||||
railties (>= 6.0.0, < 8)
|
railties (>= 6.0.0, < 8)
|
||||||
railties (7.1.1)
|
railties (7.1.2)
|
||||||
actionpack (= 7.1.1)
|
actionpack (= 7.1.2)
|
||||||
activesupport (= 7.1.1)
|
activesupport (= 7.1.2)
|
||||||
irb
|
irb
|
||||||
rackup (>= 1.0.0)
|
rackup (>= 1.0.0)
|
||||||
rake (>= 12.2)
|
rake (>= 12.2)
|
||||||
|
@ -689,13 +688,13 @@ GEM
|
||||||
fugit (~> 1.1, >= 1.1.6)
|
fugit (~> 1.1, >= 1.1.6)
|
||||||
safety_net_attestation (0.4.0)
|
safety_net_attestation (0.4.0)
|
||||||
jwt (~> 2.0)
|
jwt (~> 2.0)
|
||||||
sanitize (6.0.2)
|
sanitize (6.1.0)
|
||||||
crass (~> 1.0.2)
|
crass (~> 1.0.2)
|
||||||
nokogiri (>= 1.12.0)
|
nokogiri (>= 1.12.0)
|
||||||
scenic (1.7.0)
|
scenic (1.7.0)
|
||||||
activerecord (>= 4.0.0)
|
activerecord (>= 4.0.0)
|
||||||
railties (>= 4.0.0)
|
railties (>= 4.0.0)
|
||||||
selenium-webdriver (4.13.1)
|
selenium-webdriver (4.15.0)
|
||||||
rexml (~> 3.2, >= 3.2.5)
|
rexml (~> 3.2, >= 3.2.5)
|
||||||
rubyzip (>= 1.2.2, < 3.0)
|
rubyzip (>= 1.2.2, < 3.0)
|
||||||
websocket (~> 1.0)
|
websocket (~> 1.0)
|
||||||
|
@ -710,7 +709,7 @@ GEM
|
||||||
rufus-scheduler (~> 3.2)
|
rufus-scheduler (~> 3.2)
|
||||||
sidekiq (>= 6, < 8)
|
sidekiq (>= 6, < 8)
|
||||||
tilt (>= 1.4.0)
|
tilt (>= 1.4.0)
|
||||||
sidekiq-unique-jobs (7.1.29)
|
sidekiq-unique-jobs (7.1.30)
|
||||||
brpoplpush-redis_script (> 0.1.1, <= 2.0.0)
|
brpoplpush-redis_script (> 0.1.1, <= 2.0.0)
|
||||||
concurrent-ruby (~> 1.0, >= 1.0.5)
|
concurrent-ruby (~> 1.0, >= 1.0.5)
|
||||||
redis (< 5.0)
|
redis (< 5.0)
|
||||||
|
@ -718,7 +717,7 @@ GEM
|
||||||
thor (>= 0.20, < 3.0)
|
thor (>= 0.20, < 3.0)
|
||||||
simple-navigation (4.4.0)
|
simple-navigation (4.4.0)
|
||||||
activesupport (>= 2.3.2)
|
activesupport (>= 2.3.2)
|
||||||
simple_form (5.2.0)
|
simple_form (5.3.0)
|
||||||
actionpack (>= 5.2)
|
actionpack (>= 5.2)
|
||||||
activemodel (>= 5.2)
|
activemodel (>= 5.2)
|
||||||
simplecov (0.22.0)
|
simplecov (0.22.0)
|
||||||
|
@ -739,8 +738,8 @@ GEM
|
||||||
statsd-ruby (1.5.0)
|
statsd-ruby (1.5.0)
|
||||||
stoplight (3.0.2)
|
stoplight (3.0.2)
|
||||||
redlock (~> 1.0)
|
redlock (~> 1.0)
|
||||||
stringio (3.0.8)
|
stringio (3.0.9)
|
||||||
strong_migrations (1.3.0)
|
strong_migrations (1.6.4)
|
||||||
activerecord (>= 5.2)
|
activerecord (>= 5.2)
|
||||||
swd (1.3.0)
|
swd (1.3.0)
|
||||||
activesupport (>= 3)
|
activesupport (>= 3)
|
||||||
|
@ -753,9 +752,9 @@ GEM
|
||||||
terrapin (0.6.0)
|
terrapin (0.6.0)
|
||||||
climate_control (>= 0.0.3, < 1.0)
|
climate_control (>= 0.0.3, < 1.0)
|
||||||
test-prof (1.2.3)
|
test-prof (1.2.3)
|
||||||
thor (1.2.2)
|
thor (1.3.0)
|
||||||
tilt (2.3.0)
|
tilt (2.3.0)
|
||||||
timeout (0.4.0)
|
timeout (0.4.1)
|
||||||
tpm-key_attestation (0.12.0)
|
tpm-key_attestation (0.12.0)
|
||||||
bindata (~> 2.4)
|
bindata (~> 2.4)
|
||||||
openssl (> 2.0)
|
openssl (> 2.0)
|
||||||
|
@ -858,7 +857,7 @@ DEPENDENCIES
|
||||||
fast_blank (~> 1.0)
|
fast_blank (~> 1.0)
|
||||||
fastimage
|
fastimage
|
||||||
fog-core (<= 2.4.0)
|
fog-core (<= 2.4.0)
|
||||||
fog-openstack (~> 0.3)
|
fog-openstack (~> 1.0)
|
||||||
fuubar (~> 2.5)
|
fuubar (~> 2.5)
|
||||||
haml-rails (~> 2.0)
|
haml-rails (~> 2.0)
|
||||||
haml_lint
|
haml_lint
|
||||||
|
@ -883,7 +882,7 @@ DEPENDENCIES
|
||||||
md-paperclip-azure (~> 2.2)
|
md-paperclip-azure (~> 2.2)
|
||||||
memory_profiler
|
memory_profiler
|
||||||
mime-types (~> 3.5.0)
|
mime-types (~> 3.5.0)
|
||||||
net-http (~> 0.3.2)
|
net-http (~> 0.4.0)
|
||||||
net-ldap (~> 0.18)
|
net-ldap (~> 0.18)
|
||||||
nokogiri (~> 1.15)
|
nokogiri (~> 1.15)
|
||||||
nsa!
|
nsa!
|
||||||
|
@ -941,7 +940,7 @@ DEPENDENCIES
|
||||||
sprockets-rails (~> 3.4)
|
sprockets-rails (~> 3.4)
|
||||||
stackprof
|
stackprof
|
||||||
stoplight (~> 3.0.1)
|
stoplight (~> 3.0.1)
|
||||||
strong_migrations (= 1.3.0)
|
strong_migrations (= 1.6.4)
|
||||||
test-prof
|
test-prof
|
||||||
thor (~> 1.2)
|
thor (~> 1.2)
|
||||||
tty-prompt (~> 0.23)
|
tty-prompt (~> 0.23)
|
||||||
|
|
2
Vagrantfile
vendored
2
Vagrantfile
vendored
|
@ -112,7 +112,7 @@ bundle install
|
||||||
|
|
||||||
# Install node modules
|
# Install node modules
|
||||||
sudo corepack enable
|
sudo corepack enable
|
||||||
yarn set version classic
|
corepack prepare
|
||||||
yarn install
|
yarn install
|
||||||
|
|
||||||
# Build Mastodon
|
# Build Mastodon
|
||||||
|
|
|
@ -53,7 +53,7 @@ class PublicStatusesIndex < Chewy::Index
|
||||||
index_scope ::Status.unscoped
|
index_scope ::Status.unscoped
|
||||||
.kept
|
.kept
|
||||||
.indexable
|
.indexable
|
||||||
.includes(:media_attachments, :preloadable_poll, :preview_cards, :tags)
|
.includes(:media_attachments, :preloadable_poll, :tags, preview_cards_status: :preview_card)
|
||||||
|
|
||||||
root date_detection: false do
|
root date_detection: false do
|
||||||
field(:id, type: 'long')
|
field(:id, type: 'long')
|
||||||
|
|
|
@ -50,7 +50,7 @@ class StatusesIndex < Chewy::Index
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
index_scope ::Status.unscoped.kept.without_reblogs.includes(:media_attachments, :preview_cards, :local_mentioned, :local_favorited, :local_reblogged, :local_bookmarked, :tags, preloadable_poll: :local_voters), delete_if: ->(status) { status.searchable_by.empty? }
|
index_scope ::Status.unscoped.kept.without_reblogs.includes(:media_attachments, :local_mentioned, :local_favorited, :local_reblogged, :local_bookmarked, :tags, preview_cards_status: :preview_card, preloadable_poll: :local_voters), delete_if: ->(status) { status.searchable_by.empty? }
|
||||||
|
|
||||||
root date_detection: false do
|
root date_detection: false do
|
||||||
field(:id, type: 'long')
|
field(:id, type: 'long')
|
||||||
|
|
|
@ -31,6 +31,11 @@ module Admin
|
||||||
|
|
||||||
private
|
private
|
||||||
|
|
||||||
|
def batched_ordered_status_edits
|
||||||
|
@status.edits.reorder(nil).includes(:account, status: [:account]).find_each(order: :asc)
|
||||||
|
end
|
||||||
|
helper_method :batched_ordered_status_edits
|
||||||
|
|
||||||
def admin_status_batch_action_params
|
def admin_status_batch_action_params
|
||||||
params.require(:admin_status_batch_action).permit(status_ids: [])
|
params.require(:admin_status_batch_action).permit(status_ids: [])
|
||||||
end
|
end
|
||||||
|
|
|
@ -7,6 +7,7 @@ class Api::BaseController < ApplicationController
|
||||||
include RateLimitHeaders
|
include RateLimitHeaders
|
||||||
include AccessTokenTrackingConcern
|
include AccessTokenTrackingConcern
|
||||||
include ApiCachingConcern
|
include ApiCachingConcern
|
||||||
|
include Api::ContentSecurityPolicy
|
||||||
|
|
||||||
skip_before_action :require_functional!, unless: :limited_federation_mode?
|
skip_before_action :require_functional!, unless: :limited_federation_mode?
|
||||||
|
|
||||||
|
@ -17,26 +18,6 @@ class Api::BaseController < ApplicationController
|
||||||
|
|
||||||
protect_from_forgery with: :null_session
|
protect_from_forgery with: :null_session
|
||||||
|
|
||||||
content_security_policy do |p|
|
|
||||||
# Set every directive that does not have a fallback
|
|
||||||
p.default_src :none
|
|
||||||
p.frame_ancestors :none
|
|
||||||
p.form_action :none
|
|
||||||
|
|
||||||
# Disable every directive with a fallback to cut on response size
|
|
||||||
p.base_uri false
|
|
||||||
p.font_src false
|
|
||||||
p.img_src false
|
|
||||||
p.style_src false
|
|
||||||
p.media_src false
|
|
||||||
p.frame_src false
|
|
||||||
p.manifest_src false
|
|
||||||
p.connect_src false
|
|
||||||
p.script_src false
|
|
||||||
p.child_src false
|
|
||||||
p.worker_src false
|
|
||||||
end
|
|
||||||
|
|
||||||
rescue_from ActiveRecord::RecordInvalid, Mastodon::ValidationError do |e|
|
rescue_from ActiveRecord::RecordInvalid, Mastodon::ValidationError do |e|
|
||||||
render json: { error: e.to_s }, status: 422
|
render json: { error: e.to_s }, status: 422
|
||||||
end
|
end
|
||||||
|
|
|
@ -5,10 +5,11 @@ class Api::V1::Accounts::RelationshipsController < Api::BaseController
|
||||||
before_action :require_user!
|
before_action :require_user!
|
||||||
|
|
||||||
def index
|
def index
|
||||||
accounts = Account.where(id: account_ids).select('id')
|
scope = Account.where(id: account_ids).select('id')
|
||||||
|
scope.merge!(Account.without_suspended) unless truthy_param?(:with_suspended)
|
||||||
# .where doesn't guarantee that our results are in the same order
|
# .where doesn't guarantee that our results are in the same order
|
||||||
# we requested them, so return the "right" order to the requestor.
|
# we requested them, so return the "right" order to the requestor.
|
||||||
@accounts = accounts.index_by(&:id).values_at(*account_ids).compact
|
@accounts = scope.index_by(&:id).values_at(*account_ids).compact
|
||||||
render json: @accounts, each_serializer: REST::RelationshipSerializer, relationships: relationships
|
render json: @accounts, each_serializer: REST::RelationshipSerializer, relationships: relationships
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
|
@ -1,6 +1,8 @@
|
||||||
# frozen_string_literal: true
|
# frozen_string_literal: true
|
||||||
|
|
||||||
class Api::V1::AccountsController < Api::BaseController
|
class Api::V1::AccountsController < Api::BaseController
|
||||||
|
include RegistrationHelper
|
||||||
|
|
||||||
before_action -> { authorize_if_got_token! :read, :'read:accounts' }, except: [:create, :follow, :unfollow, :remove_from_followers, :block, :unblock, :mute, :unmute]
|
before_action -> { authorize_if_got_token! :read, :'read:accounts' }, except: [:create, :follow, :unfollow, :remove_from_followers, :block, :unblock, :mute, :unmute]
|
||||||
before_action -> { doorkeeper_authorize! :follow, :write, :'write:follows' }, only: [:follow, :unfollow, :remove_from_followers]
|
before_action -> { doorkeeper_authorize! :follow, :write, :'write:follows' }, only: [:follow, :unfollow, :remove_from_followers]
|
||||||
before_action -> { doorkeeper_authorize! :follow, :write, :'write:mutes' }, only: [:mute, :unmute]
|
before_action -> { doorkeeper_authorize! :follow, :write, :'write:mutes' }, only: [:mute, :unmute]
|
||||||
|
@ -90,18 +92,14 @@ class Api::V1::AccountsController < Api::BaseController
|
||||||
end
|
end
|
||||||
|
|
||||||
def account_params
|
def account_params
|
||||||
params.permit(:username, :email, :password, :agreement, :locale, :reason, :time_zone)
|
params.permit(:username, :email, :password, :agreement, :locale, :reason, :time_zone, :invite_code)
|
||||||
|
end
|
||||||
|
|
||||||
|
def invite
|
||||||
|
Invite.find_by(code: params[:invite_code]) if params[:invite_code].present?
|
||||||
end
|
end
|
||||||
|
|
||||||
def check_enabled_registrations
|
def check_enabled_registrations
|
||||||
forbidden if single_user_mode? || omniauth_only? || !allowed_registrations?
|
forbidden unless allowed_registration?(request.remote_ip, invite)
|
||||||
end
|
|
||||||
|
|
||||||
def allowed_registrations?
|
|
||||||
Setting.registrations_mode != 'none'
|
|
||||||
end
|
|
||||||
|
|
||||||
def omniauth_only?
|
|
||||||
ENV['OMNIAUTH_ONLY'] == 'true'
|
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
|
@ -41,10 +41,10 @@ class Api::V1::ConversationsController < Api::BaseController
|
||||||
account: :account_stat,
|
account: :account_stat,
|
||||||
last_status: [
|
last_status: [
|
||||||
:media_attachments,
|
:media_attachments,
|
||||||
:preview_cards,
|
|
||||||
:status_stat,
|
:status_stat,
|
||||||
:tags,
|
:tags,
|
||||||
{
|
{
|
||||||
|
preview_cards_status: :preview_card,
|
||||||
active_mentions: [account: :account_stat],
|
active_mentions: [account: :account_stat],
|
||||||
account: :account_stat,
|
account: :account_stat,
|
||||||
},
|
},
|
||||||
|
|
|
@ -1,12 +1,8 @@
|
||||||
# frozen_string_literal: true
|
# frozen_string_literal: true
|
||||||
|
|
||||||
class Api::V1::Instances::ActivityController < Api::BaseController
|
class Api::V1::Instances::ActivityController < Api::V1::Instances::BaseController
|
||||||
before_action :require_enabled_api!
|
before_action :require_enabled_api!
|
||||||
|
|
||||||
skip_before_action :require_authenticated_user!, unless: :limited_federation_mode?
|
|
||||||
|
|
||||||
vary_by ''
|
|
||||||
|
|
||||||
def show
|
def show
|
||||||
cache_even_if_authenticated!
|
cache_even_if_authenticated!
|
||||||
render_with_cache json: :activity, expires_in: 1.day
|
render_with_cache json: :activity, expires_in: 1.day
|
||||||
|
|
8
app/controllers/api/v1/instances/base_controller.rb
Normal file
8
app/controllers/api/v1/instances/base_controller.rb
Normal file
|
@ -0,0 +1,8 @@
|
||||||
|
# frozen_string_literal: true
|
||||||
|
|
||||||
|
class Api::V1::Instances::BaseController < Api::BaseController
|
||||||
|
skip_before_action :require_authenticated_user!,
|
||||||
|
unless: :limited_federation_mode?
|
||||||
|
|
||||||
|
vary_by ''
|
||||||
|
end
|
|
@ -1,8 +1,6 @@
|
||||||
# frozen_string_literal: true
|
# frozen_string_literal: true
|
||||||
|
|
||||||
class Api::V1::Instances::DomainBlocksController < Api::BaseController
|
class Api::V1::Instances::DomainBlocksController < Api::V1::Instances::BaseController
|
||||||
skip_before_action :require_authenticated_user!, unless: :limited_federation_mode?
|
|
||||||
|
|
||||||
before_action :require_enabled_api!
|
before_action :require_enabled_api!
|
||||||
before_action :set_domain_blocks
|
before_action :set_domain_blocks
|
||||||
|
|
||||||
|
@ -15,7 +13,7 @@ class Api::V1::Instances::DomainBlocksController < Api::BaseController
|
||||||
cache_if_unauthenticated!
|
cache_if_unauthenticated!
|
||||||
end
|
end
|
||||||
|
|
||||||
render json: @domain_blocks, each_serializer: REST::DomainBlockSerializer, with_comment: (Setting.show_domain_blocks_rationale == 'all' || (Setting.show_domain_blocks_rationale == 'users' && user_signed_in?))
|
render json: @domain_blocks, each_serializer: REST::DomainBlockSerializer, with_comment: show_rationale_in_response?
|
||||||
end
|
end
|
||||||
|
|
||||||
private
|
private
|
||||||
|
@ -27,4 +25,16 @@ class Api::V1::Instances::DomainBlocksController < Api::BaseController
|
||||||
def set_domain_blocks
|
def set_domain_blocks
|
||||||
@domain_blocks = DomainBlock.with_user_facing_limitations.by_severity
|
@domain_blocks = DomainBlock.with_user_facing_limitations.by_severity
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def show_rationale_in_response?
|
||||||
|
always_show_rationale? || show_rationale_for_user?
|
||||||
|
end
|
||||||
|
|
||||||
|
def always_show_rationale?
|
||||||
|
Setting.show_domain_blocks_rationale == 'all'
|
||||||
|
end
|
||||||
|
|
||||||
|
def show_rationale_for_user?
|
||||||
|
Setting.show_domain_blocks_rationale == 'users' && user_signed_in?
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
|
@ -1,13 +1,10 @@
|
||||||
# frozen_string_literal: true
|
# frozen_string_literal: true
|
||||||
|
|
||||||
class Api::V1::Instances::ExtendedDescriptionsController < Api::BaseController
|
class Api::V1::Instances::ExtendedDescriptionsController < Api::V1::Instances::BaseController
|
||||||
skip_before_action :require_authenticated_user!, unless: :limited_federation_mode?
|
|
||||||
skip_around_action :set_locale
|
skip_around_action :set_locale
|
||||||
|
|
||||||
before_action :set_extended_description
|
before_action :set_extended_description
|
||||||
|
|
||||||
vary_by ''
|
|
||||||
|
|
||||||
# Override `current_user` to avoid reading session cookies unless in whitelist mode
|
# Override `current_user` to avoid reading session cookies unless in whitelist mode
|
||||||
def current_user
|
def current_user
|
||||||
super if limited_federation_mode?
|
super if limited_federation_mode?
|
||||||
|
|
|
@ -1,13 +1,10 @@
|
||||||
# frozen_string_literal: true
|
# frozen_string_literal: true
|
||||||
|
|
||||||
class Api::V1::Instances::LanguagesController < Api::BaseController
|
class Api::V1::Instances::LanguagesController < Api::V1::Instances::BaseController
|
||||||
skip_before_action :require_authenticated_user!, unless: :limited_federation_mode?
|
|
||||||
skip_around_action :set_locale
|
skip_around_action :set_locale
|
||||||
|
|
||||||
before_action :set_languages
|
before_action :set_languages
|
||||||
|
|
||||||
vary_by ''
|
|
||||||
|
|
||||||
def show
|
def show
|
||||||
cache_even_if_authenticated!
|
cache_even_if_authenticated!
|
||||||
render json: @languages, each_serializer: REST::LanguageSerializer
|
render json: @languages, each_serializer: REST::LanguageSerializer
|
||||||
|
|
|
@ -1,13 +1,10 @@
|
||||||
# frozen_string_literal: true
|
# frozen_string_literal: true
|
||||||
|
|
||||||
class Api::V1::Instances::PeersController < Api::BaseController
|
class Api::V1::Instances::PeersController < Api::V1::Instances::BaseController
|
||||||
before_action :require_enabled_api!
|
before_action :require_enabled_api!
|
||||||
|
|
||||||
skip_before_action :require_authenticated_user!, unless: :limited_federation_mode?
|
|
||||||
skip_around_action :set_locale
|
skip_around_action :set_locale
|
||||||
|
|
||||||
vary_by ''
|
|
||||||
|
|
||||||
# Override `current_user` to avoid reading session cookies unless in whitelist mode
|
# Override `current_user` to avoid reading session cookies unless in whitelist mode
|
||||||
def current_user
|
def current_user
|
||||||
super if limited_federation_mode?
|
super if limited_federation_mode?
|
||||||
|
|
|
@ -1,12 +1,8 @@
|
||||||
# frozen_string_literal: true
|
# frozen_string_literal: true
|
||||||
|
|
||||||
class Api::V1::Instances::PrivacyPoliciesController < Api::BaseController
|
class Api::V1::Instances::PrivacyPoliciesController < Api::V1::Instances::BaseController
|
||||||
skip_before_action :require_authenticated_user!, unless: :limited_federation_mode?
|
|
||||||
|
|
||||||
before_action :set_privacy_policy
|
before_action :set_privacy_policy
|
||||||
|
|
||||||
vary_by ''
|
|
||||||
|
|
||||||
def show
|
def show
|
||||||
cache_even_if_authenticated!
|
cache_even_if_authenticated!
|
||||||
render json: @privacy_policy, serializer: REST::PrivacyPolicySerializer
|
render json: @privacy_policy, serializer: REST::PrivacyPolicySerializer
|
||||||
|
|
|
@ -1,13 +1,10 @@
|
||||||
# frozen_string_literal: true
|
# frozen_string_literal: true
|
||||||
|
|
||||||
class Api::V1::Instances::RulesController < Api::BaseController
|
class Api::V1::Instances::RulesController < Api::V1::Instances::BaseController
|
||||||
skip_before_action :require_authenticated_user!, unless: :limited_federation_mode?
|
|
||||||
skip_around_action :set_locale
|
skip_around_action :set_locale
|
||||||
|
|
||||||
before_action :set_rules
|
before_action :set_rules
|
||||||
|
|
||||||
vary_by ''
|
|
||||||
|
|
||||||
# Override `current_user` to avoid reading session cookies unless in whitelist mode
|
# Override `current_user` to avoid reading session cookies unless in whitelist mode
|
||||||
def current_user
|
def current_user
|
||||||
super if limited_federation_mode?
|
super if limited_federation_mode?
|
||||||
|
|
|
@ -1,12 +1,8 @@
|
||||||
# frozen_string_literal: true
|
# frozen_string_literal: true
|
||||||
|
|
||||||
class Api::V1::Instances::TranslationLanguagesController < Api::BaseController
|
class Api::V1::Instances::TranslationLanguagesController < Api::V1::Instances::BaseController
|
||||||
skip_before_action :require_authenticated_user!, unless: :limited_federation_mode?
|
|
||||||
|
|
||||||
before_action :set_languages
|
before_action :set_languages
|
||||||
|
|
||||||
vary_by ''
|
|
||||||
|
|
||||||
def show
|
def show
|
||||||
cache_even_if_authenticated!
|
cache_even_if_authenticated!
|
||||||
render json: @languages
|
render json: @languages
|
||||||
|
|
30
app/controllers/api/v1/invites_controller.rb
Normal file
30
app/controllers/api/v1/invites_controller.rb
Normal file
|
@ -0,0 +1,30 @@
|
||||||
|
# frozen_string_literal: true
|
||||||
|
|
||||||
|
class Api::V1::InvitesController < Api::BaseController
|
||||||
|
include RegistrationHelper
|
||||||
|
|
||||||
|
skip_before_action :require_authenticated_user!
|
||||||
|
skip_around_action :set_locale
|
||||||
|
|
||||||
|
before_action :set_invite
|
||||||
|
before_action :check_enabled_registrations!
|
||||||
|
|
||||||
|
# Override `current_user` to avoid reading session cookies
|
||||||
|
def current_user; end
|
||||||
|
|
||||||
|
def show
|
||||||
|
render json: { invite_code: params[:invite_code], instance_api_url: api_v2_instance_url }, status: 200
|
||||||
|
end
|
||||||
|
|
||||||
|
private
|
||||||
|
|
||||||
|
def set_invite
|
||||||
|
@invite = Invite.find_by!(code: params[:invite_code])
|
||||||
|
end
|
||||||
|
|
||||||
|
def check_enabled_registrations!
|
||||||
|
return render json: { error: I18n.t('invites.invalid') }, status: 401 unless @invite.valid_for_use?
|
||||||
|
|
||||||
|
raise Mastodon::NotPermittedError unless allowed_registration?(request.remote_ip, @invite)
|
||||||
|
end
|
||||||
|
end
|
16
app/controllers/api/v1/statuses/base_controller.rb
Normal file
16
app/controllers/api/v1/statuses/base_controller.rb
Normal file
|
@ -0,0 +1,16 @@
|
||||||
|
# frozen_string_literal: true
|
||||||
|
|
||||||
|
class Api::V1::Statuses::BaseController < Api::BaseController
|
||||||
|
include Authorization
|
||||||
|
|
||||||
|
before_action :set_status
|
||||||
|
|
||||||
|
private
|
||||||
|
|
||||||
|
def set_status
|
||||||
|
@status = Status.find(params[:status_id])
|
||||||
|
authorize @status, :show?
|
||||||
|
rescue Mastodon::NotPermittedError
|
||||||
|
not_found
|
||||||
|
end
|
||||||
|
end
|
|
@ -1,11 +1,9 @@
|
||||||
# frozen_string_literal: true
|
# frozen_string_literal: true
|
||||||
|
|
||||||
class Api::V1::Statuses::BookmarksController < Api::BaseController
|
class Api::V1::Statuses::BookmarksController < Api::V1::Statuses::BaseController
|
||||||
include Authorization
|
|
||||||
|
|
||||||
before_action -> { doorkeeper_authorize! :write, :'write:bookmarks' }
|
before_action -> { doorkeeper_authorize! :write, :'write:bookmarks' }
|
||||||
before_action :require_user!
|
before_action :require_user!
|
||||||
before_action :set_status, only: [:create]
|
skip_before_action :set_status, only: [:destroy]
|
||||||
|
|
||||||
def create
|
def create
|
||||||
current_account.bookmarks.find_or_create_by!(account: current_account, status: @status)
|
current_account.bookmarks.find_or_create_by!(account: current_account, status: @status)
|
||||||
|
@ -28,13 +26,4 @@ class Api::V1::Statuses::BookmarksController < Api::BaseController
|
||||||
rescue Mastodon::NotPermittedError
|
rescue Mastodon::NotPermittedError
|
||||||
not_found
|
not_found
|
||||||
end
|
end
|
||||||
|
|
||||||
private
|
|
||||||
|
|
||||||
def set_status
|
|
||||||
@status = Status.find(params[:status_id])
|
|
||||||
authorize @status, :show?
|
|
||||||
rescue Mastodon::NotPermittedError
|
|
||||||
not_found
|
|
||||||
end
|
|
||||||
end
|
end
|
||||||
|
|
|
@ -1,10 +1,7 @@
|
||||||
# frozen_string_literal: true
|
# frozen_string_literal: true
|
||||||
|
|
||||||
class Api::V1::Statuses::FavouritedByAccountsController < Api::BaseController
|
class Api::V1::Statuses::FavouritedByAccountsController < Api::V1::Statuses::BaseController
|
||||||
include Authorization
|
|
||||||
|
|
||||||
before_action -> { authorize_if_got_token! :read, :'read:accounts' }
|
before_action -> { authorize_if_got_token! :read, :'read:accounts' }
|
||||||
before_action :set_status
|
|
||||||
after_action :insert_pagination_headers
|
after_action :insert_pagination_headers
|
||||||
|
|
||||||
def index
|
def index
|
||||||
|
@ -61,13 +58,6 @@ class Api::V1::Statuses::FavouritedByAccountsController < Api::BaseController
|
||||||
@accounts.size == limit_param(DEFAULT_ACCOUNTS_LIMIT)
|
@accounts.size == limit_param(DEFAULT_ACCOUNTS_LIMIT)
|
||||||
end
|
end
|
||||||
|
|
||||||
def set_status
|
|
||||||
@status = Status.find(params[:status_id])
|
|
||||||
authorize @status, :show?
|
|
||||||
rescue Mastodon::NotPermittedError
|
|
||||||
not_found
|
|
||||||
end
|
|
||||||
|
|
||||||
def pagination_params(core_params)
|
def pagination_params(core_params)
|
||||||
params.slice(:limit).permit(:limit).merge(core_params)
|
params.slice(:limit).permit(:limit).merge(core_params)
|
||||||
end
|
end
|
||||||
|
|
|
@ -1,11 +1,9 @@
|
||||||
# frozen_string_literal: true
|
# frozen_string_literal: true
|
||||||
|
|
||||||
class Api::V1::Statuses::FavouritesController < Api::BaseController
|
class Api::V1::Statuses::FavouritesController < Api::V1::Statuses::BaseController
|
||||||
include Authorization
|
|
||||||
|
|
||||||
before_action -> { doorkeeper_authorize! :write, :'write:favourites' }
|
before_action -> { doorkeeper_authorize! :write, :'write:favourites' }
|
||||||
before_action :require_user!
|
before_action :require_user!
|
||||||
before_action :set_status, only: [:create]
|
skip_before_action :set_status, only: [:destroy]
|
||||||
|
|
||||||
def create
|
def create
|
||||||
FavouriteService.new.call(current_account, @status)
|
FavouriteService.new.call(current_account, @status)
|
||||||
|
@ -30,13 +28,4 @@ class Api::V1::Statuses::FavouritesController < Api::BaseController
|
||||||
rescue Mastodon::NotPermittedError
|
rescue Mastodon::NotPermittedError
|
||||||
not_found
|
not_found
|
||||||
end
|
end
|
||||||
|
|
||||||
private
|
|
||||||
|
|
||||||
def set_status
|
|
||||||
@status = Status.find(params[:status_id])
|
|
||||||
authorize @status, :show?
|
|
||||||
rescue Mastodon::NotPermittedError
|
|
||||||
not_found
|
|
||||||
end
|
|
||||||
end
|
end
|
||||||
|
|
|
@ -1,10 +1,7 @@
|
||||||
# frozen_string_literal: true
|
# frozen_string_literal: true
|
||||||
|
|
||||||
class Api::V1::Statuses::HistoriesController < Api::BaseController
|
class Api::V1::Statuses::HistoriesController < Api::V1::Statuses::BaseController
|
||||||
include Authorization
|
|
||||||
|
|
||||||
before_action -> { authorize_if_got_token! :read, :'read:statuses' }
|
before_action -> { authorize_if_got_token! :read, :'read:statuses' }
|
||||||
before_action :set_status
|
|
||||||
|
|
||||||
def show
|
def show
|
||||||
cache_if_unauthenticated!
|
cache_if_unauthenticated!
|
||||||
|
@ -16,11 +13,4 @@ class Api::V1::Statuses::HistoriesController < Api::BaseController
|
||||||
def status_edits
|
def status_edits
|
||||||
@status.edits.includes(:account, status: [:account]).to_a.presence || [@status.build_snapshot(at_time: @status.edited_at || @status.created_at)]
|
@status.edits.includes(:account, status: [:account]).to_a.presence || [@status.build_snapshot(at_time: @status.edited_at || @status.created_at)]
|
||||||
end
|
end
|
||||||
|
|
||||||
def set_status
|
|
||||||
@status = Status.find(params[:status_id])
|
|
||||||
authorize @status, :show?
|
|
||||||
rescue Mastodon::NotPermittedError
|
|
||||||
not_found
|
|
||||||
end
|
|
||||||
end
|
end
|
||||||
|
|
|
@ -1,11 +1,8 @@
|
||||||
# frozen_string_literal: true
|
# frozen_string_literal: true
|
||||||
|
|
||||||
class Api::V1::Statuses::MutesController < Api::BaseController
|
class Api::V1::Statuses::MutesController < Api::V1::Statuses::BaseController
|
||||||
include Authorization
|
|
||||||
|
|
||||||
before_action -> { doorkeeper_authorize! :write, :'write:mutes' }
|
before_action -> { doorkeeper_authorize! :write, :'write:mutes' }
|
||||||
before_action :require_user!
|
before_action :require_user!
|
||||||
before_action :set_status
|
|
||||||
before_action :set_conversation
|
before_action :set_conversation
|
||||||
|
|
||||||
def create
|
def create
|
||||||
|
@ -24,13 +21,6 @@ class Api::V1::Statuses::MutesController < Api::BaseController
|
||||||
|
|
||||||
private
|
private
|
||||||
|
|
||||||
def set_status
|
|
||||||
@status = Status.find(params[:status_id])
|
|
||||||
authorize @status, :show?
|
|
||||||
rescue Mastodon::NotPermittedError
|
|
||||||
not_found
|
|
||||||
end
|
|
||||||
|
|
||||||
def set_conversation
|
def set_conversation
|
||||||
@conversation = @status.conversation
|
@conversation = @status.conversation
|
||||||
raise Mastodon::ValidationError if @conversation.nil?
|
raise Mastodon::ValidationError if @conversation.nil?
|
||||||
|
|
|
@ -1,11 +1,8 @@
|
||||||
# frozen_string_literal: true
|
# frozen_string_literal: true
|
||||||
|
|
||||||
class Api::V1::Statuses::PinsController < Api::BaseController
|
class Api::V1::Statuses::PinsController < Api::V1::Statuses::BaseController
|
||||||
include Authorization
|
|
||||||
|
|
||||||
before_action -> { doorkeeper_authorize! :write, :'write:accounts' }
|
before_action -> { doorkeeper_authorize! :write, :'write:accounts' }
|
||||||
before_action :require_user!
|
before_action :require_user!
|
||||||
before_action :set_status
|
|
||||||
|
|
||||||
def create
|
def create
|
||||||
StatusPin.create!(account: current_account, status: @status)
|
StatusPin.create!(account: current_account, status: @status)
|
||||||
|
@ -26,10 +23,6 @@ class Api::V1::Statuses::PinsController < Api::BaseController
|
||||||
|
|
||||||
private
|
private
|
||||||
|
|
||||||
def set_status
|
|
||||||
@status = Status.find(params[:status_id])
|
|
||||||
end
|
|
||||||
|
|
||||||
def distribute_add_activity!
|
def distribute_add_activity!
|
||||||
json = ActiveModelSerializers::SerializableResource.new(
|
json = ActiveModelSerializers::SerializableResource.new(
|
||||||
@status,
|
@status,
|
||||||
|
|
|
@ -1,10 +1,7 @@
|
||||||
# frozen_string_literal: true
|
# frozen_string_literal: true
|
||||||
|
|
||||||
class Api::V1::Statuses::RebloggedByAccountsController < Api::BaseController
|
class Api::V1::Statuses::RebloggedByAccountsController < Api::V1::Statuses::BaseController
|
||||||
include Authorization
|
|
||||||
|
|
||||||
before_action -> { authorize_if_got_token! :read, :'read:accounts' }
|
before_action -> { authorize_if_got_token! :read, :'read:accounts' }
|
||||||
before_action :set_status
|
|
||||||
after_action :insert_pagination_headers
|
after_action :insert_pagination_headers
|
||||||
|
|
||||||
def index
|
def index
|
||||||
|
@ -57,13 +54,6 @@ class Api::V1::Statuses::RebloggedByAccountsController < Api::BaseController
|
||||||
@accounts.size == limit_param(DEFAULT_ACCOUNTS_LIMIT)
|
@accounts.size == limit_param(DEFAULT_ACCOUNTS_LIMIT)
|
||||||
end
|
end
|
||||||
|
|
||||||
def set_status
|
|
||||||
@status = Status.find(params[:status_id])
|
|
||||||
authorize @status, :show?
|
|
||||||
rescue Mastodon::NotPermittedError
|
|
||||||
not_found
|
|
||||||
end
|
|
||||||
|
|
||||||
def pagination_params(core_params)
|
def pagination_params(core_params)
|
||||||
params.slice(:limit).permit(:limit).merge(core_params)
|
params.slice(:limit).permit(:limit).merge(core_params)
|
||||||
end
|
end
|
||||||
|
|
|
@ -1,13 +1,13 @@
|
||||||
# frozen_string_literal: true
|
# frozen_string_literal: true
|
||||||
|
|
||||||
class Api::V1::Statuses::ReblogsController < Api::BaseController
|
class Api::V1::Statuses::ReblogsController < Api::V1::Statuses::BaseController
|
||||||
include Authorization
|
|
||||||
include Redisable
|
include Redisable
|
||||||
include Lockable
|
include Lockable
|
||||||
|
|
||||||
before_action -> { doorkeeper_authorize! :write, :'write:statuses' }
|
before_action -> { doorkeeper_authorize! :write, :'write:statuses' }
|
||||||
before_action :require_user!
|
before_action :require_user!
|
||||||
before_action :set_reblog, only: [:create]
|
before_action :set_reblog, only: [:create]
|
||||||
|
skip_before_action :set_status
|
||||||
|
|
||||||
override_rate_limit_headers :create, family: :statuses
|
override_rate_limit_headers :create, family: :statuses
|
||||||
|
|
||||||
|
|
|
@ -1,21 +1,9 @@
|
||||||
# frozen_string_literal: true
|
# frozen_string_literal: true
|
||||||
|
|
||||||
class Api::V1::Statuses::SourcesController < Api::BaseController
|
class Api::V1::Statuses::SourcesController < Api::V1::Statuses::BaseController
|
||||||
include Authorization
|
|
||||||
|
|
||||||
before_action -> { doorkeeper_authorize! :read, :'read:statuses' }
|
before_action -> { doorkeeper_authorize! :read, :'read:statuses' }
|
||||||
before_action :set_status
|
|
||||||
|
|
||||||
def show
|
def show
|
||||||
render json: @status, serializer: REST::StatusSourceSerializer
|
render json: @status, serializer: REST::StatusSourceSerializer
|
||||||
end
|
end
|
||||||
|
|
||||||
private
|
|
||||||
|
|
||||||
def set_status
|
|
||||||
@status = Status.find(params[:status_id])
|
|
||||||
authorize @status, :show?
|
|
||||||
rescue Mastodon::NotPermittedError
|
|
||||||
not_found
|
|
||||||
end
|
|
||||||
end
|
end
|
||||||
|
|
|
@ -1,10 +1,7 @@
|
||||||
# frozen_string_literal: true
|
# frozen_string_literal: true
|
||||||
|
|
||||||
class Api::V1::Statuses::TranslationsController < Api::BaseController
|
class Api::V1::Statuses::TranslationsController < Api::V1::Statuses::BaseController
|
||||||
include Authorization
|
|
||||||
|
|
||||||
before_action -> { doorkeeper_authorize! :read, :'read:statuses' }
|
before_action -> { doorkeeper_authorize! :read, :'read:statuses' }
|
||||||
before_action :set_status
|
|
||||||
before_action :set_translation
|
before_action :set_translation
|
||||||
|
|
||||||
rescue_from TranslationService::NotConfiguredError, with: :not_found
|
rescue_from TranslationService::NotConfiguredError, with: :not_found
|
||||||
|
@ -24,13 +21,6 @@ class Api::V1::Statuses::TranslationsController < Api::BaseController
|
||||||
|
|
||||||
private
|
private
|
||||||
|
|
||||||
def set_status
|
|
||||||
@status = Status.find(params[:status_id])
|
|
||||||
authorize @status, :show?
|
|
||||||
rescue Mastodon::NotPermittedError
|
|
||||||
not_found
|
|
||||||
end
|
|
||||||
|
|
||||||
def set_translation
|
def set_translation
|
||||||
@translation = TranslateStatusService.new.call(@status, content_locale)
|
@translation = TranslateStatusService.new.call(@status, content_locale)
|
||||||
end
|
end
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
# frozen_string_literal: true
|
# frozen_string_literal: true
|
||||||
|
|
||||||
class Auth::RegistrationsController < Devise::RegistrationsController
|
class Auth::RegistrationsController < Devise::RegistrationsController
|
||||||
|
include RegistrationHelper
|
||||||
include RegistrationSpamConcern
|
include RegistrationSpamConcern
|
||||||
|
|
||||||
layout :determine_layout
|
layout :determine_layout
|
||||||
|
@ -83,19 +84,7 @@ class Auth::RegistrationsController < Devise::RegistrationsController
|
||||||
end
|
end
|
||||||
|
|
||||||
def check_enabled_registrations
|
def check_enabled_registrations
|
||||||
redirect_to root_path if single_user_mode? || omniauth_only? || !allowed_registrations? || ip_blocked?
|
redirect_to root_path unless allowed_registration?(request.remote_ip, @invite)
|
||||||
end
|
|
||||||
|
|
||||||
def allowed_registrations?
|
|
||||||
Setting.registrations_mode != 'none' || @invite&.valid_for_use?
|
|
||||||
end
|
|
||||||
|
|
||||||
def omniauth_only?
|
|
||||||
ENV['OMNIAUTH_ONLY'] == 'true'
|
|
||||||
end
|
|
||||||
|
|
||||||
def ip_blocked?
|
|
||||||
IpBlock.where(severity: :sign_up_block).where('ip >>= ?', request.remote_ip.to_s).exists?
|
|
||||||
end
|
end
|
||||||
|
|
||||||
def invite_code
|
def invite_code
|
||||||
|
|
27
app/controllers/concerns/api/content_security_policy.rb
Normal file
27
app/controllers/concerns/api/content_security_policy.rb
Normal file
|
@ -0,0 +1,27 @@
|
||||||
|
# frozen_string_literal: true
|
||||||
|
|
||||||
|
module Api::ContentSecurityPolicy
|
||||||
|
extend ActiveSupport::Concern
|
||||||
|
|
||||||
|
included do
|
||||||
|
content_security_policy do |policy|
|
||||||
|
# Set every directive that does not have a fallback
|
||||||
|
policy.default_src :none
|
||||||
|
policy.frame_ancestors :none
|
||||||
|
policy.form_action :none
|
||||||
|
|
||||||
|
# Disable every directive with a fallback to cut on response size
|
||||||
|
policy.base_uri false
|
||||||
|
policy.font_src false
|
||||||
|
policy.img_src false
|
||||||
|
policy.style_src false
|
||||||
|
policy.media_src false
|
||||||
|
policy.frame_src false
|
||||||
|
policy.manifest_src false
|
||||||
|
policy.connect_src false
|
||||||
|
policy.script_src false
|
||||||
|
policy.child_src false
|
||||||
|
policy.worker_src false
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
|
@ -6,7 +6,7 @@ module Admin::AccountModerationNotesHelper
|
||||||
|
|
||||||
link_to path || admin_account_path(account.id), class: name_tag_classes(account), title: account.acct do
|
link_to path || admin_account_path(account.id), class: name_tag_classes(account), title: account.acct do
|
||||||
safe_join([
|
safe_join([
|
||||||
image_tag(account.avatar.url, width: 15, height: 15, alt: display_name(account), class: 'avatar'),
|
image_tag(account.avatar.url, width: 15, height: 15, alt: '', class: 'avatar'),
|
||||||
content_tag(:span, account.acct, class: 'username'),
|
content_tag(:span, account.acct, class: 'username'),
|
||||||
], ' ')
|
], ' ')
|
||||||
end
|
end
|
||||||
|
|
|
@ -91,6 +91,14 @@ module ApplicationHelper
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def html_title
|
||||||
|
safe_join(
|
||||||
|
[content_for(:page_title).to_s.chomp, title]
|
||||||
|
.select(&:present?),
|
||||||
|
' - '
|
||||||
|
)
|
||||||
|
end
|
||||||
|
|
||||||
def title
|
def title
|
||||||
Rails.env.production? ? site_title : "#{site_title} (Dev)"
|
Rails.env.production? ? site_title : "#{site_title} (Dev)"
|
||||||
end
|
end
|
||||||
|
|
|
@ -298,5 +298,3 @@ module LanguagesHelper
|
||||||
locale_name.to_sym if locale_name.present? && I18n.available_locales.include?(locale_name.to_sym)
|
locale_name.to_sym if locale_name.present? && I18n.available_locales.include?(locale_name.to_sym)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
# rubocop:enable Metrics/ModuleLength
|
|
||||||
|
|
21
app/helpers/registration_helper.rb
Normal file
21
app/helpers/registration_helper.rb
Normal file
|
@ -0,0 +1,21 @@
|
||||||
|
# frozen_string_literal: true
|
||||||
|
|
||||||
|
module RegistrationHelper
|
||||||
|
extend ActiveSupport::Concern
|
||||||
|
|
||||||
|
def allowed_registration?(remote_ip, invite)
|
||||||
|
!Rails.configuration.x.single_user_mode && !omniauth_only? && (registrations_open? || invite&.valid_for_use?) && !ip_blocked?(remote_ip)
|
||||||
|
end
|
||||||
|
|
||||||
|
def registrations_open?
|
||||||
|
Setting.registrations_mode != 'none'
|
||||||
|
end
|
||||||
|
|
||||||
|
def omniauth_only?
|
||||||
|
ENV['OMNIAUTH_ONLY'] == 'true'
|
||||||
|
end
|
||||||
|
|
||||||
|
def ip_blocked?(remote_ip)
|
||||||
|
IpBlock.where(severity: :sign_up_block).exists?(['ip >>= ?', remote_ip.to_s])
|
||||||
|
end
|
||||||
|
end
|
|
@ -25,7 +25,7 @@ module SettingsHelper
|
||||||
return if account.nil?
|
return if account.nil?
|
||||||
|
|
||||||
link_to ActivityPub::TagManager.instance.url_for(account), class: 'name-tag', title: account.acct do
|
link_to ActivityPub::TagManager.instance.url_for(account), class: 'name-tag', title: account.acct do
|
||||||
safe_join([image_tag(account.avatar.url, width: 15, height: 15, alt: display_name(account), class: 'avatar'), content_tag(:span, account.acct, class: 'username')], ' ')
|
safe_join([image_tag(account.avatar.url, width: 15, height: 15, alt: '', class: 'avatar'), content_tag(:span, account.acct, class: 'username')], ' ')
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
|
@ -567,7 +567,7 @@ export function fetchRelationships(accountIds) {
|
||||||
|
|
||||||
dispatch(fetchRelationshipsRequest(newAccountIds));
|
dispatch(fetchRelationshipsRequest(newAccountIds));
|
||||||
|
|
||||||
api(getState).get(`/api/v1/accounts/relationships?${newAccountIds.map(id => `id[]=${id}`).join('&')}`).then(response => {
|
api(getState).get(`/api/v1/accounts/relationships?with_suspended=true&${newAccountIds.map(id => `id[]=${id}`).join('&')}`).then(response => {
|
||||||
dispatch(fetchRelationshipsSuccess(response.data));
|
dispatch(fetchRelationshipsSuccess(response.data));
|
||||||
}).catch(error => {
|
}).catch(error => {
|
||||||
dispatch(fetchRelationshipsFail(error));
|
dispatch(fetchRelationshipsFail(error));
|
||||||
|
|
|
@ -119,7 +119,7 @@ class Account extends ImmutablePureComponent {
|
||||||
buttons = <Button title={intl.formatMessage(messages.mute)} onClick={this.handleMute} />;
|
buttons = <Button title={intl.formatMessage(messages.mute)} onClick={this.handleMute} />;
|
||||||
} else if (defaultAction === 'block') {
|
} else if (defaultAction === 'block') {
|
||||||
buttons = <Button text={intl.formatMessage(messages.block)} onClick={this.handleBlock} />;
|
buttons = <Button text={intl.formatMessage(messages.block)} onClick={this.handleBlock} />;
|
||||||
} else if (!account.get('moved') || following) {
|
} else if (!account.get('suspended') && !account.get('moved') || following) {
|
||||||
buttons = <Button text={intl.formatMessage(following ? messages.unfollow : messages.follow)} onClick={this.handleFollow} />;
|
buttons = <Button text={intl.formatMessage(following ? messages.unfollow : messages.follow)} onClick={this.handleFollow} />;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -209,7 +209,7 @@ class Header extends ImmutablePureComponent {
|
||||||
actionBtn = '';
|
actionBtn = '';
|
||||||
}
|
}
|
||||||
|
|
||||||
if (suspended && !account.getIn(['relationship', 'following'])) {
|
if (account.get('suspended') && !account.getIn(['relationship', 'following'])) {
|
||||||
actionBtn = '';
|
actionBtn = '';
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -217,7 +217,7 @@ class Header extends ImmutablePureComponent {
|
||||||
lockedIcon = <Icon id='lock' title={intl.formatMessage(messages.account_locked)} />;
|
lockedIcon = <Icon id='lock' title={intl.formatMessage(messages.account_locked)} />;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (signedIn && account.get('id') !== me && !suspended) {
|
if (signedIn && account.get('id') !== me && !account.get('suspended')) {
|
||||||
menu.push({ text: intl.formatMessage(messages.mention, { name: account.get('username') }), action: this.props.onMention });
|
menu.push({ text: intl.formatMessage(messages.mention, { name: account.get('username') }), action: this.props.onMention });
|
||||||
menu.push({ text: intl.formatMessage(messages.direct, { name: account.get('username') }), action: this.props.onDirect });
|
menu.push({ text: intl.formatMessage(messages.direct, { name: account.get('username') }), action: this.props.onDirect });
|
||||||
menu.push(null);
|
menu.push(null);
|
||||||
|
@ -228,7 +228,7 @@ class Header extends ImmutablePureComponent {
|
||||||
menu.push(null);
|
menu.push(null);
|
||||||
}
|
}
|
||||||
|
|
||||||
if ('share' in navigator && !suspended) {
|
if ('share' in navigator && !account.get('suspended')) {
|
||||||
menu.push({ text: intl.formatMessage(messages.share, { name: account.get('username') }), action: this.handleShare });
|
menu.push({ text: intl.formatMessage(messages.share, { name: account.get('username') }), action: this.handleShare });
|
||||||
menu.push(null);
|
menu.push(null);
|
||||||
}
|
}
|
||||||
|
@ -276,8 +276,10 @@ class Header extends ImmutablePureComponent {
|
||||||
menu.push({ text: intl.formatMessage(messages.block, { name: account.get('username') }), action: this.props.onBlock, dangerous: true });
|
menu.push({ text: intl.formatMessage(messages.block, { name: account.get('username') }), action: this.props.onBlock, dangerous: true });
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (!account.get('suspended')) {
|
||||||
menu.push({ text: intl.formatMessage(messages.report, { name: account.get('username') }), action: this.props.onReport, dangerous: true });
|
menu.push({ text: intl.formatMessage(messages.report, { name: account.get('username') }), action: this.props.onReport, dangerous: true });
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if (signedIn && isRemote) {
|
if (signedIn && isRemote) {
|
||||||
menu.push(null);
|
menu.push(null);
|
||||||
|
@ -340,7 +342,6 @@ class Header extends ImmutablePureComponent {
|
||||||
{role}
|
{role}
|
||||||
</a>
|
</a>
|
||||||
|
|
||||||
{!suspended && (
|
|
||||||
<div className='account__header__tabs__buttons'>
|
<div className='account__header__tabs__buttons'>
|
||||||
{!hidden && (
|
{!hidden && (
|
||||||
<>
|
<>
|
||||||
|
@ -351,7 +352,6 @@ class Header extends ImmutablePureComponent {
|
||||||
|
|
||||||
<DropdownMenuContainer disabled={menu.length === 0} items={menu} icon='ellipsis-v' size={24} direction='right' />
|
<DropdownMenuContainer disabled={menu.length === 0} items={menu} icon='ellipsis-v' size={24} direction='right' />
|
||||||
</div>
|
</div>
|
||||||
)}
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className='account__header__tabs__name'>
|
<div className='account__header__tabs__name'>
|
||||||
|
|
|
@ -200,7 +200,7 @@ class ListTimeline extends PureComponent {
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className='setting-toggle'>
|
<div className='setting-toggle'>
|
||||||
<Toggle id={`list-${id}-exclusive`} defaultChecked={isExclusive} onChange={this.onExclusiveToggle} />
|
<Toggle id={`list-${id}-exclusive`} checked={isExclusive} onChange={this.onExclusiveToggle} />
|
||||||
<label htmlFor={`list-${id}-exclusive`} className='setting-toggle__label'>
|
<label htmlFor={`list-${id}-exclusive`} className='setting-toggle__label'>
|
||||||
<FormattedMessage id='lists.exclusive' defaultMessage='Hide these posts from home' />
|
<FormattedMessage id='lists.exclusive' defaultMessage='Hide these posts from home' />
|
||||||
</label>
|
</label>
|
||||||
|
|
|
@ -11,6 +11,7 @@ import { throttle } from 'lodash';
|
||||||
|
|
||||||
import { Blurhash } from 'flavours/glitch/components/blurhash';
|
import { Blurhash } from 'flavours/glitch/components/blurhash';
|
||||||
import { Icon } from 'flavours/glitch/components/icon';
|
import { Icon } from 'flavours/glitch/components/icon';
|
||||||
|
import { playerSettings } from 'flavours/glitch/settings';
|
||||||
|
|
||||||
import { displayMedia, useBlurhash } from '../../initial_state';
|
import { displayMedia, useBlurhash } from '../../initial_state';
|
||||||
import { isFullscreen, requestFullscreen, exitFullscreen } from '../ui/util/fullscreen';
|
import { isFullscreen, requestFullscreen, exitFullscreen } from '../ui/util/fullscreen';
|
||||||
|
@ -221,8 +222,8 @@ class Video extends PureComponent {
|
||||||
|
|
||||||
if(!isNaN(x)) {
|
if(!isNaN(x)) {
|
||||||
this.setState((state) => ({ volume: x, muted: state.muted && x === 0 }), () => {
|
this.setState((state) => ({ volume: x, muted: state.muted && x === 0 }), () => {
|
||||||
this.video.volume = x;
|
this._syncVideoToVolumeState(x);
|
||||||
this.video.muted = this.state.muted;
|
this._saveVolumeState(x);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}, 15);
|
}, 15);
|
||||||
|
@ -360,6 +361,8 @@ class Video extends PureComponent {
|
||||||
document.addEventListener('MSFullscreenChange', this.handleFullscreenChange, true);
|
document.addEventListener('MSFullscreenChange', this.handleFullscreenChange, true);
|
||||||
|
|
||||||
window.addEventListener('scroll', this.handleScroll);
|
window.addEventListener('scroll', this.handleScroll);
|
||||||
|
|
||||||
|
this._syncVideoFromLocalStorage();
|
||||||
}
|
}
|
||||||
|
|
||||||
componentWillUnmount () {
|
componentWillUnmount () {
|
||||||
|
@ -432,8 +435,28 @@ class Video extends PureComponent {
|
||||||
const muted = !(this.video.muted || this.state.volume === 0);
|
const muted = !(this.video.muted || this.state.volume === 0);
|
||||||
|
|
||||||
this.setState((state) => ({ muted, volume: Math.max(state.volume || 0.5, 0.05) }), () => {
|
this.setState((state) => ({ muted, volume: Math.max(state.volume || 0.5, 0.05) }), () => {
|
||||||
this.video.volume = this.state.volume;
|
this._syncVideoToVolumeState();
|
||||||
this.video.muted = this.state.muted;
|
this._saveVolumeState();
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
_syncVideoToVolumeState = (volume = null, muted = null) => {
|
||||||
|
if (!this.video) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
this.video.volume = volume ?? this.state.volume;
|
||||||
|
this.video.muted = muted ?? this.state.muted;
|
||||||
|
};
|
||||||
|
|
||||||
|
_saveVolumeState = (volume = null, muted = null) => {
|
||||||
|
playerSettings.set('volume', volume ?? this.state.volume);
|
||||||
|
playerSettings.set('muted', muted ?? this.state.muted);
|
||||||
|
};
|
||||||
|
|
||||||
|
_syncVideoFromLocalStorage = () => {
|
||||||
|
this.setState({ volume: playerSettings.get('volume') ?? 0.5, muted: playerSettings.get('muted') ?? false }, () => {
|
||||||
|
this._syncVideoToVolumeState();
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -479,6 +502,7 @@ class Video extends PureComponent {
|
||||||
|
|
||||||
handleVolumeChange = () => {
|
handleVolumeChange = () => {
|
||||||
this.setState({ volume: this.video.volume, muted: this.video.muted });
|
this.setState({ volume: this.video.volume, muted: this.video.muted });
|
||||||
|
this._saveVolumeState(this.video.volume, this.video.muted);
|
||||||
};
|
};
|
||||||
|
|
||||||
handleOpenVideo = () => {
|
handleOpenVideo = () => {
|
||||||
|
|
|
@ -47,3 +47,4 @@ export const pushNotificationsSetting = new Settings('mastodon_push_notification
|
||||||
export const tagHistory = new Settings('mastodon_tag_history');
|
export const tagHistory = new Settings('mastodon_tag_history');
|
||||||
export const bannerSettings = new Settings('mastodon_banner_settings');
|
export const bannerSettings = new Settings('mastodon_banner_settings');
|
||||||
export const searchHistory = new Settings('mastodon_search_history');
|
export const searchHistory = new Settings('mastodon_search_history');
|
||||||
|
export const playerSettings = new Settings('mastodon_player');
|
||||||
|
|
|
@ -36,6 +36,7 @@
|
||||||
|
|
||||||
.modal-root__modal {
|
.modal-root__modal {
|
||||||
pointer-events: auto;
|
pointer-events: auto;
|
||||||
|
user-select: text;
|
||||||
display: flex;
|
display: flex;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -460,7 +460,7 @@ export function fetchRelationships(accountIds) {
|
||||||
|
|
||||||
dispatch(fetchRelationshipsRequest(newAccountIds));
|
dispatch(fetchRelationshipsRequest(newAccountIds));
|
||||||
|
|
||||||
api(getState).get(`/api/v1/accounts/relationships?${newAccountIds.map(id => `id[]=${id}`).join('&')}`).then(response => {
|
api(getState).get(`/api/v1/accounts/relationships?with_suspended=true&${newAccountIds.map(id => `id[]=${id}`).join('&')}`).then(response => {
|
||||||
dispatch(fetchRelationshipsSuccess({ relationships: response.data }));
|
dispatch(fetchRelationshipsSuccess({ relationships: response.data }));
|
||||||
}).catch(error => {
|
}).catch(error => {
|
||||||
dispatch(fetchRelationshipsFail(error));
|
dispatch(fetchRelationshipsFail(error));
|
||||||
|
|
|
@ -42,4 +42,5 @@ export interface ApiAccountJSON {
|
||||||
suspended?: boolean;
|
suspended?: boolean;
|
||||||
limited?: boolean;
|
limited?: boolean;
|
||||||
memorial?: boolean;
|
memorial?: boolean;
|
||||||
|
hide_collections: boolean;
|
||||||
}
|
}
|
||||||
|
|
|
@ -13,7 +13,7 @@ exports[`<Avatar /> Autoplay renders a animated avatar 1`] = `
|
||||||
}
|
}
|
||||||
>
|
>
|
||||||
<img
|
<img
|
||||||
alt="alice"
|
alt=""
|
||||||
src="/animated/alice.gif"
|
src="/animated/alice.gif"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
@ -32,7 +32,7 @@ exports[`<Avatar /> Still renders a still avatar 1`] = `
|
||||||
}
|
}
|
||||||
>
|
>
|
||||||
<img
|
<img
|
||||||
alt="alice"
|
alt=""
|
||||||
src="/static/alice.jpg"
|
src="/static/alice.jpg"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
@ -119,7 +119,7 @@ class Account extends ImmutablePureComponent {
|
||||||
buttons = <Button title={intl.formatMessage(messages.mute)} onClick={this.handleMute} />;
|
buttons = <Button title={intl.formatMessage(messages.mute)} onClick={this.handleMute} />;
|
||||||
} else if (defaultAction === 'block') {
|
} else if (defaultAction === 'block') {
|
||||||
buttons = <Button text={intl.formatMessage(messages.block)} onClick={this.handleBlock} />;
|
buttons = <Button text={intl.formatMessage(messages.block)} onClick={this.handleBlock} />;
|
||||||
} else if (!account.get('moved') || following) {
|
} else if (!account.get('suspended') && !account.get('moved') || following) {
|
||||||
buttons = <Button text={intl.formatMessage(following ? messages.unfollow : messages.follow)} onClick={this.handleFollow} />;
|
buttons = <Button text={intl.formatMessage(following ? messages.unfollow : messages.follow)} onClick={this.handleFollow} />;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -42,7 +42,7 @@ export const Avatar: React.FC<Props> = ({
|
||||||
onMouseLeave={handleMouseLeave}
|
onMouseLeave={handleMouseLeave}
|
||||||
style={style}
|
style={style}
|
||||||
>
|
>
|
||||||
{src && <img src={src} alt={account?.get('acct')} />}
|
{src && <img src={src} alt='' />}
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
|
@ -19,6 +19,8 @@ import { ReactComponent as StarIcon } from '@material-symbols/svg-600/outlined/s
|
||||||
import { ReactComponent as StarBorderIcon } from '@material-symbols/svg-600/outlined/star.svg';
|
import { ReactComponent as StarBorderIcon } from '@material-symbols/svg-600/outlined/star.svg';
|
||||||
import { ReactComponent as VisibilityIcon } from '@material-symbols/svg-600/outlined/visibility.svg';
|
import { ReactComponent as VisibilityIcon } from '@material-symbols/svg-600/outlined/visibility.svg';
|
||||||
|
|
||||||
|
import { ReactComponent as RepeatDisabledIcon } from 'mastodon/../svg-icons/repeat_disabled.svg';
|
||||||
|
import { ReactComponent as RepeatPrivateIcon } from 'mastodon/../svg-icons/repeat_private.svg';
|
||||||
import { PERMISSION_MANAGE_USERS, PERMISSION_MANAGE_FEDERATION } from 'mastodon/permissions';
|
import { PERMISSION_MANAGE_USERS, PERMISSION_MANAGE_FEDERATION } from 'mastodon/permissions';
|
||||||
import { WithRouterPropTypes } from 'mastodon/utils/react_router';
|
import { WithRouterPropTypes } from 'mastodon/utils/react_router';
|
||||||
|
|
||||||
|
@ -348,6 +350,7 @@ class StatusActionBar extends ImmutablePureComponent {
|
||||||
let replyIcon;
|
let replyIcon;
|
||||||
let replyIconComponent;
|
let replyIconComponent;
|
||||||
let replyTitle;
|
let replyTitle;
|
||||||
|
|
||||||
if (status.get('in_reply_to_id', null) === null) {
|
if (status.get('in_reply_to_id', null) === null) {
|
||||||
replyIcon = 'reply';
|
replyIcon = 'reply';
|
||||||
replyIconComponent = ReplyIcon;
|
replyIconComponent = ReplyIcon;
|
||||||
|
@ -360,15 +363,20 @@ class StatusActionBar extends ImmutablePureComponent {
|
||||||
|
|
||||||
const reblogPrivate = status.getIn(['account', 'id']) === me && status.get('visibility') === 'private';
|
const reblogPrivate = status.getIn(['account', 'id']) === me && status.get('visibility') === 'private';
|
||||||
|
|
||||||
let reblogTitle = '';
|
let reblogTitle, reblogIconComponent;
|
||||||
|
|
||||||
if (status.get('reblogged')) {
|
if (status.get('reblogged')) {
|
||||||
reblogTitle = intl.formatMessage(messages.cancel_reblog_private);
|
reblogTitle = intl.formatMessage(messages.cancel_reblog_private);
|
||||||
|
reblogIconComponent = publicStatus ? RepeatIcon : RepeatPrivateIcon;
|
||||||
} else if (publicStatus) {
|
} else if (publicStatus) {
|
||||||
reblogTitle = intl.formatMessage(messages.reblog);
|
reblogTitle = intl.formatMessage(messages.reblog);
|
||||||
|
reblogIconComponent = RepeatIcon;
|
||||||
} else if (reblogPrivate) {
|
} else if (reblogPrivate) {
|
||||||
reblogTitle = intl.formatMessage(messages.reblog_private);
|
reblogTitle = intl.formatMessage(messages.reblog_private);
|
||||||
|
reblogIconComponent = RepeatPrivateIcon;
|
||||||
} else {
|
} else {
|
||||||
reblogTitle = intl.formatMessage(messages.cannot_reblog);
|
reblogTitle = intl.formatMessage(messages.cannot_reblog);
|
||||||
|
reblogIconComponent = RepeatDisabledIcon;
|
||||||
}
|
}
|
||||||
|
|
||||||
const filterButton = this.props.onFilter && (
|
const filterButton = this.props.onFilter && (
|
||||||
|
@ -380,7 +388,7 @@ class StatusActionBar extends ImmutablePureComponent {
|
||||||
return (
|
return (
|
||||||
<div className='status__action-bar'>
|
<div className='status__action-bar'>
|
||||||
<IconButton className='status__action-bar__button' title={replyTitle} icon={isReply ? 'reply' : replyIcon} iconComponent={isReply ? ReplyIcon : replyIconComponent} onClick={this.handleReplyClick} counter={status.get('replies_count')} />
|
<IconButton className='status__action-bar__button' title={replyTitle} icon={isReply ? 'reply' : replyIcon} iconComponent={isReply ? ReplyIcon : replyIconComponent} onClick={this.handleReplyClick} counter={status.get('replies_count')} />
|
||||||
<IconButton className={classNames('status__action-bar__button', { reblogPrivate })} disabled={!publicStatus && !reblogPrivate} active={status.get('reblogged')} title={reblogTitle} icon='retweet' iconComponent={RepeatIcon} onClick={this.handleReblogClick} counter={withCounters ? status.get('reblogs_count') : undefined} />
|
<IconButton className={classNames('status__action-bar__button', { reblogPrivate })} disabled={!publicStatus && !reblogPrivate} active={status.get('reblogged')} title={reblogTitle} icon='retweet' iconComponent={reblogIconComponent} onClick={this.handleReblogClick} counter={withCounters ? status.get('reblogs_count') : undefined} />
|
||||||
<IconButton className='status__action-bar__button star-icon' animate active={status.get('favourited')} title={intl.formatMessage(messages.favourite)} icon='star' iconComponent={status.get('favourited') ? StarIcon : StarBorderIcon} onClick={this.handleFavouriteClick} counter={withCounters ? status.get('favourites_count') : undefined} />
|
<IconButton className='status__action-bar__button star-icon' animate active={status.get('favourited')} title={intl.formatMessage(messages.favourite)} icon='star' iconComponent={status.get('favourited') ? StarIcon : StarBorderIcon} onClick={this.handleFavouriteClick} counter={withCounters ? status.get('favourites_count') : undefined} />
|
||||||
<IconButton className='status__action-bar__button bookmark-icon' disabled={!signedIn} active={status.get('bookmarked')} title={intl.formatMessage(messages.bookmark)} icon='bookmark' iconComponent={status.get('bookmarked') ? BookmarkIcon : BookmarkBorderIcon} onClick={this.handleBookmarkClick} />
|
<IconButton className='status__action-bar__button bookmark-icon' disabled={!signedIn} active={status.get('bookmarked')} title={intl.formatMessage(messages.bookmark)} icon='bookmark' iconComponent={status.get('bookmarked') ? BookmarkIcon : BookmarkBorderIcon} onClick={this.handleBookmarkClick} />
|
||||||
|
|
||||||
|
|
80
app/javascript/mastodon/features/__tests__/toggle-play.jsx
Normal file
80
app/javascript/mastodon/features/__tests__/toggle-play.jsx
Normal file
|
@ -0,0 +1,80 @@
|
||||||
|
import PropTypes from 'prop-types';
|
||||||
|
import React, { Component } from 'react';
|
||||||
|
|
||||||
|
import { render, fireEvent } from '@testing-library/react';
|
||||||
|
|
||||||
|
class Media extends Component {
|
||||||
|
|
||||||
|
constructor(props) {
|
||||||
|
super(props);
|
||||||
|
|
||||||
|
this.state = {
|
||||||
|
paused: props.paused || false,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
handleMediaClick = () => {
|
||||||
|
const { onClick } = this.props;
|
||||||
|
|
||||||
|
this.setState(prevState => ({
|
||||||
|
paused: !prevState.paused,
|
||||||
|
}));
|
||||||
|
|
||||||
|
if (typeof onClick === 'function') {
|
||||||
|
onClick();
|
||||||
|
}
|
||||||
|
|
||||||
|
const { title } = this.props;
|
||||||
|
const mediaElements = document.querySelectorAll(`div[title="${title}"]`);
|
||||||
|
|
||||||
|
setTimeout(() => {
|
||||||
|
mediaElements.forEach(element => {
|
||||||
|
if (element !== this && !element.classList.contains('paused')) {
|
||||||
|
element.click();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}, 0);
|
||||||
|
};
|
||||||
|
|
||||||
|
render() {
|
||||||
|
const { title } = this.props;
|
||||||
|
const { paused } = this.state;
|
||||||
|
|
||||||
|
return (
|
||||||
|
<button title={title} onClick={this.handleMediaClick}>
|
||||||
|
Media Component - {paused ? 'Paused' : 'Playing'}
|
||||||
|
</button>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
Media.propTypes = {
|
||||||
|
title: PropTypes.string.isRequired,
|
||||||
|
onClick: PropTypes.func,
|
||||||
|
paused: PropTypes.bool,
|
||||||
|
};
|
||||||
|
|
||||||
|
describe('Media attachments test', () => {
|
||||||
|
let currentMedia = null;
|
||||||
|
const togglePlayMock = jest.fn();
|
||||||
|
|
||||||
|
it('plays a new media file and pauses others that were playing', () => {
|
||||||
|
const container = render(
|
||||||
|
<div>
|
||||||
|
<Media title='firstMedia' paused onClick={togglePlayMock} />
|
||||||
|
<Media title='secondMedia' paused onClick={togglePlayMock} />
|
||||||
|
</div>,
|
||||||
|
);
|
||||||
|
|
||||||
|
fireEvent.click(container.getByTitle('firstMedia'));
|
||||||
|
expect(togglePlayMock).toHaveBeenCalledTimes(1);
|
||||||
|
currentMedia = container.getByTitle('firstMedia');
|
||||||
|
expect(currentMedia.textContent).toMatch(/Playing/);
|
||||||
|
|
||||||
|
fireEvent.click(container.getByTitle('secondMedia'));
|
||||||
|
expect(togglePlayMock).toHaveBeenCalledTimes(2);
|
||||||
|
currentMedia = container.getByTitle('secondMedia');
|
||||||
|
expect(currentMedia.textContent).toMatch(/Playing/);
|
||||||
|
});
|
||||||
|
});
|
|
@ -289,7 +289,7 @@ class Header extends ImmutablePureComponent {
|
||||||
lockedIcon = <Icon id='lock' icon={LockIcon} title={intl.formatMessage(messages.account_locked)} />;
|
lockedIcon = <Icon id='lock' icon={LockIcon} title={intl.formatMessage(messages.account_locked)} />;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (signedIn && account.get('id') !== me) {
|
if (signedIn && account.get('id') !== me && !account.get('suspended')) {
|
||||||
menu.push({ text: intl.formatMessage(messages.mention, { name: account.get('username') }), action: this.props.onMention });
|
menu.push({ text: intl.formatMessage(messages.mention, { name: account.get('username') }), action: this.props.onMention });
|
||||||
menu.push({ text: intl.formatMessage(messages.direct, { name: account.get('username') }), action: this.props.onDirect });
|
menu.push({ text: intl.formatMessage(messages.direct, { name: account.get('username') }), action: this.props.onDirect });
|
||||||
menu.push(null);
|
menu.push(null);
|
||||||
|
@ -299,7 +299,7 @@ class Header extends ImmutablePureComponent {
|
||||||
menu.push({ text: intl.formatMessage(messages.openOriginalPage), href: account.get('url') });
|
menu.push({ text: intl.formatMessage(messages.openOriginalPage), href: account.get('url') });
|
||||||
}
|
}
|
||||||
|
|
||||||
if ('share' in navigator) {
|
if ('share' in navigator && !account.get('suspended')) {
|
||||||
menu.push({ text: intl.formatMessage(messages.share, { name: account.get('username') }), action: this.handleShare });
|
menu.push({ text: intl.formatMessage(messages.share, { name: account.get('username') }), action: this.handleShare });
|
||||||
menu.push(null);
|
menu.push(null);
|
||||||
}
|
}
|
||||||
|
@ -347,8 +347,10 @@ class Header extends ImmutablePureComponent {
|
||||||
menu.push({ text: intl.formatMessage(messages.block, { name: account.get('username') }), action: this.props.onBlock, dangerous: true });
|
menu.push({ text: intl.formatMessage(messages.block, { name: account.get('username') }), action: this.props.onBlock, dangerous: true });
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (!account.get('suspended')) {
|
||||||
menu.push({ text: intl.formatMessage(messages.report, { name: account.get('username') }), action: this.props.onReport, dangerous: true });
|
menu.push({ text: intl.formatMessage(messages.report, { name: account.get('username') }), action: this.props.onReport, dangerous: true });
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if (signedIn && isRemote) {
|
if (signedIn && isRemote) {
|
||||||
menu.push(null);
|
menu.push(null);
|
||||||
|
@ -395,7 +397,7 @@ class Header extends ImmutablePureComponent {
|
||||||
|
|
||||||
<div className='account__header__image'>
|
<div className='account__header__image'>
|
||||||
<div className='account__header__info'>
|
<div className='account__header__info'>
|
||||||
{!suspended && info}
|
{info}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{!(suspended || hidden) && <img src={autoPlayGif ? account.get('header') : account.get('header_static')} alt='' className='parallax' />}
|
{!(suspended || hidden) && <img src={autoPlayGif ? account.get('header') : account.get('header_static')} alt='' className='parallax' />}
|
||||||
|
@ -407,7 +409,6 @@ class Header extends ImmutablePureComponent {
|
||||||
<Avatar account={suspended || hidden ? undefined : account} size={90} />
|
<Avatar account={suspended || hidden ? undefined : account} size={90} />
|
||||||
</a>
|
</a>
|
||||||
|
|
||||||
{!suspended && (
|
|
||||||
<div className='account__header__tabs__buttons'>
|
<div className='account__header__tabs__buttons'>
|
||||||
{!hidden && (
|
{!hidden && (
|
||||||
<>
|
<>
|
||||||
|
@ -418,7 +419,6 @@ class Header extends ImmutablePureComponent {
|
||||||
|
|
||||||
<DropdownMenuContainer disabled={menu.length === 0} items={menu} icon='ellipsis-v' iconComponent={MoreHorizIcon} size={24} direction='right' />
|
<DropdownMenuContainer disabled={menu.length === 0} items={menu} icon='ellipsis-v' iconComponent={MoreHorizIcon} size={24} direction='right' />
|
||||||
</div>
|
</div>
|
||||||
)}
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className='account__header__tabs__name'>
|
<div className='account__header__tabs__name'>
|
||||||
|
|
|
@ -178,7 +178,7 @@ const mapDispatchToProps = (dispatch, { intl }) => ({
|
||||||
modalType: 'IMAGE',
|
modalType: 'IMAGE',
|
||||||
modalProps: {
|
modalProps: {
|
||||||
src: account.get('avatar'),
|
src: account.get('avatar'),
|
||||||
alt: account.get('acct'),
|
alt: '',
|
||||||
},
|
},
|
||||||
}));
|
}));
|
||||||
},
|
},
|
||||||
|
|
|
@ -20,6 +20,7 @@ import { formatTime, getPointerPosition, fileNameFromURL } from 'mastodon/featur
|
||||||
|
|
||||||
import { Blurhash } from '../../components/blurhash';
|
import { Blurhash } from '../../components/blurhash';
|
||||||
import { displayMedia, useBlurhash } from '../../initial_state';
|
import { displayMedia, useBlurhash } from '../../initial_state';
|
||||||
|
import { currentMedia, setCurrentMedia } from '../../reducers/media_attachments';
|
||||||
|
|
||||||
import Visualizer from './visualizer';
|
import Visualizer from './visualizer';
|
||||||
|
|
||||||
|
@ -165,15 +166,32 @@ class Audio extends PureComponent {
|
||||||
}
|
}
|
||||||
|
|
||||||
togglePlay = () => {
|
togglePlay = () => {
|
||||||
if (!this.audioContext) {
|
const audios = document.querySelectorAll('audio');
|
||||||
this._initAudioContext();
|
|
||||||
|
audios.forEach((audio) => {
|
||||||
|
const button = audio.previousElementSibling;
|
||||||
|
button.addEventListener('click', () => {
|
||||||
|
if(audio.paused) {
|
||||||
|
audios.forEach((e) => {
|
||||||
|
if (e !== audio) {
|
||||||
|
e.pause();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
audio.play();
|
||||||
|
this.setState({ paused: false });
|
||||||
|
} else {
|
||||||
|
audio.pause();
|
||||||
|
this.setState({ paused: true });
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
if (currentMedia !== null) {
|
||||||
|
currentMedia.pause();
|
||||||
}
|
}
|
||||||
|
|
||||||
if (this.state.paused) {
|
this.audio.play();
|
||||||
this.setState({ paused: false }, () => this.audio.play());
|
setCurrentMedia(this.audio);
|
||||||
} else {
|
|
||||||
this.setState({ paused: true }, () => this.audio.pause());
|
|
||||||
}
|
|
||||||
};
|
};
|
||||||
|
|
||||||
handleResize = debounce(() => {
|
handleResize = debounce(() => {
|
||||||
|
@ -195,6 +213,7 @@ class Audio extends PureComponent {
|
||||||
};
|
};
|
||||||
|
|
||||||
handlePause = () => {
|
handlePause = () => {
|
||||||
|
this.audio.pause();
|
||||||
this.setState({ paused: true });
|
this.setState({ paused: true });
|
||||||
|
|
||||||
if (this.audioContext) {
|
if (this.audioContext) {
|
||||||
|
|
|
@ -45,6 +45,7 @@ const mapStateToProps = (state, { params: { acct, id } }) => {
|
||||||
hasMore: !!state.getIn(['user_lists', 'followers', accountId, 'next']),
|
hasMore: !!state.getIn(['user_lists', 'followers', accountId, 'next']),
|
||||||
isLoading: state.getIn(['user_lists', 'followers', accountId, 'isLoading'], true),
|
isLoading: state.getIn(['user_lists', 'followers', accountId, 'isLoading'], true),
|
||||||
suspended: state.getIn(['accounts', accountId, 'suspended'], false),
|
suspended: state.getIn(['accounts', accountId, 'suspended'], false),
|
||||||
|
hideCollections: state.getIn(['accounts', accountId, 'hide_collections'], false),
|
||||||
hidden: getAccountHidden(state, accountId),
|
hidden: getAccountHidden(state, accountId),
|
||||||
blockedBy: state.getIn(['relationships', accountId, 'blocked_by'], false),
|
blockedBy: state.getIn(['relationships', accountId, 'blocked_by'], false),
|
||||||
};
|
};
|
||||||
|
@ -111,7 +112,7 @@ class Followers extends ImmutablePureComponent {
|
||||||
}, 300, { leading: true });
|
}, 300, { leading: true });
|
||||||
|
|
||||||
render () {
|
render () {
|
||||||
const { accountId, accountIds, hasMore, blockedBy, isAccount, multiColumn, isLoading, suspended, hidden, remote, remoteUrl } = this.props;
|
const { accountId, accountIds, hasMore, blockedBy, isAccount, multiColumn, isLoading, suspended, hidden, remote, remoteUrl, hideCollections } = this.props;
|
||||||
|
|
||||||
if (!isAccount) {
|
if (!isAccount) {
|
||||||
return (
|
return (
|
||||||
|
@ -137,6 +138,8 @@ class Followers extends ImmutablePureComponent {
|
||||||
emptyMessage = <LimitedAccountHint accountId={accountId} />;
|
emptyMessage = <LimitedAccountHint accountId={accountId} />;
|
||||||
} else if (blockedBy) {
|
} else if (blockedBy) {
|
||||||
emptyMessage = <FormattedMessage id='empty_column.account_unavailable' defaultMessage='Profile unavailable' />;
|
emptyMessage = <FormattedMessage id='empty_column.account_unavailable' defaultMessage='Profile unavailable' />;
|
||||||
|
} else if (hideCollections && accountIds.isEmpty()) {
|
||||||
|
emptyMessage = <FormattedMessage id='empty_column.account_hides_collections' defaultMessage='This user has chosen to not make this information available' />;
|
||||||
} else if (remote && accountIds.isEmpty()) {
|
} else if (remote && accountIds.isEmpty()) {
|
||||||
emptyMessage = <RemoteHint url={remoteUrl} />;
|
emptyMessage = <RemoteHint url={remoteUrl} />;
|
||||||
} else {
|
} else {
|
||||||
|
|
|
@ -45,6 +45,7 @@ const mapStateToProps = (state, { params: { acct, id } }) => {
|
||||||
hasMore: !!state.getIn(['user_lists', 'following', accountId, 'next']),
|
hasMore: !!state.getIn(['user_lists', 'following', accountId, 'next']),
|
||||||
isLoading: state.getIn(['user_lists', 'following', accountId, 'isLoading'], true),
|
isLoading: state.getIn(['user_lists', 'following', accountId, 'isLoading'], true),
|
||||||
suspended: state.getIn(['accounts', accountId, 'suspended'], false),
|
suspended: state.getIn(['accounts', accountId, 'suspended'], false),
|
||||||
|
hideCollections: state.getIn(['accounts', accountId, 'hide_collections'], false),
|
||||||
hidden: getAccountHidden(state, accountId),
|
hidden: getAccountHidden(state, accountId),
|
||||||
blockedBy: state.getIn(['relationships', accountId, 'blocked_by'], false),
|
blockedBy: state.getIn(['relationships', accountId, 'blocked_by'], false),
|
||||||
};
|
};
|
||||||
|
@ -111,7 +112,7 @@ class Following extends ImmutablePureComponent {
|
||||||
}, 300, { leading: true });
|
}, 300, { leading: true });
|
||||||
|
|
||||||
render () {
|
render () {
|
||||||
const { accountId, accountIds, hasMore, blockedBy, isAccount, multiColumn, isLoading, suspended, hidden, remote, remoteUrl } = this.props;
|
const { accountId, accountIds, hasMore, blockedBy, isAccount, multiColumn, isLoading, suspended, hidden, remote, remoteUrl, hideCollections } = this.props;
|
||||||
|
|
||||||
if (!isAccount) {
|
if (!isAccount) {
|
||||||
return (
|
return (
|
||||||
|
@ -137,6 +138,8 @@ class Following extends ImmutablePureComponent {
|
||||||
emptyMessage = <LimitedAccountHint accountId={accountId} />;
|
emptyMessage = <LimitedAccountHint accountId={accountId} />;
|
||||||
} else if (blockedBy) {
|
} else if (blockedBy) {
|
||||||
emptyMessage = <FormattedMessage id='empty_column.account_unavailable' defaultMessage='Profile unavailable' />;
|
emptyMessage = <FormattedMessage id='empty_column.account_unavailable' defaultMessage='Profile unavailable' />;
|
||||||
|
} else if (hideCollections && accountIds.isEmpty()) {
|
||||||
|
emptyMessage = <FormattedMessage id='empty_column.account_hides_collections' defaultMessage='This user has chosen to not make this information available' />;
|
||||||
} else if (remote && accountIds.isEmpty()) {
|
} else if (remote && accountIds.isEmpty()) {
|
||||||
emptyMessage = <RemoteHint url={remoteUrl} />;
|
emptyMessage = <RemoteHint url={remoteUrl} />;
|
||||||
} else {
|
} else {
|
||||||
|
|
|
@ -204,7 +204,7 @@ class ListTimeline extends PureComponent {
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className='setting-toggle'>
|
<div className='setting-toggle'>
|
||||||
<Toggle id={`list-${id}-exclusive`} defaultChecked={isExclusive} onChange={this.onExclusiveToggle} />
|
<Toggle id={`list-${id}-exclusive`} checked={isExclusive} onChange={this.onExclusiveToggle} />
|
||||||
<label htmlFor={`list-${id}-exclusive`} className='setting-toggle__label'>
|
<label htmlFor={`list-${id}-exclusive`} className='setting-toggle__label'>
|
||||||
<FormattedMessage id='lists.exclusive' defaultMessage='Hide these posts from home' />
|
<FormattedMessage id='lists.exclusive' defaultMessage='Hide these posts from home' />
|
||||||
</label>
|
</label>
|
||||||
|
|
|
@ -18,6 +18,8 @@ import { ReactComponent as ReplyAllIcon } from '@material-symbols/svg-600/outlin
|
||||||
import { ReactComponent as StarIcon } from '@material-symbols/svg-600/outlined/star-fill.svg';
|
import { ReactComponent as StarIcon } from '@material-symbols/svg-600/outlined/star-fill.svg';
|
||||||
import { ReactComponent as StarBorderIcon } from '@material-symbols/svg-600/outlined/star.svg';
|
import { ReactComponent as StarBorderIcon } from '@material-symbols/svg-600/outlined/star.svg';
|
||||||
|
|
||||||
|
import { ReactComponent as RepeatDisabledIcon } from 'mastodon/../svg-icons/repeat_disabled.svg';
|
||||||
|
import { ReactComponent as RepeatPrivateIcon } from 'mastodon/../svg-icons/repeat_private.svg';
|
||||||
import { PERMISSION_MANAGE_USERS, PERMISSION_MANAGE_FEDERATION } from 'mastodon/permissions';
|
import { PERMISSION_MANAGE_USERS, PERMISSION_MANAGE_FEDERATION } from 'mastodon/permissions';
|
||||||
import { WithRouterPropTypes } from 'mastodon/utils/react_router';
|
import { WithRouterPropTypes } from 'mastodon/utils/react_router';
|
||||||
|
|
||||||
|
@ -280,6 +282,7 @@ class ActionBar extends PureComponent {
|
||||||
|
|
||||||
let replyIcon;
|
let replyIcon;
|
||||||
let replyIconComponent;
|
let replyIconComponent;
|
||||||
|
|
||||||
if (status.get('in_reply_to_id', null) === null) {
|
if (status.get('in_reply_to_id', null) === null) {
|
||||||
replyIcon = 'reply';
|
replyIcon = 'reply';
|
||||||
replyIconComponent = ReplyIcon;
|
replyIconComponent = ReplyIcon;
|
||||||
|
@ -290,21 +293,26 @@ class ActionBar extends PureComponent {
|
||||||
|
|
||||||
const reblogPrivate = status.getIn(['account', 'id']) === me && status.get('visibility') === 'private';
|
const reblogPrivate = status.getIn(['account', 'id']) === me && status.get('visibility') === 'private';
|
||||||
|
|
||||||
let reblogTitle;
|
let reblogTitle, reblogIconComponent;
|
||||||
|
|
||||||
if (status.get('reblogged')) {
|
if (status.get('reblogged')) {
|
||||||
reblogTitle = intl.formatMessage(messages.cancel_reblog_private);
|
reblogTitle = intl.formatMessage(messages.cancel_reblog_private);
|
||||||
|
reblogIconComponent = publicStatus ? RepeatIcon : RepeatPrivateIcon;
|
||||||
} else if (publicStatus) {
|
} else if (publicStatus) {
|
||||||
reblogTitle = intl.formatMessage(messages.reblog);
|
reblogTitle = intl.formatMessage(messages.reblog);
|
||||||
|
reblogIconComponent = RepeatIcon;
|
||||||
} else if (reblogPrivate) {
|
} else if (reblogPrivate) {
|
||||||
reblogTitle = intl.formatMessage(messages.reblog_private);
|
reblogTitle = intl.formatMessage(messages.reblog_private);
|
||||||
|
reblogIconComponent = RepeatPrivateIcon;
|
||||||
} else {
|
} else {
|
||||||
reblogTitle = intl.formatMessage(messages.cannot_reblog);
|
reblogTitle = intl.formatMessage(messages.cannot_reblog);
|
||||||
|
reblogIconComponent = RepeatDisabledIcon;
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className='detailed-status__action-bar'>
|
<div className='detailed-status__action-bar'>
|
||||||
<div className='detailed-status__button'><IconButton title={intl.formatMessage(messages.reply)} icon={status.get('in_reply_to_account_id') === status.getIn(['account', 'id']) ? 'reply' : replyIcon} iconComponent={status.get('in_reply_to_account_id') === status.getIn(['account', 'id']) ? ReplyIcon : replyIconComponent} onClick={this.handleReplyClick} /></div>
|
<div className='detailed-status__button'><IconButton title={intl.formatMessage(messages.reply)} icon={status.get('in_reply_to_account_id') === status.getIn(['account', 'id']) ? 'reply' : replyIcon} iconComponent={status.get('in_reply_to_account_id') === status.getIn(['account', 'id']) ? ReplyIcon : replyIconComponent} onClick={this.handleReplyClick} /></div>
|
||||||
<div className='detailed-status__button'><IconButton className={classNames({ reblogPrivate })} disabled={!publicStatus && !reblogPrivate} active={status.get('reblogged')} title={reblogTitle} icon='retweet' iconComponent={RepeatIcon} onClick={this.handleReblogClick} /></div>
|
<div className='detailed-status__button'><IconButton className={classNames({ reblogPrivate })} disabled={!publicStatus && !reblogPrivate} active={status.get('reblogged')} title={reblogTitle} icon='retweet' iconComponent={reblogIconComponent} onClick={this.handleReblogClick} /></div>
|
||||||
<div className='detailed-status__button'><IconButton className='star-icon' animate active={status.get('favourited')} title={intl.formatMessage(messages.favourite)} icon='star' iconComponent={status.get('favourited') ? StarIcon : StarBorderIcon} onClick={this.handleFavouriteClick} /></div>
|
<div className='detailed-status__button'><IconButton className='star-icon' animate active={status.get('favourited')} title={intl.formatMessage(messages.favourite)} icon='star' iconComponent={status.get('favourited') ? StarIcon : StarBorderIcon} onClick={this.handleFavouriteClick} /></div>
|
||||||
<div className='detailed-status__button'><IconButton className='bookmark-icon' disabled={!signedIn} active={status.get('bookmarked')} title={intl.formatMessage(messages.bookmark)} icon='bookmark' iconComponent={status.get('bookmarked') ? BookmarkIcon : BookmarkBorderIcon} onClick={this.handleBookmarkClick} /></div>
|
<div className='detailed-status__button'><IconButton className='bookmark-icon' disabled={!signedIn} active={status.get('bookmarked')} title={intl.formatMessage(messages.bookmark)} icon='bookmark' iconComponent={status.get('bookmarked') ? BookmarkIcon : BookmarkBorderIcon} onClick={this.handleBookmarkClick} /></div>
|
||||||
|
|
||||||
|
|
|
@ -19,8 +19,10 @@ import { throttle } from 'lodash';
|
||||||
|
|
||||||
import { Blurhash } from 'mastodon/components/blurhash';
|
import { Blurhash } from 'mastodon/components/blurhash';
|
||||||
import { Icon } from 'mastodon/components/icon';
|
import { Icon } from 'mastodon/components/icon';
|
||||||
|
import { playerSettings } from 'mastodon/settings';
|
||||||
|
|
||||||
import { displayMedia, useBlurhash } from '../../initial_state';
|
import { displayMedia, useBlurhash } from '../../initial_state';
|
||||||
|
import { currentMedia, setCurrentMedia } from '../../reducers/media_attachments';
|
||||||
import { isFullscreen, requestFullscreen, exitFullscreen } from '../ui/util/fullscreen';
|
import { isFullscreen, requestFullscreen, exitFullscreen } from '../ui/util/fullscreen';
|
||||||
|
|
||||||
const messages = defineMessages({
|
const messages = defineMessages({
|
||||||
|
@ -180,6 +182,7 @@ class Video extends PureComponent {
|
||||||
};
|
};
|
||||||
|
|
||||||
handlePause = () => {
|
handlePause = () => {
|
||||||
|
this.video.pause();
|
||||||
this.setState({ paused: true });
|
this.setState({ paused: true });
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -226,8 +229,8 @@ class Video extends PureComponent {
|
||||||
|
|
||||||
if(!isNaN(x)) {
|
if(!isNaN(x)) {
|
||||||
this.setState((state) => ({ volume: x, muted: state.muted && x === 0 }), () => {
|
this.setState((state) => ({ volume: x, muted: state.muted && x === 0 }), () => {
|
||||||
this.video.volume = x;
|
this._syncVideoToVolumeState(x);
|
||||||
this.video.muted = this.state.muted;
|
this._saveVolumeState(x);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}, 15);
|
}, 15);
|
||||||
|
@ -343,11 +346,32 @@ class Video extends PureComponent {
|
||||||
};
|
};
|
||||||
|
|
||||||
togglePlay = () => {
|
togglePlay = () => {
|
||||||
if (this.state.paused) {
|
const videos = document.querySelectorAll('video');
|
||||||
this.setState({ paused: false }, () => this.video.play());
|
|
||||||
} else {
|
videos.forEach((video) => {
|
||||||
this.setState({ paused: true }, () => this.video.pause());
|
const button = video.nextElementSibling;
|
||||||
|
button.addEventListener('click', () => {
|
||||||
|
if (video.paused) {
|
||||||
|
videos.forEach((e) => {
|
||||||
|
if (e !== video) {
|
||||||
|
e.pause();
|
||||||
}
|
}
|
||||||
|
});
|
||||||
|
video.play();
|
||||||
|
this.setState({ paused: false });
|
||||||
|
} else {
|
||||||
|
video.pause();
|
||||||
|
this.setState({ paused: true });
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
if (currentMedia !== null) {
|
||||||
|
currentMedia.pause();
|
||||||
|
}
|
||||||
|
|
||||||
|
this.video.play();
|
||||||
|
setCurrentMedia(this.video);
|
||||||
};
|
};
|
||||||
|
|
||||||
toggleFullscreen = () => {
|
toggleFullscreen = () => {
|
||||||
|
@ -365,6 +389,8 @@ class Video extends PureComponent {
|
||||||
document.addEventListener('MSFullscreenChange', this.handleFullscreenChange, true);
|
document.addEventListener('MSFullscreenChange', this.handleFullscreenChange, true);
|
||||||
|
|
||||||
window.addEventListener('scroll', this.handleScroll);
|
window.addEventListener('scroll', this.handleScroll);
|
||||||
|
|
||||||
|
this._syncVideoFromLocalStorage();
|
||||||
}
|
}
|
||||||
|
|
||||||
componentWillUnmount () {
|
componentWillUnmount () {
|
||||||
|
@ -437,8 +463,28 @@ class Video extends PureComponent {
|
||||||
const muted = !(this.video.muted || this.state.volume === 0);
|
const muted = !(this.video.muted || this.state.volume === 0);
|
||||||
|
|
||||||
this.setState((state) => ({ muted, volume: Math.max(state.volume || 0.5, 0.05) }), () => {
|
this.setState((state) => ({ muted, volume: Math.max(state.volume || 0.5, 0.05) }), () => {
|
||||||
this.video.volume = this.state.volume;
|
this._syncVideoToVolumeState();
|
||||||
this.video.muted = this.state.muted;
|
this._saveVolumeState();
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
_syncVideoToVolumeState = (volume = null, muted = null) => {
|
||||||
|
if (!this.video) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
this.video.volume = volume ?? this.state.volume;
|
||||||
|
this.video.muted = muted ?? this.state.muted;
|
||||||
|
};
|
||||||
|
|
||||||
|
_saveVolumeState = (volume = null, muted = null) => {
|
||||||
|
playerSettings.set('volume', volume ?? this.state.volume);
|
||||||
|
playerSettings.set('muted', muted ?? this.state.muted);
|
||||||
|
};
|
||||||
|
|
||||||
|
_syncVideoFromLocalStorage = () => {
|
||||||
|
this.setState({ volume: playerSettings.get('volume') ?? 0.5, muted: playerSettings.get('muted') ?? false }, () => {
|
||||||
|
this._syncVideoToVolumeState();
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -480,6 +526,7 @@ class Video extends PureComponent {
|
||||||
|
|
||||||
handleVolumeChange = () => {
|
handleVolumeChange = () => {
|
||||||
this.setState({ volume: this.video.volume, muted: this.video.muted });
|
this.setState({ volume: this.video.volume, muted: this.video.muted });
|
||||||
|
this._saveVolumeState(this.video.volume, this.video.muted);
|
||||||
};
|
};
|
||||||
|
|
||||||
handleOpenVideo = () => {
|
handleOpenVideo = () => {
|
||||||
|
|
|
@ -14,6 +14,7 @@
|
||||||
"account.badges.group": "Groep",
|
"account.badges.group": "Groep",
|
||||||
"account.block": "Blokkeer @{name}",
|
"account.block": "Blokkeer @{name}",
|
||||||
"account.block_domain": "Blokkeer domein {domain}",
|
"account.block_domain": "Blokkeer domein {domain}",
|
||||||
|
"account.block_short": "Blokkeer",
|
||||||
"account.blocked": "Geblokkeer",
|
"account.blocked": "Geblokkeer",
|
||||||
"account.browse_more_on_origin_server": "Verken die oorspronklike profiel",
|
"account.browse_more_on_origin_server": "Verken die oorspronklike profiel",
|
||||||
"account.cancel_follow_request": "Herroep volgversoek",
|
"account.cancel_follow_request": "Herroep volgversoek",
|
||||||
|
@ -45,6 +46,7 @@
|
||||||
"account.posts_with_replies": "Plasings en antwoorde",
|
"account.posts_with_replies": "Plasings en antwoorde",
|
||||||
"account.report": "Rapporteer @{name}",
|
"account.report": "Rapporteer @{name}",
|
||||||
"account.requested": "Wag op goedkeuring. Klik om volgversoek te kanselleer",
|
"account.requested": "Wag op goedkeuring. Klik om volgversoek te kanselleer",
|
||||||
|
"account.requested_follow": "{name} het versoek om jou te volg",
|
||||||
"account.share": "Deel @{name} se profiel",
|
"account.share": "Deel @{name} se profiel",
|
||||||
"account.show_reblogs": "Wys aangestuurde plasings van @{name}",
|
"account.show_reblogs": "Wys aangestuurde plasings van @{name}",
|
||||||
"account.statuses_counter": "{count, plural, one {{counter} Plaas} other {{counter} Plasings}}",
|
"account.statuses_counter": "{count, plural, one {{counter} Plaas} other {{counter} Plasings}}",
|
||||||
|
@ -82,6 +84,7 @@
|
||||||
"column.community": "Plaaslike tydlyn",
|
"column.community": "Plaaslike tydlyn",
|
||||||
"column.directory": "Blaai deur profiele",
|
"column.directory": "Blaai deur profiele",
|
||||||
"column.domain_blocks": "Geblokkeerde domeine",
|
"column.domain_blocks": "Geblokkeerde domeine",
|
||||||
|
"column.favourites": "Gunstelinge",
|
||||||
"column.follow_requests": "Volgversoeke",
|
"column.follow_requests": "Volgversoeke",
|
||||||
"column.home": "Tuis",
|
"column.home": "Tuis",
|
||||||
"column.lists": "Lyste",
|
"column.lists": "Lyste",
|
||||||
|
@ -271,6 +274,7 @@
|
||||||
"privacy.unlisted.short": "Ongelys",
|
"privacy.unlisted.short": "Ongelys",
|
||||||
"privacy_policy.last_updated": "Laaste bywerking op {date}",
|
"privacy_policy.last_updated": "Laaste bywerking op {date}",
|
||||||
"privacy_policy.title": "Privaatheidsbeleid",
|
"privacy_policy.title": "Privaatheidsbeleid",
|
||||||
|
"regeneration_indicator.sublabel": "Jou tuis-voer word voorberei!",
|
||||||
"reply_indicator.cancel": "Kanselleer",
|
"reply_indicator.cancel": "Kanselleer",
|
||||||
"report.placeholder": "Type or paste additional comments",
|
"report.placeholder": "Type or paste additional comments",
|
||||||
"report.submit": "Submit report",
|
"report.submit": "Submit report",
|
||||||
|
|
|
@ -201,7 +201,7 @@
|
||||||
"disabled_account_banner.text": "Ваш уліковы запіс {disabledAccount} часова адключаны.",
|
"disabled_account_banner.text": "Ваш уліковы запіс {disabledAccount} часова адключаны.",
|
||||||
"dismissable_banner.community_timeline": "Гэта самыя апошнія допісы ад людзей, уліковыя запісы якіх размяшчаюцца на {domain}.",
|
"dismissable_banner.community_timeline": "Гэта самыя апошнія допісы ад людзей, уліковыя запісы якіх размяшчаюцца на {domain}.",
|
||||||
"dismissable_banner.dismiss": "Адхіліць",
|
"dismissable_banner.dismiss": "Адхіліць",
|
||||||
"dismissable_banner.explore_links": "Гэтыя навіны абмяркоўваюцца прама зараз на гэтым і іншых серверах дэцэнтралізаванай сеткі.",
|
"dismissable_banner.explore_links": "Гэтыя навіны абмяркоўваюцца цяпер на гэтым і іншых серверах дэцэнтралізаванай сеткі.",
|
||||||
"dismissable_banner.explore_statuses": "Допісы з гэтага і іншых сервераў дэцэнтралізаванай сеткі, якія набіраюць папулярнасць прама зараз.",
|
"dismissable_banner.explore_statuses": "Допісы з гэтага і іншых сервераў дэцэнтралізаванай сеткі, якія набіраюць папулярнасць прама зараз.",
|
||||||
"dismissable_banner.explore_tags": "Гэтыя хэштэгі зараз набіраюць папулярнасць сярод людзей на гэтым і іншых серверах дэцэнтралізаванай сеткі",
|
"dismissable_banner.explore_tags": "Гэтыя хэштэгі зараз набіраюць папулярнасць сярод людзей на гэтым і іншых серверах дэцэнтралізаванай сеткі",
|
||||||
"dismissable_banner.public_timeline": "Гэта апошнія публічныя допісы людзей з усей сеткі, за якімі сочаць карыстальнікі {domain}.",
|
"dismissable_banner.public_timeline": "Гэта апошнія публічныя допісы людзей з усей сеткі, за якімі сочаць карыстальнікі {domain}.",
|
||||||
|
@ -222,6 +222,7 @@
|
||||||
"emoji_button.search_results": "Вынікі пошуку",
|
"emoji_button.search_results": "Вынікі пошуку",
|
||||||
"emoji_button.symbols": "Сімвалы",
|
"emoji_button.symbols": "Сімвалы",
|
||||||
"emoji_button.travel": "Падарожжы і месцы",
|
"emoji_button.travel": "Падарожжы і месцы",
|
||||||
|
"empty_column.account_hides_collections": "Гэты карыстальнік вырашыў схаваць гэтую інфармацыю",
|
||||||
"empty_column.account_suspended": "Уліковы запіс прыпынены",
|
"empty_column.account_suspended": "Уліковы запіс прыпынены",
|
||||||
"empty_column.account_timeline": "Тут няма допісаў!",
|
"empty_column.account_timeline": "Тут няма допісаў!",
|
||||||
"empty_column.account_unavailable": "Профіль недаступны",
|
"empty_column.account_unavailable": "Профіль недаступны",
|
||||||
|
@ -481,7 +482,7 @@
|
||||||
"onboarding.share.lead": "Дайце людзям ведаць, як яны могуць знайсці вас на Mastodon!",
|
"onboarding.share.lead": "Дайце людзям ведаць, як яны могуць знайсці вас на Mastodon!",
|
||||||
"onboarding.share.message": "Я {username} на #Mastodon! Сачыце за мной на {url}",
|
"onboarding.share.message": "Я {username} на #Mastodon! Сачыце за мной на {url}",
|
||||||
"onboarding.share.next_steps": "Магчымыя наступныя крокі:",
|
"onboarding.share.next_steps": "Магчымыя наступныя крокі:",
|
||||||
"onboarding.share.title": "Падзяліцеся сваім профілем",
|
"onboarding.share.title": "Абагульце свой профіль",
|
||||||
"onboarding.start.lead": "Your new Mastodon account is ready to go. Here's how you can make the most of it:",
|
"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.skip": "Want to skip right ahead?",
|
||||||
"onboarding.start.title": "Вы зрабілі гэта!",
|
"onboarding.start.title": "Вы зрабілі гэта!",
|
||||||
|
@ -492,7 +493,7 @@
|
||||||
"onboarding.steps.setup_profile.body": "Others are more likely to interact with you with a filled out profile.",
|
"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.setup_profile.title": "Customize your profile",
|
||||||
"onboarding.steps.share_profile.body": "Let your friends know how to find you on Mastodon!",
|
"onboarding.steps.share_profile.body": "Let your friends know how to find you on Mastodon!",
|
||||||
"onboarding.steps.share_profile.title": "Share your profile",
|
"onboarding.steps.share_profile.title": "Абагульць ваш профіль у Mastodon",
|
||||||
"onboarding.tips.2fa": "<strong>Ці вы ведаеце?</strong> Вы можаце абараніць свой уліковы запіс, усталяваўшы двухфактарную аўтэнтыфікацыю ў наладах уліковага запісу. Яна працуе з любой праграмай TOTP на ваш выбар, нумар тэлефона не патрэбны!",
|
"onboarding.tips.2fa": "<strong>Ці вы ведаеце?</strong> Вы можаце абараніць свой уліковы запіс, усталяваўшы двухфактарную аўтэнтыфікацыю ў наладах уліковага запісу. Яна працуе з любой праграмай TOTP на ваш выбар, нумар тэлефона не патрэбны!",
|
||||||
"onboarding.tips.accounts_from_other_servers": "<strong>Ці вы ведаеце?</strong> Паколькі Mastodon дэцэнтралізаваны, некаторыя профілі, якія вам трапляюцца, будуць размяшчацца на іншых серверах, адрозных ад вашага. І ўсё ж вы можаце бесперашкодна ўзаемадзейнічаць з імі! Іх сервер пазначаны ў другой палове імя карыстальніка!",
|
"onboarding.tips.accounts_from_other_servers": "<strong>Ці вы ведаеце?</strong> Паколькі Mastodon дэцэнтралізаваны, некаторыя профілі, якія вам трапляюцца, будуць размяшчацца на іншых серверах, адрозных ад вашага. І ўсё ж вы можаце бесперашкодна ўзаемадзейнічаць з імі! Іх сервер пазначаны ў другой палове імя карыстальніка!",
|
||||||
"onboarding.tips.migration": "<strong>Ці вы ведаеце?</strong> Калі вы адчуваеце, што {domain} не з'яўляецца для вас лепшым выбарам у будучыні, вы можаце перайсці на іншы сервер Mastodon, не губляючы сваіх падпісчыкаў. Вы нават можаце стварыць свой уласны сервер!",
|
"onboarding.tips.migration": "<strong>Ці вы ведаеце?</strong> Калі вы адчуваеце, што {domain} не з'яўляецца для вас лепшым выбарам у будучыні, вы можаце перайсці на іншы сервер Mastodon, не губляючы сваіх падпісчыкаў. Вы нават можаце стварыць свой уласны сервер!",
|
||||||
|
|
|
@ -222,6 +222,7 @@
|
||||||
"emoji_button.search_results": "Resultats de la cerca",
|
"emoji_button.search_results": "Resultats de la cerca",
|
||||||
"emoji_button.symbols": "Símbols",
|
"emoji_button.symbols": "Símbols",
|
||||||
"emoji_button.travel": "Viatges i llocs",
|
"emoji_button.travel": "Viatges i llocs",
|
||||||
|
"empty_column.account_hides_collections": "Aquest usuari ha elegit no mostrar aquesta informació",
|
||||||
"empty_column.account_suspended": "Compte suspès",
|
"empty_column.account_suspended": "Compte suspès",
|
||||||
"empty_column.account_timeline": "No hi ha tuts aquí!",
|
"empty_column.account_timeline": "No hi ha tuts aquí!",
|
||||||
"empty_column.account_unavailable": "Perfil no disponible",
|
"empty_column.account_unavailable": "Perfil no disponible",
|
||||||
|
|
|
@ -222,6 +222,7 @@
|
||||||
"emoji_button.search_results": "Canlyniadau chwilio",
|
"emoji_button.search_results": "Canlyniadau chwilio",
|
||||||
"emoji_button.symbols": "Symbolau",
|
"emoji_button.symbols": "Symbolau",
|
||||||
"emoji_button.travel": "Teithio a Llefydd",
|
"emoji_button.travel": "Teithio a Llefydd",
|
||||||
|
"empty_column.account_hides_collections": "Mae'r defnyddiwr wedi dewis i beidio rhannu'r wybodaeth yma",
|
||||||
"empty_column.account_suspended": "Cyfrif wedi'i atal",
|
"empty_column.account_suspended": "Cyfrif wedi'i atal",
|
||||||
"empty_column.account_timeline": "Dim postiadau yma!",
|
"empty_column.account_timeline": "Dim postiadau yma!",
|
||||||
"empty_column.account_unavailable": "Nid yw'r proffil ar gael",
|
"empty_column.account_unavailable": "Nid yw'r proffil ar gael",
|
||||||
|
|
|
@ -222,6 +222,7 @@
|
||||||
"emoji_button.search_results": "Søgeresultater",
|
"emoji_button.search_results": "Søgeresultater",
|
||||||
"emoji_button.symbols": "Symboler",
|
"emoji_button.symbols": "Symboler",
|
||||||
"emoji_button.travel": "Rejser og steder",
|
"emoji_button.travel": "Rejser og steder",
|
||||||
|
"empty_column.account_hides_collections": "Brugeren har valgt ikke at gøre denne information tilgængelig",
|
||||||
"empty_column.account_suspended": "Konto suspenderet",
|
"empty_column.account_suspended": "Konto suspenderet",
|
||||||
"empty_column.account_timeline": "Ingen indlæg hér!",
|
"empty_column.account_timeline": "Ingen indlæg hér!",
|
||||||
"empty_column.account_unavailable": "Profil utilgængelig",
|
"empty_column.account_unavailable": "Profil utilgængelig",
|
||||||
|
|
|
@ -88,7 +88,7 @@
|
||||||
"attachments_list.unprocessed": "(ausstehend)",
|
"attachments_list.unprocessed": "(ausstehend)",
|
||||||
"audio.hide": "Audio ausblenden",
|
"audio.hide": "Audio ausblenden",
|
||||||
"autosuggest_hashtag.per_week": "{count} pro Woche",
|
"autosuggest_hashtag.per_week": "{count} pro Woche",
|
||||||
"boost_modal.combo": "Drücke {combo}, um das beim nächsten Mal zu überspringen",
|
"boost_modal.combo": "Mit {combo} wird dieses Fenster beim nächsten Mal nicht mehr angezeigt",
|
||||||
"bundle_column_error.copy_stacktrace": "Fehlerbericht kopieren",
|
"bundle_column_error.copy_stacktrace": "Fehlerbericht kopieren",
|
||||||
"bundle_column_error.error.body": "Die angeforderte Seite konnte nicht dargestellt werden. Dies könnte auf einen Fehler in unserem Code oder auf ein Browser-Kompatibilitätsproblem zurückzuführen sein.",
|
"bundle_column_error.error.body": "Die angeforderte Seite konnte nicht dargestellt werden. Dies könnte auf einen Fehler in unserem Code oder auf ein Browser-Kompatibilitätsproblem zurückzuführen sein.",
|
||||||
"bundle_column_error.error.title": "Oh nein!",
|
"bundle_column_error.error.title": "Oh nein!",
|
||||||
|
@ -222,6 +222,7 @@
|
||||||
"emoji_button.search_results": "Suchergebnisse",
|
"emoji_button.search_results": "Suchergebnisse",
|
||||||
"emoji_button.symbols": "Symbole",
|
"emoji_button.symbols": "Symbole",
|
||||||
"emoji_button.travel": "Reisen & Orte",
|
"emoji_button.travel": "Reisen & Orte",
|
||||||
|
"empty_column.account_hides_collections": "Das Konto hat sich dazu entschieden, diese Information nicht zu veröffentlichen",
|
||||||
"empty_column.account_suspended": "Konto gesperrt",
|
"empty_column.account_suspended": "Konto gesperrt",
|
||||||
"empty_column.account_timeline": "Keine Beiträge vorhanden!",
|
"empty_column.account_timeline": "Keine Beiträge vorhanden!",
|
||||||
"empty_column.account_unavailable": "Profil nicht verfügbar",
|
"empty_column.account_unavailable": "Profil nicht verfügbar",
|
||||||
|
|
|
@ -222,6 +222,7 @@
|
||||||
"emoji_button.search_results": "Search results",
|
"emoji_button.search_results": "Search results",
|
||||||
"emoji_button.symbols": "Symbols",
|
"emoji_button.symbols": "Symbols",
|
||||||
"emoji_button.travel": "Travel & Places",
|
"emoji_button.travel": "Travel & Places",
|
||||||
|
"empty_column.account_hides_collections": "This user has chosen to not make this information available",
|
||||||
"empty_column.account_suspended": "Account suspended",
|
"empty_column.account_suspended": "Account suspended",
|
||||||
"empty_column.account_timeline": "No posts here!",
|
"empty_column.account_timeline": "No posts here!",
|
||||||
"empty_column.account_unavailable": "Profile unavailable",
|
"empty_column.account_unavailable": "Profile unavailable",
|
||||||
|
|
|
@ -222,6 +222,7 @@
|
||||||
"emoji_button.search_results": "Resultados de búsqueda",
|
"emoji_button.search_results": "Resultados de búsqueda",
|
||||||
"emoji_button.symbols": "Símbolos",
|
"emoji_button.symbols": "Símbolos",
|
||||||
"emoji_button.travel": "Viajes y lugares",
|
"emoji_button.travel": "Viajes y lugares",
|
||||||
|
"empty_column.account_hides_collections": "Este usuario eligió no publicar esta información",
|
||||||
"empty_column.account_suspended": "Cuenta suspendida",
|
"empty_column.account_suspended": "Cuenta suspendida",
|
||||||
"empty_column.account_timeline": "¡No hay mensajes acá!",
|
"empty_column.account_timeline": "¡No hay mensajes acá!",
|
||||||
"empty_column.account_unavailable": "Perfil no disponible",
|
"empty_column.account_unavailable": "Perfil no disponible",
|
||||||
|
|
|
@ -222,6 +222,7 @@
|
||||||
"emoji_button.search_results": "Resultados de búsqueda",
|
"emoji_button.search_results": "Resultados de búsqueda",
|
||||||
"emoji_button.symbols": "Símbolos",
|
"emoji_button.symbols": "Símbolos",
|
||||||
"emoji_button.travel": "Viajes y lugares",
|
"emoji_button.travel": "Viajes y lugares",
|
||||||
|
"empty_column.account_hides_collections": "Este usuario ha elegido no hacer disponible esta información",
|
||||||
"empty_column.account_suspended": "Cuenta suspendida",
|
"empty_column.account_suspended": "Cuenta suspendida",
|
||||||
"empty_column.account_timeline": "¡No hay toots aquí!",
|
"empty_column.account_timeline": "¡No hay toots aquí!",
|
||||||
"empty_column.account_unavailable": "Perfil no disponible",
|
"empty_column.account_unavailable": "Perfil no disponible",
|
||||||
|
|
|
@ -222,6 +222,7 @@
|
||||||
"emoji_button.search_results": "Resultados de búsqueda",
|
"emoji_button.search_results": "Resultados de búsqueda",
|
||||||
"emoji_button.symbols": "Símbolos",
|
"emoji_button.symbols": "Símbolos",
|
||||||
"emoji_button.travel": "Viajes y lugares",
|
"emoji_button.travel": "Viajes y lugares",
|
||||||
|
"empty_column.account_hides_collections": "Este usuario ha decidido no mostrar esta información",
|
||||||
"empty_column.account_suspended": "Cuenta suspendida",
|
"empty_column.account_suspended": "Cuenta suspendida",
|
||||||
"empty_column.account_timeline": "¡No hay publicaciones aquí!",
|
"empty_column.account_timeline": "¡No hay publicaciones aquí!",
|
||||||
"empty_column.account_unavailable": "Perfil no disponible",
|
"empty_column.account_unavailable": "Perfil no disponible",
|
||||||
|
|
|
@ -222,6 +222,7 @@
|
||||||
"emoji_button.search_results": "Otsitulemused",
|
"emoji_button.search_results": "Otsitulemused",
|
||||||
"emoji_button.symbols": "Sümbolid",
|
"emoji_button.symbols": "Sümbolid",
|
||||||
"emoji_button.travel": "Reisimine & kohad",
|
"emoji_button.travel": "Reisimine & kohad",
|
||||||
|
"empty_column.account_hides_collections": "See kasutaja otsustas mitte teha seda infot saadavaks",
|
||||||
"empty_column.account_suspended": "Konto kustutatud",
|
"empty_column.account_suspended": "Konto kustutatud",
|
||||||
"empty_column.account_timeline": "Siin postitusi ei ole!",
|
"empty_column.account_timeline": "Siin postitusi ei ole!",
|
||||||
"empty_column.account_unavailable": "Profiil pole saadaval",
|
"empty_column.account_unavailable": "Profiil pole saadaval",
|
||||||
|
|
|
@ -222,6 +222,7 @@
|
||||||
"emoji_button.search_results": "Bilaketaren emaitzak",
|
"emoji_button.search_results": "Bilaketaren emaitzak",
|
||||||
"emoji_button.symbols": "Sinboloak",
|
"emoji_button.symbols": "Sinboloak",
|
||||||
"emoji_button.travel": "Bidaiak eta tokiak",
|
"emoji_button.travel": "Bidaiak eta tokiak",
|
||||||
|
"empty_column.account_hides_collections": "Erabiltzaile honek informazio hau erabilgarri ez egotea aukeratu du.",
|
||||||
"empty_column.account_suspended": "Kanporatutako kontua",
|
"empty_column.account_suspended": "Kanporatutako kontua",
|
||||||
"empty_column.account_timeline": "Ez dago bidalketarik hemen!",
|
"empty_column.account_timeline": "Ez dago bidalketarik hemen!",
|
||||||
"empty_column.account_unavailable": "Profila ez dago eskuragarri",
|
"empty_column.account_unavailable": "Profila ez dago eskuragarri",
|
||||||
|
|
|
@ -222,6 +222,7 @@
|
||||||
"emoji_button.search_results": "نتایج جستوجو",
|
"emoji_button.search_results": "نتایج جستوجو",
|
||||||
"emoji_button.symbols": "نمادها",
|
"emoji_button.symbols": "نمادها",
|
||||||
"emoji_button.travel": "سفر و مکان",
|
"emoji_button.travel": "سفر و مکان",
|
||||||
|
"empty_column.account_hides_collections": "کاربر خواسته که این اطّلاعات در دسترس نباشند",
|
||||||
"empty_column.account_suspended": "حساب معلق شد",
|
"empty_column.account_suspended": "حساب معلق شد",
|
||||||
"empty_column.account_timeline": "هیچ فرستهای اینجا نیست!",
|
"empty_column.account_timeline": "هیچ فرستهای اینجا نیست!",
|
||||||
"empty_column.account_unavailable": "نمایهٔ موجود نیست",
|
"empty_column.account_unavailable": "نمایهٔ موجود نیست",
|
||||||
|
@ -358,13 +359,13 @@
|
||||||
"keyboard_shortcuts.profile": "گشودن نمایهٔ نویسنده",
|
"keyboard_shortcuts.profile": "گشودن نمایهٔ نویسنده",
|
||||||
"keyboard_shortcuts.reply": "پاسخ به فرسته",
|
"keyboard_shortcuts.reply": "پاسخ به فرسته",
|
||||||
"keyboard_shortcuts.requests": "گشودن سیاههٔ درخواستهای پیگیری",
|
"keyboard_shortcuts.requests": "گشودن سیاههٔ درخواستهای پیگیری",
|
||||||
"keyboard_shortcuts.search": "تمرکز روی جستوجو",
|
"keyboard_shortcuts.search": "تمرکز روی نوار جستوجو",
|
||||||
"keyboard_shortcuts.spoilers": "نمایش/نهفتن زمینهٔ هشدار محتوا",
|
"keyboard_shortcuts.spoilers": "نمایش/نهفتن زمینهٔ هشدار محتوا",
|
||||||
"keyboard_shortcuts.start": "گشودن ستون «آغاز کنید»",
|
"keyboard_shortcuts.start": "گشودن ستون «آغاز کنید»",
|
||||||
"keyboard_shortcuts.toggle_hidden": "نمایش/نهفتن نوشتهٔ پشت هشدار محتوا",
|
"keyboard_shortcuts.toggle_hidden": "نمایش/نهفتن نوشتهٔ پشت هشدار محتوا",
|
||||||
"keyboard_shortcuts.toggle_sensitivity": "نمایش/نهفتن رسانه",
|
"keyboard_shortcuts.toggle_sensitivity": "نمایش/نهفتن رسانه",
|
||||||
"keyboard_shortcuts.toot": "شروع یک فرستهٔ جدید",
|
"keyboard_shortcuts.toot": "شروع یک فرستهٔ جدید",
|
||||||
"keyboard_shortcuts.unfocus": "برداشتن تمرکز از نوشتن/جستوجو",
|
"keyboard_shortcuts.unfocus": "برداشتن تمرکز از ناحیهٔ نوشتن یا جستوجو",
|
||||||
"keyboard_shortcuts.up": "بالا بردن در سیاهه",
|
"keyboard_shortcuts.up": "بالا بردن در سیاهه",
|
||||||
"lightbox.close": "بستن",
|
"lightbox.close": "بستن",
|
||||||
"lightbox.compress": "فشردهسازی جعبهٔ نمایش تصویر",
|
"lightbox.compress": "فشردهسازی جعبهٔ نمایش تصویر",
|
||||||
|
|
|
@ -222,6 +222,7 @@
|
||||||
"emoji_button.search_results": "Hakutulokset",
|
"emoji_button.search_results": "Hakutulokset",
|
||||||
"emoji_button.symbols": "Symbolit",
|
"emoji_button.symbols": "Symbolit",
|
||||||
"emoji_button.travel": "Matkailu ja paikat",
|
"emoji_button.travel": "Matkailu ja paikat",
|
||||||
|
"empty_column.account_hides_collections": "Käyttäjä on päättänyt olla julkaisematta näitä tietoja",
|
||||||
"empty_column.account_suspended": "Tili jäädytetty",
|
"empty_column.account_suspended": "Tili jäädytetty",
|
||||||
"empty_column.account_timeline": "Ei viestejä täällä.",
|
"empty_column.account_timeline": "Ei viestejä täällä.",
|
||||||
"empty_column.account_unavailable": "Profiilia ei löydy",
|
"empty_column.account_unavailable": "Profiilia ei löydy",
|
||||||
|
|
|
@ -222,6 +222,7 @@
|
||||||
"emoji_button.search_results": "Leitiúrslit",
|
"emoji_button.search_results": "Leitiúrslit",
|
||||||
"emoji_button.symbols": "Ímyndir",
|
"emoji_button.symbols": "Ímyndir",
|
||||||
"emoji_button.travel": "Ferðing og støð",
|
"emoji_button.travel": "Ferðing og støð",
|
||||||
|
"empty_column.account_hides_collections": "Hesin brúkarin hevur valt, at hesar upplýsingarnar ikki skulu vera tøkar",
|
||||||
"empty_column.account_suspended": "Kontan gjørd óvirkin",
|
"empty_column.account_suspended": "Kontan gjørd óvirkin",
|
||||||
"empty_column.account_timeline": "Einki uppslag her!",
|
"empty_column.account_timeline": "Einki uppslag her!",
|
||||||
"empty_column.account_unavailable": "Vangin er ikki tøkur",
|
"empty_column.account_unavailable": "Vangin er ikki tøkur",
|
||||||
|
|
|
@ -222,6 +222,7 @@
|
||||||
"emoji_button.search_results": "Résultats",
|
"emoji_button.search_results": "Résultats",
|
||||||
"emoji_button.symbols": "Symboles",
|
"emoji_button.symbols": "Symboles",
|
||||||
"emoji_button.travel": "Voyage et lieux",
|
"emoji_button.travel": "Voyage et lieux",
|
||||||
|
"empty_column.account_hides_collections": "Cet utilisateur·ice préfère ne pas rendre publiques ces informations",
|
||||||
"empty_column.account_suspended": "Compte suspendu",
|
"empty_column.account_suspended": "Compte suspendu",
|
||||||
"empty_column.account_timeline": "Aucune publication ici!",
|
"empty_column.account_timeline": "Aucune publication ici!",
|
||||||
"empty_column.account_unavailable": "Profil non disponible",
|
"empty_column.account_unavailable": "Profil non disponible",
|
||||||
|
|
|
@ -222,6 +222,7 @@
|
||||||
"emoji_button.search_results": "Résultats de la recherche",
|
"emoji_button.search_results": "Résultats de la recherche",
|
||||||
"emoji_button.symbols": "Symboles",
|
"emoji_button.symbols": "Symboles",
|
||||||
"emoji_button.travel": "Voyage et lieux",
|
"emoji_button.travel": "Voyage et lieux",
|
||||||
|
"empty_column.account_hides_collections": "Cet utilisateur·ice préfère ne pas rendre publiques ces informations",
|
||||||
"empty_column.account_suspended": "Compte suspendu",
|
"empty_column.account_suspended": "Compte suspendu",
|
||||||
"empty_column.account_timeline": "Aucun message ici !",
|
"empty_column.account_timeline": "Aucun message ici !",
|
||||||
"empty_column.account_unavailable": "Profil non disponible",
|
"empty_column.account_unavailable": "Profil non disponible",
|
||||||
|
|
|
@ -222,6 +222,7 @@
|
||||||
"emoji_button.search_results": "Sykresultaten",
|
"emoji_button.search_results": "Sykresultaten",
|
||||||
"emoji_button.symbols": "Symboalen",
|
"emoji_button.symbols": "Symboalen",
|
||||||
"emoji_button.travel": "Reizgje en lokaasjes",
|
"emoji_button.travel": "Reizgje en lokaasjes",
|
||||||
|
"empty_column.account_hides_collections": "Dizze brûker hat derfoar keazen dizze ynformaasje net beskikber te meitsjen",
|
||||||
"empty_column.account_suspended": "Account beskoattele",
|
"empty_column.account_suspended": "Account beskoattele",
|
||||||
"empty_column.account_timeline": "Hjir binne gjin berjochten!",
|
"empty_column.account_timeline": "Hjir binne gjin berjochten!",
|
||||||
"empty_column.account_unavailable": "Profyl net beskikber",
|
"empty_column.account_unavailable": "Profyl net beskikber",
|
||||||
|
|
|
@ -222,6 +222,7 @@
|
||||||
"emoji_button.search_results": "Resultados da procura",
|
"emoji_button.search_results": "Resultados da procura",
|
||||||
"emoji_button.symbols": "Símbolos",
|
"emoji_button.symbols": "Símbolos",
|
||||||
"emoji_button.travel": "Viaxes e Lugares",
|
"emoji_button.travel": "Viaxes e Lugares",
|
||||||
|
"empty_column.account_hides_collections": "A usuaria decideu non facer pública esta información",
|
||||||
"empty_column.account_suspended": "Conta suspendida",
|
"empty_column.account_suspended": "Conta suspendida",
|
||||||
"empty_column.account_timeline": "Non hai publicacións aquí!",
|
"empty_column.account_timeline": "Non hai publicacións aquí!",
|
||||||
"empty_column.account_unavailable": "Perfil non dispoñible",
|
"empty_column.account_unavailable": "Perfil non dispoñible",
|
||||||
|
|
|
@ -62,7 +62,7 @@
|
||||||
"account.share": "שתף את הפרופיל של @{name}",
|
"account.share": "שתף את הפרופיל של @{name}",
|
||||||
"account.show_reblogs": "הצג הדהודים מאת @{name}",
|
"account.show_reblogs": "הצג הדהודים מאת @{name}",
|
||||||
"account.statuses_counter": "{count, plural, one {הודעה} two {הודעותיים} many {{count} הודעות} other {{count} הודעות}}",
|
"account.statuses_counter": "{count, plural, one {הודעה} two {הודעותיים} many {{count} הודעות} other {{count} הודעות}}",
|
||||||
"account.unblock": "הסר את החסימה של @{name}",
|
"account.unblock": "להסיר חסימה ל- @{name}",
|
||||||
"account.unblock_domain": "הסירי את החסימה של קהילת {domain}",
|
"account.unblock_domain": "הסירי את החסימה של קהילת {domain}",
|
||||||
"account.unblock_short": "הסר חסימה",
|
"account.unblock_short": "הסר חסימה",
|
||||||
"account.unendorse": "אל תקדם בפרופיל",
|
"account.unendorse": "אל תקדם בפרופיל",
|
||||||
|
@ -222,6 +222,7 @@
|
||||||
"emoji_button.search_results": "תוצאות חיפוש",
|
"emoji_button.search_results": "תוצאות חיפוש",
|
||||||
"emoji_button.symbols": "סמלים",
|
"emoji_button.symbols": "סמלים",
|
||||||
"emoji_button.travel": "טיולים ואתרים",
|
"emoji_button.travel": "טיולים ואתרים",
|
||||||
|
"empty_column.account_hides_collections": "המשתמש.ת בחר.ה להסתיר מידע זה",
|
||||||
"empty_column.account_suspended": "חשבון מושהה",
|
"empty_column.account_suspended": "חשבון מושהה",
|
||||||
"empty_column.account_timeline": "אין עדיין אף הודעה!",
|
"empty_column.account_timeline": "אין עדיין אף הודעה!",
|
||||||
"empty_column.account_unavailable": "פרופיל לא זמין",
|
"empty_column.account_unavailable": "פרופיל לא זמין",
|
||||||
|
|
|
@ -222,6 +222,7 @@
|
||||||
"emoji_button.search_results": "Keresési találatok",
|
"emoji_button.search_results": "Keresési találatok",
|
||||||
"emoji_button.symbols": "Szimbólumok",
|
"emoji_button.symbols": "Szimbólumok",
|
||||||
"emoji_button.travel": "Utazás és Helyek",
|
"emoji_button.travel": "Utazás és Helyek",
|
||||||
|
"empty_column.account_hides_collections": "Ez a felhasználó úgy döntött, hogy nem teszi elérhetővé ezt az információt.",
|
||||||
"empty_column.account_suspended": "Fiók felfüggesztve",
|
"empty_column.account_suspended": "Fiók felfüggesztve",
|
||||||
"empty_column.account_timeline": "Itt nincs bejegyzés!",
|
"empty_column.account_timeline": "Itt nincs bejegyzés!",
|
||||||
"empty_column.account_unavailable": "A profil nem érhető el",
|
"empty_column.account_unavailable": "A profil nem érhető el",
|
||||||
|
|
|
@ -1,8 +1,11 @@
|
||||||
{
|
{
|
||||||
"account.add_or_remove_from_list": "Tinye ma ọ bụ Wepu na ndepụta",
|
"account.add_or_remove_from_list": "Tinye ma ọ bụ Wepu na ndepụta",
|
||||||
"account.badges.bot": "Bot",
|
"account.badges.bot": "Bot",
|
||||||
|
"account.badges.group": "Otù",
|
||||||
"account.cancel_follow_request": "Withdraw follow request",
|
"account.cancel_follow_request": "Withdraw follow request",
|
||||||
"account.follow": "Soro",
|
"account.follow": "Soro",
|
||||||
|
"account.followers": "Ndị na-eso",
|
||||||
|
"account.following": "Na-eso",
|
||||||
"account.follows_you": "Na-eso gị",
|
"account.follows_you": "Na-eso gị",
|
||||||
"account.mute": "Mee ogbi @{name}",
|
"account.mute": "Mee ogbi @{name}",
|
||||||
"account.unfollow": "Kwụsị iso",
|
"account.unfollow": "Kwụsị iso",
|
||||||
|
@ -11,16 +14,20 @@
|
||||||
"audio.hide": "Zoo ụda",
|
"audio.hide": "Zoo ụda",
|
||||||
"bundle_column_error.retry": "Nwaa ọzọ",
|
"bundle_column_error.retry": "Nwaa ọzọ",
|
||||||
"bundle_column_error.routing.title": "404",
|
"bundle_column_error.routing.title": "404",
|
||||||
|
"bundle_modal_error.close": "Mechie",
|
||||||
"bundle_modal_error.retry": "Nwaa ọzọ",
|
"bundle_modal_error.retry": "Nwaa ọzọ",
|
||||||
"column.about": "Maka",
|
"column.about": "Maka",
|
||||||
"column.blocks": "Ojiarụ egbochiri",
|
"column.blocks": "Ojiarụ egbochiri",
|
||||||
"column.bookmarks": "Ebenrụtụakā",
|
"column.bookmarks": "Ebenrụtụakā",
|
||||||
"column.home": "Be",
|
"column.home": "Be",
|
||||||
|
"column.lists": "Ndepụta",
|
||||||
"column.pins": "Pinned post",
|
"column.pins": "Pinned post",
|
||||||
|
"column_header.pin": "Gbado na profaịlụ gị",
|
||||||
"column_subheading.settings": "Mwube",
|
"column_subheading.settings": "Mwube",
|
||||||
"community.column_settings.media_only": "Media only",
|
"community.column_settings.media_only": "Media only",
|
||||||
"compose.language.change": "Gbanwee asụsụ",
|
"compose.language.change": "Gbanwee asụsụ",
|
||||||
"compose.language.search": "Chọọ asụsụ...",
|
"compose.language.search": "Chọọ asụsụ...",
|
||||||
|
"compose.published.open": "Mepe",
|
||||||
"compose_form.encryption_warning": "Posts on Mastodon are not end-to-end encrypted. Do not share any dangerous information over Mastodon.",
|
"compose_form.encryption_warning": "Posts on Mastodon are not end-to-end encrypted. Do not share any dangerous information over Mastodon.",
|
||||||
"compose_form.hashtag_warning": "This post won't be listed under any hashtag as it is unlisted. Only public posts can be searched by hashtag.",
|
"compose_form.hashtag_warning": "This post won't be listed under any hashtag as it is unlisted. Only public posts can be searched by hashtag.",
|
||||||
"compose_form.placeholder": "What is on your mind?",
|
"compose_form.placeholder": "What is on your mind?",
|
||||||
|
@ -32,7 +39,10 @@
|
||||||
"confirmations.delete.message": "Are you sure you want to delete this status?",
|
"confirmations.delete.message": "Are you sure you want to delete this status?",
|
||||||
"confirmations.delete_list.confirm": "Hichapụ",
|
"confirmations.delete_list.confirm": "Hichapụ",
|
||||||
"confirmations.domain_block.confirm": "Hide entire domain",
|
"confirmations.domain_block.confirm": "Hide entire domain",
|
||||||
|
"confirmations.edit.confirm": "Dezie",
|
||||||
|
"confirmations.mute.confirm": "Mee ogbi",
|
||||||
"confirmations.reply.confirm": "Zaa",
|
"confirmations.reply.confirm": "Zaa",
|
||||||
|
"confirmations.unfollow.confirm": "Kwụsị iso",
|
||||||
"conversation.delete": "Hichapụ nkata",
|
"conversation.delete": "Hichapụ nkata",
|
||||||
"dismissable_banner.explore_links": "These news stories are being talked about by people on this and other servers of the decentralized network right now.",
|
"dismissable_banner.explore_links": "These news stories are being talked about by people on this and other servers of the decentralized network right now.",
|
||||||
"dismissable_banner.explore_tags": "These hashtags are gaining traction among people on this and other servers of the decentralized network right now.",
|
"dismissable_banner.explore_tags": "These hashtags are gaining traction among people on this and other servers of the decentralized network right now.",
|
||||||
|
@ -76,6 +86,7 @@
|
||||||
"keyboard_shortcuts.unfocus": "to un-focus compose textarea/search",
|
"keyboard_shortcuts.unfocus": "to un-focus compose textarea/search",
|
||||||
"keyboard_shortcuts.up": "to move up in the list",
|
"keyboard_shortcuts.up": "to move up in the list",
|
||||||
"lists.delete": "Hichapụ ndepụta",
|
"lists.delete": "Hichapụ ndepụta",
|
||||||
|
"lists.edit": "Dezie ndepụta",
|
||||||
"lists.subheading": "Ndepụta gị",
|
"lists.subheading": "Ndepụta gị",
|
||||||
"loading_indicator.label": "Na-adọnye...",
|
"loading_indicator.label": "Na-adọnye...",
|
||||||
"navigation_bar.about": "Maka",
|
"navigation_bar.about": "Maka",
|
||||||
|
@ -100,20 +111,27 @@
|
||||||
"privacy.change": "Adjust status privacy",
|
"privacy.change": "Adjust status privacy",
|
||||||
"privacy.direct.short": "Direct",
|
"privacy.direct.short": "Direct",
|
||||||
"privacy.private.short": "Followers-only",
|
"privacy.private.short": "Followers-only",
|
||||||
|
"relative_time.full.just_now": "kịta",
|
||||||
"relative_time.just_now": "kịta",
|
"relative_time.just_now": "kịta",
|
||||||
"relative_time.today": "taa",
|
"relative_time.today": "taa",
|
||||||
"reply_indicator.cancel": "Kagbuo",
|
"reply_indicator.cancel": "Kagbuo",
|
||||||
"report.categories.other": "Ọzọ",
|
"report.categories.other": "Ọzọ",
|
||||||
|
"report.categories.spam": "Nzipụ Ozièlètrọniìk Nkeāchọghị",
|
||||||
|
"report.mute": "Mee ogbi",
|
||||||
"report.placeholder": "Type or paste additional comments",
|
"report.placeholder": "Type or paste additional comments",
|
||||||
"report.submit": "Submit report",
|
"report.submit": "Submit report",
|
||||||
"report.target": "Report {target}",
|
"report.target": "Report {target}",
|
||||||
"report_notification.attached_statuses": "{count, plural, one {# post} other {# posts}} attached",
|
"report_notification.attached_statuses": "{count, plural, one {# post} other {# posts}} attached",
|
||||||
|
"report_notification.categories.other": "Ọzọ",
|
||||||
|
"search.placeholder": "Chọọ",
|
||||||
"server_banner.active_users": "ojiarụ dị ìrè",
|
"server_banner.active_users": "ojiarụ dị ìrè",
|
||||||
|
"server_banner.learn_more": "Mụtakwuo",
|
||||||
"sign_in_banner.sign_in": "Sign in",
|
"sign_in_banner.sign_in": "Sign in",
|
||||||
"status.admin_status": "Open this status in the moderation interface",
|
"status.admin_status": "Open this status in the moderation interface",
|
||||||
"status.bookmark": "Kee ebenrụtụakā",
|
"status.bookmark": "Kee ebenrụtụakā",
|
||||||
"status.copy": "Copy link to status",
|
"status.copy": "Copy link to status",
|
||||||
"status.delete": "Hichapụ",
|
"status.delete": "Hichapụ",
|
||||||
|
"status.edit": "Dezie",
|
||||||
"status.edited_x_times": "Edited {count, plural, one {# time} other {# times}}",
|
"status.edited_x_times": "Edited {count, plural, one {# time} other {# times}}",
|
||||||
"status.open": "Expand this status",
|
"status.open": "Expand this status",
|
||||||
"status.remove_bookmark": "Wepu ebenrụtụakā",
|
"status.remove_bookmark": "Wepu ebenrụtụakā",
|
||||||
|
|
|
@ -222,6 +222,7 @@
|
||||||
"emoji_button.search_results": "Leitarniðurstöður",
|
"emoji_button.search_results": "Leitarniðurstöður",
|
||||||
"emoji_button.symbols": "Tákn",
|
"emoji_button.symbols": "Tákn",
|
||||||
"emoji_button.travel": "Ferðalög og staðir",
|
"emoji_button.travel": "Ferðalög og staðir",
|
||||||
|
"empty_column.account_hides_collections": "Notandinn hefur valið að gera ekki tiltækar þessar upplýsingar",
|
||||||
"empty_column.account_suspended": "Notandaaðgangur í frysti",
|
"empty_column.account_suspended": "Notandaaðgangur í frysti",
|
||||||
"empty_column.account_timeline": "Engar færslur hér!",
|
"empty_column.account_timeline": "Engar færslur hér!",
|
||||||
"empty_column.account_unavailable": "Notandasnið ekki tiltækt",
|
"empty_column.account_unavailable": "Notandasnið ekki tiltækt",
|
||||||
|
|
|
@ -222,6 +222,7 @@
|
||||||
"emoji_button.search_results": "Risultati della ricerca",
|
"emoji_button.search_results": "Risultati della ricerca",
|
||||||
"emoji_button.symbols": "Simboli",
|
"emoji_button.symbols": "Simboli",
|
||||||
"emoji_button.travel": "Viaggi & Luoghi",
|
"emoji_button.travel": "Viaggi & Luoghi",
|
||||||
|
"empty_column.account_hides_collections": "Questo utente ha scelto di non rendere disponibili queste informazioni",
|
||||||
"empty_column.account_suspended": "Profilo sospeso",
|
"empty_column.account_suspended": "Profilo sospeso",
|
||||||
"empty_column.account_timeline": "Nessun post qui!",
|
"empty_column.account_timeline": "Nessun post qui!",
|
||||||
"empty_column.account_unavailable": "Profilo non disponibile",
|
"empty_column.account_unavailable": "Profilo non disponibile",
|
||||||
|
|
|
@ -222,6 +222,7 @@
|
||||||
"emoji_button.search_results": "検索結果",
|
"emoji_button.search_results": "検索結果",
|
||||||
"emoji_button.symbols": "記号",
|
"emoji_button.symbols": "記号",
|
||||||
"emoji_button.travel": "旅行と場所",
|
"emoji_button.travel": "旅行と場所",
|
||||||
|
"empty_column.account_hides_collections": "このユーザーはこの情報を開示しないことにしています。",
|
||||||
"empty_column.account_suspended": "アカウントは停止されています",
|
"empty_column.account_suspended": "アカウントは停止されています",
|
||||||
"empty_column.account_timeline": "投稿がありません!",
|
"empty_column.account_timeline": "投稿がありません!",
|
||||||
"empty_column.account_unavailable": "プロフィールは利用できません",
|
"empty_column.account_unavailable": "プロフィールは利用できません",
|
||||||
|
@ -585,8 +586,8 @@
|
||||||
"search.no_recent_searches": "検索履歴はありません",
|
"search.no_recent_searches": "検索履歴はありません",
|
||||||
"search.placeholder": "検索",
|
"search.placeholder": "検索",
|
||||||
"search.quick_action.account_search": "{x}に該当するプロフィール",
|
"search.quick_action.account_search": "{x}に該当するプロフィール",
|
||||||
"search.quick_action.go_to_account": "{x}のプロフィールを見る",
|
"search.quick_action.go_to_account": "プロフィール {x} を見る",
|
||||||
"search.quick_action.go_to_hashtag": "{x}に該当するハッシュタグ",
|
"search.quick_action.go_to_hashtag": "ハッシュタグ {x} を見る",
|
||||||
"search.quick_action.open_url": "MastodonでURLを開く",
|
"search.quick_action.open_url": "MastodonでURLを開く",
|
||||||
"search.quick_action.status_search": "{x}に該当する投稿",
|
"search.quick_action.status_search": "{x}に該当する投稿",
|
||||||
"search.search_or_paste": "検索またはURLを入力",
|
"search.search_or_paste": "検索またはURLを入力",
|
||||||
|
|
|
@ -222,6 +222,7 @@
|
||||||
"emoji_button.search_results": "검색 결과",
|
"emoji_button.search_results": "검색 결과",
|
||||||
"emoji_button.symbols": "기호",
|
"emoji_button.symbols": "기호",
|
||||||
"emoji_button.travel": "여행과 장소",
|
"emoji_button.travel": "여행과 장소",
|
||||||
|
"empty_column.account_hides_collections": "이 사용자는 이 정보를 사용할 수 없도록 설정했습니다",
|
||||||
"empty_column.account_suspended": "계정 정지됨",
|
"empty_column.account_suspended": "계정 정지됨",
|
||||||
"empty_column.account_timeline": "이곳에는 게시물이 없습니다!",
|
"empty_column.account_timeline": "이곳에는 게시물이 없습니다!",
|
||||||
"empty_column.account_unavailable": "프로필 사용 불가",
|
"empty_column.account_unavailable": "프로필 사용 불가",
|
||||||
|
|
Some files were not shown because too many files have changed in this diff Show more
Loading…
Reference in a new issue