mirror of
https://github.com/mastodon/mastodon.git
synced 2025-01-24 02:51:43 +01:00
Merge branch 'main' into attribution-domains-api
# Conflicts: # config/locales/simple_form.de.yml # config/locales/simple_form.zh-CN.yml
This commit is contained in:
commit
f4bcc47d57
2
.github/workflows/bundler-audit.yml
vendored
2
.github/workflows/bundler-audit.yml
vendored
@ -36,4 +36,4 @@ jobs:
|
|||||||
bundler-cache: true
|
bundler-cache: true
|
||||||
|
|
||||||
- name: Run bundler-audit
|
- name: Run bundler-audit
|
||||||
run: bundle exec bundler-audit check --update
|
run: bin/bundler-audit check --update
|
||||||
|
10
.github/workflows/check-i18n.yml
vendored
10
.github/workflows/check-i18n.yml
vendored
@ -35,18 +35,18 @@ jobs:
|
|||||||
git diff --exit-code
|
git diff --exit-code
|
||||||
|
|
||||||
- name: Check locale file normalization
|
- name: Check locale file normalization
|
||||||
run: bundle exec i18n-tasks check-normalized
|
run: bin/i18n-tasks check-normalized
|
||||||
|
|
||||||
- name: Check for unused strings
|
- name: Check for unused strings
|
||||||
run: bundle exec i18n-tasks unused
|
run: bin/i18n-tasks unused
|
||||||
|
|
||||||
- name: Check for missing strings in English YML
|
- name: Check for missing strings in English YML
|
||||||
run: |
|
run: |
|
||||||
bundle exec i18n-tasks add-missing -l en
|
bin/i18n-tasks add-missing -l en
|
||||||
git diff --exit-code
|
git diff --exit-code
|
||||||
|
|
||||||
- name: Check for wrong string interpolations
|
- name: Check for wrong string interpolations
|
||||||
run: bundle exec i18n-tasks check-consistent-interpolations
|
run: bin/i18n-tasks check-consistent-interpolations
|
||||||
|
|
||||||
- name: Check that all required locale files exist
|
- name: Check that all required locale files exist
|
||||||
run: bundle exec rake repo:check_locales_files
|
run: bin/rake repo:check_locales_files
|
||||||
|
@ -46,7 +46,7 @@ jobs:
|
|||||||
uses: ./.github/actions/setup-ruby
|
uses: ./.github/actions/setup-ruby
|
||||||
|
|
||||||
- name: Run i18n normalize task
|
- name: Run i18n normalize task
|
||||||
run: bundle exec i18n-tasks normalize
|
run: bin/i18n-tasks normalize
|
||||||
|
|
||||||
# Create or update the pull request
|
# Create or update the pull request
|
||||||
- name: Create Pull Request
|
- name: Create Pull Request
|
||||||
|
2
.github/workflows/crowdin-download.yml
vendored
2
.github/workflows/crowdin-download.yml
vendored
@ -48,7 +48,7 @@ jobs:
|
|||||||
uses: ./.github/actions/setup-ruby
|
uses: ./.github/actions/setup-ruby
|
||||||
|
|
||||||
- name: Run i18n normalize task
|
- name: Run i18n normalize task
|
||||||
run: bundle exec i18n-tasks normalize
|
run: bin/i18n-tasks normalize
|
||||||
|
|
||||||
# Create or update the pull request
|
# Create or update the pull request
|
||||||
- name: Create Pull Request
|
- name: Create Pull Request
|
||||||
|
2
.github/workflows/lint-haml.yml
vendored
2
.github/workflows/lint-haml.yml
vendored
@ -43,4 +43,4 @@ jobs:
|
|||||||
- name: Run haml-lint
|
- name: Run haml-lint
|
||||||
run: |
|
run: |
|
||||||
echo "::add-matcher::.github/workflows/haml-lint-problem-matcher.json"
|
echo "::add-matcher::.github/workflows/haml-lint-problem-matcher.json"
|
||||||
bundle exec haml-lint --reporter github
|
bin/haml-lint --reporter github
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
# syntax=docker/dockerfile:1.10
|
# syntax=docker/dockerfile:1.11
|
||||||
|
|
||||||
# This file is designed for production server deployment, not local development work
|
# This file is designed for production server deployment, not local development work
|
||||||
# For a containerized local dev environment, see: https://github.com/mastodon/mastodon/blob/main/README.md#docker
|
# For a containerized local dev environment, see: https://github.com/mastodon/mastodon/blob/main/README.md#docker
|
||||||
|
2
Gemfile
2
Gemfile
@ -114,7 +114,7 @@ group :opentelemetry do
|
|||||||
gem 'opentelemetry-instrumentation-net_http', '~> 0.22.4', require: false
|
gem 'opentelemetry-instrumentation-net_http', '~> 0.22.4', require: false
|
||||||
gem 'opentelemetry-instrumentation-pg', '~> 0.29.0', require: false
|
gem 'opentelemetry-instrumentation-pg', '~> 0.29.0', require: false
|
||||||
gem 'opentelemetry-instrumentation-rack', '~> 0.25.0', require: false
|
gem 'opentelemetry-instrumentation-rack', '~> 0.25.0', require: false
|
||||||
gem 'opentelemetry-instrumentation-rails', '~> 0.32.0', require: false
|
gem 'opentelemetry-instrumentation-rails', '~> 0.33.0', require: false
|
||||||
gem 'opentelemetry-instrumentation-redis', '~> 0.25.3', require: false
|
gem 'opentelemetry-instrumentation-redis', '~> 0.25.3', require: false
|
||||||
gem 'opentelemetry-instrumentation-sidekiq', '~> 0.25.2', require: false
|
gem 'opentelemetry-instrumentation-sidekiq', '~> 0.25.2', require: false
|
||||||
gem 'opentelemetry-sdk', '~> 1.4', require: false
|
gem 'opentelemetry-sdk', '~> 1.4', require: false
|
||||||
|
43
Gemfile.lock
43
Gemfile.lock
@ -93,18 +93,17 @@ GEM
|
|||||||
annotaterb (4.13.0)
|
annotaterb (4.13.0)
|
||||||
ast (2.4.2)
|
ast (2.4.2)
|
||||||
attr_required (1.0.2)
|
attr_required (1.0.2)
|
||||||
awrence (1.2.1)
|
|
||||||
aws-eventstream (1.3.0)
|
aws-eventstream (1.3.0)
|
||||||
aws-partitions (1.1008.0)
|
aws-partitions (1.1013.0)
|
||||||
aws-sdk-core (3.213.0)
|
aws-sdk-core (3.214.0)
|
||||||
aws-eventstream (~> 1, >= 1.3.0)
|
aws-eventstream (~> 1, >= 1.3.0)
|
||||||
aws-partitions (~> 1, >= 1.992.0)
|
aws-partitions (~> 1, >= 1.992.0)
|
||||||
aws-sigv4 (~> 1.9)
|
aws-sigv4 (~> 1.9)
|
||||||
jmespath (~> 1, >= 1.6.1)
|
jmespath (~> 1, >= 1.6.1)
|
||||||
aws-sdk-kms (1.95.0)
|
aws-sdk-kms (1.96.0)
|
||||||
aws-sdk-core (~> 3, >= 3.210.0)
|
aws-sdk-core (~> 3, >= 3.210.0)
|
||||||
aws-sigv4 (~> 1.5)
|
aws-sigv4 (~> 1.5)
|
||||||
aws-sdk-s3 (1.171.0)
|
aws-sdk-s3 (1.174.0)
|
||||||
aws-sdk-core (~> 3, >= 3.210.0)
|
aws-sdk-core (~> 3, >= 3.210.0)
|
||||||
aws-sdk-kms (~> 1)
|
aws-sdk-kms (~> 1)
|
||||||
aws-sigv4 (~> 1.5)
|
aws-sigv4 (~> 1.5)
|
||||||
@ -200,7 +199,7 @@ GEM
|
|||||||
activerecord (>= 4.2, < 9.0)
|
activerecord (>= 4.2, < 9.0)
|
||||||
docile (1.4.1)
|
docile (1.4.1)
|
||||||
domain_name (0.6.20240107)
|
domain_name (0.6.20240107)
|
||||||
doorkeeper (5.7.1)
|
doorkeeper (5.8.0)
|
||||||
railties (>= 5)
|
railties (>= 5)
|
||||||
dotenv (3.1.4)
|
dotenv (3.1.4)
|
||||||
drb (2.2.1)
|
drb (2.2.1)
|
||||||
@ -346,10 +345,11 @@ GEM
|
|||||||
json-ld-preloaded (3.3.1)
|
json-ld-preloaded (3.3.1)
|
||||||
json-ld (~> 3.3)
|
json-ld (~> 3.3)
|
||||||
rdf (~> 3.3)
|
rdf (~> 3.3)
|
||||||
json-schema (5.0.1)
|
json-schema (5.1.0)
|
||||||
addressable (~> 2.8)
|
addressable (~> 2.8)
|
||||||
jsonapi-renderer (0.2.2)
|
jsonapi-renderer (0.2.2)
|
||||||
jwt (2.7.1)
|
jwt (2.9.3)
|
||||||
|
base64
|
||||||
kaminari (1.2.2)
|
kaminari (1.2.2)
|
||||||
activesupport (>= 4.1.0)
|
activesupport (>= 4.1.0)
|
||||||
kaminari-actionview (= 1.2.2)
|
kaminari-actionview (= 1.2.2)
|
||||||
@ -411,7 +411,7 @@ GEM
|
|||||||
minitest (5.25.1)
|
minitest (5.25.1)
|
||||||
msgpack (1.7.5)
|
msgpack (1.7.5)
|
||||||
multi_json (1.15.0)
|
multi_json (1.15.0)
|
||||||
mutex_m (0.2.0)
|
mutex_m (0.3.0)
|
||||||
net-http (0.5.0)
|
net-http (0.5.0)
|
||||||
uri
|
uri
|
||||||
net-imap (0.5.1)
|
net-imap (0.5.1)
|
||||||
@ -424,7 +424,7 @@ GEM
|
|||||||
timeout
|
timeout
|
||||||
net-smtp (0.5.0)
|
net-smtp (0.5.0)
|
||||||
net-protocol
|
net-protocol
|
||||||
nio4r (2.7.3)
|
nio4r (2.7.4)
|
||||||
nokogiri (1.16.7)
|
nokogiri (1.16.7)
|
||||||
mini_portile2 (~> 2.8.2)
|
mini_portile2 (~> 2.8.2)
|
||||||
racc (~> 1.4)
|
racc (~> 1.4)
|
||||||
@ -478,13 +478,13 @@ GEM
|
|||||||
opentelemetry-api (~> 1.0)
|
opentelemetry-api (~> 1.0)
|
||||||
opentelemetry-instrumentation-active_support (~> 0.1)
|
opentelemetry-instrumentation-active_support (~> 0.1)
|
||||||
opentelemetry-instrumentation-base (~> 0.22.1)
|
opentelemetry-instrumentation-base (~> 0.22.1)
|
||||||
opentelemetry-instrumentation-action_pack (0.9.0)
|
opentelemetry-instrumentation-action_pack (0.10.0)
|
||||||
opentelemetry-api (~> 1.0)
|
opentelemetry-api (~> 1.0)
|
||||||
opentelemetry-instrumentation-base (~> 0.22.1)
|
opentelemetry-instrumentation-base (~> 0.22.1)
|
||||||
opentelemetry-instrumentation-rack (~> 0.21)
|
opentelemetry-instrumentation-rack (~> 0.21)
|
||||||
opentelemetry-instrumentation-action_view (0.7.2)
|
opentelemetry-instrumentation-action_view (0.7.3)
|
||||||
opentelemetry-api (~> 1.0)
|
opentelemetry-api (~> 1.0)
|
||||||
opentelemetry-instrumentation-active_support (~> 0.1)
|
opentelemetry-instrumentation-active_support (~> 0.6)
|
||||||
opentelemetry-instrumentation-base (~> 0.22.1)
|
opentelemetry-instrumentation-base (~> 0.22.1)
|
||||||
opentelemetry-instrumentation-active_job (0.7.8)
|
opentelemetry-instrumentation-active_job (0.7.8)
|
||||||
opentelemetry-api (~> 1.0)
|
opentelemetry-api (~> 1.0)
|
||||||
@ -527,10 +527,10 @@ GEM
|
|||||||
opentelemetry-instrumentation-rack (0.25.0)
|
opentelemetry-instrumentation-rack (0.25.0)
|
||||||
opentelemetry-api (~> 1.0)
|
opentelemetry-api (~> 1.0)
|
||||||
opentelemetry-instrumentation-base (~> 0.22.1)
|
opentelemetry-instrumentation-base (~> 0.22.1)
|
||||||
opentelemetry-instrumentation-rails (0.32.0)
|
opentelemetry-instrumentation-rails (0.33.0)
|
||||||
opentelemetry-api (~> 1.0)
|
opentelemetry-api (~> 1.0)
|
||||||
opentelemetry-instrumentation-action_mailer (~> 0.2.0)
|
opentelemetry-instrumentation-action_mailer (~> 0.2.0)
|
||||||
opentelemetry-instrumentation-action_pack (~> 0.9.0)
|
opentelemetry-instrumentation-action_pack (~> 0.10.0)
|
||||||
opentelemetry-instrumentation-action_view (~> 0.7.0)
|
opentelemetry-instrumentation-action_view (~> 0.7.0)
|
||||||
opentelemetry-instrumentation-active_job (~> 0.7.0)
|
opentelemetry-instrumentation-active_job (~> 0.7.0)
|
||||||
opentelemetry-instrumentation-active_record (~> 0.8.0)
|
opentelemetry-instrumentation-active_record (~> 0.8.0)
|
||||||
@ -580,7 +580,7 @@ GEM
|
|||||||
psych (5.2.0)
|
psych (5.2.0)
|
||||||
stringio
|
stringio
|
||||||
public_suffix (6.0.1)
|
public_suffix (6.0.1)
|
||||||
puma (6.4.3)
|
puma (6.5.0)
|
||||||
nio4r (~> 2.0)
|
nio4r (~> 2.0)
|
||||||
pundit (2.4.0)
|
pundit (2.4.0)
|
||||||
activesupport (>= 3.0.0)
|
activesupport (>= 3.0.0)
|
||||||
@ -752,7 +752,7 @@ GEM
|
|||||||
activerecord (>= 4.0.0)
|
activerecord (>= 4.0.0)
|
||||||
railties (>= 4.0.0)
|
railties (>= 4.0.0)
|
||||||
securerandom (0.3.2)
|
securerandom (0.3.2)
|
||||||
selenium-webdriver (4.26.0)
|
selenium-webdriver (4.27.0)
|
||||||
base64 (~> 0.2)
|
base64 (~> 0.2)
|
||||||
logger (~> 1.4)
|
logger (~> 1.4)
|
||||||
rexml (~> 3.2, >= 3.2.5)
|
rexml (~> 3.2, >= 3.2.5)
|
||||||
@ -844,9 +844,8 @@ GEM
|
|||||||
public_suffix
|
public_suffix
|
||||||
warden (1.2.9)
|
warden (1.2.9)
|
||||||
rack (>= 2.0.9)
|
rack (>= 2.0.9)
|
||||||
webauthn (3.1.0)
|
webauthn (3.2.2)
|
||||||
android_key_attestation (~> 0.3.0)
|
android_key_attestation (~> 0.3.0)
|
||||||
awrence (~> 1.1)
|
|
||||||
bindata (~> 2.4)
|
bindata (~> 2.4)
|
||||||
cbor (~> 0.5.9)
|
cbor (~> 0.5.9)
|
||||||
cose (~> 1.1)
|
cose (~> 1.1)
|
||||||
@ -966,7 +965,7 @@ DEPENDENCIES
|
|||||||
opentelemetry-instrumentation-net_http (~> 0.22.4)
|
opentelemetry-instrumentation-net_http (~> 0.22.4)
|
||||||
opentelemetry-instrumentation-pg (~> 0.29.0)
|
opentelemetry-instrumentation-pg (~> 0.29.0)
|
||||||
opentelemetry-instrumentation-rack (~> 0.25.0)
|
opentelemetry-instrumentation-rack (~> 0.25.0)
|
||||||
opentelemetry-instrumentation-rails (~> 0.32.0)
|
opentelemetry-instrumentation-rails (~> 0.33.0)
|
||||||
opentelemetry-instrumentation-redis (~> 0.25.3)
|
opentelemetry-instrumentation-redis (~> 0.25.3)
|
||||||
opentelemetry-instrumentation-sidekiq (~> 0.25.2)
|
opentelemetry-instrumentation-sidekiq (~> 0.25.2)
|
||||||
opentelemetry-sdk (~> 1.4)
|
opentelemetry-sdk (~> 1.4)
|
||||||
@ -1031,7 +1030,7 @@ DEPENDENCIES
|
|||||||
xorcist (~> 1.1)
|
xorcist (~> 1.1)
|
||||||
|
|
||||||
RUBY VERSION
|
RUBY VERSION
|
||||||
ruby 3.3.5p100
|
ruby 3.3.6p108
|
||||||
|
|
||||||
BUNDLED WITH
|
BUNDLED WITH
|
||||||
2.5.22
|
2.5.23
|
||||||
|
@ -15,17 +15,12 @@ class Api::V1::Lists::AccountsController < Api::BaseController
|
|||||||
end
|
end
|
||||||
|
|
||||||
def create
|
def create
|
||||||
ApplicationRecord.transaction do
|
AddAccountsToListService.new.call(@list, Account.find(account_ids))
|
||||||
list_accounts.each do |account|
|
|
||||||
@list.accounts << account
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
render_empty
|
render_empty
|
||||||
end
|
end
|
||||||
|
|
||||||
def destroy
|
def destroy
|
||||||
ListAccount.where(list: @list, account_id: account_ids).destroy_all
|
RemoveAccountsFromListService.new.call(@list, Account.where(id: account_ids))
|
||||||
render_empty
|
render_empty
|
||||||
end
|
end
|
||||||
|
|
||||||
@ -43,10 +38,6 @@ class Api::V1::Lists::AccountsController < Api::BaseController
|
|||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
def list_accounts
|
|
||||||
Account.find(account_ids)
|
|
||||||
end
|
|
||||||
|
|
||||||
def account_ids
|
def account_ids
|
||||||
Array(resource_params[:account_ids])
|
Array(resource_params[:account_ids])
|
||||||
end
|
end
|
||||||
|
@ -22,7 +22,6 @@ class ApplicationController < ActionController::Base
|
|||||||
helper_method :use_seamless_external_login?
|
helper_method :use_seamless_external_login?
|
||||||
helper_method :sso_account_settings
|
helper_method :sso_account_settings
|
||||||
helper_method :limited_federation_mode?
|
helper_method :limited_federation_mode?
|
||||||
helper_method :body_class_string
|
|
||||||
helper_method :skip_csrf_meta_tags?
|
helper_method :skip_csrf_meta_tags?
|
||||||
|
|
||||||
rescue_from ActionController::ParameterMissing, Paperclip::AdapterRegistry::NoHandlerError, with: :bad_request
|
rescue_from ActionController::ParameterMissing, Paperclip::AdapterRegistry::NoHandlerError, with: :bad_request
|
||||||
@ -158,10 +157,6 @@ class ApplicationController < ActionController::Base
|
|||||||
current_user.setting_theme
|
current_user.setting_theme
|
||||||
end
|
end
|
||||||
|
|
||||||
def body_class_string
|
|
||||||
@body_classes || ''
|
|
||||||
end
|
|
||||||
|
|
||||||
def respond_with_error(code)
|
def respond_with_error(code)
|
||||||
respond_to do |format|
|
respond_to do |format|
|
||||||
format.any { render "errors/#{code}", layout: 'error', status: code, formats: [:html] }
|
format.any { render "errors/#{code}", layout: 'error', status: code, formats: [:html] }
|
||||||
|
@ -28,7 +28,7 @@ module CacheConcern
|
|||||||
def render_with_cache(**options)
|
def render_with_cache(**options)
|
||||||
raise ArgumentError, 'Only JSON render calls are supported' unless options.key?(:json) || block_given?
|
raise ArgumentError, 'Only JSON render calls are supported' unless options.key?(:json) || block_given?
|
||||||
|
|
||||||
key = options.delete(:key) || [[params[:controller], params[:action]].join('/'), options[:json].respond_to?(:cache_key) ? options[:json].cache_key : nil, options[:fields].nil? ? nil : options[:fields].join(',')].compact.join(':')
|
key = options.delete(:key) || [[params[:controller], params[:action]].join('/'), options[:json].respond_to?(:cache_key) ? options[:json].cache_key : nil, options[:fields]&.join(',')].compact.join(':')
|
||||||
expires_in = options.delete(:expires_in) || 3.minutes
|
expires_in = options.delete(:expires_in) || 3.minutes
|
||||||
body = Rails.cache.read(key, raw: true)
|
body = Rails.cache.read(key, raw: true)
|
||||||
|
|
||||||
|
@ -143,7 +143,7 @@ module ApplicationHelper
|
|||||||
end
|
end
|
||||||
|
|
||||||
def body_classes
|
def body_classes
|
||||||
output = body_class_string.split
|
output = []
|
||||||
output << content_for(:body_classes)
|
output << content_for(:body_classes)
|
||||||
output << "theme-#{current_theme.parameterize}"
|
output << "theme-#{current_theme.parameterize}"
|
||||||
output << 'system-font' if current_account&.user&.setting_system_font_ui
|
output << 'system-font' if current_account&.user&.setting_system_font_ui
|
||||||
|
@ -1,8 +1,5 @@
|
|||||||
import api from '../api';
|
import api from '../api';
|
||||||
|
|
||||||
import { showAlertForError } from './alerts';
|
|
||||||
import { importFetchedAccounts } from './importer';
|
|
||||||
|
|
||||||
export const LIST_FETCH_REQUEST = 'LIST_FETCH_REQUEST';
|
export const LIST_FETCH_REQUEST = 'LIST_FETCH_REQUEST';
|
||||||
export const LIST_FETCH_SUCCESS = 'LIST_FETCH_SUCCESS';
|
export const LIST_FETCH_SUCCESS = 'LIST_FETCH_SUCCESS';
|
||||||
export const LIST_FETCH_FAIL = 'LIST_FETCH_FAIL';
|
export const LIST_FETCH_FAIL = 'LIST_FETCH_FAIL';
|
||||||
@ -11,45 +8,10 @@ export const LISTS_FETCH_REQUEST = 'LISTS_FETCH_REQUEST';
|
|||||||
export const LISTS_FETCH_SUCCESS = 'LISTS_FETCH_SUCCESS';
|
export const LISTS_FETCH_SUCCESS = 'LISTS_FETCH_SUCCESS';
|
||||||
export const LISTS_FETCH_FAIL = 'LISTS_FETCH_FAIL';
|
export const LISTS_FETCH_FAIL = 'LISTS_FETCH_FAIL';
|
||||||
|
|
||||||
export const LIST_EDITOR_TITLE_CHANGE = 'LIST_EDITOR_TITLE_CHANGE';
|
|
||||||
export const LIST_EDITOR_RESET = 'LIST_EDITOR_RESET';
|
|
||||||
export const LIST_EDITOR_SETUP = 'LIST_EDITOR_SETUP';
|
|
||||||
|
|
||||||
export const LIST_CREATE_REQUEST = 'LIST_CREATE_REQUEST';
|
|
||||||
export const LIST_CREATE_SUCCESS = 'LIST_CREATE_SUCCESS';
|
|
||||||
export const LIST_CREATE_FAIL = 'LIST_CREATE_FAIL';
|
|
||||||
|
|
||||||
export const LIST_UPDATE_REQUEST = 'LIST_UPDATE_REQUEST';
|
|
||||||
export const LIST_UPDATE_SUCCESS = 'LIST_UPDATE_SUCCESS';
|
|
||||||
export const LIST_UPDATE_FAIL = 'LIST_UPDATE_FAIL';
|
|
||||||
|
|
||||||
export const LIST_DELETE_REQUEST = 'LIST_DELETE_REQUEST';
|
export const LIST_DELETE_REQUEST = 'LIST_DELETE_REQUEST';
|
||||||
export const LIST_DELETE_SUCCESS = 'LIST_DELETE_SUCCESS';
|
export const LIST_DELETE_SUCCESS = 'LIST_DELETE_SUCCESS';
|
||||||
export const LIST_DELETE_FAIL = 'LIST_DELETE_FAIL';
|
export const LIST_DELETE_FAIL = 'LIST_DELETE_FAIL';
|
||||||
|
|
||||||
export const LIST_ACCOUNTS_FETCH_REQUEST = 'LIST_ACCOUNTS_FETCH_REQUEST';
|
|
||||||
export const LIST_ACCOUNTS_FETCH_SUCCESS = 'LIST_ACCOUNTS_FETCH_SUCCESS';
|
|
||||||
export const LIST_ACCOUNTS_FETCH_FAIL = 'LIST_ACCOUNTS_FETCH_FAIL';
|
|
||||||
|
|
||||||
export const LIST_EDITOR_SUGGESTIONS_CHANGE = 'LIST_EDITOR_SUGGESTIONS_CHANGE';
|
|
||||||
export const LIST_EDITOR_SUGGESTIONS_READY = 'LIST_EDITOR_SUGGESTIONS_READY';
|
|
||||||
export const LIST_EDITOR_SUGGESTIONS_CLEAR = 'LIST_EDITOR_SUGGESTIONS_CLEAR';
|
|
||||||
|
|
||||||
export const LIST_EDITOR_ADD_REQUEST = 'LIST_EDITOR_ADD_REQUEST';
|
|
||||||
export const LIST_EDITOR_ADD_SUCCESS = 'LIST_EDITOR_ADD_SUCCESS';
|
|
||||||
export const LIST_EDITOR_ADD_FAIL = 'LIST_EDITOR_ADD_FAIL';
|
|
||||||
|
|
||||||
export const LIST_EDITOR_REMOVE_REQUEST = 'LIST_EDITOR_REMOVE_REQUEST';
|
|
||||||
export const LIST_EDITOR_REMOVE_SUCCESS = 'LIST_EDITOR_REMOVE_SUCCESS';
|
|
||||||
export const LIST_EDITOR_REMOVE_FAIL = 'LIST_EDITOR_REMOVE_FAIL';
|
|
||||||
|
|
||||||
export const LIST_ADDER_RESET = 'LIST_ADDER_RESET';
|
|
||||||
export const LIST_ADDER_SETUP = 'LIST_ADDER_SETUP';
|
|
||||||
|
|
||||||
export const LIST_ADDER_LISTS_FETCH_REQUEST = 'LIST_ADDER_LISTS_FETCH_REQUEST';
|
|
||||||
export const LIST_ADDER_LISTS_FETCH_SUCCESS = 'LIST_ADDER_LISTS_FETCH_SUCCESS';
|
|
||||||
export const LIST_ADDER_LISTS_FETCH_FAIL = 'LIST_ADDER_LISTS_FETCH_FAIL';
|
|
||||||
|
|
||||||
export const fetchList = id => (dispatch, getState) => {
|
export const fetchList = id => (dispatch, getState) => {
|
||||||
if (getState().getIn(['lists', id])) {
|
if (getState().getIn(['lists', id])) {
|
||||||
return;
|
return;
|
||||||
@ -100,89 +62,6 @@ export const fetchListsFail = error => ({
|
|||||||
error,
|
error,
|
||||||
});
|
});
|
||||||
|
|
||||||
export const submitListEditor = shouldReset => (dispatch, getState) => {
|
|
||||||
const listId = getState().getIn(['listEditor', 'listId']);
|
|
||||||
const title = getState().getIn(['listEditor', 'title']);
|
|
||||||
|
|
||||||
if (listId === null) {
|
|
||||||
dispatch(createList(title, shouldReset));
|
|
||||||
} else {
|
|
||||||
dispatch(updateList(listId, title, shouldReset));
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
export const setupListEditor = listId => (dispatch, getState) => {
|
|
||||||
dispatch({
|
|
||||||
type: LIST_EDITOR_SETUP,
|
|
||||||
list: getState().getIn(['lists', listId]),
|
|
||||||
});
|
|
||||||
|
|
||||||
dispatch(fetchListAccounts(listId));
|
|
||||||
};
|
|
||||||
|
|
||||||
export const changeListEditorTitle = value => ({
|
|
||||||
type: LIST_EDITOR_TITLE_CHANGE,
|
|
||||||
value,
|
|
||||||
});
|
|
||||||
|
|
||||||
export const createList = (title, shouldReset) => (dispatch) => {
|
|
||||||
dispatch(createListRequest());
|
|
||||||
|
|
||||||
api().post('/api/v1/lists', { title }).then(({ data }) => {
|
|
||||||
dispatch(createListSuccess(data));
|
|
||||||
|
|
||||||
if (shouldReset) {
|
|
||||||
dispatch(resetListEditor());
|
|
||||||
}
|
|
||||||
}).catch(err => dispatch(createListFail(err)));
|
|
||||||
};
|
|
||||||
|
|
||||||
export const createListRequest = () => ({
|
|
||||||
type: LIST_CREATE_REQUEST,
|
|
||||||
});
|
|
||||||
|
|
||||||
export const createListSuccess = list => ({
|
|
||||||
type: LIST_CREATE_SUCCESS,
|
|
||||||
list,
|
|
||||||
});
|
|
||||||
|
|
||||||
export const createListFail = error => ({
|
|
||||||
type: LIST_CREATE_FAIL,
|
|
||||||
error,
|
|
||||||
});
|
|
||||||
|
|
||||||
export const updateList = (id, title, shouldReset, isExclusive, replies_policy) => (dispatch) => {
|
|
||||||
dispatch(updateListRequest(id));
|
|
||||||
|
|
||||||
api().put(`/api/v1/lists/${id}`, { title, replies_policy, exclusive: typeof isExclusive === 'undefined' ? undefined : !!isExclusive }).then(({ data }) => {
|
|
||||||
dispatch(updateListSuccess(data));
|
|
||||||
|
|
||||||
if (shouldReset) {
|
|
||||||
dispatch(resetListEditor());
|
|
||||||
}
|
|
||||||
}).catch(err => dispatch(updateListFail(id, err)));
|
|
||||||
};
|
|
||||||
|
|
||||||
export const updateListRequest = id => ({
|
|
||||||
type: LIST_UPDATE_REQUEST,
|
|
||||||
id,
|
|
||||||
});
|
|
||||||
|
|
||||||
export const updateListSuccess = list => ({
|
|
||||||
type: LIST_UPDATE_SUCCESS,
|
|
||||||
list,
|
|
||||||
});
|
|
||||||
|
|
||||||
export const updateListFail = (id, error) => ({
|
|
||||||
type: LIST_UPDATE_FAIL,
|
|
||||||
id,
|
|
||||||
error,
|
|
||||||
});
|
|
||||||
|
|
||||||
export const resetListEditor = () => ({
|
|
||||||
type: LIST_EDITOR_RESET,
|
|
||||||
});
|
|
||||||
|
|
||||||
export const deleteList = id => (dispatch) => {
|
export const deleteList = id => (dispatch) => {
|
||||||
dispatch(deleteListRequest(id));
|
dispatch(deleteListRequest(id));
|
||||||
|
|
||||||
@ -206,167 +85,3 @@ export const deleteListFail = (id, error) => ({
|
|||||||
id,
|
id,
|
||||||
error,
|
error,
|
||||||
});
|
});
|
||||||
|
|
||||||
export const fetchListAccounts = listId => (dispatch) => {
|
|
||||||
dispatch(fetchListAccountsRequest(listId));
|
|
||||||
|
|
||||||
api().get(`/api/v1/lists/${listId}/accounts`, { params: { limit: 0 } }).then(({ data }) => {
|
|
||||||
dispatch(importFetchedAccounts(data));
|
|
||||||
dispatch(fetchListAccountsSuccess(listId, data));
|
|
||||||
}).catch(err => dispatch(fetchListAccountsFail(listId, err)));
|
|
||||||
};
|
|
||||||
|
|
||||||
export const fetchListAccountsRequest = id => ({
|
|
||||||
type: LIST_ACCOUNTS_FETCH_REQUEST,
|
|
||||||
id,
|
|
||||||
});
|
|
||||||
|
|
||||||
export const fetchListAccountsSuccess = (id, accounts, next) => ({
|
|
||||||
type: LIST_ACCOUNTS_FETCH_SUCCESS,
|
|
||||||
id,
|
|
||||||
accounts,
|
|
||||||
next,
|
|
||||||
});
|
|
||||||
|
|
||||||
export const fetchListAccountsFail = (id, error) => ({
|
|
||||||
type: LIST_ACCOUNTS_FETCH_FAIL,
|
|
||||||
id,
|
|
||||||
error,
|
|
||||||
});
|
|
||||||
|
|
||||||
export const fetchListSuggestions = q => (dispatch) => {
|
|
||||||
const params = {
|
|
||||||
q,
|
|
||||||
resolve: false,
|
|
||||||
limit: 4,
|
|
||||||
following: true,
|
|
||||||
};
|
|
||||||
|
|
||||||
api().get('/api/v1/accounts/search', { params }).then(({ data }) => {
|
|
||||||
dispatch(importFetchedAccounts(data));
|
|
||||||
dispatch(fetchListSuggestionsReady(q, data));
|
|
||||||
}).catch(error => dispatch(showAlertForError(error)));
|
|
||||||
};
|
|
||||||
|
|
||||||
export const fetchListSuggestionsReady = (query, accounts) => ({
|
|
||||||
type: LIST_EDITOR_SUGGESTIONS_READY,
|
|
||||||
query,
|
|
||||||
accounts,
|
|
||||||
});
|
|
||||||
|
|
||||||
export const clearListSuggestions = () => ({
|
|
||||||
type: LIST_EDITOR_SUGGESTIONS_CLEAR,
|
|
||||||
});
|
|
||||||
|
|
||||||
export const changeListSuggestions = value => ({
|
|
||||||
type: LIST_EDITOR_SUGGESTIONS_CHANGE,
|
|
||||||
value,
|
|
||||||
});
|
|
||||||
|
|
||||||
export const addToListEditor = accountId => (dispatch, getState) => {
|
|
||||||
dispatch(addToList(getState().getIn(['listEditor', 'listId']), accountId));
|
|
||||||
};
|
|
||||||
|
|
||||||
export const addToList = (listId, accountId) => (dispatch) => {
|
|
||||||
dispatch(addToListRequest(listId, accountId));
|
|
||||||
|
|
||||||
api().post(`/api/v1/lists/${listId}/accounts`, { account_ids: [accountId] })
|
|
||||||
.then(() => dispatch(addToListSuccess(listId, accountId)))
|
|
||||||
.catch(err => dispatch(addToListFail(listId, accountId, err)));
|
|
||||||
};
|
|
||||||
|
|
||||||
export const addToListRequest = (listId, accountId) => ({
|
|
||||||
type: LIST_EDITOR_ADD_REQUEST,
|
|
||||||
listId,
|
|
||||||
accountId,
|
|
||||||
});
|
|
||||||
|
|
||||||
export const addToListSuccess = (listId, accountId) => ({
|
|
||||||
type: LIST_EDITOR_ADD_SUCCESS,
|
|
||||||
listId,
|
|
||||||
accountId,
|
|
||||||
});
|
|
||||||
|
|
||||||
export const addToListFail = (listId, accountId, error) => ({
|
|
||||||
type: LIST_EDITOR_ADD_FAIL,
|
|
||||||
listId,
|
|
||||||
accountId,
|
|
||||||
error,
|
|
||||||
});
|
|
||||||
|
|
||||||
export const removeFromListEditor = accountId => (dispatch, getState) => {
|
|
||||||
dispatch(removeFromList(getState().getIn(['listEditor', 'listId']), accountId));
|
|
||||||
};
|
|
||||||
|
|
||||||
export const removeFromList = (listId, accountId) => (dispatch) => {
|
|
||||||
dispatch(removeFromListRequest(listId, accountId));
|
|
||||||
|
|
||||||
api().delete(`/api/v1/lists/${listId}/accounts`, { params: { account_ids: [accountId] } })
|
|
||||||
.then(() => dispatch(removeFromListSuccess(listId, accountId)))
|
|
||||||
.catch(err => dispatch(removeFromListFail(listId, accountId, err)));
|
|
||||||
};
|
|
||||||
|
|
||||||
export const removeFromListRequest = (listId, accountId) => ({
|
|
||||||
type: LIST_EDITOR_REMOVE_REQUEST,
|
|
||||||
listId,
|
|
||||||
accountId,
|
|
||||||
});
|
|
||||||
|
|
||||||
export const removeFromListSuccess = (listId, accountId) => ({
|
|
||||||
type: LIST_EDITOR_REMOVE_SUCCESS,
|
|
||||||
listId,
|
|
||||||
accountId,
|
|
||||||
});
|
|
||||||
|
|
||||||
export const removeFromListFail = (listId, accountId, error) => ({
|
|
||||||
type: LIST_EDITOR_REMOVE_FAIL,
|
|
||||||
listId,
|
|
||||||
accountId,
|
|
||||||
error,
|
|
||||||
});
|
|
||||||
|
|
||||||
export const resetListAdder = () => ({
|
|
||||||
type: LIST_ADDER_RESET,
|
|
||||||
});
|
|
||||||
|
|
||||||
export const setupListAdder = accountId => (dispatch, getState) => {
|
|
||||||
dispatch({
|
|
||||||
type: LIST_ADDER_SETUP,
|
|
||||||
account: getState().getIn(['accounts', accountId]),
|
|
||||||
});
|
|
||||||
dispatch(fetchLists());
|
|
||||||
dispatch(fetchAccountLists(accountId));
|
|
||||||
};
|
|
||||||
|
|
||||||
export const fetchAccountLists = accountId => (dispatch) => {
|
|
||||||
dispatch(fetchAccountListsRequest(accountId));
|
|
||||||
|
|
||||||
api().get(`/api/v1/accounts/${accountId}/lists`)
|
|
||||||
.then(({ data }) => dispatch(fetchAccountListsSuccess(accountId, data)))
|
|
||||||
.catch(err => dispatch(fetchAccountListsFail(accountId, err)));
|
|
||||||
};
|
|
||||||
|
|
||||||
export const fetchAccountListsRequest = id => ({
|
|
||||||
type:LIST_ADDER_LISTS_FETCH_REQUEST,
|
|
||||||
id,
|
|
||||||
});
|
|
||||||
|
|
||||||
export const fetchAccountListsSuccess = (id, lists) => ({
|
|
||||||
type: LIST_ADDER_LISTS_FETCH_SUCCESS,
|
|
||||||
id,
|
|
||||||
lists,
|
|
||||||
});
|
|
||||||
|
|
||||||
export const fetchAccountListsFail = (id, err) => ({
|
|
||||||
type: LIST_ADDER_LISTS_FETCH_FAIL,
|
|
||||||
id,
|
|
||||||
err,
|
|
||||||
});
|
|
||||||
|
|
||||||
export const addToListAdder = listId => (dispatch, getState) => {
|
|
||||||
dispatch(addToList(listId, getState().getIn(['listAdder', 'accountId'])));
|
|
||||||
};
|
|
||||||
|
|
||||||
export const removeFromListAdder = listId => (dispatch, getState) => {
|
|
||||||
dispatch(removeFromList(listId, getState().getIn(['listAdder', 'accountId'])));
|
|
||||||
};
|
|
||||||
|
13
app/javascript/mastodon/actions/lists_typed.ts
Normal file
13
app/javascript/mastodon/actions/lists_typed.ts
Normal file
@ -0,0 +1,13 @@
|
|||||||
|
import { apiCreate, apiUpdate } from 'mastodon/api/lists';
|
||||||
|
import type { List } from 'mastodon/models/list';
|
||||||
|
import { createDataLoadingThunk } from 'mastodon/store/typed_functions';
|
||||||
|
|
||||||
|
export const createList = createDataLoadingThunk(
|
||||||
|
'list/create',
|
||||||
|
(list: Partial<List>) => apiCreate(list),
|
||||||
|
);
|
||||||
|
|
||||||
|
export const updateList = createDataLoadingThunk(
|
||||||
|
'list/update',
|
||||||
|
(list: Partial<List>) => apiUpdate(list),
|
||||||
|
);
|
@ -141,6 +141,9 @@ export const pollRecentNotifications = createDataLoadingThunk(
|
|||||||
|
|
||||||
return { notifications };
|
return { notifications };
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
useLoadingBar: false,
|
||||||
|
},
|
||||||
);
|
);
|
||||||
|
|
||||||
export const processNewNotificationForGroups = createAppAsyncThunk(
|
export const processNewNotificationForGroups = createAppAsyncThunk(
|
||||||
|
@ -1,58 +0,0 @@
|
|||||||
import api from '../api';
|
|
||||||
|
|
||||||
import { fetchRelationships } from './accounts';
|
|
||||||
import { importFetchedAccounts } from './importer';
|
|
||||||
|
|
||||||
export const SUGGESTIONS_FETCH_REQUEST = 'SUGGESTIONS_FETCH_REQUEST';
|
|
||||||
export const SUGGESTIONS_FETCH_SUCCESS = 'SUGGESTIONS_FETCH_SUCCESS';
|
|
||||||
export const SUGGESTIONS_FETCH_FAIL = 'SUGGESTIONS_FETCH_FAIL';
|
|
||||||
|
|
||||||
export const SUGGESTIONS_DISMISS = 'SUGGESTIONS_DISMISS';
|
|
||||||
|
|
||||||
export function fetchSuggestions(withRelationships = false) {
|
|
||||||
return (dispatch) => {
|
|
||||||
dispatch(fetchSuggestionsRequest());
|
|
||||||
|
|
||||||
api().get('/api/v2/suggestions', { params: { limit: 20 } }).then(response => {
|
|
||||||
dispatch(importFetchedAccounts(response.data.map(x => x.account)));
|
|
||||||
dispatch(fetchSuggestionsSuccess(response.data));
|
|
||||||
|
|
||||||
if (withRelationships) {
|
|
||||||
dispatch(fetchRelationships(response.data.map(item => item.account.id)));
|
|
||||||
}
|
|
||||||
}).catch(error => dispatch(fetchSuggestionsFail(error)));
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
export function fetchSuggestionsRequest() {
|
|
||||||
return {
|
|
||||||
type: SUGGESTIONS_FETCH_REQUEST,
|
|
||||||
skipLoading: true,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
export function fetchSuggestionsSuccess(suggestions) {
|
|
||||||
return {
|
|
||||||
type: SUGGESTIONS_FETCH_SUCCESS,
|
|
||||||
suggestions,
|
|
||||||
skipLoading: true,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
export function fetchSuggestionsFail(error) {
|
|
||||||
return {
|
|
||||||
type: SUGGESTIONS_FETCH_FAIL,
|
|
||||||
error,
|
|
||||||
skipLoading: true,
|
|
||||||
skipAlert: true,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
export const dismissSuggestion = accountId => (dispatch) => {
|
|
||||||
dispatch({
|
|
||||||
type: SUGGESTIONS_DISMISS,
|
|
||||||
id: accountId,
|
|
||||||
});
|
|
||||||
|
|
||||||
api().delete(`/api/v1/suggestions/${accountId}`).catch(() => {});
|
|
||||||
};
|
|
24
app/javascript/mastodon/actions/suggestions.ts
Normal file
24
app/javascript/mastodon/actions/suggestions.ts
Normal file
@ -0,0 +1,24 @@
|
|||||||
|
import {
|
||||||
|
apiGetSuggestions,
|
||||||
|
apiDeleteSuggestion,
|
||||||
|
} from 'mastodon/api/suggestions';
|
||||||
|
import { createDataLoadingThunk } from 'mastodon/store/typed_functions';
|
||||||
|
|
||||||
|
import { fetchRelationships } from './accounts';
|
||||||
|
import { importFetchedAccounts } from './importer';
|
||||||
|
|
||||||
|
export const fetchSuggestions = createDataLoadingThunk(
|
||||||
|
'suggestions/fetch',
|
||||||
|
() => apiGetSuggestions(20),
|
||||||
|
(data, { dispatch }) => {
|
||||||
|
dispatch(importFetchedAccounts(data.map((x) => x.account)));
|
||||||
|
dispatch(fetchRelationships(data.map((x) => x.account.id)));
|
||||||
|
|
||||||
|
return data;
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
|
export const dismissSuggestion = createDataLoadingThunk(
|
||||||
|
'suggestions/dismiss',
|
||||||
|
({ accountId }: { accountId: string }) => apiDeleteSuggestion(accountId),
|
||||||
|
);
|
@ -68,6 +68,7 @@ export async function apiRequest<ApiResponse = unknown>(
|
|||||||
method: Method,
|
method: Method,
|
||||||
url: string,
|
url: string,
|
||||||
args: {
|
args: {
|
||||||
|
signal?: AbortSignal;
|
||||||
params?: RequestParamsOrData;
|
params?: RequestParamsOrData;
|
||||||
data?: RequestParamsOrData;
|
data?: RequestParamsOrData;
|
||||||
timeout?: number;
|
timeout?: number;
|
||||||
|
32
app/javascript/mastodon/api/lists.ts
Normal file
32
app/javascript/mastodon/api/lists.ts
Normal file
@ -0,0 +1,32 @@
|
|||||||
|
import {
|
||||||
|
apiRequestPost,
|
||||||
|
apiRequestPut,
|
||||||
|
apiRequestGet,
|
||||||
|
apiRequestDelete,
|
||||||
|
} from 'mastodon/api';
|
||||||
|
import type { ApiAccountJSON } from 'mastodon/api_types/accounts';
|
||||||
|
import type { ApiListJSON } from 'mastodon/api_types/lists';
|
||||||
|
|
||||||
|
export const apiCreate = (list: Partial<ApiListJSON>) =>
|
||||||
|
apiRequestPost<ApiListJSON>('v1/lists', list);
|
||||||
|
|
||||||
|
export const apiUpdate = (list: Partial<ApiListJSON>) =>
|
||||||
|
apiRequestPut<ApiListJSON>(`v1/lists/${list.id}`, list);
|
||||||
|
|
||||||
|
export const apiGetAccounts = (listId: string) =>
|
||||||
|
apiRequestGet<ApiAccountJSON[]>(`v1/lists/${listId}/accounts`, {
|
||||||
|
limit: 0,
|
||||||
|
});
|
||||||
|
|
||||||
|
export const apiGetAccountLists = (accountId: string) =>
|
||||||
|
apiRequestGet<ApiListJSON[]>(`v1/accounts/${accountId}/lists`);
|
||||||
|
|
||||||
|
export const apiAddAccountToList = (listId: string, accountId: string) =>
|
||||||
|
apiRequestPost(`v1/lists/${listId}/accounts`, {
|
||||||
|
account_ids: [accountId],
|
||||||
|
});
|
||||||
|
|
||||||
|
export const apiRemoveAccountFromList = (listId: string, accountId: string) =>
|
||||||
|
apiRequestDelete(`v1/lists/${listId}/accounts`, {
|
||||||
|
account_ids: [accountId],
|
||||||
|
});
|
8
app/javascript/mastodon/api/suggestions.ts
Normal file
8
app/javascript/mastodon/api/suggestions.ts
Normal file
@ -0,0 +1,8 @@
|
|||||||
|
import { apiRequestGet, apiRequestDelete } from 'mastodon/api';
|
||||||
|
import type { ApiSuggestionJSON } from 'mastodon/api_types/suggestions';
|
||||||
|
|
||||||
|
export const apiGetSuggestions = (limit: number) =>
|
||||||
|
apiRequestGet<ApiSuggestionJSON[]>('v2/suggestions', { limit });
|
||||||
|
|
||||||
|
export const apiDeleteSuggestion = (accountId: string) =>
|
||||||
|
apiRequestDelete(`v1/suggestions/${accountId}`);
|
10
app/javascript/mastodon/api_types/lists.ts
Normal file
10
app/javascript/mastodon/api_types/lists.ts
Normal file
@ -0,0 +1,10 @@
|
|||||||
|
// See app/serializers/rest/list_serializer.rb
|
||||||
|
|
||||||
|
export type RepliesPolicyType = 'list' | 'followed' | 'none';
|
||||||
|
|
||||||
|
export interface ApiListJSON {
|
||||||
|
id: string;
|
||||||
|
title: string;
|
||||||
|
exclusive: boolean;
|
||||||
|
replies_policy: RepliesPolicyType;
|
||||||
|
}
|
13
app/javascript/mastodon/api_types/suggestions.ts
Normal file
13
app/javascript/mastodon/api_types/suggestions.ts
Normal file
@ -0,0 +1,13 @@
|
|||||||
|
import type { ApiAccountJSON } from 'mastodon/api_types/accounts';
|
||||||
|
|
||||||
|
export type ApiSuggestionSourceJSON =
|
||||||
|
| 'featured'
|
||||||
|
| 'most_followed'
|
||||||
|
| 'most_interactions'
|
||||||
|
| 'similar_to_recently_followed'
|
||||||
|
| 'friends_of_friends';
|
||||||
|
|
||||||
|
export interface ApiSuggestionJSON {
|
||||||
|
sources: [ApiSuggestionSourceJSON, ...ApiSuggestionSourceJSON[]];
|
||||||
|
account: ApiAccountJSON;
|
||||||
|
}
|
@ -10,6 +10,7 @@ import ImmutablePropTypes from 'react-immutable-proptypes';
|
|||||||
|
|
||||||
import MoreHorizIcon from '@/material-icons/400-24px/more_horiz.svg?react';
|
import MoreHorizIcon from '@/material-icons/400-24px/more_horiz.svg?react';
|
||||||
import { EmptyAccount } from 'mastodon/components/empty_account';
|
import { EmptyAccount } from 'mastodon/components/empty_account';
|
||||||
|
import { FollowButton } from 'mastodon/components/follow_button';
|
||||||
import { ShortNumber } from 'mastodon/components/short_number';
|
import { ShortNumber } from 'mastodon/components/short_number';
|
||||||
import { VerifiedBadge } from 'mastodon/components/verified_badge';
|
import { VerifiedBadge } from 'mastodon/components/verified_badge';
|
||||||
|
|
||||||
@ -23,9 +24,6 @@ import { DisplayName } from './display_name';
|
|||||||
import { RelativeTimestamp } from './relative_timestamp';
|
import { RelativeTimestamp } from './relative_timestamp';
|
||||||
|
|
||||||
const messages = defineMessages({
|
const messages = defineMessages({
|
||||||
follow: { id: 'account.follow', defaultMessage: 'Follow' },
|
|
||||||
unfollow: { id: 'account.unfollow', defaultMessage: 'Unfollow' },
|
|
||||||
cancel_follow_request: { id: 'account.cancel_follow_request', defaultMessage: 'Withdraw follow request' },
|
|
||||||
unblock: { id: 'account.unblock_short', defaultMessage: 'Unblock' },
|
unblock: { id: 'account.unblock_short', defaultMessage: 'Unblock' },
|
||||||
unmute: { id: 'account.unmute_short', defaultMessage: 'Unmute' },
|
unmute: { id: 'account.unmute_short', defaultMessage: 'Unmute' },
|
||||||
mute_notifications: { id: 'account.mute_notifications_short', defaultMessage: 'Mute notifications' },
|
mute_notifications: { id: 'account.mute_notifications_short', defaultMessage: 'Mute notifications' },
|
||||||
@ -35,13 +33,9 @@ const messages = defineMessages({
|
|||||||
more: { id: 'status.more', defaultMessage: 'More' },
|
more: { id: 'status.more', defaultMessage: 'More' },
|
||||||
});
|
});
|
||||||
|
|
||||||
const Account = ({ size = 46, account, onFollow, onBlock, onMute, onMuteNotifications, hidden, minimal, defaultAction, withBio }) => {
|
const Account = ({ size = 46, account, onBlock, onMute, onMuteNotifications, hidden, minimal, defaultAction, withBio }) => {
|
||||||
const intl = useIntl();
|
const intl = useIntl();
|
||||||
|
|
||||||
const handleFollow = useCallback(() => {
|
|
||||||
onFollow(account);
|
|
||||||
}, [onFollow, account]);
|
|
||||||
|
|
||||||
const handleBlock = useCallback(() => {
|
const handleBlock = useCallback(() => {
|
||||||
onBlock(account);
|
onBlock(account);
|
||||||
}, [onBlock, account]);
|
}, [onBlock, account]);
|
||||||
@ -74,13 +68,12 @@ const Account = ({ size = 46, account, onFollow, onBlock, onMute, onMuteNotifica
|
|||||||
let buttons;
|
let buttons;
|
||||||
|
|
||||||
if (account.get('id') !== me && account.get('relationship', null) !== null) {
|
if (account.get('id') !== me && account.get('relationship', null) !== null) {
|
||||||
const following = account.getIn(['relationship', 'following']);
|
|
||||||
const requested = account.getIn(['relationship', 'requested']);
|
const requested = account.getIn(['relationship', 'requested']);
|
||||||
const blocking = account.getIn(['relationship', 'blocking']);
|
const blocking = account.getIn(['relationship', 'blocking']);
|
||||||
const muting = account.getIn(['relationship', 'muting']);
|
const muting = account.getIn(['relationship', 'muting']);
|
||||||
|
|
||||||
if (requested) {
|
if (requested) {
|
||||||
buttons = <Button text={intl.formatMessage(messages.cancel_follow_request)} onClick={handleFollow} />;
|
buttons = <FollowButton accountId={account.get('id')} />;
|
||||||
} else if (blocking) {
|
} else if (blocking) {
|
||||||
buttons = <Button text={intl.formatMessage(messages.unblock)} onClick={handleBlock} />;
|
buttons = <Button text={intl.formatMessage(messages.unblock)} onClick={handleBlock} />;
|
||||||
} else if (muting) {
|
} else if (muting) {
|
||||||
@ -109,9 +102,11 @@ const Account = ({ size = 46, account, onFollow, onBlock, onMute, onMuteNotifica
|
|||||||
buttons = <Button text={intl.formatMessage(messages.mute)} onClick={handleMute} />;
|
buttons = <Button text={intl.formatMessage(messages.mute)} onClick={handleMute} />;
|
||||||
} else if (defaultAction === 'block') {
|
} else if (defaultAction === 'block') {
|
||||||
buttons = <Button text={intl.formatMessage(messages.block)} onClick={handleBlock} />;
|
buttons = <Button text={intl.formatMessage(messages.block)} onClick={handleBlock} />;
|
||||||
} else if (!account.get('suspended') && !account.get('moved') || following) {
|
} else {
|
||||||
buttons = <Button text={intl.formatMessage(following ? messages.unfollow : messages.follow)} onClick={handleFollow} />;
|
buttons = <FollowButton accountId={account.get('id')} />;
|
||||||
}
|
}
|
||||||
|
} else {
|
||||||
|
buttons = <FollowButton accountId={account.get('id')} />;
|
||||||
}
|
}
|
||||||
|
|
||||||
let muteTimeRemaining;
|
let muteTimeRemaining;
|
||||||
@ -168,7 +163,6 @@ const Account = ({ size = 46, account, onFollow, onBlock, onMute, onMuteNotifica
|
|||||||
Account.propTypes = {
|
Account.propTypes = {
|
||||||
size: PropTypes.number,
|
size: PropTypes.number,
|
||||||
account: ImmutablePropTypes.record,
|
account: ImmutablePropTypes.record,
|
||||||
onFollow: PropTypes.func,
|
|
||||||
onBlock: PropTypes.func,
|
onBlock: PropTypes.func,
|
||||||
onMute: PropTypes.func,
|
onMute: PropTypes.func,
|
||||||
onMuteNotifications: PropTypes.func,
|
onMuteNotifications: PropTypes.func,
|
||||||
|
@ -7,11 +7,11 @@ import { Icon } from './icon';
|
|||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
value: string;
|
value: string;
|
||||||
checked: boolean;
|
checked?: boolean;
|
||||||
indeterminate: boolean;
|
indeterminate?: boolean;
|
||||||
name: string;
|
name?: string;
|
||||||
onChange: (event: React.ChangeEvent<HTMLInputElement>) => void;
|
onChange?: (event: React.ChangeEvent<HTMLInputElement>) => void;
|
||||||
label: React.ReactNode;
|
label?: React.ReactNode;
|
||||||
}
|
}
|
||||||
|
|
||||||
export const CheckBox: React.FC<Props> = ({
|
export const CheckBox: React.FC<Props> = ({
|
||||||
@ -30,6 +30,7 @@ export const CheckBox: React.FC<Props> = ({
|
|||||||
value={value}
|
value={value}
|
||||||
checked={checked}
|
checked={checked}
|
||||||
onChange={onChange}
|
onChange={onChange}
|
||||||
|
readOnly={!onChange}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<span
|
<span
|
||||||
@ -42,7 +43,7 @@ export const CheckBox: React.FC<Props> = ({
|
|||||||
)}
|
)}
|
||||||
</span>
|
</span>
|
||||||
|
|
||||||
<span>{label}</span>
|
{label && <span>{label}</span>}
|
||||||
</label>
|
</label>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
@ -24,7 +24,7 @@ function useHandleClick(onClick?: OnClickCallback) {
|
|||||||
}, [history, onClick]);
|
}, [history, onClick]);
|
||||||
}
|
}
|
||||||
|
|
||||||
export const ColumnBackButton: React.FC<{ onClick: OnClickCallback }> = ({
|
export const ColumnBackButton: React.FC<{ onClick?: OnClickCallback }> = ({
|
||||||
onClick,
|
onClick,
|
||||||
}) => {
|
}) => {
|
||||||
const handleClick = useHandleClick(onClick);
|
const handleClick = useHandleClick(onClick);
|
||||||
|
67
app/javascript/mastodon/components/column_search_header.tsx
Normal file
67
app/javascript/mastodon/components/column_search_header.tsx
Normal file
@ -0,0 +1,67 @@
|
|||||||
|
import { useCallback, useState, useEffect, useRef } from 'react';
|
||||||
|
|
||||||
|
import { FormattedMessage } from 'react-intl';
|
||||||
|
|
||||||
|
export const ColumnSearchHeader: React.FC<{
|
||||||
|
onBack: () => void;
|
||||||
|
onSubmit: (value: string) => void;
|
||||||
|
onActivate: () => void;
|
||||||
|
placeholder: string;
|
||||||
|
active: boolean;
|
||||||
|
}> = ({ onBack, onActivate, onSubmit, placeholder, active }) => {
|
||||||
|
const inputRef = useRef<HTMLInputElement>(null);
|
||||||
|
const [value, setValue] = useState('');
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (!active) {
|
||||||
|
setValue('');
|
||||||
|
}
|
||||||
|
}, [active]);
|
||||||
|
|
||||||
|
const handleChange = useCallback(
|
||||||
|
({ target: { value } }: React.ChangeEvent<HTMLInputElement>) => {
|
||||||
|
setValue(value);
|
||||||
|
onSubmit(value);
|
||||||
|
},
|
||||||
|
[setValue, onSubmit],
|
||||||
|
);
|
||||||
|
|
||||||
|
const handleKeyUp = useCallback(
|
||||||
|
(e: React.KeyboardEvent<HTMLInputElement>) => {
|
||||||
|
if (e.key === 'Escape') {
|
||||||
|
e.preventDefault();
|
||||||
|
onBack();
|
||||||
|
inputRef.current?.blur();
|
||||||
|
}
|
||||||
|
},
|
||||||
|
[onBack],
|
||||||
|
);
|
||||||
|
|
||||||
|
const handleFocus = useCallback(() => {
|
||||||
|
onActivate();
|
||||||
|
}, [onActivate]);
|
||||||
|
|
||||||
|
const handleSubmit = useCallback(() => {
|
||||||
|
onSubmit(value);
|
||||||
|
}, [onSubmit, value]);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<form className='column-search-header' onSubmit={handleSubmit}>
|
||||||
|
<input
|
||||||
|
ref={inputRef}
|
||||||
|
type='search'
|
||||||
|
value={value}
|
||||||
|
onChange={handleChange}
|
||||||
|
onKeyUp={handleKeyUp}
|
||||||
|
placeholder={placeholder}
|
||||||
|
onFocus={handleFocus}
|
||||||
|
/>
|
||||||
|
|
||||||
|
{active && (
|
||||||
|
<button type='button' className='link-button' onClick={onBack}>
|
||||||
|
<FormattedMessage id='column_search.cancel' defaultMessage='Cancel' />
|
||||||
|
</button>
|
||||||
|
)}
|
||||||
|
</form>
|
||||||
|
);
|
||||||
|
};
|
@ -99,7 +99,12 @@ export const FollowButton: React.FC<{
|
|||||||
return (
|
return (
|
||||||
<Button
|
<Button
|
||||||
onClick={handleClick}
|
onClick={handleClick}
|
||||||
disabled={relationship?.blocked_by || relationship?.blocking}
|
disabled={
|
||||||
|
relationship?.blocked_by ||
|
||||||
|
relationship?.blocking ||
|
||||||
|
(!(relationship?.following || relationship?.requested) &&
|
||||||
|
(account?.suspended || !!account?.moved))
|
||||||
|
}
|
||||||
secondary={following}
|
secondary={following}
|
||||||
className={following ? 'button--destructive' : undefined}
|
className={following ? 'button--destructive' : undefined}
|
||||||
>
|
>
|
||||||
|
@ -80,6 +80,7 @@ class ScrollableList extends PureComponent {
|
|||||||
children: PropTypes.node,
|
children: PropTypes.node,
|
||||||
bindToDocument: PropTypes.bool,
|
bindToDocument: PropTypes.bool,
|
||||||
preventScroll: PropTypes.bool,
|
preventScroll: PropTypes.bool,
|
||||||
|
footer: PropTypes.node,
|
||||||
};
|
};
|
||||||
|
|
||||||
static defaultProps = {
|
static defaultProps = {
|
||||||
@ -324,7 +325,7 @@ class ScrollableList extends PureComponent {
|
|||||||
};
|
};
|
||||||
|
|
||||||
render () {
|
render () {
|
||||||
const { children, scrollKey, trackScroll, showLoading, isLoading, hasMore, numPending, prepend, alwaysPrepend, append, emptyMessage, onLoadMore } = this.props;
|
const { children, scrollKey, trackScroll, showLoading, isLoading, hasMore, numPending, prepend, alwaysPrepend, append, footer, emptyMessage, onLoadMore } = this.props;
|
||||||
const { fullscreen } = this.state;
|
const { fullscreen } = this.state;
|
||||||
const childrenCount = Children.count(children);
|
const childrenCount = Children.count(children);
|
||||||
|
|
||||||
@ -342,11 +343,13 @@ class ScrollableList extends PureComponent {
|
|||||||
<div className='scrollable__append'>
|
<div className='scrollable__append'>
|
||||||
<LoadingIndicator />
|
<LoadingIndicator />
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
{footer}
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
} else if (isLoading || childrenCount > 0 || numPending > 0 || hasMore || !emptyMessage) {
|
} else if (isLoading || childrenCount > 0 || numPending > 0 || hasMore || !emptyMessage) {
|
||||||
scrollableArea = (
|
scrollableArea = (
|
||||||
<div className={classNames('scrollable', { fullscreen })} ref={this.setRef} onMouseMove={this.handleMouseMove}>
|
<div className={classNames('scrollable scrollable--flex', { fullscreen })} ref={this.setRef} onMouseMove={this.handleMouseMove}>
|
||||||
<div role='feed' className='item-list'>
|
<div role='feed' className='item-list'>
|
||||||
{prepend}
|
{prepend}
|
||||||
|
|
||||||
@ -375,6 +378,8 @@ class ScrollableList extends PureComponent {
|
|||||||
|
|
||||||
{!hasMore && append}
|
{!hasMore && append}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
{footer}
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
} else {
|
} else {
|
||||||
@ -385,6 +390,8 @@ class ScrollableList extends PureComponent {
|
|||||||
<div className='empty-column-indicator'>
|
<div className='empty-column-indicator'>
|
||||||
{emptyMessage}
|
{emptyMessage}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
{footer}
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@ -3,6 +3,8 @@ import PropTypes from 'prop-types';
|
|||||||
import { injectIntl, defineMessages, FormattedMessage } from 'react-intl';
|
import { injectIntl, defineMessages, FormattedMessage } from 'react-intl';
|
||||||
|
|
||||||
import classNames from 'classnames';
|
import classNames from 'classnames';
|
||||||
|
import { Link } from 'react-router-dom';
|
||||||
|
|
||||||
|
|
||||||
import ImmutablePropTypes from 'react-immutable-proptypes';
|
import ImmutablePropTypes from 'react-immutable-proptypes';
|
||||||
import ImmutablePureComponent from 'react-immutable-pure-component';
|
import ImmutablePureComponent from 'react-immutable-pure-component';
|
||||||
@ -164,32 +166,18 @@ class Status extends ImmutablePureComponent {
|
|||||||
};
|
};
|
||||||
|
|
||||||
handleClick = e => {
|
handleClick = e => {
|
||||||
if (e && (e.button !== 0 || e.ctrlKey || e.metaKey)) {
|
e.preventDefault();
|
||||||
|
this.handleHotkeyOpen(e);
|
||||||
|
};
|
||||||
|
|
||||||
|
handleMouseUp = e => {
|
||||||
|
// Only handle clicks on the empty space above the content
|
||||||
|
|
||||||
|
if (e.target !== e.currentTarget) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (e) {
|
this.handleClick(e);
|
||||||
e.preventDefault();
|
|
||||||
}
|
|
||||||
|
|
||||||
this.handleHotkeyOpen();
|
|
||||||
};
|
|
||||||
|
|
||||||
handlePrependAccountClick = e => {
|
|
||||||
this.handleAccountClick(e, false);
|
|
||||||
};
|
|
||||||
|
|
||||||
handleAccountClick = (e, proper = true) => {
|
|
||||||
if (e && (e.button !== 0 || e.ctrlKey || e.metaKey)) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (e) {
|
|
||||||
e.preventDefault();
|
|
||||||
e.stopPropagation();
|
|
||||||
}
|
|
||||||
|
|
||||||
this._openProfile(proper);
|
|
||||||
};
|
};
|
||||||
|
|
||||||
handleExpandedToggle = () => {
|
handleExpandedToggle = () => {
|
||||||
@ -287,7 +275,7 @@ class Status extends ImmutablePureComponent {
|
|||||||
this.props.onMention(this._properStatus().get('account'));
|
this.props.onMention(this._properStatus().get('account'));
|
||||||
};
|
};
|
||||||
|
|
||||||
handleHotkeyOpen = () => {
|
handleHotkeyOpen = (e) => {
|
||||||
if (this.props.onClick) {
|
if (this.props.onClick) {
|
||||||
this.props.onClick();
|
this.props.onClick();
|
||||||
return;
|
return;
|
||||||
@ -300,7 +288,13 @@ class Status extends ImmutablePureComponent {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
history.push(`/@${status.getIn(['account', 'acct'])}/${status.get('id')}`);
|
const path = `/@${status.getIn(['account', 'acct'])}/${status.get('id')}`;
|
||||||
|
|
||||||
|
if (e?.button === 0 && !(e?.ctrlKey || e?.metaKey)) {
|
||||||
|
history.push(path);
|
||||||
|
} else if (e?.button === 1 || (e?.button === 0 && (e?.ctrlKey || e?.metaKey))) {
|
||||||
|
window.open(path, '_blank', 'noreferrer noopener');
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
handleHotkeyOpenProfile = () => {
|
handleHotkeyOpenProfile = () => {
|
||||||
@ -412,7 +406,7 @@ class Status extends ImmutablePureComponent {
|
|||||||
prepend = (
|
prepend = (
|
||||||
<div className='status__prepend'>
|
<div className='status__prepend'>
|
||||||
<div className='status__prepend__icon'><Icon id='retweet' icon={RepeatIcon} /></div>
|
<div className='status__prepend__icon'><Icon id='retweet' icon={RepeatIcon} /></div>
|
||||||
<FormattedMessage id='status.reblogged_by' defaultMessage='{name} boosted' values={{ name: <a onClick={this.handlePrependAccountClick} data-id={status.getIn(['account', 'id'])} data-hover-card-account={status.getIn(['account', 'id'])} href={`/@${status.getIn(['account', 'acct'])}`} className='status__display-name muted'><bdi><strong dangerouslySetInnerHTML={display_name_html} /></bdi></a> }} />
|
<FormattedMessage id='status.reblogged_by' defaultMessage='{name} boosted' values={{ name: <Link data-id={status.getIn(['account', 'id'])} data-hover-card-account={status.getIn(['account', 'id'])} to={`/@${status.getIn(['account', 'acct'])}`} className='status__display-name muted'><bdi><strong dangerouslySetInnerHTML={display_name_html} /></bdi></Link> }} />
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
|
||||||
@ -550,20 +544,19 @@ class Status extends ImmutablePureComponent {
|
|||||||
<div className={classNames('status', `status-${status.get('visibility')}`, { 'status-reply': !!status.get('in_reply_to_id'), 'status--in-thread': !!rootId, 'status--first-in-thread': previousId && (!connectUp || connectToRoot), muted: this.props.muted })} data-id={status.get('id')}>
|
<div className={classNames('status', `status-${status.get('visibility')}`, { 'status-reply': !!status.get('in_reply_to_id'), 'status--in-thread': !!rootId, 'status--first-in-thread': previousId && (!connectUp || connectToRoot), muted: this.props.muted })} data-id={status.get('id')}>
|
||||||
{(connectReply || connectUp || connectToRoot) && <div className={classNames('status__line', { 'status__line--full': connectReply, 'status__line--first': !status.get('in_reply_to_id') && !connectToRoot })} />}
|
{(connectReply || connectUp || connectToRoot) && <div className={classNames('status__line', { 'status__line--full': connectReply, 'status__line--first': !status.get('in_reply_to_id') && !connectToRoot })} />}
|
||||||
|
|
||||||
{/* eslint-disable-next-line jsx-a11y/no-static-element-interactions */}
|
<div onMouseUp={this.handleMouseUp} className='status__info'>
|
||||||
<div onClick={this.handleClick} className='status__info'>
|
<Link to={`/@${status.getIn(['account', 'acct'])}/${status.get('id')}`} className='status__relative-time'>
|
||||||
<a href={`/@${status.getIn(['account', 'acct'])}/${status.get('id')}`} className='status__relative-time' target='_blank' rel='noopener noreferrer'>
|
|
||||||
<span className='status__visibility-icon'><VisibilityIcon visibility={status.get('visibility')} /></span>
|
<span className='status__visibility-icon'><VisibilityIcon visibility={status.get('visibility')} /></span>
|
||||||
<RelativeTimestamp timestamp={status.get('created_at')} />{status.get('edited_at') && <abbr title={intl.formatMessage(messages.edited, { date: intl.formatDate(status.get('edited_at'), { year: 'numeric', month: 'short', day: '2-digit', hour: '2-digit', minute: '2-digit' }) })}> *</abbr>}
|
<RelativeTimestamp timestamp={status.get('created_at')} />{status.get('edited_at') && <abbr title={intl.formatMessage(messages.edited, { date: intl.formatDate(status.get('edited_at'), { year: 'numeric', month: 'short', day: '2-digit', hour: '2-digit', minute: '2-digit' }) })}> *</abbr>}
|
||||||
</a>
|
</Link>
|
||||||
|
|
||||||
<a onClick={this.handleAccountClick} href={`/@${status.getIn(['account', 'acct'])}`} title={status.getIn(['account', 'acct'])} data-hover-card-account={status.getIn(['account', 'id'])} className='status__display-name' target='_blank' rel='noopener noreferrer'>
|
<Link to={`/@${status.getIn(['account', 'acct'])}`} title={status.getIn(['account', 'acct'])} data-hover-card-account={status.getIn(['account', 'id'])} className='status__display-name'>
|
||||||
<div className='status__avatar'>
|
<div className='status__avatar'>
|
||||||
{statusAvatar}
|
{statusAvatar}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<DisplayName account={status.get('account')} />
|
<DisplayName account={status.get('account')} />
|
||||||
</a>
|
</Link>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{matchedFilters && <FilterWarning title={matchedFilters.join(', ')} expanded={this.state.showDespiteFilter} onClick={this.handleFilterToggle} />}
|
{matchedFilters && <FilterWarning title={matchedFilters.join(', ')} expanded={this.state.showDespiteFilter} onClick={this.handleFilterToggle} />}
|
||||||
|
@ -204,8 +204,8 @@ class StatusContent extends PureComponent {
|
|||||||
element = element.parentNode;
|
element = element.parentNode;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (deltaX + deltaY < 5 && e.button === 0 && this.props.onClick) {
|
if (deltaX + deltaY < 5 && (e.button === 0 || e.button === 1) && this.props.onClick) {
|
||||||
this.props.onClick();
|
this.props.onClick(e);
|
||||||
}
|
}
|
||||||
|
|
||||||
this.startXY = null;
|
this.startXY = null;
|
||||||
|
@ -15,6 +15,13 @@ const mapStateToProps = state => ({
|
|||||||
openedViaKeyboard: state.dropdownMenu.keyboard,
|
openedViaKeyboard: state.dropdownMenu.keyboard,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param {any} dispatch
|
||||||
|
* @param {Object} root0
|
||||||
|
* @param {any} [root0.status]
|
||||||
|
* @param {any} root0.items
|
||||||
|
* @param {any} [root0.scrollKey]
|
||||||
|
*/
|
||||||
const mapDispatchToProps = (dispatch, { status, items, scrollKey }) => ({
|
const mapDispatchToProps = (dispatch, { status, items, scrollKey }) => ({
|
||||||
onOpen(id, onItemClick, keyboard) {
|
onOpen(id, onItemClick, keyboard) {
|
||||||
if (status) {
|
if (status) {
|
||||||
|
@ -5,7 +5,6 @@ import { FormattedMessage } from 'react-intl';
|
|||||||
|
|
||||||
import { withRouter } from 'react-router-dom';
|
import { withRouter } from 'react-router-dom';
|
||||||
|
|
||||||
import ImmutablePropTypes from 'react-immutable-proptypes';
|
|
||||||
import { connect } from 'react-redux';
|
import { connect } from 'react-redux';
|
||||||
|
|
||||||
import { fetchSuggestions } from 'mastodon/actions/suggestions';
|
import { fetchSuggestions } from 'mastodon/actions/suggestions';
|
||||||
@ -15,15 +14,15 @@ import { WithRouterPropTypes } from 'mastodon/utils/react_router';
|
|||||||
import { Card } from './components/card';
|
import { Card } from './components/card';
|
||||||
|
|
||||||
const mapStateToProps = state => ({
|
const mapStateToProps = state => ({
|
||||||
suggestions: state.getIn(['suggestions', 'items']),
|
suggestions: state.suggestions.items,
|
||||||
isLoading: state.getIn(['suggestions', 'isLoading']),
|
isLoading: state.suggestions.isLoading,
|
||||||
});
|
});
|
||||||
|
|
||||||
class Suggestions extends PureComponent {
|
class Suggestions extends PureComponent {
|
||||||
|
|
||||||
static propTypes = {
|
static propTypes = {
|
||||||
isLoading: PropTypes.bool,
|
isLoading: PropTypes.bool,
|
||||||
suggestions: ImmutablePropTypes.list,
|
suggestions: PropTypes.array,
|
||||||
dispatch: PropTypes.func.isRequired,
|
dispatch: PropTypes.func.isRequired,
|
||||||
...WithRouterPropTypes,
|
...WithRouterPropTypes,
|
||||||
};
|
};
|
||||||
@ -32,17 +31,17 @@ class Suggestions extends PureComponent {
|
|||||||
const { dispatch, suggestions, history } = this.props;
|
const { dispatch, suggestions, history } = this.props;
|
||||||
|
|
||||||
// If we're navigating back to the screen, do not trigger a reload
|
// If we're navigating back to the screen, do not trigger a reload
|
||||||
if (history.action === 'POP' && suggestions.size > 0) {
|
if (history.action === 'POP' && suggestions.length > 0) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
dispatch(fetchSuggestions(true));
|
dispatch(fetchSuggestions());
|
||||||
}
|
}
|
||||||
|
|
||||||
render () {
|
render () {
|
||||||
const { isLoading, suggestions } = this.props;
|
const { isLoading, suggestions } = this.props;
|
||||||
|
|
||||||
if (!isLoading && suggestions.isEmpty()) {
|
if (!isLoading && suggestions.length === 0) {
|
||||||
return (
|
return (
|
||||||
<div className='explore__suggestions scrollable scrollable--flex'>
|
<div className='explore__suggestions scrollable scrollable--flex'>
|
||||||
<div className='empty-column-indicator'>
|
<div className='empty-column-indicator'>
|
||||||
@ -56,9 +55,9 @@ class Suggestions extends PureComponent {
|
|||||||
<div className='explore__suggestions scrollable' data-nosnippet>
|
<div className='explore__suggestions scrollable' data-nosnippet>
|
||||||
{isLoading ? <LoadingIndicator /> : suggestions.map(suggestion => (
|
{isLoading ? <LoadingIndicator /> : suggestions.map(suggestion => (
|
||||||
<Card
|
<Card
|
||||||
key={suggestion.get('account')}
|
key={suggestion.account_id}
|
||||||
id={suggestion.get('account')}
|
id={suggestion.account_id}
|
||||||
source={suggestion.getIn(['sources', 0])}
|
source={suggestion.sources[0]}
|
||||||
/>
|
/>
|
||||||
))}
|
))}
|
||||||
</div>
|
</div>
|
||||||
|
@ -1,217 +0,0 @@
|
|||||||
import PropTypes from 'prop-types';
|
|
||||||
import { useEffect, useCallback, useRef, useState } from 'react';
|
|
||||||
|
|
||||||
import { FormattedMessage, useIntl, defineMessages } from 'react-intl';
|
|
||||||
|
|
||||||
import { Link } from 'react-router-dom';
|
|
||||||
|
|
||||||
import ImmutablePropTypes from 'react-immutable-proptypes';
|
|
||||||
import { useDispatch, useSelector } from 'react-redux';
|
|
||||||
|
|
||||||
import ChevronLeftIcon from '@/material-icons/400-24px/chevron_left.svg?react';
|
|
||||||
import ChevronRightIcon from '@/material-icons/400-24px/chevron_right.svg?react';
|
|
||||||
import CloseIcon from '@/material-icons/400-24px/close.svg?react';
|
|
||||||
import InfoIcon from '@/material-icons/400-24px/info.svg?react';
|
|
||||||
import { changeSetting } from 'mastodon/actions/settings';
|
|
||||||
import { fetchSuggestions, dismissSuggestion } from 'mastodon/actions/suggestions';
|
|
||||||
import { Avatar } from 'mastodon/components/avatar';
|
|
||||||
import { DisplayName } from 'mastodon/components/display_name';
|
|
||||||
import { FollowButton } from 'mastodon/components/follow_button';
|
|
||||||
import { Icon } from 'mastodon/components/icon';
|
|
||||||
import { IconButton } from 'mastodon/components/icon_button';
|
|
||||||
import { VerifiedBadge } from 'mastodon/components/verified_badge';
|
|
||||||
import { domain } from 'mastodon/initial_state';
|
|
||||||
|
|
||||||
const messages = defineMessages({
|
|
||||||
follow: { id: 'account.follow', defaultMessage: 'Follow' },
|
|
||||||
unfollow: { id: 'account.unfollow', defaultMessage: 'Unfollow' },
|
|
||||||
previous: { id: 'lightbox.previous', defaultMessage: 'Previous' },
|
|
||||||
next: { id: 'lightbox.next', defaultMessage: 'Next' },
|
|
||||||
dismiss: { id: 'follow_suggestions.dismiss', defaultMessage: "Don't show again" },
|
|
||||||
friendsOfFriendsHint: { id: 'follow_suggestions.hints.friends_of_friends', defaultMessage: 'This profile is popular among the people you follow.' },
|
|
||||||
similarToRecentlyFollowedHint: { id: 'follow_suggestions.hints.similar_to_recently_followed', defaultMessage: 'This profile is similar to the profiles you have most recently followed.' },
|
|
||||||
featuredHint: { id: 'follow_suggestions.hints.featured', defaultMessage: 'This profile has been hand-picked by the {domain} team.' },
|
|
||||||
mostFollowedHint: { id: 'follow_suggestions.hints.most_followed', defaultMessage: 'This profile is one of the most followed on {domain}.'},
|
|
||||||
mostInteractionsHint: { id: 'follow_suggestions.hints.most_interactions', defaultMessage: 'This profile has been recently getting a lot of attention on {domain}.' },
|
|
||||||
});
|
|
||||||
|
|
||||||
const Source = ({ id }) => {
|
|
||||||
const intl = useIntl();
|
|
||||||
|
|
||||||
let label, hint;
|
|
||||||
|
|
||||||
switch (id) {
|
|
||||||
case 'friends_of_friends':
|
|
||||||
hint = intl.formatMessage(messages.friendsOfFriendsHint);
|
|
||||||
label = <FormattedMessage id='follow_suggestions.personalized_suggestion' defaultMessage='Personalized suggestion' />;
|
|
||||||
break;
|
|
||||||
case 'similar_to_recently_followed':
|
|
||||||
hint = intl.formatMessage(messages.similarToRecentlyFollowedHint);
|
|
||||||
label = <FormattedMessage id='follow_suggestions.personalized_suggestion' defaultMessage='Personalized suggestion' />;
|
|
||||||
break;
|
|
||||||
case 'featured':
|
|
||||||
hint = intl.formatMessage(messages.featuredHint, { domain });
|
|
||||||
label = <FormattedMessage id='follow_suggestions.curated_suggestion' defaultMessage='Staff pick' />;
|
|
||||||
break;
|
|
||||||
case 'most_followed':
|
|
||||||
hint = intl.formatMessage(messages.mostFollowedHint, { domain });
|
|
||||||
label = <FormattedMessage id='follow_suggestions.popular_suggestion' defaultMessage='Popular suggestion' />;
|
|
||||||
break;
|
|
||||||
case 'most_interactions':
|
|
||||||
hint = intl.formatMessage(messages.mostInteractionsHint, { domain });
|
|
||||||
label = <FormattedMessage id='follow_suggestions.popular_suggestion' defaultMessage='Popular suggestion' />;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
return (
|
|
||||||
<div className='inline-follow-suggestions__body__scrollable__card__text-stack__source' title={hint}>
|
|
||||||
<Icon icon={InfoIcon} />
|
|
||||||
{label}
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
Source.propTypes = {
|
|
||||||
id: PropTypes.oneOf(['friends_of_friends', 'similar_to_recently_followed', 'featured', 'most_followed', 'most_interactions']),
|
|
||||||
};
|
|
||||||
|
|
||||||
const Card = ({ id, sources }) => {
|
|
||||||
const intl = useIntl();
|
|
||||||
const account = useSelector(state => state.getIn(['accounts', id]));
|
|
||||||
const firstVerifiedField = account.get('fields').find(item => !!item.get('verified_at'));
|
|
||||||
const dispatch = useDispatch();
|
|
||||||
|
|
||||||
const handleDismiss = useCallback(() => {
|
|
||||||
dispatch(dismissSuggestion(id));
|
|
||||||
}, [id, dispatch]);
|
|
||||||
|
|
||||||
return (
|
|
||||||
<div className='inline-follow-suggestions__body__scrollable__card'>
|
|
||||||
<IconButton iconComponent={CloseIcon} onClick={handleDismiss} title={intl.formatMessage(messages.dismiss)} />
|
|
||||||
|
|
||||||
<div className='inline-follow-suggestions__body__scrollable__card__avatar'>
|
|
||||||
<Link to={`/@${account.get('acct')}`}><Avatar account={account} size={72} /></Link>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div className='inline-follow-suggestions__body__scrollable__card__text-stack'>
|
|
||||||
<Link to={`/@${account.get('acct')}`}><DisplayName account={account} /></Link>
|
|
||||||
{firstVerifiedField ? <VerifiedBadge link={firstVerifiedField.get('value')} /> : <Source id={sources.get(0)} />}
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<FollowButton accountId={id} />
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
Card.propTypes = {
|
|
||||||
id: PropTypes.string.isRequired,
|
|
||||||
sources: ImmutablePropTypes.list,
|
|
||||||
};
|
|
||||||
|
|
||||||
const DISMISSIBLE_ID = 'home/follow-suggestions';
|
|
||||||
|
|
||||||
export const InlineFollowSuggestions = ({ hidden }) => {
|
|
||||||
const intl = useIntl();
|
|
||||||
const dispatch = useDispatch();
|
|
||||||
const suggestions = useSelector(state => state.getIn(['suggestions', 'items']));
|
|
||||||
const isLoading = useSelector(state => state.getIn(['suggestions', 'isLoading']));
|
|
||||||
const dismissed = useSelector(state => state.getIn(['settings', 'dismissed_banners', DISMISSIBLE_ID]));
|
|
||||||
const bodyRef = useRef();
|
|
||||||
const [canScrollLeft, setCanScrollLeft] = useState(false);
|
|
||||||
const [canScrollRight, setCanScrollRight] = useState(true);
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
dispatch(fetchSuggestions());
|
|
||||||
}, [dispatch]);
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
if (!bodyRef.current) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (getComputedStyle(bodyRef.current).direction === 'rtl') {
|
|
||||||
setCanScrollLeft((bodyRef.current.clientWidth - bodyRef.current.scrollLeft) < bodyRef.current.scrollWidth);
|
|
||||||
setCanScrollRight(bodyRef.current.scrollLeft < 0);
|
|
||||||
} else {
|
|
||||||
setCanScrollLeft(bodyRef.current.scrollLeft > 0);
|
|
||||||
setCanScrollRight((bodyRef.current.scrollLeft + bodyRef.current.clientWidth) < bodyRef.current.scrollWidth);
|
|
||||||
}
|
|
||||||
}, [setCanScrollRight, setCanScrollLeft, bodyRef, suggestions]);
|
|
||||||
|
|
||||||
const handleLeftNav = useCallback(() => {
|
|
||||||
bodyRef.current.scrollLeft -= 200;
|
|
||||||
}, [bodyRef]);
|
|
||||||
|
|
||||||
const handleRightNav = useCallback(() => {
|
|
||||||
bodyRef.current.scrollLeft += 200;
|
|
||||||
}, [bodyRef]);
|
|
||||||
|
|
||||||
const handleScroll = useCallback(() => {
|
|
||||||
if (!bodyRef.current) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (getComputedStyle(bodyRef.current).direction === 'rtl') {
|
|
||||||
setCanScrollLeft((bodyRef.current.clientWidth - bodyRef.current.scrollLeft) < bodyRef.current.scrollWidth);
|
|
||||||
setCanScrollRight(bodyRef.current.scrollLeft < 0);
|
|
||||||
} else {
|
|
||||||
setCanScrollLeft(bodyRef.current.scrollLeft > 0);
|
|
||||||
setCanScrollRight((bodyRef.current.scrollLeft + bodyRef.current.clientWidth) < bodyRef.current.scrollWidth);
|
|
||||||
}
|
|
||||||
}, [setCanScrollRight, setCanScrollLeft, bodyRef]);
|
|
||||||
|
|
||||||
const handleDismiss = useCallback(() => {
|
|
||||||
dispatch(changeSetting(['dismissed_banners', DISMISSIBLE_ID], true));
|
|
||||||
}, [dispatch]);
|
|
||||||
|
|
||||||
if (dismissed || (!isLoading && suggestions.isEmpty())) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (hidden) {
|
|
||||||
return (
|
|
||||||
<div className='inline-follow-suggestions' />
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
return (
|
|
||||||
<div className='inline-follow-suggestions'>
|
|
||||||
<div className='inline-follow-suggestions__header'>
|
|
||||||
<h3><FormattedMessage id='follow_suggestions.who_to_follow' defaultMessage='Who to follow' /></h3>
|
|
||||||
|
|
||||||
<div className='inline-follow-suggestions__header__actions'>
|
|
||||||
<button className='link-button' onClick={handleDismiss}><FormattedMessage id='follow_suggestions.dismiss' defaultMessage="Don't show again" /></button>
|
|
||||||
<Link to='/explore/suggestions' className='link-button'><FormattedMessage id='follow_suggestions.view_all' defaultMessage='View all' /></Link>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div className='inline-follow-suggestions__body'>
|
|
||||||
<div className='inline-follow-suggestions__body__scrollable' ref={bodyRef} onScroll={handleScroll}>
|
|
||||||
{suggestions.map(suggestion => (
|
|
||||||
<Card
|
|
||||||
key={suggestion.get('account')}
|
|
||||||
id={suggestion.get('account')}
|
|
||||||
sources={suggestion.get('sources')}
|
|
||||||
/>
|
|
||||||
))}
|
|
||||||
</div>
|
|
||||||
|
|
||||||
{canScrollLeft && (
|
|
||||||
<button className='inline-follow-suggestions__body__scroll-button left' onClick={handleLeftNav} aria-label={intl.formatMessage(messages.previous)}>
|
|
||||||
<div className='inline-follow-suggestions__body__scroll-button__icon'><Icon icon={ChevronLeftIcon} /></div>
|
|
||||||
</button>
|
|
||||||
)}
|
|
||||||
|
|
||||||
{canScrollRight && (
|
|
||||||
<button className='inline-follow-suggestions__body__scroll-button right' onClick={handleRightNav} aria-label={intl.formatMessage(messages.next)}>
|
|
||||||
<div className='inline-follow-suggestions__body__scroll-button__icon'><Icon icon={ChevronRightIcon} /></div>
|
|
||||||
</button>
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
InlineFollowSuggestions.propTypes = {
|
|
||||||
hidden: PropTypes.bool,
|
|
||||||
};
|
|
@ -0,0 +1,326 @@
|
|||||||
|
import { useEffect, useCallback, useRef, useState } from 'react';
|
||||||
|
|
||||||
|
import { FormattedMessage, useIntl, defineMessages } from 'react-intl';
|
||||||
|
|
||||||
|
import { Link } from 'react-router-dom';
|
||||||
|
|
||||||
|
import ChevronLeftIcon from '@/material-icons/400-24px/chevron_left.svg?react';
|
||||||
|
import ChevronRightIcon from '@/material-icons/400-24px/chevron_right.svg?react';
|
||||||
|
import CloseIcon from '@/material-icons/400-24px/close.svg?react';
|
||||||
|
import InfoIcon from '@/material-icons/400-24px/info.svg?react';
|
||||||
|
import { changeSetting } from 'mastodon/actions/settings';
|
||||||
|
import {
|
||||||
|
fetchSuggestions,
|
||||||
|
dismissSuggestion,
|
||||||
|
} from 'mastodon/actions/suggestions';
|
||||||
|
import type { ApiSuggestionSourceJSON } from 'mastodon/api_types/suggestions';
|
||||||
|
import { Avatar } from 'mastodon/components/avatar';
|
||||||
|
import { DisplayName } from 'mastodon/components/display_name';
|
||||||
|
import { FollowButton } from 'mastodon/components/follow_button';
|
||||||
|
import { Icon } from 'mastodon/components/icon';
|
||||||
|
import { IconButton } from 'mastodon/components/icon_button';
|
||||||
|
import { VerifiedBadge } from 'mastodon/components/verified_badge';
|
||||||
|
import { domain } from 'mastodon/initial_state';
|
||||||
|
import { useAppDispatch, useAppSelector } from 'mastodon/store';
|
||||||
|
|
||||||
|
const messages = defineMessages({
|
||||||
|
follow: { id: 'account.follow', defaultMessage: 'Follow' },
|
||||||
|
unfollow: { id: 'account.unfollow', defaultMessage: 'Unfollow' },
|
||||||
|
previous: { id: 'lightbox.previous', defaultMessage: 'Previous' },
|
||||||
|
next: { id: 'lightbox.next', defaultMessage: 'Next' },
|
||||||
|
dismiss: {
|
||||||
|
id: 'follow_suggestions.dismiss',
|
||||||
|
defaultMessage: "Don't show again",
|
||||||
|
},
|
||||||
|
friendsOfFriendsHint: {
|
||||||
|
id: 'follow_suggestions.hints.friends_of_friends',
|
||||||
|
defaultMessage: 'This profile is popular among the people you follow.',
|
||||||
|
},
|
||||||
|
similarToRecentlyFollowedHint: {
|
||||||
|
id: 'follow_suggestions.hints.similar_to_recently_followed',
|
||||||
|
defaultMessage:
|
||||||
|
'This profile is similar to the profiles you have most recently followed.',
|
||||||
|
},
|
||||||
|
featuredHint: {
|
||||||
|
id: 'follow_suggestions.hints.featured',
|
||||||
|
defaultMessage: 'This profile has been hand-picked by the {domain} team.',
|
||||||
|
},
|
||||||
|
mostFollowedHint: {
|
||||||
|
id: 'follow_suggestions.hints.most_followed',
|
||||||
|
defaultMessage: 'This profile is one of the most followed on {domain}.',
|
||||||
|
},
|
||||||
|
mostInteractionsHint: {
|
||||||
|
id: 'follow_suggestions.hints.most_interactions',
|
||||||
|
defaultMessage:
|
||||||
|
'This profile has been recently getting a lot of attention on {domain}.',
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
const Source: React.FC<{
|
||||||
|
id: ApiSuggestionSourceJSON;
|
||||||
|
}> = ({ id }) => {
|
||||||
|
const intl = useIntl();
|
||||||
|
|
||||||
|
let label, hint;
|
||||||
|
|
||||||
|
switch (id) {
|
||||||
|
case 'friends_of_friends':
|
||||||
|
hint = intl.formatMessage(messages.friendsOfFriendsHint);
|
||||||
|
label = (
|
||||||
|
<FormattedMessage
|
||||||
|
id='follow_suggestions.personalized_suggestion'
|
||||||
|
defaultMessage='Personalized suggestion'
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
break;
|
||||||
|
case 'similar_to_recently_followed':
|
||||||
|
hint = intl.formatMessage(messages.similarToRecentlyFollowedHint);
|
||||||
|
label = (
|
||||||
|
<FormattedMessage
|
||||||
|
id='follow_suggestions.personalized_suggestion'
|
||||||
|
defaultMessage='Personalized suggestion'
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
break;
|
||||||
|
case 'featured':
|
||||||
|
hint = intl.formatMessage(messages.featuredHint, { domain });
|
||||||
|
label = (
|
||||||
|
<FormattedMessage
|
||||||
|
id='follow_suggestions.curated_suggestion'
|
||||||
|
defaultMessage='Staff pick'
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
break;
|
||||||
|
case 'most_followed':
|
||||||
|
hint = intl.formatMessage(messages.mostFollowedHint, { domain });
|
||||||
|
label = (
|
||||||
|
<FormattedMessage
|
||||||
|
id='follow_suggestions.popular_suggestion'
|
||||||
|
defaultMessage='Popular suggestion'
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
break;
|
||||||
|
case 'most_interactions':
|
||||||
|
hint = intl.formatMessage(messages.mostInteractionsHint, { domain });
|
||||||
|
label = (
|
||||||
|
<FormattedMessage
|
||||||
|
id='follow_suggestions.popular_suggestion'
|
||||||
|
defaultMessage='Popular suggestion'
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div
|
||||||
|
className='inline-follow-suggestions__body__scrollable__card__text-stack__source'
|
||||||
|
title={hint}
|
||||||
|
>
|
||||||
|
<Icon id='' icon={InfoIcon} />
|
||||||
|
{label}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
const Card: React.FC<{
|
||||||
|
id: string;
|
||||||
|
sources: [ApiSuggestionSourceJSON, ...ApiSuggestionSourceJSON[]];
|
||||||
|
}> = ({ id, sources }) => {
|
||||||
|
const intl = useIntl();
|
||||||
|
const account = useAppSelector((state) => state.accounts.get(id));
|
||||||
|
const firstVerifiedField = account?.fields.find((item) => !!item.verified_at);
|
||||||
|
const dispatch = useAppDispatch();
|
||||||
|
|
||||||
|
const handleDismiss = useCallback(() => {
|
||||||
|
void dispatch(dismissSuggestion({ accountId: id }));
|
||||||
|
}, [id, dispatch]);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className='inline-follow-suggestions__body__scrollable__card'>
|
||||||
|
<IconButton
|
||||||
|
icon=''
|
||||||
|
iconComponent={CloseIcon}
|
||||||
|
onClick={handleDismiss}
|
||||||
|
title={intl.formatMessage(messages.dismiss)}
|
||||||
|
/>
|
||||||
|
|
||||||
|
<div className='inline-follow-suggestions__body__scrollable__card__avatar'>
|
||||||
|
<Link to={`/@${account?.acct}`}>
|
||||||
|
<Avatar account={account} size={72} />
|
||||||
|
</Link>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className='inline-follow-suggestions__body__scrollable__card__text-stack'>
|
||||||
|
<Link to={`/@${account?.acct}`}>
|
||||||
|
<DisplayName account={account} />
|
||||||
|
</Link>
|
||||||
|
{firstVerifiedField ? (
|
||||||
|
<VerifiedBadge link={firstVerifiedField.value} />
|
||||||
|
) : (
|
||||||
|
<Source id={sources[0]} />
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<FollowButton accountId={id} />
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
const DISMISSIBLE_ID = 'home/follow-suggestions';
|
||||||
|
|
||||||
|
export const InlineFollowSuggestions: React.FC<{
|
||||||
|
hidden?: boolean;
|
||||||
|
}> = ({ hidden }) => {
|
||||||
|
const intl = useIntl();
|
||||||
|
const dispatch = useAppDispatch();
|
||||||
|
const suggestions = useAppSelector((state) => state.suggestions.items);
|
||||||
|
const isLoading = useAppSelector((state) => state.suggestions.isLoading);
|
||||||
|
const dismissed = useAppSelector(
|
||||||
|
(state) =>
|
||||||
|
// eslint-disable-next-line @typescript-eslint/no-unsafe-member-access, @typescript-eslint/no-unsafe-call
|
||||||
|
state.settings.getIn(['dismissed_banners', DISMISSIBLE_ID]) as boolean,
|
||||||
|
);
|
||||||
|
const bodyRef = useRef<HTMLDivElement>(null);
|
||||||
|
const [canScrollLeft, setCanScrollLeft] = useState(false);
|
||||||
|
const [canScrollRight, setCanScrollRight] = useState(true);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
void dispatch(fetchSuggestions());
|
||||||
|
}, [dispatch]);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (!bodyRef.current) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (getComputedStyle(bodyRef.current).direction === 'rtl') {
|
||||||
|
setCanScrollLeft(
|
||||||
|
bodyRef.current.clientWidth - bodyRef.current.scrollLeft <
|
||||||
|
bodyRef.current.scrollWidth,
|
||||||
|
);
|
||||||
|
setCanScrollRight(bodyRef.current.scrollLeft < 0);
|
||||||
|
} else {
|
||||||
|
setCanScrollLeft(bodyRef.current.scrollLeft > 0);
|
||||||
|
setCanScrollRight(
|
||||||
|
bodyRef.current.scrollLeft + bodyRef.current.clientWidth <
|
||||||
|
bodyRef.current.scrollWidth,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}, [setCanScrollRight, setCanScrollLeft, suggestions]);
|
||||||
|
|
||||||
|
const handleLeftNav = useCallback(() => {
|
||||||
|
if (!bodyRef.current) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
bodyRef.current.scrollLeft -= 200;
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
const handleRightNav = useCallback(() => {
|
||||||
|
if (!bodyRef.current) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
bodyRef.current.scrollLeft += 200;
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
const handleScroll = useCallback(() => {
|
||||||
|
if (!bodyRef.current) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (getComputedStyle(bodyRef.current).direction === 'rtl') {
|
||||||
|
setCanScrollLeft(
|
||||||
|
bodyRef.current.clientWidth - bodyRef.current.scrollLeft <
|
||||||
|
bodyRef.current.scrollWidth,
|
||||||
|
);
|
||||||
|
setCanScrollRight(bodyRef.current.scrollLeft < 0);
|
||||||
|
} else {
|
||||||
|
setCanScrollLeft(bodyRef.current.scrollLeft > 0);
|
||||||
|
setCanScrollRight(
|
||||||
|
bodyRef.current.scrollLeft + bodyRef.current.clientWidth <
|
||||||
|
bodyRef.current.scrollWidth,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}, [setCanScrollRight, setCanScrollLeft]);
|
||||||
|
|
||||||
|
const handleDismiss = useCallback(() => {
|
||||||
|
dispatch(changeSetting(['dismissed_banners', DISMISSIBLE_ID], true));
|
||||||
|
}, [dispatch]);
|
||||||
|
|
||||||
|
if (dismissed || (!isLoading && suggestions.length === 0)) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (hidden) {
|
||||||
|
return <div className='inline-follow-suggestions' />;
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className='inline-follow-suggestions'>
|
||||||
|
<div className='inline-follow-suggestions__header'>
|
||||||
|
<h3>
|
||||||
|
<FormattedMessage
|
||||||
|
id='follow_suggestions.who_to_follow'
|
||||||
|
defaultMessage='Who to follow'
|
||||||
|
/>
|
||||||
|
</h3>
|
||||||
|
|
||||||
|
<div className='inline-follow-suggestions__header__actions'>
|
||||||
|
<button className='link-button' onClick={handleDismiss}>
|
||||||
|
<FormattedMessage
|
||||||
|
id='follow_suggestions.dismiss'
|
||||||
|
defaultMessage="Don't show again"
|
||||||
|
/>
|
||||||
|
</button>
|
||||||
|
<Link to='/explore/suggestions' className='link-button'>
|
||||||
|
<FormattedMessage
|
||||||
|
id='follow_suggestions.view_all'
|
||||||
|
defaultMessage='View all'
|
||||||
|
/>
|
||||||
|
</Link>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className='inline-follow-suggestions__body'>
|
||||||
|
<div
|
||||||
|
className='inline-follow-suggestions__body__scrollable'
|
||||||
|
ref={bodyRef}
|
||||||
|
onScroll={handleScroll}
|
||||||
|
>
|
||||||
|
{suggestions.map((suggestion) => (
|
||||||
|
<Card
|
||||||
|
key={suggestion.account_id}
|
||||||
|
id={suggestion.account_id}
|
||||||
|
sources={suggestion.sources}
|
||||||
|
/>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{canScrollLeft && (
|
||||||
|
<button
|
||||||
|
className='inline-follow-suggestions__body__scroll-button left'
|
||||||
|
onClick={handleLeftNav}
|
||||||
|
aria-label={intl.formatMessage(messages.previous)}
|
||||||
|
>
|
||||||
|
<div className='inline-follow-suggestions__body__scroll-button__icon'>
|
||||||
|
<Icon id='' icon={ChevronLeftIcon} />
|
||||||
|
</div>
|
||||||
|
</button>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{canScrollRight && (
|
||||||
|
<button
|
||||||
|
className='inline-follow-suggestions__body__scroll-button right'
|
||||||
|
onClick={handleRightNav}
|
||||||
|
aria-label={intl.formatMessage(messages.next)}
|
||||||
|
>
|
||||||
|
<div className='inline-follow-suggestions__body__scroll-button__icon'>
|
||||||
|
<Icon id='' icon={ChevronRightIcon} />
|
||||||
|
</div>
|
||||||
|
</button>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
@ -1,43 +0,0 @@
|
|||||||
import { injectIntl } from 'react-intl';
|
|
||||||
|
|
||||||
import ImmutablePropTypes from 'react-immutable-proptypes';
|
|
||||||
import ImmutablePureComponent from 'react-immutable-pure-component';
|
|
||||||
import { connect } from 'react-redux';
|
|
||||||
|
|
||||||
import { Avatar } from '../../../components/avatar';
|
|
||||||
import { DisplayName } from '../../../components/display_name';
|
|
||||||
import { makeGetAccount } from '../../../selectors';
|
|
||||||
|
|
||||||
const makeMapStateToProps = () => {
|
|
||||||
const getAccount = makeGetAccount();
|
|
||||||
|
|
||||||
const mapStateToProps = (state, { accountId }) => ({
|
|
||||||
account: getAccount(state, accountId),
|
|
||||||
});
|
|
||||||
|
|
||||||
return mapStateToProps;
|
|
||||||
};
|
|
||||||
|
|
||||||
class Account extends ImmutablePureComponent {
|
|
||||||
|
|
||||||
static propTypes = {
|
|
||||||
account: ImmutablePropTypes.record.isRequired,
|
|
||||||
};
|
|
||||||
|
|
||||||
render () {
|
|
||||||
const { account } = this.props;
|
|
||||||
return (
|
|
||||||
<div className='account'>
|
|
||||||
<div className='account__wrapper'>
|
|
||||||
<div className='account__display-name'>
|
|
||||||
<div className='account__avatar-wrapper'><Avatar account={account} size={36} /></div>
|
|
||||||
<DisplayName account={account} />
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
export default connect(makeMapStateToProps)(injectIntl(Account));
|
|
@ -1,75 +0,0 @@
|
|||||||
import PropTypes from 'prop-types';
|
|
||||||
|
|
||||||
import { defineMessages, injectIntl } from 'react-intl';
|
|
||||||
|
|
||||||
import ImmutablePropTypes from 'react-immutable-proptypes';
|
|
||||||
import ImmutablePureComponent from 'react-immutable-pure-component';
|
|
||||||
import { connect } from 'react-redux';
|
|
||||||
|
|
||||||
import AddIcon from '@/material-icons/400-24px/add.svg?react';
|
|
||||||
import CloseIcon from '@/material-icons/400-24px/close.svg?react';
|
|
||||||
import ListAltIcon from '@/material-icons/400-24px/list_alt.svg?react';
|
|
||||||
import { Icon } from 'mastodon/components/icon';
|
|
||||||
|
|
||||||
import { removeFromListAdder, addToListAdder } from '../../../actions/lists';
|
|
||||||
import { IconButton } from '../../../components/icon_button';
|
|
||||||
|
|
||||||
const messages = defineMessages({
|
|
||||||
remove: { id: 'lists.account.remove', defaultMessage: 'Remove from list' },
|
|
||||||
add: { id: 'lists.account.add', defaultMessage: 'Add to list' },
|
|
||||||
});
|
|
||||||
|
|
||||||
const MapStateToProps = (state, { listId, added }) => ({
|
|
||||||
list: state.get('lists').get(listId),
|
|
||||||
added: typeof added === 'undefined' ? state.getIn(['listAdder', 'lists', 'items']).includes(listId) : added,
|
|
||||||
});
|
|
||||||
|
|
||||||
const mapDispatchToProps = (dispatch, { listId }) => ({
|
|
||||||
onRemove: () => dispatch(removeFromListAdder(listId)),
|
|
||||||
onAdd: () => dispatch(addToListAdder(listId)),
|
|
||||||
});
|
|
||||||
|
|
||||||
class List extends ImmutablePureComponent {
|
|
||||||
|
|
||||||
static propTypes = {
|
|
||||||
list: ImmutablePropTypes.map.isRequired,
|
|
||||||
intl: PropTypes.object.isRequired,
|
|
||||||
onRemove: PropTypes.func.isRequired,
|
|
||||||
onAdd: PropTypes.func.isRequired,
|
|
||||||
added: PropTypes.bool,
|
|
||||||
};
|
|
||||||
|
|
||||||
static defaultProps = {
|
|
||||||
added: false,
|
|
||||||
};
|
|
||||||
|
|
||||||
render () {
|
|
||||||
const { list, intl, onRemove, onAdd, added } = this.props;
|
|
||||||
|
|
||||||
let button;
|
|
||||||
|
|
||||||
if (added) {
|
|
||||||
button = <IconButton icon='times' iconComponent={CloseIcon} title={intl.formatMessage(messages.remove)} onClick={onRemove} />;
|
|
||||||
} else {
|
|
||||||
button = <IconButton icon='plus' iconComponent={AddIcon} title={intl.formatMessage(messages.add)} onClick={onAdd} />;
|
|
||||||
}
|
|
||||||
|
|
||||||
return (
|
|
||||||
<div className='list'>
|
|
||||||
<div className='list__wrapper'>
|
|
||||||
<div className='list__display-name'>
|
|
||||||
<Icon id='list-ul' icon={ListAltIcon} className='column-link__icon' />
|
|
||||||
{list.get('title')}
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div className='account__relationship'>
|
|
||||||
{button}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
export default connect(MapStateToProps, mapDispatchToProps)(injectIntl(List));
|
|
@ -1,76 +0,0 @@
|
|||||||
import PropTypes from 'prop-types';
|
|
||||||
|
|
||||||
import { injectIntl } from 'react-intl';
|
|
||||||
|
|
||||||
import { createSelector } from '@reduxjs/toolkit';
|
|
||||||
import ImmutablePropTypes from 'react-immutable-proptypes';
|
|
||||||
import ImmutablePureComponent from 'react-immutable-pure-component';
|
|
||||||
import { connect } from 'react-redux';
|
|
||||||
|
|
||||||
import { setupListAdder, resetListAdder } from '../../actions/lists';
|
|
||||||
import NewListForm from '../lists/components/new_list_form';
|
|
||||||
|
|
||||||
import Account from './components/account';
|
|
||||||
import List from './components/list';
|
|
||||||
// hack
|
|
||||||
|
|
||||||
const getOrderedLists = createSelector([state => state.get('lists')], lists => {
|
|
||||||
if (!lists) {
|
|
||||||
return lists;
|
|
||||||
}
|
|
||||||
|
|
||||||
return lists.toList().filter(item => !!item).sort((a, b) => a.get('title').localeCompare(b.get('title')));
|
|
||||||
});
|
|
||||||
|
|
||||||
const mapStateToProps = state => ({
|
|
||||||
listIds: getOrderedLists(state).map(list=>list.get('id')),
|
|
||||||
});
|
|
||||||
|
|
||||||
const mapDispatchToProps = dispatch => ({
|
|
||||||
onInitialize: accountId => dispatch(setupListAdder(accountId)),
|
|
||||||
onReset: () => dispatch(resetListAdder()),
|
|
||||||
});
|
|
||||||
|
|
||||||
class ListAdder extends ImmutablePureComponent {
|
|
||||||
|
|
||||||
static propTypes = {
|
|
||||||
accountId: PropTypes.string.isRequired,
|
|
||||||
onClose: PropTypes.func.isRequired,
|
|
||||||
intl: PropTypes.object.isRequired,
|
|
||||||
onInitialize: PropTypes.func.isRequired,
|
|
||||||
onReset: PropTypes.func.isRequired,
|
|
||||||
listIds: ImmutablePropTypes.list.isRequired,
|
|
||||||
};
|
|
||||||
|
|
||||||
componentDidMount () {
|
|
||||||
const { onInitialize, accountId } = this.props;
|
|
||||||
onInitialize(accountId);
|
|
||||||
}
|
|
||||||
|
|
||||||
componentWillUnmount () {
|
|
||||||
const { onReset } = this.props;
|
|
||||||
onReset();
|
|
||||||
}
|
|
||||||
|
|
||||||
render () {
|
|
||||||
const { accountId, listIds } = this.props;
|
|
||||||
|
|
||||||
return (
|
|
||||||
<div className='modal-root__modal list-adder'>
|
|
||||||
<div className='list-adder__account'>
|
|
||||||
<Account accountId={accountId} />
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<NewListForm />
|
|
||||||
|
|
||||||
|
|
||||||
<div className='list-adder__lists'>
|
|
||||||
{listIds.map(ListId => <List key={ListId} listId={ListId} />)}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
export default connect(mapStateToProps, mapDispatchToProps)(injectIntl(ListAdder));
|
|
213
app/javascript/mastodon/features/list_adder/index.tsx
Normal file
213
app/javascript/mastodon/features/list_adder/index.tsx
Normal file
@ -0,0 +1,213 @@
|
|||||||
|
import { useEffect, useState, useCallback } from 'react';
|
||||||
|
|
||||||
|
import { FormattedMessage, useIntl, defineMessages } from 'react-intl';
|
||||||
|
|
||||||
|
import { isFulfilled } from '@reduxjs/toolkit';
|
||||||
|
|
||||||
|
import CloseIcon from '@/material-icons/400-24px/close.svg?react';
|
||||||
|
import ListAltIcon from '@/material-icons/400-24px/list_alt.svg?react';
|
||||||
|
import { fetchLists } from 'mastodon/actions/lists';
|
||||||
|
import { createList } from 'mastodon/actions/lists_typed';
|
||||||
|
import {
|
||||||
|
apiGetAccountLists,
|
||||||
|
apiAddAccountToList,
|
||||||
|
apiRemoveAccountFromList,
|
||||||
|
} from 'mastodon/api/lists';
|
||||||
|
import type { ApiListJSON } from 'mastodon/api_types/lists';
|
||||||
|
import { Button } from 'mastodon/components/button';
|
||||||
|
import { CheckBox } from 'mastodon/components/check_box';
|
||||||
|
import { Icon } from 'mastodon/components/icon';
|
||||||
|
import { IconButton } from 'mastodon/components/icon_button';
|
||||||
|
import { getOrderedLists } from 'mastodon/selectors/lists';
|
||||||
|
import { useAppDispatch, useAppSelector } from 'mastodon/store';
|
||||||
|
|
||||||
|
const messages = defineMessages({
|
||||||
|
newList: {
|
||||||
|
id: 'lists.new_list_name',
|
||||||
|
defaultMessage: 'New list name',
|
||||||
|
},
|
||||||
|
createList: {
|
||||||
|
id: 'lists.create',
|
||||||
|
defaultMessage: 'Create',
|
||||||
|
},
|
||||||
|
close: {
|
||||||
|
id: 'lightbox.close',
|
||||||
|
defaultMessage: 'Close',
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
const ListItem: React.FC<{
|
||||||
|
id: string;
|
||||||
|
title: string;
|
||||||
|
checked: boolean;
|
||||||
|
onChange: (id: string, checked: boolean) => void;
|
||||||
|
}> = ({ id, title, checked, onChange }) => {
|
||||||
|
const handleChange = useCallback(
|
||||||
|
(e: React.ChangeEvent<HTMLInputElement>) => {
|
||||||
|
onChange(id, e.target.checked);
|
||||||
|
},
|
||||||
|
[id, onChange],
|
||||||
|
);
|
||||||
|
|
||||||
|
return (
|
||||||
|
// eslint-disable-next-line jsx-a11y/label-has-associated-control
|
||||||
|
<label className='lists__item'>
|
||||||
|
<div className='lists__item__title'>
|
||||||
|
<Icon id='list-ul' icon={ListAltIcon} />
|
||||||
|
<span>{title}</span>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<CheckBox value={id} checked={checked} onChange={handleChange} />
|
||||||
|
</label>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
const NewListItem: React.FC<{
|
||||||
|
onCreate: (list: ApiListJSON) => void;
|
||||||
|
}> = ({ onCreate }) => {
|
||||||
|
const intl = useIntl();
|
||||||
|
const dispatch = useAppDispatch();
|
||||||
|
const [title, setTitle] = useState('');
|
||||||
|
|
||||||
|
const handleChange = useCallback(
|
||||||
|
({ target: { value } }: React.ChangeEvent<HTMLInputElement>) => {
|
||||||
|
setTitle(value);
|
||||||
|
},
|
||||||
|
[setTitle],
|
||||||
|
);
|
||||||
|
|
||||||
|
const handleSubmit = useCallback(() => {
|
||||||
|
if (title.trim().length === 0) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
void dispatch(createList({ title })).then((result) => {
|
||||||
|
if (isFulfilled(result)) {
|
||||||
|
onCreate(result.payload);
|
||||||
|
setTitle('');
|
||||||
|
}
|
||||||
|
|
||||||
|
return '';
|
||||||
|
});
|
||||||
|
}, [setTitle, dispatch, onCreate, title]);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<form className='lists__item' onSubmit={handleSubmit}>
|
||||||
|
<label className='lists__item__title'>
|
||||||
|
<Icon id='list-ul' icon={ListAltIcon} />
|
||||||
|
|
||||||
|
<input
|
||||||
|
type='text'
|
||||||
|
value={title}
|
||||||
|
onChange={handleChange}
|
||||||
|
maxLength={30}
|
||||||
|
required
|
||||||
|
placeholder={intl.formatMessage(messages.newList)}
|
||||||
|
/>
|
||||||
|
</label>
|
||||||
|
|
||||||
|
<Button text={intl.formatMessage(messages.createList)} type='submit' />
|
||||||
|
</form>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
const ListAdder: React.FC<{
|
||||||
|
accountId: string;
|
||||||
|
onClose: () => void;
|
||||||
|
}> = ({ accountId, onClose }) => {
|
||||||
|
const intl = useIntl();
|
||||||
|
const dispatch = useAppDispatch();
|
||||||
|
const account = useAppSelector((state) => state.accounts.get(accountId));
|
||||||
|
const lists = useAppSelector((state) => getOrderedLists(state));
|
||||||
|
const [listIds, setListIds] = useState<string[]>([]);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
dispatch(fetchLists());
|
||||||
|
|
||||||
|
apiGetAccountLists(accountId)
|
||||||
|
.then((data) => {
|
||||||
|
setListIds(data.map((l) => l.id));
|
||||||
|
return '';
|
||||||
|
})
|
||||||
|
.catch(() => {
|
||||||
|
// Nothing
|
||||||
|
});
|
||||||
|
}, [dispatch, setListIds, accountId]);
|
||||||
|
|
||||||
|
const handleToggle = useCallback(
|
||||||
|
(listId: string, checked: boolean) => {
|
||||||
|
if (checked) {
|
||||||
|
setListIds((currentListIds) => [listId, ...currentListIds]);
|
||||||
|
|
||||||
|
apiAddAccountToList(listId, accountId).catch(() => {
|
||||||
|
setListIds((currentListIds) =>
|
||||||
|
currentListIds.filter((id) => id !== listId),
|
||||||
|
);
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
setListIds((currentListIds) =>
|
||||||
|
currentListIds.filter((id) => id !== listId),
|
||||||
|
);
|
||||||
|
|
||||||
|
apiRemoveAccountFromList(listId, accountId).catch(() => {
|
||||||
|
setListIds((currentListIds) => [listId, ...currentListIds]);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
},
|
||||||
|
[setListIds, accountId],
|
||||||
|
);
|
||||||
|
|
||||||
|
const handleCreate = useCallback(
|
||||||
|
(list: ApiListJSON) => {
|
||||||
|
setListIds((currentListIds) => [list.id, ...currentListIds]);
|
||||||
|
|
||||||
|
apiAddAccountToList(list.id, accountId).catch(() => {
|
||||||
|
setListIds((currentListIds) =>
|
||||||
|
currentListIds.filter((id) => id !== list.id),
|
||||||
|
);
|
||||||
|
});
|
||||||
|
},
|
||||||
|
[setListIds, accountId],
|
||||||
|
);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className='modal-root__modal dialog-modal'>
|
||||||
|
<div className='dialog-modal__header'>
|
||||||
|
<IconButton
|
||||||
|
className='dialog-modal__header__close'
|
||||||
|
title={intl.formatMessage(messages.close)}
|
||||||
|
icon='times'
|
||||||
|
iconComponent={CloseIcon}
|
||||||
|
onClick={onClose}
|
||||||
|
/>
|
||||||
|
|
||||||
|
<span className='dialog-modal__header__title'>
|
||||||
|
<FormattedMessage
|
||||||
|
id='lists.add_to_lists'
|
||||||
|
defaultMessage='Add {name} to lists'
|
||||||
|
values={{ name: <strong>@{account?.acct}</strong> }}
|
||||||
|
/>
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className='dialog-modal__content'>
|
||||||
|
<div className='lists-scrollable'>
|
||||||
|
<NewListItem onCreate={handleCreate} />
|
||||||
|
|
||||||
|
{lists.map((list) => (
|
||||||
|
<ListItem
|
||||||
|
key={list.id}
|
||||||
|
id={list.id}
|
||||||
|
title={list.title}
|
||||||
|
checked={listIds.includes(list.id)}
|
||||||
|
onChange={handleToggle}
|
||||||
|
/>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
// eslint-disable-next-line import/no-default-export
|
||||||
|
export default ListAdder;
|
@ -1,82 +0,0 @@
|
|||||||
import PropTypes from 'prop-types';
|
|
||||||
|
|
||||||
import { defineMessages, injectIntl } from 'react-intl';
|
|
||||||
|
|
||||||
import ImmutablePropTypes from 'react-immutable-proptypes';
|
|
||||||
import ImmutablePureComponent from 'react-immutable-pure-component';
|
|
||||||
import { connect } from 'react-redux';
|
|
||||||
|
|
||||||
import AddIcon from '@/material-icons/400-24px/add.svg?react';
|
|
||||||
import CloseIcon from '@/material-icons/400-24px/close.svg?react';
|
|
||||||
|
|
||||||
import { removeFromListEditor, addToListEditor } from '../../../actions/lists';
|
|
||||||
import { Avatar } from '../../../components/avatar';
|
|
||||||
import { DisplayName } from '../../../components/display_name';
|
|
||||||
import { IconButton } from '../../../components/icon_button';
|
|
||||||
import { makeGetAccount } from '../../../selectors';
|
|
||||||
|
|
||||||
const messages = defineMessages({
|
|
||||||
remove: { id: 'lists.account.remove', defaultMessage: 'Remove from list' },
|
|
||||||
add: { id: 'lists.account.add', defaultMessage: 'Add to list' },
|
|
||||||
});
|
|
||||||
|
|
||||||
const makeMapStateToProps = () => {
|
|
||||||
const getAccount = makeGetAccount();
|
|
||||||
|
|
||||||
const mapStateToProps = (state, { accountId, added }) => ({
|
|
||||||
account: getAccount(state, accountId),
|
|
||||||
added: typeof added === 'undefined' ? state.getIn(['listEditor', 'accounts', 'items']).includes(accountId) : added,
|
|
||||||
});
|
|
||||||
|
|
||||||
return mapStateToProps;
|
|
||||||
};
|
|
||||||
|
|
||||||
const mapDispatchToProps = (dispatch, { accountId }) => ({
|
|
||||||
onRemove: () => dispatch(removeFromListEditor(accountId)),
|
|
||||||
onAdd: () => dispatch(addToListEditor(accountId)),
|
|
||||||
});
|
|
||||||
|
|
||||||
class Account extends ImmutablePureComponent {
|
|
||||||
|
|
||||||
static propTypes = {
|
|
||||||
account: ImmutablePropTypes.record.isRequired,
|
|
||||||
intl: PropTypes.object.isRequired,
|
|
||||||
onRemove: PropTypes.func.isRequired,
|
|
||||||
onAdd: PropTypes.func.isRequired,
|
|
||||||
added: PropTypes.bool,
|
|
||||||
};
|
|
||||||
|
|
||||||
static defaultProps = {
|
|
||||||
added: false,
|
|
||||||
};
|
|
||||||
|
|
||||||
render () {
|
|
||||||
const { account, intl, onRemove, onAdd, added } = this.props;
|
|
||||||
|
|
||||||
let button;
|
|
||||||
|
|
||||||
if (added) {
|
|
||||||
button = <IconButton icon='times' iconComponent={CloseIcon} title={intl.formatMessage(messages.remove)} onClick={onRemove} />;
|
|
||||||
} else {
|
|
||||||
button = <IconButton icon='plus' iconComponent={AddIcon} title={intl.formatMessage(messages.add)} onClick={onAdd} />;
|
|
||||||
}
|
|
||||||
|
|
||||||
return (
|
|
||||||
<div className='account'>
|
|
||||||
<div className='account__wrapper'>
|
|
||||||
<div className='account__display-name'>
|
|
||||||
<div className='account__avatar-wrapper'><Avatar account={account} size={36} /></div>
|
|
||||||
<DisplayName account={account} />
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div className='account__relationship'>
|
|
||||||
{button}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
export default connect(makeMapStateToProps, mapDispatchToProps)(injectIntl(Account));
|
|
@ -1,76 +0,0 @@
|
|||||||
import PropTypes from 'prop-types';
|
|
||||||
import { PureComponent } from 'react';
|
|
||||||
|
|
||||||
import { defineMessages, injectIntl } from 'react-intl';
|
|
||||||
|
|
||||||
import { connect } from 'react-redux';
|
|
||||||
|
|
||||||
import CheckIcon from '@/material-icons/400-24px/check.svg?react';
|
|
||||||
|
|
||||||
import { changeListEditorTitle, submitListEditor } from '../../../actions/lists';
|
|
||||||
import { IconButton } from '../../../components/icon_button';
|
|
||||||
|
|
||||||
const messages = defineMessages({
|
|
||||||
title: { id: 'lists.edit.submit', defaultMessage: 'Change title' },
|
|
||||||
});
|
|
||||||
|
|
||||||
const mapStateToProps = state => ({
|
|
||||||
value: state.getIn(['listEditor', 'title']),
|
|
||||||
disabled: !state.getIn(['listEditor', 'isChanged']) || !state.getIn(['listEditor', 'title']),
|
|
||||||
});
|
|
||||||
|
|
||||||
const mapDispatchToProps = dispatch => ({
|
|
||||||
onChange: value => dispatch(changeListEditorTitle(value)),
|
|
||||||
onSubmit: () => dispatch(submitListEditor(false)),
|
|
||||||
});
|
|
||||||
|
|
||||||
class ListForm extends PureComponent {
|
|
||||||
|
|
||||||
static propTypes = {
|
|
||||||
value: PropTypes.string.isRequired,
|
|
||||||
disabled: PropTypes.bool,
|
|
||||||
intl: PropTypes.object.isRequired,
|
|
||||||
onChange: PropTypes.func.isRequired,
|
|
||||||
onSubmit: PropTypes.func.isRequired,
|
|
||||||
};
|
|
||||||
|
|
||||||
handleChange = e => {
|
|
||||||
this.props.onChange(e.target.value);
|
|
||||||
};
|
|
||||||
|
|
||||||
handleSubmit = e => {
|
|
||||||
e.preventDefault();
|
|
||||||
this.props.onSubmit();
|
|
||||||
};
|
|
||||||
|
|
||||||
handleClick = () => {
|
|
||||||
this.props.onSubmit();
|
|
||||||
};
|
|
||||||
|
|
||||||
render () {
|
|
||||||
const { value, disabled, intl } = this.props;
|
|
||||||
|
|
||||||
const title = intl.formatMessage(messages.title);
|
|
||||||
|
|
||||||
return (
|
|
||||||
<form className='column-inline-form' onSubmit={this.handleSubmit}>
|
|
||||||
<input
|
|
||||||
className='setting-text'
|
|
||||||
value={value}
|
|
||||||
onChange={this.handleChange}
|
|
||||||
/>
|
|
||||||
|
|
||||||
<IconButton
|
|
||||||
disabled={disabled}
|
|
||||||
icon='check'
|
|
||||||
iconComponent={CheckIcon}
|
|
||||||
title={title}
|
|
||||||
onClick={this.handleClick}
|
|
||||||
/>
|
|
||||||
</form>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
export default connect(mapStateToProps, mapDispatchToProps)(injectIntl(ListForm));
|
|
@ -1,83 +0,0 @@
|
|||||||
import PropTypes from 'prop-types';
|
|
||||||
import { PureComponent } from 'react';
|
|
||||||
|
|
||||||
import { defineMessages, injectIntl } from 'react-intl';
|
|
||||||
|
|
||||||
import classNames from 'classnames';
|
|
||||||
|
|
||||||
import { connect } from 'react-redux';
|
|
||||||
|
|
||||||
import CancelIcon from '@/material-icons/400-24px/cancel.svg?react';
|
|
||||||
import SearchIcon from '@/material-icons/400-24px/search.svg?react';
|
|
||||||
import { Icon } from 'mastodon/components/icon';
|
|
||||||
|
|
||||||
import { fetchListSuggestions, clearListSuggestions, changeListSuggestions } from '../../../actions/lists';
|
|
||||||
|
|
||||||
const messages = defineMessages({
|
|
||||||
search: { id: 'lists.search', defaultMessage: 'Search among people you follow' },
|
|
||||||
});
|
|
||||||
|
|
||||||
const mapStateToProps = state => ({
|
|
||||||
value: state.getIn(['listEditor', 'suggestions', 'value']),
|
|
||||||
});
|
|
||||||
|
|
||||||
const mapDispatchToProps = dispatch => ({
|
|
||||||
onSubmit: value => dispatch(fetchListSuggestions(value)),
|
|
||||||
onClear: () => dispatch(clearListSuggestions()),
|
|
||||||
onChange: value => dispatch(changeListSuggestions(value)),
|
|
||||||
});
|
|
||||||
|
|
||||||
class Search extends PureComponent {
|
|
||||||
|
|
||||||
static propTypes = {
|
|
||||||
intl: PropTypes.object.isRequired,
|
|
||||||
value: PropTypes.string.isRequired,
|
|
||||||
onChange: PropTypes.func.isRequired,
|
|
||||||
onSubmit: PropTypes.func.isRequired,
|
|
||||||
onClear: PropTypes.func.isRequired,
|
|
||||||
};
|
|
||||||
|
|
||||||
handleChange = e => {
|
|
||||||
this.props.onChange(e.target.value);
|
|
||||||
};
|
|
||||||
|
|
||||||
handleKeyUp = e => {
|
|
||||||
if (e.keyCode === 13) {
|
|
||||||
this.props.onSubmit(this.props.value);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
handleClear = () => {
|
|
||||||
this.props.onClear();
|
|
||||||
};
|
|
||||||
|
|
||||||
render () {
|
|
||||||
const { value, intl } = this.props;
|
|
||||||
const hasValue = value.length > 0;
|
|
||||||
|
|
||||||
return (
|
|
||||||
<div className='list-editor__search search'>
|
|
||||||
<label>
|
|
||||||
<span style={{ display: 'none' }}>{intl.formatMessage(messages.search)}</span>
|
|
||||||
|
|
||||||
<input
|
|
||||||
className='search__input'
|
|
||||||
type='text'
|
|
||||||
value={value}
|
|
||||||
onChange={this.handleChange}
|
|
||||||
onKeyUp={this.handleKeyUp}
|
|
||||||
placeholder={intl.formatMessage(messages.search)}
|
|
||||||
/>
|
|
||||||
</label>
|
|
||||||
|
|
||||||
<div role='button' tabIndex={0} className='search__icon' onClick={this.handleClear}>
|
|
||||||
<Icon id='search' icon={SearchIcon} className={classNames({ active: !hasValue })} />
|
|
||||||
<Icon id='times-circle' icon={CancelIcon} aria-label={intl.formatMessage(messages.search)} className={classNames({ active: hasValue })} />
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
export default connect(mapStateToProps, mapDispatchToProps)(injectIntl(Search));
|
|
@ -1,83 +0,0 @@
|
|||||||
import PropTypes from 'prop-types';
|
|
||||||
|
|
||||||
import { injectIntl } from 'react-intl';
|
|
||||||
|
|
||||||
import ImmutablePropTypes from 'react-immutable-proptypes';
|
|
||||||
import ImmutablePureComponent from 'react-immutable-pure-component';
|
|
||||||
import { connect } from 'react-redux';
|
|
||||||
|
|
||||||
import spring from 'react-motion/lib/spring';
|
|
||||||
|
|
||||||
import { setupListEditor, clearListSuggestions, resetListEditor } from '../../actions/lists';
|
|
||||||
import Motion from '../ui/util/optional_motion';
|
|
||||||
|
|
||||||
import Account from './components/account';
|
|
||||||
import EditListForm from './components/edit_list_form';
|
|
||||||
import Search from './components/search';
|
|
||||||
|
|
||||||
const mapStateToProps = state => ({
|
|
||||||
accountIds: state.getIn(['listEditor', 'accounts', 'items']),
|
|
||||||
searchAccountIds: state.getIn(['listEditor', 'suggestions', 'items']),
|
|
||||||
});
|
|
||||||
|
|
||||||
const mapDispatchToProps = dispatch => ({
|
|
||||||
onInitialize: listId => dispatch(setupListEditor(listId)),
|
|
||||||
onClear: () => dispatch(clearListSuggestions()),
|
|
||||||
onReset: () => dispatch(resetListEditor()),
|
|
||||||
});
|
|
||||||
|
|
||||||
class ListEditor extends ImmutablePureComponent {
|
|
||||||
|
|
||||||
static propTypes = {
|
|
||||||
listId: PropTypes.string.isRequired,
|
|
||||||
onClose: PropTypes.func.isRequired,
|
|
||||||
intl: PropTypes.object.isRequired,
|
|
||||||
onInitialize: PropTypes.func.isRequired,
|
|
||||||
onClear: PropTypes.func.isRequired,
|
|
||||||
onReset: PropTypes.func.isRequired,
|
|
||||||
accountIds: ImmutablePropTypes.list.isRequired,
|
|
||||||
searchAccountIds: ImmutablePropTypes.list.isRequired,
|
|
||||||
};
|
|
||||||
|
|
||||||
componentDidMount () {
|
|
||||||
const { onInitialize, listId } = this.props;
|
|
||||||
onInitialize(listId);
|
|
||||||
}
|
|
||||||
|
|
||||||
componentWillUnmount () {
|
|
||||||
const { onReset } = this.props;
|
|
||||||
onReset();
|
|
||||||
}
|
|
||||||
|
|
||||||
render () {
|
|
||||||
const { accountIds, searchAccountIds, onClear } = this.props;
|
|
||||||
const showSearch = searchAccountIds.size > 0;
|
|
||||||
|
|
||||||
return (
|
|
||||||
<div className='modal-root__modal list-editor'>
|
|
||||||
<EditListForm />
|
|
||||||
|
|
||||||
<Search />
|
|
||||||
|
|
||||||
<div className='drawer__pager'>
|
|
||||||
<div className='drawer__inner list-editor__accounts'>
|
|
||||||
{accountIds.map(accountId => <Account key={accountId} accountId={accountId} added />)}
|
|
||||||
</div>
|
|
||||||
|
|
||||||
{showSearch && <div role='button' tabIndex={-1} className='drawer__backdrop' onClick={onClear} />}
|
|
||||||
|
|
||||||
<Motion defaultStyle={{ x: -100 }} style={{ x: spring(showSearch ? 0 : -100, { stiffness: 210, damping: 20 }) }}>
|
|
||||||
{({ x }) => (
|
|
||||||
<div className='drawer__inner backdrop' style={{ transform: x === 0 ? null : `translateX(${x}%)`, visibility: x === -100 ? 'hidden' : 'visible' }}>
|
|
||||||
{searchAccountIds.map(accountId => <Account key={accountId} accountId={accountId} />)}
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
</Motion>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
export default connect(mapStateToProps, mapDispatchToProps)(injectIntl(ListEditor));
|
|
@ -1,21 +1,19 @@
|
|||||||
import PropTypes from 'prop-types';
|
import PropTypes from 'prop-types';
|
||||||
import { PureComponent } from 'react';
|
import { PureComponent } from 'react';
|
||||||
|
|
||||||
import { FormattedMessage, defineMessages, injectIntl } from 'react-intl';
|
import { FormattedMessage } from 'react-intl';
|
||||||
|
|
||||||
import { Helmet } from 'react-helmet';
|
import { Helmet } from 'react-helmet';
|
||||||
import { withRouter } from 'react-router-dom';
|
import { Link, withRouter } from 'react-router-dom';
|
||||||
|
|
||||||
import ImmutablePropTypes from 'react-immutable-proptypes';
|
import ImmutablePropTypes from 'react-immutable-proptypes';
|
||||||
import { connect } from 'react-redux';
|
import { connect } from 'react-redux';
|
||||||
|
|
||||||
import Toggle from 'react-toggle';
|
|
||||||
|
|
||||||
import DeleteIcon from '@/material-icons/400-24px/delete.svg?react';
|
import DeleteIcon from '@/material-icons/400-24px/delete.svg?react';
|
||||||
import EditIcon from '@/material-icons/400-24px/edit.svg?react';
|
import EditIcon from '@/material-icons/400-24px/edit.svg?react';
|
||||||
import ListAltIcon from '@/material-icons/400-24px/list_alt.svg?react';
|
import ListAltIcon from '@/material-icons/400-24px/list_alt.svg?react';
|
||||||
import { addColumn, removeColumn, moveColumn } from 'mastodon/actions/columns';
|
import { addColumn, removeColumn, moveColumn } from 'mastodon/actions/columns';
|
||||||
import { fetchList, updateList } from 'mastodon/actions/lists';
|
import { fetchList } from 'mastodon/actions/lists';
|
||||||
import { openModal } from 'mastodon/actions/modal';
|
import { openModal } from 'mastodon/actions/modal';
|
||||||
import { connectListStream } from 'mastodon/actions/streaming';
|
import { connectListStream } from 'mastodon/actions/streaming';
|
||||||
import { expandListTimeline } from 'mastodon/actions/timelines';
|
import { expandListTimeline } from 'mastodon/actions/timelines';
|
||||||
@ -23,17 +21,10 @@ import Column from 'mastodon/components/column';
|
|||||||
import ColumnHeader from 'mastodon/components/column_header';
|
import ColumnHeader from 'mastodon/components/column_header';
|
||||||
import { Icon } from 'mastodon/components/icon';
|
import { Icon } from 'mastodon/components/icon';
|
||||||
import { LoadingIndicator } from 'mastodon/components/loading_indicator';
|
import { LoadingIndicator } from 'mastodon/components/loading_indicator';
|
||||||
import { RadioButton } from 'mastodon/components/radio_button';
|
|
||||||
import BundleColumnError from 'mastodon/features/ui/components/bundle_column_error';
|
import BundleColumnError from 'mastodon/features/ui/components/bundle_column_error';
|
||||||
import StatusListContainer from 'mastodon/features/ui/containers/status_list_container';
|
import StatusListContainer from 'mastodon/features/ui/containers/status_list_container';
|
||||||
import { WithRouterPropTypes } from 'mastodon/utils/react_router';
|
import { WithRouterPropTypes } from 'mastodon/utils/react_router';
|
||||||
|
|
||||||
const messages = defineMessages({
|
|
||||||
followed: { id: 'lists.replies_policy.followed', defaultMessage: 'Any followed user' },
|
|
||||||
none: { id: 'lists.replies_policy.none', defaultMessage: 'No one' },
|
|
||||||
list: { id: 'lists.replies_policy.list', defaultMessage: 'Members of the list' },
|
|
||||||
});
|
|
||||||
|
|
||||||
const mapStateToProps = (state, props) => ({
|
const mapStateToProps = (state, props) => ({
|
||||||
list: state.getIn(['lists', props.params.id]),
|
list: state.getIn(['lists', props.params.id]),
|
||||||
hasUnread: state.getIn(['timelines', `list:${props.params.id}`, 'unread']) > 0,
|
hasUnread: state.getIn(['timelines', `list:${props.params.id}`, 'unread']) > 0,
|
||||||
@ -115,13 +106,6 @@ class ListTimeline extends PureComponent {
|
|||||||
this.props.dispatch(expandListTimeline(id, { maxId }));
|
this.props.dispatch(expandListTimeline(id, { maxId }));
|
||||||
};
|
};
|
||||||
|
|
||||||
handleEditClick = () => {
|
|
||||||
this.props.dispatch(openModal({
|
|
||||||
modalType: 'LIST_EDITOR',
|
|
||||||
modalProps: { listId: this.props.params.id },
|
|
||||||
}));
|
|
||||||
};
|
|
||||||
|
|
||||||
handleDeleteClick = () => {
|
handleDeleteClick = () => {
|
||||||
const { dispatch, columnId } = this.props;
|
const { dispatch, columnId } = this.props;
|
||||||
const { id } = this.props.params;
|
const { id } = this.props.params;
|
||||||
@ -129,25 +113,11 @@ class ListTimeline extends PureComponent {
|
|||||||
dispatch(openModal({ modalType: 'CONFIRM_DELETE_LIST', modalProps: { listId: id, columnId } }));
|
dispatch(openModal({ modalType: 'CONFIRM_DELETE_LIST', modalProps: { listId: id, columnId } }));
|
||||||
};
|
};
|
||||||
|
|
||||||
handleRepliesPolicyChange = ({ target }) => {
|
|
||||||
const { dispatch } = this.props;
|
|
||||||
const { id } = this.props.params;
|
|
||||||
dispatch(updateList(id, undefined, false, undefined, target.value));
|
|
||||||
};
|
|
||||||
|
|
||||||
onExclusiveToggle = ({ target }) => {
|
|
||||||
const { dispatch } = this.props;
|
|
||||||
const { id } = this.props.params;
|
|
||||||
dispatch(updateList(id, undefined, false, target.checked, undefined));
|
|
||||||
};
|
|
||||||
|
|
||||||
render () {
|
render () {
|
||||||
const { hasUnread, columnId, multiColumn, list, intl } = this.props;
|
const { hasUnread, columnId, multiColumn, list } = this.props;
|
||||||
const { id } = this.props.params;
|
const { id } = this.props.params;
|
||||||
const pinned = !!columnId;
|
const pinned = !!columnId;
|
||||||
const title = list ? list.get('title') : id;
|
const title = list ? list.get('title') : id;
|
||||||
const replies_policy = list ? list.get('replies_policy') : undefined;
|
|
||||||
const isExclusive = list ? list.get('exclusive') : undefined;
|
|
||||||
|
|
||||||
if (typeof list === 'undefined') {
|
if (typeof list === 'undefined') {
|
||||||
return (
|
return (
|
||||||
@ -178,35 +148,14 @@ class ListTimeline extends PureComponent {
|
|||||||
>
|
>
|
||||||
<div className='column-settings'>
|
<div className='column-settings'>
|
||||||
<section className='column-header__links'>
|
<section className='column-header__links'>
|
||||||
<button type='button' className='text-btn column-header__setting-btn' tabIndex={0} onClick={this.handleEditClick}>
|
<Link to={`/lists/${id}/edit`} className='text-btn column-header__setting-btn'>
|
||||||
<Icon id='pencil' icon={EditIcon} /> <FormattedMessage id='lists.edit' defaultMessage='Edit list' />
|
<Icon id='pencil' icon={EditIcon} /> <FormattedMessage id='lists.edit' defaultMessage='Edit list' />
|
||||||
</button>
|
</Link>
|
||||||
|
|
||||||
<button type='button' className='text-btn column-header__setting-btn' tabIndex={0} onClick={this.handleDeleteClick}>
|
<button type='button' className='text-btn column-header__setting-btn' tabIndex={0} onClick={this.handleDeleteClick}>
|
||||||
<Icon id='trash' icon={DeleteIcon} /> <FormattedMessage id='lists.delete' defaultMessage='Delete list' />
|
<Icon id='trash' icon={DeleteIcon} /> <FormattedMessage id='lists.delete' defaultMessage='Delete list' />
|
||||||
</button>
|
</button>
|
||||||
</section>
|
</section>
|
||||||
|
|
||||||
<section>
|
|
||||||
<div className='setting-toggle'>
|
|
||||||
<Toggle id={`list-${id}-exclusive`} checked={isExclusive} onChange={this.onExclusiveToggle} />
|
|
||||||
<label htmlFor={`list-${id}-exclusive`} className='setting-toggle__label'>
|
|
||||||
<FormattedMessage id='lists.exclusive' defaultMessage='Hide these posts from home' />
|
|
||||||
</label>
|
|
||||||
</div>
|
|
||||||
</section>
|
|
||||||
|
|
||||||
{replies_policy !== undefined && (
|
|
||||||
<section aria-labelledby={`list-${id}-replies-policy`}>
|
|
||||||
<h3 id={`list-${id}-replies-policy`}><FormattedMessage id='lists.replies_policy.title' defaultMessage='Show replies to:' /></h3>
|
|
||||||
|
|
||||||
<div className='column-settings__row'>
|
|
||||||
{ ['none', 'list', 'followed'].map(policy => (
|
|
||||||
<RadioButton name='order' key={policy} value={policy} label={intl.formatMessage(messages[policy])} checked={replies_policy === policy} onChange={this.handleRepliesPolicyChange} />
|
|
||||||
))}
|
|
||||||
</div>
|
|
||||||
</section>
|
|
||||||
)}
|
|
||||||
</div>
|
</div>
|
||||||
</ColumnHeader>
|
</ColumnHeader>
|
||||||
|
|
||||||
@ -229,4 +178,4 @@ class ListTimeline extends PureComponent {
|
|||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export default withRouter(connect(mapStateToProps)(injectIntl(ListTimeline)));
|
export default withRouter(connect(mapStateToProps)(ListTimeline));
|
||||||
|
@ -1,80 +0,0 @@
|
|||||||
import PropTypes from 'prop-types';
|
|
||||||
import { PureComponent } from 'react';
|
|
||||||
|
|
||||||
import { defineMessages, injectIntl } from 'react-intl';
|
|
||||||
|
|
||||||
import { connect } from 'react-redux';
|
|
||||||
|
|
||||||
import { changeListEditorTitle, submitListEditor } from 'mastodon/actions/lists';
|
|
||||||
import { Button } from 'mastodon/components/button';
|
|
||||||
|
|
||||||
const messages = defineMessages({
|
|
||||||
label: { id: 'lists.new.title_placeholder', defaultMessage: 'New list title' },
|
|
||||||
title: { id: 'lists.new.create', defaultMessage: 'Add list' },
|
|
||||||
});
|
|
||||||
|
|
||||||
const mapStateToProps = state => ({
|
|
||||||
value: state.getIn(['listEditor', 'title']),
|
|
||||||
disabled: state.getIn(['listEditor', 'isSubmitting']),
|
|
||||||
});
|
|
||||||
|
|
||||||
const mapDispatchToProps = dispatch => ({
|
|
||||||
onChange: value => dispatch(changeListEditorTitle(value)),
|
|
||||||
onSubmit: () => dispatch(submitListEditor(true)),
|
|
||||||
});
|
|
||||||
|
|
||||||
class NewListForm extends PureComponent {
|
|
||||||
|
|
||||||
static propTypes = {
|
|
||||||
value: PropTypes.string.isRequired,
|
|
||||||
disabled: PropTypes.bool,
|
|
||||||
intl: PropTypes.object.isRequired,
|
|
||||||
onChange: PropTypes.func.isRequired,
|
|
||||||
onSubmit: PropTypes.func.isRequired,
|
|
||||||
};
|
|
||||||
|
|
||||||
handleChange = e => {
|
|
||||||
this.props.onChange(e.target.value);
|
|
||||||
};
|
|
||||||
|
|
||||||
handleSubmit = e => {
|
|
||||||
e.preventDefault();
|
|
||||||
this.props.onSubmit();
|
|
||||||
};
|
|
||||||
|
|
||||||
handleClick = () => {
|
|
||||||
this.props.onSubmit();
|
|
||||||
};
|
|
||||||
|
|
||||||
render () {
|
|
||||||
const { value, disabled, intl } = this.props;
|
|
||||||
|
|
||||||
const label = intl.formatMessage(messages.label);
|
|
||||||
const title = intl.formatMessage(messages.title);
|
|
||||||
|
|
||||||
return (
|
|
||||||
<form className='column-inline-form' onSubmit={this.handleSubmit}>
|
|
||||||
<label>
|
|
||||||
<span style={{ display: 'none' }}>{label}</span>
|
|
||||||
|
|
||||||
<input
|
|
||||||
className='setting-text'
|
|
||||||
value={value}
|
|
||||||
disabled={disabled}
|
|
||||||
onChange={this.handleChange}
|
|
||||||
placeholder={label}
|
|
||||||
/>
|
|
||||||
</label>
|
|
||||||
|
|
||||||
<Button
|
|
||||||
disabled={disabled || !value}
|
|
||||||
text={title}
|
|
||||||
onClick={this.handleClick}
|
|
||||||
/>
|
|
||||||
</form>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
export default connect(mapStateToProps, mapDispatchToProps)(injectIntl(NewListForm));
|
|
@ -1,94 +0,0 @@
|
|||||||
import PropTypes from 'prop-types';
|
|
||||||
|
|
||||||
import { defineMessages, injectIntl, FormattedMessage } from 'react-intl';
|
|
||||||
|
|
||||||
import { Helmet } from 'react-helmet';
|
|
||||||
|
|
||||||
import { createSelector } from '@reduxjs/toolkit';
|
|
||||||
import ImmutablePropTypes from 'react-immutable-proptypes';
|
|
||||||
import ImmutablePureComponent from 'react-immutable-pure-component';
|
|
||||||
import { connect } from 'react-redux';
|
|
||||||
|
|
||||||
import ListAltIcon from '@/material-icons/400-24px/list_alt.svg?react';
|
|
||||||
import { fetchLists } from 'mastodon/actions/lists';
|
|
||||||
import Column from 'mastodon/components/column';
|
|
||||||
import ColumnHeader from 'mastodon/components/column_header';
|
|
||||||
import { LoadingIndicator } from 'mastodon/components/loading_indicator';
|
|
||||||
import ScrollableList from 'mastodon/components/scrollable_list';
|
|
||||||
import ColumnLink from 'mastodon/features/ui/components/column_link';
|
|
||||||
import ColumnSubheading from 'mastodon/features/ui/components/column_subheading';
|
|
||||||
|
|
||||||
import NewListForm from './components/new_list_form';
|
|
||||||
|
|
||||||
const messages = defineMessages({
|
|
||||||
heading: { id: 'column.lists', defaultMessage: 'Lists' },
|
|
||||||
subheading: { id: 'lists.subheading', defaultMessage: 'Your lists' },
|
|
||||||
});
|
|
||||||
|
|
||||||
const getOrderedLists = createSelector([state => state.get('lists')], lists => {
|
|
||||||
if (!lists) {
|
|
||||||
return lists;
|
|
||||||
}
|
|
||||||
|
|
||||||
return lists.toList().filter(item => !!item).sort((a, b) => a.get('title').localeCompare(b.get('title')));
|
|
||||||
});
|
|
||||||
|
|
||||||
const mapStateToProps = state => ({
|
|
||||||
lists: getOrderedLists(state),
|
|
||||||
});
|
|
||||||
|
|
||||||
class Lists extends ImmutablePureComponent {
|
|
||||||
|
|
||||||
static propTypes = {
|
|
||||||
params: PropTypes.object.isRequired,
|
|
||||||
dispatch: PropTypes.func.isRequired,
|
|
||||||
lists: ImmutablePropTypes.list,
|
|
||||||
intl: PropTypes.object.isRequired,
|
|
||||||
multiColumn: PropTypes.bool,
|
|
||||||
};
|
|
||||||
|
|
||||||
UNSAFE_componentWillMount () {
|
|
||||||
this.props.dispatch(fetchLists());
|
|
||||||
}
|
|
||||||
|
|
||||||
render () {
|
|
||||||
const { intl, lists, multiColumn } = this.props;
|
|
||||||
|
|
||||||
if (!lists) {
|
|
||||||
return (
|
|
||||||
<Column>
|
|
||||||
<LoadingIndicator />
|
|
||||||
</Column>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
const emptyMessage = <FormattedMessage id='empty_column.lists' defaultMessage="You don't have any lists yet. When you create one, it will show up here." />;
|
|
||||||
|
|
||||||
return (
|
|
||||||
<Column bindToDocument={!multiColumn} label={intl.formatMessage(messages.heading)}>
|
|
||||||
<ColumnHeader title={intl.formatMessage(messages.heading)} icon='list-ul' iconComponent={ListAltIcon} multiColumn={multiColumn} />
|
|
||||||
|
|
||||||
<NewListForm />
|
|
||||||
|
|
||||||
<ScrollableList
|
|
||||||
scrollKey='lists'
|
|
||||||
emptyMessage={emptyMessage}
|
|
||||||
prepend={<ColumnSubheading text={intl.formatMessage(messages.subheading)} />}
|
|
||||||
bindToDocument={!multiColumn}
|
|
||||||
>
|
|
||||||
{lists.map(list =>
|
|
||||||
<ColumnLink key={list.get('id')} to={`/lists/${list.get('id')}`} icon='list-ul' iconComponent={ListAltIcon} text={list.get('title')} />,
|
|
||||||
)}
|
|
||||||
</ScrollableList>
|
|
||||||
|
|
||||||
<Helmet>
|
|
||||||
<title>{intl.formatMessage(messages.heading)}</title>
|
|
||||||
<meta name='robots' content='noindex' />
|
|
||||||
</Helmet>
|
|
||||||
</Column>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
export default connect(mapStateToProps)(injectIntl(Lists));
|
|
145
app/javascript/mastodon/features/lists/index.tsx
Normal file
145
app/javascript/mastodon/features/lists/index.tsx
Normal file
@ -0,0 +1,145 @@
|
|||||||
|
import { useEffect, useMemo, useCallback } from 'react';
|
||||||
|
|
||||||
|
import { defineMessages, useIntl, FormattedMessage } from 'react-intl';
|
||||||
|
|
||||||
|
import { Helmet } from 'react-helmet';
|
||||||
|
import { Link } from 'react-router-dom';
|
||||||
|
|
||||||
|
import AddIcon from '@/material-icons/400-24px/add.svg?react';
|
||||||
|
import ListAltIcon from '@/material-icons/400-24px/list_alt.svg?react';
|
||||||
|
import MoreHorizIcon from '@/material-icons/400-24px/more_horiz.svg?react';
|
||||||
|
import SquigglyArrow from '@/svg-icons/squiggly_arrow.svg?react';
|
||||||
|
import { fetchLists } from 'mastodon/actions/lists';
|
||||||
|
import { openModal } from 'mastodon/actions/modal';
|
||||||
|
import Column from 'mastodon/components/column';
|
||||||
|
import { ColumnHeader } from 'mastodon/components/column_header';
|
||||||
|
import { Icon } from 'mastodon/components/icon';
|
||||||
|
import ScrollableList from 'mastodon/components/scrollable_list';
|
||||||
|
import DropdownMenuContainer from 'mastodon/containers/dropdown_menu_container';
|
||||||
|
import { getOrderedLists } from 'mastodon/selectors/lists';
|
||||||
|
import { useAppSelector, useAppDispatch } from 'mastodon/store';
|
||||||
|
|
||||||
|
const messages = defineMessages({
|
||||||
|
heading: { id: 'column.lists', defaultMessage: 'Lists' },
|
||||||
|
create: { id: 'lists.create_list', defaultMessage: 'Create list' },
|
||||||
|
edit: { id: 'lists.edit', defaultMessage: 'Edit list' },
|
||||||
|
delete: { id: 'lists.delete', defaultMessage: 'Delete list' },
|
||||||
|
more: { id: 'status.more', defaultMessage: 'More' },
|
||||||
|
});
|
||||||
|
|
||||||
|
const ListItem: React.FC<{
|
||||||
|
id: string;
|
||||||
|
title: string;
|
||||||
|
}> = ({ id, title }) => {
|
||||||
|
const dispatch = useAppDispatch();
|
||||||
|
const intl = useIntl();
|
||||||
|
|
||||||
|
const handleDeleteClick = useCallback(() => {
|
||||||
|
dispatch(
|
||||||
|
openModal({
|
||||||
|
modalType: 'CONFIRM_DELETE_LIST',
|
||||||
|
modalProps: {
|
||||||
|
listId: id,
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
}, [dispatch, id]);
|
||||||
|
|
||||||
|
const menu = useMemo(
|
||||||
|
() => [
|
||||||
|
{ text: intl.formatMessage(messages.edit), to: `/lists/${id}/edit` },
|
||||||
|
{ text: intl.formatMessage(messages.delete), action: handleDeleteClick },
|
||||||
|
],
|
||||||
|
[intl, id, handleDeleteClick],
|
||||||
|
);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className='lists__item'>
|
||||||
|
<Link to={`/lists/${id}`} className='lists__item__title'>
|
||||||
|
<Icon id='list-ul' icon={ListAltIcon} />
|
||||||
|
<span>{title}</span>
|
||||||
|
</Link>
|
||||||
|
|
||||||
|
<DropdownMenuContainer
|
||||||
|
scrollKey='lists'
|
||||||
|
items={menu}
|
||||||
|
icons='ellipsis-h'
|
||||||
|
iconComponent={MoreHorizIcon}
|
||||||
|
direction='right'
|
||||||
|
title={intl.formatMessage(messages.more)}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
const Lists: React.FC<{
|
||||||
|
multiColumn?: boolean;
|
||||||
|
}> = ({ multiColumn }) => {
|
||||||
|
const dispatch = useAppDispatch();
|
||||||
|
const intl = useIntl();
|
||||||
|
const lists = useAppSelector((state) => getOrderedLists(state));
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
dispatch(fetchLists());
|
||||||
|
}, [dispatch]);
|
||||||
|
|
||||||
|
const emptyMessage = (
|
||||||
|
<>
|
||||||
|
<span>
|
||||||
|
<FormattedMessage
|
||||||
|
id='lists.no_lists_yet'
|
||||||
|
defaultMessage='No lists yet.'
|
||||||
|
/>
|
||||||
|
<br />
|
||||||
|
<FormattedMessage
|
||||||
|
id='lists.create_a_list_to_organize'
|
||||||
|
defaultMessage='Create a new list to organize your Home feed'
|
||||||
|
/>
|
||||||
|
</span>
|
||||||
|
|
||||||
|
<SquigglyArrow className='empty-column-indicator__arrow' />
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Column
|
||||||
|
bindToDocument={!multiColumn}
|
||||||
|
label={intl.formatMessage(messages.heading)}
|
||||||
|
>
|
||||||
|
<ColumnHeader
|
||||||
|
title={intl.formatMessage(messages.heading)}
|
||||||
|
icon='list-ul'
|
||||||
|
iconComponent={ListAltIcon}
|
||||||
|
multiColumn={multiColumn}
|
||||||
|
extraButton={
|
||||||
|
<Link
|
||||||
|
to='/lists/new'
|
||||||
|
className='column-header__button'
|
||||||
|
title={intl.formatMessage(messages.create)}
|
||||||
|
aria-label={intl.formatMessage(messages.create)}
|
||||||
|
>
|
||||||
|
<Icon id='plus' icon={AddIcon} />
|
||||||
|
</Link>
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
|
||||||
|
<ScrollableList
|
||||||
|
scrollKey='lists'
|
||||||
|
emptyMessage={emptyMessage}
|
||||||
|
bindToDocument={!multiColumn}
|
||||||
|
>
|
||||||
|
{lists.map((list) => (
|
||||||
|
<ListItem key={list.id} id={list.id} title={list.title} />
|
||||||
|
))}
|
||||||
|
</ScrollableList>
|
||||||
|
|
||||||
|
<Helmet>
|
||||||
|
<title>{intl.formatMessage(messages.heading)}</title>
|
||||||
|
<meta name='robots' content='noindex' />
|
||||||
|
</Helmet>
|
||||||
|
</Column>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
// eslint-disable-next-line import/no-default-export
|
||||||
|
export default Lists;
|
304
app/javascript/mastodon/features/lists/members.tsx
Normal file
304
app/javascript/mastodon/features/lists/members.tsx
Normal file
@ -0,0 +1,304 @@
|
|||||||
|
import { useCallback, useState, useEffect, useRef } from 'react';
|
||||||
|
|
||||||
|
import { defineMessages, useIntl, FormattedMessage } from 'react-intl';
|
||||||
|
|
||||||
|
import { Helmet } from 'react-helmet';
|
||||||
|
import { useParams, Link } from 'react-router-dom';
|
||||||
|
|
||||||
|
import { useDebouncedCallback } from 'use-debounce';
|
||||||
|
|
||||||
|
import ListAltIcon from '@/material-icons/400-24px/list_alt.svg?react';
|
||||||
|
import SquigglyArrow from '@/svg-icons/squiggly_arrow.svg?react';
|
||||||
|
import { importFetchedAccounts } from 'mastodon/actions/importer';
|
||||||
|
import { fetchList } from 'mastodon/actions/lists';
|
||||||
|
import { apiRequest } from 'mastodon/api';
|
||||||
|
import {
|
||||||
|
apiGetAccounts,
|
||||||
|
apiAddAccountToList,
|
||||||
|
apiRemoveAccountFromList,
|
||||||
|
} from 'mastodon/api/lists';
|
||||||
|
import type { ApiAccountJSON } from 'mastodon/api_types/accounts';
|
||||||
|
import { Avatar } from 'mastodon/components/avatar';
|
||||||
|
import { Button } from 'mastodon/components/button';
|
||||||
|
import Column from 'mastodon/components/column';
|
||||||
|
import { ColumnHeader } from 'mastodon/components/column_header';
|
||||||
|
import { ColumnSearchHeader } from 'mastodon/components/column_search_header';
|
||||||
|
import { FollowersCounter } from 'mastodon/components/counters';
|
||||||
|
import { DisplayName } from 'mastodon/components/display_name';
|
||||||
|
import ScrollableList from 'mastodon/components/scrollable_list';
|
||||||
|
import { ShortNumber } from 'mastodon/components/short_number';
|
||||||
|
import { VerifiedBadge } from 'mastodon/components/verified_badge';
|
||||||
|
import { useAppDispatch, useAppSelector } from 'mastodon/store';
|
||||||
|
|
||||||
|
const messages = defineMessages({
|
||||||
|
heading: { id: 'column.list_members', defaultMessage: 'Manage list members' },
|
||||||
|
placeholder: {
|
||||||
|
id: 'lists.search_placeholder',
|
||||||
|
defaultMessage: 'Search people you follow',
|
||||||
|
},
|
||||||
|
enterSearch: { id: 'lists.add_to_list', defaultMessage: 'Add to list' },
|
||||||
|
add: { id: 'lists.add_member', defaultMessage: 'Add' },
|
||||||
|
remove: { id: 'lists.remove_member', defaultMessage: 'Remove' },
|
||||||
|
back: { id: 'column_back_button.label', defaultMessage: 'Back' },
|
||||||
|
});
|
||||||
|
|
||||||
|
type Mode = 'remove' | 'add';
|
||||||
|
|
||||||
|
const AccountItem: React.FC<{
|
||||||
|
accountId: string;
|
||||||
|
listId: string;
|
||||||
|
partOfList: boolean;
|
||||||
|
onToggle: (accountId: string) => void;
|
||||||
|
}> = ({ accountId, listId, partOfList, onToggle }) => {
|
||||||
|
const intl = useIntl();
|
||||||
|
const account = useAppSelector((state) => state.accounts.get(accountId));
|
||||||
|
|
||||||
|
const handleClick = useCallback(() => {
|
||||||
|
if (partOfList) {
|
||||||
|
void apiRemoveAccountFromList(listId, accountId);
|
||||||
|
} else {
|
||||||
|
void apiAddAccountToList(listId, accountId);
|
||||||
|
}
|
||||||
|
|
||||||
|
onToggle(accountId);
|
||||||
|
}, [accountId, listId, partOfList, onToggle]);
|
||||||
|
|
||||||
|
if (!account) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
const firstVerifiedField = account.fields.find((item) => !!item.verified_at);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className='account'>
|
||||||
|
<div className='account__wrapper'>
|
||||||
|
<Link
|
||||||
|
key={account.id}
|
||||||
|
className='account__display-name'
|
||||||
|
title={account.acct}
|
||||||
|
to={`/@${account.acct}`}
|
||||||
|
data-hover-card-account={account.id}
|
||||||
|
>
|
||||||
|
<div className='account__avatar-wrapper'>
|
||||||
|
<Avatar account={account} size={36} />
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className='account__contents'>
|
||||||
|
<DisplayName account={account} />
|
||||||
|
|
||||||
|
<div className='account__details'>
|
||||||
|
<ShortNumber
|
||||||
|
value={account.followers_count}
|
||||||
|
renderer={FollowersCounter}
|
||||||
|
/>{' '}
|
||||||
|
{firstVerifiedField && (
|
||||||
|
<VerifiedBadge link={firstVerifiedField.value} />
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</Link>
|
||||||
|
|
||||||
|
<div className='account__relationship'>
|
||||||
|
<Button
|
||||||
|
text={intl.formatMessage(
|
||||||
|
partOfList ? messages.remove : messages.add,
|
||||||
|
)}
|
||||||
|
secondary={partOfList}
|
||||||
|
onClick={handleClick}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
const ListMembers: React.FC<{
|
||||||
|
multiColumn?: boolean;
|
||||||
|
}> = ({ multiColumn }) => {
|
||||||
|
const dispatch = useAppDispatch();
|
||||||
|
const { id } = useParams<{ id: string }>();
|
||||||
|
const intl = useIntl();
|
||||||
|
|
||||||
|
const [searching, setSearching] = useState(false);
|
||||||
|
const [accountIds, setAccountIds] = useState<string[]>([]);
|
||||||
|
const [searchAccountIds, setSearchAccountIds] = useState<string[]>([]);
|
||||||
|
const [loading, setLoading] = useState(true);
|
||||||
|
const [mode, setMode] = useState<Mode>('remove');
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (id) {
|
||||||
|
setLoading(true);
|
||||||
|
dispatch(fetchList(id));
|
||||||
|
|
||||||
|
void apiGetAccounts(id)
|
||||||
|
.then((data) => {
|
||||||
|
dispatch(importFetchedAccounts(data));
|
||||||
|
setAccountIds(data.map((a) => a.id));
|
||||||
|
setLoading(false);
|
||||||
|
return '';
|
||||||
|
})
|
||||||
|
.catch(() => {
|
||||||
|
setLoading(false);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}, [dispatch, id]);
|
||||||
|
|
||||||
|
const handleSearchClick = useCallback(() => {
|
||||||
|
setMode('add');
|
||||||
|
}, [setMode]);
|
||||||
|
|
||||||
|
const handleDismissSearchClick = useCallback(() => {
|
||||||
|
setMode('remove');
|
||||||
|
setSearching(false);
|
||||||
|
}, [setMode]);
|
||||||
|
|
||||||
|
const handleAccountToggle = useCallback(
|
||||||
|
(accountId: string) => {
|
||||||
|
const partOfList = accountIds.includes(accountId);
|
||||||
|
|
||||||
|
if (partOfList) {
|
||||||
|
setAccountIds(accountIds.filter((id) => id !== accountId));
|
||||||
|
} else {
|
||||||
|
setAccountIds([accountId, ...accountIds]);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
[accountIds, setAccountIds],
|
||||||
|
);
|
||||||
|
|
||||||
|
const searchRequestRef = useRef<AbortController | null>(null);
|
||||||
|
|
||||||
|
const handleSearch = useDebouncedCallback(
|
||||||
|
(value: string) => {
|
||||||
|
if (searchRequestRef.current) {
|
||||||
|
searchRequestRef.current.abort();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (value.trim().length === 0) {
|
||||||
|
setSearching(false);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
setLoading(true);
|
||||||
|
|
||||||
|
searchRequestRef.current = new AbortController();
|
||||||
|
|
||||||
|
void apiRequest<ApiAccountJSON[]>('GET', 'v1/accounts/search', {
|
||||||
|
signal: searchRequestRef.current.signal,
|
||||||
|
params: {
|
||||||
|
q: value,
|
||||||
|
resolve: false,
|
||||||
|
following: true,
|
||||||
|
},
|
||||||
|
})
|
||||||
|
.then((data) => {
|
||||||
|
dispatch(importFetchedAccounts(data));
|
||||||
|
setSearchAccountIds(data.map((a) => a.id));
|
||||||
|
setLoading(false);
|
||||||
|
setSearching(true);
|
||||||
|
return '';
|
||||||
|
})
|
||||||
|
.catch(() => {
|
||||||
|
setSearching(true);
|
||||||
|
setLoading(false);
|
||||||
|
});
|
||||||
|
},
|
||||||
|
500,
|
||||||
|
{ leading: true, trailing: true },
|
||||||
|
);
|
||||||
|
|
||||||
|
let displayedAccountIds: string[];
|
||||||
|
|
||||||
|
if (mode === 'add' && searching) {
|
||||||
|
displayedAccountIds = searchAccountIds;
|
||||||
|
} else {
|
||||||
|
displayedAccountIds = accountIds;
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Column
|
||||||
|
bindToDocument={!multiColumn}
|
||||||
|
label={intl.formatMessage(messages.heading)}
|
||||||
|
>
|
||||||
|
<ColumnHeader
|
||||||
|
title={intl.formatMessage(messages.heading)}
|
||||||
|
icon='list-ul'
|
||||||
|
iconComponent={ListAltIcon}
|
||||||
|
multiColumn={multiColumn}
|
||||||
|
showBackButton
|
||||||
|
/>
|
||||||
|
|
||||||
|
<ColumnSearchHeader
|
||||||
|
placeholder={intl.formatMessage(messages.placeholder)}
|
||||||
|
onBack={handleDismissSearchClick}
|
||||||
|
onSubmit={handleSearch}
|
||||||
|
onActivate={handleSearchClick}
|
||||||
|
active={mode === 'add'}
|
||||||
|
/>
|
||||||
|
|
||||||
|
<ScrollableList
|
||||||
|
scrollKey='list_members'
|
||||||
|
trackScroll={!multiColumn}
|
||||||
|
bindToDocument={!multiColumn}
|
||||||
|
isLoading={loading}
|
||||||
|
showLoading={loading && displayedAccountIds.length === 0}
|
||||||
|
hasMore={false}
|
||||||
|
footer={
|
||||||
|
<>
|
||||||
|
{displayedAccountIds.length > 0 && <div className='spacer' />}
|
||||||
|
|
||||||
|
<div className='column-footer'>
|
||||||
|
<Link to={`/lists/${id}`} className='button button--block'>
|
||||||
|
<FormattedMessage id='lists.done' defaultMessage='Done' />
|
||||||
|
</Link>
|
||||||
|
</div>
|
||||||
|
</>
|
||||||
|
}
|
||||||
|
emptyMessage={
|
||||||
|
mode === 'remove' ? (
|
||||||
|
<>
|
||||||
|
<span>
|
||||||
|
<FormattedMessage
|
||||||
|
id='lists.no_members_yet'
|
||||||
|
defaultMessage='No members yet.'
|
||||||
|
/>
|
||||||
|
<br />
|
||||||
|
<FormattedMessage
|
||||||
|
id='lists.find_users_to_add'
|
||||||
|
defaultMessage='Find users to add'
|
||||||
|
/>
|
||||||
|
</span>
|
||||||
|
|
||||||
|
<SquigglyArrow className='empty-column-indicator__arrow' />
|
||||||
|
</>
|
||||||
|
) : (
|
||||||
|
<FormattedMessage
|
||||||
|
id='lists.no_results_found'
|
||||||
|
defaultMessage='No results found.'
|
||||||
|
/>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
>
|
||||||
|
{displayedAccountIds.map((accountId) => (
|
||||||
|
<AccountItem
|
||||||
|
key={accountId}
|
||||||
|
accountId={accountId}
|
||||||
|
listId={id}
|
||||||
|
partOfList={
|
||||||
|
displayedAccountIds === accountIds ||
|
||||||
|
accountIds.includes(accountId)
|
||||||
|
}
|
||||||
|
onToggle={handleAccountToggle}
|
||||||
|
/>
|
||||||
|
))}
|
||||||
|
</ScrollableList>
|
||||||
|
|
||||||
|
<Helmet>
|
||||||
|
<title>{intl.formatMessage(messages.heading)}</title>
|
||||||
|
<meta name='robots' content='noindex' />
|
||||||
|
</Helmet>
|
||||||
|
</Column>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
// eslint-disable-next-line import/no-default-export
|
||||||
|
export default ListMembers;
|
296
app/javascript/mastodon/features/lists/new.tsx
Normal file
296
app/javascript/mastodon/features/lists/new.tsx
Normal file
@ -0,0 +1,296 @@
|
|||||||
|
import { useCallback, useState, useEffect } from 'react';
|
||||||
|
|
||||||
|
import { defineMessages, useIntl, FormattedMessage } from 'react-intl';
|
||||||
|
|
||||||
|
import { Helmet } from 'react-helmet';
|
||||||
|
import { useParams, useHistory, Link } from 'react-router-dom';
|
||||||
|
|
||||||
|
import { isFulfilled } from '@reduxjs/toolkit';
|
||||||
|
|
||||||
|
import Toggle from 'react-toggle';
|
||||||
|
|
||||||
|
import ListAltIcon from '@/material-icons/400-24px/list_alt.svg?react';
|
||||||
|
import { fetchList } from 'mastodon/actions/lists';
|
||||||
|
import { createList, updateList } from 'mastodon/actions/lists_typed';
|
||||||
|
import { apiGetAccounts } from 'mastodon/api/lists';
|
||||||
|
import type { RepliesPolicyType } from 'mastodon/api_types/lists';
|
||||||
|
import Column from 'mastodon/components/column';
|
||||||
|
import { ColumnHeader } from 'mastodon/components/column_header';
|
||||||
|
import { LoadingIndicator } from 'mastodon/components/loading_indicator';
|
||||||
|
import { useAppDispatch, useAppSelector } from 'mastodon/store';
|
||||||
|
|
||||||
|
const messages = defineMessages({
|
||||||
|
edit: { id: 'column.edit_list', defaultMessage: 'Edit list' },
|
||||||
|
create: { id: 'column.create_list', defaultMessage: 'Create list' },
|
||||||
|
});
|
||||||
|
|
||||||
|
const MembersLink: React.FC<{
|
||||||
|
id: string;
|
||||||
|
}> = ({ id }) => {
|
||||||
|
const [count, setCount] = useState(0);
|
||||||
|
const [avatars, setAvatars] = useState<string[]>([]);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
void apiGetAccounts(id)
|
||||||
|
.then((data) => {
|
||||||
|
setCount(data.length);
|
||||||
|
setAvatars(data.slice(0, 3).map((a) => a.avatar));
|
||||||
|
return '';
|
||||||
|
})
|
||||||
|
.catch(() => {
|
||||||
|
// Nothing
|
||||||
|
});
|
||||||
|
}, [id, setCount, setAvatars]);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Link to={`/lists/${id}/members`} className='app-form__link'>
|
||||||
|
<div className='app-form__link__text'>
|
||||||
|
<strong>
|
||||||
|
<FormattedMessage
|
||||||
|
id='lists.list_members'
|
||||||
|
defaultMessage='List members'
|
||||||
|
/>
|
||||||
|
</strong>
|
||||||
|
<FormattedMessage
|
||||||
|
id='lists.list_members_count'
|
||||||
|
defaultMessage='{count, plural, one {# member} other {# members}}'
|
||||||
|
values={{ count }}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className='avatar-pile'>
|
||||||
|
{avatars.map((url) => (
|
||||||
|
<img key={url} src={url} alt='' />
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
</Link>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
const NewList: React.FC<{
|
||||||
|
multiColumn?: boolean;
|
||||||
|
}> = ({ multiColumn }) => {
|
||||||
|
const dispatch = useAppDispatch();
|
||||||
|
const { id } = useParams<{ id?: string }>();
|
||||||
|
const intl = useIntl();
|
||||||
|
const history = useHistory();
|
||||||
|
|
||||||
|
const list = useAppSelector((state) =>
|
||||||
|
id ? state.lists.get(id) : undefined,
|
||||||
|
);
|
||||||
|
const [title, setTitle] = useState('');
|
||||||
|
const [exclusive, setExclusive] = useState(false);
|
||||||
|
const [repliesPolicy, setRepliesPolicy] = useState<RepliesPolicyType>('list');
|
||||||
|
const [submitting, setSubmitting] = useState(false);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (id) {
|
||||||
|
dispatch(fetchList(id));
|
||||||
|
}
|
||||||
|
}, [dispatch, id]);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (id && list) {
|
||||||
|
setTitle(list.title);
|
||||||
|
setExclusive(list.exclusive);
|
||||||
|
setRepliesPolicy(list.replies_policy);
|
||||||
|
}
|
||||||
|
}, [setTitle, setExclusive, setRepliesPolicy, id, list]);
|
||||||
|
|
||||||
|
const handleTitleChange = useCallback(
|
||||||
|
({ target: { value } }: React.ChangeEvent<HTMLInputElement>) => {
|
||||||
|
setTitle(value);
|
||||||
|
},
|
||||||
|
[setTitle],
|
||||||
|
);
|
||||||
|
|
||||||
|
const handleExclusiveChange = useCallback(
|
||||||
|
({ target: { checked } }: React.ChangeEvent<HTMLInputElement>) => {
|
||||||
|
setExclusive(checked);
|
||||||
|
},
|
||||||
|
[setExclusive],
|
||||||
|
);
|
||||||
|
|
||||||
|
const handleRepliesPolicyChange = useCallback(
|
||||||
|
({ target: { value } }: React.ChangeEvent<HTMLSelectElement>) => {
|
||||||
|
setRepliesPolicy(value as RepliesPolicyType);
|
||||||
|
},
|
||||||
|
[setRepliesPolicy],
|
||||||
|
);
|
||||||
|
|
||||||
|
const handleSubmit = useCallback(() => {
|
||||||
|
setSubmitting(true);
|
||||||
|
|
||||||
|
if (id) {
|
||||||
|
void dispatch(
|
||||||
|
updateList({
|
||||||
|
id,
|
||||||
|
title,
|
||||||
|
exclusive,
|
||||||
|
replies_policy: repliesPolicy,
|
||||||
|
}),
|
||||||
|
).then(() => {
|
||||||
|
setSubmitting(false);
|
||||||
|
return '';
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
void dispatch(
|
||||||
|
createList({
|
||||||
|
title,
|
||||||
|
exclusive,
|
||||||
|
replies_policy: repliesPolicy,
|
||||||
|
}),
|
||||||
|
).then((result) => {
|
||||||
|
setSubmitting(false);
|
||||||
|
|
||||||
|
if (isFulfilled(result)) {
|
||||||
|
history.replace(`/lists/${result.payload.id}/edit`);
|
||||||
|
history.push(`/lists/${result.payload.id}/members`);
|
||||||
|
}
|
||||||
|
|
||||||
|
return '';
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}, [history, dispatch, setSubmitting, id, title, exclusive, repliesPolicy]);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Column
|
||||||
|
bindToDocument={!multiColumn}
|
||||||
|
label={intl.formatMessage(id ? messages.edit : messages.create)}
|
||||||
|
>
|
||||||
|
<ColumnHeader
|
||||||
|
title={intl.formatMessage(id ? messages.edit : messages.create)}
|
||||||
|
icon='list-ul'
|
||||||
|
iconComponent={ListAltIcon}
|
||||||
|
multiColumn={multiColumn}
|
||||||
|
showBackButton
|
||||||
|
/>
|
||||||
|
|
||||||
|
<div className='scrollable'>
|
||||||
|
<form className='simple_form app-form' onSubmit={handleSubmit}>
|
||||||
|
<div className='fields-group'>
|
||||||
|
<div className='input with_label'>
|
||||||
|
<div className='label_input'>
|
||||||
|
<label htmlFor='list_title'>
|
||||||
|
<FormattedMessage
|
||||||
|
id='lists.list_name'
|
||||||
|
defaultMessage='List name'
|
||||||
|
/>
|
||||||
|
</label>
|
||||||
|
|
||||||
|
<div className='label_input__wrapper'>
|
||||||
|
<input
|
||||||
|
id='list_title'
|
||||||
|
type='text'
|
||||||
|
value={title}
|
||||||
|
onChange={handleTitleChange}
|
||||||
|
maxLength={30}
|
||||||
|
required
|
||||||
|
placeholder=' '
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className='fields-group'>
|
||||||
|
<div className='input with_label'>
|
||||||
|
<div className='label_input'>
|
||||||
|
<label htmlFor='list_replies_policy'>
|
||||||
|
<FormattedMessage
|
||||||
|
id='lists.show_replies_to'
|
||||||
|
defaultMessage='Include replies from list members to'
|
||||||
|
/>
|
||||||
|
</label>
|
||||||
|
|
||||||
|
<div className='label_input__wrapper'>
|
||||||
|
<select
|
||||||
|
id='list_replies_policy'
|
||||||
|
value={repliesPolicy}
|
||||||
|
onChange={handleRepliesPolicyChange}
|
||||||
|
>
|
||||||
|
<FormattedMessage
|
||||||
|
id='lists.replies_policy.none'
|
||||||
|
defaultMessage='No one'
|
||||||
|
>
|
||||||
|
{(msg) => <option value='none'>{msg}</option>}
|
||||||
|
</FormattedMessage>
|
||||||
|
<FormattedMessage
|
||||||
|
id='lists.replies_policy.list'
|
||||||
|
defaultMessage='Members of the list'
|
||||||
|
>
|
||||||
|
{(msg) => <option value='list'>{msg}</option>}
|
||||||
|
</FormattedMessage>
|
||||||
|
<FormattedMessage
|
||||||
|
id='lists.replies_policy.followed'
|
||||||
|
defaultMessage='Any followed user'
|
||||||
|
>
|
||||||
|
{(msg) => <option value='followed'>{msg}</option>}
|
||||||
|
</FormattedMessage>
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{id && (
|
||||||
|
<div className='fields-group'>
|
||||||
|
<MembersLink id={id} />
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
|
||||||
|
<div className='fields-group'>
|
||||||
|
{/* eslint-disable-next-line jsx-a11y/label-has-associated-control */}
|
||||||
|
<label className='app-form__toggle'>
|
||||||
|
<div className='app-form__toggle__label'>
|
||||||
|
<strong>
|
||||||
|
<FormattedMessage
|
||||||
|
id='lists.exclusive'
|
||||||
|
defaultMessage='Hide members in Home'
|
||||||
|
/>
|
||||||
|
</strong>
|
||||||
|
<span className='hint'>
|
||||||
|
<FormattedMessage
|
||||||
|
id='lists.exclusive_hint'
|
||||||
|
defaultMessage='If someone is on this list, hide them in your Home feed to avoid seeing their posts twice.'
|
||||||
|
/>
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className='app-form__toggle__toggle'>
|
||||||
|
<div>
|
||||||
|
<Toggle
|
||||||
|
checked={exclusive}
|
||||||
|
onChange={handleExclusiveChange}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</label>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className='actions'>
|
||||||
|
<button className='button' type='submit'>
|
||||||
|
{submitting ? (
|
||||||
|
<LoadingIndicator />
|
||||||
|
) : id ? (
|
||||||
|
<FormattedMessage id='lists.save' defaultMessage='Save' />
|
||||||
|
) : (
|
||||||
|
<FormattedMessage id='lists.create' defaultMessage='Create' />
|
||||||
|
)}
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<Helmet>
|
||||||
|
<title>
|
||||||
|
{intl.formatMessage(id ? messages.edit : messages.create)}
|
||||||
|
</title>
|
||||||
|
<meta name='robots' content='noindex' />
|
||||||
|
</Helmet>
|
||||||
|
</Column>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
// eslint-disable-next-line import/no-default-export
|
||||||
|
export default NewList;
|
@ -1,119 +0,0 @@
|
|||||||
import PropTypes from 'prop-types';
|
|
||||||
import { PureComponent } from 'react';
|
|
||||||
|
|
||||||
import { defineMessages, injectIntl, FormattedMessage } from 'react-intl';
|
|
||||||
|
|
||||||
import HomeIcon from '@/material-icons/400-24px/home-fill.svg?react';
|
|
||||||
import InsertChartIcon from '@/material-icons/400-24px/insert_chart.svg?react';
|
|
||||||
import PersonAddIcon from '@/material-icons/400-24px/person_add.svg?react';
|
|
||||||
import RepeatIcon from '@/material-icons/400-24px/repeat.svg?react';
|
|
||||||
import ReplyAllIcon from '@/material-icons/400-24px/reply_all.svg?react';
|
|
||||||
import StarIcon from '@/material-icons/400-24px/star.svg?react';
|
|
||||||
import { Icon } from 'mastodon/components/icon';
|
|
||||||
|
|
||||||
const tooltips = defineMessages({
|
|
||||||
mentions: { id: 'notifications.filter.mentions', defaultMessage: 'Mentions' },
|
|
||||||
favourites: { id: 'notifications.filter.favourites', defaultMessage: 'Favorites' },
|
|
||||||
boosts: { id: 'notifications.filter.boosts', defaultMessage: 'Boosts' },
|
|
||||||
polls: { id: 'notifications.filter.polls', defaultMessage: 'Poll results' },
|
|
||||||
follows: { id: 'notifications.filter.follows', defaultMessage: 'Follows' },
|
|
||||||
statuses: { id: 'notifications.filter.statuses', defaultMessage: 'Updates from people you follow' },
|
|
||||||
});
|
|
||||||
|
|
||||||
class FilterBar extends PureComponent {
|
|
||||||
|
|
||||||
static propTypes = {
|
|
||||||
selectFilter: PropTypes.func.isRequired,
|
|
||||||
selectedFilter: PropTypes.string.isRequired,
|
|
||||||
advancedMode: PropTypes.bool.isRequired,
|
|
||||||
intl: PropTypes.object.isRequired,
|
|
||||||
};
|
|
||||||
|
|
||||||
onClick (notificationType) {
|
|
||||||
return () => this.props.selectFilter(notificationType);
|
|
||||||
}
|
|
||||||
|
|
||||||
render () {
|
|
||||||
const { selectedFilter, advancedMode, intl } = this.props;
|
|
||||||
const renderedElement = !advancedMode ? (
|
|
||||||
<div className='notification__filter-bar'>
|
|
||||||
<button
|
|
||||||
className={selectedFilter === 'all' ? 'active' : ''}
|
|
||||||
onClick={this.onClick('all')}
|
|
||||||
>
|
|
||||||
<FormattedMessage
|
|
||||||
id='notifications.filter.all'
|
|
||||||
defaultMessage='All'
|
|
||||||
/>
|
|
||||||
</button>
|
|
||||||
<button
|
|
||||||
className={selectedFilter === 'mention' ? 'active' : ''}
|
|
||||||
onClick={this.onClick('mention')}
|
|
||||||
>
|
|
||||||
<FormattedMessage
|
|
||||||
id='notifications.filter.mentions'
|
|
||||||
defaultMessage='Mentions'
|
|
||||||
/>
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
) : (
|
|
||||||
<div className='notification__filter-bar'>
|
|
||||||
<button
|
|
||||||
className={selectedFilter === 'all' ? 'active' : ''}
|
|
||||||
onClick={this.onClick('all')}
|
|
||||||
>
|
|
||||||
<FormattedMessage
|
|
||||||
id='notifications.filter.all'
|
|
||||||
defaultMessage='All'
|
|
||||||
/>
|
|
||||||
</button>
|
|
||||||
<button
|
|
||||||
className={selectedFilter === 'mention' ? 'active' : ''}
|
|
||||||
onClick={this.onClick('mention')}
|
|
||||||
title={intl.formatMessage(tooltips.mentions)}
|
|
||||||
>
|
|
||||||
<Icon id='reply-all' icon={ReplyAllIcon} />
|
|
||||||
</button>
|
|
||||||
<button
|
|
||||||
className={selectedFilter === 'favourite' ? 'active' : ''}
|
|
||||||
onClick={this.onClick('favourite')}
|
|
||||||
title={intl.formatMessage(tooltips.favourites)}
|
|
||||||
>
|
|
||||||
<Icon id='star' icon={StarIcon} />
|
|
||||||
</button>
|
|
||||||
<button
|
|
||||||
className={selectedFilter === 'reblog' ? 'active' : ''}
|
|
||||||
onClick={this.onClick('reblog')}
|
|
||||||
title={intl.formatMessage(tooltips.boosts)}
|
|
||||||
>
|
|
||||||
<Icon id='retweet' icon={RepeatIcon} />
|
|
||||||
</button>
|
|
||||||
<button
|
|
||||||
className={selectedFilter === 'poll' ? 'active' : ''}
|
|
||||||
onClick={this.onClick('poll')}
|
|
||||||
title={intl.formatMessage(tooltips.polls)}
|
|
||||||
>
|
|
||||||
<Icon id='tasks' icon={InsertChartIcon} />
|
|
||||||
</button>
|
|
||||||
<button
|
|
||||||
className={selectedFilter === 'status' ? 'active' : ''}
|
|
||||||
onClick={this.onClick('status')}
|
|
||||||
title={intl.formatMessage(tooltips.statuses)}
|
|
||||||
>
|
|
||||||
<Icon id='home' icon={HomeIcon} />
|
|
||||||
</button>
|
|
||||||
<button
|
|
||||||
className={selectedFilter === 'follow' ? 'active' : ''}
|
|
||||||
onClick={this.onClick('follow')}
|
|
||||||
title={intl.formatMessage(tooltips.follows)}
|
|
||||||
>
|
|
||||||
<Icon id='user-plus' icon={PersonAddIcon} />
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
return renderedElement;
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
export default injectIntl(FilterBar);
|
|
@ -1,17 +0,0 @@
|
|||||||
import { connect } from 'react-redux';
|
|
||||||
|
|
||||||
import { setFilter } from '../../../actions/notifications';
|
|
||||||
import FilterBar from '../components/filter_bar';
|
|
||||||
|
|
||||||
const makeMapStateToProps = state => ({
|
|
||||||
selectedFilter: state.getIn(['settings', 'notifications', 'quickFilter', 'active']),
|
|
||||||
advancedMode: state.getIn(['settings', 'notifications', 'quickFilter', 'advanced']),
|
|
||||||
});
|
|
||||||
|
|
||||||
const mapDispatchToProps = (dispatch) => ({
|
|
||||||
selectFilter (newActiveFilter) {
|
|
||||||
dispatch(setFilter(newActiveFilter));
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
export default connect(makeMapStateToProps, mapDispatchToProps)(FilterBar);
|
|
@ -1,308 +0,0 @@
|
|||||||
import PropTypes from 'prop-types';
|
|
||||||
import { PureComponent } from 'react';
|
|
||||||
|
|
||||||
import { defineMessages, injectIntl, FormattedMessage } from 'react-intl';
|
|
||||||
|
|
||||||
import { Helmet } from 'react-helmet';
|
|
||||||
|
|
||||||
import { createSelector } from '@reduxjs/toolkit';
|
|
||||||
import { List as ImmutableList } from 'immutable';
|
|
||||||
import ImmutablePropTypes from 'react-immutable-proptypes';
|
|
||||||
import { connect } from 'react-redux';
|
|
||||||
|
|
||||||
import { debounce } from 'lodash';
|
|
||||||
|
|
||||||
import DoneAllIcon from '@/material-icons/400-24px/done_all.svg?react';
|
|
||||||
import NotificationsIcon from '@/material-icons/400-24px/notifications-fill.svg?react';
|
|
||||||
import { compareId } from 'mastodon/compare_id';
|
|
||||||
import { Icon } from 'mastodon/components/icon';
|
|
||||||
import { NotSignedInIndicator } from 'mastodon/components/not_signed_in_indicator';
|
|
||||||
import { identityContextPropShape, withIdentity } from 'mastodon/identity_context';
|
|
||||||
|
|
||||||
import { addColumn, removeColumn, moveColumn } from '../../actions/columns';
|
|
||||||
import { submitMarkers } from '../../actions/markers';
|
|
||||||
import {
|
|
||||||
expandNotifications,
|
|
||||||
scrollTopNotifications,
|
|
||||||
loadPending,
|
|
||||||
mountNotifications,
|
|
||||||
unmountNotifications,
|
|
||||||
markNotificationsAsRead,
|
|
||||||
} from '../../actions/notifications';
|
|
||||||
import Column from '../../components/column';
|
|
||||||
import ColumnHeader from '../../components/column_header';
|
|
||||||
import { LoadGap } from '../../components/load_gap';
|
|
||||||
import ScrollableList from '../../components/scrollable_list';
|
|
||||||
|
|
||||||
import {
|
|
||||||
FilteredNotificationsBanner,
|
|
||||||
FilteredNotificationsIconButton,
|
|
||||||
} from './components/filtered_notifications_banner';
|
|
||||||
import NotificationsPermissionBanner from './components/notifications_permission_banner';
|
|
||||||
import ColumnSettingsContainer from './containers/column_settings_container';
|
|
||||||
import FilterBarContainer from './containers/filter_bar_container';
|
|
||||||
import NotificationContainer from './containers/notification_container';
|
|
||||||
|
|
||||||
const messages = defineMessages({
|
|
||||||
title: { id: 'column.notifications', defaultMessage: 'Notifications' },
|
|
||||||
markAsRead : { id: 'notifications.mark_as_read', defaultMessage: 'Mark every notification as read' },
|
|
||||||
});
|
|
||||||
|
|
||||||
const getExcludedTypes = createSelector([
|
|
||||||
state => state.getIn(['settings', 'notifications', 'shows']),
|
|
||||||
], (shows) => {
|
|
||||||
return ImmutableList(shows.filter(item => !item).keys());
|
|
||||||
});
|
|
||||||
|
|
||||||
const getNotifications = createSelector([
|
|
||||||
state => state.getIn(['settings', 'notifications', 'quickFilter', 'show']),
|
|
||||||
state => state.getIn(['settings', 'notifications', 'quickFilter', 'active']),
|
|
||||||
getExcludedTypes,
|
|
||||||
state => state.getIn(['notifications', 'items']),
|
|
||||||
], (showFilterBar, allowedType, excludedTypes, notifications) => {
|
|
||||||
if (!showFilterBar || allowedType === 'all') {
|
|
||||||
// used if user changed the notification settings after loading the notifications from the server
|
|
||||||
// otherwise a list of notifications will come pre-filtered from the backend
|
|
||||||
// we need to turn it off for FilterBar in order not to block ourselves from seeing a specific category
|
|
||||||
return notifications.filterNot(item => item !== null && excludedTypes.includes(item.get('type')));
|
|
||||||
}
|
|
||||||
return notifications.filter(item => item === null || allowedType === item.get('type'));
|
|
||||||
});
|
|
||||||
|
|
||||||
const mapStateToProps = state => ({
|
|
||||||
notifications: getNotifications(state),
|
|
||||||
isLoading: state.getIn(['notifications', 'isLoading'], 0) > 0,
|
|
||||||
isUnread: state.getIn(['notifications', 'unread']) > 0 || state.getIn(['notifications', 'pendingItems']).size > 0,
|
|
||||||
hasMore: state.getIn(['notifications', 'hasMore']),
|
|
||||||
numPending: state.getIn(['notifications', 'pendingItems'], ImmutableList()).size,
|
|
||||||
lastReadId: state.getIn(['settings', 'notifications', 'showUnread']) ? state.getIn(['notifications', 'readMarkerId']) : '0',
|
|
||||||
canMarkAsRead: state.getIn(['settings', 'notifications', 'showUnread']) && state.getIn(['notifications', 'readMarkerId']) !== '0' && getNotifications(state).some(item => item !== null && compareId(item.get('id'), state.getIn(['notifications', 'readMarkerId'])) > 0),
|
|
||||||
needsNotificationPermission: state.getIn(['settings', 'notifications', 'alerts']).includes(true) && state.getIn(['notifications', 'browserSupport']) && state.getIn(['notifications', 'browserPermission']) === 'default' && !state.getIn(['settings', 'notifications', 'dismissPermissionBanner']),
|
|
||||||
});
|
|
||||||
|
|
||||||
class Notifications extends PureComponent {
|
|
||||||
static propTypes = {
|
|
||||||
identity: identityContextPropShape,
|
|
||||||
columnId: PropTypes.string,
|
|
||||||
notifications: ImmutablePropTypes.list.isRequired,
|
|
||||||
dispatch: PropTypes.func.isRequired,
|
|
||||||
intl: PropTypes.object.isRequired,
|
|
||||||
isLoading: PropTypes.bool,
|
|
||||||
isUnread: PropTypes.bool,
|
|
||||||
multiColumn: PropTypes.bool,
|
|
||||||
hasMore: PropTypes.bool,
|
|
||||||
numPending: PropTypes.number,
|
|
||||||
lastReadId: PropTypes.string,
|
|
||||||
canMarkAsRead: PropTypes.bool,
|
|
||||||
needsNotificationPermission: PropTypes.bool,
|
|
||||||
};
|
|
||||||
|
|
||||||
static defaultProps = {
|
|
||||||
trackScroll: true,
|
|
||||||
};
|
|
||||||
|
|
||||||
UNSAFE_componentWillMount() {
|
|
||||||
this.props.dispatch(mountNotifications());
|
|
||||||
}
|
|
||||||
|
|
||||||
componentWillUnmount () {
|
|
||||||
this.handleLoadOlder.cancel();
|
|
||||||
this.handleScrollToTop.cancel();
|
|
||||||
this.handleScroll.cancel();
|
|
||||||
this.props.dispatch(scrollTopNotifications(false));
|
|
||||||
this.props.dispatch(unmountNotifications());
|
|
||||||
}
|
|
||||||
|
|
||||||
handleLoadGap = (maxId) => {
|
|
||||||
this.props.dispatch(expandNotifications({ maxId }));
|
|
||||||
};
|
|
||||||
|
|
||||||
handleLoadOlder = debounce(() => {
|
|
||||||
const last = this.props.notifications.last();
|
|
||||||
this.props.dispatch(expandNotifications({ maxId: last && last.get('id') }));
|
|
||||||
}, 300, { leading: true });
|
|
||||||
|
|
||||||
handleLoadPending = () => {
|
|
||||||
this.props.dispatch(loadPending());
|
|
||||||
};
|
|
||||||
|
|
||||||
handleScrollToTop = debounce(() => {
|
|
||||||
this.props.dispatch(scrollTopNotifications(true));
|
|
||||||
}, 100);
|
|
||||||
|
|
||||||
handleScroll = debounce(() => {
|
|
||||||
this.props.dispatch(scrollTopNotifications(false));
|
|
||||||
}, 100);
|
|
||||||
|
|
||||||
handlePin = () => {
|
|
||||||
const { columnId, dispatch } = this.props;
|
|
||||||
|
|
||||||
if (columnId) {
|
|
||||||
dispatch(removeColumn(columnId));
|
|
||||||
} else {
|
|
||||||
dispatch(addColumn('NOTIFICATIONS', {}));
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
handleMove = (dir) => {
|
|
||||||
const { columnId, dispatch } = this.props;
|
|
||||||
dispatch(moveColumn(columnId, dir));
|
|
||||||
};
|
|
||||||
|
|
||||||
handleHeaderClick = () => {
|
|
||||||
this.column.scrollTop();
|
|
||||||
};
|
|
||||||
|
|
||||||
setColumnRef = c => {
|
|
||||||
this.column = c;
|
|
||||||
};
|
|
||||||
|
|
||||||
handleMoveUp = id => {
|
|
||||||
const elementIndex = this.props.notifications.findIndex(item => item !== null && item.get('id') === id) - 1;
|
|
||||||
this._selectChild(elementIndex, true);
|
|
||||||
};
|
|
||||||
|
|
||||||
handleMoveDown = id => {
|
|
||||||
const elementIndex = this.props.notifications.findIndex(item => item !== null && item.get('id') === id) + 1;
|
|
||||||
this._selectChild(elementIndex, false);
|
|
||||||
};
|
|
||||||
|
|
||||||
_selectChild (index, align_top) {
|
|
||||||
const container = this.column.node;
|
|
||||||
const element = container.querySelector(`article:nth-of-type(${index + 1}) .focusable`);
|
|
||||||
|
|
||||||
if (element) {
|
|
||||||
if (align_top && container.scrollTop > element.offsetTop) {
|
|
||||||
element.scrollIntoView(true);
|
|
||||||
} else if (!align_top && container.scrollTop + container.clientHeight < element.offsetTop + element.offsetHeight) {
|
|
||||||
element.scrollIntoView(false);
|
|
||||||
}
|
|
||||||
element.focus();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
handleMarkAsRead = () => {
|
|
||||||
this.props.dispatch(markNotificationsAsRead());
|
|
||||||
this.props.dispatch(submitMarkers({ immediate: true }));
|
|
||||||
};
|
|
||||||
|
|
||||||
render () {
|
|
||||||
const { intl, notifications, isLoading, isUnread, columnId, multiColumn, hasMore, numPending, lastReadId, canMarkAsRead, needsNotificationPermission } = this.props;
|
|
||||||
const pinned = !!columnId;
|
|
||||||
const emptyMessage = <FormattedMessage id='empty_column.notifications' defaultMessage="You don't have any notifications yet. When other people interact with you, you will see it here." />;
|
|
||||||
const { signedIn } = this.props.identity;
|
|
||||||
|
|
||||||
let scrollableContent = null;
|
|
||||||
|
|
||||||
const filterBarContainer = signedIn
|
|
||||||
? (<FilterBarContainer />)
|
|
||||||
: null;
|
|
||||||
|
|
||||||
if (isLoading && this.scrollableContent) {
|
|
||||||
scrollableContent = this.scrollableContent;
|
|
||||||
} else if (notifications.size > 0 || hasMore) {
|
|
||||||
scrollableContent = notifications.map((item, index) => item === null ? (
|
|
||||||
<LoadGap
|
|
||||||
key={'gap:' + notifications.getIn([index + 1, 'id'])}
|
|
||||||
disabled={isLoading}
|
|
||||||
param={index > 0 ? notifications.getIn([index - 1, 'id']) : null}
|
|
||||||
onClick={this.handleLoadGap}
|
|
||||||
/>
|
|
||||||
) : (
|
|
||||||
<NotificationContainer
|
|
||||||
key={item.get('id')}
|
|
||||||
notification={item}
|
|
||||||
accountId={item.get('account')}
|
|
||||||
onMoveUp={this.handleMoveUp}
|
|
||||||
onMoveDown={this.handleMoveDown}
|
|
||||||
unread={lastReadId !== '0' && compareId(item.get('id'), lastReadId) > 0}
|
|
||||||
/>
|
|
||||||
));
|
|
||||||
} else {
|
|
||||||
scrollableContent = null;
|
|
||||||
}
|
|
||||||
|
|
||||||
this.scrollableContent = scrollableContent;
|
|
||||||
|
|
||||||
let scrollContainer;
|
|
||||||
|
|
||||||
const prepend = (
|
|
||||||
<>
|
|
||||||
{needsNotificationPermission && <NotificationsPermissionBanner />}
|
|
||||||
<FilteredNotificationsBanner />
|
|
||||||
</>
|
|
||||||
);
|
|
||||||
|
|
||||||
if (signedIn) {
|
|
||||||
scrollContainer = (
|
|
||||||
<ScrollableList
|
|
||||||
scrollKey={`notifications-${columnId}`}
|
|
||||||
trackScroll={!pinned}
|
|
||||||
isLoading={isLoading}
|
|
||||||
showLoading={isLoading && notifications.size === 0}
|
|
||||||
hasMore={hasMore}
|
|
||||||
numPending={numPending}
|
|
||||||
prepend={prepend}
|
|
||||||
alwaysPrepend
|
|
||||||
emptyMessage={emptyMessage}
|
|
||||||
onLoadMore={this.handleLoadOlder}
|
|
||||||
onLoadPending={this.handleLoadPending}
|
|
||||||
onScrollToTop={this.handleScrollToTop}
|
|
||||||
onScroll={this.handleScroll}
|
|
||||||
bindToDocument={!multiColumn}
|
|
||||||
>
|
|
||||||
{scrollableContent}
|
|
||||||
</ScrollableList>
|
|
||||||
);
|
|
||||||
} else {
|
|
||||||
scrollContainer = <NotSignedInIndicator />;
|
|
||||||
}
|
|
||||||
|
|
||||||
const extraButton = (
|
|
||||||
<>
|
|
||||||
<FilteredNotificationsIconButton className='column-header__button' />
|
|
||||||
{canMarkAsRead && (
|
|
||||||
<button
|
|
||||||
aria-label={intl.formatMessage(messages.markAsRead)}
|
|
||||||
title={intl.formatMessage(messages.markAsRead)}
|
|
||||||
onClick={this.handleMarkAsRead}
|
|
||||||
className='column-header__button'
|
|
||||||
>
|
|
||||||
<Icon id='done-all' icon={DoneAllIcon} />
|
|
||||||
</button>
|
|
||||||
)}
|
|
||||||
</>
|
|
||||||
);
|
|
||||||
|
|
||||||
return (
|
|
||||||
<Column bindToDocument={!multiColumn} ref={this.setColumnRef} label={intl.formatMessage(messages.title)}>
|
|
||||||
<ColumnHeader
|
|
||||||
icon='bell'
|
|
||||||
iconComponent={NotificationsIcon}
|
|
||||||
active={isUnread}
|
|
||||||
title={intl.formatMessage(messages.title)}
|
|
||||||
onPin={this.handlePin}
|
|
||||||
onMove={this.handleMove}
|
|
||||||
onClick={this.handleHeaderClick}
|
|
||||||
pinned={pinned}
|
|
||||||
multiColumn={multiColumn}
|
|
||||||
extraButton={extraButton}
|
|
||||||
>
|
|
||||||
<ColumnSettingsContainer />
|
|
||||||
</ColumnHeader>
|
|
||||||
|
|
||||||
{filterBarContainer}
|
|
||||||
|
|
||||||
{scrollContainer}
|
|
||||||
|
|
||||||
<Helmet>
|
|
||||||
<title>{intl.formatMessage(messages.title)}</title>
|
|
||||||
<meta name='robots' content='noindex' />
|
|
||||||
</Helmet>
|
|
||||||
</Column>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
export default connect(mapStateToProps)(withIdentity(injectIntl(Notifications)));
|
|
@ -43,7 +43,7 @@ export const EmbeddedStatus: React.FC<{ statusId: string }> = ({
|
|||||||
);
|
);
|
||||||
|
|
||||||
const handleMouseUp = useCallback<React.MouseEventHandler<HTMLDivElement>>(
|
const handleMouseUp = useCallback<React.MouseEventHandler<HTMLDivElement>>(
|
||||||
({ clientX, clientY, target, button }) => {
|
({ clientX, clientY, target, button, ctrlKey, metaKey }) => {
|
||||||
const [startX, startY] = clickCoordinatesRef.current ?? [0, 0];
|
const [startX, startY] = clickCoordinatesRef.current ?? [0, 0];
|
||||||
const [deltaX, deltaY] = [
|
const [deltaX, deltaY] = [
|
||||||
Math.abs(clientX - startX),
|
Math.abs(clientX - startX),
|
||||||
@ -64,8 +64,14 @@ export const EmbeddedStatus: React.FC<{ statusId: string }> = ({
|
|||||||
element = element.parentNode as HTMLDivElement | null;
|
element = element.parentNode as HTMLDivElement | null;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (deltaX + deltaY < 5 && button === 0 && account) {
|
if (deltaX + deltaY < 5 && account) {
|
||||||
history.push(`/@${account.acct}/${statusId}`);
|
const path = `/@${account.acct}/${statusId}`;
|
||||||
|
|
||||||
|
if (button === 0 && !(ctrlKey || metaKey)) {
|
||||||
|
history.push(path);
|
||||||
|
} else if (button === 1 || (button === 0 && (ctrlKey || metaKey))) {
|
||||||
|
window.open(path, '_blank', 'noreferrer noopener');
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
clickCoordinatesRef.current = null;
|
clickCoordinatesRef.current = null;
|
||||||
|
@ -1,9 +0,0 @@
|
|||||||
import Notifications_v2 from 'mastodon/features/notifications_v2';
|
|
||||||
|
|
||||||
export const NotificationsWrapper = (props) => {
|
|
||||||
return (
|
|
||||||
<Notifications_v2 {...props} />
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
export default NotificationsWrapper;
|
|
@ -1,57 +0,0 @@
|
|||||||
import PropTypes from 'prop-types';
|
|
||||||
|
|
||||||
import { Link } from 'react-router-dom';
|
|
||||||
|
|
||||||
import ArrowRightAltIcon from '@/material-icons/400-24px/arrow_right_alt.svg?react';
|
|
||||||
import CheckIcon from '@/material-icons/400-24px/done.svg?react';
|
|
||||||
import { Icon } from 'mastodon/components/icon';
|
|
||||||
|
|
||||||
export const Step = ({ label, description, icon, iconComponent, completed, onClick, href, to }) => {
|
|
||||||
const content = (
|
|
||||||
<>
|
|
||||||
<div className='onboarding__steps__item__icon'>
|
|
||||||
<Icon id={icon} icon={iconComponent} />
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div className='onboarding__steps__item__description'>
|
|
||||||
<h6>{label}</h6>
|
|
||||||
<p>{description}</p>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div className={completed ? 'onboarding__steps__item__progress' : 'onboarding__steps__item__go'}>
|
|
||||||
{completed ? <Icon icon={CheckIcon} /> : <Icon icon={ArrowRightAltIcon} />}
|
|
||||||
</div>
|
|
||||||
</>
|
|
||||||
);
|
|
||||||
|
|
||||||
if (href) {
|
|
||||||
return (
|
|
||||||
<a href={href} onClick={onClick} target='_blank' rel='noopener' className='onboarding__steps__item'>
|
|
||||||
{content}
|
|
||||||
</a>
|
|
||||||
);
|
|
||||||
} else if (to) {
|
|
||||||
return (
|
|
||||||
<Link to={to} className='onboarding__steps__item'>
|
|
||||||
{content}
|
|
||||||
</Link>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
return (
|
|
||||||
<button onClick={onClick} className='onboarding__steps__item'>
|
|
||||||
{content}
|
|
||||||
</button>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
Step.propTypes = {
|
|
||||||
label: PropTypes.node,
|
|
||||||
description: PropTypes.node,
|
|
||||||
icon: PropTypes.string,
|
|
||||||
iconComponent: PropTypes.func,
|
|
||||||
completed: PropTypes.bool,
|
|
||||||
href: PropTypes.string,
|
|
||||||
to: PropTypes.string,
|
|
||||||
onClick: PropTypes.func,
|
|
||||||
};
|
|
@ -1,62 +0,0 @@
|
|||||||
import { useEffect } from 'react';
|
|
||||||
|
|
||||||
import { FormattedMessage } from 'react-intl';
|
|
||||||
|
|
||||||
import { Link } from 'react-router-dom';
|
|
||||||
|
|
||||||
import { useDispatch } from 'react-redux';
|
|
||||||
|
|
||||||
|
|
||||||
import { fetchSuggestions } from 'mastodon/actions/suggestions';
|
|
||||||
import { markAsPartial } from 'mastodon/actions/timelines';
|
|
||||||
import { ColumnBackButton } from 'mastodon/components/column_back_button';
|
|
||||||
import { EmptyAccount } from 'mastodon/components/empty_account';
|
|
||||||
import Account from 'mastodon/containers/account_container';
|
|
||||||
import { useAppSelector } from 'mastodon/store';
|
|
||||||
|
|
||||||
export const Follows = () => {
|
|
||||||
const dispatch = useDispatch();
|
|
||||||
const isLoading = useAppSelector(state => state.getIn(['suggestions', 'isLoading']));
|
|
||||||
const suggestions = useAppSelector(state => state.getIn(['suggestions', 'items']));
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
dispatch(fetchSuggestions(true));
|
|
||||||
|
|
||||||
return () => {
|
|
||||||
dispatch(markAsPartial('home'));
|
|
||||||
};
|
|
||||||
}, [dispatch]);
|
|
||||||
|
|
||||||
let loadedContent;
|
|
||||||
|
|
||||||
if (isLoading) {
|
|
||||||
loadedContent = (new Array(8)).fill().map((_, i) => <EmptyAccount key={i} />);
|
|
||||||
} else if (suggestions.isEmpty()) {
|
|
||||||
loadedContent = <div className='follow-recommendations__empty'><FormattedMessage id='onboarding.follows.empty' defaultMessage='Unfortunately, no results can be shown right now. You can try using search or browsing the explore page to find people to follow, or try again later.' /></div>;
|
|
||||||
} else {
|
|
||||||
loadedContent = suggestions.map(suggestion => <Account id={suggestion.get('account')} key={suggestion.get('account')} withBio />);
|
|
||||||
}
|
|
||||||
|
|
||||||
return (
|
|
||||||
<>
|
|
||||||
<ColumnBackButton />
|
|
||||||
|
|
||||||
<div className='scrollable privacy-policy'>
|
|
||||||
<div className='column-title'>
|
|
||||||
<h3><FormattedMessage id='onboarding.follows.title' defaultMessage='Popular on Mastodon' /></h3>
|
|
||||||
<p><FormattedMessage id='onboarding.follows.lead' defaultMessage='You curate your own home feed. The more people you follow, the more active and interesting it will be. These profiles may be a good starting point—you can always unfollow them later!' /></p>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div className='follow-recommendations'>
|
|
||||||
{loadedContent}
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<p className='onboarding__lead'><FormattedMessage id='onboarding.tips.accounts_from_other_servers' defaultMessage='<strong>Did you know?</strong> Since Mastodon is decentralized, some profiles you come across will be hosted on servers other than yours. And yet you can interact with them seamlessly! Their server is in the second half of their username!' values={{ strong: chunks => <strong>{chunks}</strong> }} /></p>
|
|
||||||
|
|
||||||
<div className='onboarding__footer'>
|
|
||||||
<Link className='link-button' to='/start'><FormattedMessage id='onboarding.actions.back' defaultMessage='Take me back' /></Link>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</>
|
|
||||||
);
|
|
||||||
};
|
|
191
app/javascript/mastodon/features/onboarding/follows.tsx
Normal file
191
app/javascript/mastodon/features/onboarding/follows.tsx
Normal file
@ -0,0 +1,191 @@
|
|||||||
|
import { useEffect, useState, useCallback, useRef } from 'react';
|
||||||
|
|
||||||
|
import { FormattedMessage, useIntl, defineMessages } from 'react-intl';
|
||||||
|
|
||||||
|
import { Helmet } from 'react-helmet';
|
||||||
|
import { Link } from 'react-router-dom';
|
||||||
|
|
||||||
|
import { useDebouncedCallback } from 'use-debounce';
|
||||||
|
|
||||||
|
import PersonIcon from '@/material-icons/400-24px/person.svg?react';
|
||||||
|
import { fetchRelationships } from 'mastodon/actions/accounts';
|
||||||
|
import { importFetchedAccounts } from 'mastodon/actions/importer';
|
||||||
|
import { fetchSuggestions } from 'mastodon/actions/suggestions';
|
||||||
|
import { markAsPartial } from 'mastodon/actions/timelines';
|
||||||
|
import { apiRequest } from 'mastodon/api';
|
||||||
|
import type { ApiAccountJSON } from 'mastodon/api_types/accounts';
|
||||||
|
import Column from 'mastodon/components/column';
|
||||||
|
import { ColumnHeader } from 'mastodon/components/column_header';
|
||||||
|
import { ColumnSearchHeader } from 'mastodon/components/column_search_header';
|
||||||
|
import ScrollableList from 'mastodon/components/scrollable_list';
|
||||||
|
import Account from 'mastodon/containers/account_container';
|
||||||
|
import { useAppSelector, useAppDispatch } from 'mastodon/store';
|
||||||
|
|
||||||
|
const messages = defineMessages({
|
||||||
|
title: {
|
||||||
|
id: 'onboarding.follows.title',
|
||||||
|
defaultMessage: 'Follow people to get started',
|
||||||
|
},
|
||||||
|
search: { id: 'onboarding.follows.search', defaultMessage: 'Search' },
|
||||||
|
back: { id: 'onboarding.follows.back', defaultMessage: 'Back' },
|
||||||
|
});
|
||||||
|
|
||||||
|
type Mode = 'remove' | 'add';
|
||||||
|
|
||||||
|
export const Follows: React.FC<{
|
||||||
|
multiColumn?: boolean;
|
||||||
|
}> = ({ multiColumn }) => {
|
||||||
|
const intl = useIntl();
|
||||||
|
const dispatch = useAppDispatch();
|
||||||
|
const isLoading = useAppSelector((state) => state.suggestions.isLoading);
|
||||||
|
const suggestions = useAppSelector((state) => state.suggestions.items);
|
||||||
|
const [searchAccountIds, setSearchAccountIds] = useState<string[]>([]);
|
||||||
|
const [mode, setMode] = useState<Mode>('remove');
|
||||||
|
const [isLoadingSearch, setIsLoadingSearch] = useState(false);
|
||||||
|
const [isSearching, setIsSearching] = useState(false);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
void dispatch(fetchSuggestions());
|
||||||
|
|
||||||
|
return () => {
|
||||||
|
dispatch(markAsPartial('home'));
|
||||||
|
};
|
||||||
|
}, [dispatch]);
|
||||||
|
|
||||||
|
const handleSearchClick = useCallback(() => {
|
||||||
|
setMode('add');
|
||||||
|
}, [setMode]);
|
||||||
|
|
||||||
|
const handleDismissSearchClick = useCallback(() => {
|
||||||
|
setMode('remove');
|
||||||
|
setIsSearching(false);
|
||||||
|
}, [setMode, setIsSearching]);
|
||||||
|
|
||||||
|
const searchRequestRef = useRef<AbortController | null>(null);
|
||||||
|
|
||||||
|
const handleSearch = useDebouncedCallback(
|
||||||
|
(value: string) => {
|
||||||
|
if (searchRequestRef.current) {
|
||||||
|
searchRequestRef.current.abort();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (value.trim().length === 0) {
|
||||||
|
setIsSearching(false);
|
||||||
|
setSearchAccountIds([]);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
setIsSearching(true);
|
||||||
|
setIsLoadingSearch(true);
|
||||||
|
|
||||||
|
searchRequestRef.current = new AbortController();
|
||||||
|
|
||||||
|
void apiRequest<ApiAccountJSON[]>('GET', 'v1/accounts/search', {
|
||||||
|
signal: searchRequestRef.current.signal,
|
||||||
|
params: {
|
||||||
|
q: value,
|
||||||
|
},
|
||||||
|
})
|
||||||
|
.then((data) => {
|
||||||
|
dispatch(importFetchedAccounts(data));
|
||||||
|
dispatch(fetchRelationships(data.map((a) => a.id)));
|
||||||
|
setSearchAccountIds(data.map((a) => a.id));
|
||||||
|
setIsLoadingSearch(false);
|
||||||
|
return '';
|
||||||
|
})
|
||||||
|
.catch(() => {
|
||||||
|
setIsLoadingSearch(false);
|
||||||
|
});
|
||||||
|
},
|
||||||
|
500,
|
||||||
|
{ leading: true, trailing: true },
|
||||||
|
);
|
||||||
|
|
||||||
|
let displayedAccountIds: string[];
|
||||||
|
|
||||||
|
if (mode === 'add' && isSearching) {
|
||||||
|
displayedAccountIds = searchAccountIds;
|
||||||
|
} else {
|
||||||
|
displayedAccountIds = suggestions.map(
|
||||||
|
(suggestion) => suggestion.account_id,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Column
|
||||||
|
bindToDocument={!multiColumn}
|
||||||
|
label={intl.formatMessage(messages.title)}
|
||||||
|
>
|
||||||
|
<ColumnHeader
|
||||||
|
title={intl.formatMessage(messages.title)}
|
||||||
|
icon='person'
|
||||||
|
iconComponent={PersonIcon}
|
||||||
|
multiColumn={multiColumn}
|
||||||
|
showBackButton
|
||||||
|
/>
|
||||||
|
|
||||||
|
<ColumnSearchHeader
|
||||||
|
placeholder={intl.formatMessage(messages.search)}
|
||||||
|
onBack={handleDismissSearchClick}
|
||||||
|
onActivate={handleSearchClick}
|
||||||
|
active={mode === 'add'}
|
||||||
|
onSubmit={handleSearch}
|
||||||
|
/>
|
||||||
|
|
||||||
|
<ScrollableList
|
||||||
|
scrollKey='follow_recommendations'
|
||||||
|
trackScroll={!multiColumn}
|
||||||
|
bindToDocument={!multiColumn}
|
||||||
|
showLoading={
|
||||||
|
(isLoading || isLoadingSearch) && displayedAccountIds.length === 0
|
||||||
|
}
|
||||||
|
hasMore={false}
|
||||||
|
isLoading={isLoading || isLoadingSearch}
|
||||||
|
footer={
|
||||||
|
<>
|
||||||
|
{displayedAccountIds.length > 0 && <div className='spacer' />}
|
||||||
|
|
||||||
|
<div className='column-footer'>
|
||||||
|
<Link className='button button--block' to='/home'>
|
||||||
|
<FormattedMessage
|
||||||
|
id='onboarding.follows.done'
|
||||||
|
defaultMessage='Done'
|
||||||
|
/>
|
||||||
|
</Link>
|
||||||
|
</div>
|
||||||
|
</>
|
||||||
|
}
|
||||||
|
emptyMessage={
|
||||||
|
mode === 'remove' ? (
|
||||||
|
<FormattedMessage
|
||||||
|
id='onboarding.follows.empty'
|
||||||
|
defaultMessage='Unfortunately, no results can be shown right now. You can try using search or browsing the explore page to find people to follow, or try again later.'
|
||||||
|
/>
|
||||||
|
) : (
|
||||||
|
<FormattedMessage
|
||||||
|
id='lists.no_results_found'
|
||||||
|
defaultMessage='No results found.'
|
||||||
|
/>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
>
|
||||||
|
{displayedAccountIds.map((accountId) => (
|
||||||
|
<Account
|
||||||
|
/* @ts-expect-error inferred props are wrong */
|
||||||
|
id={accountId}
|
||||||
|
key={accountId}
|
||||||
|
withBio={false}
|
||||||
|
/>
|
||||||
|
))}
|
||||||
|
</ScrollableList>
|
||||||
|
|
||||||
|
<Helmet>
|
||||||
|
<title>{intl.formatMessage(messages.title)}</title>
|
||||||
|
<meta name='robots' content='noindex' />
|
||||||
|
</Helmet>
|
||||||
|
</Column>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
// eslint-disable-next-line import/no-default-export
|
||||||
|
export default Follows;
|
@ -1,91 +0,0 @@
|
|||||||
import { useCallback } from 'react';
|
|
||||||
|
|
||||||
import { FormattedMessage, useIntl, defineMessages } from 'react-intl';
|
|
||||||
|
|
||||||
import { Helmet } from 'react-helmet';
|
|
||||||
import { Link, Switch, Route } from 'react-router-dom';
|
|
||||||
|
|
||||||
import { useDispatch } from 'react-redux';
|
|
||||||
|
|
||||||
|
|
||||||
import illustration from '@/images/elephant_ui_conversation.svg';
|
|
||||||
import AccountCircleIcon from '@/material-icons/400-24px/account_circle.svg?react';
|
|
||||||
import ArrowRightAltIcon from '@/material-icons/400-24px/arrow_right_alt.svg?react';
|
|
||||||
import ContentCopyIcon from '@/material-icons/400-24px/content_copy.svg?react';
|
|
||||||
import EditNoteIcon from '@/material-icons/400-24px/edit_note.svg?react';
|
|
||||||
import PersonAddIcon from '@/material-icons/400-24px/person_add.svg?react';
|
|
||||||
import { focusCompose } from 'mastodon/actions/compose';
|
|
||||||
import { Icon } from 'mastodon/components/icon';
|
|
||||||
import { NotSignedInIndicator } from 'mastodon/components/not_signed_in_indicator';
|
|
||||||
import Column from 'mastodon/features/ui/components/column';
|
|
||||||
import { me } from 'mastodon/initial_state';
|
|
||||||
import { useAppSelector } from 'mastodon/store';
|
|
||||||
import { assetHost } from 'mastodon/utils/config';
|
|
||||||
|
|
||||||
import { Step } from './components/step';
|
|
||||||
import { Follows } from './follows';
|
|
||||||
import { Profile } from './profile';
|
|
||||||
import { Share } from './share';
|
|
||||||
|
|
||||||
const messages = defineMessages({
|
|
||||||
template: { id: 'onboarding.compose.template', defaultMessage: 'Hello #Mastodon!' },
|
|
||||||
});
|
|
||||||
|
|
||||||
const Onboarding = () => {
|
|
||||||
const account = useAppSelector(state => state.getIn(['accounts', me]));
|
|
||||||
const dispatch = useDispatch();
|
|
||||||
const intl = useIntl();
|
|
||||||
|
|
||||||
const handleComposeClick = useCallback(() => {
|
|
||||||
dispatch(focusCompose(intl.formatMessage(messages.template)));
|
|
||||||
}, [dispatch, intl]);
|
|
||||||
|
|
||||||
return (
|
|
||||||
<Column>
|
|
||||||
{account ? (
|
|
||||||
<Switch>
|
|
||||||
<Route path='/start' exact>
|
|
||||||
<div className='scrollable privacy-policy'>
|
|
||||||
<div className='column-title'>
|
|
||||||
<img src={illustration} alt='' className='onboarding__illustration' />
|
|
||||||
<h3><FormattedMessage id='onboarding.start.title' defaultMessage="You've made it!" /></h3>
|
|
||||||
<p><FormattedMessage id='onboarding.start.lead' defaultMessage="Your new Mastodon account is ready to go. Here's how you can make the most of it:" /></p>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div className='onboarding__steps'>
|
|
||||||
<Step to='/start/profile' completed={(!account.get('avatar').endsWith('missing.png')) || (account.get('display_name').length > 0 && account.get('note').length > 0)} icon='address-book-o' iconComponent={AccountCircleIcon} label={<FormattedMessage id='onboarding.steps.setup_profile.title' defaultMessage='Customize your profile' />} description={<FormattedMessage id='onboarding.steps.setup_profile.body' defaultMessage='Others are more likely to interact with you with a filled out profile.' />} />
|
|
||||||
<Step to='/start/follows' completed={(account.get('following_count') * 1) >= 1} icon='user-plus' iconComponent={PersonAddIcon} label={<FormattedMessage id='onboarding.steps.follow_people.title' defaultMessage='Find at least {count, plural, one {one person} other {# people}} to follow' values={{ count: 7 }} />} description={<FormattedMessage id='onboarding.steps.follow_people.body' defaultMessage="You curate your own home feed. Let's fill it with interesting people." />} />
|
|
||||||
<Step onClick={handleComposeClick} completed={(account.get('statuses_count') * 1) >= 1} icon='pencil-square-o' iconComponent={EditNoteIcon} label={<FormattedMessage id='onboarding.steps.publish_status.title' defaultMessage='Make your first post' />} description={<FormattedMessage id='onboarding.steps.publish_status.body' defaultMessage='Say hello to the world.' values={{ emoji: <img className='emojione' alt='🐘' src={`${assetHost}/emoji/1f418.svg`} /> }} />} />
|
|
||||||
<Step to='/start/share' icon='copy' iconComponent={ContentCopyIcon} label={<FormattedMessage id='onboarding.steps.share_profile.title' defaultMessage='Share your profile' />} description={<FormattedMessage id='onboarding.steps.share_profile.body' defaultMessage='Let your friends know how to find you on Mastodon!' />} />
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<p className='onboarding__lead'><FormattedMessage id='onboarding.start.skip' defaultMessage="Don't need help getting started?" /></p>
|
|
||||||
|
|
||||||
<div className='onboarding__links'>
|
|
||||||
<Link to='/explore' className='onboarding__link'>
|
|
||||||
<FormattedMessage id='onboarding.actions.go_to_explore' defaultMessage='Take me to trending' />
|
|
||||||
<Icon icon={ArrowRightAltIcon} />
|
|
||||||
</Link>
|
|
||||||
|
|
||||||
<Link to='/home' className='onboarding__link'>
|
|
||||||
<FormattedMessage id='onboarding.actions.go_to_home' defaultMessage='Take me to my home feed' />
|
|
||||||
<Icon icon={ArrowRightAltIcon} />
|
|
||||||
</Link>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</Route>
|
|
||||||
|
|
||||||
<Route path='/start/profile' component={Profile} />
|
|
||||||
<Route path='/start/follows' component={Follows} />
|
|
||||||
<Route path='/start/share' component={Share} />
|
|
||||||
</Switch>
|
|
||||||
) : <NotSignedInIndicator />}
|
|
||||||
|
|
||||||
<Helmet>
|
|
||||||
<meta name='robots' content='noindex' />
|
|
||||||
</Helmet>
|
|
||||||
</Column>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
export default Onboarding;
|
|
@ -1,162 +0,0 @@
|
|||||||
import { useState, useMemo, useCallback, createRef } from 'react';
|
|
||||||
|
|
||||||
import { useIntl, defineMessages, FormattedMessage } from 'react-intl';
|
|
||||||
|
|
||||||
import classNames from 'classnames';
|
|
||||||
import { useHistory } from 'react-router-dom';
|
|
||||||
|
|
||||||
|
|
||||||
import { useDispatch } from 'react-redux';
|
|
||||||
|
|
||||||
import Toggle from 'react-toggle';
|
|
||||||
|
|
||||||
import AddPhotoAlternateIcon from '@/material-icons/400-24px/add_photo_alternate.svg?react';
|
|
||||||
import EditIcon from '@/material-icons/400-24px/edit.svg?react';
|
|
||||||
import { updateAccount } from 'mastodon/actions/accounts';
|
|
||||||
import { Button } from 'mastodon/components/button';
|
|
||||||
import { ColumnBackButton } from 'mastodon/components/column_back_button';
|
|
||||||
import { Icon } from 'mastodon/components/icon';
|
|
||||||
import { LoadingIndicator } from 'mastodon/components/loading_indicator';
|
|
||||||
import { me } from 'mastodon/initial_state';
|
|
||||||
import { useAppSelector } from 'mastodon/store';
|
|
||||||
import { unescapeHTML } from 'mastodon/utils/html';
|
|
||||||
|
|
||||||
const messages = defineMessages({
|
|
||||||
uploadHeader: { id: 'onboarding.profile.upload_header', defaultMessage: 'Upload profile header' },
|
|
||||||
uploadAvatar: { id: 'onboarding.profile.upload_avatar', defaultMessage: 'Upload profile picture' },
|
|
||||||
});
|
|
||||||
|
|
||||||
const nullIfMissing = path => path.endsWith('missing.png') ? null : path;
|
|
||||||
|
|
||||||
export const Profile = () => {
|
|
||||||
const account = useAppSelector(state => state.getIn(['accounts', me]));
|
|
||||||
const [displayName, setDisplayName] = useState(account.get('display_name'));
|
|
||||||
const [note, setNote] = useState(unescapeHTML(account.get('note')));
|
|
||||||
const [avatar, setAvatar] = useState(null);
|
|
||||||
const [header, setHeader] = useState(null);
|
|
||||||
const [discoverable, setDiscoverable] = useState(account.get('discoverable'));
|
|
||||||
const [isSaving, setIsSaving] = useState(false);
|
|
||||||
const [errors, setErrors] = useState();
|
|
||||||
const avatarFileRef = createRef();
|
|
||||||
const headerFileRef = createRef();
|
|
||||||
const dispatch = useDispatch();
|
|
||||||
const intl = useIntl();
|
|
||||||
const history = useHistory();
|
|
||||||
|
|
||||||
const handleDisplayNameChange = useCallback(e => {
|
|
||||||
setDisplayName(e.target.value);
|
|
||||||
}, [setDisplayName]);
|
|
||||||
|
|
||||||
const handleNoteChange = useCallback(e => {
|
|
||||||
setNote(e.target.value);
|
|
||||||
}, [setNote]);
|
|
||||||
|
|
||||||
const handleDiscoverableChange = useCallback(e => {
|
|
||||||
setDiscoverable(e.target.checked);
|
|
||||||
}, [setDiscoverable]);
|
|
||||||
|
|
||||||
const handleAvatarChange = useCallback(e => {
|
|
||||||
setAvatar(e.target?.files?.[0]);
|
|
||||||
}, [setAvatar]);
|
|
||||||
|
|
||||||
const handleHeaderChange = useCallback(e => {
|
|
||||||
setHeader(e.target?.files?.[0]);
|
|
||||||
}, [setHeader]);
|
|
||||||
|
|
||||||
const avatarPreview = useMemo(() => avatar ? URL.createObjectURL(avatar) : nullIfMissing(account.get('avatar')), [avatar, account]);
|
|
||||||
const headerPreview = useMemo(() => header ? URL.createObjectURL(header) : nullIfMissing(account.get('header')), [header, account]);
|
|
||||||
|
|
||||||
const handleSubmit = useCallback(() => {
|
|
||||||
setIsSaving(true);
|
|
||||||
|
|
||||||
dispatch(updateAccount({
|
|
||||||
displayName,
|
|
||||||
note,
|
|
||||||
avatar,
|
|
||||||
header,
|
|
||||||
discoverable,
|
|
||||||
indexable: discoverable,
|
|
||||||
})).then(() => history.push('/start/follows')).catch(err => {
|
|
||||||
setIsSaving(false);
|
|
||||||
setErrors(err.response.data.details);
|
|
||||||
});
|
|
||||||
}, [dispatch, displayName, note, avatar, header, discoverable, history]);
|
|
||||||
|
|
||||||
return (
|
|
||||||
<>
|
|
||||||
<ColumnBackButton />
|
|
||||||
|
|
||||||
<div className='scrollable privacy-policy'>
|
|
||||||
<div className='column-title'>
|
|
||||||
<h3><FormattedMessage id='onboarding.profile.title' defaultMessage='Profile setup' /></h3>
|
|
||||||
<p><FormattedMessage id='onboarding.profile.lead' defaultMessage='You can always complete this later in the settings, where even more customization options are available.' /></p>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div className='simple_form'>
|
|
||||||
<div className='onboarding__profile'>
|
|
||||||
<label className={classNames('app-form__header-input', { selected: !!headerPreview, invalid: !!errors?.header })} title={intl.formatMessage(messages.uploadHeader)}>
|
|
||||||
<input
|
|
||||||
type='file'
|
|
||||||
hidden
|
|
||||||
ref={headerFileRef}
|
|
||||||
accept='image/*'
|
|
||||||
onChange={handleHeaderChange}
|
|
||||||
/>
|
|
||||||
|
|
||||||
{headerPreview && <img src={headerPreview} alt='' />}
|
|
||||||
|
|
||||||
<Icon icon={headerPreview ? EditIcon : AddPhotoAlternateIcon} />
|
|
||||||
</label>
|
|
||||||
|
|
||||||
<label className={classNames('app-form__avatar-input', { selected: !!avatarPreview, invalid: !!errors?.avatar })} title={intl.formatMessage(messages.uploadAvatar)}>
|
|
||||||
<input
|
|
||||||
type='file'
|
|
||||||
hidden
|
|
||||||
ref={avatarFileRef}
|
|
||||||
accept='image/*'
|
|
||||||
onChange={handleAvatarChange}
|
|
||||||
/>
|
|
||||||
|
|
||||||
{avatarPreview && <img src={avatarPreview} alt='' />}
|
|
||||||
|
|
||||||
<Icon icon={avatarPreview ? EditIcon : AddPhotoAlternateIcon} />
|
|
||||||
</label>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div className={classNames('input with_block_label', { field_with_errors: !!errors?.display_name })}>
|
|
||||||
<label htmlFor='display_name'><FormattedMessage id='onboarding.profile.display_name' defaultMessage='Display name' /></label>
|
|
||||||
<span className='hint'><FormattedMessage id='onboarding.profile.display_name_hint' defaultMessage='Your full name or your fun name…' /></span>
|
|
||||||
<div className='label_input'>
|
|
||||||
<input id='display_name' type='text' value={displayName} onChange={handleDisplayNameChange} maxLength={30} />
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div className={classNames('input with_block_label', { field_with_errors: !!errors?.note })}>
|
|
||||||
<label htmlFor='note'><FormattedMessage id='onboarding.profile.note' defaultMessage='Bio' /></label>
|
|
||||||
<span className='hint'><FormattedMessage id='onboarding.profile.note_hint' defaultMessage='You can @mention other people or #hashtags…' /></span>
|
|
||||||
<div className='label_input'>
|
|
||||||
<textarea id='note' value={note} onChange={handleNoteChange} maxLength={500} />
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<label className='app-form__toggle'>
|
|
||||||
<div className='app-form__toggle__label'>
|
|
||||||
<strong><FormattedMessage id='onboarding.profile.discoverable' defaultMessage='Make my profile discoverable' /></strong> <span className='recommended'><FormattedMessage id='recommended' defaultMessage='Recommended' /></span>
|
|
||||||
<span className='hint'><FormattedMessage id='onboarding.profile.discoverable_hint' defaultMessage='When you opt in to discoverability on Mastodon, your posts may appear in search results and trending, and your profile may be suggested to people with similar interests to you.' /></span>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div className='app-form__toggle__toggle'>
|
|
||||||
<div>
|
|
||||||
<Toggle checked={discoverable} onChange={handleDiscoverableChange} />
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</label>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div className='onboarding__footer'>
|
|
||||||
<Button block onClick={handleSubmit} disabled={isSaving}>{isSaving ? <LoadingIndicator /> : <FormattedMessage id='onboarding.profile.save_and_continue' defaultMessage='Save and continue' />}</Button>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</>
|
|
||||||
);
|
|
||||||
};
|
|
329
app/javascript/mastodon/features/onboarding/profile.tsx
Normal file
329
app/javascript/mastodon/features/onboarding/profile.tsx
Normal file
@ -0,0 +1,329 @@
|
|||||||
|
import { useState, useMemo, useCallback, createRef } from 'react';
|
||||||
|
|
||||||
|
import { useIntl, defineMessages, FormattedMessage } from 'react-intl';
|
||||||
|
|
||||||
|
import classNames from 'classnames';
|
||||||
|
import { Helmet } from 'react-helmet';
|
||||||
|
import { useHistory } from 'react-router-dom';
|
||||||
|
|
||||||
|
import Toggle from 'react-toggle';
|
||||||
|
|
||||||
|
import AddPhotoAlternateIcon from '@/material-icons/400-24px/add_photo_alternate.svg?react';
|
||||||
|
import EditIcon from '@/material-icons/400-24px/edit.svg?react';
|
||||||
|
import PersonIcon from '@/material-icons/400-24px/person.svg?react';
|
||||||
|
import { updateAccount } from 'mastodon/actions/accounts';
|
||||||
|
import { Button } from 'mastodon/components/button';
|
||||||
|
import Column from 'mastodon/components/column';
|
||||||
|
import { ColumnHeader } from 'mastodon/components/column_header';
|
||||||
|
import { Icon } from 'mastodon/components/icon';
|
||||||
|
import { LoadingIndicator } from 'mastodon/components/loading_indicator';
|
||||||
|
import { me } from 'mastodon/initial_state';
|
||||||
|
import { useAppSelector, useAppDispatch } from 'mastodon/store';
|
||||||
|
import { unescapeHTML } from 'mastodon/utils/html';
|
||||||
|
|
||||||
|
const messages = defineMessages({
|
||||||
|
title: {
|
||||||
|
id: 'onboarding.profile.title',
|
||||||
|
defaultMessage: 'Profile setup',
|
||||||
|
},
|
||||||
|
uploadHeader: {
|
||||||
|
id: 'onboarding.profile.upload_header',
|
||||||
|
defaultMessage: 'Upload profile header',
|
||||||
|
},
|
||||||
|
uploadAvatar: {
|
||||||
|
id: 'onboarding.profile.upload_avatar',
|
||||||
|
defaultMessage: 'Upload profile picture',
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
const nullIfMissing = (path: string) =>
|
||||||
|
path.endsWith('missing.png') ? null : path;
|
||||||
|
|
||||||
|
interface ApiAccountErrors {
|
||||||
|
display_name?: unknown;
|
||||||
|
note?: unknown;
|
||||||
|
avatar?: unknown;
|
||||||
|
header?: unknown;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const Profile: React.FC<{
|
||||||
|
multiColumn?: boolean;
|
||||||
|
}> = ({ multiColumn }) => {
|
||||||
|
const account = useAppSelector((state) =>
|
||||||
|
me ? state.accounts.get(me) : undefined,
|
||||||
|
);
|
||||||
|
const [displayName, setDisplayName] = useState(account?.display_name ?? '');
|
||||||
|
const [note, setNote] = useState(
|
||||||
|
account ? (unescapeHTML(account.note) ?? '') : '',
|
||||||
|
);
|
||||||
|
const [avatar, setAvatar] = useState<File>();
|
||||||
|
const [header, setHeader] = useState<File>();
|
||||||
|
const [discoverable, setDiscoverable] = useState(true);
|
||||||
|
const [isSaving, setIsSaving] = useState(false);
|
||||||
|
const [errors, setErrors] = useState<ApiAccountErrors>();
|
||||||
|
const avatarFileRef = createRef<HTMLInputElement>();
|
||||||
|
const headerFileRef = createRef<HTMLInputElement>();
|
||||||
|
const dispatch = useAppDispatch();
|
||||||
|
const intl = useIntl();
|
||||||
|
const history = useHistory();
|
||||||
|
|
||||||
|
const handleDisplayNameChange = useCallback(
|
||||||
|
(e: React.ChangeEvent<HTMLInputElement>) => {
|
||||||
|
setDisplayName(e.target.value);
|
||||||
|
},
|
||||||
|
[setDisplayName],
|
||||||
|
);
|
||||||
|
|
||||||
|
const handleNoteChange = useCallback(
|
||||||
|
(e: React.ChangeEvent<HTMLTextAreaElement>) => {
|
||||||
|
setNote(e.target.value);
|
||||||
|
},
|
||||||
|
[setNote],
|
||||||
|
);
|
||||||
|
|
||||||
|
const handleDiscoverableChange = useCallback(
|
||||||
|
(e: React.ChangeEvent<HTMLInputElement>) => {
|
||||||
|
setDiscoverable(e.target.checked);
|
||||||
|
},
|
||||||
|
[setDiscoverable],
|
||||||
|
);
|
||||||
|
|
||||||
|
const handleAvatarChange = useCallback(
|
||||||
|
(e: React.ChangeEvent<HTMLInputElement>) => {
|
||||||
|
setAvatar(e.target.files?.[0]);
|
||||||
|
},
|
||||||
|
[setAvatar],
|
||||||
|
);
|
||||||
|
|
||||||
|
const handleHeaderChange = useCallback(
|
||||||
|
(e: React.ChangeEvent<HTMLInputElement>) => {
|
||||||
|
setHeader(e.target.files?.[0]);
|
||||||
|
},
|
||||||
|
[setHeader],
|
||||||
|
);
|
||||||
|
|
||||||
|
const avatarPreview = useMemo(
|
||||||
|
() =>
|
||||||
|
avatar
|
||||||
|
? URL.createObjectURL(avatar)
|
||||||
|
: nullIfMissing(account?.avatar ?? 'missing.png'),
|
||||||
|
[avatar, account],
|
||||||
|
);
|
||||||
|
const headerPreview = useMemo(
|
||||||
|
() =>
|
||||||
|
header
|
||||||
|
? URL.createObjectURL(header)
|
||||||
|
: nullIfMissing(account?.header ?? 'missing.png'),
|
||||||
|
[header, account],
|
||||||
|
);
|
||||||
|
|
||||||
|
const handleSubmit = useCallback(() => {
|
||||||
|
setIsSaving(true);
|
||||||
|
|
||||||
|
dispatch(
|
||||||
|
updateAccount({
|
||||||
|
displayName,
|
||||||
|
note,
|
||||||
|
avatar,
|
||||||
|
header,
|
||||||
|
discoverable,
|
||||||
|
indexable: discoverable,
|
||||||
|
}),
|
||||||
|
)
|
||||||
|
.then(() => {
|
||||||
|
history.push('/start/follows');
|
||||||
|
return '';
|
||||||
|
})
|
||||||
|
// eslint-disable-next-line @typescript-eslint/use-unknown-in-catch-callback-variable
|
||||||
|
.catch((err) => {
|
||||||
|
// eslint-disable-next-line @typescript-eslint/no-unsafe-member-access
|
||||||
|
if (err.response) {
|
||||||
|
// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment, @typescript-eslint/no-unsafe-member-access
|
||||||
|
const { details }: { details: ApiAccountErrors } = err.response.data;
|
||||||
|
setErrors(details);
|
||||||
|
}
|
||||||
|
|
||||||
|
setIsSaving(false);
|
||||||
|
});
|
||||||
|
}, [dispatch, displayName, note, avatar, header, discoverable, history]);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Column
|
||||||
|
bindToDocument={!multiColumn}
|
||||||
|
label={intl.formatMessage(messages.title)}
|
||||||
|
>
|
||||||
|
<ColumnHeader
|
||||||
|
title={intl.formatMessage(messages.title)}
|
||||||
|
icon='person'
|
||||||
|
iconComponent={PersonIcon}
|
||||||
|
multiColumn={multiColumn}
|
||||||
|
/>
|
||||||
|
|
||||||
|
<div className='scrollable scrollable--flex'>
|
||||||
|
<div className='simple_form app-form'>
|
||||||
|
<div className='onboarding__profile'>
|
||||||
|
<label
|
||||||
|
className={classNames('app-form__header-input', {
|
||||||
|
selected: !!headerPreview,
|
||||||
|
invalid: !!errors?.header,
|
||||||
|
})}
|
||||||
|
title={intl.formatMessage(messages.uploadHeader)}
|
||||||
|
>
|
||||||
|
<input
|
||||||
|
type='file'
|
||||||
|
hidden
|
||||||
|
ref={headerFileRef}
|
||||||
|
accept='image/*'
|
||||||
|
onChange={handleHeaderChange}
|
||||||
|
/>
|
||||||
|
|
||||||
|
{headerPreview && <img src={headerPreview} alt='' />}
|
||||||
|
|
||||||
|
<Icon
|
||||||
|
id=''
|
||||||
|
icon={headerPreview ? EditIcon : AddPhotoAlternateIcon}
|
||||||
|
/>
|
||||||
|
</label>
|
||||||
|
|
||||||
|
<label
|
||||||
|
className={classNames('app-form__avatar-input', {
|
||||||
|
selected: !!avatarPreview,
|
||||||
|
invalid: !!errors?.avatar,
|
||||||
|
})}
|
||||||
|
title={intl.formatMessage(messages.uploadAvatar)}
|
||||||
|
>
|
||||||
|
<input
|
||||||
|
type='file'
|
||||||
|
hidden
|
||||||
|
ref={avatarFileRef}
|
||||||
|
accept='image/*'
|
||||||
|
onChange={handleAvatarChange}
|
||||||
|
/>
|
||||||
|
|
||||||
|
{avatarPreview && <img src={avatarPreview} alt='' />}
|
||||||
|
|
||||||
|
<Icon
|
||||||
|
id=''
|
||||||
|
icon={avatarPreview ? EditIcon : AddPhotoAlternateIcon}
|
||||||
|
/>
|
||||||
|
</label>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className='fields-group'>
|
||||||
|
<div
|
||||||
|
className={classNames('input with_block_label', {
|
||||||
|
field_with_errors: !!errors?.display_name,
|
||||||
|
})}
|
||||||
|
>
|
||||||
|
<label htmlFor='display_name'>
|
||||||
|
<FormattedMessage
|
||||||
|
id='onboarding.profile.display_name'
|
||||||
|
defaultMessage='Display name'
|
||||||
|
/>
|
||||||
|
</label>
|
||||||
|
<span className='hint'>
|
||||||
|
<FormattedMessage
|
||||||
|
id='onboarding.profile.display_name_hint'
|
||||||
|
defaultMessage='Your full name or your fun name…'
|
||||||
|
/>
|
||||||
|
</span>
|
||||||
|
<div className='label_input'>
|
||||||
|
<input
|
||||||
|
id='display_name'
|
||||||
|
type='text'
|
||||||
|
value={displayName}
|
||||||
|
onChange={handleDisplayNameChange}
|
||||||
|
maxLength={30}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className='fields-group'>
|
||||||
|
<div
|
||||||
|
className={classNames('input with_block_label', {
|
||||||
|
field_with_errors: !!errors?.note,
|
||||||
|
})}
|
||||||
|
>
|
||||||
|
<label htmlFor='note'>
|
||||||
|
<FormattedMessage
|
||||||
|
id='onboarding.profile.note'
|
||||||
|
defaultMessage='Bio'
|
||||||
|
/>
|
||||||
|
</label>
|
||||||
|
<span className='hint'>
|
||||||
|
<FormattedMessage
|
||||||
|
id='onboarding.profile.note_hint'
|
||||||
|
defaultMessage='You can @mention other people or #hashtags…'
|
||||||
|
/>
|
||||||
|
</span>
|
||||||
|
<div className='label_input'>
|
||||||
|
<textarea
|
||||||
|
id='note'
|
||||||
|
value={note}
|
||||||
|
onChange={handleNoteChange}
|
||||||
|
maxLength={500}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<label className='app-form__toggle'>
|
||||||
|
<div className='app-form__toggle__label'>
|
||||||
|
<strong>
|
||||||
|
<FormattedMessage
|
||||||
|
id='onboarding.profile.discoverable'
|
||||||
|
defaultMessage='Make my profile discoverable'
|
||||||
|
/>
|
||||||
|
</strong>{' '}
|
||||||
|
<span className='recommended'>
|
||||||
|
<FormattedMessage
|
||||||
|
id='recommended'
|
||||||
|
defaultMessage='Recommended'
|
||||||
|
/>
|
||||||
|
</span>
|
||||||
|
<span className='hint'>
|
||||||
|
<FormattedMessage
|
||||||
|
id='onboarding.profile.discoverable_hint'
|
||||||
|
defaultMessage='When you opt in to discoverability on Mastodon, your posts may appear in search results and trending, and your profile may be suggested to people with similar interests to you.'
|
||||||
|
/>
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className='app-form__toggle__toggle'>
|
||||||
|
<div>
|
||||||
|
<Toggle
|
||||||
|
checked={discoverable}
|
||||||
|
onChange={handleDiscoverableChange}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</label>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className='spacer' />
|
||||||
|
|
||||||
|
<div className='column-footer'>
|
||||||
|
<Button block onClick={handleSubmit} disabled={isSaving}>
|
||||||
|
{isSaving ? (
|
||||||
|
<LoadingIndicator />
|
||||||
|
) : (
|
||||||
|
<FormattedMessage
|
||||||
|
id='onboarding.profile.save_and_continue'
|
||||||
|
defaultMessage='Save and continue'
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<Helmet>
|
||||||
|
<title>{intl.formatMessage(messages.title)}</title>
|
||||||
|
<meta name='robots' content='noindex' />
|
||||||
|
</Helmet>
|
||||||
|
</Column>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
// eslint-disable-next-line import/no-default-export
|
||||||
|
export default Profile;
|
@ -1,120 +0,0 @@
|
|||||||
import PropTypes from 'prop-types';
|
|
||||||
import { PureComponent } from 'react';
|
|
||||||
|
|
||||||
import { defineMessages, useIntl, FormattedMessage } from 'react-intl';
|
|
||||||
|
|
||||||
import classNames from 'classnames';
|
|
||||||
import { Link } from 'react-router-dom';
|
|
||||||
|
|
||||||
|
|
||||||
import SwipeableViews from 'react-swipeable-views';
|
|
||||||
|
|
||||||
import ArrowRightAltIcon from '@/material-icons/400-24px/arrow_right_alt.svg?react';
|
|
||||||
import { ColumnBackButton } from 'mastodon/components/column_back_button';
|
|
||||||
import { CopyPasteText } from 'mastodon/components/copy_paste_text';
|
|
||||||
import { Icon } from 'mastodon/components/icon';
|
|
||||||
import { me, domain } from 'mastodon/initial_state';
|
|
||||||
import { useAppSelector } from 'mastodon/store';
|
|
||||||
|
|
||||||
const messages = defineMessages({
|
|
||||||
shareableMessage: { id: 'onboarding.share.message', defaultMessage: 'I\'m {username} on #Mastodon! Come follow me at {url}' },
|
|
||||||
});
|
|
||||||
|
|
||||||
class TipCarousel extends PureComponent {
|
|
||||||
|
|
||||||
static propTypes = {
|
|
||||||
children: PropTypes.node,
|
|
||||||
};
|
|
||||||
|
|
||||||
state = {
|
|
||||||
index: 0,
|
|
||||||
};
|
|
||||||
|
|
||||||
handleSwipe = index => {
|
|
||||||
this.setState({ index });
|
|
||||||
};
|
|
||||||
|
|
||||||
handleChangeIndex = e => {
|
|
||||||
this.setState({ index: Number(e.currentTarget.getAttribute('data-index')) });
|
|
||||||
};
|
|
||||||
|
|
||||||
handleKeyDown = e => {
|
|
||||||
switch(e.key) {
|
|
||||||
case 'ArrowLeft':
|
|
||||||
e.preventDefault();
|
|
||||||
this.setState(({ index }, { children }) => ({ index: Math.abs(index - 1) % children.length }));
|
|
||||||
break;
|
|
||||||
case 'ArrowRight':
|
|
||||||
e.preventDefault();
|
|
||||||
this.setState(({ index }, { children }) => ({ index: (index + 1) % children.length }));
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
render () {
|
|
||||||
const { children } = this.props;
|
|
||||||
const { index } = this.state;
|
|
||||||
|
|
||||||
return (
|
|
||||||
<div className='tip-carousel' tabIndex='0' onKeyDown={this.handleKeyDown}>
|
|
||||||
<SwipeableViews onChangeIndex={this.handleSwipe} index={index} enableMouseEvents tabIndex='-1'>
|
|
||||||
{children}
|
|
||||||
</SwipeableViews>
|
|
||||||
|
|
||||||
<div className='media-modal__pagination'>
|
|
||||||
{children.map((_, i) => (
|
|
||||||
<button key={i} className={classNames('media-modal__page-dot', { active: i === index })} data-index={i} onClick={this.handleChangeIndex}>
|
|
||||||
{i + 1}
|
|
||||||
</button>
|
|
||||||
))}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
export const Share = () => {
|
|
||||||
const account = useAppSelector(state => state.getIn(['accounts', me]));
|
|
||||||
const intl = useIntl();
|
|
||||||
const url = (new URL(`/@${account.get('username')}`, document.baseURI)).href;
|
|
||||||
|
|
||||||
return (
|
|
||||||
<>
|
|
||||||
<ColumnBackButton />
|
|
||||||
|
|
||||||
<div className='scrollable privacy-policy'>
|
|
||||||
<div className='column-title'>
|
|
||||||
<h3><FormattedMessage id='onboarding.share.title' defaultMessage='Share your profile' /></h3>
|
|
||||||
<p><FormattedMessage id='onboarding.share.lead' defaultMessage='Let people know how they can find you on Mastodon!' /></p>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<CopyPasteText value={intl.formatMessage(messages.shareableMessage, { username: `@${account.get('username')}@${domain}`, url })} />
|
|
||||||
|
|
||||||
<TipCarousel>
|
|
||||||
<div><p className='onboarding__lead'><FormattedMessage id='onboarding.tips.verification' defaultMessage='<strong>Did you know?</strong> You can verify your account by putting a link to your Mastodon profile on your own website and adding the website to your profile. No fees or documents necessary!' values={{ strong: chunks => <strong>{chunks}</strong> }} /></p></div>
|
|
||||||
<div><p className='onboarding__lead'><FormattedMessage id='onboarding.tips.migration' defaultMessage='<strong>Did you know?</strong> If you feel like {domain} is not a great server choice for you in the future, you can move to another Mastodon server without losing your followers. You can even host your own server!' values={{ domain, strong: chunks => <strong>{chunks}</strong> }} /></p></div>
|
|
||||||
<div><p className='onboarding__lead'><FormattedMessage id='onboarding.tips.2fa' defaultMessage='<strong>Did you know?</strong> You can secure your account by setting up two-factor authentication in your account settings. It works with any TOTP app of your choice, no phone number necessary!' values={{ strong: chunks => <strong>{chunks}</strong> }} /></p></div>
|
|
||||||
</TipCarousel>
|
|
||||||
|
|
||||||
<p className='onboarding__lead'><FormattedMessage id='onboarding.share.next_steps' defaultMessage='Possible next steps:' /></p>
|
|
||||||
|
|
||||||
<div className='onboarding__links'>
|
|
||||||
<Link to='/home' className='onboarding__link'>
|
|
||||||
<FormattedMessage id='onboarding.actions.go_to_home' defaultMessage='Take me to my home feed' />
|
|
||||||
<Icon icon={ArrowRightAltIcon} />
|
|
||||||
</Link>
|
|
||||||
|
|
||||||
<Link to='/explore' className='onboarding__link'>
|
|
||||||
<FormattedMessage id='onboarding.actions.go_to_explore' defaultMessage='Take me to trending' />
|
|
||||||
<Icon icon={ArrowRightAltIcon} />
|
|
||||||
</Link>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div className='onboarding__footer'>
|
|
||||||
<Link className='link-button' to='/start'><FormattedMessage id='onboarding.action.back' defaultMessage='Take me back' /></Link>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</>
|
|
||||||
);
|
|
||||||
};
|
|
@ -8,7 +8,7 @@ import { FormattedMessage } from 'react-intl';
|
|||||||
import classNames from 'classnames';
|
import classNames from 'classnames';
|
||||||
|
|
||||||
|
|
||||||
import Immutable from 'immutable';
|
import { is } from 'immutable';
|
||||||
import ImmutablePropTypes from 'react-immutable-proptypes';
|
import ImmutablePropTypes from 'react-immutable-proptypes';
|
||||||
|
|
||||||
import DescriptionIcon from '@/material-icons/400-24px/description-fill.svg?react';
|
import DescriptionIcon from '@/material-icons/400-24px/description-fill.svg?react';
|
||||||
@ -73,7 +73,7 @@ export default class Card extends PureComponent {
|
|||||||
};
|
};
|
||||||
|
|
||||||
UNSAFE_componentWillReceiveProps (nextProps) {
|
UNSAFE_componentWillReceiveProps (nextProps) {
|
||||||
if (!Immutable.is(this.props.card, nextProps.card)) {
|
if (!is(this.props.card, nextProps.card)) {
|
||||||
this.setState({ embedded: false, previewLoaded: false });
|
this.setState({ embedded: false, previewLoaded: false });
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -7,7 +7,7 @@ import { Helmet } from 'react-helmet';
|
|||||||
import { withRouter } from 'react-router-dom';
|
import { withRouter } from 'react-router-dom';
|
||||||
|
|
||||||
import { createSelector } from '@reduxjs/toolkit';
|
import { createSelector } from '@reduxjs/toolkit';
|
||||||
import Immutable from 'immutable';
|
import { List as ImmutableList } from 'immutable';
|
||||||
import ImmutablePropTypes from 'react-immutable-proptypes';
|
import ImmutablePropTypes from 'react-immutable-proptypes';
|
||||||
import ImmutablePureComponent from 'react-immutable-pure-component';
|
import ImmutablePureComponent from 'react-immutable-pure-component';
|
||||||
import { connect } from 'react-redux';
|
import { connect } from 'react-redux';
|
||||||
@ -87,7 +87,7 @@ const makeMapStateToProps = () => {
|
|||||||
(_, { id }) => id,
|
(_, { id }) => id,
|
||||||
state => state.getIn(['contexts', 'inReplyTos']),
|
state => state.getIn(['contexts', 'inReplyTos']),
|
||||||
], (statusId, inReplyTos) => {
|
], (statusId, inReplyTos) => {
|
||||||
let ancestorsIds = Immutable.List();
|
let ancestorsIds = ImmutableList();
|
||||||
ancestorsIds = ancestorsIds.withMutations(mutable => {
|
ancestorsIds = ancestorsIds.withMutations(mutable => {
|
||||||
let id = statusId;
|
let id = statusId;
|
||||||
|
|
||||||
@ -134,14 +134,14 @@ const makeMapStateToProps = () => {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
return Immutable.List(descendantsIds);
|
return ImmutableList(descendantsIds);
|
||||||
});
|
});
|
||||||
|
|
||||||
const mapStateToProps = (state, props) => {
|
const mapStateToProps = (state, props) => {
|
||||||
const status = getStatus(state, { id: props.params.statusId });
|
const status = getStatus(state, { id: props.params.statusId });
|
||||||
|
|
||||||
let ancestorsIds = Immutable.List();
|
let ancestorsIds = ImmutableList();
|
||||||
let descendantsIds = Immutable.List();
|
let descendantsIds = ImmutableList();
|
||||||
|
|
||||||
if (status) {
|
if (status) {
|
||||||
ancestorsIds = getAncestorsIds(state, { id: status.get('in_reply_to_id') });
|
ancestorsIds = getAncestorsIds(state, { id: status.get('in_reply_to_id') });
|
||||||
|
@ -23,7 +23,7 @@ const getAccountLanguages = createSelector([
|
|||||||
(state, accountId) => state.getIn(['timelines', `account:${accountId}`, 'items'], ImmutableList()),
|
(state, accountId) => state.getIn(['timelines', `account:${accountId}`, 'items'], ImmutableList()),
|
||||||
state => state.get('statuses'),
|
state => state.get('statuses'),
|
||||||
], (statusIds, statuses) =>
|
], (statusIds, statuses) =>
|
||||||
new ImmutableSet(statusIds.map(statusId => statuses.get(statusId)).filter(status => !status.get('reblog')).map(status => status.get('language'))));
|
ImmutableSet(statusIds.map(statusId => statuses.get(statusId)).filter(status => !status.get('reblog')).map(status => status.get('language'))));
|
||||||
|
|
||||||
const mapStateToProps = (state, { accountId }) => ({
|
const mapStateToProps = (state, { accountId }) => ({
|
||||||
acct: state.getIn(['accounts', accountId, 'acct']),
|
acct: state.getIn(['accounts', accountId, 'acct']),
|
||||||
|
@ -8,7 +8,7 @@ import { scrollRight } from '../../../scroll';
|
|||||||
import BundleContainer from '../containers/bundle_container';
|
import BundleContainer from '../containers/bundle_container';
|
||||||
import {
|
import {
|
||||||
Compose,
|
Compose,
|
||||||
NotificationsWrapper,
|
Notifications,
|
||||||
HomeTimeline,
|
HomeTimeline,
|
||||||
CommunityTimeline,
|
CommunityTimeline,
|
||||||
PublicTimeline,
|
PublicTimeline,
|
||||||
@ -30,7 +30,7 @@ import NavigationPanel from './navigation_panel';
|
|||||||
const componentMap = {
|
const componentMap = {
|
||||||
'COMPOSE': Compose,
|
'COMPOSE': Compose,
|
||||||
'HOME': HomeTimeline,
|
'HOME': HomeTimeline,
|
||||||
'NOTIFICATIONS': NotificationsWrapper,
|
'NOTIFICATIONS': Notifications,
|
||||||
'PUBLIC': PublicTimeline,
|
'PUBLIC': PublicTimeline,
|
||||||
'REMOTE': PublicTimeline,
|
'REMOTE': PublicTimeline,
|
||||||
'COMMUNITY': CommunityTimeline,
|
'COMMUNITY': CommunityTimeline,
|
||||||
|
@ -10,7 +10,6 @@ import {
|
|||||||
DomainBlockModal,
|
DomainBlockModal,
|
||||||
ReportModal,
|
ReportModal,
|
||||||
EmbedModal,
|
EmbedModal,
|
||||||
ListEditor,
|
|
||||||
ListAdder,
|
ListAdder,
|
||||||
CompareHistoryModal,
|
CompareHistoryModal,
|
||||||
FilterModal,
|
FilterModal,
|
||||||
@ -64,7 +63,6 @@ export const MODAL_COMPONENTS = {
|
|||||||
'REPORT': ReportModal,
|
'REPORT': ReportModal,
|
||||||
'ACTIONS': () => Promise.resolve({ default: ActionsModal }),
|
'ACTIONS': () => Promise.resolve({ default: ActionsModal }),
|
||||||
'EMBED': EmbedModal,
|
'EMBED': EmbedModal,
|
||||||
'LIST_EDITOR': ListEditor,
|
|
||||||
'FOCAL_POINT': () => Promise.resolve({ default: FocalPointModal }),
|
'FOCAL_POINT': () => Promise.resolve({ default: FocalPointModal }),
|
||||||
'LIST_ADDER': ListAdder,
|
'LIST_ADDER': ListAdder,
|
||||||
'COMPARE_HISTORY': CompareHistoryModal,
|
'COMPARE_HISTORY': CompareHistoryModal,
|
||||||
|
@ -49,7 +49,7 @@ import {
|
|||||||
Favourites,
|
Favourites,
|
||||||
DirectTimeline,
|
DirectTimeline,
|
||||||
HashtagTimeline,
|
HashtagTimeline,
|
||||||
NotificationsWrapper,
|
Notifications,
|
||||||
NotificationRequests,
|
NotificationRequests,
|
||||||
NotificationRequest,
|
NotificationRequest,
|
||||||
FollowRequests,
|
FollowRequests,
|
||||||
@ -58,14 +58,17 @@ import {
|
|||||||
FollowedTags,
|
FollowedTags,
|
||||||
LinkTimeline,
|
LinkTimeline,
|
||||||
ListTimeline,
|
ListTimeline,
|
||||||
|
Lists,
|
||||||
|
ListEdit,
|
||||||
|
ListMembers,
|
||||||
Blocks,
|
Blocks,
|
||||||
DomainBlocks,
|
DomainBlocks,
|
||||||
Mutes,
|
Mutes,
|
||||||
PinnedStatuses,
|
PinnedStatuses,
|
||||||
Lists,
|
|
||||||
Directory,
|
Directory,
|
||||||
|
OnboardingProfile,
|
||||||
|
OnboardingFollows,
|
||||||
Explore,
|
Explore,
|
||||||
Onboarding,
|
|
||||||
About,
|
About,
|
||||||
PrivacyPolicy,
|
PrivacyPolicy,
|
||||||
} from './util/async-components';
|
} from './util/async-components';
|
||||||
@ -205,8 +208,11 @@ class SwitchingColumnsArea extends PureComponent {
|
|||||||
<WrappedRoute path={['/conversations', '/timelines/direct']} component={DirectTimeline} content={children} />
|
<WrappedRoute path={['/conversations', '/timelines/direct']} component={DirectTimeline} content={children} />
|
||||||
<WrappedRoute path='/tags/:id' component={HashtagTimeline} content={children} />
|
<WrappedRoute path='/tags/:id' component={HashtagTimeline} content={children} />
|
||||||
<WrappedRoute path='/links/:url' component={LinkTimeline} content={children} />
|
<WrappedRoute path='/links/:url' component={LinkTimeline} content={children} />
|
||||||
|
<WrappedRoute path='/lists/new' component={ListEdit} content={children} />
|
||||||
|
<WrappedRoute path='/lists/:id/edit' component={ListEdit} content={children} />
|
||||||
|
<WrappedRoute path='/lists/:id/members' component={ListMembers} content={children} />
|
||||||
<WrappedRoute path='/lists/:id' component={ListTimeline} content={children} />
|
<WrappedRoute path='/lists/:id' component={ListTimeline} content={children} />
|
||||||
<WrappedRoute path='/notifications' component={NotificationsWrapper} content={children} exact />
|
<WrappedRoute path='/notifications' component={Notifications} content={children} exact />
|
||||||
<WrappedRoute path='/notifications/requests' component={NotificationRequests} content={children} exact />
|
<WrappedRoute path='/notifications/requests' component={NotificationRequests} content={children} exact />
|
||||||
<WrappedRoute path='/notifications/requests/:id' component={NotificationRequest} content={children} exact />
|
<WrappedRoute path='/notifications/requests/:id' component={NotificationRequest} content={children} exact />
|
||||||
<WrappedRoute path='/favourites' component={FavouritedStatuses} content={children} />
|
<WrappedRoute path='/favourites' component={FavouritedStatuses} content={children} />
|
||||||
@ -214,7 +220,8 @@ class SwitchingColumnsArea extends PureComponent {
|
|||||||
<WrappedRoute path='/bookmarks' component={BookmarkedStatuses} content={children} />
|
<WrappedRoute path='/bookmarks' component={BookmarkedStatuses} content={children} />
|
||||||
<WrappedRoute path='/pinned' component={PinnedStatuses} content={children} />
|
<WrappedRoute path='/pinned' component={PinnedStatuses} content={children} />
|
||||||
|
|
||||||
<WrappedRoute path='/start' component={Onboarding} content={children} />
|
<WrappedRoute path={['/start', '/start/profile']} exact component={OnboardingProfile} content={children} />
|
||||||
|
<WrappedRoute path='/start/follows' component={OnboardingFollows} content={children} />
|
||||||
<WrappedRoute path='/directory' component={Directory} content={children} />
|
<WrappedRoute path='/directory' component={Directory} content={children} />
|
||||||
<WrappedRoute path={['/explore', '/search']} component={Explore} content={children} />
|
<WrappedRoute path={['/explore', '/search']} component={Explore} content={children} />
|
||||||
<WrappedRoute path={['/publish', '/statuses/new']} component={Compose} content={children} />
|
<WrappedRoute path={['/publish', '/statuses/new']} component={Compose} content={children} />
|
||||||
|
@ -7,15 +7,7 @@ export function Compose () {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export function Notifications () {
|
export function Notifications () {
|
||||||
return import(/* webpackChunkName: "features/notifications_v1" */'../../notifications');
|
return import(/* webpackChunkName: "features/notifications" */'../../notifications_v2');
|
||||||
}
|
|
||||||
|
|
||||||
export function Notifications_v2 () {
|
|
||||||
return import(/* webpackChunkName: "features/notifications_v2" */'../../notifications_v2');
|
|
||||||
}
|
|
||||||
|
|
||||||
export function NotificationsWrapper () {
|
|
||||||
return import(/* webpackChunkName: "features/notifications" */'../../notifications_wrapper');
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export function HomeTimeline () {
|
export function HomeTimeline () {
|
||||||
@ -150,10 +142,6 @@ export function EmbedModal () {
|
|||||||
return import(/* webpackChunkName: "modals/embed_modal" */'../components/embed_modal');
|
return import(/* webpackChunkName: "modals/embed_modal" */'../components/embed_modal');
|
||||||
}
|
}
|
||||||
|
|
||||||
export function ListEditor () {
|
|
||||||
return import(/* webpackChunkName: "features/list_editor" */'../../list_editor');
|
|
||||||
}
|
|
||||||
|
|
||||||
export function ListAdder () {
|
export function ListAdder () {
|
||||||
return import(/*webpackChunkName: "features/list_adder" */'../../list_adder');
|
return import(/*webpackChunkName: "features/list_adder" */'../../list_adder');
|
||||||
}
|
}
|
||||||
@ -170,8 +158,12 @@ export function Directory () {
|
|||||||
return import(/* webpackChunkName: "features/directory" */'../../directory');
|
return import(/* webpackChunkName: "features/directory" */'../../directory');
|
||||||
}
|
}
|
||||||
|
|
||||||
export function Onboarding () {
|
export function OnboardingProfile () {
|
||||||
return import(/* webpackChunkName: "features/onboarding" */'../../onboarding');
|
return import(/* webpackChunkName: "features/onboarding" */'../../onboarding/profile');
|
||||||
|
}
|
||||||
|
|
||||||
|
export function OnboardingFollows () {
|
||||||
|
return import(/* webpackChunkName: "features/onboarding" */'../../onboarding/follows');
|
||||||
}
|
}
|
||||||
|
|
||||||
export function CompareHistoryModal () {
|
export function CompareHistoryModal () {
|
||||||
@ -221,3 +213,11 @@ export function LinkTimeline () {
|
|||||||
export function AnnualReportModal () {
|
export function AnnualReportModal () {
|
||||||
return import(/*webpackChunkName: "modals/annual_report_modal" */'../components/annual_report_modal');
|
return import(/*webpackChunkName: "modals/annual_report_modal" */'../components/annual_report_modal');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function ListEdit () {
|
||||||
|
return import(/*webpackChunkName: "features/lists" */'../../lists/new');
|
||||||
|
}
|
||||||
|
|
||||||
|
export function ListMembers () {
|
||||||
|
return import(/* webpackChunkName: "features/lists" */'../../lists/members');
|
||||||
|
}
|
||||||
|
@ -154,7 +154,6 @@
|
|||||||
"empty_column.hashtag": "Daar is nog niks vir hierdie hutsetiket nie.",
|
"empty_column.hashtag": "Daar is nog niks vir hierdie hutsetiket nie.",
|
||||||
"empty_column.home": "Your home timeline is empty! Follow more people to fill it up. {suggestions}",
|
"empty_column.home": "Your home timeline is empty! Follow more people to fill it up. {suggestions}",
|
||||||
"empty_column.list": "Hierdie lys is nog leeg. Nuwe plasings deur lyslede sal voortaan hier verskyn.",
|
"empty_column.list": "Hierdie lys is nog leeg. Nuwe plasings deur lyslede sal voortaan hier verskyn.",
|
||||||
"empty_column.lists": "Jy het nog geen lyste nie. Wanneer jy een skep, sal dit hier vertoon.",
|
|
||||||
"empty_column.notifications": "Jy het nog geen kennisgewings nie. Interaksie van ander mense met jou, sal hier vertoon.",
|
"empty_column.notifications": "Jy het nog geen kennisgewings nie. Interaksie van ander mense met jou, sal hier vertoon.",
|
||||||
"explore.search_results": "Soekresultate",
|
"explore.search_results": "Soekresultate",
|
||||||
"explore.suggested_follows": "Mense",
|
"explore.suggested_follows": "Mense",
|
||||||
@ -222,15 +221,8 @@
|
|||||||
"limited_account_hint.action": "Vertoon profiel in elk geval",
|
"limited_account_hint.action": "Vertoon profiel in elk geval",
|
||||||
"limited_account_hint.title": "Hierdie profiel is deur moderators van {domain} versteek.",
|
"limited_account_hint.title": "Hierdie profiel is deur moderators van {domain} versteek.",
|
||||||
"link_preview.author": "Deur {name}",
|
"link_preview.author": "Deur {name}",
|
||||||
"lists.account.add": "Voeg by lys",
|
|
||||||
"lists.account.remove": "Verwyder vanaf lys",
|
|
||||||
"lists.delete": "Verwyder lys",
|
"lists.delete": "Verwyder lys",
|
||||||
"lists.edit": "Redigeer lys",
|
"lists.edit": "Redigeer lys",
|
||||||
"lists.edit.submit": "Verander titel",
|
|
||||||
"lists.new.create": "Voeg lys by",
|
|
||||||
"lists.new.title_placeholder": "Nuwe lys titel",
|
|
||||||
"lists.search": "Soek tussen mense wat jy volg",
|
|
||||||
"lists.subheading": "Jou lyste",
|
|
||||||
"moved_to_account_banner.text": "Jou rekening {disabledAccount} is tans gedeaktiveer omdat jy na {movedToAccount} verhuis het.",
|
"moved_to_account_banner.text": "Jou rekening {disabledAccount} is tans gedeaktiveer omdat jy na {movedToAccount} verhuis het.",
|
||||||
"navigation_bar.about": "Oor",
|
"navigation_bar.about": "Oor",
|
||||||
"navigation_bar.bookmarks": "Boekmerke",
|
"navigation_bar.bookmarks": "Boekmerke",
|
||||||
|
@ -186,7 +186,6 @@
|
|||||||
"empty_column.hashtag": "No i hai cosa en este hashtag encara.",
|
"empty_column.hashtag": "No i hai cosa en este hashtag encara.",
|
||||||
"empty_column.home": "La tuya linia temporal ye vueda! Sigue a mas personas pa replenar-la. {suggestions}",
|
"empty_column.home": "La tuya linia temporal ye vueda! Sigue a mas personas pa replenar-la. {suggestions}",
|
||||||
"empty_column.list": "No i hai cosa en esta lista encara. Quan miembros d'esta lista publiquen nuevos estatus, estes amaneixerán qui.",
|
"empty_column.list": "No i hai cosa en esta lista encara. Quan miembros d'esta lista publiquen nuevos estatus, estes amaneixerán qui.",
|
||||||
"empty_column.lists": "No tiens garra lista. Quan en crees una, s'amostrará aquí.",
|
|
||||||
"empty_column.mutes": "Encara no has silenciau a garra usuario.",
|
"empty_column.mutes": "Encara no has silenciau a garra usuario.",
|
||||||
"empty_column.notifications": "No tiens garra notificación encara. Interactúa con atros pa empecipiar una conversación.",
|
"empty_column.notifications": "No tiens garra notificación encara. Interactúa con atros pa empecipiar una conversación.",
|
||||||
"empty_column.public": "No i hai cosa aquí! Escribe bella cosa publicament, u sigue usuarios d'atras instancias manualment pa emplir-lo",
|
"empty_column.public": "No i hai cosa aquí! Escribe bella cosa publicament, u sigue usuarios d'atras instancias manualment pa emplir-lo",
|
||||||
@ -292,19 +291,11 @@
|
|||||||
"lightbox.previous": "Anterior",
|
"lightbox.previous": "Anterior",
|
||||||
"limited_account_hint.action": "Amostrar perfil de totz modos",
|
"limited_account_hint.action": "Amostrar perfil de totz modos",
|
||||||
"limited_account_hint.title": "Este perfil ha estau amagau per los moderadors de {domain}.",
|
"limited_account_hint.title": "Este perfil ha estau amagau per los moderadors de {domain}.",
|
||||||
"lists.account.add": "Anyadir a lista",
|
|
||||||
"lists.account.remove": "Sacar de lista",
|
|
||||||
"lists.delete": "Borrar lista",
|
"lists.delete": "Borrar lista",
|
||||||
"lists.edit": "Editar lista",
|
"lists.edit": "Editar lista",
|
||||||
"lists.edit.submit": "Cambiar titol",
|
|
||||||
"lists.new.create": "Anyadir lista",
|
|
||||||
"lists.new.title_placeholder": "Titol d'a nueva lista",
|
|
||||||
"lists.replies_policy.followed": "Qualsequier usuario seguiu",
|
"lists.replies_policy.followed": "Qualsequier usuario seguiu",
|
||||||
"lists.replies_policy.list": "Miembros d'a lista",
|
"lists.replies_policy.list": "Miembros d'a lista",
|
||||||
"lists.replies_policy.none": "Dengún",
|
"lists.replies_policy.none": "Dengún",
|
||||||
"lists.replies_policy.title": "Amostrar respuestas a:",
|
|
||||||
"lists.search": "Buscar entre la chent a la quala sigues",
|
|
||||||
"lists.subheading": "Las tuyas listas",
|
|
||||||
"load_pending": "{count, plural, one {# nuevo elemento} other {# nuevos elementos}}",
|
"load_pending": "{count, plural, one {# nuevo elemento} other {# nuevos elementos}}",
|
||||||
"moved_to_account_banner.text": "La tuya cuenta {disabledAccount} ye actualment deshabilitada perque t'has mudau a {movedToAccount}.",
|
"moved_to_account_banner.text": "La tuya cuenta {disabledAccount} ye actualment deshabilitada perque t'has mudau a {movedToAccount}.",
|
||||||
"navigation_bar.about": "Sobre",
|
"navigation_bar.about": "Sobre",
|
||||||
|
@ -269,7 +269,6 @@
|
|||||||
"empty_column.hashtag": "ليس هناك بعدُ أي محتوى ذو علاقة بهذا الوسم.",
|
"empty_column.hashtag": "ليس هناك بعدُ أي محتوى ذو علاقة بهذا الوسم.",
|
||||||
"empty_column.home": "إنّ الخيط الزمني لصفحتك الرئيسة فارغ. قم بمتابعة المزيد من الناس كي يمتلأ.",
|
"empty_column.home": "إنّ الخيط الزمني لصفحتك الرئيسة فارغ. قم بمتابعة المزيد من الناس كي يمتلأ.",
|
||||||
"empty_column.list": "هذه القائمة فارغة مؤقتا و لكن سوف تمتلئ تدريجيا عندما يبدأ الأعضاء المُنتَمين إليها بنشر منشورات.",
|
"empty_column.list": "هذه القائمة فارغة مؤقتا و لكن سوف تمتلئ تدريجيا عندما يبدأ الأعضاء المُنتَمين إليها بنشر منشورات.",
|
||||||
"empty_column.lists": "ليس عندك أية قائمة بعد. سوف تظهر قوائمك هنا إن قمت بإنشاء واحدة.",
|
|
||||||
"empty_column.mutes": "لم تقم بكتم أي مستخدم بعد.",
|
"empty_column.mutes": "لم تقم بكتم أي مستخدم بعد.",
|
||||||
"empty_column.notification_requests": "لا يوجد شيء هنا. عندما تتلقى إشعارات جديدة، سوف تظهر هنا وفقًا لإعداداتك.",
|
"empty_column.notification_requests": "لا يوجد شيء هنا. عندما تتلقى إشعارات جديدة، سوف تظهر هنا وفقًا لإعداداتك.",
|
||||||
"empty_column.notifications": "لم تتلق أي إشعار بعدُ. تفاعل مع المستخدمين الآخرين لإنشاء محادثة.",
|
"empty_column.notifications": "لم تتلق أي إشعار بعدُ. تفاعل مع المستخدمين الآخرين لإنشاء محادثة.",
|
||||||
@ -425,20 +424,11 @@
|
|||||||
"limited_account_hint.title": "تم إخفاء هذا الملف الشخصي من قبل مشرفي {domain}.",
|
"limited_account_hint.title": "تم إخفاء هذا الملف الشخصي من قبل مشرفي {domain}.",
|
||||||
"link_preview.author": "مِن {name}",
|
"link_preview.author": "مِن {name}",
|
||||||
"link_preview.more_from_author": "المزيد من {name}",
|
"link_preview.more_from_author": "المزيد من {name}",
|
||||||
"lists.account.add": "أضف إلى القائمة",
|
|
||||||
"lists.account.remove": "احذف من القائمة",
|
|
||||||
"lists.delete": "احذف القائمة",
|
"lists.delete": "احذف القائمة",
|
||||||
"lists.edit": "عدّل القائمة",
|
"lists.edit": "عدّل القائمة",
|
||||||
"lists.edit.submit": "تعديل العنوان",
|
|
||||||
"lists.exclusive": "إخفاء هذه المنشورات من الخيط الرئيسي",
|
|
||||||
"lists.new.create": "إضافة قائمة",
|
|
||||||
"lists.new.title_placeholder": "عنوان القائمة الجديدة",
|
|
||||||
"lists.replies_policy.followed": "أي مستخدم متابَع",
|
"lists.replies_policy.followed": "أي مستخدم متابَع",
|
||||||
"lists.replies_policy.list": "أعضاء القائمة",
|
"lists.replies_policy.list": "أعضاء القائمة",
|
||||||
"lists.replies_policy.none": "لا أحد",
|
"lists.replies_policy.none": "لا أحد",
|
||||||
"lists.replies_policy.title": "عرض الردود لـ:",
|
|
||||||
"lists.search": "إبحث في قائمة الحسابات التي تُتابِعها",
|
|
||||||
"lists.subheading": "قوائمك",
|
|
||||||
"load_pending": "{count, plural, one {# عنصر جديد} other {# عناصر جديدة}}",
|
"load_pending": "{count, plural, one {# عنصر جديد} other {# عناصر جديدة}}",
|
||||||
"loading_indicator.label": "جاري التحميل…",
|
"loading_indicator.label": "جاري التحميل…",
|
||||||
"media_gallery.hide": "إخفاء",
|
"media_gallery.hide": "إخفاء",
|
||||||
@ -490,6 +480,7 @@
|
|||||||
"notification.label.private_reply": "رد خاص",
|
"notification.label.private_reply": "رد خاص",
|
||||||
"notification.label.reply": "ردّ",
|
"notification.label.reply": "ردّ",
|
||||||
"notification.mention": "إشارة",
|
"notification.mention": "إشارة",
|
||||||
|
"notification.mentioned_you": "{name} mentioned you",
|
||||||
"notification.moderation-warning.learn_more": "اعرف المزيد",
|
"notification.moderation-warning.learn_more": "اعرف المزيد",
|
||||||
"notification.moderation_warning": "لقد تلقيت تحذيرًا بالإشراف",
|
"notification.moderation_warning": "لقد تلقيت تحذيرًا بالإشراف",
|
||||||
"notification.moderation_warning.action_delete_statuses": "تم حذف بعض من منشوراتك.",
|
"notification.moderation_warning.action_delete_statuses": "تم حذف بعض من منشوراتك.",
|
||||||
|
@ -155,7 +155,6 @@
|
|||||||
"empty_column.hashtag": "Entá nun hai nada con esta etiqueta.",
|
"empty_column.hashtag": "Entá nun hai nada con esta etiqueta.",
|
||||||
"empty_column.home": "¡La to llinia de tiempu ta balera! Sigui a cuentes pa enllenala.",
|
"empty_column.home": "¡La to llinia de tiempu ta balera! Sigui a cuentes pa enllenala.",
|
||||||
"empty_column.list": "Nun hai nada nesta llista. Cuando los perfiles d'esta llista espublicen artículos nuevos, apaecen equí.",
|
"empty_column.list": "Nun hai nada nesta llista. Cuando los perfiles d'esta llista espublicen artículos nuevos, apaecen equí.",
|
||||||
"empty_column.lists": "Nun tienes nenguna llista. Cuando crees dalguna, apaez equí.",
|
|
||||||
"empty_column.mutes": "Nun tienes nengún perfil colos avisos desactivaos.",
|
"empty_column.mutes": "Nun tienes nengún perfil colos avisos desactivaos.",
|
||||||
"empty_column.notifications": "Nun tienes nengún avisu. Cuando otros perfiles interactúen contigo, apaez equí.",
|
"empty_column.notifications": "Nun tienes nengún avisu. Cuando otros perfiles interactúen contigo, apaez equí.",
|
||||||
"empty_column.public": "¡Equí nun hai nada! Escribi daqué públicamente o sigui a perfiles d'otros sirvidores pa enllenar esta seición",
|
"empty_column.public": "¡Equí nun hai nada! Escribi daqué públicamente o sigui a perfiles d'otros sirvidores pa enllenar esta seición",
|
||||||
@ -260,15 +259,9 @@
|
|||||||
"limited_account_hint.action": "Amosar el perfil de toes toes",
|
"limited_account_hint.action": "Amosar el perfil de toes toes",
|
||||||
"lists.delete": "Desaniciar la llista",
|
"lists.delete": "Desaniciar la llista",
|
||||||
"lists.edit": "Editar la llista",
|
"lists.edit": "Editar la llista",
|
||||||
"lists.edit.submit": "Camudar el títulu",
|
|
||||||
"lists.new.create": "Amestar la llista",
|
|
||||||
"lists.new.title_placeholder": "Títulu",
|
|
||||||
"lists.replies_policy.followed": "Cualesquier perfil siguíu",
|
"lists.replies_policy.followed": "Cualesquier perfil siguíu",
|
||||||
"lists.replies_policy.list": "Perfiles de la llista",
|
"lists.replies_policy.list": "Perfiles de la llista",
|
||||||
"lists.replies_policy.none": "Naide",
|
"lists.replies_policy.none": "Naide",
|
||||||
"lists.replies_policy.title": "Amosar les rempuestes a:",
|
|
||||||
"lists.search": "Buscar ente los perfiles que sigues",
|
|
||||||
"lists.subheading": "Les tos llistes",
|
|
||||||
"load_pending": "{count, plural, one {# elementu nuevu} other {# elementos nuevos}}",
|
"load_pending": "{count, plural, one {# elementu nuevu} other {# elementos nuevos}}",
|
||||||
"navigation_bar.about": "Tocante a",
|
"navigation_bar.about": "Tocante a",
|
||||||
"navigation_bar.blocks": "Perfiles bloquiaos",
|
"navigation_bar.blocks": "Perfiles bloquiaos",
|
||||||
|
@ -273,7 +273,6 @@
|
|||||||
"empty_column.hashtag": "Па гэтаму хэштэгу пакуль што нічога няма.",
|
"empty_column.hashtag": "Па гэтаму хэштэгу пакуль што нічога няма.",
|
||||||
"empty_column.home": "Галоўная стужка пустая! Падпішыцеся на іншых людзей, каб запоўніць яе. {suggestions}",
|
"empty_column.home": "Галоўная стужка пустая! Падпішыцеся на іншых людзей, каб запоўніць яе. {suggestions}",
|
||||||
"empty_column.list": "У гэтым спісе пакуль што нічога няма. Калі члены лісту апублікуюць новыя запісы, яны з'явяцца тут.",
|
"empty_column.list": "У гэтым спісе пакуль што нічога няма. Калі члены лісту апублікуюць новыя запісы, яны з'явяцца тут.",
|
||||||
"empty_column.lists": "Як толькі вы створыце новы спіс ён будзе захоўвацца тут, але пакуль што тут пуста.",
|
|
||||||
"empty_column.mutes": "Вы яшчэ нікога не ігнаруеце.",
|
"empty_column.mutes": "Вы яшчэ нікога не ігнаруеце.",
|
||||||
"empty_column.notification_requests": "Чысціня! Тут нічога няма. Калі вы будзеце атрымліваць новыя апавяшчэння, яны будуць з'яўляцца тут у адпаведнасці з вашымі наладамі.",
|
"empty_column.notification_requests": "Чысціня! Тут нічога няма. Калі вы будзеце атрымліваць новыя апавяшчэння, яны будуць з'яўляцца тут у адпаведнасці з вашымі наладамі.",
|
||||||
"empty_column.notifications": "У вас няма ніякіх апавяшчэнняў. Калі іншыя людзі ўзаемадзейнічаюць з вамі, вы ўбачыце гэта тут.",
|
"empty_column.notifications": "У вас няма ніякіх апавяшчэнняў. Калі іншыя людзі ўзаемадзейнічаюць з вамі, вы ўбачыце гэта тут.",
|
||||||
@ -427,20 +426,11 @@
|
|||||||
"link_preview.author": "Ад {name}",
|
"link_preview.author": "Ад {name}",
|
||||||
"link_preview.more_from_author": "Больш ад {name}",
|
"link_preview.more_from_author": "Больш ад {name}",
|
||||||
"link_preview.shares": "{count, plural, one {{counter} допіс} few {{counter} допісы} many {{counter} допісаў} other {{counter} допісу}}",
|
"link_preview.shares": "{count, plural, one {{counter} допіс} few {{counter} допісы} many {{counter} допісаў} other {{counter} допісу}}",
|
||||||
"lists.account.add": "Дадаць да спісу",
|
|
||||||
"lists.account.remove": "Выдаліць са спісу",
|
|
||||||
"lists.delete": "Выдаліць спіс",
|
"lists.delete": "Выдаліць спіс",
|
||||||
"lists.edit": "Рэдагаваць спіс",
|
"lists.edit": "Рэдагаваць спіс",
|
||||||
"lists.edit.submit": "Змяніць назву",
|
|
||||||
"lists.exclusive": "Схаваць гэтыя допісы з галоўнай старонкі",
|
|
||||||
"lists.new.create": "Дадаць спіс",
|
|
||||||
"lists.new.title_placeholder": "Назва новага спіса",
|
|
||||||
"lists.replies_policy.followed": "Любы карыстальнік, на якога вы падпісаліся",
|
"lists.replies_policy.followed": "Любы карыстальнік, на якога вы падпісаліся",
|
||||||
"lists.replies_policy.list": "Удзельнікі гэтага спісу",
|
"lists.replies_policy.list": "Удзельнікі гэтага спісу",
|
||||||
"lists.replies_policy.none": "Нікога",
|
"lists.replies_policy.none": "Нікога",
|
||||||
"lists.replies_policy.title": "Паказваць адказы:",
|
|
||||||
"lists.search": "Шукайце сярод людзей, на якіх Вы падпісаны",
|
|
||||||
"lists.subheading": "Вашыя спісы",
|
|
||||||
"load_pending": "{count, plural, one {# новы элемент} few {# новыя элементы} many {# новых элементаў} other {# новых элементаў}}",
|
"load_pending": "{count, plural, one {# новы элемент} few {# новыя элементы} many {# новых элементаў} other {# новых элементаў}}",
|
||||||
"loading_indicator.label": "Загрузка…",
|
"loading_indicator.label": "Загрузка…",
|
||||||
"media_gallery.hide": "Схаваць",
|
"media_gallery.hide": "Схаваць",
|
||||||
|
@ -87,6 +87,7 @@
|
|||||||
"alert.unexpected.title": "Опаа!",
|
"alert.unexpected.title": "Опаа!",
|
||||||
"alt_text_badge.title": "Алтернативен текст",
|
"alt_text_badge.title": "Алтернативен текст",
|
||||||
"announcement.announcement": "Оповестяване",
|
"announcement.announcement": "Оповестяване",
|
||||||
|
"annual_report.summary.archetype.booster": "Якият подсилвател",
|
||||||
"annual_report.summary.archetype.lurker": "Дебнещото",
|
"annual_report.summary.archetype.lurker": "Дебнещото",
|
||||||
"annual_report.summary.archetype.oracle": "Оракул",
|
"annual_report.summary.archetype.oracle": "Оракул",
|
||||||
"annual_report.summary.archetype.pollster": "Анкетьорче",
|
"annual_report.summary.archetype.pollster": "Анкетьорче",
|
||||||
@ -95,12 +96,14 @@
|
|||||||
"annual_report.summary.followers.total": "{count} общо",
|
"annual_report.summary.followers.total": "{count} общо",
|
||||||
"annual_report.summary.here_it_is": "Ето преглед на вашата {year} година:",
|
"annual_report.summary.here_it_is": "Ето преглед на вашата {year} година:",
|
||||||
"annual_report.summary.highlighted_post.by_favourites": "най-правено като любима публикация",
|
"annual_report.summary.highlighted_post.by_favourites": "най-правено като любима публикация",
|
||||||
|
"annual_report.summary.highlighted_post.by_reblogs": "най-подсилваната публикация",
|
||||||
"annual_report.summary.highlighted_post.by_replies": "публикации с най-много отговори",
|
"annual_report.summary.highlighted_post.by_replies": "публикации с най-много отговори",
|
||||||
"annual_report.summary.highlighted_post.possessive": "на {name}",
|
"annual_report.summary.highlighted_post.possessive": "на {name}",
|
||||||
"annual_report.summary.most_used_app.most_used_app": "най-употребявано приложение",
|
"annual_report.summary.most_used_app.most_used_app": "най-употребявано приложение",
|
||||||
"annual_report.summary.most_used_hashtag.most_used_hashtag": "най-употребяван хаштаг",
|
"annual_report.summary.most_used_hashtag.most_used_hashtag": "най-употребяван хаштаг",
|
||||||
"annual_report.summary.new_posts.new_posts": "нови публикации",
|
"annual_report.summary.new_posts.new_posts": "нови публикации",
|
||||||
"annual_report.summary.percentile.text": "<topLabel>Това ви слага най-отгоре</topLabel><percentage></percentage><bottomLabel>сред потребителите на Mastodon.</bottomLabel>",
|
"annual_report.summary.percentile.text": "<topLabel>Това ви слага най-отгоре</topLabel><percentage></percentage><bottomLabel>сред потребителите на Mastodon.</bottomLabel>",
|
||||||
|
"annual_report.summary.percentile.we_wont_tell_bernie": "Няма да кажем на Бърни Сандърс.",
|
||||||
"annual_report.summary.thanks": "Благодарим, че сте част от Mastodon!",
|
"annual_report.summary.thanks": "Благодарим, че сте част от Mastodon!",
|
||||||
"attachments_list.unprocessed": "(необработено)",
|
"attachments_list.unprocessed": "(необработено)",
|
||||||
"audio.hide": "Скриване на звука",
|
"audio.hide": "Скриване на звука",
|
||||||
@ -136,13 +139,16 @@
|
|||||||
"column.blocks": "Блокирани потребители",
|
"column.blocks": "Блокирани потребители",
|
||||||
"column.bookmarks": "Отметки",
|
"column.bookmarks": "Отметки",
|
||||||
"column.community": "Локален инфопоток",
|
"column.community": "Локален инфопоток",
|
||||||
|
"column.create_list": "Създаване на списък",
|
||||||
"column.direct": "Частни споменавания",
|
"column.direct": "Частни споменавания",
|
||||||
"column.directory": "Разглеждане на профили",
|
"column.directory": "Разглеждане на профили",
|
||||||
"column.domain_blocks": "Блокирани домейни",
|
"column.domain_blocks": "Блокирани домейни",
|
||||||
|
"column.edit_list": "Промяна на списъка",
|
||||||
"column.favourites": "Любими",
|
"column.favourites": "Любими",
|
||||||
"column.firehose": "Инфоканали на живо",
|
"column.firehose": "Инфоканали на живо",
|
||||||
"column.follow_requests": "Заявки за последване",
|
"column.follow_requests": "Заявки за последване",
|
||||||
"column.home": "Начало",
|
"column.home": "Начало",
|
||||||
|
"column.list_members": "Управление на списъка с участници",
|
||||||
"column.lists": "Списъци",
|
"column.lists": "Списъци",
|
||||||
"column.mutes": "Заглушени потребители",
|
"column.mutes": "Заглушени потребители",
|
||||||
"column.notifications": "Известия",
|
"column.notifications": "Известия",
|
||||||
@ -238,6 +244,8 @@
|
|||||||
"domain_block_modal.they_cant_follow": "Никого от този сървър не може да ви последва.",
|
"domain_block_modal.they_cant_follow": "Никого от този сървър не може да ви последва.",
|
||||||
"domain_block_modal.they_wont_know": "Няма да узнаят, че са били блокирани.",
|
"domain_block_modal.they_wont_know": "Няма да узнаят, че са били блокирани.",
|
||||||
"domain_block_modal.title": "Блокирате ли домейн?",
|
"domain_block_modal.title": "Блокирате ли домейн?",
|
||||||
|
"domain_block_modal.you_will_lose_num_followers": "Ще загубите {followersCount, plural, one {{followersCountDisplay} последовател} other {{followersCountDisplay} последователи}} и {followingCount, plural, one {{followingCountDisplay} лице, което следвате} other {{followingCountDisplay} души, които следвате}}.",
|
||||||
|
"domain_block_modal.you_will_lose_relationships": "Ще загубите всичките си последователи и хората, които следвате от този сървър.",
|
||||||
"domain_block_modal.you_wont_see_posts": "Няма да виждате публикации или известия от потребителите на този сървър.",
|
"domain_block_modal.you_wont_see_posts": "Няма да виждате публикации или известия от потребителите на този сървър.",
|
||||||
"domain_pill.activitypub_lets_connect": "Позволява ви да се свързвате и взаимодействате с хора не само в Mastodon, но и през различни социални приложения.",
|
"domain_pill.activitypub_lets_connect": "Позволява ви да се свързвате и взаимодействате с хора не само в Mastodon, но и през различни социални приложения.",
|
||||||
"domain_pill.activitypub_like_language": "ActivityPub е като език на Mastodon, говорещ с други социални мрежи.",
|
"domain_pill.activitypub_like_language": "ActivityPub е като език на Mastodon, говорещ с други социални мрежи.",
|
||||||
@ -286,7 +294,6 @@
|
|||||||
"empty_column.hashtag": "Още няма нищо в този хаштаг.",
|
"empty_column.hashtag": "Още няма нищо в този хаштаг.",
|
||||||
"empty_column.home": "Вашата начална часова ос е празна! Последвайте повече хора, за да я запълните. {suggestions}",
|
"empty_column.home": "Вашата начална часова ос е празна! Последвайте повече хора, за да я запълните. {suggestions}",
|
||||||
"empty_column.list": "Все още списъкът е празен. Членуващите на списъка, публикуващи нови публикации, ще се появят тук.",
|
"empty_column.list": "Все още списъкът е празен. Членуващите на списъка, публикуващи нови публикации, ще се появят тук.",
|
||||||
"empty_column.lists": "Все още нямате списъци. Когато създадете такъв, той ще се покаже тук.",
|
|
||||||
"empty_column.mutes": "Още не сте заглушавали потребители.",
|
"empty_column.mutes": "Още не сте заглушавали потребители.",
|
||||||
"empty_column.notification_requests": "Всичко е чисто! Тук няма нищо. Получавайки нови известия, те ще се появят тук според настройките ви.",
|
"empty_column.notification_requests": "Всичко е чисто! Тук няма нищо. Получавайки нови известия, те ще се появят тук според настройките ви.",
|
||||||
"empty_column.notifications": "Все още нямате известия. Взаимодействайте с другите, за да започнете разговора.",
|
"empty_column.notifications": "Все още нямате известия. Взаимодействайте с другите, за да започнете разговора.",
|
||||||
@ -386,10 +393,18 @@
|
|||||||
"ignore_notifications_modal.disclaimer": "Mastodon не може да осведоми потребители, че сте пренебрегнали известията им. Пренебрегването на известията няма да спре самите съобщения да не бъдат изпращани.",
|
"ignore_notifications_modal.disclaimer": "Mastodon не може да осведоми потребители, че сте пренебрегнали известията им. Пренебрегването на известията няма да спре самите съобщения да не бъдат изпращани.",
|
||||||
"ignore_notifications_modal.filter_to_act_users": "Вие все още ще може да приемате, отхвърляте или докладвате потребители",
|
"ignore_notifications_modal.filter_to_act_users": "Вие все още ще може да приемате, отхвърляте или докладвате потребители",
|
||||||
"ignore_notifications_modal.filter_to_avoid_confusion": "Прецеждането помага за избягване на възможно объркване",
|
"ignore_notifications_modal.filter_to_avoid_confusion": "Прецеждането помага за избягване на възможно объркване",
|
||||||
|
"ignore_notifications_modal.filter_to_review_separately": "Може да разгледате отделно филтрираните известия",
|
||||||
|
"ignore_notifications_modal.ignore": "Пренебрегване на известията",
|
||||||
|
"ignore_notifications_modal.limited_accounts_title": "Пренебрегвате ли известията от модерирани акаунти?",
|
||||||
|
"ignore_notifications_modal.new_accounts_title": "Пренебрегвате ли известията от нови акаунти?",
|
||||||
|
"ignore_notifications_modal.not_followers_title": "Пренебрегвате ли известията от хора, които не са ви последвали?",
|
||||||
|
"ignore_notifications_modal.not_following_title": "Пренебрегвате ли известията от хора, които не сте последвали?",
|
||||||
|
"ignore_notifications_modal.private_mentions_title": "Пренебрегвате ли известия от непоискани лични споменавания?",
|
||||||
"interaction_modal.description.favourite": "Имайки акаунт в Mastodon, може да сложите тази публикации в любими, за да позволите на автора да узнае, че я цените и да я запазите за по-късно.",
|
"interaction_modal.description.favourite": "Имайки акаунт в Mastodon, може да сложите тази публикации в любими, за да позволите на автора да узнае, че я цените и да я запазите за по-късно.",
|
||||||
"interaction_modal.description.follow": "С акаунт в Mastodon може да последвате {name}, за да получавате публикациите от този акаунт в началния си инфоканал.",
|
"interaction_modal.description.follow": "С акаунт в Mastodon може да последвате {name}, за да получавате публикациите от този акаунт в началния си инфоканал.",
|
||||||
"interaction_modal.description.reblog": "С акаунт в Mastodon може да подсилите тази публикация, за да я споделите с последователите си.",
|
"interaction_modal.description.reblog": "С акаунт в Mastodon може да подсилите тази публикация, за да я споделите с последователите си.",
|
||||||
"interaction_modal.description.reply": "С акаунт в Mastodon може да добавите отговор към тази публикация.",
|
"interaction_modal.description.reply": "С акаунт в Mastodon може да добавите отговор към тази публикация.",
|
||||||
|
"interaction_modal.description.vote": "Имайки акаунт в Mastodon, можете да гласувате в тази анкета.",
|
||||||
"interaction_modal.login.action": "Към началото",
|
"interaction_modal.login.action": "Към началото",
|
||||||
"interaction_modal.login.prompt": "Домейнът на сървъра ви, примерно, mastodon.social",
|
"interaction_modal.login.prompt": "Домейнът на сървъра ви, примерно, mastodon.social",
|
||||||
"interaction_modal.no_account_yet": "Още не е в Мастодон?",
|
"interaction_modal.no_account_yet": "Още не е в Мастодон?",
|
||||||
@ -449,20 +464,29 @@
|
|||||||
"link_preview.author": "От {name}",
|
"link_preview.author": "От {name}",
|
||||||
"link_preview.more_from_author": "Още от {name}",
|
"link_preview.more_from_author": "Още от {name}",
|
||||||
"link_preview.shares": "{count, plural, one {{counter} публикация} other {{counter} публикации}}",
|
"link_preview.shares": "{count, plural, one {{counter} публикация} other {{counter} публикации}}",
|
||||||
"lists.account.add": "Добавяне към списък",
|
"lists.add_member": "Добавяне",
|
||||||
"lists.account.remove": "Премахване от списъка",
|
"lists.add_to_list": "Добавяне в списък",
|
||||||
|
"lists.add_to_lists": "Добавяне на {name} в списъци",
|
||||||
|
"lists.create": "Създаване",
|
||||||
|
"lists.create_a_list_to_organize": "Сътворете нов списък, за да организирате инфоканала си на Начало",
|
||||||
|
"lists.create_list": "Създаване на списък",
|
||||||
"lists.delete": "Изтриване на списъка",
|
"lists.delete": "Изтриване на списъка",
|
||||||
|
"lists.done": "Готово",
|
||||||
"lists.edit": "Промяна на списъка",
|
"lists.edit": "Промяна на списъка",
|
||||||
"lists.edit.submit": "Промяна на заглавие",
|
"lists.exclusive": "Скриване на членуващи в Начало",
|
||||||
"lists.exclusive": "Скриване на тези публикации от началото",
|
"lists.exclusive_hint": "Ако някой е в този списък, то скрийте го в инфоканала си на Начало, за да избегнете виждането на публикациите му два пъти.",
|
||||||
"lists.new.create": "Добавяне на списък",
|
"lists.find_users_to_add": "Намерете потребители за добавяне",
|
||||||
"lists.new.title_placeholder": "Ново заглавие на списъка",
|
"lists.list_members": "Списък членуващи",
|
||||||
|
"lists.list_name": "Име на списък",
|
||||||
|
"lists.no_lists_yet": "Още няма списъци.",
|
||||||
|
"lists.no_members_yet": "Още няма членуващи.",
|
||||||
|
"lists.no_results_found": "Няма намерени резултати.",
|
||||||
|
"lists.remove_member": "Премахване",
|
||||||
"lists.replies_policy.followed": "Някой последван потребител",
|
"lists.replies_policy.followed": "Някой последван потребител",
|
||||||
"lists.replies_policy.list": "Членуващите в списъка",
|
"lists.replies_policy.list": "Членуващите в списъка",
|
||||||
"lists.replies_policy.none": "Никого",
|
"lists.replies_policy.none": "Никого",
|
||||||
"lists.replies_policy.title": "Показване на отговори на:",
|
"lists.save": "Запазване",
|
||||||
"lists.search": "Търсене измежду последваните",
|
"lists.search_placeholder": "Търсене сред, които сте последвали",
|
||||||
"lists.subheading": "Вашите списъци",
|
|
||||||
"load_pending": "{count, plural, one {# нов елемент} other {# нови елемента}}",
|
"load_pending": "{count, plural, one {# нов елемент} other {# нови елемента}}",
|
||||||
"loading_indicator.label": "Зареждане…",
|
"loading_indicator.label": "Зареждане…",
|
||||||
"media_gallery.hide": "Скриване",
|
"media_gallery.hide": "Скриване",
|
||||||
@ -511,6 +535,7 @@
|
|||||||
"notification.admin.report_statuses_other": "{name} докладва {target}",
|
"notification.admin.report_statuses_other": "{name} докладва {target}",
|
||||||
"notification.admin.sign_up": "{name} се регистрира",
|
"notification.admin.sign_up": "{name} се регистрира",
|
||||||
"notification.admin.sign_up.name_and_others": "{name} и {count, plural, one {# друг} other {# други}} се регистрираха",
|
"notification.admin.sign_up.name_and_others": "{name} и {count, plural, one {# друг} other {# други}} се регистрираха",
|
||||||
|
"notification.annual_report.view": "Преглед на #Wrapstodon",
|
||||||
"notification.favourite": "{name} направи любима публикацията ви",
|
"notification.favourite": "{name} направи любима публикацията ви",
|
||||||
"notification.favourite.name_and_others_with_link": "{name} и <a>{count, plural, one {# друг} other {# други}}</a> направиха любима ваша публикация",
|
"notification.favourite.name_and_others_with_link": "{name} и <a>{count, plural, one {# друг} other {# други}}</a> направиха любима ваша публикация",
|
||||||
"notification.follow": "{name} ви последва",
|
"notification.follow": "{name} ви последва",
|
||||||
@ -546,6 +571,7 @@
|
|||||||
"notification_requests.accept": "Приемам",
|
"notification_requests.accept": "Приемам",
|
||||||
"notification_requests.confirm_accept_multiple.message": "На път сте да приемете {count, plural, one {едно известие за заявка} other {# известия за заявки}}. Наистина ли искате да продължите?",
|
"notification_requests.confirm_accept_multiple.message": "На път сте да приемете {count, plural, one {едно известие за заявка} other {# известия за заявки}}. Наистина ли искате да продължите?",
|
||||||
"notification_requests.confirm_accept_multiple.title": "Приемате ли заявките за известие?",
|
"notification_requests.confirm_accept_multiple.title": "Приемате ли заявките за известие?",
|
||||||
|
"notification_requests.confirm_dismiss_multiple.message": "На път сте да отхвърлите {count, plural, one {една заявка за известие} other {# заявки за известие}}. Няма да имате лесен достъп до {count, plural, one {това лице} other {тях}} отново. Наистина ли искате да продължите?",
|
||||||
"notification_requests.confirm_dismiss_multiple.title": "Отхвърляте ли заявките за известие?",
|
"notification_requests.confirm_dismiss_multiple.title": "Отхвърляте ли заявките за известие?",
|
||||||
"notification_requests.dismiss": "Отхвърлям",
|
"notification_requests.dismiss": "Отхвърлям",
|
||||||
"notification_requests.edit_selection": "Редактиране",
|
"notification_requests.edit_selection": "Редактиране",
|
||||||
@ -593,6 +619,7 @@
|
|||||||
"notifications.permission_required": "Известията на работния плот ги няма, щото няма дадено нужното позволение.",
|
"notifications.permission_required": "Известията на работния плот ги няма, щото няма дадено нужното позволение.",
|
||||||
"notifications.policy.accept": "Приемам",
|
"notifications.policy.accept": "Приемам",
|
||||||
"notifications.policy.accept_hint": "Показване в известия",
|
"notifications.policy.accept_hint": "Показване в известия",
|
||||||
|
"notifications.policy.drop_hint": "Изпращане в празнотата, за да не се видим никога пак",
|
||||||
"notifications.policy.filter": "Филтър",
|
"notifications.policy.filter": "Филтър",
|
||||||
"notifications.policy.filter_limited_accounts_hint": "Ограничено от модераторите на сървъра",
|
"notifications.policy.filter_limited_accounts_hint": "Ограничено от модераторите на сървъра",
|
||||||
"notifications.policy.filter_limited_accounts_title": "Модерирани акаунти",
|
"notifications.policy.filter_limited_accounts_title": "Модерирани акаунти",
|
||||||
@ -638,7 +665,7 @@
|
|||||||
"onboarding.steps.follow_people.title": "Персонализиране на началния ви инфоканал",
|
"onboarding.steps.follow_people.title": "Персонализиране на началния ви инфоканал",
|
||||||
"onboarding.steps.publish_status.body": "Поздравете целия свят.",
|
"onboarding.steps.publish_status.body": "Поздравете целия свят.",
|
||||||
"onboarding.steps.publish_status.title": "Направете първата си публикация",
|
"onboarding.steps.publish_status.title": "Направете първата си публикация",
|
||||||
"onboarding.steps.setup_profile.body": "Други са по-вероятно да взаимодействат с вас с попълнения профил.",
|
"onboarding.steps.setup_profile.body": "Подсилете взаимодействията си, имайки изчерпателен профил.",
|
||||||
"onboarding.steps.setup_profile.title": "Пригодете профила си",
|
"onboarding.steps.setup_profile.title": "Пригодете профила си",
|
||||||
"onboarding.steps.share_profile.body": "Позволете на приятелите си да знаят как да ви намират в Mastodon!",
|
"onboarding.steps.share_profile.body": "Позволете на приятелите си да знаят как да ви намират в Mastodon!",
|
||||||
"onboarding.steps.share_profile.title": "Споделяне на профила ви",
|
"onboarding.steps.share_profile.title": "Споделяне на профила ви",
|
||||||
@ -852,6 +879,10 @@
|
|||||||
"upload_form.audio_description": "Опишете за хора, които са глухи или трудно чуват",
|
"upload_form.audio_description": "Опишете за хора, които са глухи или трудно чуват",
|
||||||
"upload_form.description": "Опишете за хора, които са слепи или имат слабо зрение",
|
"upload_form.description": "Опишете за хора, които са слепи или имат слабо зрение",
|
||||||
"upload_form.drag_and_drop.instructions": "Натиснете интервал или enter, за да подберете мултимедийно прикачване. Провлачвайки, ползвайте клавишите със стрелки, за да премествате мултимедията във всяка дадена посока. Натиснете пак интервал или enter, за да се стовари мултимедийното прикачване в новото си положение или натиснете Esc за отмяна.",
|
"upload_form.drag_and_drop.instructions": "Натиснете интервал или enter, за да подберете мултимедийно прикачване. Провлачвайки, ползвайте клавишите със стрелки, за да премествате мултимедията във всяка дадена посока. Натиснете пак интервал или enter, за да се стовари мултимедийното прикачване в новото си положение или натиснете Esc за отмяна.",
|
||||||
|
"upload_form.drag_and_drop.on_drag_cancel": "Провлачването е отменено. Мултимедийното прикачване {item} е спуснато.",
|
||||||
|
"upload_form.drag_and_drop.on_drag_end": "Мултимедийното прикачване {item} е спуснато.",
|
||||||
|
"upload_form.drag_and_drop.on_drag_over": "Мултимедийното прикачване {item} е преместено.",
|
||||||
|
"upload_form.drag_and_drop.on_drag_start": "Избрано мултимедийно прикачване {item}.",
|
||||||
"upload_form.edit": "Редактиране",
|
"upload_form.edit": "Редактиране",
|
||||||
"upload_form.thumbnail": "Промяна на миниобраза",
|
"upload_form.thumbnail": "Промяна на миниобраза",
|
||||||
"upload_form.video_description": "Опишете за хора, които са глухи или трудно чуват, слепи или имат слабо зрение",
|
"upload_form.video_description": "Опишете за хора, които са глухи или трудно чуват, слепи или имат слабо зрение",
|
||||||
|
@ -202,7 +202,6 @@
|
|||||||
"empty_column.hashtag": "এই হেসটাগে এখনো কিছু নেই।",
|
"empty_column.hashtag": "এই হেসটাগে এখনো কিছু নেই।",
|
||||||
"empty_column.home": "আপনার বাড়ির সময়রেখা এখনো খালি! {public} এ ঘুরে আসুন অথবা অনুসন্ধান বেবহার করে শুরু করতে পারেন এবং অন্য ব্যবহারকারীদের সাথে সাক্ষাৎ করতে পারেন।",
|
"empty_column.home": "আপনার বাড়ির সময়রেখা এখনো খালি! {public} এ ঘুরে আসুন অথবা অনুসন্ধান বেবহার করে শুরু করতে পারেন এবং অন্য ব্যবহারকারীদের সাথে সাক্ষাৎ করতে পারেন।",
|
||||||
"empty_column.list": "এই তালিকাতে এখনো কিছু নেই. যখন এই তালিকায় থাকা ব্যবহারকারী নতুন কিছু লিখবে, সেগুলো এখানে পাওয়া যাবে।",
|
"empty_column.list": "এই তালিকাতে এখনো কিছু নেই. যখন এই তালিকায় থাকা ব্যবহারকারী নতুন কিছু লিখবে, সেগুলো এখানে পাওয়া যাবে।",
|
||||||
"empty_column.lists": "আপনার এখনো কোনো তালিকা তৈরী নেই। যদি বা যখন তৈরী করেন, সেগুলো এখানে পাওয়া যাবে।",
|
|
||||||
"empty_column.mutes": "আপনি এখনো কোনো ব্যবহারকারীকে নিঃশব্দ করেননি।",
|
"empty_column.mutes": "আপনি এখনো কোনো ব্যবহারকারীকে নিঃশব্দ করেননি।",
|
||||||
"empty_column.notifications": "আপনার এখনো কোনো প্রজ্ঞাপন নেই। কথোপকথন শুরু করতে, অন্যদের সাথে মেলামেশা করতে পারেন।",
|
"empty_column.notifications": "আপনার এখনো কোনো প্রজ্ঞাপন নেই। কথোপকথন শুরু করতে, অন্যদের সাথে মেলামেশা করতে পারেন।",
|
||||||
"empty_column.public": "এখানে এখনো কিছু নেই! প্রকাশ্য ভাবে কিছু লিখুন বা অন্য সার্ভার থেকে কাওকে অনুসরণ করে এই জায়গা ভরে ফেলুন",
|
"empty_column.public": "এখানে এখনো কিছু নেই! প্রকাশ্য ভাবে কিছু লিখুন বা অন্য সার্ভার থেকে কাওকে অনুসরণ করে এই জায়গা ভরে ফেলুন",
|
||||||
@ -277,16 +276,9 @@
|
|||||||
"lightbox.next": "পরবর্তী",
|
"lightbox.next": "পরবর্তী",
|
||||||
"lightbox.previous": "পূর্ববর্তী",
|
"lightbox.previous": "পূর্ববর্তী",
|
||||||
"link_preview.author": "{name} এর লিখা",
|
"link_preview.author": "{name} এর লিখা",
|
||||||
"lists.account.add": "তালিকাতে যুক্ত করতে",
|
|
||||||
"lists.account.remove": "তালিকা থেকে বাদ দিতে",
|
|
||||||
"lists.delete": "তালিকা মুছে ফেলতে",
|
"lists.delete": "তালিকা মুছে ফেলতে",
|
||||||
"lists.edit": "তালিকা সম্পাদনা করতে",
|
"lists.edit": "তালিকা সম্পাদনা করতে",
|
||||||
"lists.edit.submit": "শিরোনাম সম্পাদনা করতে",
|
|
||||||
"lists.new.create": "তালিকাতে যুক্ত করতে",
|
|
||||||
"lists.new.title_placeholder": "তালিকার নতুন শিরোনাম দিতে",
|
|
||||||
"lists.replies_policy.none": "কেউ না",
|
"lists.replies_policy.none": "কেউ না",
|
||||||
"lists.search": "যাদের অনুসরণ করেন তাদের ভেতরে খুঁজুন",
|
|
||||||
"lists.subheading": "আপনার তালিকা",
|
|
||||||
"load_pending": "{count, plural, one {# নতুন জিনিস} other {# নতুন জিনিস}}",
|
"load_pending": "{count, plural, one {# নতুন জিনিস} other {# নতুন জিনিস}}",
|
||||||
"navigation_bar.about": "পরিচিতি",
|
"navigation_bar.about": "পরিচিতি",
|
||||||
"navigation_bar.blocks": "বন্ধ করা ব্যবহারকারী",
|
"navigation_bar.blocks": "বন্ধ করা ব্যবহারকারী",
|
||||||
|
@ -223,7 +223,6 @@
|
|||||||
"empty_column.hashtag": "N'eus netra en hashtag-mañ c'hoazh.",
|
"empty_column.hashtag": "N'eus netra en hashtag-mañ c'hoazh.",
|
||||||
"empty_column.home": "Goullo eo ho red-amzer degemer! Kit da weladenniñ {public} pe implijit ar c'hlask evit kregiñ ganti ha kejañ gant implijer·ien·ezed all.",
|
"empty_column.home": "Goullo eo ho red-amzer degemer! Kit da weladenniñ {public} pe implijit ar c'hlask evit kregiñ ganti ha kejañ gant implijer·ien·ezed all.",
|
||||||
"empty_column.list": "Goullo eo al listenn-mañ evit c'hoazh. Pa vo embannet toudoù nevez gant e izili e teuint war wel amañ.",
|
"empty_column.list": "Goullo eo al listenn-mañ evit c'hoazh. Pa vo embannet toudoù nevez gant e izili e teuint war wel amañ.",
|
||||||
"empty_column.lists": "N'ho peus roll ebet c'hoazh. Pa vo krouet unan ganeoc'h e vo diskouezet amañ.",
|
|
||||||
"empty_column.mutes": "N'ho peus kuzhet implijer ebet c'hoazh.",
|
"empty_column.mutes": "N'ho peus kuzhet implijer ebet c'hoazh.",
|
||||||
"empty_column.notifications": "N'ho peus kemenn ebet c'hoazh. Grit gant implijer·ezed·ien all evit loc'hañ ar gomz.",
|
"empty_column.notifications": "N'ho peus kemenn ebet c'hoazh. Grit gant implijer·ezed·ien all evit loc'hañ ar gomz.",
|
||||||
"empty_column.public": "N'eus netra amañ! Skrivit un dra bennak foran pe heuilhit implijer·ien·ezed eus dafariadoù all evit leuniañ",
|
"empty_column.public": "N'eus netra amañ! Skrivit un dra bennak foran pe heuilhit implijer·ien·ezed eus dafariadoù all evit leuniañ",
|
||||||
@ -346,19 +345,11 @@
|
|||||||
"limited_account_hint.action": "Diskouez an aelad memes tra",
|
"limited_account_hint.action": "Diskouez an aelad memes tra",
|
||||||
"limited_account_hint.title": "Kuzhet eo bet ar profil-mañ gant an evezhierien eus {domain}.",
|
"limited_account_hint.title": "Kuzhet eo bet ar profil-mañ gant an evezhierien eus {domain}.",
|
||||||
"link_preview.author": "Gant {name}",
|
"link_preview.author": "Gant {name}",
|
||||||
"lists.account.add": "Ouzhpennañ d'al listenn",
|
|
||||||
"lists.account.remove": "Lemel kuit eus al listenn",
|
|
||||||
"lists.delete": "Dilemel al listenn",
|
"lists.delete": "Dilemel al listenn",
|
||||||
"lists.edit": "Kemmañ al listenn",
|
"lists.edit": "Kemmañ al listenn",
|
||||||
"lists.edit.submit": "Cheñch an titl",
|
|
||||||
"lists.new.create": "Ouzhpennañ ul listenn",
|
|
||||||
"lists.new.title_placeholder": "Titl nevez al listenn",
|
|
||||||
"lists.replies_policy.followed": "Pep implijer.ez heuliet",
|
"lists.replies_policy.followed": "Pep implijer.ez heuliet",
|
||||||
"lists.replies_policy.list": "Izili ar roll",
|
"lists.replies_policy.list": "Izili ar roll",
|
||||||
"lists.replies_policy.none": "Den ebet",
|
"lists.replies_policy.none": "Den ebet",
|
||||||
"lists.replies_policy.title": "Diskouez ar respontoù:",
|
|
||||||
"lists.search": "Klask e-touez tud heuliet ganeoc'h",
|
|
||||||
"lists.subheading": "Ho listennoù",
|
|
||||||
"load_pending": "{count, plural, one {# dra nevez} other {# dra nevez}}",
|
"load_pending": "{count, plural, one {# dra nevez} other {# dra nevez}}",
|
||||||
"loading_indicator.label": "O kargañ…",
|
"loading_indicator.label": "O kargañ…",
|
||||||
"navigation_bar.about": "Diwar-benn",
|
"navigation_bar.about": "Diwar-benn",
|
||||||
|
@ -134,13 +134,16 @@
|
|||||||
"column.blocks": "Usuaris blocats",
|
"column.blocks": "Usuaris blocats",
|
||||||
"column.bookmarks": "Marcadors",
|
"column.bookmarks": "Marcadors",
|
||||||
"column.community": "Línia de temps local",
|
"column.community": "Línia de temps local",
|
||||||
|
"column.create_list": "Crea una llista",
|
||||||
"column.direct": "Mencions privades",
|
"column.direct": "Mencions privades",
|
||||||
"column.directory": "Navega pels perfils",
|
"column.directory": "Navega pels perfils",
|
||||||
"column.domain_blocks": "Dominis blocats",
|
"column.domain_blocks": "Dominis blocats",
|
||||||
|
"column.edit_list": "Edita la llista",
|
||||||
"column.favourites": "Favorits",
|
"column.favourites": "Favorits",
|
||||||
"column.firehose": "Tuts en directe",
|
"column.firehose": "Tuts en directe",
|
||||||
"column.follow_requests": "Peticions de seguir-te",
|
"column.follow_requests": "Peticions de seguir-te",
|
||||||
"column.home": "Inici",
|
"column.home": "Inici",
|
||||||
|
"column.list_members": "Gestiona els membres de la llista",
|
||||||
"column.lists": "Llistes",
|
"column.lists": "Llistes",
|
||||||
"column.mutes": "Usuaris silenciats",
|
"column.mutes": "Usuaris silenciats",
|
||||||
"column.notifications": "Notificacions",
|
"column.notifications": "Notificacions",
|
||||||
@ -286,7 +289,6 @@
|
|||||||
"empty_column.hashtag": "Encara no hi ha res en aquesta etiqueta.",
|
"empty_column.hashtag": "Encara no hi ha res en aquesta etiqueta.",
|
||||||
"empty_column.home": "La teva línia de temps és buida! Segueix més gent per a emplenar-la. {suggestions}",
|
"empty_column.home": "La teva línia de temps és buida! Segueix més gent per a emplenar-la. {suggestions}",
|
||||||
"empty_column.list": "Encara no hi ha res en aquesta llista. Quan els membres facin nous tuts, apareixeran aquí.",
|
"empty_column.list": "Encara no hi ha res en aquesta llista. Quan els membres facin nous tuts, apareixeran aquí.",
|
||||||
"empty_column.lists": "Encara no tens cap llista. Quan en facis una, apareixerà aquí.",
|
|
||||||
"empty_column.mutes": "Encara no has silenciat cap usuari.",
|
"empty_column.mutes": "Encara no has silenciat cap usuari.",
|
||||||
"empty_column.notification_requests": "Tot net, ja no hi ha res aquí! Quan rebeu notificacions noves, segons la vostra configuració, apareixeran aquí.",
|
"empty_column.notification_requests": "Tot net, ja no hi ha res aquí! Quan rebeu notificacions noves, segons la vostra configuració, apareixeran aquí.",
|
||||||
"empty_column.notifications": "Encara no tens notificacions. Quan altre gent interactuï amb tu, les veuràs aquí.",
|
"empty_column.notifications": "Encara no tens notificacions. Quan altre gent interactuï amb tu, les veuràs aquí.",
|
||||||
@ -459,20 +461,32 @@
|
|||||||
"link_preview.author": "Per {name}",
|
"link_preview.author": "Per {name}",
|
||||||
"link_preview.more_from_author": "Més de {name}",
|
"link_preview.more_from_author": "Més de {name}",
|
||||||
"link_preview.shares": "{count, plural, one {{counter} publicació} other {{counter} publicacions}}",
|
"link_preview.shares": "{count, plural, one {{counter} publicació} other {{counter} publicacions}}",
|
||||||
"lists.account.add": "Afegeix a la llista",
|
"lists.add_member": "Afegeix",
|
||||||
"lists.account.remove": "Elimina de la llista",
|
"lists.add_to_list": "Afegeix a la llista",
|
||||||
|
"lists.add_to_lists": "Afegeix {name} a les llistes",
|
||||||
|
"lists.create": "Crea",
|
||||||
|
"lists.create_a_list_to_organize": "Creeu una nova llista per a organitzar la pantalla d'inici",
|
||||||
|
"lists.create_list": "Crea una llista",
|
||||||
"lists.delete": "Elimina la llista",
|
"lists.delete": "Elimina la llista",
|
||||||
|
"lists.done": "Fet",
|
||||||
"lists.edit": "Edita la llista",
|
"lists.edit": "Edita la llista",
|
||||||
"lists.edit.submit": "Canvia el títol",
|
"lists.exclusive": "Amaga membres a Inici",
|
||||||
"lists.exclusive": "Amaga aquests tuts a Inici",
|
"lists.exclusive_hint": "Si algú és a la llista, amagueu-los de la pantalla d'inici, per a no veure'n les publicacions duplicades.",
|
||||||
"lists.new.create": "Afegeix una llista",
|
"lists.find_users_to_add": "Troba usuaris per a afegir",
|
||||||
"lists.new.title_placeholder": "Nou títol de la llista",
|
"lists.list_members": "Membres de la llista",
|
||||||
|
"lists.list_members_count": "{count, plural, one {# membre} other {# membres}}",
|
||||||
|
"lists.list_name": "Nom de la llista",
|
||||||
|
"lists.new_list_name": "Nom de la nova llista",
|
||||||
|
"lists.no_lists_yet": "Encara no hi ha cap llista.",
|
||||||
|
"lists.no_members_yet": "Encara no hi ha membres.",
|
||||||
|
"lists.no_results_found": "No s'han trobat resultats.",
|
||||||
|
"lists.remove_member": "Elimina",
|
||||||
"lists.replies_policy.followed": "Qualsevol usuari que segueixis",
|
"lists.replies_policy.followed": "Qualsevol usuari que segueixis",
|
||||||
"lists.replies_policy.list": "Membres de la llista",
|
"lists.replies_policy.list": "Membres de la llista",
|
||||||
"lists.replies_policy.none": "Ningú",
|
"lists.replies_policy.none": "Ningú",
|
||||||
"lists.replies_policy.title": "Mostra respostes a:",
|
"lists.save": "Desa",
|
||||||
"lists.search": "Cerca entre les persones que segueixes",
|
"lists.search_placeholder": "Cerca persones que seguiu",
|
||||||
"lists.subheading": "Les teves llistes",
|
"lists.show_replies_to": "Inclou respostes de membres de la llista a",
|
||||||
"load_pending": "{count, plural, one {# element nou} other {# elements nous}}",
|
"load_pending": "{count, plural, one {# element nou} other {# elements nous}}",
|
||||||
"loading_indicator.label": "Es carrega…",
|
"loading_indicator.label": "Es carrega…",
|
||||||
"media_gallery.hide": "Amaga",
|
"media_gallery.hide": "Amaga",
|
||||||
@ -630,11 +644,11 @@
|
|||||||
"onboarding.action.back": "Porta'm enrere",
|
"onboarding.action.back": "Porta'm enrere",
|
||||||
"onboarding.actions.back": "Porta'm enrere",
|
"onboarding.actions.back": "Porta'm enrere",
|
||||||
"onboarding.actions.go_to_explore": "Mira què és tendència",
|
"onboarding.actions.go_to_explore": "Mira què és tendència",
|
||||||
"onboarding.actions.go_to_home": "Ves a la teva línia de temps",
|
"onboarding.actions.go_to_home": "Aneu a la vostra pantalla d'inici",
|
||||||
"onboarding.compose.template": "Hola Mastodon!",
|
"onboarding.compose.template": "Hola Mastodon!",
|
||||||
"onboarding.follows.empty": "Malauradament, cap resultat pot ser mostrat ara mateix. Pots provar de fer servir la cerca o visitar la pàgina Explora per a trobar gent a qui seguir o provar-ho de nou més tard.",
|
"onboarding.follows.empty": "Malauradament, cap resultat pot ser mostrat ara mateix. Pots provar de fer servir la cerca o visitar la pàgina Explora per a trobar gent a qui seguir o provar-ho de nou més tard.",
|
||||||
"onboarding.follows.lead": "La teva línia de temps inici només està a les teves mans. Com més gent segueixis, més activa i interessant serà. Aquests perfils poden ser un bon punt d'inici—sempre pots acabar deixant de seguir-los!:",
|
"onboarding.follows.lead": "La vostra pantalla d'inici és la manera principal d'experimentar Mastodon. Com més gent seguiu, més activa i interessant serà. Per a començar, alguns suggeriments:",
|
||||||
"onboarding.follows.title": "Personalitza la pantalla d'inci",
|
"onboarding.follows.title": "Personalitzeu la pantalla d'inci",
|
||||||
"onboarding.profile.discoverable": "Fes el meu perfil descobrible",
|
"onboarding.profile.discoverable": "Fes el meu perfil descobrible",
|
||||||
"onboarding.profile.discoverable_hint": "En acceptar d'ésser descobert a Mastodon els teus missatges poden aparèixer dins les tendències i els resultats de cerques, i el teu perfil es pot suggerir a qui tingui interessos semblants als teus.",
|
"onboarding.profile.discoverable_hint": "En acceptar d'ésser descobert a Mastodon els teus missatges poden aparèixer dins les tendències i els resultats de cerques, i el teu perfil es pot suggerir a qui tingui interessos semblants als teus.",
|
||||||
"onboarding.profile.display_name": "Nom que es mostrarà",
|
"onboarding.profile.display_name": "Nom que es mostrarà",
|
||||||
@ -654,7 +668,7 @@
|
|||||||
"onboarding.start.skip": "Vols saltar-te tota la resta?",
|
"onboarding.start.skip": "Vols saltar-te tota la resta?",
|
||||||
"onboarding.start.title": "Llestos!",
|
"onboarding.start.title": "Llestos!",
|
||||||
"onboarding.steps.follow_people.body": "Mastodon va de seguir a gent interessant.",
|
"onboarding.steps.follow_people.body": "Mastodon va de seguir a gent interessant.",
|
||||||
"onboarding.steps.follow_people.title": "Personalitza la pantalla d'inci",
|
"onboarding.steps.follow_people.title": "Personalitzeu la pantalla d'inici",
|
||||||
"onboarding.steps.publish_status.body": "Saluda al món amb text, fotos, vídeos o enquestes {emoji}",
|
"onboarding.steps.publish_status.body": "Saluda al món amb text, fotos, vídeos o enquestes {emoji}",
|
||||||
"onboarding.steps.publish_status.title": "Fes el teu primer tut",
|
"onboarding.steps.publish_status.title": "Fes el teu primer tut",
|
||||||
"onboarding.steps.setup_profile.body": "És més fàcil que altres interactuïn amb tu si tens un perfil complet.",
|
"onboarding.steps.setup_profile.body": "És més fàcil que altres interactuïn amb tu si tens un perfil complet.",
|
||||||
@ -693,7 +707,7 @@
|
|||||||
"recommended": "Recomanat",
|
"recommended": "Recomanat",
|
||||||
"refresh": "Actualitza",
|
"refresh": "Actualitza",
|
||||||
"regeneration_indicator.label": "Es carrega…",
|
"regeneration_indicator.label": "Es carrega…",
|
||||||
"regeneration_indicator.sublabel": "Es prepara la teva línia de temps d'Inici!",
|
"regeneration_indicator.sublabel": "Es prepara la vostra pantalla d'Inici!",
|
||||||
"relative_time.days": "{number}d",
|
"relative_time.days": "{number}d",
|
||||||
"relative_time.full.days": "fa {number, plural, one {# dia} other {# dies}}",
|
"relative_time.full.days": "fa {number, plural, one {# dia} other {# dies}}",
|
||||||
"relative_time.full.hours": "fa {number, plural, one {# hora} other {# hores}}",
|
"relative_time.full.hours": "fa {number, plural, one {# hora} other {# hores}}",
|
||||||
|
@ -221,7 +221,6 @@
|
|||||||
"empty_column.hashtag": "هێشتا هیچ شتێک لەم هاشتاگەدا نییە.",
|
"empty_column.hashtag": "هێشتا هیچ شتێک لەم هاشتاگەدا نییە.",
|
||||||
"empty_column.home": "تایم لاینی ماڵەوەت بەتاڵە! سەردانی {public} بکە یان گەڕان بەکاربێنە بۆ دەستپێکردن و بینینی بەکارهێنەرانی تر.",
|
"empty_column.home": "تایم لاینی ماڵەوەت بەتاڵە! سەردانی {public} بکە یان گەڕان بەکاربێنە بۆ دەستپێکردن و بینینی بەکارهێنەرانی تر.",
|
||||||
"empty_column.list": "هێشتا هیچ شتێک لەم لیستەدا نییە. کاتێک ئەندامانی ئەم لیستە دەنگی نوێ بڵاودەکەن، لێرە دەردەکەون.",
|
"empty_column.list": "هێشتا هیچ شتێک لەم لیستەدا نییە. کاتێک ئەندامانی ئەم لیستە دەنگی نوێ بڵاودەکەن، لێرە دەردەکەون.",
|
||||||
"empty_column.lists": "تۆ هێشتا هیچ لیستت دروست نەکردووە، کاتێک دانەیەک دروست دەکەیت، لێرە پیشان دەدرێت.",
|
|
||||||
"empty_column.mutes": "تۆ هێشتا هیچ بەکارهێنەرێکت بێدەنگ نەکردووە.",
|
"empty_column.mutes": "تۆ هێشتا هیچ بەکارهێنەرێکت بێدەنگ نەکردووە.",
|
||||||
"empty_column.notifications": "تۆ هێشتا هیچ ئاگانامێکت نیە. چالاکی لەگەڵ کەسانی دیکە بکە بۆ دەستپێکردنی گفتوگۆکە.",
|
"empty_column.notifications": "تۆ هێشتا هیچ ئاگانامێکت نیە. چالاکی لەگەڵ کەسانی دیکە بکە بۆ دەستپێکردنی گفتوگۆکە.",
|
||||||
"empty_column.public": "لێرە هیچ نییە! شتێک بە ئاشکرا بنووسە(بەگشتی)، یان بە دەستی شوێن بەکارهێنەران بکەوە لە ڕاژەکانی ترەوە بۆ پڕکردنەوەی",
|
"empty_column.public": "لێرە هیچ نییە! شتێک بە ئاشکرا بنووسە(بەگشتی)، یان بە دەستی شوێن بەکارهێنەران بکەوە لە ڕاژەکانی ترەوە بۆ پڕکردنەوەی",
|
||||||
@ -338,19 +337,11 @@
|
|||||||
"lightbox.previous": "پێشوو",
|
"lightbox.previous": "پێشوو",
|
||||||
"limited_account_hint.action": "بەهەر حاڵ پڕۆفایلی پیشان بدە",
|
"limited_account_hint.action": "بەهەر حاڵ پڕۆفایلی پیشان بدە",
|
||||||
"limited_account_hint.title": "ئەم پرۆفایلە لەلایەن بەڕێوەبەرانی {domain} شاراوەتەوە.",
|
"limited_account_hint.title": "ئەم پرۆفایلە لەلایەن بەڕێوەبەرانی {domain} شاراوەتەوە.",
|
||||||
"lists.account.add": "زیادکردن بۆ لیست",
|
|
||||||
"lists.account.remove": "لابردن لە لیست",
|
|
||||||
"lists.delete": "سڕینەوەی لیست",
|
"lists.delete": "سڕینەوەی لیست",
|
||||||
"lists.edit": "دەستکاری لیست",
|
"lists.edit": "دەستکاری لیست",
|
||||||
"lists.edit.submit": "گۆڕینی ناونیشان",
|
|
||||||
"lists.new.create": "زیادکردنی لیست",
|
|
||||||
"lists.new.title_placeholder": "ناونیشانی لیستی نوێ",
|
|
||||||
"lists.replies_policy.followed": "هەر بەکارهێنەرێکی بەدواکەوتوو",
|
"lists.replies_policy.followed": "هەر بەکارهێنەرێکی بەدواکەوتوو",
|
||||||
"lists.replies_policy.list": "ئەندامانی لیستەکە",
|
"lists.replies_policy.list": "ئەندامانی لیستەکە",
|
||||||
"lists.replies_policy.none": "هیچکەس",
|
"lists.replies_policy.none": "هیچکەس",
|
||||||
"lists.replies_policy.title": "پیشاندانی وەڵامەکان بۆ:",
|
|
||||||
"lists.search": "بگەڕێ لەناو ئەو کەسانەی کە شوێنیان کەوتویت",
|
|
||||||
"lists.subheading": "لیستەکانت",
|
|
||||||
"load_pending": "{count, plural, one {# بەڕگەی نوێ} other {# بەڕگەی نوێ}}",
|
"load_pending": "{count, plural, one {# بەڕگەی نوێ} other {# بەڕگەی نوێ}}",
|
||||||
"moved_to_account_banner.text": "ئەکاونتەکەت {disabledAccount} لە ئێستادا لەکارخراوە چونکە تۆ چوویتە {movedToAccount}.",
|
"moved_to_account_banner.text": "ئەکاونتەکەت {disabledAccount} لە ئێستادا لەکارخراوە چونکە تۆ چوویتە {movedToAccount}.",
|
||||||
"navigation_bar.about": "دەربارە",
|
"navigation_bar.about": "دەربارە",
|
||||||
|
@ -132,7 +132,6 @@
|
|||||||
"empty_column.hashtag": "Ùn c'hè ancu nunda quì.",
|
"empty_column.hashtag": "Ùn c'hè ancu nunda quì.",
|
||||||
"empty_column.home": "A vostr'accolta hè viota! Pudete andà nant'à {public} o pruvà a ricerca per truvà parsone da siguità.",
|
"empty_column.home": "A vostr'accolta hè viota! Pudete andà nant'à {public} o pruvà a ricerca per truvà parsone da siguità.",
|
||||||
"empty_column.list": "Ùn c'hè ancu nunda quì. Quandu membri di sta lista manderanu novi statuti, i vidarete quì.",
|
"empty_column.list": "Ùn c'hè ancu nunda quì. Quandu membri di sta lista manderanu novi statuti, i vidarete quì.",
|
||||||
"empty_column.lists": "Ùn avete manc'una lista. Quandu farete una, sarà mustrata quì.",
|
|
||||||
"empty_column.mutes": "Per avà ùn avete manc'un utilizatore piattatu.",
|
"empty_column.mutes": "Per avà ùn avete manc'un utilizatore piattatu.",
|
||||||
"empty_column.notifications": "Ùn avete ancu nisuna nutificazione. Interact with others to start the conversation.",
|
"empty_column.notifications": "Ùn avete ancu nisuna nutificazione. Interact with others to start the conversation.",
|
||||||
"empty_column.public": "Ùn c'hè nunda quì! Scrivete qualcosa in pubblicu o seguitate utilizatori d'altri servori per empie a linea pubblica",
|
"empty_column.public": "Ùn c'hè nunda quì! Scrivete qualcosa in pubblicu o seguitate utilizatori d'altri servori per empie a linea pubblica",
|
||||||
@ -198,19 +197,11 @@
|
|||||||
"lightbox.close": "Chjudà",
|
"lightbox.close": "Chjudà",
|
||||||
"lightbox.next": "Siguente",
|
"lightbox.next": "Siguente",
|
||||||
"lightbox.previous": "Pricidente",
|
"lightbox.previous": "Pricidente",
|
||||||
"lists.account.add": "Aghjunghje à a lista",
|
|
||||||
"lists.account.remove": "Toglie di a lista",
|
|
||||||
"lists.delete": "Toglie a lista",
|
"lists.delete": "Toglie a lista",
|
||||||
"lists.edit": "Mudificà a lista",
|
"lists.edit": "Mudificà a lista",
|
||||||
"lists.edit.submit": "Cambià u titulu",
|
|
||||||
"lists.new.create": "Aghjunghje",
|
|
||||||
"lists.new.title_placeholder": "Titulu di a lista",
|
|
||||||
"lists.replies_policy.followed": "Tutti i vostri abbunamenti",
|
"lists.replies_policy.followed": "Tutti i vostri abbunamenti",
|
||||||
"lists.replies_policy.list": "Membri di a lista",
|
"lists.replies_policy.list": "Membri di a lista",
|
||||||
"lists.replies_policy.none": "Nimu",
|
"lists.replies_policy.none": "Nimu",
|
||||||
"lists.replies_policy.title": "Vede e risposte à:",
|
|
||||||
"lists.search": "Circà indè i vostr'abbunamenti",
|
|
||||||
"lists.subheading": "E vo liste",
|
|
||||||
"load_pending": "{count, plural, one {# entrata nova} other {# entrate nove}}",
|
"load_pending": "{count, plural, one {# entrata nova} other {# entrate nove}}",
|
||||||
"navigation_bar.blocks": "Utilizatori bluccati",
|
"navigation_bar.blocks": "Utilizatori bluccati",
|
||||||
"navigation_bar.bookmarks": "Segnalibri",
|
"navigation_bar.bookmarks": "Segnalibri",
|
||||||
|
@ -267,7 +267,6 @@
|
|||||||
"empty_column.hashtag": "Pod tímto hashtagem zde zatím nic není.",
|
"empty_column.hashtag": "Pod tímto hashtagem zde zatím nic není.",
|
||||||
"empty_column.home": "Vaše domovská časová osa je prázdná! Naplňte ji sledováním dalších lidí.",
|
"empty_column.home": "Vaše domovská časová osa je prázdná! Naplňte ji sledováním dalších lidí.",
|
||||||
"empty_column.list": "V tomto seznamu zatím nic není. Až nějaký člen z tohoto seznamu zveřejní nový příspěvek, objeví se zde.",
|
"empty_column.list": "V tomto seznamu zatím nic není. Až nějaký člen z tohoto seznamu zveřejní nový příspěvek, objeví se zde.",
|
||||||
"empty_column.lists": "Zatím nemáte žádné seznamy. Až nějaký vytvoříte, zobrazí se zde.",
|
|
||||||
"empty_column.mutes": "Zatím jste neskryli žádného uživatele.",
|
"empty_column.mutes": "Zatím jste neskryli žádného uživatele.",
|
||||||
"empty_column.notification_requests": "Vyčištěno! Nic tu není. Jakmile obdržíš nové notifikace, objeví se zde podle tvého nastavení.",
|
"empty_column.notification_requests": "Vyčištěno! Nic tu není. Jakmile obdržíš nové notifikace, objeví se zde podle tvého nastavení.",
|
||||||
"empty_column.notifications": "Zatím nemáte žádná oznámení. Až s vámi někdo bude interagovat, uvidíte to zde.",
|
"empty_column.notifications": "Zatím nemáte žádná oznámení. Až s vámi někdo bude interagovat, uvidíte to zde.",
|
||||||
@ -415,20 +414,11 @@
|
|||||||
"link_preview.author": "Podle {name}",
|
"link_preview.author": "Podle {name}",
|
||||||
"link_preview.more_from_author": "Více od {name}",
|
"link_preview.more_from_author": "Více od {name}",
|
||||||
"link_preview.shares": "{count, plural, one {{counter} příspěvek} few {{counter} příspěvky} many {{counter} příspěvků} other {{counter} příspěvků}}",
|
"link_preview.shares": "{count, plural, one {{counter} příspěvek} few {{counter} příspěvky} many {{counter} příspěvků} other {{counter} příspěvků}}",
|
||||||
"lists.account.add": "Přidat do seznamu",
|
|
||||||
"lists.account.remove": "Odebrat ze seznamu",
|
|
||||||
"lists.delete": "Smazat seznam",
|
"lists.delete": "Smazat seznam",
|
||||||
"lists.edit": "Upravit seznam",
|
"lists.edit": "Upravit seznam",
|
||||||
"lists.edit.submit": "Změnit název",
|
|
||||||
"lists.exclusive": "Skrýt tyto příspěvky z domovské stránky",
|
|
||||||
"lists.new.create": "Přidat seznam",
|
|
||||||
"lists.new.title_placeholder": "Název nového seznamu",
|
|
||||||
"lists.replies_policy.followed": "Sledovaným uživatelům",
|
"lists.replies_policy.followed": "Sledovaným uživatelům",
|
||||||
"lists.replies_policy.list": "Členům seznamu",
|
"lists.replies_policy.list": "Členům seznamu",
|
||||||
"lists.replies_policy.none": "Nikomu",
|
"lists.replies_policy.none": "Nikomu",
|
||||||
"lists.replies_policy.title": "Odpovědi zobrazovat:",
|
|
||||||
"lists.search": "Hledejte mezi lidmi, které sledujete",
|
|
||||||
"lists.subheading": "Vaše seznamy",
|
|
||||||
"load_pending": "{count, plural, one {# nová položka} few {# nové položky} many {# nových položek} other {# nových položek}}",
|
"load_pending": "{count, plural, one {# nová položka} few {# nové položky} many {# nových položek} other {# nových položek}}",
|
||||||
"loading_indicator.label": "Načítání…",
|
"loading_indicator.label": "Načítání…",
|
||||||
"moved_to_account_banner.text": "Váš účet {disabledAccount} je momentálně deaktivován, protože jste se přesunul/a na {movedToAccount}.",
|
"moved_to_account_banner.text": "Váš účet {disabledAccount} je momentálně deaktivován, protože jste se přesunul/a na {movedToAccount}.",
|
||||||
|
@ -292,7 +292,6 @@
|
|||||||
"empty_column.hashtag": "Nid oes dim ar yr hashnod hwn eto.",
|
"empty_column.hashtag": "Nid oes dim ar yr hashnod hwn eto.",
|
||||||
"empty_column.home": "Mae eich ffrwd gartref yn wag! Dilynwch fwy o bobl i'w llenwi.",
|
"empty_column.home": "Mae eich ffrwd gartref yn wag! Dilynwch fwy o bobl i'w llenwi.",
|
||||||
"empty_column.list": "Does dim yn y rhestr yma eto. Pan fydd aelodau'r rhestr yn cyhoeddi postiad newydd, mi fydd yn ymddangos yma.",
|
"empty_column.list": "Does dim yn y rhestr yma eto. Pan fydd aelodau'r rhestr yn cyhoeddi postiad newydd, mi fydd yn ymddangos yma.",
|
||||||
"empty_column.lists": "Nid oes gennych unrhyw restrau eto. Pan fyddwch yn creu un, mi fydd yn ymddangos yma.",
|
|
||||||
"empty_column.mutes": "Nid ydych wedi tewi unrhyw ddefnyddwyr eto.",
|
"empty_column.mutes": "Nid ydych wedi tewi unrhyw ddefnyddwyr eto.",
|
||||||
"empty_column.notification_requests": "Dim i boeni amdano! Does dim byd yma. Pan fyddwch yn derbyn hysbysiadau newydd, byddan nhw'n ymddangos yma yn ôl eich gosodiadau.",
|
"empty_column.notification_requests": "Dim i boeni amdano! Does dim byd yma. Pan fyddwch yn derbyn hysbysiadau newydd, byddan nhw'n ymddangos yma yn ôl eich gosodiadau.",
|
||||||
"empty_column.notifications": "Nid oes gennych unrhyw hysbysiadau eto. Rhyngweithiwch ag eraill i ddechrau'r sgwrs.",
|
"empty_column.notifications": "Nid oes gennych unrhyw hysbysiadau eto. Rhyngweithiwch ag eraill i ddechrau'r sgwrs.",
|
||||||
@ -465,20 +464,11 @@
|
|||||||
"link_preview.author": "Gan {name}",
|
"link_preview.author": "Gan {name}",
|
||||||
"link_preview.more_from_author": "Mwy gan {name}",
|
"link_preview.more_from_author": "Mwy gan {name}",
|
||||||
"link_preview.shares": "{count, plural, one {{counter} postiad } two {{counter} bostiad } few {{counter} postiad} many {{counter} postiad} other {{counter} postiad}}",
|
"link_preview.shares": "{count, plural, one {{counter} postiad } two {{counter} bostiad } few {{counter} postiad} many {{counter} postiad} other {{counter} postiad}}",
|
||||||
"lists.account.add": "Ychwanegu at restr",
|
|
||||||
"lists.account.remove": "Tynnu o'r rhestr",
|
|
||||||
"lists.delete": "Dileu rhestr",
|
"lists.delete": "Dileu rhestr",
|
||||||
"lists.edit": "Golygu rhestr",
|
"lists.edit": "Golygu rhestr",
|
||||||
"lists.edit.submit": "Newid teitl",
|
|
||||||
"lists.exclusive": "Cuddio'r postiadau hyn o'r ffrwd gartref",
|
|
||||||
"lists.new.create": "Ychwanegu rhestr",
|
|
||||||
"lists.new.title_placeholder": "Teitl rhestr newydd",
|
|
||||||
"lists.replies_policy.followed": "Unrhyw ddefnyddiwr sy'n cael ei ddilyn",
|
"lists.replies_policy.followed": "Unrhyw ddefnyddiwr sy'n cael ei ddilyn",
|
||||||
"lists.replies_policy.list": "Aelodau'r rhestr",
|
"lists.replies_policy.list": "Aelodau'r rhestr",
|
||||||
"lists.replies_policy.none": "Neb",
|
"lists.replies_policy.none": "Neb",
|
||||||
"lists.replies_policy.title": "Dangos atebion i:",
|
|
||||||
"lists.search": "Chwilio ymysg pobl rydych yn eu dilyn",
|
|
||||||
"lists.subheading": "Eich rhestrau",
|
|
||||||
"load_pending": "{count, plural, one {# eitem newydd} other {# eitem newydd}}",
|
"load_pending": "{count, plural, one {# eitem newydd} other {# eitem newydd}}",
|
||||||
"loading_indicator.label": "Yn llwytho…",
|
"loading_indicator.label": "Yn llwytho…",
|
||||||
"media_gallery.hide": "Cuddio",
|
"media_gallery.hide": "Cuddio",
|
||||||
|
@ -140,13 +140,16 @@
|
|||||||
"column.blocks": "Blokerede brugere",
|
"column.blocks": "Blokerede brugere",
|
||||||
"column.bookmarks": "Bogmærker",
|
"column.bookmarks": "Bogmærker",
|
||||||
"column.community": "Lokal tidslinje",
|
"column.community": "Lokal tidslinje",
|
||||||
|
"column.create_list": "Opret liste",
|
||||||
"column.direct": "Private omtaler",
|
"column.direct": "Private omtaler",
|
||||||
"column.directory": "Tjek profiler",
|
"column.directory": "Tjek profiler",
|
||||||
"column.domain_blocks": "Blokerede domæner",
|
"column.domain_blocks": "Blokerede domæner",
|
||||||
|
"column.edit_list": "Redigér liste",
|
||||||
"column.favourites": "Favoritter",
|
"column.favourites": "Favoritter",
|
||||||
"column.firehose": "Live feeds",
|
"column.firehose": "Live feeds",
|
||||||
"column.follow_requests": "Følgeanmodninger",
|
"column.follow_requests": "Følgeanmodninger",
|
||||||
"column.home": "Hjem",
|
"column.home": "Hjem",
|
||||||
|
"column.list_members": "Håndtér listemedlemmer",
|
||||||
"column.lists": "Lister",
|
"column.lists": "Lister",
|
||||||
"column.mutes": "Skjulte brugere (mutede)",
|
"column.mutes": "Skjulte brugere (mutede)",
|
||||||
"column.notifications": "Notifikationer",
|
"column.notifications": "Notifikationer",
|
||||||
@ -292,7 +295,6 @@
|
|||||||
"empty_column.hashtag": "Der er intet med dette hashtag endnu.",
|
"empty_column.hashtag": "Der er intet med dette hashtag endnu.",
|
||||||
"empty_column.home": "Din hjemmetidslinje er tom! Følg nogle personer, for at udfylde den. {suggestions}",
|
"empty_column.home": "Din hjemmetidslinje er tom! Følg nogle personer, for at udfylde den. {suggestions}",
|
||||||
"empty_column.list": "Der er ikke noget på denne liste endnu. Når medlemmer af listen udgiver nye indlæg vil de fremgå hér.",
|
"empty_column.list": "Der er ikke noget på denne liste endnu. Når medlemmer af listen udgiver nye indlæg vil de fremgå hér.",
|
||||||
"empty_column.lists": "Du har endnu ingen lister. Når du opretter én, vil den fremgå hér.",
|
|
||||||
"empty_column.mutes": "Du har endnu ikke skjult (muted) nogle brugere.",
|
"empty_column.mutes": "Du har endnu ikke skjult (muted) nogle brugere.",
|
||||||
"empty_column.notification_requests": "Alt er klar! Der er intet her. Når der modtages nye notifikationer, fremgår de her jf. dine indstillinger.",
|
"empty_column.notification_requests": "Alt er klar! Der er intet her. Når der modtages nye notifikationer, fremgår de her jf. dine indstillinger.",
|
||||||
"empty_column.notifications": "Du har endnu ingen notifikationer. Når andre interagerer med dig, vil det fremgå hér.",
|
"empty_column.notifications": "Du har endnu ingen notifikationer. Når andre interagerer med dig, vil det fremgå hér.",
|
||||||
@ -465,20 +467,31 @@
|
|||||||
"link_preview.author": "Af {name}",
|
"link_preview.author": "Af {name}",
|
||||||
"link_preview.more_from_author": "Mere fra {name}",
|
"link_preview.more_from_author": "Mere fra {name}",
|
||||||
"link_preview.shares": "{count, plural, one {{counter} indlæg} other {{counter} indlæg}}",
|
"link_preview.shares": "{count, plural, one {{counter} indlæg} other {{counter} indlæg}}",
|
||||||
"lists.account.add": "Føj til liste",
|
"lists.add_member": "Tilføj",
|
||||||
"lists.account.remove": "Fjern fra liste",
|
"lists.add_to_list": "Føj til liste",
|
||||||
|
"lists.add_to_lists": "Føj {name} til lister",
|
||||||
|
"lists.create": "Opret",
|
||||||
|
"lists.create_a_list_to_organize": "Opret en ny liste til organisering af hjemmefeed",
|
||||||
|
"lists.create_list": "Opret liste",
|
||||||
"lists.delete": "Slet liste",
|
"lists.delete": "Slet liste",
|
||||||
|
"lists.done": "Færdig",
|
||||||
"lists.edit": "Redigér liste",
|
"lists.edit": "Redigér liste",
|
||||||
"lists.edit.submit": "Skift titel",
|
"lists.exclusive": "Skjul medlemmer i Hjem",
|
||||||
"lists.exclusive": "Skjul disse indlæg hjemmefra",
|
"lists.exclusive_hint": "Er nogen er på denne liste, skjul personen i hjemme-feeds for at undgå at se vedkommendes indlæg to gange.",
|
||||||
"lists.new.create": "Tilføj liste",
|
"lists.find_users_to_add": "Find brugere at tilføje",
|
||||||
"lists.new.title_placeholder": "Ny listetitel",
|
"lists.list_members_count": "{count, plural, one {# medlem} other {# medlemmer}}",
|
||||||
|
"lists.list_name": "Listetitel",
|
||||||
|
"lists.new_list_name": "Ny listetitel",
|
||||||
|
"lists.no_lists_yet": "Ingen lister endnu.",
|
||||||
|
"lists.no_members_yet": "Ingen medlemmer endnu.",
|
||||||
|
"lists.no_results_found": "Ingen resultater fundet.",
|
||||||
|
"lists.remove_member": "Fjern",
|
||||||
"lists.replies_policy.followed": "Enhver bruger, der følges",
|
"lists.replies_policy.followed": "Enhver bruger, der følges",
|
||||||
"lists.replies_policy.list": "Listemedlemmer",
|
"lists.replies_policy.list": "Listemedlemmer",
|
||||||
"lists.replies_policy.none": "Ingen",
|
"lists.replies_policy.none": "Ingen",
|
||||||
"lists.replies_policy.title": "Vis svar til:",
|
"lists.save": "Gem",
|
||||||
"lists.search": "Søg blandt personer, som følges",
|
"lists.search_placeholder": "Søg efter folk, man følger",
|
||||||
"lists.subheading": "Dine lister",
|
"lists.show_replies_to": "Medtag svar fra listemedlemmer til",
|
||||||
"load_pending": "{count, plural, one {# nyt emne} other {# nye emner}}",
|
"load_pending": "{count, plural, one {# nyt emne} other {# nye emner}}",
|
||||||
"loading_indicator.label": "Indlæser…",
|
"loading_indicator.label": "Indlæser…",
|
||||||
"media_gallery.hide": "Skjul",
|
"media_gallery.hide": "Skjul",
|
||||||
|
@ -89,9 +89,9 @@
|
|||||||
"announcement.announcement": "Ankündigung",
|
"announcement.announcement": "Ankündigung",
|
||||||
"annual_report.summary.archetype.booster": "Trendjäger*in",
|
"annual_report.summary.archetype.booster": "Trendjäger*in",
|
||||||
"annual_report.summary.archetype.lurker": "Beobachter*in",
|
"annual_report.summary.archetype.lurker": "Beobachter*in",
|
||||||
"annual_report.summary.archetype.oracle": "Orakel",
|
"annual_report.summary.archetype.oracle": "Universaltalent",
|
||||||
"annual_report.summary.archetype.pollster": "Meinungsforscher*in",
|
"annual_report.summary.archetype.pollster": "Meinungsforscher*in",
|
||||||
"annual_report.summary.archetype.replier": "Geselliger Schmetterling",
|
"annual_report.summary.archetype.replier": "Sozialer Schmetterling",
|
||||||
"annual_report.summary.followers.followers": "Follower",
|
"annual_report.summary.followers.followers": "Follower",
|
||||||
"annual_report.summary.followers.total": "{count} insgesamt",
|
"annual_report.summary.followers.total": "{count} insgesamt",
|
||||||
"annual_report.summary.here_it_is": "Dein Jahresrückblick für {year}:",
|
"annual_report.summary.here_it_is": "Dein Jahresrückblick für {year}:",
|
||||||
@ -113,7 +113,7 @@
|
|||||||
"block_modal.show_more": "Mehr anzeigen",
|
"block_modal.show_more": "Mehr anzeigen",
|
||||||
"block_modal.they_cant_mention": "Das Profil wird dich nicht erwähnen oder dir folgen können.",
|
"block_modal.they_cant_mention": "Das Profil wird dich nicht erwähnen oder dir folgen können.",
|
||||||
"block_modal.they_cant_see_posts": "Deine Beiträge können nicht mehr angesehen werden und du wirst deren Beiträge nicht mehr sehen.",
|
"block_modal.they_cant_see_posts": "Deine Beiträge können nicht mehr angesehen werden und du wirst deren Beiträge nicht mehr sehen.",
|
||||||
"block_modal.they_will_know": "Es wird erkennbar sein, dass dieses Profil blockiert wurde.",
|
"block_modal.they_will_know": "Das Profil wird erkennen können, dass du es blockiert hast.",
|
||||||
"block_modal.title": "Profil blockieren?",
|
"block_modal.title": "Profil blockieren?",
|
||||||
"block_modal.you_wont_see_mentions": "Du wirst keine Beiträge sehen, die dieses Profil erwähnen.",
|
"block_modal.you_wont_see_mentions": "Du wirst keine Beiträge sehen, die dieses Profil erwähnen.",
|
||||||
"boost_modal.combo": "Mit {combo} erscheint dieses Fenster beim nächsten Mal nicht mehr",
|
"boost_modal.combo": "Mit {combo} erscheint dieses Fenster beim nächsten Mal nicht mehr",
|
||||||
@ -140,13 +140,16 @@
|
|||||||
"column.blocks": "Blockierte Profile",
|
"column.blocks": "Blockierte Profile",
|
||||||
"column.bookmarks": "Lesezeichen",
|
"column.bookmarks": "Lesezeichen",
|
||||||
"column.community": "Lokale Timeline",
|
"column.community": "Lokale Timeline",
|
||||||
|
"column.create_list": "Liste erstellen",
|
||||||
"column.direct": "Private Erwähnungen",
|
"column.direct": "Private Erwähnungen",
|
||||||
"column.directory": "Profile durchsuchen",
|
"column.directory": "Profile durchsuchen",
|
||||||
"column.domain_blocks": "Blockierte Domains",
|
"column.domain_blocks": "Blockierte Domains",
|
||||||
|
"column.edit_list": "Liste bearbeiten",
|
||||||
"column.favourites": "Favoriten",
|
"column.favourites": "Favoriten",
|
||||||
"column.firehose": "Live-Feeds",
|
"column.firehose": "Live-Feeds",
|
||||||
"column.follow_requests": "Follower-Anfragen",
|
"column.follow_requests": "Follower-Anfragen",
|
||||||
"column.home": "Startseite",
|
"column.home": "Startseite",
|
||||||
|
"column.list_members": "Listenmitglieder verwalten",
|
||||||
"column.lists": "Listen",
|
"column.lists": "Listen",
|
||||||
"column.mutes": "Stummgeschaltete Profile",
|
"column.mutes": "Stummgeschaltete Profile",
|
||||||
"column.notifications": "Benachrichtigungen",
|
"column.notifications": "Benachrichtigungen",
|
||||||
@ -252,7 +255,7 @@
|
|||||||
"domain_pill.their_server": "Deren digitale Heimat. Hier „leben“ alle Beiträge von diesem Profil.",
|
"domain_pill.their_server": "Deren digitale Heimat. Hier „leben“ alle Beiträge von diesem Profil.",
|
||||||
"domain_pill.their_username": "Deren eindeutigen Identität auf dem betreffenden Server. Es ist möglich, Profile mit dem gleichen Profilnamen auf verschiedenen Servern zu finden.",
|
"domain_pill.their_username": "Deren eindeutigen Identität auf dem betreffenden Server. Es ist möglich, Profile mit dem gleichen Profilnamen auf verschiedenen Servern zu finden.",
|
||||||
"domain_pill.username": "Profilname",
|
"domain_pill.username": "Profilname",
|
||||||
"domain_pill.whats_in_a_handle": "Was ist Teil der Adresse?",
|
"domain_pill.whats_in_a_handle": "Woraus besteht eine Adresse?",
|
||||||
"domain_pill.who_they_are": "Adressen teilen mit, wer jemand ist und wo sich jemand aufhält. Daher kannst du mit Leuten im gesamten Social Web interagieren, wenn es eine durch <button>ActivityPub angetriebene Plattform</button> ist.",
|
"domain_pill.who_they_are": "Adressen teilen mit, wer jemand ist und wo sich jemand aufhält. Daher kannst du mit Leuten im gesamten Social Web interagieren, wenn es eine durch <button>ActivityPub angetriebene Plattform</button> ist.",
|
||||||
"domain_pill.who_you_are": "Deine Adresse teilt mit, wer du bist und wo du dich aufhältst. Daher können andere Leute im gesamten Social Web mit dir interagieren, wenn es eine durch <button>ActivityPub angetriebene Plattform</button> ist.",
|
"domain_pill.who_you_are": "Deine Adresse teilt mit, wer du bist und wo du dich aufhältst. Daher können andere Leute im gesamten Social Web mit dir interagieren, wenn es eine durch <button>ActivityPub angetriebene Plattform</button> ist.",
|
||||||
"domain_pill.your_handle": "Deine Adresse:",
|
"domain_pill.your_handle": "Deine Adresse:",
|
||||||
@ -292,7 +295,6 @@
|
|||||||
"empty_column.hashtag": "Unter diesem Hashtag gibt es noch nichts.",
|
"empty_column.hashtag": "Unter diesem Hashtag gibt es noch nichts.",
|
||||||
"empty_column.home": "Die Timeline deiner Startseite ist leer! Folge mehr Leuten, um sie zu füllen.",
|
"empty_column.home": "Die Timeline deiner Startseite ist leer! Folge mehr Leuten, um sie zu füllen.",
|
||||||
"empty_column.list": "Diese Liste ist derzeit leer. Wenn Konten auf dieser Liste neue Beiträge veröffentlichen, werden sie hier erscheinen.",
|
"empty_column.list": "Diese Liste ist derzeit leer. Wenn Konten auf dieser Liste neue Beiträge veröffentlichen, werden sie hier erscheinen.",
|
||||||
"empty_column.lists": "Du hast noch keine Listen. Sobald du eine anlegst, wird sie hier erscheinen.",
|
|
||||||
"empty_column.mutes": "Du hast keine Profile stummgeschaltet.",
|
"empty_column.mutes": "Du hast keine Profile stummgeschaltet.",
|
||||||
"empty_column.notification_requests": "Alles klar! Hier gibt es nichts. Wenn Sie neue Mitteilungen erhalten, werden diese entsprechend Ihren Einstellungen hier angezeigt.",
|
"empty_column.notification_requests": "Alles klar! Hier gibt es nichts. Wenn Sie neue Mitteilungen erhalten, werden diese entsprechend Ihren Einstellungen hier angezeigt.",
|
||||||
"empty_column.notifications": "Du hast noch keine Benachrichtigungen. Sobald andere Personen mit dir interagieren, wirst du hier darüber informiert.",
|
"empty_column.notifications": "Du hast noch keine Benachrichtigungen. Sobald andere Personen mit dir interagieren, wirst du hier darüber informiert.",
|
||||||
@ -328,9 +330,9 @@
|
|||||||
"filter_warning.matches_filter": "Übereinstimmend mit dem Filter „<span>{title}</span>“",
|
"filter_warning.matches_filter": "Übereinstimmend mit dem Filter „<span>{title}</span>“",
|
||||||
"filtered_notifications_banner.pending_requests": "Von {count, plural, =0 {keinem, den} one {einer Person, die} other {# Personen, die}} du möglicherweise kennst",
|
"filtered_notifications_banner.pending_requests": "Von {count, plural, =0 {keinem, den} one {einer Person, die} other {# Personen, die}} du möglicherweise kennst",
|
||||||
"filtered_notifications_banner.title": "Gefilterte Benachrichtigungen",
|
"filtered_notifications_banner.title": "Gefilterte Benachrichtigungen",
|
||||||
"firehose.all": "Alles",
|
"firehose.all": "Alle Server",
|
||||||
"firehose.local": "Dieser Server",
|
"firehose.local": "Dieser Server",
|
||||||
"firehose.remote": "Andere Server",
|
"firehose.remote": "Externe Server",
|
||||||
"follow_request.authorize": "Genehmigen",
|
"follow_request.authorize": "Genehmigen",
|
||||||
"follow_request.reject": "Ablehnen",
|
"follow_request.reject": "Ablehnen",
|
||||||
"follow_requests.unlocked_explanation": "Auch wenn dein Konto öffentlich bzw. nicht geschützt ist, haben die Moderator*innen von {domain} gedacht, dass du diesen Follower lieber manuell bestätigen solltest.",
|
"follow_requests.unlocked_explanation": "Auch wenn dein Konto öffentlich bzw. nicht geschützt ist, haben die Moderator*innen von {domain} gedacht, dass du diesen Follower lieber manuell bestätigen solltest.",
|
||||||
@ -465,20 +467,32 @@
|
|||||||
"link_preview.author": "Von {name}",
|
"link_preview.author": "Von {name}",
|
||||||
"link_preview.more_from_author": "Mehr von {name}",
|
"link_preview.more_from_author": "Mehr von {name}",
|
||||||
"link_preview.shares": "{count, plural, one {{counter} Beitrag} other {{counter} Beiträge}}",
|
"link_preview.shares": "{count, plural, one {{counter} Beitrag} other {{counter} Beiträge}}",
|
||||||
"lists.account.add": "Zur Liste hinzufügen",
|
"lists.add_member": "Hinzufügen",
|
||||||
"lists.account.remove": "Von der Liste entfernen",
|
"lists.add_to_list": "Zur Liste hinzufügen",
|
||||||
|
"lists.add_to_lists": "{name} zu Listen hinzufügen",
|
||||||
|
"lists.create": "Erstellen",
|
||||||
|
"lists.create_a_list_to_organize": "Erstelle eine neue Liste, um deine Startseite zu organisieren",
|
||||||
|
"lists.create_list": "Liste erstellen",
|
||||||
"lists.delete": "Liste löschen",
|
"lists.delete": "Liste löschen",
|
||||||
|
"lists.done": "Fertig",
|
||||||
"lists.edit": "Liste bearbeiten",
|
"lists.edit": "Liste bearbeiten",
|
||||||
"lists.edit.submit": "Titel ändern",
|
"lists.exclusive": "Mitglieder auf der Startseite ausblenden",
|
||||||
"lists.exclusive": "Diese Beiträge in der Startseite ausblenden",
|
"lists.exclusive_hint": "Profile, die sich auf dieser Liste befinden, werden nicht auf deiner Startseite angezeigt, damit deren Beiträge nicht doppelt erscheinen.",
|
||||||
"lists.new.create": "Neue Liste erstellen",
|
"lists.find_users_to_add": "Suche nach Profilen, um sie hinzuzufügen",
|
||||||
"lists.new.title_placeholder": "Titel der neuen Liste",
|
"lists.list_members": "Listenmitglieder",
|
||||||
|
"lists.list_members_count": "{count, plural, one {# Mitglied} other {# Mitglieder}}",
|
||||||
|
"lists.list_name": "Titel der Liste",
|
||||||
|
"lists.new_list_name": "Neuer Listentitel",
|
||||||
|
"lists.no_lists_yet": "Noch keine Listen vorhanden.",
|
||||||
|
"lists.no_members_yet": "Keine Mitglieder vorhanden.",
|
||||||
|
"lists.no_results_found": "Keine Suchergebnisse.",
|
||||||
|
"lists.remove_member": "Entfernen",
|
||||||
"lists.replies_policy.followed": "Alle folgenden Profile",
|
"lists.replies_policy.followed": "Alle folgenden Profile",
|
||||||
"lists.replies_policy.list": "Mitglieder der Liste",
|
"lists.replies_policy.list": "Mitglieder der Liste",
|
||||||
"lists.replies_policy.none": "Niemanden",
|
"lists.replies_policy.none": "Niemanden",
|
||||||
"lists.replies_policy.title": "Antworten anzeigen für:",
|
"lists.save": "Speichern",
|
||||||
"lists.search": "Suche nach Leuten, denen du folgst",
|
"lists.search_placeholder": "Nach Profilen suchen, denen du folgst",
|
||||||
"lists.subheading": "Deine Listen",
|
"lists.show_replies_to": "Antworten von Listenmitgliedern einbeziehen für …",
|
||||||
"load_pending": "{count, plural, one {# neuer Beitrag} other {# neue Beiträge}}",
|
"load_pending": "{count, plural, one {# neuer Beitrag} other {# neue Beiträge}}",
|
||||||
"loading_indicator.label": "Wird geladen …",
|
"loading_indicator.label": "Wird geladen …",
|
||||||
"media_gallery.hide": "Ausblenden",
|
"media_gallery.hide": "Ausblenden",
|
||||||
@ -486,12 +500,12 @@
|
|||||||
"mute_modal.hide_from_notifications": "Benachrichtigungen ausblenden",
|
"mute_modal.hide_from_notifications": "Benachrichtigungen ausblenden",
|
||||||
"mute_modal.hide_options": "Einstellungen ausblenden",
|
"mute_modal.hide_options": "Einstellungen ausblenden",
|
||||||
"mute_modal.indefinite": "Bis ich die Stummschaltung aufhebe",
|
"mute_modal.indefinite": "Bis ich die Stummschaltung aufhebe",
|
||||||
"mute_modal.show_options": "Einstellungen anzeigen",
|
"mute_modal.show_options": "Optionen anzeigen",
|
||||||
"mute_modal.they_can_mention_and_follow": "Das Profil wird dich weiterhin erwähnen und dir folgen können, aber du wirst davon nichts sehen.",
|
"mute_modal.they_can_mention_and_follow": "Das Profil wird dich weiterhin erwähnen und dir folgen können, aber du wirst davon nichts sehen.",
|
||||||
"mute_modal.they_wont_know": "Es wird nicht erkennbar sein, dass dieses Profil stummgeschaltet wurde.",
|
"mute_modal.they_wont_know": "Das Profil wird nicht erkennen können, dass du es stummgeschaltet hast.",
|
||||||
"mute_modal.title": "Profil stummschalten?",
|
"mute_modal.title": "Profil stummschalten?",
|
||||||
"mute_modal.you_wont_see_mentions": "Du wirst keine Beiträge sehen, die dieses Profil erwähnen.",
|
"mute_modal.you_wont_see_mentions": "Du wirst keine Beiträge sehen, die dieses Profil erwähnen.",
|
||||||
"mute_modal.you_wont_see_posts": "Deine Beiträge können weiterhin angesehen werden, aber du wirst deren Beiträge nicht mehr sehen.",
|
"mute_modal.you_wont_see_posts": "Deine Beiträge können von diesem stummgeschalteten Profil weiterhin gesehen werden, aber du wirst dessen Beiträge nicht mehr sehen.",
|
||||||
"navigation_bar.about": "Über",
|
"navigation_bar.about": "Über",
|
||||||
"navigation_bar.administration": "Administration",
|
"navigation_bar.administration": "Administration",
|
||||||
"navigation_bar.advanced_interface": "Im erweiterten Webinterface öffnen",
|
"navigation_bar.advanced_interface": "Im erweiterten Webinterface öffnen",
|
||||||
@ -527,7 +541,7 @@
|
|||||||
"notification.admin.report_statuses_other": "{name} meldete {target}",
|
"notification.admin.report_statuses_other": "{name} meldete {target}",
|
||||||
"notification.admin.sign_up": "{name} registrierte sich",
|
"notification.admin.sign_up": "{name} registrierte sich",
|
||||||
"notification.admin.sign_up.name_and_others": "{name} und {count, plural, one {# weiteres Profil} other {# weitere Profile}} registrierten sich",
|
"notification.admin.sign_up.name_and_others": "{name} und {count, plural, one {# weiteres Profil} other {# weitere Profile}} registrierten sich",
|
||||||
"notification.annual_report.message": "Dein {year} #Wrapstodon erwartet dich! Lass deine Highlights und unvergesslichen Momente auf Mastodon erneut aufleben!",
|
"notification.annual_report.message": "Dein #Wrapstodon für {year} erwartet dich! Lass deine Highlights und unvergesslichen Momente auf Mastodon erneut aufleben!",
|
||||||
"notification.annual_report.view": "#Wrapstodon ansehen",
|
"notification.annual_report.view": "#Wrapstodon ansehen",
|
||||||
"notification.favourite": "{name} favorisierte deinen Beitrag",
|
"notification.favourite": "{name} favorisierte deinen Beitrag",
|
||||||
"notification.favourite.name_and_others_with_link": "{name} und <a>{count, plural, one {# weiteres Profil} other {# weitere Profile}}</a> favorisierten deinen Beitrag",
|
"notification.favourite.name_and_others_with_link": "{name} und <a>{count, plural, one {# weiteres Profil} other {# weitere Profile}}</a> favorisierten deinen Beitrag",
|
||||||
@ -688,7 +702,7 @@
|
|||||||
"poll_button.remove_poll": "Umfrage entfernen",
|
"poll_button.remove_poll": "Umfrage entfernen",
|
||||||
"privacy.change": "Sichtbarkeit anpassen",
|
"privacy.change": "Sichtbarkeit anpassen",
|
||||||
"privacy.direct.long": "Alle in diesem Beitrag erwähnten Profile",
|
"privacy.direct.long": "Alle in diesem Beitrag erwähnten Profile",
|
||||||
"privacy.direct.short": "Bestimmte Profile",
|
"privacy.direct.short": "Ausgewählte Profile",
|
||||||
"privacy.private.long": "Nur deine Follower",
|
"privacy.private.long": "Nur deine Follower",
|
||||||
"privacy.private.short": "Follower",
|
"privacy.private.short": "Follower",
|
||||||
"privacy.public.long": "Alle in und außerhalb von Mastodon",
|
"privacy.public.long": "Alle in und außerhalb von Mastodon",
|
||||||
|
@ -291,7 +291,6 @@
|
|||||||
"empty_column.hashtag": "Δεν υπάρχει ακόμα κάτι για αυτή την ετικέτα.",
|
"empty_column.hashtag": "Δεν υπάρχει ακόμα κάτι για αυτή την ετικέτα.",
|
||||||
"empty_column.home": "Η τοπική σου ροή είναι κενή! Πήγαινε στο {public} ή κάνε αναζήτηση για να ξεκινήσεις και να γνωρίσεις άλλους χρήστες.",
|
"empty_column.home": "Η τοπική σου ροή είναι κενή! Πήγαινε στο {public} ή κάνε αναζήτηση για να ξεκινήσεις και να γνωρίσεις άλλους χρήστες.",
|
||||||
"empty_column.list": "Δεν υπάρχει τίποτα σε αυτή τη λίστα ακόμα. Όταν τα μέλη της δημοσιεύσουν νέες καταστάσεις, θα εμφανιστούν εδώ.",
|
"empty_column.list": "Δεν υπάρχει τίποτα σε αυτή τη λίστα ακόμα. Όταν τα μέλη της δημοσιεύσουν νέες καταστάσεις, θα εμφανιστούν εδώ.",
|
||||||
"empty_column.lists": "Δεν έχεις καμία λίστα ακόμα. Μόλις φτιάξεις μια, θα εμφανιστεί εδώ.",
|
|
||||||
"empty_column.mutes": "Δεν έχεις κανένα χρήστη σε σίγαση ακόμα.",
|
"empty_column.mutes": "Δεν έχεις κανένα χρήστη σε σίγαση ακόμα.",
|
||||||
"empty_column.notification_requests": "Όλα καθαρά! Δεν υπάρχει τίποτα εδώ. Όταν λαμβάνεις νέες ειδοποιήσεις, αυτές θα εμφανίζονται εδώ σύμφωνα με τις ρυθμίσεις σου.",
|
"empty_column.notification_requests": "Όλα καθαρά! Δεν υπάρχει τίποτα εδώ. Όταν λαμβάνεις νέες ειδοποιήσεις, αυτές θα εμφανίζονται εδώ σύμφωνα με τις ρυθμίσεις σου.",
|
||||||
"empty_column.notifications": "Δεν έχεις ειδοποιήσεις ακόμα. Όταν άλλα άτομα αλληλεπιδράσουν μαζί σου, θα το δεις εδώ.",
|
"empty_column.notifications": "Δεν έχεις ειδοποιήσεις ακόμα. Όταν άλλα άτομα αλληλεπιδράσουν μαζί σου, θα το δεις εδώ.",
|
||||||
@ -464,20 +463,11 @@
|
|||||||
"link_preview.author": "Από {name}",
|
"link_preview.author": "Από {name}",
|
||||||
"link_preview.more_from_author": "Περισσότερα από {name}",
|
"link_preview.more_from_author": "Περισσότερα από {name}",
|
||||||
"link_preview.shares": "{count, plural, one {{counter} ανάρτηση} other {{counter} αναρτήσεις}}",
|
"link_preview.shares": "{count, plural, one {{counter} ανάρτηση} other {{counter} αναρτήσεις}}",
|
||||||
"lists.account.add": "Πρόσθεσε στη λίστα",
|
|
||||||
"lists.account.remove": "Βγάλε από τη λίστα",
|
|
||||||
"lists.delete": "Διαγραφή λίστας",
|
"lists.delete": "Διαγραφή λίστας",
|
||||||
"lists.edit": "Επεξεργασία λίστας",
|
"lists.edit": "Επεξεργασία λίστας",
|
||||||
"lists.edit.submit": "Αλλαγή τίτλου",
|
|
||||||
"lists.exclusive": "Απόκρυψη αυτών των αναρτήσεων από την αρχική",
|
|
||||||
"lists.new.create": "Προσθήκη λίστας",
|
|
||||||
"lists.new.title_placeholder": "Τίτλος νέας λίστα",
|
|
||||||
"lists.replies_policy.followed": "Οποιοσδήποτε χρήστης που ακολουθείς",
|
"lists.replies_policy.followed": "Οποιοσδήποτε χρήστης που ακολουθείς",
|
||||||
"lists.replies_policy.list": "Μέλη της λίστας",
|
"lists.replies_policy.list": "Μέλη της λίστας",
|
||||||
"lists.replies_policy.none": "Κανένας",
|
"lists.replies_policy.none": "Κανένας",
|
||||||
"lists.replies_policy.title": "Εμφάνιση απαντήσεων σε:",
|
|
||||||
"lists.search": "Αναζήτησε μεταξύ των ανθρώπων που ακουλουθείς",
|
|
||||||
"lists.subheading": "Οι λίστες σου",
|
|
||||||
"load_pending": "{count, plural, one {# νέο στοιχείο} other {# νέα στοιχεία}}",
|
"load_pending": "{count, plural, one {# νέο στοιχείο} other {# νέα στοιχεία}}",
|
||||||
"loading_indicator.label": "Φόρτωση…",
|
"loading_indicator.label": "Φόρτωση…",
|
||||||
"media_gallery.hide": "Απόκρυψη",
|
"media_gallery.hide": "Απόκρυψη",
|
||||||
|
@ -87,6 +87,25 @@
|
|||||||
"alert.unexpected.title": "Oops!",
|
"alert.unexpected.title": "Oops!",
|
||||||
"alt_text_badge.title": "Alt text",
|
"alt_text_badge.title": "Alt text",
|
||||||
"announcement.announcement": "Announcement",
|
"announcement.announcement": "Announcement",
|
||||||
|
"annual_report.summary.archetype.booster": "The cool-hunter",
|
||||||
|
"annual_report.summary.archetype.lurker": "The lurker",
|
||||||
|
"annual_report.summary.archetype.oracle": "The oracle",
|
||||||
|
"annual_report.summary.archetype.pollster": "The pollster",
|
||||||
|
"annual_report.summary.archetype.replier": "The social butterfly",
|
||||||
|
"annual_report.summary.followers.followers": "followers",
|
||||||
|
"annual_report.summary.followers.total": "{count} total",
|
||||||
|
"annual_report.summary.here_it_is": "Here is your {year} in review:",
|
||||||
|
"annual_report.summary.highlighted_post.by_favourites": "most favourited post",
|
||||||
|
"annual_report.summary.highlighted_post.by_reblogs": "most boosted post",
|
||||||
|
"annual_report.summary.highlighted_post.by_replies": "post with the most replies",
|
||||||
|
"annual_report.summary.highlighted_post.possessive": "{name}'s",
|
||||||
|
"annual_report.summary.most_used_app.most_used_app": "most used app",
|
||||||
|
"annual_report.summary.most_used_hashtag.most_used_hashtag": "most used hashtag",
|
||||||
|
"annual_report.summary.most_used_hashtag.none": "None",
|
||||||
|
"annual_report.summary.new_posts.new_posts": "new posts",
|
||||||
|
"annual_report.summary.percentile.text": "<topLabel>That puts you in the top</topLabel><percentage></percentage><bottomLabel>of Mastodon users.</bottomLabel>",
|
||||||
|
"annual_report.summary.percentile.we_wont_tell_bernie": "We won't tell Bernie.",
|
||||||
|
"annual_report.summary.thanks": "Thanks for being part of Mastodon!",
|
||||||
"attachments_list.unprocessed": "(unprocessed)",
|
"attachments_list.unprocessed": "(unprocessed)",
|
||||||
"audio.hide": "Hide audio",
|
"audio.hide": "Hide audio",
|
||||||
"block_modal.remote_users_caveat": "We will ask the server {domain} to respect your decision. However, compliance is not guaranteed since some servers may handle blocks differently. Public posts may still be visible to non-logged-in users.",
|
"block_modal.remote_users_caveat": "We will ask the server {domain} to respect your decision. However, compliance is not guaranteed since some servers may handle blocks differently. Public posts may still be visible to non-logged-in users.",
|
||||||
@ -158,6 +177,7 @@
|
|||||||
"compose_form.poll.duration": "Poll duration",
|
"compose_form.poll.duration": "Poll duration",
|
||||||
"compose_form.poll.multiple": "Multiple choice",
|
"compose_form.poll.multiple": "Multiple choice",
|
||||||
"compose_form.poll.option_placeholder": "Option {number}",
|
"compose_form.poll.option_placeholder": "Option {number}",
|
||||||
|
"compose_form.poll.single": "Single choice",
|
||||||
"compose_form.poll.switch_to_multiple": "Change poll to allow multiple choices",
|
"compose_form.poll.switch_to_multiple": "Change poll to allow multiple choices",
|
||||||
"compose_form.poll.switch_to_single": "Change poll to allow for a single choice",
|
"compose_form.poll.switch_to_single": "Change poll to allow for a single choice",
|
||||||
"compose_form.poll.type": "Style",
|
"compose_form.poll.type": "Style",
|
||||||
@ -196,6 +216,7 @@
|
|||||||
"confirmations.unfollow.title": "Unfollow user?",
|
"confirmations.unfollow.title": "Unfollow user?",
|
||||||
"content_warning.hide": "Hide post",
|
"content_warning.hide": "Hide post",
|
||||||
"content_warning.show": "Show anyway",
|
"content_warning.show": "Show anyway",
|
||||||
|
"content_warning.show_more": "Show more",
|
||||||
"conversation.delete": "Delete conversation",
|
"conversation.delete": "Delete conversation",
|
||||||
"conversation.mark_as_read": "Mark as read",
|
"conversation.mark_as_read": "Mark as read",
|
||||||
"conversation.open": "View conversation",
|
"conversation.open": "View conversation",
|
||||||
@ -271,7 +292,6 @@
|
|||||||
"empty_column.hashtag": "There is nothing in this hashtag yet.",
|
"empty_column.hashtag": "There is nothing in this hashtag yet.",
|
||||||
"empty_column.home": "Your home timeline is empty! Follow more people to fill it up.",
|
"empty_column.home": "Your home timeline is empty! Follow more people to fill it up.",
|
||||||
"empty_column.list": "There is nothing in this list yet. When members of this list post new statuses, they will appear here.",
|
"empty_column.list": "There is nothing in this list yet. When members of this list post new statuses, they will appear here.",
|
||||||
"empty_column.lists": "You don't have any lists yet. When you create one, it will show up here.",
|
|
||||||
"empty_column.mutes": "You haven't muted any users yet.",
|
"empty_column.mutes": "You haven't muted any users yet.",
|
||||||
"empty_column.notification_requests": "All clear! There is nothing here. When you receive new notifications, they will appear here according to your settings.",
|
"empty_column.notification_requests": "All clear! There is nothing here. When you receive new notifications, they will appear here according to your settings.",
|
||||||
"empty_column.notifications": "You don't have any notifications yet. When other people interact with you, you will see it here.",
|
"empty_column.notifications": "You don't have any notifications yet. When other people interact with you, you will see it here.",
|
||||||
@ -304,6 +324,7 @@
|
|||||||
"filter_modal.select_filter.subtitle": "Use an existing category or create a new one",
|
"filter_modal.select_filter.subtitle": "Use an existing category or create a new one",
|
||||||
"filter_modal.select_filter.title": "Filter this post",
|
"filter_modal.select_filter.title": "Filter this post",
|
||||||
"filter_modal.title.status": "Filter a post",
|
"filter_modal.title.status": "Filter a post",
|
||||||
|
"filter_warning.matches_filter": "Matches filter \"<span>{title}</span>\"",
|
||||||
"filtered_notifications_banner.pending_requests": "From {count, plural, =0 {no one} one {one person} other {# people}} you may know",
|
"filtered_notifications_banner.pending_requests": "From {count, plural, =0 {no one} one {one person} other {# people}} you may know",
|
||||||
"filtered_notifications_banner.title": "Filtered notifications",
|
"filtered_notifications_banner.title": "Filtered notifications",
|
||||||
"firehose.all": "All",
|
"firehose.all": "All",
|
||||||
@ -383,6 +404,7 @@
|
|||||||
"interaction_modal.description.follow": "With an account on Mastodon, you can follow {name} to receive their posts in your home feed.",
|
"interaction_modal.description.follow": "With an account on Mastodon, you can follow {name} to receive their posts in your home feed.",
|
||||||
"interaction_modal.description.reblog": "With an account on Mastodon, you can boost this post to share it with your own followers.",
|
"interaction_modal.description.reblog": "With an account on Mastodon, you can boost this post to share it with your own followers.",
|
||||||
"interaction_modal.description.reply": "With an account on Mastodon, you can respond to this post.",
|
"interaction_modal.description.reply": "With an account on Mastodon, you can respond to this post.",
|
||||||
|
"interaction_modal.description.vote": "With an account on Mastodon, you can vote in this poll.",
|
||||||
"interaction_modal.login.action": "Take me home",
|
"interaction_modal.login.action": "Take me home",
|
||||||
"interaction_modal.login.prompt": "Domain of your home server, e.g. mastodon.social",
|
"interaction_modal.login.prompt": "Domain of your home server, e.g. mastodon.social",
|
||||||
"interaction_modal.no_account_yet": "Not on Mastodon?",
|
"interaction_modal.no_account_yet": "Not on Mastodon?",
|
||||||
@ -394,6 +416,7 @@
|
|||||||
"interaction_modal.title.follow": "Follow {name}",
|
"interaction_modal.title.follow": "Follow {name}",
|
||||||
"interaction_modal.title.reblog": "Boost {name}'s post",
|
"interaction_modal.title.reblog": "Boost {name}'s post",
|
||||||
"interaction_modal.title.reply": "Reply to {name}'s post",
|
"interaction_modal.title.reply": "Reply to {name}'s post",
|
||||||
|
"interaction_modal.title.vote": "Vote in {name}'s poll",
|
||||||
"intervals.full.days": "{number, plural, one {# day} other {# days}}",
|
"intervals.full.days": "{number, plural, one {# day} other {# days}}",
|
||||||
"intervals.full.hours": "{number, plural, one {# hour} other {# hours}}",
|
"intervals.full.hours": "{number, plural, one {# hour} other {# hours}}",
|
||||||
"intervals.full.minutes": "{number, plural, one {# minute} other {# minutes}}",
|
"intervals.full.minutes": "{number, plural, one {# minute} other {# minutes}}",
|
||||||
@ -441,20 +464,11 @@
|
|||||||
"link_preview.author": "By {name}",
|
"link_preview.author": "By {name}",
|
||||||
"link_preview.more_from_author": "More from {name}",
|
"link_preview.more_from_author": "More from {name}",
|
||||||
"link_preview.shares": "{count, plural, one {{counter} post} other {{counter} posts}}",
|
"link_preview.shares": "{count, plural, one {{counter} post} other {{counter} posts}}",
|
||||||
"lists.account.add": "Add to list",
|
|
||||||
"lists.account.remove": "Remove from list",
|
|
||||||
"lists.delete": "Delete list",
|
"lists.delete": "Delete list",
|
||||||
"lists.edit": "Edit list",
|
"lists.edit": "Edit list",
|
||||||
"lists.edit.submit": "Change title",
|
|
||||||
"lists.exclusive": "Hide these posts from home",
|
|
||||||
"lists.new.create": "Add list",
|
|
||||||
"lists.new.title_placeholder": "New list title",
|
|
||||||
"lists.replies_policy.followed": "Any followed user",
|
"lists.replies_policy.followed": "Any followed user",
|
||||||
"lists.replies_policy.list": "Members of the list",
|
"lists.replies_policy.list": "Members of the list",
|
||||||
"lists.replies_policy.none": "No one",
|
"lists.replies_policy.none": "No one",
|
||||||
"lists.replies_policy.title": "Show replies to:",
|
|
||||||
"lists.search": "Search among people you follow",
|
|
||||||
"lists.subheading": "Your lists",
|
|
||||||
"load_pending": "{count, plural, one {# new item} other {# new items}}",
|
"load_pending": "{count, plural, one {# new item} other {# new items}}",
|
||||||
"loading_indicator.label": "Loading…",
|
"loading_indicator.label": "Loading…",
|
||||||
"media_gallery.hide": "Hide",
|
"media_gallery.hide": "Hide",
|
||||||
@ -503,9 +517,11 @@
|
|||||||
"notification.admin.report_statuses_other": "{name} reported {target}",
|
"notification.admin.report_statuses_other": "{name} reported {target}",
|
||||||
"notification.admin.sign_up": "{name} signed up",
|
"notification.admin.sign_up": "{name} signed up",
|
||||||
"notification.admin.sign_up.name_and_others": "{name} and {count, plural, one {# other} other {# others}} signed up",
|
"notification.admin.sign_up.name_and_others": "{name} and {count, plural, one {# other} other {# others}} signed up",
|
||||||
|
"notification.annual_report.message": "Your {year} #Wrapstodon awaits! Unveil your year's highlights and memorable moments on Mastodon!",
|
||||||
"notification.favourite": "{name} favourited your post",
|
"notification.favourite": "{name} favourited your post",
|
||||||
"notification.favourite.name_and_others_with_link": "{name} and <a>{count, plural, one {# other} other {# others}}</a> favourited your post",
|
"notification.favourite.name_and_others_with_link": "{name} and <a>{count, plural, one {# other} other {# others}}</a> favourited your post",
|
||||||
"notification.follow": "{name} followed you",
|
"notification.follow": "{name} followed you",
|
||||||
|
"notification.follow.name_and_others": "{name} and <a>{count, plural, one {# other} other {# others}}</a> followed you",
|
||||||
"notification.follow_request": "{name} has requested to follow you",
|
"notification.follow_request": "{name} has requested to follow you",
|
||||||
"notification.follow_request.name_and_others": "{name} and {count, plural, one {# other} other {# others}} has requested to follow you",
|
"notification.follow_request.name_and_others": "{name} and {count, plural, one {# other} other {# others}} has requested to follow you",
|
||||||
"notification.label.mention": "Mention",
|
"notification.label.mention": "Mention",
|
||||||
@ -513,6 +529,7 @@
|
|||||||
"notification.label.private_reply": "Private reply",
|
"notification.label.private_reply": "Private reply",
|
||||||
"notification.label.reply": "Reply",
|
"notification.label.reply": "Reply",
|
||||||
"notification.mention": "Mention",
|
"notification.mention": "Mention",
|
||||||
|
"notification.mentioned_you": "{name} mentioned you",
|
||||||
"notification.moderation-warning.learn_more": "Learn more",
|
"notification.moderation-warning.learn_more": "Learn more",
|
||||||
"notification.moderation_warning": "You have received a moderation warning",
|
"notification.moderation_warning": "You have received a moderation warning",
|
||||||
"notification.moderation_warning.action_delete_statuses": "Some of your posts have been removed.",
|
"notification.moderation_warning.action_delete_statuses": "Some of your posts have been removed.",
|
||||||
@ -563,6 +580,7 @@
|
|||||||
"notifications.column_settings.filter_bar.category": "Quick filter bar",
|
"notifications.column_settings.filter_bar.category": "Quick filter bar",
|
||||||
"notifications.column_settings.follow": "New followers:",
|
"notifications.column_settings.follow": "New followers:",
|
||||||
"notifications.column_settings.follow_request": "New follow requests:",
|
"notifications.column_settings.follow_request": "New follow requests:",
|
||||||
|
"notifications.column_settings.group": "Group",
|
||||||
"notifications.column_settings.mention": "Mentions:",
|
"notifications.column_settings.mention": "Mentions:",
|
||||||
"notifications.column_settings.poll": "Poll results:",
|
"notifications.column_settings.poll": "Poll results:",
|
||||||
"notifications.column_settings.push": "Push notifications",
|
"notifications.column_settings.push": "Push notifications",
|
||||||
|
@ -140,13 +140,16 @@
|
|||||||
"column.blocks": "Blocked users",
|
"column.blocks": "Blocked users",
|
||||||
"column.bookmarks": "Bookmarks",
|
"column.bookmarks": "Bookmarks",
|
||||||
"column.community": "Local timeline",
|
"column.community": "Local timeline",
|
||||||
|
"column.create_list": "Create list",
|
||||||
"column.direct": "Private mentions",
|
"column.direct": "Private mentions",
|
||||||
"column.directory": "Browse profiles",
|
"column.directory": "Browse profiles",
|
||||||
"column.domain_blocks": "Blocked domains",
|
"column.domain_blocks": "Blocked domains",
|
||||||
|
"column.edit_list": "Edit list",
|
||||||
"column.favourites": "Favorites",
|
"column.favourites": "Favorites",
|
||||||
"column.firehose": "Live feeds",
|
"column.firehose": "Live feeds",
|
||||||
"column.follow_requests": "Follow requests",
|
"column.follow_requests": "Follow requests",
|
||||||
"column.home": "Home",
|
"column.home": "Home",
|
||||||
|
"column.list_members": "Manage list members",
|
||||||
"column.lists": "Lists",
|
"column.lists": "Lists",
|
||||||
"column.mutes": "Muted users",
|
"column.mutes": "Muted users",
|
||||||
"column.notifications": "Notifications",
|
"column.notifications": "Notifications",
|
||||||
@ -159,6 +162,7 @@
|
|||||||
"column_header.pin": "Pin",
|
"column_header.pin": "Pin",
|
||||||
"column_header.show_settings": "Show settings",
|
"column_header.show_settings": "Show settings",
|
||||||
"column_header.unpin": "Unpin",
|
"column_header.unpin": "Unpin",
|
||||||
|
"column_search.cancel": "Cancel",
|
||||||
"column_subheading.settings": "Settings",
|
"column_subheading.settings": "Settings",
|
||||||
"community.column_settings.local_only": "Local only",
|
"community.column_settings.local_only": "Local only",
|
||||||
"community.column_settings.media_only": "Media Only",
|
"community.column_settings.media_only": "Media Only",
|
||||||
@ -292,7 +296,6 @@
|
|||||||
"empty_column.hashtag": "There is nothing in this hashtag yet.",
|
"empty_column.hashtag": "There is nothing in this hashtag yet.",
|
||||||
"empty_column.home": "Your home timeline is empty! Follow more people to fill it up.",
|
"empty_column.home": "Your home timeline is empty! Follow more people to fill it up.",
|
||||||
"empty_column.list": "There is nothing in this list yet. When members of this list publish new posts, they will appear here.",
|
"empty_column.list": "There is nothing in this list yet. When members of this list publish new posts, they will appear here.",
|
||||||
"empty_column.lists": "You don't have any lists yet. When you create one, it will show up here.",
|
|
||||||
"empty_column.mutes": "You haven't muted any users yet.",
|
"empty_column.mutes": "You haven't muted any users yet.",
|
||||||
"empty_column.notification_requests": "All clear! There is nothing here. When you receive new notifications, they will appear here according to your settings.",
|
"empty_column.notification_requests": "All clear! There is nothing here. When you receive new notifications, they will appear here according to your settings.",
|
||||||
"empty_column.notifications": "You don't have any notifications yet. When other people interact with you, you will see it here.",
|
"empty_column.notifications": "You don't have any notifications yet. When other people interact with you, you will see it here.",
|
||||||
@ -465,20 +468,32 @@
|
|||||||
"link_preview.author": "By {name}",
|
"link_preview.author": "By {name}",
|
||||||
"link_preview.more_from_author": "More from {name}",
|
"link_preview.more_from_author": "More from {name}",
|
||||||
"link_preview.shares": "{count, plural, one {{counter} post} other {{counter} posts}}",
|
"link_preview.shares": "{count, plural, one {{counter} post} other {{counter} posts}}",
|
||||||
"lists.account.add": "Add to list",
|
"lists.add_member": "Add",
|
||||||
"lists.account.remove": "Remove from list",
|
"lists.add_to_list": "Add to list",
|
||||||
|
"lists.add_to_lists": "Add {name} to lists",
|
||||||
|
"lists.create": "Create",
|
||||||
|
"lists.create_a_list_to_organize": "Create a new list to organize your Home feed",
|
||||||
|
"lists.create_list": "Create list",
|
||||||
"lists.delete": "Delete list",
|
"lists.delete": "Delete list",
|
||||||
|
"lists.done": "Done",
|
||||||
"lists.edit": "Edit list",
|
"lists.edit": "Edit list",
|
||||||
"lists.edit.submit": "Change title",
|
"lists.exclusive": "Hide members in Home",
|
||||||
"lists.exclusive": "Hide these posts from home",
|
"lists.exclusive_hint": "If someone is on this list, hide them in your Home feed to avoid seeing their posts twice.",
|
||||||
"lists.new.create": "Add list",
|
"lists.find_users_to_add": "Find users to add",
|
||||||
"lists.new.title_placeholder": "New list title",
|
"lists.list_members": "List members",
|
||||||
|
"lists.list_members_count": "{count, plural, one {# member} other {# members}}",
|
||||||
|
"lists.list_name": "List name",
|
||||||
|
"lists.new_list_name": "New list name",
|
||||||
|
"lists.no_lists_yet": "No lists yet.",
|
||||||
|
"lists.no_members_yet": "No members yet.",
|
||||||
|
"lists.no_results_found": "No results found.",
|
||||||
|
"lists.remove_member": "Remove",
|
||||||
"lists.replies_policy.followed": "Any followed user",
|
"lists.replies_policy.followed": "Any followed user",
|
||||||
"lists.replies_policy.list": "Members of the list",
|
"lists.replies_policy.list": "Members of the list",
|
||||||
"lists.replies_policy.none": "No one",
|
"lists.replies_policy.none": "No one",
|
||||||
"lists.replies_policy.title": "Show replies to:",
|
"lists.save": "Save",
|
||||||
"lists.search": "Search among people you follow",
|
"lists.search_placeholder": "Search people you follow",
|
||||||
"lists.subheading": "Your lists",
|
"lists.show_replies_to": "Include replies from list members to",
|
||||||
"load_pending": "{count, plural, one {# new item} other {# new items}}",
|
"load_pending": "{count, plural, one {# new item} other {# new items}}",
|
||||||
"loading_indicator.label": "Loading…",
|
"loading_indicator.label": "Loading…",
|
||||||
"media_gallery.hide": "Hide",
|
"media_gallery.hide": "Hide",
|
||||||
@ -635,44 +650,21 @@
|
|||||||
"notifications_permission_banner.enable": "Enable desktop notifications",
|
"notifications_permission_banner.enable": "Enable desktop notifications",
|
||||||
"notifications_permission_banner.how_to_control": "To receive notifications when Mastodon isn't open, enable desktop notifications. You can control precisely which types of interactions generate desktop notifications through the {icon} button above once they're enabled.",
|
"notifications_permission_banner.how_to_control": "To receive notifications when Mastodon isn't open, enable desktop notifications. You can control precisely which types of interactions generate desktop notifications through the {icon} button above once they're enabled.",
|
||||||
"notifications_permission_banner.title": "Never miss a thing",
|
"notifications_permission_banner.title": "Never miss a thing",
|
||||||
"onboarding.action.back": "Take me back",
|
"onboarding.follows.back": "Back",
|
||||||
"onboarding.actions.back": "Take me back",
|
"onboarding.follows.done": "Done",
|
||||||
"onboarding.actions.go_to_explore": "Take me to trending",
|
|
||||||
"onboarding.actions.go_to_home": "Take me to my home feed",
|
|
||||||
"onboarding.compose.template": "Hello #Mastodon!",
|
|
||||||
"onboarding.follows.empty": "Unfortunately, no results can be shown right now. You can try using search or browsing the explore page to find people to follow, or try again later.",
|
"onboarding.follows.empty": "Unfortunately, no results can be shown right now. You can try using search or browsing the explore page to find people to follow, or try again later.",
|
||||||
"onboarding.follows.lead": "Your home feed is the primary way to experience Mastodon. The more people you follow, the more active and interesting it will be. To get you started, here are some suggestions:",
|
"onboarding.follows.search": "Search",
|
||||||
"onboarding.follows.title": "Personalize your home feed",
|
"onboarding.follows.title": "Follow people to get started",
|
||||||
"onboarding.profile.discoverable": "Make my profile discoverable",
|
"onboarding.profile.discoverable": "Make my profile discoverable",
|
||||||
"onboarding.profile.discoverable_hint": "When you opt in to discoverability on Mastodon, your posts may appear in search results and trending, and your profile may be suggested to people with similar interests to you.",
|
"onboarding.profile.discoverable_hint": "When you opt in to discoverability on Mastodon, your posts may appear in search results and trending, and your profile may be suggested to people with similar interests to you.",
|
||||||
"onboarding.profile.display_name": "Display name",
|
"onboarding.profile.display_name": "Display name",
|
||||||
"onboarding.profile.display_name_hint": "Your full name or your fun name…",
|
"onboarding.profile.display_name_hint": "Your full name or your fun name…",
|
||||||
"onboarding.profile.lead": "You can always complete this later in the settings, where even more customization options are available.",
|
|
||||||
"onboarding.profile.note": "Bio",
|
"onboarding.profile.note": "Bio",
|
||||||
"onboarding.profile.note_hint": "You can @mention other people or #hashtags…",
|
"onboarding.profile.note_hint": "You can @mention other people or #hashtags…",
|
||||||
"onboarding.profile.save_and_continue": "Save and continue",
|
"onboarding.profile.save_and_continue": "Save and continue",
|
||||||
"onboarding.profile.title": "Profile setup",
|
"onboarding.profile.title": "Profile setup",
|
||||||
"onboarding.profile.upload_avatar": "Upload profile picture",
|
"onboarding.profile.upload_avatar": "Upload profile picture",
|
||||||
"onboarding.profile.upload_header": "Upload profile header",
|
"onboarding.profile.upload_header": "Upload profile header",
|
||||||
"onboarding.share.lead": "Let people know how they can find you on Mastodon!",
|
|
||||||
"onboarding.share.message": "I'm {username} on #Mastodon! Come follow me at {url}",
|
|
||||||
"onboarding.share.next_steps": "Possible next steps:",
|
|
||||||
"onboarding.share.title": "Share your profile",
|
|
||||||
"onboarding.start.lead": "You're now part of Mastodon, a unique, decentralized social media platform where you—not an algorithm—curate your own experience. Let's get you started on this new social frontier:",
|
|
||||||
"onboarding.start.skip": "Don't need help getting started?",
|
|
||||||
"onboarding.start.title": "You've made it!",
|
|
||||||
"onboarding.steps.follow_people.body": "Following interesting people is what Mastodon is all about.",
|
|
||||||
"onboarding.steps.follow_people.title": "Personalize your home feed",
|
|
||||||
"onboarding.steps.publish_status.body": "Say hello to the world with text, photos, videos, or polls {emoji}",
|
|
||||||
"onboarding.steps.publish_status.title": "Make your first post",
|
|
||||||
"onboarding.steps.setup_profile.body": "Boost your interactions by having a comprehensive profile.",
|
|
||||||
"onboarding.steps.setup_profile.title": "Personalize your profile",
|
|
||||||
"onboarding.steps.share_profile.body": "Let your friends know how to find you on Mastodon",
|
|
||||||
"onboarding.steps.share_profile.title": "Share your Mastodon profile",
|
|
||||||
"onboarding.tips.2fa": "<strong>Did you know?</strong> You can secure your account by setting up two-factor authentication in your account settings. It works with any TOTP app of your choice, no phone number necessary!",
|
|
||||||
"onboarding.tips.accounts_from_other_servers": "<strong>Did you know?</strong> Since Mastodon is decentralized, some profiles you come across will be hosted on servers other than yours. And yet you can interact with them seamlessly! Their server is in the second half of their username!",
|
|
||||||
"onboarding.tips.migration": "<strong>Did you know?</strong> If you feel like {domain} is not a great server choice for you in the future, you can move to another Mastodon server without losing your followers. You can even host your own server!",
|
|
||||||
"onboarding.tips.verification": "<strong>Did you know?</strong> You can verify your account by putting a link to your Mastodon profile on your own website and adding the website to your profile. No fees or documents necessary!",
|
|
||||||
"password_confirmation.exceeds_maxlength": "Password confirmation exceeds the maximum password length",
|
"password_confirmation.exceeds_maxlength": "Password confirmation exceeds the maximum password length",
|
||||||
"password_confirmation.mismatching": "Password confirmation does not match",
|
"password_confirmation.mismatching": "Password confirmation does not match",
|
||||||
"picture_in_picture.restore": "Put it back",
|
"picture_in_picture.restore": "Put it back",
|
||||||
|
@ -128,9 +128,11 @@
|
|||||||
"column.blocks": "Blokitaj uzantoj",
|
"column.blocks": "Blokitaj uzantoj",
|
||||||
"column.bookmarks": "Legosignoj",
|
"column.bookmarks": "Legosignoj",
|
||||||
"column.community": "Loka templinio",
|
"column.community": "Loka templinio",
|
||||||
|
"column.create_list": "Krei liston",
|
||||||
"column.direct": "Privataj mencioj",
|
"column.direct": "Privataj mencioj",
|
||||||
"column.directory": "Foliumi la profilojn",
|
"column.directory": "Foliumi la profilojn",
|
||||||
"column.domain_blocks": "Blokitaj domajnoj",
|
"column.domain_blocks": "Blokitaj domajnoj",
|
||||||
|
"column.edit_list": "Redakti liston",
|
||||||
"column.favourites": "Stelumoj",
|
"column.favourites": "Stelumoj",
|
||||||
"column.firehose": "Rektaj fluoj",
|
"column.firehose": "Rektaj fluoj",
|
||||||
"column.follow_requests": "Petoj de sekvado",
|
"column.follow_requests": "Petoj de sekvado",
|
||||||
@ -280,7 +282,6 @@
|
|||||||
"empty_column.hashtag": "Ankoraŭ estas nenio per ĉi tiu kradvorto.",
|
"empty_column.hashtag": "Ankoraŭ estas nenio per ĉi tiu kradvorto.",
|
||||||
"empty_column.home": "Via hejma tempolinio estas malplena! Vizitu {public} aŭ uzu la serĉilon por renkonti aliajn uzantojn.",
|
"empty_column.home": "Via hejma tempolinio estas malplena! Vizitu {public} aŭ uzu la serĉilon por renkonti aliajn uzantojn.",
|
||||||
"empty_column.list": "Ankoraŭ estas nenio en ĉi tiu listo. Kiam membroj de ĉi tiu listo afiŝos novajn afiŝojn, ili aperos ĉi tie.",
|
"empty_column.list": "Ankoraŭ estas nenio en ĉi tiu listo. Kiam membroj de ĉi tiu listo afiŝos novajn afiŝojn, ili aperos ĉi tie.",
|
||||||
"empty_column.lists": "Vi ankoraŭ ne havas liston. Kiam vi kreos iun, ĝi aperos ĉi tie.",
|
|
||||||
"empty_column.mutes": "Vi ne ankoraŭ silentigis iun uzanton.",
|
"empty_column.mutes": "Vi ne ankoraŭ silentigis iun uzanton.",
|
||||||
"empty_column.notification_requests": "Ĉio klara! Estas nenio tie ĉi. Kiam vi ricevas novajn sciigojn, ili aperos ĉi tie laŭ viaj agordoj.",
|
"empty_column.notification_requests": "Ĉio klara! Estas nenio tie ĉi. Kiam vi ricevas novajn sciigojn, ili aperos ĉi tie laŭ viaj agordoj.",
|
||||||
"empty_column.notifications": "Vi ankoraŭ ne havas sciigojn. Interagu kun aliaj por komenci konversacion.",
|
"empty_column.notifications": "Vi ankoraŭ ne havas sciigojn. Interagu kun aliaj por komenci konversacion.",
|
||||||
@ -453,20 +454,22 @@
|
|||||||
"link_preview.author": "De {name}",
|
"link_preview.author": "De {name}",
|
||||||
"link_preview.more_from_author": "Pli de {name}",
|
"link_preview.more_from_author": "Pli de {name}",
|
||||||
"link_preview.shares": "{count, plural, one {{counter} afiŝo} other {{counter} afiŝoj}}",
|
"link_preview.shares": "{count, plural, one {{counter} afiŝo} other {{counter} afiŝoj}}",
|
||||||
"lists.account.add": "Aldoni al la listo",
|
"lists.add_member": "Aldoni",
|
||||||
"lists.account.remove": "Forigi de la listo",
|
"lists.add_to_list": "Aldoni al la listo",
|
||||||
|
"lists.add_to_lists": "Aldoni {name} al la listo",
|
||||||
|
"lists.create": "Krei",
|
||||||
|
"lists.create_list": "Krei liston",
|
||||||
"lists.delete": "Forigi la liston",
|
"lists.delete": "Forigi la liston",
|
||||||
|
"lists.done": "Farita",
|
||||||
"lists.edit": "Redakti la liston",
|
"lists.edit": "Redakti la liston",
|
||||||
"lists.edit.submit": "Ŝanĝi titolon",
|
"lists.no_lists_yet": "Ankoraŭ ne estas listoj.",
|
||||||
"lists.exclusive": "Kaŝi ĉi tiujn afiŝojn de hejmo",
|
"lists.no_members_yet": "Ankoraŭ neniuj membroj.",
|
||||||
"lists.new.create": "Aldoni liston",
|
"lists.no_results_found": "Neniuj rezultoj trovitaj.",
|
||||||
"lists.new.title_placeholder": "Titolo de la nova listo",
|
"lists.remove_member": "Forigi",
|
||||||
"lists.replies_policy.followed": "Iu sekvanta uzanto",
|
"lists.replies_policy.followed": "Iu sekvanta uzanto",
|
||||||
"lists.replies_policy.list": "Membroj de la listo",
|
"lists.replies_policy.list": "Membroj de la listo",
|
||||||
"lists.replies_policy.none": "Neniu",
|
"lists.replies_policy.none": "Neniu",
|
||||||
"lists.replies_policy.title": "Montri respondojn al:",
|
"lists.save": "Konservi",
|
||||||
"lists.search": "Serĉi inter la homoj, kiujn vi sekvas",
|
|
||||||
"lists.subheading": "Viaj listoj",
|
|
||||||
"load_pending": "{count,plural, one {# nova elemento} other {# novaj elementoj}}",
|
"load_pending": "{count,plural, one {# nova elemento} other {# novaj elementoj}}",
|
||||||
"loading_indicator.label": "Ŝargado…",
|
"loading_indicator.label": "Ŝargado…",
|
||||||
"media_gallery.hide": "Kaŝi",
|
"media_gallery.hide": "Kaŝi",
|
||||||
|
@ -140,13 +140,16 @@
|
|||||||
"column.blocks": "Usuarios bloqueados",
|
"column.blocks": "Usuarios bloqueados",
|
||||||
"column.bookmarks": "Marcadores",
|
"column.bookmarks": "Marcadores",
|
||||||
"column.community": "Línea temporal local",
|
"column.community": "Línea temporal local",
|
||||||
|
"column.create_list": "Crear una lista",
|
||||||
"column.direct": "Menciones privadas",
|
"column.direct": "Menciones privadas",
|
||||||
"column.directory": "Explorar perfiles",
|
"column.directory": "Explorar perfiles",
|
||||||
"column.domain_blocks": "Dominios bloqueados",
|
"column.domain_blocks": "Dominios bloqueados",
|
||||||
|
"column.edit_list": "Editar lista",
|
||||||
"column.favourites": "Favoritos",
|
"column.favourites": "Favoritos",
|
||||||
"column.firehose": "Líneas temporales en vivo",
|
"column.firehose": "Líneas temporales en vivo",
|
||||||
"column.follow_requests": "Solicitudes de seguimiento",
|
"column.follow_requests": "Solicitudes de seguimiento",
|
||||||
"column.home": "Principal",
|
"column.home": "Principal",
|
||||||
|
"column.list_members": "Administrar miembros de la lista",
|
||||||
"column.lists": "Listas",
|
"column.lists": "Listas",
|
||||||
"column.mutes": "Usuarios silenciados",
|
"column.mutes": "Usuarios silenciados",
|
||||||
"column.notifications": "Notificaciones",
|
"column.notifications": "Notificaciones",
|
||||||
@ -292,7 +295,6 @@
|
|||||||
"empty_column.hashtag": "Todavía no hay nada con esta etiqueta.",
|
"empty_column.hashtag": "Todavía no hay nada con esta etiqueta.",
|
||||||
"empty_column.home": "¡Tu línea temporal principal está vacía! Seguí a más cuentas para llenarla.",
|
"empty_column.home": "¡Tu línea temporal principal está vacía! Seguí a más cuentas para llenarla.",
|
||||||
"empty_column.list": "Todavía no hay nada en esta lista. Cuando miembros de esta lista envíen nuevos mensaje, se mostrarán acá.",
|
"empty_column.list": "Todavía no hay nada en esta lista. Cuando miembros de esta lista envíen nuevos mensaje, se mostrarán acá.",
|
||||||
"empty_column.lists": "Todavía no tenés ninguna lista. Cuando creés una, se mostrará acá.",
|
|
||||||
"empty_column.mutes": "Todavía no silenciaste a ningún usuario.",
|
"empty_column.mutes": "Todavía no silenciaste a ningún usuario.",
|
||||||
"empty_column.notification_requests": "¡Todo limpio! No hay nada acá. Cuando recibás nuevas notificaciones, aparecerán acá, acorde a tu configuración.",
|
"empty_column.notification_requests": "¡Todo limpio! No hay nada acá. Cuando recibás nuevas notificaciones, aparecerán acá, acorde a tu configuración.",
|
||||||
"empty_column.notifications": "Todavía no tenés ninguna notificación. Cuando otras cuentas interactúen con vos, vas a ver la notificación acá.",
|
"empty_column.notifications": "Todavía no tenés ninguna notificación. Cuando otras cuentas interactúen con vos, vas a ver la notificación acá.",
|
||||||
@ -465,20 +467,32 @@
|
|||||||
"link_preview.author": "Por {name}",
|
"link_preview.author": "Por {name}",
|
||||||
"link_preview.more_from_author": "Más de {name}",
|
"link_preview.more_from_author": "Más de {name}",
|
||||||
"link_preview.shares": "{count, plural, one {{counter} mensaje} other {{counter} mensajes}}",
|
"link_preview.shares": "{count, plural, one {{counter} mensaje} other {{counter} mensajes}}",
|
||||||
"lists.account.add": "Agregar a lista",
|
"lists.add_member": "Añadir",
|
||||||
"lists.account.remove": "Quitar de lista",
|
"lists.add_to_list": "Añadir a la lista",
|
||||||
|
"lists.add_to_lists": "Añadir {name} a las listas",
|
||||||
|
"lists.create": "Crear",
|
||||||
|
"lists.create_a_list_to_organize": "Crea una nueva lista para organizar tu página de inicio",
|
||||||
|
"lists.create_list": "Crear una lista",
|
||||||
"lists.delete": "Eliminar lista",
|
"lists.delete": "Eliminar lista",
|
||||||
|
"lists.done": "Hecho",
|
||||||
"lists.edit": "Editar lista",
|
"lists.edit": "Editar lista",
|
||||||
"lists.edit.submit": "Cambiar título",
|
"lists.exclusive": "Ocultar miembros en Inicio",
|
||||||
"lists.exclusive": "Ocultar estos mensajes del inicio",
|
"lists.exclusive_hint": "Si alguien está en esta lista, escóndelo en tu página de inicio para evitar ver sus publicaciones dos veces.",
|
||||||
"lists.new.create": "Agregar lista",
|
"lists.find_users_to_add": "Buscar usuarios para añadir",
|
||||||
"lists.new.title_placeholder": "Título de nueva lista",
|
"lists.list_members": "Miembros de la lista",
|
||||||
|
"lists.list_members_count": "{count, plural,one {# miembro} other {# miembros}}",
|
||||||
|
"lists.list_name": "Nombre de la lista",
|
||||||
|
"lists.new_list_name": "Nombre de la nueva lista",
|
||||||
|
"lists.no_lists_yet": "Aún no hay listas.",
|
||||||
|
"lists.no_members_yet": "Aún no hay miembros.",
|
||||||
|
"lists.no_results_found": "No se encontraron resultados.",
|
||||||
|
"lists.remove_member": "Eliminar",
|
||||||
"lists.replies_policy.followed": "Cualquier cuenta seguida",
|
"lists.replies_policy.followed": "Cualquier cuenta seguida",
|
||||||
"lists.replies_policy.list": "Miembros de la lista",
|
"lists.replies_policy.list": "Miembros de la lista",
|
||||||
"lists.replies_policy.none": "Nadie",
|
"lists.replies_policy.none": "Nadie",
|
||||||
"lists.replies_policy.title": "Mostrar respuestas a:",
|
"lists.save": "Guardar",
|
||||||
"lists.search": "Buscar entre la gente que seguís",
|
"lists.search_placeholder": "Buscar gente a la que sigues",
|
||||||
"lists.subheading": "Tus listas",
|
"lists.show_replies_to": "Incluir las respuestas de los miembros de la lista a",
|
||||||
"load_pending": "{count, plural, one {# elemento nuevo} other {# elementos nuevos}}",
|
"load_pending": "{count, plural, one {# elemento nuevo} other {# elementos nuevos}}",
|
||||||
"loading_indicator.label": "Cargando…",
|
"loading_indicator.label": "Cargando…",
|
||||||
"media_gallery.hide": "Ocultar",
|
"media_gallery.hide": "Ocultar",
|
||||||
|
@ -140,13 +140,16 @@
|
|||||||
"column.blocks": "Usuarios bloqueados",
|
"column.blocks": "Usuarios bloqueados",
|
||||||
"column.bookmarks": "Marcadores",
|
"column.bookmarks": "Marcadores",
|
||||||
"column.community": "Línea de tiempo local",
|
"column.community": "Línea de tiempo local",
|
||||||
|
"column.create_list": "Crear una lista",
|
||||||
"column.direct": "Menciones privadas",
|
"column.direct": "Menciones privadas",
|
||||||
"column.directory": "Buscar perfiles",
|
"column.directory": "Buscar perfiles",
|
||||||
"column.domain_blocks": "Dominios ocultados",
|
"column.domain_blocks": "Dominios ocultados",
|
||||||
|
"column.edit_list": "Editar lista",
|
||||||
"column.favourites": "Favoritos",
|
"column.favourites": "Favoritos",
|
||||||
"column.firehose": "Cronologías",
|
"column.firehose": "Cronologías",
|
||||||
"column.follow_requests": "Solicitudes de seguimiento",
|
"column.follow_requests": "Solicitudes de seguimiento",
|
||||||
"column.home": "Inicio",
|
"column.home": "Inicio",
|
||||||
|
"column.list_members": "Administrar miembros de la lista",
|
||||||
"column.lists": "Listas",
|
"column.lists": "Listas",
|
||||||
"column.mutes": "Usuarios silenciados",
|
"column.mutes": "Usuarios silenciados",
|
||||||
"column.notifications": "Notificaciones",
|
"column.notifications": "Notificaciones",
|
||||||
@ -292,7 +295,6 @@
|
|||||||
"empty_column.hashtag": "No hay nada en esta etiqueta aún.",
|
"empty_column.hashtag": "No hay nada en esta etiqueta aún.",
|
||||||
"empty_column.home": "No estás siguiendo a nadie aún. Visita {public} o haz búsquedas para empezar y conocer gente nueva.",
|
"empty_column.home": "No estás siguiendo a nadie aún. Visita {public} o haz búsquedas para empezar y conocer gente nueva.",
|
||||||
"empty_column.list": "No hay nada en esta lista aún. Cuando miembros de esta lista publiquen nuevos estatus, estos aparecerán qui.",
|
"empty_column.list": "No hay nada en esta lista aún. Cuando miembros de esta lista publiquen nuevos estatus, estos aparecerán qui.",
|
||||||
"empty_column.lists": "No tienes ninguna lista. cuando crees una, se mostrará aquí.",
|
|
||||||
"empty_column.mutes": "Aún no has silenciado a ningún usuario.",
|
"empty_column.mutes": "Aún no has silenciado a ningún usuario.",
|
||||||
"empty_column.notification_requests": "¡Todo limpio! No hay nada aquí. Cuando recibas nuevas notificaciones, aparecerán aquí conforme a tu configuración.",
|
"empty_column.notification_requests": "¡Todo limpio! No hay nada aquí. Cuando recibas nuevas notificaciones, aparecerán aquí conforme a tu configuración.",
|
||||||
"empty_column.notifications": "No tienes ninguna notificación aún. Interactúa con otros para empezar una conversación.",
|
"empty_column.notifications": "No tienes ninguna notificación aún. Interactúa con otros para empezar una conversación.",
|
||||||
@ -465,20 +467,32 @@
|
|||||||
"link_preview.author": "Por {name}",
|
"link_preview.author": "Por {name}",
|
||||||
"link_preview.more_from_author": "Más de {name}",
|
"link_preview.more_from_author": "Más de {name}",
|
||||||
"link_preview.shares": "{count, plural, one {{counter} publicación} other {{counter} publicaciones}}",
|
"link_preview.shares": "{count, plural, one {{counter} publicación} other {{counter} publicaciones}}",
|
||||||
"lists.account.add": "Añadir a lista",
|
"lists.add_member": "Añadir",
|
||||||
"lists.account.remove": "Quitar de lista",
|
"lists.add_to_list": "Añadir a la lista",
|
||||||
|
"lists.add_to_lists": "Añadir {name} a las listas",
|
||||||
|
"lists.create": "Crear",
|
||||||
|
"lists.create_a_list_to_organize": "Crea una nueva lista para organizar tu página de inicio",
|
||||||
|
"lists.create_list": "Crear una lista",
|
||||||
"lists.delete": "Borrar lista",
|
"lists.delete": "Borrar lista",
|
||||||
|
"lists.done": "Hecho",
|
||||||
"lists.edit": "Editar lista",
|
"lists.edit": "Editar lista",
|
||||||
"lists.edit.submit": "Cambiar título",
|
"lists.exclusive": "Ocultar miembros en Inicio",
|
||||||
"lists.exclusive": "Ocultar estas publicaciones en inicio",
|
"lists.exclusive_hint": "Si alguien está en esta lista, escóndelo en tu página de inicio para evitar ver sus publicaciones dos veces.",
|
||||||
"lists.new.create": "Añadir lista",
|
"lists.find_users_to_add": "Buscar usuarios para añadir",
|
||||||
"lists.new.title_placeholder": "Título de la nueva lista",
|
"lists.list_members": "Miembros de la lista",
|
||||||
|
"lists.list_members_count": "{count, plural,one {# miembro} other {# miembros}}",
|
||||||
|
"lists.list_name": "Nombre de la lista",
|
||||||
|
"lists.new_list_name": "Nombre de la nueva lista",
|
||||||
|
"lists.no_lists_yet": "Aún no hay listas.",
|
||||||
|
"lists.no_members_yet": "Aún no hay miembros.",
|
||||||
|
"lists.no_results_found": "No se encontraron resultados.",
|
||||||
|
"lists.remove_member": "Eliminar",
|
||||||
"lists.replies_policy.followed": "Cualquier usuario seguido",
|
"lists.replies_policy.followed": "Cualquier usuario seguido",
|
||||||
"lists.replies_policy.list": "Miembros de la lista",
|
"lists.replies_policy.list": "Miembros de la lista",
|
||||||
"lists.replies_policy.none": "Nadie",
|
"lists.replies_policy.none": "Nadie",
|
||||||
"lists.replies_policy.title": "Mostrar respuestas a:",
|
"lists.save": "Guardar",
|
||||||
"lists.search": "Buscar entre la gente a la que sigues",
|
"lists.search_placeholder": "Buscar gente a la que sigues",
|
||||||
"lists.subheading": "Tus listas",
|
"lists.show_replies_to": "Incluir las respuestas de los miembros de la lista a",
|
||||||
"load_pending": "{count, plural, one {# nuevo elemento} other {# nuevos elementos}}",
|
"load_pending": "{count, plural, one {# nuevo elemento} other {# nuevos elementos}}",
|
||||||
"loading_indicator.label": "Cargando…",
|
"loading_indicator.label": "Cargando…",
|
||||||
"media_gallery.hide": "Ocultar",
|
"media_gallery.hide": "Ocultar",
|
||||||
|
@ -140,13 +140,16 @@
|
|||||||
"column.blocks": "Usuarios bloqueados",
|
"column.blocks": "Usuarios bloqueados",
|
||||||
"column.bookmarks": "Marcadores",
|
"column.bookmarks": "Marcadores",
|
||||||
"column.community": "Cronología local",
|
"column.community": "Cronología local",
|
||||||
|
"column.create_list": "Crear una lista",
|
||||||
"column.direct": "Menciones privadas",
|
"column.direct": "Menciones privadas",
|
||||||
"column.directory": "Buscar perfiles",
|
"column.directory": "Buscar perfiles",
|
||||||
"column.domain_blocks": "Dominios bloqueados",
|
"column.domain_blocks": "Dominios bloqueados",
|
||||||
|
"column.edit_list": "Editar lista",
|
||||||
"column.favourites": "Favoritos",
|
"column.favourites": "Favoritos",
|
||||||
"column.firehose": "Cronologías",
|
"column.firehose": "Cronologías",
|
||||||
"column.follow_requests": "Solicitudes de seguimiento",
|
"column.follow_requests": "Solicitudes de seguimiento",
|
||||||
"column.home": "Inicio",
|
"column.home": "Inicio",
|
||||||
|
"column.list_members": "Administrar miembros de la lista",
|
||||||
"column.lists": "Listas",
|
"column.lists": "Listas",
|
||||||
"column.mutes": "Usuarios silenciados",
|
"column.mutes": "Usuarios silenciados",
|
||||||
"column.notifications": "Notificaciones",
|
"column.notifications": "Notificaciones",
|
||||||
@ -292,7 +295,6 @@
|
|||||||
"empty_column.hashtag": "No hay nada en esta etiqueta todavía.",
|
"empty_column.hashtag": "No hay nada en esta etiqueta todavía.",
|
||||||
"empty_column.home": "¡Tu línea temporal está vacía! Sigue a más personas para rellenarla.",
|
"empty_column.home": "¡Tu línea temporal está vacía! Sigue a más personas para rellenarla.",
|
||||||
"empty_column.list": "Aún no hay nada en esta lista. Cuando los miembros de esta lista publiquen nuevos estados, estos aparecerán aquí.",
|
"empty_column.list": "Aún no hay nada en esta lista. Cuando los miembros de esta lista publiquen nuevos estados, estos aparecerán aquí.",
|
||||||
"empty_column.lists": "No tienes ninguna lista. Cuando crees una, se mostrará aquí.",
|
|
||||||
"empty_column.mutes": "Aún no has silenciado a ningún usuario.",
|
"empty_column.mutes": "Aún no has silenciado a ningún usuario.",
|
||||||
"empty_column.notification_requests": "¡Todo limpio! No hay nada aquí. Cuando recibas nuevas notificaciones, aparecerán aquí conforme a tu configuración.",
|
"empty_column.notification_requests": "¡Todo limpio! No hay nada aquí. Cuando recibas nuevas notificaciones, aparecerán aquí conforme a tu configuración.",
|
||||||
"empty_column.notifications": "Aún no tienes ninguna notificación. Cuando otras personas interactúen contigo, aparecerán aquí.",
|
"empty_column.notifications": "Aún no tienes ninguna notificación. Cuando otras personas interactúen contigo, aparecerán aquí.",
|
||||||
@ -402,7 +404,7 @@
|
|||||||
"ignore_notifications_modal.not_following_title": "¿Ignorar notificaciones de personas a las que no sigues?",
|
"ignore_notifications_modal.not_following_title": "¿Ignorar notificaciones de personas a las que no sigues?",
|
||||||
"ignore_notifications_modal.private_mentions_title": "¿Ignorar notificaciones de menciones privadas no solicitadas?",
|
"ignore_notifications_modal.private_mentions_title": "¿Ignorar notificaciones de menciones privadas no solicitadas?",
|
||||||
"interaction_modal.description.favourite": "Con una cuenta en Mastodon, puedes marcar como favorita esta publicación para que el autor sepa que te gusta, y guardala para más adelante.",
|
"interaction_modal.description.favourite": "Con una cuenta en Mastodon, puedes marcar como favorita esta publicación para que el autor sepa que te gusta, y guardala para más adelante.",
|
||||||
"interaction_modal.description.follow": "Con una cuenta en Mastodon, puedes seguir {name} para recibir sus publicaciones en tu línea temporal de inicio.",
|
"interaction_modal.description.follow": "Con una cuenta en Mastodon, puedes seguir {name} para recibir sus publicaciones en tu página de inicio.",
|
||||||
"interaction_modal.description.reblog": "Con una cuenta en Mastodon, puedes impulsar esta publicación para compartirla con tus propios seguidores.",
|
"interaction_modal.description.reblog": "Con una cuenta en Mastodon, puedes impulsar esta publicación para compartirla con tus propios seguidores.",
|
||||||
"interaction_modal.description.reply": "Con una cuenta en Mastodon, puedes responder a esta publicación.",
|
"interaction_modal.description.reply": "Con una cuenta en Mastodon, puedes responder a esta publicación.",
|
||||||
"interaction_modal.description.vote": "Con una cuenta en Mastodon, puedes votar en esta encuesta.",
|
"interaction_modal.description.vote": "Con una cuenta en Mastodon, puedes votar en esta encuesta.",
|
||||||
@ -465,20 +467,32 @@
|
|||||||
"link_preview.author": "Por {name}",
|
"link_preview.author": "Por {name}",
|
||||||
"link_preview.more_from_author": "Más de {name}",
|
"link_preview.more_from_author": "Más de {name}",
|
||||||
"link_preview.shares": "{count, plural, one {{counter} publicación} other {{counter} publicaciones}}",
|
"link_preview.shares": "{count, plural, one {{counter} publicación} other {{counter} publicaciones}}",
|
||||||
"lists.account.add": "Añadir a lista",
|
"lists.add_member": "Añadir",
|
||||||
"lists.account.remove": "Quitar de lista",
|
"lists.add_to_list": "Añadir a la lista",
|
||||||
|
"lists.add_to_lists": "Añadir {name} a las listas",
|
||||||
|
"lists.create": "Crear",
|
||||||
|
"lists.create_a_list_to_organize": "Crea una nueva lista para organizar tu página de inicio",
|
||||||
|
"lists.create_list": "Crear una lista",
|
||||||
"lists.delete": "Borrar lista",
|
"lists.delete": "Borrar lista",
|
||||||
|
"lists.done": "Hecho",
|
||||||
"lists.edit": "Editar lista",
|
"lists.edit": "Editar lista",
|
||||||
"lists.edit.submit": "Cambiar título",
|
"lists.exclusive": "Ocultar miembros en Inicio",
|
||||||
"lists.exclusive": "Ocultar estas publicaciones de inicio",
|
"lists.exclusive_hint": "Si alguien está en esta lista, escóndelo en tu página de inicio para evitar ver sus publicaciones dos veces.",
|
||||||
"lists.new.create": "Añadir lista",
|
"lists.find_users_to_add": "Buscar usuarios para añadir",
|
||||||
"lists.new.title_placeholder": "Título de la nueva lista",
|
"lists.list_members": "Miembros de la lista",
|
||||||
|
"lists.list_members_count": "{count, plural,one {# miembro} other {# miembros}}",
|
||||||
|
"lists.list_name": "Nombre de la lista",
|
||||||
|
"lists.new_list_name": "Nombre de la nueva lista",
|
||||||
|
"lists.no_lists_yet": "Aún no hay listas.",
|
||||||
|
"lists.no_members_yet": "Aún no hay miembros.",
|
||||||
|
"lists.no_results_found": "No se encontraron resultados.",
|
||||||
|
"lists.remove_member": "Eliminar",
|
||||||
"lists.replies_policy.followed": "Cualquier usuario seguido",
|
"lists.replies_policy.followed": "Cualquier usuario seguido",
|
||||||
"lists.replies_policy.list": "Miembros de la lista",
|
"lists.replies_policy.list": "Miembros de la lista",
|
||||||
"lists.replies_policy.none": "Nadie",
|
"lists.replies_policy.none": "Nadie",
|
||||||
"lists.replies_policy.title": "Mostrar respuestas a:",
|
"lists.save": "Guardar",
|
||||||
"lists.search": "Buscar entre las personas a las que sigues",
|
"lists.search_placeholder": "Buscar gente a la que sigues",
|
||||||
"lists.subheading": "Tus listas",
|
"lists.show_replies_to": "Incluir las respuestas de los miembros de la lista a",
|
||||||
"load_pending": "{count, plural, one {# nuevo elemento} other {# nuevos elementos}}",
|
"load_pending": "{count, plural, one {# nuevo elemento} other {# nuevos elementos}}",
|
||||||
"loading_indicator.label": "Cargando…",
|
"loading_indicator.label": "Cargando…",
|
||||||
"media_gallery.hide": "Ocultar",
|
"media_gallery.hide": "Ocultar",
|
||||||
@ -638,11 +652,11 @@
|
|||||||
"onboarding.action.back": "Llévame atrás",
|
"onboarding.action.back": "Llévame atrás",
|
||||||
"onboarding.actions.back": "Llévame atrás",
|
"onboarding.actions.back": "Llévame atrás",
|
||||||
"onboarding.actions.go_to_explore": "Llévame a tendencias",
|
"onboarding.actions.go_to_explore": "Llévame a tendencias",
|
||||||
"onboarding.actions.go_to_home": "Ir a mi inicio",
|
"onboarding.actions.go_to_home": "Ir a mi página de inicio",
|
||||||
"onboarding.compose.template": "¡Hola #Mastodon!",
|
"onboarding.compose.template": "¡Hola #Mastodon!",
|
||||||
"onboarding.follows.empty": "Desafortunadamente, no se pueden mostrar resultados en este momento. Puedes intentar usar la búsqueda o navegar por la página de exploración para encontrar personas a las que seguir, o inténtalo de nuevo más tarde.",
|
"onboarding.follows.empty": "Desafortunadamente, no se pueden mostrar resultados en este momento. Puedes intentar usar la búsqueda o navegar por la página de exploración para encontrar personas a las que seguir, o inténtalo de nuevo más tarde.",
|
||||||
"onboarding.follows.lead": "Tu línea de inicio es la forma principal de experimentar Mastodon. Cuanta más personas sigas, más activa e interesante será. Para empezar, aquí hay algunas sugerencias:",
|
"onboarding.follows.lead": "Tu página de inicio es la forma principal de experimentar Mastodon. Cuanta más personas sigas, más activa e interesante será. Para empezar, aquí hay algunas sugerencias:",
|
||||||
"onboarding.follows.title": "Personaliza tu línea de inicio",
|
"onboarding.follows.title": "Personaliza tu página de inicio",
|
||||||
"onboarding.profile.discoverable": "Hacer que mi perfil aparezca en búsquedas",
|
"onboarding.profile.discoverable": "Hacer que mi perfil aparezca en búsquedas",
|
||||||
"onboarding.profile.discoverable_hint": "Cuando permites que tu perfil aparezca en búsquedas en Mastodon, tus publicaciones podrán aparecer en los resultados de búsqueda y en tendencias, y tu perfil podrá recomendarse a gente con intereses similares a los tuyos.",
|
"onboarding.profile.discoverable_hint": "Cuando permites que tu perfil aparezca en búsquedas en Mastodon, tus publicaciones podrán aparecer en los resultados de búsqueda y en tendencias, y tu perfil podrá recomendarse a gente con intereses similares a los tuyos.",
|
||||||
"onboarding.profile.display_name": "Nombre para mostrar",
|
"onboarding.profile.display_name": "Nombre para mostrar",
|
||||||
@ -662,7 +676,7 @@
|
|||||||
"onboarding.start.skip": "¿No necesitas ayuda para empezar?",
|
"onboarding.start.skip": "¿No necesitas ayuda para empezar?",
|
||||||
"onboarding.start.title": "¡Lo has logrado!",
|
"onboarding.start.title": "¡Lo has logrado!",
|
||||||
"onboarding.steps.follow_people.body": "Seguir personas interesante es de lo que trata Mastodon.",
|
"onboarding.steps.follow_people.body": "Seguir personas interesante es de lo que trata Mastodon.",
|
||||||
"onboarding.steps.follow_people.title": "Personaliza tu línea de inicio",
|
"onboarding.steps.follow_people.title": "Personaliza tu página de inicio",
|
||||||
"onboarding.steps.publish_status.body": "Di hola al mundo con texto, fotos, vídeos o encuestas {emoji}",
|
"onboarding.steps.publish_status.body": "Di hola al mundo con texto, fotos, vídeos o encuestas {emoji}",
|
||||||
"onboarding.steps.publish_status.title": "Escribe tu primera publicación",
|
"onboarding.steps.publish_status.title": "Escribe tu primera publicación",
|
||||||
"onboarding.steps.setup_profile.body": "Aumenta tus interacciones con un perfil completo.",
|
"onboarding.steps.setup_profile.body": "Aumenta tus interacciones con un perfil completo.",
|
||||||
@ -701,7 +715,7 @@
|
|||||||
"recommended": "Recomendado",
|
"recommended": "Recomendado",
|
||||||
"refresh": "Actualizar",
|
"refresh": "Actualizar",
|
||||||
"regeneration_indicator.label": "Cargando…",
|
"regeneration_indicator.label": "Cargando…",
|
||||||
"regeneration_indicator.sublabel": "¡Tu historia de inicio se está preparando!",
|
"regeneration_indicator.sublabel": "¡Tu página de inicio se está preparando!",
|
||||||
"relative_time.days": "{number} d",
|
"relative_time.days": "{number} d",
|
||||||
"relative_time.full.days": "hace {number, plural, one {# día} other {# días}}",
|
"relative_time.full.days": "hace {number, plural, one {# día} other {# días}}",
|
||||||
"relative_time.full.hours": "hace {number, plural, one {# hora} other {# horas}}",
|
"relative_time.full.hours": "hace {number, plural, one {# hora} other {# horas}}",
|
||||||
@ -755,7 +769,7 @@
|
|||||||
"report.thanks.title": "¿No quieres esto?",
|
"report.thanks.title": "¿No quieres esto?",
|
||||||
"report.thanks.title_actionable": "Gracias por informar, estudiaremos esto.",
|
"report.thanks.title_actionable": "Gracias por informar, estudiaremos esto.",
|
||||||
"report.unfollow": "Dejar de seguir a @{name}",
|
"report.unfollow": "Dejar de seguir a @{name}",
|
||||||
"report.unfollow_explanation": "Estás siguiendo esta cuenta. Para no ver sus publicaciones en tu muro de inicio, deja de seguirla.",
|
"report.unfollow_explanation": "Estás siguiendo esta cuenta. Para dejar de ver sus publicaciones en tu página de inicio, deja de seguirla.",
|
||||||
"report_notification.attached_statuses": "{count, plural, one {{count} publicación} other {{count} publicaciones}} adjunta(s)",
|
"report_notification.attached_statuses": "{count, plural, one {{count} publicación} other {{count} publicaciones}} adjunta(s)",
|
||||||
"report_notification.categories.legal": "Legal",
|
"report_notification.categories.legal": "Legal",
|
||||||
"report_notification.categories.legal_sentence": "contenido ilegal",
|
"report_notification.categories.legal_sentence": "contenido ilegal",
|
||||||
|
@ -273,7 +273,6 @@
|
|||||||
"empty_column.hashtag": "Selle sildi all ei ole ühtegi postitust.",
|
"empty_column.hashtag": "Selle sildi all ei ole ühtegi postitust.",
|
||||||
"empty_column.home": "Su koduajajoon on tühi. Jälgi rohkemaid inimesi, et seda täita {suggestions}",
|
"empty_column.home": "Su koduajajoon on tühi. Jälgi rohkemaid inimesi, et seda täita {suggestions}",
|
||||||
"empty_column.list": "Siin loetelus pole veel midagi. Kui loetelu liikmed teevad uusi postitusi, näed neid siin.",
|
"empty_column.list": "Siin loetelus pole veel midagi. Kui loetelu liikmed teevad uusi postitusi, näed neid siin.",
|
||||||
"empty_column.lists": "Pole veel ühtegi nimekirja. Kui lood mõne, näed neid siin.",
|
|
||||||
"empty_column.mutes": "Sa pole veel ühtegi kasutajat vaigistanud.",
|
"empty_column.mutes": "Sa pole veel ühtegi kasutajat vaigistanud.",
|
||||||
"empty_column.notification_requests": "Kõik tühi! Siin pole mitte midagi. Kui saad uusi teavitusi, ilmuvad need siin vastavalt sinu seadistustele.",
|
"empty_column.notification_requests": "Kõik tühi! Siin pole mitte midagi. Kui saad uusi teavitusi, ilmuvad need siin vastavalt sinu seadistustele.",
|
||||||
"empty_column.notifications": "Ei ole veel teateid. Kui keegi suhtleb sinuga, näed seda siin.",
|
"empty_column.notifications": "Ei ole veel teateid. Kui keegi suhtleb sinuga, näed seda siin.",
|
||||||
@ -446,20 +445,11 @@
|
|||||||
"link_preview.author": "{name} poolt",
|
"link_preview.author": "{name} poolt",
|
||||||
"link_preview.more_from_author": "Veel kasutajalt {name}",
|
"link_preview.more_from_author": "Veel kasutajalt {name}",
|
||||||
"link_preview.shares": "{count, plural, one {{counter} postitus} other {{counter} postitust}}",
|
"link_preview.shares": "{count, plural, one {{counter} postitus} other {{counter} postitust}}",
|
||||||
"lists.account.add": "Lisa nimekirja",
|
|
||||||
"lists.account.remove": "Eemalda nimekirjast",
|
|
||||||
"lists.delete": "Kustuta nimekiri",
|
"lists.delete": "Kustuta nimekiri",
|
||||||
"lists.edit": "Muuda nimekirja",
|
"lists.edit": "Muuda nimekirja",
|
||||||
"lists.edit.submit": "Pealkirja muutmine",
|
|
||||||
"lists.exclusive": "Peida koduvaatest need postitused",
|
|
||||||
"lists.new.create": "Lisa nimekiri",
|
|
||||||
"lists.new.title_placeholder": "Uue nimekirja pealkiri",
|
|
||||||
"lists.replies_policy.followed": "Igalt jälgitud kasutajalt",
|
"lists.replies_policy.followed": "Igalt jälgitud kasutajalt",
|
||||||
"lists.replies_policy.list": "Listi liikmetelt",
|
"lists.replies_policy.list": "Listi liikmetelt",
|
||||||
"lists.replies_policy.none": "Mitte kelleltki",
|
"lists.replies_policy.none": "Mitte kelleltki",
|
||||||
"lists.replies_policy.title": "Näita vastuseid nendele:",
|
|
||||||
"lists.search": "Otsi enda jälgitavate inimeste hulgast",
|
|
||||||
"lists.subheading": "Sinu nimekirjad",
|
|
||||||
"load_pending": "{count, plural, one {# uus kirje} other {# uut kirjet}}",
|
"load_pending": "{count, plural, one {# uus kirje} other {# uut kirjet}}",
|
||||||
"loading_indicator.label": "Laadimine…",
|
"loading_indicator.label": "Laadimine…",
|
||||||
"media_gallery.hide": "Peida",
|
"media_gallery.hide": "Peida",
|
||||||
|
@ -269,7 +269,6 @@
|
|||||||
"empty_column.hashtag": "Ez dago ezer traola honetan oraindik.",
|
"empty_column.hashtag": "Ez dago ezer traola honetan oraindik.",
|
||||||
"empty_column.home": "Zure hasierako denbora-lerroa hutsik dago! Jarraitu jende gehiago betetzeko.",
|
"empty_column.home": "Zure hasierako denbora-lerroa hutsik dago! Jarraitu jende gehiago betetzeko.",
|
||||||
"empty_column.list": "Ez dago ezer zerrenda honetan. Zerrenda honetako kideek bidalketa berriak argitaratzean, hemen agertuko dira.",
|
"empty_column.list": "Ez dago ezer zerrenda honetan. Zerrenda honetako kideek bidalketa berriak argitaratzean, hemen agertuko dira.",
|
||||||
"empty_column.lists": "Ez duzu zerrendarik oraindik. Baten bat sortzen duzunean hemen agertuko da.",
|
|
||||||
"empty_column.mutes": "Ez duzu erabiltzailerik mututu oraindik.",
|
"empty_column.mutes": "Ez duzu erabiltzailerik mututu oraindik.",
|
||||||
"empty_column.notification_requests": "Garbi-garbi! Ezertxo ere ez hemen. Jakinarazpenak jasotzen dituzunean, hemen agertuko dira zure ezarpenen arabera.",
|
"empty_column.notification_requests": "Garbi-garbi! Ezertxo ere ez hemen. Jakinarazpenak jasotzen dituzunean, hemen agertuko dira zure ezarpenen arabera.",
|
||||||
"empty_column.notifications": "Ez duzu jakinarazpenik oraindik. Jarri besteekin harremanetan elkarrizketa abiatzeko.",
|
"empty_column.notifications": "Ez duzu jakinarazpenik oraindik. Jarri besteekin harremanetan elkarrizketa abiatzeko.",
|
||||||
@ -437,20 +436,11 @@
|
|||||||
"link_preview.author": "Egilea: {name}",
|
"link_preview.author": "Egilea: {name}",
|
||||||
"link_preview.more_from_author": "{name} erabiltzaileaz gehiago jakin",
|
"link_preview.more_from_author": "{name} erabiltzaileaz gehiago jakin",
|
||||||
"link_preview.shares": "{count, plural, one {{counter} bidalketa} other {{counter} bidalketa}}",
|
"link_preview.shares": "{count, plural, one {{counter} bidalketa} other {{counter} bidalketa}}",
|
||||||
"lists.account.add": "Gehitu zerrendara",
|
|
||||||
"lists.account.remove": "Kendu zerrendatik",
|
|
||||||
"lists.delete": "Ezabatu zerrenda",
|
"lists.delete": "Ezabatu zerrenda",
|
||||||
"lists.edit": "Editatu zerrenda",
|
"lists.edit": "Editatu zerrenda",
|
||||||
"lists.edit.submit": "Aldatu izenburua",
|
|
||||||
"lists.exclusive": "Ezkutatu argitalpen hauek hasieratik",
|
|
||||||
"lists.new.create": "Gehitu zerrenda",
|
|
||||||
"lists.new.title_placeholder": "Zerrenda berriaren izena",
|
|
||||||
"lists.replies_policy.followed": "Jarraitutako edozein erabiltzaile",
|
"lists.replies_policy.followed": "Jarraitutako edozein erabiltzaile",
|
||||||
"lists.replies_policy.list": "Zerrendako kideak",
|
"lists.replies_policy.list": "Zerrendako kideak",
|
||||||
"lists.replies_policy.none": "Bat ere ez",
|
"lists.replies_policy.none": "Bat ere ez",
|
||||||
"lists.replies_policy.title": "Erakutsi erantzunak:",
|
|
||||||
"lists.search": "Bilatu jarraitzen dituzun pertsonen artean",
|
|
||||||
"lists.subheading": "Zure zerrendak",
|
|
||||||
"load_pending": "{count, plural, one {elementu berri #} other {# elementu berri}}",
|
"load_pending": "{count, plural, one {elementu berri #} other {# elementu berri}}",
|
||||||
"loading_indicator.label": "Kargatzen…",
|
"loading_indicator.label": "Kargatzen…",
|
||||||
"media_gallery.hide": "Ezkutatu",
|
"media_gallery.hide": "Ezkutatu",
|
||||||
|
@ -87,6 +87,7 @@
|
|||||||
"alert.unexpected.title": "ای وای!",
|
"alert.unexpected.title": "ای وای!",
|
||||||
"alt_text_badge.title": "متن جایگزین",
|
"alt_text_badge.title": "متن جایگزین",
|
||||||
"announcement.announcement": "اعلامیه",
|
"announcement.announcement": "اعلامیه",
|
||||||
|
"annual_report.summary.followers.followers": "دنبال کننده",
|
||||||
"attachments_list.unprocessed": "(پردازش نشده)",
|
"attachments_list.unprocessed": "(پردازش نشده)",
|
||||||
"audio.hide": "نهفتن صدا",
|
"audio.hide": "نهفتن صدا",
|
||||||
"block_modal.remote_users_caveat": "ما از کارساز {domain} خواهیم خواست که به تصمیم شما احترام بگذارد. با این حال، تضمینی برای رعایت آن وجود ندارد زیرا برخی کارسازها ممکن است بلوکها را بهطور متفاوتی مدیریت کنند. فرستههای عمومی ممکن است همچنان برای کاربران که وارد نشده قابل مشاهده باشند.",
|
"block_modal.remote_users_caveat": "ما از کارساز {domain} خواهیم خواست که به تصمیم شما احترام بگذارد. با این حال، تضمینی برای رعایت آن وجود ندارد زیرا برخی کارسازها ممکن است بلوکها را بهطور متفاوتی مدیریت کنند. فرستههای عمومی ممکن است همچنان برای کاربران که وارد نشده قابل مشاهده باشند.",
|
||||||
@ -272,7 +273,6 @@
|
|||||||
"empty_column.hashtag": "هنوز هیچ چیزی در این برچسب نیست.",
|
"empty_column.hashtag": "هنوز هیچ چیزی در این برچسب نیست.",
|
||||||
"empty_column.home": "خط زمانی خانگیتان خالی است! برای پر کردنش، افراد بیشتری را پی بگیرید. {suggestions}",
|
"empty_column.home": "خط زمانی خانگیتان خالی است! برای پر کردنش، افراد بیشتری را پی بگیرید. {suggestions}",
|
||||||
"empty_column.list": "هنوز چیزی در این سیاهه نیست. هنگامی که اعضایش فرستههای جدیدی بفرستند، اینجا ظاهر خواهند شد.",
|
"empty_column.list": "هنوز چیزی در این سیاهه نیست. هنگامی که اعضایش فرستههای جدیدی بفرستند، اینجا ظاهر خواهند شد.",
|
||||||
"empty_column.lists": "هنوز هیچ سیاههای ندارید. هنگامی که یکی بسازید، اینجا نشان داده خواهد شد.",
|
|
||||||
"empty_column.mutes": "هنوز هیچ کاربری را خموش نکردهاید.",
|
"empty_column.mutes": "هنوز هیچ کاربری را خموش نکردهاید.",
|
||||||
"empty_column.notifications": "هنوز هیچ آگاهیآی ندارید. هنگامی که دیگران با شما برهمکنش داشته باشند،اینحا خواهید دیدش.",
|
"empty_column.notifications": "هنوز هیچ آگاهیآی ندارید. هنگامی که دیگران با شما برهمکنش داشته باشند،اینحا خواهید دیدش.",
|
||||||
"empty_column.public": "اینجا هنوز چیزی نیست! خودتان چیزی بنویسید یا کاربران کارسازهای دیگر را پیگیری کنید تا اینجا پُر شود",
|
"empty_column.public": "اینجا هنوز چیزی نیست! خودتان چیزی بنویسید یا کاربران کارسازهای دیگر را پیگیری کنید تا اینجا پُر شود",
|
||||||
@ -437,20 +437,13 @@
|
|||||||
"link_preview.author": "از {name}",
|
"link_preview.author": "از {name}",
|
||||||
"link_preview.more_from_author": "بیشتر از {name}",
|
"link_preview.more_from_author": "بیشتر از {name}",
|
||||||
"link_preview.shares": "{count, plural, one {{counter} فرسته} other {{counter} فرسته}}",
|
"link_preview.shares": "{count, plural, one {{counter} فرسته} other {{counter} فرسته}}",
|
||||||
"lists.account.add": "افزودن به سیاهه",
|
|
||||||
"lists.account.remove": "برداشتن از سیاهه",
|
|
||||||
"lists.delete": "حذف سیاهه",
|
"lists.delete": "حذف سیاهه",
|
||||||
"lists.edit": "ویرایش سیاهه",
|
"lists.edit": "ویرایش سیاهه",
|
||||||
"lists.edit.submit": "تغییر عنوان",
|
"lists.remove_member": "حذف",
|
||||||
"lists.exclusive": "نهفتن این فرستهها از خانه",
|
|
||||||
"lists.new.create": "افزودن سیاهه",
|
|
||||||
"lists.new.title_placeholder": "عنوان سیاههٔ جدید",
|
|
||||||
"lists.replies_policy.followed": "هر کاربر پیگرفته",
|
"lists.replies_policy.followed": "هر کاربر پیگرفته",
|
||||||
"lists.replies_policy.list": "اعضای سیاهه",
|
"lists.replies_policy.list": "اعضای سیاهه",
|
||||||
"lists.replies_policy.none": "هیچ کدام",
|
"lists.replies_policy.none": "هیچ کدام",
|
||||||
"lists.replies_policy.title": "نمایش پاسخها به:",
|
"lists.save": "ذخیره",
|
||||||
"lists.search": "جستوجو بین کسانی که پیگرفتهاید",
|
|
||||||
"lists.subheading": "سیاهههایتان",
|
|
||||||
"load_pending": "{count, plural, one {# مورد جدید} other {# مورد جدید}}",
|
"load_pending": "{count, plural, one {# مورد جدید} other {# مورد جدید}}",
|
||||||
"loading_indicator.label": "در حال بارگذاری…",
|
"loading_indicator.label": "در حال بارگذاری…",
|
||||||
"media_gallery.hide": "نهفتن",
|
"media_gallery.hide": "نهفتن",
|
||||||
|
@ -139,13 +139,16 @@
|
|||||||
"column.blocks": "Estetyt käyttäjät",
|
"column.blocks": "Estetyt käyttäjät",
|
||||||
"column.bookmarks": "Kirjanmerkit",
|
"column.bookmarks": "Kirjanmerkit",
|
||||||
"column.community": "Paikallinen aikajana",
|
"column.community": "Paikallinen aikajana",
|
||||||
|
"column.create_list": "Luo lista",
|
||||||
"column.direct": "Yksityismaininnat",
|
"column.direct": "Yksityismaininnat",
|
||||||
"column.directory": "Selaa profiileja",
|
"column.directory": "Selaa profiileja",
|
||||||
"column.domain_blocks": "Estetyt verkkotunnukset",
|
"column.domain_blocks": "Estetyt verkkotunnukset",
|
||||||
|
"column.edit_list": "Muokkaa listaa",
|
||||||
"column.favourites": "Suosikit",
|
"column.favourites": "Suosikit",
|
||||||
"column.firehose": "Livesyötteet",
|
"column.firehose": "Livesyötteet",
|
||||||
"column.follow_requests": "Seurantapyynnöt",
|
"column.follow_requests": "Seurantapyynnöt",
|
||||||
"column.home": "Koti",
|
"column.home": "Koti",
|
||||||
|
"column.list_members": "Hallitse listan jäseniä",
|
||||||
"column.lists": "Listat",
|
"column.lists": "Listat",
|
||||||
"column.mutes": "Mykistetyt käyttäjät",
|
"column.mutes": "Mykistetyt käyttäjät",
|
||||||
"column.notifications": "Ilmoitukset",
|
"column.notifications": "Ilmoitukset",
|
||||||
@ -291,7 +294,6 @@
|
|||||||
"empty_column.hashtag": "Tällä aihetunnisteella ei löydy vielä sisältöä.",
|
"empty_column.hashtag": "Tällä aihetunnisteella ei löydy vielä sisältöä.",
|
||||||
"empty_column.home": "Kotiaikajanasi on tyhjä! Seuraa useampia käyttäjiä, niin näet enemmän sisältöä.",
|
"empty_column.home": "Kotiaikajanasi on tyhjä! Seuraa useampia käyttäjiä, niin näet enemmän sisältöä.",
|
||||||
"empty_column.list": "Tällä listalla ei ole vielä mitään. Kun tämän listan jäsenet lähettävät uusia julkaisuja, ne näkyvät tässä.",
|
"empty_column.list": "Tällä listalla ei ole vielä mitään. Kun tämän listan jäsenet lähettävät uusia julkaisuja, ne näkyvät tässä.",
|
||||||
"empty_column.lists": "Sinulla ei ole vielä yhtään listaa. Kun luot sellaisen, näkyy se tässä.",
|
|
||||||
"empty_column.mutes": "Et ole mykistänyt vielä yhtään käyttäjää.",
|
"empty_column.mutes": "Et ole mykistänyt vielä yhtään käyttäjää.",
|
||||||
"empty_column.notification_requests": "Olet ajan tasalla! Täällä ei ole mitään uutta kerrottavaa. Kun saat uusia ilmoituksia, ne näkyvät täällä asetustesi mukaisesti.",
|
"empty_column.notification_requests": "Olet ajan tasalla! Täällä ei ole mitään uutta kerrottavaa. Kun saat uusia ilmoituksia, ne näkyvät täällä asetustesi mukaisesti.",
|
||||||
"empty_column.notifications": "Sinulla ei ole vielä ilmoituksia. Kun muut ovat vuorovaikutuksessa kanssasi, näet sen täällä.",
|
"empty_column.notifications": "Sinulla ei ole vielä ilmoituksia. Kun muut ovat vuorovaikutuksessa kanssasi, näet sen täällä.",
|
||||||
@ -464,20 +466,32 @@
|
|||||||
"link_preview.author": "Tehnyt {name}",
|
"link_preview.author": "Tehnyt {name}",
|
||||||
"link_preview.more_from_author": "Lisää tekijältä {name}",
|
"link_preview.more_from_author": "Lisää tekijältä {name}",
|
||||||
"link_preview.shares": "{count, plural, one {{counter} julkaisu} other {{counter} julkaisua}}",
|
"link_preview.shares": "{count, plural, one {{counter} julkaisu} other {{counter} julkaisua}}",
|
||||||
"lists.account.add": "Lisää listalle",
|
"lists.add_member": "Lisää",
|
||||||
"lists.account.remove": "Poista listalta",
|
"lists.add_to_list": "Lisää listalle",
|
||||||
|
"lists.add_to_lists": "Lisää {name} listalle",
|
||||||
|
"lists.create": "Luo",
|
||||||
|
"lists.create_a_list_to_organize": "Luo uusi lista kotisyötteesi järjestämiseksi",
|
||||||
|
"lists.create_list": "Luo lista",
|
||||||
"lists.delete": "Poista lista",
|
"lists.delete": "Poista lista",
|
||||||
|
"lists.done": "Valmis",
|
||||||
"lists.edit": "Muokkaa listaa",
|
"lists.edit": "Muokkaa listaa",
|
||||||
"lists.edit.submit": "Vaihda nimi",
|
"lists.exclusive": "Piilota jäsenet kotisyötteestä",
|
||||||
"lists.exclusive": "Piilota nämä julkaisut kotisyötteestä",
|
"lists.exclusive_hint": "Jos joku on tällä listalla, piilota hänet kotisyötteestäsi, jotta et näe hänen julkaisujaan kahteen kertaan.",
|
||||||
"lists.new.create": "Lisää lista",
|
"lists.find_users_to_add": "Etsi lisättäviä käyttäjiä",
|
||||||
"lists.new.title_placeholder": "Uuden listan nimi",
|
"lists.list_members": "Listan jäsenet",
|
||||||
|
"lists.list_members_count": "{count, plural, one {# jäsen} other {# jäsentä}}",
|
||||||
|
"lists.list_name": "Listan nimi",
|
||||||
|
"lists.new_list_name": "Uuden listan nimi",
|
||||||
|
"lists.no_lists_yet": "Ei vielä listoja.",
|
||||||
|
"lists.no_members_yet": "Ei vielä jäseniä.",
|
||||||
|
"lists.no_results_found": "Tuloksia ei löytynyt.",
|
||||||
|
"lists.remove_member": "Poista",
|
||||||
"lists.replies_policy.followed": "Jokaiselle seuratulle käyttäjälle",
|
"lists.replies_policy.followed": "Jokaiselle seuratulle käyttäjälle",
|
||||||
"lists.replies_policy.list": "Listan jäsenille",
|
"lists.replies_policy.list": "Listan jäsenille",
|
||||||
"lists.replies_policy.none": "Ei kellekään",
|
"lists.replies_policy.none": "Ei kellekään",
|
||||||
"lists.replies_policy.title": "Näytä vastaukset:",
|
"lists.save": "Tallenna",
|
||||||
"lists.search": "Hae seuraamistasi käyttäjistä",
|
"lists.search_placeholder": "Hae käyttäjiä seurattavaksi",
|
||||||
"lists.subheading": "Omat listasi",
|
"lists.show_replies_to": "Sisällytä listan jäsenten vastaukset kohteeseen",
|
||||||
"load_pending": "{count, plural, one {# uusi kohde} other {# uutta kohdetta}}",
|
"load_pending": "{count, plural, one {# uusi kohde} other {# uutta kohdetta}}",
|
||||||
"loading_indicator.label": "Ladataan…",
|
"loading_indicator.label": "Ladataan…",
|
||||||
"media_gallery.hide": "Piilota",
|
"media_gallery.hide": "Piilota",
|
||||||
|
@ -184,7 +184,6 @@
|
|||||||
"empty_column.hashtag": "Wala pang laman ang hashtag na ito.",
|
"empty_column.hashtag": "Wala pang laman ang hashtag na ito.",
|
||||||
"empty_column.home": "Walang laman ang timeline ng tahanan mo! Sumunod sa marami pang tao para mapunan ito.",
|
"empty_column.home": "Walang laman ang timeline ng tahanan mo! Sumunod sa marami pang tao para mapunan ito.",
|
||||||
"empty_column.list": "Wala pang laman ang listahang ito. Kapag naglathala ng mga bagong post ang mga miyembro ng listahang ito, makikita iyon dito.",
|
"empty_column.list": "Wala pang laman ang listahang ito. Kapag naglathala ng mga bagong post ang mga miyembro ng listahang ito, makikita iyon dito.",
|
||||||
"empty_column.lists": "Wala ka pang mga listahan. Kapag gumawa ka ng isa, makikita yun dito.",
|
|
||||||
"errors.unexpected_crash.report_issue": "Iulat ang isyu",
|
"errors.unexpected_crash.report_issue": "Iulat ang isyu",
|
||||||
"explore.search_results": "Mga resulta ng paghahanap",
|
"explore.search_results": "Mga resulta ng paghahanap",
|
||||||
"explore.suggested_follows": "Mga tao",
|
"explore.suggested_follows": "Mga tao",
|
||||||
@ -234,15 +233,8 @@
|
|||||||
"lightbox.next": "Susunod",
|
"lightbox.next": "Susunod",
|
||||||
"lightbox.previous": "Nakaraan",
|
"lightbox.previous": "Nakaraan",
|
||||||
"link_preview.author": "Ni/ng {name}",
|
"link_preview.author": "Ni/ng {name}",
|
||||||
"lists.account.add": "Idagdag sa talaan",
|
|
||||||
"lists.account.remove": "Tanggalin mula sa talaan",
|
|
||||||
"lists.delete": "Burahin ang listahan",
|
"lists.delete": "Burahin ang listahan",
|
||||||
"lists.edit.submit": "Baguhin ang pamagat",
|
|
||||||
"lists.new.create": "Idagdag sa talaan",
|
|
||||||
"lists.new.title_placeholder": "Bagong pangalan ng talaan",
|
|
||||||
"lists.replies_policy.none": "Walang simuman",
|
"lists.replies_policy.none": "Walang simuman",
|
||||||
"lists.replies_policy.title": "Ipakita ang mga tugon sa:",
|
|
||||||
"lists.subheading": "Iyong mga talaan",
|
|
||||||
"loading_indicator.label": "Kumakarga…",
|
"loading_indicator.label": "Kumakarga…",
|
||||||
"media_gallery.hide": "Itago",
|
"media_gallery.hide": "Itago",
|
||||||
"mute_modal.hide_from_notifications": "Itago mula sa mga abiso",
|
"mute_modal.hide_from_notifications": "Itago mula sa mga abiso",
|
||||||
|
@ -140,13 +140,16 @@
|
|||||||
"column.blocks": "Bannaðir brúkarar",
|
"column.blocks": "Bannaðir brúkarar",
|
||||||
"column.bookmarks": "Bókamerki",
|
"column.bookmarks": "Bókamerki",
|
||||||
"column.community": "Lokal tíðarlinja",
|
"column.community": "Lokal tíðarlinja",
|
||||||
|
"column.create_list": "Ger lista",
|
||||||
"column.direct": "Privatar umrøður",
|
"column.direct": "Privatar umrøður",
|
||||||
"column.directory": "Blaða gjøgnum vangar",
|
"column.directory": "Blaða gjøgnum vangar",
|
||||||
"column.domain_blocks": "Bannað økisnøvn",
|
"column.domain_blocks": "Bannað økisnøvn",
|
||||||
|
"column.edit_list": "Broyt lista",
|
||||||
"column.favourites": "Dámdir postar",
|
"column.favourites": "Dámdir postar",
|
||||||
"column.firehose": "Beinleiðis rásir",
|
"column.firehose": "Beinleiðis rásir",
|
||||||
"column.follow_requests": "Umbønir at fylgja",
|
"column.follow_requests": "Umbønir at fylgja",
|
||||||
"column.home": "Heim",
|
"column.home": "Heim",
|
||||||
|
"column.list_members": "Rætta limalista",
|
||||||
"column.lists": "Listar",
|
"column.lists": "Listar",
|
||||||
"column.mutes": "Sløktir brúkarar",
|
"column.mutes": "Sløktir brúkarar",
|
||||||
"column.notifications": "Fráboðanir",
|
"column.notifications": "Fráboðanir",
|
||||||
@ -292,7 +295,6 @@
|
|||||||
"empty_column.hashtag": "Einki er í hesum frámerkinum enn.",
|
"empty_column.hashtag": "Einki er í hesum frámerkinum enn.",
|
||||||
"empty_column.home": "Heima-tíðarlinjan hjá tær er tóm! Fylg fleiri fyri at fylla hana. {suggestions}",
|
"empty_column.home": "Heima-tíðarlinjan hjá tær er tóm! Fylg fleiri fyri at fylla hana. {suggestions}",
|
||||||
"empty_column.list": "Einki er í hesum listanum enn. Tá limir í hesum listanum posta nýggjar postar, so síggjast teir her.",
|
"empty_column.list": "Einki er í hesum listanum enn. Tá limir í hesum listanum posta nýggjar postar, so síggjast teir her.",
|
||||||
"empty_column.lists": "Tú hevur ongar goymdar listar enn. Tá tú gert ein lista, so sært tú hann her.",
|
|
||||||
"empty_column.mutes": "Tú hevur enn ikki doyvt nakran brúkara.",
|
"empty_column.mutes": "Tú hevur enn ikki doyvt nakran brúkara.",
|
||||||
"empty_column.notification_requests": "Alt er klárt! Her er einki. Tá tú fært nýggjar fráboðanir, síggjast tær her sambært tínum stillingum.",
|
"empty_column.notification_requests": "Alt er klárt! Her er einki. Tá tú fært nýggjar fráboðanir, síggjast tær her sambært tínum stillingum.",
|
||||||
"empty_column.notifications": "Tú hevur ongar fráboðanir enn. Tá onnur samskifta við teg, so sær tú fráboðaninar her.",
|
"empty_column.notifications": "Tú hevur ongar fráboðanir enn. Tá onnur samskifta við teg, so sær tú fráboðaninar her.",
|
||||||
@ -465,20 +467,32 @@
|
|||||||
"link_preview.author": "Av {name}",
|
"link_preview.author": "Av {name}",
|
||||||
"link_preview.more_from_author": "Meira frá {name}",
|
"link_preview.more_from_author": "Meira frá {name}",
|
||||||
"link_preview.shares": "{count, plural, one {{counter} postur} other {{counter} postar}}",
|
"link_preview.shares": "{count, plural, one {{counter} postur} other {{counter} postar}}",
|
||||||
"lists.account.add": "Legg afturat lista",
|
"lists.add_member": "Legg afturat",
|
||||||
"lists.account.remove": "Tak av lista",
|
"lists.add_to_list": "Legg afturat lista",
|
||||||
|
"lists.add_to_lists": "Legg {name} afturat lista",
|
||||||
|
"lists.create": "Ger",
|
||||||
|
"lists.create_a_list_to_organize": "Ger ein nýggjan lista til heimarásina hjá tær",
|
||||||
|
"lists.create_list": "Ger lista",
|
||||||
"lists.delete": "Strika lista",
|
"lists.delete": "Strika lista",
|
||||||
|
"lists.done": "Liðugt",
|
||||||
"lists.edit": "Broyt lista",
|
"lists.edit": "Broyt lista",
|
||||||
"lists.edit.submit": "Broyt heiti",
|
"lists.exclusive": "Fjal limir á heimarás",
|
||||||
"lists.exclusive": "Fjal hesar postarnar frá heima",
|
"lists.exclusive_hint": "Um onkur er á hesum listanum, so skulu tey fjalast á heimarásini, so tú sleppir undan at síggja postar teirra tvær ferðir.",
|
||||||
"lists.new.create": "Ger nýggjan lista",
|
"lists.find_users_to_add": "Finn brúkarar at leggja afturat",
|
||||||
"lists.new.title_placeholder": "Nýtt navn á lista",
|
"lists.list_members": "Lista limir",
|
||||||
|
"lists.list_members_count": "{count, plural, one {# limur} other {# limir}}",
|
||||||
|
"lists.list_name": "Listanavn",
|
||||||
|
"lists.new_list_name": "Nýtt listanavn",
|
||||||
|
"lists.no_lists_yet": "Ongir listar enn.",
|
||||||
|
"lists.no_members_yet": "Eingir limir enn.",
|
||||||
|
"lists.no_results_found": "Eingi úrslit funnin.",
|
||||||
|
"lists.remove_member": "Burturbein",
|
||||||
"lists.replies_policy.followed": "Øllum fylgdum brúkarum",
|
"lists.replies_policy.followed": "Øllum fylgdum brúkarum",
|
||||||
"lists.replies_policy.list": "Listalimunum",
|
"lists.replies_policy.list": "Listalimunum",
|
||||||
"lists.replies_policy.none": "Eingin",
|
"lists.replies_policy.none": "Eingin",
|
||||||
"lists.replies_policy.title": "Vís svarini fyri:",
|
"lists.save": "Goym",
|
||||||
"lists.search": "Leita millum fólk, sum tú fylgir",
|
"lists.search_placeholder": "Leita eftir fólki, sum tú fylgir",
|
||||||
"lists.subheading": "Tínir listar",
|
"lists.show_replies_to": "Írokna svar frá limum á listanum til",
|
||||||
"load_pending": "{count, plural, one {# nýtt evni} other {# nýggj evni}}",
|
"load_pending": "{count, plural, one {# nýtt evni} other {# nýggj evni}}",
|
||||||
"loading_indicator.label": "Innlesur…",
|
"loading_indicator.label": "Innlesur…",
|
||||||
"media_gallery.hide": "Fjal",
|
"media_gallery.hide": "Fjal",
|
||||||
|
@ -87,9 +87,21 @@
|
|||||||
"alert.unexpected.title": "Oups!",
|
"alert.unexpected.title": "Oups!",
|
||||||
"alt_text_badge.title": "Texte Alt",
|
"alt_text_badge.title": "Texte Alt",
|
||||||
"announcement.announcement": "Annonce",
|
"announcement.announcement": "Annonce",
|
||||||
|
"annual_report.summary.archetype.lurker": "Le faucheur",
|
||||||
"annual_report.summary.archetype.oracle": "L’oracle",
|
"annual_report.summary.archetype.oracle": "L’oracle",
|
||||||
|
"annual_report.summary.followers.followers": "abonné·e·s",
|
||||||
|
"annual_report.summary.followers.total": "{count} au total",
|
||||||
"annual_report.summary.here_it_is": "Voici votre récap de {year} :",
|
"annual_report.summary.here_it_is": "Voici votre récap de {year} :",
|
||||||
|
"annual_report.summary.highlighted_post.by_favourites": "post le plus aimé",
|
||||||
|
"annual_report.summary.highlighted_post.by_reblogs": "post le plus boosté",
|
||||||
|
"annual_report.summary.highlighted_post.by_replies": "post avec le plus de réponses",
|
||||||
"annual_report.summary.most_used_app.most_used_app": "appli la plus utilisée",
|
"annual_report.summary.most_used_app.most_used_app": "appli la plus utilisée",
|
||||||
|
"annual_report.summary.most_used_hashtag.most_used_hashtag": "hashtag le plus utilisé",
|
||||||
|
"annual_report.summary.most_used_hashtag.none": "Aucun",
|
||||||
|
"annual_report.summary.new_posts.new_posts": "nouveaux posts",
|
||||||
|
"annual_report.summary.percentile.text": "<topLabel>Cela vous place dans le top</topLabel><percentage></percentage><bottomLabel>des utilisateurs de Mastodon.</bottomLabel>",
|
||||||
|
"annual_report.summary.percentile.we_wont_tell_bernie": "Nous ne le dirons pas à Bernie.",
|
||||||
|
"annual_report.summary.thanks": "Merci de faire partie de Mastodon!",
|
||||||
"attachments_list.unprocessed": "(non traité)",
|
"attachments_list.unprocessed": "(non traité)",
|
||||||
"audio.hide": "Masquer l'audio",
|
"audio.hide": "Masquer l'audio",
|
||||||
"block_modal.remote_users_caveat": "Nous allons demander au serveur {domain} de respecter votre décision. Cependant, ce respect n'est pas garanti, car certains serveurs peuvent gérer différemment les blocages. Les messages publics peuvent rester visibles par les utilisateur·rice·s non connecté·e·s.",
|
"block_modal.remote_users_caveat": "Nous allons demander au serveur {domain} de respecter votre décision. Cependant, ce respect n'est pas garanti, car certains serveurs peuvent gérer différemment les blocages. Les messages publics peuvent rester visibles par les utilisateur·rice·s non connecté·e·s.",
|
||||||
@ -124,13 +136,16 @@
|
|||||||
"column.blocks": "Comptes bloqués",
|
"column.blocks": "Comptes bloqués",
|
||||||
"column.bookmarks": "Signets",
|
"column.bookmarks": "Signets",
|
||||||
"column.community": "Fil local",
|
"column.community": "Fil local",
|
||||||
|
"column.create_list": "Créer une liste",
|
||||||
"column.direct": "Mention privée",
|
"column.direct": "Mention privée",
|
||||||
"column.directory": "Parcourir les profils",
|
"column.directory": "Parcourir les profils",
|
||||||
"column.domain_blocks": "Domaines bloqués",
|
"column.domain_blocks": "Domaines bloqués",
|
||||||
|
"column.edit_list": "Modifier la liste",
|
||||||
"column.favourites": "Favoris",
|
"column.favourites": "Favoris",
|
||||||
"column.firehose": "Flux en direct",
|
"column.firehose": "Flux en direct",
|
||||||
"column.follow_requests": "Demande d'abonnement",
|
"column.follow_requests": "Demande d'abonnement",
|
||||||
"column.home": "Accueil",
|
"column.home": "Accueil",
|
||||||
|
"column.list_members": "Gérer les membres de la liste",
|
||||||
"column.lists": "Listes",
|
"column.lists": "Listes",
|
||||||
"column.mutes": "Comptes masqués",
|
"column.mutes": "Comptes masqués",
|
||||||
"column.notifications": "Notifications",
|
"column.notifications": "Notifications",
|
||||||
@ -200,6 +215,7 @@
|
|||||||
"confirmations.unfollow.title": "Se désabonner de l'utilisateur·rice ?",
|
"confirmations.unfollow.title": "Se désabonner de l'utilisateur·rice ?",
|
||||||
"content_warning.hide": "Masquer le message",
|
"content_warning.hide": "Masquer le message",
|
||||||
"content_warning.show": "Afficher quand même",
|
"content_warning.show": "Afficher quand même",
|
||||||
|
"content_warning.show_more": "Déplier",
|
||||||
"conversation.delete": "Supprimer cette conversation",
|
"conversation.delete": "Supprimer cette conversation",
|
||||||
"conversation.mark_as_read": "Marquer comme lu",
|
"conversation.mark_as_read": "Marquer comme lu",
|
||||||
"conversation.open": "Afficher cette conversation",
|
"conversation.open": "Afficher cette conversation",
|
||||||
@ -275,7 +291,6 @@
|
|||||||
"empty_column.hashtag": "Il n’y a pas encore de contenu associé à ce hashtag.",
|
"empty_column.hashtag": "Il n’y a pas encore de contenu associé à ce hashtag.",
|
||||||
"empty_column.home": "Votre fil d'accueil est vide! Suivez plus de personnes pour la remplir. {suggestions}",
|
"empty_column.home": "Votre fil d'accueil est vide! Suivez plus de personnes pour la remplir. {suggestions}",
|
||||||
"empty_column.list": "Il n’y a rien dans cette liste pour l’instant. Quand des membres de cette liste publieront de nouvelles publications, elles apparaîtront ici.",
|
"empty_column.list": "Il n’y a rien dans cette liste pour l’instant. Quand des membres de cette liste publieront de nouvelles publications, elles apparaîtront ici.",
|
||||||
"empty_column.lists": "Vous n’avez pas encore de liste. Lorsque vous en créerez une, elle apparaîtra ici.",
|
|
||||||
"empty_column.mutes": "Vous n’avez masqué aucun compte pour le moment.",
|
"empty_column.mutes": "Vous n’avez masqué aucun compte pour le moment.",
|
||||||
"empty_column.notification_requests": "C'est fini ! Il n'y a plus rien ici. Lorsque vous recevez de nouvelles notifications, elles apparaitront ici conformément à vos préférences.",
|
"empty_column.notification_requests": "C'est fini ! Il n'y a plus rien ici. Lorsque vous recevez de nouvelles notifications, elles apparaitront ici conformément à vos préférences.",
|
||||||
"empty_column.notifications": "Vous n'avez pas encore de notifications. Quand d'autres personnes interagissent avec vous, vous en verrez ici.",
|
"empty_column.notifications": "Vous n'avez pas encore de notifications. Quand d'autres personnes interagissent avec vous, vous en verrez ici.",
|
||||||
@ -308,6 +323,7 @@
|
|||||||
"filter_modal.select_filter.subtitle": "Utilisez une catégorie existante ou en créer une nouvelle",
|
"filter_modal.select_filter.subtitle": "Utilisez une catégorie existante ou en créer une nouvelle",
|
||||||
"filter_modal.select_filter.title": "Filtrer cette publication",
|
"filter_modal.select_filter.title": "Filtrer cette publication",
|
||||||
"filter_modal.title.status": "Filtrer une publication",
|
"filter_modal.title.status": "Filtrer une publication",
|
||||||
|
"filter_warning.matches_filter": "Correspond au filtre « <span>{title}</span> »",
|
||||||
"filtered_notifications_banner.pending_requests": "De la part {count, plural, =0 {d’aucune personne} one {d'une personne} other {de # personnes}} que vous pourriez connaître",
|
"filtered_notifications_banner.pending_requests": "De la part {count, plural, =0 {d’aucune personne} one {d'une personne} other {de # personnes}} que vous pourriez connaître",
|
||||||
"filtered_notifications_banner.title": "Notifications filtrées",
|
"filtered_notifications_banner.title": "Notifications filtrées",
|
||||||
"firehose.all": "Tout",
|
"firehose.all": "Tout",
|
||||||
@ -387,6 +403,7 @@
|
|||||||
"interaction_modal.description.follow": "Avec un compte Mastodon, vous pouvez suivre {name} et recevoir leurs publications dans votre fil d'accueil.",
|
"interaction_modal.description.follow": "Avec un compte Mastodon, vous pouvez suivre {name} et recevoir leurs publications dans votre fil d'accueil.",
|
||||||
"interaction_modal.description.reblog": "Avec un compte Mastodon, vous pouvez booster cette publication pour la partager avec vos propres abonné·e·s.",
|
"interaction_modal.description.reblog": "Avec un compte Mastodon, vous pouvez booster cette publication pour la partager avec vos propres abonné·e·s.",
|
||||||
"interaction_modal.description.reply": "Avec un compte sur Mastodon, vous pouvez répondre à cette publication.",
|
"interaction_modal.description.reply": "Avec un compte sur Mastodon, vous pouvez répondre à cette publication.",
|
||||||
|
"interaction_modal.description.vote": "Avec un compte sur Mastodon, vous pouvez répondre à cette question.",
|
||||||
"interaction_modal.login.action": "Aller à mon serveur",
|
"interaction_modal.login.action": "Aller à mon serveur",
|
||||||
"interaction_modal.login.prompt": "Domaine de votre serveur, ex. mastodon.social",
|
"interaction_modal.login.prompt": "Domaine de votre serveur, ex. mastodon.social",
|
||||||
"interaction_modal.no_account_yet": "Pas sur Mastodon ?",
|
"interaction_modal.no_account_yet": "Pas sur Mastodon ?",
|
||||||
@ -398,6 +415,7 @@
|
|||||||
"interaction_modal.title.follow": "Suivre {name}",
|
"interaction_modal.title.follow": "Suivre {name}",
|
||||||
"interaction_modal.title.reblog": "Booster la publication de {name}",
|
"interaction_modal.title.reblog": "Booster la publication de {name}",
|
||||||
"interaction_modal.title.reply": "Répondre à la publication de {name}",
|
"interaction_modal.title.reply": "Répondre à la publication de {name}",
|
||||||
|
"interaction_modal.title.vote": "Voter pour le sondage de {name}",
|
||||||
"intervals.full.days": "{number, plural, one {# jour} other {# jours}}",
|
"intervals.full.days": "{number, plural, one {# jour} other {# jours}}",
|
||||||
"intervals.full.hours": "{number, plural, one {# heure} other {# heures}}",
|
"intervals.full.hours": "{number, plural, one {# heure} other {# heures}}",
|
||||||
"intervals.full.minutes": "{number, plural, one {# minute} other {# minutes}}",
|
"intervals.full.minutes": "{number, plural, one {# minute} other {# minutes}}",
|
||||||
@ -445,20 +463,30 @@
|
|||||||
"link_preview.author": "Par {name}",
|
"link_preview.author": "Par {name}",
|
||||||
"link_preview.more_from_author": "Plus via {name}",
|
"link_preview.more_from_author": "Plus via {name}",
|
||||||
"link_preview.shares": "{count, plural, one {{counter} message} other {{counter} messages}}",
|
"link_preview.shares": "{count, plural, one {{counter} message} other {{counter} messages}}",
|
||||||
"lists.account.add": "Ajouter à une liste",
|
"lists.add_member": "Ajouter",
|
||||||
"lists.account.remove": "Retirer d'une liste",
|
"lists.add_to_list": "Ajouter à la liste",
|
||||||
|
"lists.add_to_lists": "Ajouter {name} aux listes",
|
||||||
|
"lists.create": "Créer",
|
||||||
|
"lists.create_a_list_to_organize": "Créer une nouvelle liste pour organiser votre Page d'accueil",
|
||||||
|
"lists.create_list": "Créer une liste",
|
||||||
"lists.delete": "Supprimer la liste",
|
"lists.delete": "Supprimer la liste",
|
||||||
|
"lists.done": "Terminé",
|
||||||
"lists.edit": "Modifier la liste",
|
"lists.edit": "Modifier la liste",
|
||||||
"lists.edit.submit": "Modifier le titre",
|
"lists.exclusive": "Cacher les membres de la page d'accueil",
|
||||||
"lists.exclusive": "Cacher ces publications depuis la page d'accueil",
|
"lists.exclusive_hint": "Si quelqu'un est dans cette liste, les cacher dans votre fil pour éviter de voir leurs messages deux fois.",
|
||||||
"lists.new.create": "Ajouter une liste",
|
"lists.find_users_to_add": "Trouver des utilisateurs à ajouter",
|
||||||
"lists.new.title_placeholder": "Titre de la nouvelle liste",
|
"lists.list_members": "Lister les membres",
|
||||||
|
"lists.list_name": "Nom de la liste",
|
||||||
|
"lists.new_list_name": "Nom de la nouvelle liste",
|
||||||
|
"lists.no_lists_yet": "Aucune liste pour l'instant.",
|
||||||
|
"lists.no_members_yet": "Aucun membre pour l'instant.",
|
||||||
|
"lists.no_results_found": "Aucun résultat.",
|
||||||
|
"lists.remove_member": "Supprimer",
|
||||||
"lists.replies_policy.followed": "N'importe quel compte suivi",
|
"lists.replies_policy.followed": "N'importe quel compte suivi",
|
||||||
"lists.replies_policy.list": "Membres de la liste",
|
"lists.replies_policy.list": "Membres de la liste",
|
||||||
"lists.replies_policy.none": "Personne",
|
"lists.replies_policy.none": "Personne",
|
||||||
"lists.replies_policy.title": "Afficher les réponses à:",
|
"lists.save": "Enregistrer",
|
||||||
"lists.search": "Rechercher parmi les gens que vous suivez",
|
"lists.search_placeholder": "Rechercher parmi les gens que vous suivez",
|
||||||
"lists.subheading": "Vos listes",
|
|
||||||
"load_pending": "{count, plural, one {# nouvel élément} other {# nouveaux éléments}}",
|
"load_pending": "{count, plural, one {# nouvel élément} other {# nouveaux éléments}}",
|
||||||
"loading_indicator.label": "Chargement…",
|
"loading_indicator.label": "Chargement…",
|
||||||
"media_gallery.hide": "Masquer",
|
"media_gallery.hide": "Masquer",
|
||||||
@ -507,6 +535,7 @@
|
|||||||
"notification.admin.report_statuses_other": "{name} a signalé {target}",
|
"notification.admin.report_statuses_other": "{name} a signalé {target}",
|
||||||
"notification.admin.sign_up": "{name} s'est inscrit·e",
|
"notification.admin.sign_up": "{name} s'est inscrit·e",
|
||||||
"notification.admin.sign_up.name_and_others": "{name} et {count, plural, one {# autre} other {# autres}} se sont inscrit",
|
"notification.admin.sign_up.name_and_others": "{name} et {count, plural, one {# autre} other {# autres}} se sont inscrit",
|
||||||
|
"notification.annual_report.view": "Voir #Wrapstodon",
|
||||||
"notification.favourite": "{name} a ajouté votre publication à ses favoris",
|
"notification.favourite": "{name} a ajouté votre publication à ses favoris",
|
||||||
"notification.favourite.name_and_others_with_link": "{name} et <a>{count, plural, one {# autre} other {# autres}}</a> ont mis votre message en favori",
|
"notification.favourite.name_and_others_with_link": "{name} et <a>{count, plural, one {# autre} other {# autres}}</a> ont mis votre message en favori",
|
||||||
"notification.follow": "{name} vous suit",
|
"notification.follow": "{name} vous suit",
|
||||||
@ -858,6 +887,7 @@
|
|||||||
"upload_form.description": "Décrire pour les malvoyants",
|
"upload_form.description": "Décrire pour les malvoyants",
|
||||||
"upload_form.drag_and_drop.instructions": "Pour choisir un média joint, appuyez sur la touche espace ou entrée. Tout en faisant glisser, utilisez les touches fléchées pour déplacer le fichier média dans une direction donnée. Appuyez à nouveau sur la touche espace ou entrée pour déposer le fichier média dans sa nouvelle position, ou appuyez sur la touche Echap pour annuler.",
|
"upload_form.drag_and_drop.instructions": "Pour choisir un média joint, appuyez sur la touche espace ou entrée. Tout en faisant glisser, utilisez les touches fléchées pour déplacer le fichier média dans une direction donnée. Appuyez à nouveau sur la touche espace ou entrée pour déposer le fichier média dans sa nouvelle position, ou appuyez sur la touche Echap pour annuler.",
|
||||||
"upload_form.drag_and_drop.on_drag_cancel": "Le glissement a été annulé. La pièce jointe {item} n'a pas été ajoutée.",
|
"upload_form.drag_and_drop.on_drag_cancel": "Le glissement a été annulé. La pièce jointe {item} n'a pas été ajoutée.",
|
||||||
|
"upload_form.drag_and_drop.on_drag_end": "La pièce jointe du média {item} a été déplacée.",
|
||||||
"upload_form.drag_and_drop.on_drag_over": "La pièce jointe du média {item} a été déplacée.",
|
"upload_form.drag_and_drop.on_drag_over": "La pièce jointe du média {item} a été déplacée.",
|
||||||
"upload_form.edit": "Modifier",
|
"upload_form.edit": "Modifier",
|
||||||
"upload_form.thumbnail": "Changer la vignette",
|
"upload_form.thumbnail": "Changer la vignette",
|
||||||
|
@ -87,9 +87,21 @@
|
|||||||
"alert.unexpected.title": "Oups !",
|
"alert.unexpected.title": "Oups !",
|
||||||
"alt_text_badge.title": "Texte Alt",
|
"alt_text_badge.title": "Texte Alt",
|
||||||
"announcement.announcement": "Annonce",
|
"announcement.announcement": "Annonce",
|
||||||
|
"annual_report.summary.archetype.lurker": "Le faucheur",
|
||||||
"annual_report.summary.archetype.oracle": "L’oracle",
|
"annual_report.summary.archetype.oracle": "L’oracle",
|
||||||
|
"annual_report.summary.followers.followers": "abonné·e·s",
|
||||||
|
"annual_report.summary.followers.total": "{count} au total",
|
||||||
"annual_report.summary.here_it_is": "Voici votre récap de {year} :",
|
"annual_report.summary.here_it_is": "Voici votre récap de {year} :",
|
||||||
|
"annual_report.summary.highlighted_post.by_favourites": "post le plus aimé",
|
||||||
|
"annual_report.summary.highlighted_post.by_reblogs": "post le plus boosté",
|
||||||
|
"annual_report.summary.highlighted_post.by_replies": "post avec le plus de réponses",
|
||||||
"annual_report.summary.most_used_app.most_used_app": "appli la plus utilisée",
|
"annual_report.summary.most_used_app.most_used_app": "appli la plus utilisée",
|
||||||
|
"annual_report.summary.most_used_hashtag.most_used_hashtag": "hashtag le plus utilisé",
|
||||||
|
"annual_report.summary.most_used_hashtag.none": "Aucun",
|
||||||
|
"annual_report.summary.new_posts.new_posts": "nouveaux posts",
|
||||||
|
"annual_report.summary.percentile.text": "<topLabel>Cela vous place dans le top</topLabel><percentage></percentage><bottomLabel>des utilisateurs de Mastodon.</bottomLabel>",
|
||||||
|
"annual_report.summary.percentile.we_wont_tell_bernie": "Nous ne le dirons pas à Bernie.",
|
||||||
|
"annual_report.summary.thanks": "Merci de faire partie de Mastodon!",
|
||||||
"attachments_list.unprocessed": "(non traité)",
|
"attachments_list.unprocessed": "(non traité)",
|
||||||
"audio.hide": "Masquer l'audio",
|
"audio.hide": "Masquer l'audio",
|
||||||
"block_modal.remote_users_caveat": "Nous allons demander au serveur {domain} de respecter votre décision. Cependant, ce respect n'est pas garanti, car certains serveurs peuvent gérer différemment les blocages. Les messages publics peuvent rester visibles par les utilisateur·rice·s non connecté·e·s.",
|
"block_modal.remote_users_caveat": "Nous allons demander au serveur {domain} de respecter votre décision. Cependant, ce respect n'est pas garanti, car certains serveurs peuvent gérer différemment les blocages. Les messages publics peuvent rester visibles par les utilisateur·rice·s non connecté·e·s.",
|
||||||
@ -124,13 +136,16 @@
|
|||||||
"column.blocks": "Utilisateurs bloqués",
|
"column.blocks": "Utilisateurs bloqués",
|
||||||
"column.bookmarks": "Marque-pages",
|
"column.bookmarks": "Marque-pages",
|
||||||
"column.community": "Fil public local",
|
"column.community": "Fil public local",
|
||||||
|
"column.create_list": "Créer une liste",
|
||||||
"column.direct": "Mentions privées",
|
"column.direct": "Mentions privées",
|
||||||
"column.directory": "Parcourir les profils",
|
"column.directory": "Parcourir les profils",
|
||||||
"column.domain_blocks": "Domaines bloqués",
|
"column.domain_blocks": "Domaines bloqués",
|
||||||
|
"column.edit_list": "Modifier la liste",
|
||||||
"column.favourites": "Favoris",
|
"column.favourites": "Favoris",
|
||||||
"column.firehose": "Flux en direct",
|
"column.firehose": "Flux en direct",
|
||||||
"column.follow_requests": "Demandes d'abonnement",
|
"column.follow_requests": "Demandes d'abonnement",
|
||||||
"column.home": "Accueil",
|
"column.home": "Accueil",
|
||||||
|
"column.list_members": "Gérer les membres de la liste",
|
||||||
"column.lists": "Listes",
|
"column.lists": "Listes",
|
||||||
"column.mutes": "Comptes masqués",
|
"column.mutes": "Comptes masqués",
|
||||||
"column.notifications": "Notifications",
|
"column.notifications": "Notifications",
|
||||||
@ -200,6 +215,7 @@
|
|||||||
"confirmations.unfollow.title": "Se désabonner de l'utilisateur·rice ?",
|
"confirmations.unfollow.title": "Se désabonner de l'utilisateur·rice ?",
|
||||||
"content_warning.hide": "Masquer le message",
|
"content_warning.hide": "Masquer le message",
|
||||||
"content_warning.show": "Afficher quand même",
|
"content_warning.show": "Afficher quand même",
|
||||||
|
"content_warning.show_more": "Déplier",
|
||||||
"conversation.delete": "Supprimer la conversation",
|
"conversation.delete": "Supprimer la conversation",
|
||||||
"conversation.mark_as_read": "Marquer comme lu",
|
"conversation.mark_as_read": "Marquer comme lu",
|
||||||
"conversation.open": "Afficher la conversation",
|
"conversation.open": "Afficher la conversation",
|
||||||
@ -275,7 +291,6 @@
|
|||||||
"empty_column.hashtag": "Il n’y a encore aucun contenu associé à ce hashtag.",
|
"empty_column.hashtag": "Il n’y a encore aucun contenu associé à ce hashtag.",
|
||||||
"empty_column.home": "Votre fil principal est vide ! Suivez plus de personnes pour le remplir.",
|
"empty_column.home": "Votre fil principal est vide ! Suivez plus de personnes pour le remplir.",
|
||||||
"empty_column.list": "Il n’y a rien dans cette liste pour l’instant. Quand des membres de cette liste publieront de nouveaux messages, ils apparaîtront ici.",
|
"empty_column.list": "Il n’y a rien dans cette liste pour l’instant. Quand des membres de cette liste publieront de nouveaux messages, ils apparaîtront ici.",
|
||||||
"empty_column.lists": "Vous n’avez pas encore de liste. Lorsque vous en créerez une, elle apparaîtra ici.",
|
|
||||||
"empty_column.mutes": "Vous n’avez masqué aucun compte pour le moment.",
|
"empty_column.mutes": "Vous n’avez masqué aucun compte pour le moment.",
|
||||||
"empty_column.notification_requests": "C'est fini ! Il n'y a plus rien ici. Lorsque vous recevez de nouvelles notifications, elles apparaitront ici conformément à vos préférences.",
|
"empty_column.notification_requests": "C'est fini ! Il n'y a plus rien ici. Lorsque vous recevez de nouvelles notifications, elles apparaitront ici conformément à vos préférences.",
|
||||||
"empty_column.notifications": "Vous n’avez pas encore de notification. Interagissez avec d’autres personnes pour débuter la conversation.",
|
"empty_column.notifications": "Vous n’avez pas encore de notification. Interagissez avec d’autres personnes pour débuter la conversation.",
|
||||||
@ -308,6 +323,7 @@
|
|||||||
"filter_modal.select_filter.subtitle": "Utilisez une catégorie existante ou créez-en une nouvelle",
|
"filter_modal.select_filter.subtitle": "Utilisez une catégorie existante ou créez-en une nouvelle",
|
||||||
"filter_modal.select_filter.title": "Filtrer ce message",
|
"filter_modal.select_filter.title": "Filtrer ce message",
|
||||||
"filter_modal.title.status": "Filtrer un message",
|
"filter_modal.title.status": "Filtrer un message",
|
||||||
|
"filter_warning.matches_filter": "Correspond au filtre « <span>{title}</span> »",
|
||||||
"filtered_notifications_banner.pending_requests": "De la part {count, plural, =0 {d’aucune personne} one {d'une personne} other {de # personnes}} que vous pourriez connaître",
|
"filtered_notifications_banner.pending_requests": "De la part {count, plural, =0 {d’aucune personne} one {d'une personne} other {de # personnes}} que vous pourriez connaître",
|
||||||
"filtered_notifications_banner.title": "Notifications filtrées",
|
"filtered_notifications_banner.title": "Notifications filtrées",
|
||||||
"firehose.all": "Tout",
|
"firehose.all": "Tout",
|
||||||
@ -387,6 +403,7 @@
|
|||||||
"interaction_modal.description.follow": "Avec un compte Mastodon, vous pouvez suivre {name} et recevoir leurs posts dans votre fil d'actualité.",
|
"interaction_modal.description.follow": "Avec un compte Mastodon, vous pouvez suivre {name} et recevoir leurs posts dans votre fil d'actualité.",
|
||||||
"interaction_modal.description.reblog": "Avec un compte sur Mastodon, vous pouvez partager ce message pour le faire découvrir à vos propres abonné⋅e⋅s.",
|
"interaction_modal.description.reblog": "Avec un compte sur Mastodon, vous pouvez partager ce message pour le faire découvrir à vos propres abonné⋅e⋅s.",
|
||||||
"interaction_modal.description.reply": "Avec un compte sur Mastodon, vous pouvez répondre à ce message.",
|
"interaction_modal.description.reply": "Avec un compte sur Mastodon, vous pouvez répondre à ce message.",
|
||||||
|
"interaction_modal.description.vote": "Avec un compte sur Mastodon, vous pouvez répondre à cette question.",
|
||||||
"interaction_modal.login.action": "Aller à mon serveur",
|
"interaction_modal.login.action": "Aller à mon serveur",
|
||||||
"interaction_modal.login.prompt": "Domaine de votre serveur, ex. mastodon.social",
|
"interaction_modal.login.prompt": "Domaine de votre serveur, ex. mastodon.social",
|
||||||
"interaction_modal.no_account_yet": "Pas sur Mastodon ?",
|
"interaction_modal.no_account_yet": "Pas sur Mastodon ?",
|
||||||
@ -398,6 +415,7 @@
|
|||||||
"interaction_modal.title.follow": "Suivre {name}",
|
"interaction_modal.title.follow": "Suivre {name}",
|
||||||
"interaction_modal.title.reblog": "Partager le message de {name}",
|
"interaction_modal.title.reblog": "Partager le message de {name}",
|
||||||
"interaction_modal.title.reply": "Répondre au message de {name}",
|
"interaction_modal.title.reply": "Répondre au message de {name}",
|
||||||
|
"interaction_modal.title.vote": "Voter pour le sondage de {name}",
|
||||||
"intervals.full.days": "{number, plural, one {# jour} other {# jours}}",
|
"intervals.full.days": "{number, plural, one {# jour} other {# jours}}",
|
||||||
"intervals.full.hours": "{number, plural, one {# heure} other {# heures}}",
|
"intervals.full.hours": "{number, plural, one {# heure} other {# heures}}",
|
||||||
"intervals.full.minutes": "{number, plural, one {# minute} other {# minutes}}",
|
"intervals.full.minutes": "{number, plural, one {# minute} other {# minutes}}",
|
||||||
@ -445,20 +463,30 @@
|
|||||||
"link_preview.author": "Par {name}",
|
"link_preview.author": "Par {name}",
|
||||||
"link_preview.more_from_author": "Plus via {name}",
|
"link_preview.more_from_author": "Plus via {name}",
|
||||||
"link_preview.shares": "{count, plural, one {{counter} message} other {{counter} messages}}",
|
"link_preview.shares": "{count, plural, one {{counter} message} other {{counter} messages}}",
|
||||||
"lists.account.add": "Ajouter à la liste",
|
"lists.add_member": "Ajouter",
|
||||||
"lists.account.remove": "Supprimer de la liste",
|
"lists.add_to_list": "Ajouter à la liste",
|
||||||
|
"lists.add_to_lists": "Ajouter {name} aux listes",
|
||||||
|
"lists.create": "Créer",
|
||||||
|
"lists.create_a_list_to_organize": "Créer une nouvelle liste pour organiser votre Page d'accueil",
|
||||||
|
"lists.create_list": "Créer une liste",
|
||||||
"lists.delete": "Supprimer la liste",
|
"lists.delete": "Supprimer la liste",
|
||||||
|
"lists.done": "Terminé",
|
||||||
"lists.edit": "Modifier la liste",
|
"lists.edit": "Modifier la liste",
|
||||||
"lists.edit.submit": "Modifier le titre",
|
"lists.exclusive": "Cacher les membres de la page d'accueil",
|
||||||
"lists.exclusive": "Cacher ces publications sur le fil principal",
|
"lists.exclusive_hint": "Si quelqu'un est dans cette liste, les cacher dans votre fil pour éviter de voir leurs messages deux fois.",
|
||||||
"lists.new.create": "Ajouter une liste",
|
"lists.find_users_to_add": "Trouver des utilisateurs à ajouter",
|
||||||
"lists.new.title_placeholder": "Titre de la nouvelle liste",
|
"lists.list_members": "Lister les membres",
|
||||||
|
"lists.list_name": "Nom de la liste",
|
||||||
|
"lists.new_list_name": "Nom de la nouvelle liste",
|
||||||
|
"lists.no_lists_yet": "Aucune liste pour l'instant.",
|
||||||
|
"lists.no_members_yet": "Aucun membre pour l'instant.",
|
||||||
|
"lists.no_results_found": "Aucun résultat.",
|
||||||
|
"lists.remove_member": "Supprimer",
|
||||||
"lists.replies_policy.followed": "N'importe quel compte suivi",
|
"lists.replies_policy.followed": "N'importe quel compte suivi",
|
||||||
"lists.replies_policy.list": "Membres de la liste",
|
"lists.replies_policy.list": "Membres de la liste",
|
||||||
"lists.replies_policy.none": "Personne",
|
"lists.replies_policy.none": "Personne",
|
||||||
"lists.replies_policy.title": "Afficher les réponses à :",
|
"lists.save": "Enregistrer",
|
||||||
"lists.search": "Rechercher parmi les gens que vous suivez",
|
"lists.search_placeholder": "Rechercher parmi les gens que vous suivez",
|
||||||
"lists.subheading": "Vos listes",
|
|
||||||
"load_pending": "{count, plural, one {# nouvel élément} other {# nouveaux éléments}}",
|
"load_pending": "{count, plural, one {# nouvel élément} other {# nouveaux éléments}}",
|
||||||
"loading_indicator.label": "Chargement…",
|
"loading_indicator.label": "Chargement…",
|
||||||
"media_gallery.hide": "Masquer",
|
"media_gallery.hide": "Masquer",
|
||||||
@ -507,6 +535,7 @@
|
|||||||
"notification.admin.report_statuses_other": "{name} a signalé {target}",
|
"notification.admin.report_statuses_other": "{name} a signalé {target}",
|
||||||
"notification.admin.sign_up": "{name} s'est inscrit",
|
"notification.admin.sign_up": "{name} s'est inscrit",
|
||||||
"notification.admin.sign_up.name_and_others": "{name} et {count, plural, one {# autre} other {# autres}} se sont inscrit",
|
"notification.admin.sign_up.name_and_others": "{name} et {count, plural, one {# autre} other {# autres}} se sont inscrit",
|
||||||
|
"notification.annual_report.view": "Voir #Wrapstodon",
|
||||||
"notification.favourite": "{name} a ajouté votre message à ses favoris",
|
"notification.favourite": "{name} a ajouté votre message à ses favoris",
|
||||||
"notification.favourite.name_and_others_with_link": "{name} et <a>{count, plural, one {# autre} other {# autres}}</a> ont mis votre message en favori",
|
"notification.favourite.name_and_others_with_link": "{name} et <a>{count, plural, one {# autre} other {# autres}}</a> ont mis votre message en favori",
|
||||||
"notification.follow": "{name} vous suit",
|
"notification.follow": "{name} vous suit",
|
||||||
@ -858,6 +887,7 @@
|
|||||||
"upload_form.description": "Décrire pour les malvoyant·e·s",
|
"upload_form.description": "Décrire pour les malvoyant·e·s",
|
||||||
"upload_form.drag_and_drop.instructions": "Pour choisir un média joint, appuyez sur la touche espace ou entrée. Tout en faisant glisser, utilisez les touches fléchées pour déplacer le fichier média dans une direction donnée. Appuyez à nouveau sur la touche espace ou entrée pour déposer le fichier média dans sa nouvelle position, ou appuyez sur la touche Echap pour annuler.",
|
"upload_form.drag_and_drop.instructions": "Pour choisir un média joint, appuyez sur la touche espace ou entrée. Tout en faisant glisser, utilisez les touches fléchées pour déplacer le fichier média dans une direction donnée. Appuyez à nouveau sur la touche espace ou entrée pour déposer le fichier média dans sa nouvelle position, ou appuyez sur la touche Echap pour annuler.",
|
||||||
"upload_form.drag_and_drop.on_drag_cancel": "Le glissement a été annulé. La pièce jointe {item} n'a pas été ajoutée.",
|
"upload_form.drag_and_drop.on_drag_cancel": "Le glissement a été annulé. La pièce jointe {item} n'a pas été ajoutée.",
|
||||||
|
"upload_form.drag_and_drop.on_drag_end": "La pièce jointe du média {item} a été déplacée.",
|
||||||
"upload_form.drag_and_drop.on_drag_over": "La pièce jointe du média {item} a été déplacée.",
|
"upload_form.drag_and_drop.on_drag_over": "La pièce jointe du média {item} a été déplacée.",
|
||||||
"upload_form.edit": "Modifier",
|
"upload_form.edit": "Modifier",
|
||||||
"upload_form.thumbnail": "Changer la vignette",
|
"upload_form.thumbnail": "Changer la vignette",
|
||||||
|
@ -273,7 +273,6 @@
|
|||||||
"empty_column.hashtag": "Der is noch neat te finen ûnder dizze hashtag.",
|
"empty_column.hashtag": "Der is noch neat te finen ûnder dizze hashtag.",
|
||||||
"empty_column.home": "Dizze tiidline is leech! Folgje mear minsken om it te foljen. {suggestions}",
|
"empty_column.home": "Dizze tiidline is leech! Folgje mear minsken om it te foljen. {suggestions}",
|
||||||
"empty_column.list": "There is nothing in this list yet. When members of this list post new statuses, they will appear here.",
|
"empty_column.list": "There is nothing in this list yet. When members of this list post new statuses, they will appear here.",
|
||||||
"empty_column.lists": "Jo hawwe noch gjin inkelde list. Wannear’t jo der ien oanmakke hawwe, falt dat hjir te sjen.",
|
|
||||||
"empty_column.mutes": "Jo hawwe noch gjin brûkers negearre.",
|
"empty_column.mutes": "Jo hawwe noch gjin brûkers negearre.",
|
||||||
"empty_column.notification_requests": "Hielendal leech! Der is hjir neat. Wannear’t jo nije meldingen ûntfange, ferskine dizze hjir neffens jo ynstellingen.",
|
"empty_column.notification_requests": "Hielendal leech! Der is hjir neat. Wannear’t jo nije meldingen ûntfange, ferskine dizze hjir neffens jo ynstellingen.",
|
||||||
"empty_column.notifications": "Jo hawwe noch gjin meldingen. Ynteraksjes mei oare minsken sjogge jo hjir.",
|
"empty_column.notifications": "Jo hawwe noch gjin meldingen. Ynteraksjes mei oare minsken sjogge jo hjir.",
|
||||||
@ -446,20 +445,11 @@
|
|||||||
"link_preview.author": "Troch {name}",
|
"link_preview.author": "Troch {name}",
|
||||||
"link_preview.more_from_author": "Mear fan {name}",
|
"link_preview.more_from_author": "Mear fan {name}",
|
||||||
"link_preview.shares": "{count, plural, one {{counter} berjocht} other {{counter} berjochten}}",
|
"link_preview.shares": "{count, plural, one {{counter} berjocht} other {{counter} berjochten}}",
|
||||||
"lists.account.add": "Oan list tafoegje",
|
|
||||||
"lists.account.remove": "Ut list fuortsmite",
|
|
||||||
"lists.delete": "List fuortsmite",
|
"lists.delete": "List fuortsmite",
|
||||||
"lists.edit": "List bewurkje",
|
"lists.edit": "List bewurkje",
|
||||||
"lists.edit.submit": "Titel wizigje",
|
|
||||||
"lists.exclusive": "Ferstopje dizze berjochten op jo startside",
|
|
||||||
"lists.new.create": "List tafoegje",
|
|
||||||
"lists.new.title_placeholder": "Nije listtitel",
|
|
||||||
"lists.replies_policy.followed": "Elke folge brûker",
|
"lists.replies_policy.followed": "Elke folge brûker",
|
||||||
"lists.replies_policy.list": "Leden fan de list",
|
"lists.replies_policy.list": "Leden fan de list",
|
||||||
"lists.replies_policy.none": "Net ien",
|
"lists.replies_policy.none": "Net ien",
|
||||||
"lists.replies_policy.title": "Reaksjes toane oan:",
|
|
||||||
"lists.search": "Sykje nei minsken dy’t jo folgje",
|
|
||||||
"lists.subheading": "Jo listen",
|
|
||||||
"load_pending": "{count, plural, one {# nij item} other {# nije items}}",
|
"load_pending": "{count, plural, one {# nij item} other {# nije items}}",
|
||||||
"loading_indicator.label": "Lade…",
|
"loading_indicator.label": "Lade…",
|
||||||
"media_gallery.hide": "Ferstopje",
|
"media_gallery.hide": "Ferstopje",
|
||||||
|
@ -292,7 +292,6 @@
|
|||||||
"empty_column.hashtag": "Níl rud ar bith faoin haischlib seo go fóill.",
|
"empty_column.hashtag": "Níl rud ar bith faoin haischlib seo go fóill.",
|
||||||
"empty_column.home": "Tá d'amlíne baile folamh! B'fhiú duit cúpla duine eile a leanúint lena líonadh! {suggestions}",
|
"empty_column.home": "Tá d'amlíne baile folamh! B'fhiú duit cúpla duine eile a leanúint lena líonadh! {suggestions}",
|
||||||
"empty_column.list": "Níl aon rud ar an liosta seo fós. Nuair a fhoilseoidh baill an liosta seo postálacha nua, beidh siad le feiceáil anseo.",
|
"empty_column.list": "Níl aon rud ar an liosta seo fós. Nuair a fhoilseoidh baill an liosta seo postálacha nua, beidh siad le feiceáil anseo.",
|
||||||
"empty_column.lists": "Níl aon liostaí fós agat. Nuair a chruthaíonn tú ceann, feicfear anseo é.",
|
|
||||||
"empty_column.mutes": "Níl aon úsáideoir balbhaithe agat fós.",
|
"empty_column.mutes": "Níl aon úsáideoir balbhaithe agat fós.",
|
||||||
"empty_column.notification_requests": "Gach soiléir! Níl aon rud anseo. Nuair a gheobhaidh tú fógraí nua, beidh siad le feiceáil anseo de réir do shocruithe.",
|
"empty_column.notification_requests": "Gach soiléir! Níl aon rud anseo. Nuair a gheobhaidh tú fógraí nua, beidh siad le feiceáil anseo de réir do shocruithe.",
|
||||||
"empty_column.notifications": "Níl aon fógraí agat fós. Nuair a dhéanann daoine eile idirghníomhú leat, feicfear anseo é.",
|
"empty_column.notifications": "Níl aon fógraí agat fós. Nuair a dhéanann daoine eile idirghníomhú leat, feicfear anseo é.",
|
||||||
@ -465,20 +464,11 @@
|
|||||||
"link_preview.author": "Le {name}",
|
"link_preview.author": "Le {name}",
|
||||||
"link_preview.more_from_author": "Tuilleadh ó {name}",
|
"link_preview.more_from_author": "Tuilleadh ó {name}",
|
||||||
"link_preview.shares": "{count, plural, one {{counter} post} other {{counter} poist}}",
|
"link_preview.shares": "{count, plural, one {{counter} post} other {{counter} poist}}",
|
||||||
"lists.account.add": "Cuir leis an liosta",
|
|
||||||
"lists.account.remove": "Scrios as an liosta",
|
|
||||||
"lists.delete": "Scrios liosta",
|
"lists.delete": "Scrios liosta",
|
||||||
"lists.edit": "Cuir an liosta in eagar",
|
"lists.edit": "Cuir an liosta in eagar",
|
||||||
"lists.edit.submit": "Athraigh teideal",
|
|
||||||
"lists.exclusive": "Folaigh na poist seo ón mbaile",
|
|
||||||
"lists.new.create": "Cruthaigh liosta",
|
|
||||||
"lists.new.title_placeholder": "Teideal liosta nua",
|
|
||||||
"lists.replies_policy.followed": "Úsáideoir ar bith atá á leanúint",
|
"lists.replies_policy.followed": "Úsáideoir ar bith atá á leanúint",
|
||||||
"lists.replies_policy.list": "Baill an liosta",
|
"lists.replies_policy.list": "Baill an liosta",
|
||||||
"lists.replies_policy.none": "Duine ar bith",
|
"lists.replies_policy.none": "Duine ar bith",
|
||||||
"lists.replies_policy.title": "Taispeáin freagraí:",
|
|
||||||
"lists.search": "Cuardaigh i measc daoine atá á leanúint agat",
|
|
||||||
"lists.subheading": "Do liostaí",
|
|
||||||
"load_pending": "{count, plural, one {# mír nua} two {# mír nua} few {# mír nua} many {# mír nua} other {# mír nua}}",
|
"load_pending": "{count, plural, one {# mír nua} two {# mír nua} few {# mír nua} many {# mír nua} other {# mír nua}}",
|
||||||
"loading_indicator.label": "Á lódáil…",
|
"loading_indicator.label": "Á lódáil…",
|
||||||
"media_gallery.hide": "Folaigh",
|
"media_gallery.hide": "Folaigh",
|
||||||
|
@ -87,6 +87,25 @@
|
|||||||
"alert.unexpected.title": "Oich!",
|
"alert.unexpected.title": "Oich!",
|
||||||
"alt_text_badge.title": "Roghainn teacsa",
|
"alt_text_badge.title": "Roghainn teacsa",
|
||||||
"announcement.announcement": "Brath-fios",
|
"announcement.announcement": "Brath-fios",
|
||||||
|
"annual_report.summary.archetype.booster": "Brosnaiche",
|
||||||
|
"annual_report.summary.archetype.lurker": "Eala-bhalbh",
|
||||||
|
"annual_report.summary.archetype.oracle": "Coinneach Odhar",
|
||||||
|
"annual_report.summary.archetype.pollster": "Cunntair nam beachd",
|
||||||
|
"annual_report.summary.archetype.replier": "Ceatharnach nam freagairt",
|
||||||
|
"annual_report.summary.followers.followers": "luchd-leantainn",
|
||||||
|
"annual_report.summary.followers.total": "{count} gu h-iomlan",
|
||||||
|
"annual_report.summary.here_it_is": "Seo mar a chaidh {year} leat:",
|
||||||
|
"annual_report.summary.highlighted_post.by_favourites": "am post as annsa",
|
||||||
|
"annual_report.summary.highlighted_post.by_reblogs": "am post air a bhrosnachadh as trice",
|
||||||
|
"annual_report.summary.highlighted_post.by_replies": "am post dhan deach fhreagairt as trice",
|
||||||
|
"annual_report.summary.highlighted_post.possessive": "Aig {name},",
|
||||||
|
"annual_report.summary.most_used_app.most_used_app": "an aplacaid a chaidh a cleachdadh as trice",
|
||||||
|
"annual_report.summary.most_used_hashtag.most_used_hashtag": "an taga hais a chaidh a cleachdadh as trice",
|
||||||
|
"annual_report.summary.most_used_hashtag.none": "Chan eil gin",
|
||||||
|
"annual_report.summary.new_posts.new_posts": "postaichean ùra",
|
||||||
|
"annual_report.summary.percentile.text": "<topLabel>Tha thu am measg brod nan</topLabel><percentage></percentage><bottomLabel>dhen luchd-cleachdaidh Mhastodon.</bottomLabel>",
|
||||||
|
"annual_report.summary.percentile.we_wont_tell_bernie": "Ainmeil ’nad latha ’s ’nad linn.",
|
||||||
|
"annual_report.summary.thanks": "Mòran taing airson conaltradh air Mastodon.",
|
||||||
"attachments_list.unprocessed": "(gun phròiseasadh)",
|
"attachments_list.unprocessed": "(gun phròiseasadh)",
|
||||||
"audio.hide": "Falaich an fhuaim",
|
"audio.hide": "Falaich an fhuaim",
|
||||||
"block_modal.remote_users_caveat": "Iarraidh sinn air an fhrithealaiche {domain} gun gèill iad ri do cho-dhùnadh. Gidheadh, chan eil barantas gun gèill iad on a làimhsicheas cuid a fhrithealaichean bacaidhean air dòigh eadar-dhealaichte. Dh’fhaoidte gum faic daoine gun chlàradh a-steach na postaichean poblach agad fhathast.",
|
"block_modal.remote_users_caveat": "Iarraidh sinn air an fhrithealaiche {domain} gun gèill iad ri do cho-dhùnadh. Gidheadh, chan eil barantas gun gèill iad on a làimhsicheas cuid a fhrithealaichean bacaidhean air dòigh eadar-dhealaichte. Dh’fhaoidte gum faic daoine gun chlàradh a-steach na postaichean poblach agad fhathast.",
|
||||||
@ -273,7 +292,6 @@
|
|||||||
"empty_column.hashtag": "Chan eil dad san taga hais seo fhathast.",
|
"empty_column.hashtag": "Chan eil dad san taga hais seo fhathast.",
|
||||||
"empty_column.home": "Tha loidhne-ama na dachaigh agad falamh! Lean barrachd dhaoine gus a lìonadh.",
|
"empty_column.home": "Tha loidhne-ama na dachaigh agad falamh! Lean barrachd dhaoine gus a lìonadh.",
|
||||||
"empty_column.list": "Chan eil dad air an liosta seo fhathast. Nuair a phostaicheas buill a tha air an liosta seo postaichean ùra, nochdaidh iad an-seo.",
|
"empty_column.list": "Chan eil dad air an liosta seo fhathast. Nuair a phostaicheas buill a tha air an liosta seo postaichean ùra, nochdaidh iad an-seo.",
|
||||||
"empty_column.lists": "Chan eil liosta agad fhathast. Nuair chruthaicheas tu tè, nochdaidh i an-seo.",
|
|
||||||
"empty_column.mutes": "Cha do mhùch thu cleachdaiche sam bith fhathast.",
|
"empty_column.mutes": "Cha do mhùch thu cleachdaiche sam bith fhathast.",
|
||||||
"empty_column.notification_requests": "Glan! Chan eil dad an-seo. Nuair a gheibh thu brathan ùra, nochdaidh iad an-seo a-rèir nan roghainnean agad.",
|
"empty_column.notification_requests": "Glan! Chan eil dad an-seo. Nuair a gheibh thu brathan ùra, nochdaidh iad an-seo a-rèir nan roghainnean agad.",
|
||||||
"empty_column.notifications": "Cha d’ fhuair thu brath sam bith fhathast. Nuair a nì càch conaltradh leat, chì thu an-seo e.",
|
"empty_column.notifications": "Cha d’ fhuair thu brath sam bith fhathast. Nuair a nì càch conaltradh leat, chì thu an-seo e.",
|
||||||
@ -446,20 +464,11 @@
|
|||||||
"link_preview.author": "Le {name}",
|
"link_preview.author": "Le {name}",
|
||||||
"link_preview.more_from_author": "Barrachd le {name}",
|
"link_preview.more_from_author": "Barrachd le {name}",
|
||||||
"link_preview.shares": "{count, plural, one {{counter} phost} two {{counter} phost} few {{counter} postaichean} other {{counter} post}}",
|
"link_preview.shares": "{count, plural, one {{counter} phost} two {{counter} phost} few {{counter} postaichean} other {{counter} post}}",
|
||||||
"lists.account.add": "Cuir ris an liosta",
|
|
||||||
"lists.account.remove": "Thoir air falbh on liosta",
|
|
||||||
"lists.delete": "Sguab às an liosta",
|
"lists.delete": "Sguab às an liosta",
|
||||||
"lists.edit": "Deasaich an liosta",
|
"lists.edit": "Deasaich an liosta",
|
||||||
"lists.edit.submit": "Atharraich an tiotal",
|
|
||||||
"lists.exclusive": "Falaich na postaichean seo air an dachaigh",
|
|
||||||
"lists.new.create": "Cuir liosta ris",
|
|
||||||
"lists.new.title_placeholder": "Tiotal na liosta ùir",
|
|
||||||
"lists.replies_policy.followed": "Cleachdaiche sam bith a leanas mi",
|
"lists.replies_policy.followed": "Cleachdaiche sam bith a leanas mi",
|
||||||
"lists.replies_policy.list": "Buill na liosta",
|
"lists.replies_policy.list": "Buill na liosta",
|
||||||
"lists.replies_policy.none": "Na seall idir",
|
"lists.replies_policy.none": "Na seall idir",
|
||||||
"lists.replies_policy.title": "Seall freagairtean do:",
|
|
||||||
"lists.search": "Lorg am measg nan daoine a leanas tu",
|
|
||||||
"lists.subheading": "Na liostaichean agad",
|
|
||||||
"load_pending": "{count, plural, one {# nì ùr} two {# nì ùr} few {# nithean ùra} other {# nì ùr}}",
|
"load_pending": "{count, plural, one {# nì ùr} two {# nì ùr} few {# nithean ùra} other {# nì ùr}}",
|
||||||
"loading_indicator.label": "’Ga luchdadh…",
|
"loading_indicator.label": "’Ga luchdadh…",
|
||||||
"media_gallery.hide": "Falaich",
|
"media_gallery.hide": "Falaich",
|
||||||
@ -508,6 +517,8 @@
|
|||||||
"notification.admin.report_statuses_other": "Rinn {name} gearan mu {target}",
|
"notification.admin.report_statuses_other": "Rinn {name} gearan mu {target}",
|
||||||
"notification.admin.sign_up": "Chlàraich {name}",
|
"notification.admin.sign_up": "Chlàraich {name}",
|
||||||
"notification.admin.sign_up.name_and_others": "Chlàraich {name} ’s {count, plural, one {# eile} two {# eile} few {# eile} other {# eile}}",
|
"notification.admin.sign_up.name_and_others": "Chlàraich {name} ’s {count, plural, one {# eile} two {# eile} few {# eile} other {# eile}}",
|
||||||
|
"notification.annual_report.message": "Tha #Wrapstodon {year} deiseil dhut! Thoir sùil air mar a chaidh leat air Mastodon am bliadhna!",
|
||||||
|
"notification.annual_report.view": "Seall #Wrapstodon",
|
||||||
"notification.favourite": "Is annsa le {name} am post agad",
|
"notification.favourite": "Is annsa le {name} am post agad",
|
||||||
"notification.favourite.name_and_others_with_link": "Is annsa le {name} ’s <a>{count, plural, one {# eile} two {# eile} few {# eile} other {# eile}}</a> am post agad",
|
"notification.favourite.name_and_others_with_link": "Is annsa le {name} ’s <a>{count, plural, one {# eile} two {# eile} few {# eile} other {# eile}}</a> am post agad",
|
||||||
"notification.follow": "Tha {name} ’gad leantainn a-nis",
|
"notification.follow": "Tha {name} ’gad leantainn a-nis",
|
||||||
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
x
Reference in New Issue
Block a user