Merge pull request #3525 from glitch-soc/glitch-soc/merge-upstream
Merge upstream changes up to ead13d4826a7ba1699d6bb310e58642b15687671
This commit is contained in:
commit
60608f54e2
1
.github/workflows/bundler-audit.yml
vendored
1
.github/workflows/bundler-audit.yml
vendored
@ -22,6 +22,7 @@ on:
|
||||
jobs:
|
||||
security:
|
||||
runs-on: ubuntu-latest
|
||||
timeout-minutes: 15
|
||||
|
||||
env:
|
||||
BUNDLE_ONLY: development
|
||||
|
||||
1
.github/workflows/check-i18n.yml
vendored
1
.github/workflows/check-i18n.yml
vendored
@ -19,6 +19,7 @@ permissions:
|
||||
jobs:
|
||||
check-i18n:
|
||||
runs-on: ubuntu-latest
|
||||
timeout-minutes: 15
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6
|
||||
|
||||
2
.github/workflows/chromatic.yml
vendored
2
.github/workflows/chromatic.yml
vendored
@ -12,6 +12,7 @@ jobs:
|
||||
pathcheck:
|
||||
name: Check for relevant changes
|
||||
runs-on: ubuntu-latest
|
||||
timeout-minutes: 15
|
||||
outputs:
|
||||
changed: ${{ steps.filter.outputs.src }}
|
||||
steps:
|
||||
@ -38,6 +39,7 @@ jobs:
|
||||
chromatic:
|
||||
name: Run Chromatic
|
||||
runs-on: ubuntu-latest
|
||||
timeout-minutes: 15
|
||||
needs: pathcheck
|
||||
if: github.repository == 'mastodon/mastodon' && needs.pathcheck.outputs.changed == 'true'
|
||||
steps:
|
||||
|
||||
1
.github/workflows/codeql.yml
vendored
1
.github/workflows/codeql.yml
vendored
@ -17,6 +17,7 @@ jobs:
|
||||
analyze:
|
||||
name: Analyze
|
||||
runs-on: ubuntu-latest
|
||||
timeout-minutes: 15
|
||||
permissions:
|
||||
actions: read
|
||||
contents: read
|
||||
|
||||
1
.github/workflows/format-check.yml
vendored
1
.github/workflows/format-check.yml
vendored
@ -10,6 +10,7 @@ on:
|
||||
jobs:
|
||||
lint:
|
||||
runs-on: ubuntu-latest
|
||||
timeout-minutes: 15
|
||||
|
||||
steps:
|
||||
- name: Clone repository
|
||||
|
||||
1
.github/workflows/lint-css.yml
vendored
1
.github/workflows/lint-css.yml
vendored
@ -27,6 +27,7 @@ on:
|
||||
jobs:
|
||||
lint:
|
||||
runs-on: ubuntu-latest
|
||||
timeout-minutes: 15
|
||||
|
||||
steps:
|
||||
- name: Clone repository
|
||||
|
||||
1
.github/workflows/lint-haml.yml
vendored
1
.github/workflows/lint-haml.yml
vendored
@ -25,6 +25,7 @@ on:
|
||||
jobs:
|
||||
lint:
|
||||
runs-on: ubuntu-latest
|
||||
timeout-minutes: 15
|
||||
|
||||
env:
|
||||
BUNDLE_ONLY: development
|
||||
|
||||
1
.github/workflows/lint-js.yml
vendored
1
.github/workflows/lint-js.yml
vendored
@ -33,6 +33,7 @@ on:
|
||||
jobs:
|
||||
lint:
|
||||
runs-on: ubuntu-latest
|
||||
timeout-minutes: 15
|
||||
|
||||
steps:
|
||||
- name: Clone repository
|
||||
|
||||
1
.github/workflows/lint-ruby.yml
vendored
1
.github/workflows/lint-ruby.yml
vendored
@ -27,6 +27,7 @@ on:
|
||||
jobs:
|
||||
lint:
|
||||
runs-on: ubuntu-latest
|
||||
timeout-minutes: 15
|
||||
|
||||
env:
|
||||
BUNDLE_ONLY: development
|
||||
|
||||
1
.github/workflows/test-js.yml
vendored
1
.github/workflows/test-js.yml
vendored
@ -31,6 +31,7 @@ on:
|
||||
jobs:
|
||||
test:
|
||||
runs-on: ubuntu-latest
|
||||
timeout-minutes: 15
|
||||
|
||||
steps:
|
||||
- name: Clone repository
|
||||
|
||||
1
.github/workflows/test-migrations.yml
vendored
1
.github/workflows/test-migrations.yml
vendored
@ -25,6 +25,7 @@ on:
|
||||
jobs:
|
||||
test:
|
||||
runs-on: ubuntu-latest
|
||||
timeout-minutes: 15
|
||||
|
||||
strategy:
|
||||
fail-fast: false
|
||||
|
||||
4
.github/workflows/test-ruby.yml
vendored
4
.github/workflows/test-ruby.yml
vendored
@ -19,6 +19,7 @@ concurrency:
|
||||
jobs:
|
||||
build:
|
||||
runs-on: ubuntu-latest
|
||||
timeout-minutes: 15
|
||||
|
||||
strategy:
|
||||
fail-fast: true
|
||||
@ -75,6 +76,7 @@ jobs:
|
||||
|
||||
test:
|
||||
runs-on: ubuntu-latest
|
||||
timeout-minutes: 30
|
||||
|
||||
needs:
|
||||
- build
|
||||
@ -176,6 +178,7 @@ jobs:
|
||||
test-e2e:
|
||||
name: End to End testing
|
||||
runs-on: ubuntu-latest
|
||||
timeout-minutes: 15
|
||||
|
||||
needs:
|
||||
- build
|
||||
@ -279,6 +282,7 @@ jobs:
|
||||
test-search:
|
||||
name: Elastic Search integration testing
|
||||
runs-on: ubuntu-latest
|
||||
timeout-minutes: 15
|
||||
|
||||
needs:
|
||||
- build
|
||||
|
||||
@ -16,7 +16,7 @@ All notable changes to this project will be documented in this file.
|
||||
- Add export for custom filters (#39085 by @arte7)
|
||||
- Add ability to search email blocks by domain in admin UI (#38923 by @arte7)
|
||||
- Add new endpoints for profile editing in REST API (#37912, #37934, #37932, #38221, and #38339 by @ClearlyClaire)
|
||||
- Add `GET /api/v1/profile` and `PATCH /api/v1/profile` to replace the existing `update_credentials` endpoint. See [the documentation](https://www.notion.so/joinmastodon/Mastodon-v4-6-0-beta1-changelog-3656208ac91b8088a745d15a9e81f727) for more information.
|
||||
- Add `GET /api/v1/profile` and `PATCH /api/v1/profile` to replace the existing `update_credentials` endpoint. See [the documentation](https://docs.joinmastodon.org/methods/profile/) for more information.
|
||||
- Add `missing_attribution` boolean to preview cards in REST API (#38043 by @ClearlyClaire)
|
||||
- Documentation: https://docs.joinmastodon.org/entities/PreviewCard/#missing_attribution
|
||||
- Add `exclude_direct` flag to `/api/v1/accounts/:id/statuses` to exclude direct messages (#37763 by @ClearlyClaire)
|
||||
|
||||
69
Gemfile.lock
69
Gemfile.lock
@ -10,7 +10,7 @@ GIT
|
||||
GEM
|
||||
remote: https://rubygems.org/
|
||||
specs:
|
||||
action_text-trix (2.1.18)
|
||||
action_text-trix (2.1.19)
|
||||
railties
|
||||
actioncable (8.1.3)
|
||||
actionpack (= 8.1.3)
|
||||
@ -99,8 +99,8 @@ GEM
|
||||
ast (2.4.3)
|
||||
attr_required (1.0.2)
|
||||
aws-eventstream (1.4.0)
|
||||
aws-partitions (1.1255.0)
|
||||
aws-sdk-core (3.250.0)
|
||||
aws-partitions (1.1257.0)
|
||||
aws-sdk-core (3.251.0)
|
||||
aws-eventstream (~> 1, >= 1.3.0)
|
||||
aws-partitions (~> 1, >= 1.992.0)
|
||||
aws-sigv4 (~> 1.9)
|
||||
@ -108,10 +108,10 @@ GEM
|
||||
bigdecimal
|
||||
jmespath (~> 1, >= 1.6.1)
|
||||
logger
|
||||
aws-sdk-kms (1.128.0)
|
||||
aws-sdk-kms (1.129.0)
|
||||
aws-sdk-core (~> 3, >= 3.248.0)
|
||||
aws-sigv4 (~> 1.5)
|
||||
aws-sdk-s3 (1.224.0)
|
||||
aws-sdk-s3 (1.225.0)
|
||||
aws-sdk-core (~> 3, >= 3.248.0)
|
||||
aws-sdk-kms (~> 1)
|
||||
aws-sigv4 (~> 1.5)
|
||||
@ -132,7 +132,7 @@ GEM
|
||||
binding_of_caller (2.0.0)
|
||||
debug_inspector (>= 1.2.0)
|
||||
blurhash (0.1.8)
|
||||
bootsnap (1.24.4)
|
||||
bootsnap (1.24.6)
|
||||
msgpack (~> 1.2)
|
||||
brakeman (8.0.4)
|
||||
racc
|
||||
@ -159,7 +159,7 @@ GEM
|
||||
cbor (0.5.10.2)
|
||||
cgi (0.5.1)
|
||||
charlock_holmes (0.7.9)
|
||||
chewy (8.0.1)
|
||||
chewy (8.3.1)
|
||||
activesupport (>= 7.2)
|
||||
elasticsearch (>= 8.14, < 9.0)
|
||||
elasticsearch-dsl
|
||||
@ -178,13 +178,14 @@ GEM
|
||||
bigdecimal
|
||||
rexml
|
||||
crass (1.0.6)
|
||||
css_parser (2.1.0)
|
||||
css_parser (3.0.0)
|
||||
addressable
|
||||
ssrf_filter (~> 1.5)
|
||||
csv (3.3.5)
|
||||
database_cleaner-active_record (2.2.2)
|
||||
activerecord (>= 5.a)
|
||||
database_cleaner-core (~> 2.0)
|
||||
database_cleaner-core (2.0.1)
|
||||
database_cleaner-core (2.1.0)
|
||||
date (3.5.1)
|
||||
debug (1.11.1)
|
||||
irb (~> 1.10)
|
||||
@ -209,12 +210,12 @@ GEM
|
||||
activerecord (>= 7.0, < 9.0)
|
||||
docile (1.4.1)
|
||||
domain_name (0.6.20240107)
|
||||
doorkeeper (5.9.0)
|
||||
doorkeeper (5.9.1)
|
||||
railties (>= 5)
|
||||
dotenv (3.2.0)
|
||||
drb (2.2.3)
|
||||
dry-cli (1.4.1)
|
||||
elastic-transport (8.5.1)
|
||||
elastic-transport (8.5.2)
|
||||
faraday (< 3)
|
||||
multi_json
|
||||
elasticsearch (8.19.3)
|
||||
@ -224,13 +225,11 @@ GEM
|
||||
elasticsearch-api (8.19.3)
|
||||
multi_json
|
||||
elasticsearch-dsl (0.1.10)
|
||||
email_validator (2.2.4)
|
||||
activemodel
|
||||
erb (6.0.4)
|
||||
erubi (1.13.1)
|
||||
et-orbi (1.4.0)
|
||||
tzinfo
|
||||
excon (1.4.2)
|
||||
excon (1.5.0)
|
||||
logger
|
||||
fabrication (3.0.0)
|
||||
faker (3.8.0)
|
||||
@ -243,12 +242,12 @@ GEM
|
||||
faraday (>= 1, < 3)
|
||||
faraday-httpclient (2.0.2)
|
||||
httpclient (>= 2.2)
|
||||
faraday-net_http (3.4.3)
|
||||
faraday-net_http (3.4.4)
|
||||
net-http (~> 0.5)
|
||||
fast_blank (1.0.1)
|
||||
fastimage (2.4.1)
|
||||
ffi (1.17.4)
|
||||
ffi-compiler (1.3.2)
|
||||
ffi-compiler (1.4.2)
|
||||
ffi (>= 1.15.5)
|
||||
rake
|
||||
flatware (2.4.0)
|
||||
@ -273,15 +272,15 @@ GEM
|
||||
formatador (1.2.3)
|
||||
reline
|
||||
forwardable (1.4.0)
|
||||
fugit (1.12.1)
|
||||
fugit (1.12.2)
|
||||
et-orbi (~> 1.4)
|
||||
raabro (~> 1.4)
|
||||
globalid (1.3.0)
|
||||
activesupport (>= 6.1)
|
||||
google-protobuf (4.34.1)
|
||||
google-protobuf (4.35.0)
|
||||
bigdecimal
|
||||
rake (~> 13.3)
|
||||
googleapis-common-protos-types (1.22.0)
|
||||
googleapis-common-protos-types (1.23.0)
|
||||
google-protobuf (~> 4.26)
|
||||
haml (7.2.0)
|
||||
temple (>= 0.8.2)
|
||||
@ -354,9 +353,9 @@ GEM
|
||||
azure-blob (~> 0.5.2)
|
||||
hashie (~> 5.0)
|
||||
jmespath (1.6.2)
|
||||
json (2.19.7)
|
||||
json (2.19.8)
|
||||
json-canonicalization (1.0.0)
|
||||
json-jwt (1.17.0)
|
||||
json-jwt (1.17.1)
|
||||
activesupport (>= 4.2)
|
||||
aes_key_wrap
|
||||
base64
|
||||
@ -452,8 +451,8 @@ GEM
|
||||
minitest (6.0.6)
|
||||
drb (~> 2.0)
|
||||
prism (~> 1.5)
|
||||
msgpack (1.8.0)
|
||||
multi_json (1.20.1)
|
||||
msgpack (1.8.1)
|
||||
multi_json (1.21.1)
|
||||
mutex_m (0.3.0)
|
||||
net-http (0.9.1)
|
||||
uri (>= 0.11.1)
|
||||
@ -491,10 +490,9 @@ GEM
|
||||
omniauth_openid_connect (0.8.0)
|
||||
omniauth (>= 1.9, < 3)
|
||||
openid_connect (~> 2.2)
|
||||
openid_connect (2.3.1)
|
||||
openid_connect (2.5.0)
|
||||
activemodel
|
||||
attr_required (>= 1.0.0)
|
||||
email_validator
|
||||
faraday (~> 2.0)
|
||||
faraday-follow_redirects
|
||||
json-jwt (>= 1.16)
|
||||
@ -504,7 +502,7 @@ GEM
|
||||
tzinfo
|
||||
validate_url
|
||||
webfinger (~> 2.0)
|
||||
openssl (4.0.1)
|
||||
openssl (4.0.2)
|
||||
openssl-signature_algorithm (1.3.0)
|
||||
openssl (> 2.0)
|
||||
opentelemetry-api (1.10.0)
|
||||
@ -523,7 +521,7 @@ GEM
|
||||
opentelemetry-helpers-sql-processor (0.5.0)
|
||||
opentelemetry-api (~> 1.0)
|
||||
opentelemetry-common (~> 0.21)
|
||||
opentelemetry-instrumentation-action_mailer (0.8.0)
|
||||
opentelemetry-instrumentation-action_mailer (0.8.1)
|
||||
opentelemetry-instrumentation-active_support (~> 0.10)
|
||||
opentelemetry-instrumentation-action_pack (0.18.0)
|
||||
opentelemetry-instrumentation-rack (~> 0.29)
|
||||
@ -535,7 +533,7 @@ GEM
|
||||
opentelemetry-instrumentation-active_support (>= 0.7.0)
|
||||
opentelemetry-instrumentation-active_record (0.13.0)
|
||||
opentelemetry-instrumentation-base (~> 0.25)
|
||||
opentelemetry-instrumentation-active_storage (0.5.0)
|
||||
opentelemetry-instrumentation-active_storage (0.5.1)
|
||||
opentelemetry-instrumentation-active_support (~> 0.10)
|
||||
opentelemetry-instrumentation-active_support (0.12.0)
|
||||
opentelemetry-instrumentation-base (~> 0.25)
|
||||
@ -582,7 +580,7 @@ GEM
|
||||
opentelemetry-common (~> 0.20)
|
||||
opentelemetry-registry (~> 0.2)
|
||||
opentelemetry-semantic_conventions
|
||||
opentelemetry-semantic_conventions (1.37.1)
|
||||
opentelemetry-semantic_conventions (1.39.0)
|
||||
opentelemetry-api (~> 1.0)
|
||||
orm_adapter (0.5.0)
|
||||
ostruct (0.6.3)
|
||||
@ -620,11 +618,11 @@ GEM
|
||||
actionpack (>= 7.0.0)
|
||||
activesupport (>= 7.0.0)
|
||||
rack
|
||||
psych (5.3.1)
|
||||
psych (5.4.0)
|
||||
date
|
||||
stringio
|
||||
public_suffix (7.0.5)
|
||||
puma (8.0.1)
|
||||
puma (8.0.2)
|
||||
nio4r (~> 2.0)
|
||||
pundit (2.5.2)
|
||||
activesupport (>= 3.0.0)
|
||||
@ -721,7 +719,8 @@ GEM
|
||||
railties (>= 7.0)
|
||||
rexml (3.4.4)
|
||||
rotp (6.3.0)
|
||||
rouge (4.7.0)
|
||||
rouge (5.0.0)
|
||||
strscan (~> 3.1)
|
||||
rpam2 (4.0.2)
|
||||
rqrcode (3.2.0)
|
||||
chunky_png (~> 1.0)
|
||||
@ -844,6 +843,7 @@ GEM
|
||||
simplecov-html (0.13.2)
|
||||
simplecov-lcov (0.9.0)
|
||||
simplecov_json_formatter (0.1.4)
|
||||
ssrf_filter (1.5.0)
|
||||
stackprof (0.2.28)
|
||||
starry (0.2.0)
|
||||
base64
|
||||
@ -853,6 +853,7 @@ GEM
|
||||
stringio (3.2.0)
|
||||
strong_migrations (2.8.0)
|
||||
activerecord (>= 7.2)
|
||||
strscan (3.1.8)
|
||||
swd (2.0.3)
|
||||
activesupport (>= 3)
|
||||
attr_required (>= 0.0.5)
|
||||
@ -929,7 +930,7 @@ GEM
|
||||
crack (>= 0.3.2)
|
||||
hashdiff (>= 0.4.0, < 2.0.0)
|
||||
webrick (1.9.2)
|
||||
websocket-driver (0.8.0)
|
||||
websocket-driver (0.8.1)
|
||||
base64
|
||||
websocket-extensions (>= 0.1.0)
|
||||
websocket-extensions (0.1.5)
|
||||
@ -1100,4 +1101,4 @@ RUBY VERSION
|
||||
ruby 4.0.5
|
||||
|
||||
BUNDLED WITH
|
||||
4.0.11
|
||||
4.0.13
|
||||
|
||||
@ -7,10 +7,13 @@ class AuthorizeInteractionsController < ApplicationController
|
||||
before_action :set_resource
|
||||
|
||||
def show
|
||||
if @resource.is_a?(Account)
|
||||
case @resource
|
||||
when Account
|
||||
redirect_to web_url("@#{@resource.pretty_acct}")
|
||||
elsif @resource.is_a?(Status)
|
||||
when Status
|
||||
redirect_to web_url("@#{@resource.account.pretty_acct}/#{@resource.id}")
|
||||
when Collection
|
||||
redirect_to web_url("collections/#{resource.id}")
|
||||
else
|
||||
not_found
|
||||
end
|
||||
|
||||
@ -199,8 +199,10 @@ module LanguagesHelper
|
||||
kab: ['Kabyle', 'Taqbaylit'].freeze,
|
||||
ldn: ['Láadan', 'Láadan'].freeze,
|
||||
lfn: ['Lingua Franca Nova', 'lingua franca nova'].freeze,
|
||||
lzz: ['Lazuri', 'ლაზური ნენა'].freeze,
|
||||
moh: ['Mohawk', 'Kanienʼkéha'].freeze,
|
||||
nds: ['Low German', 'Plattdüütsch'].freeze,
|
||||
ota: ['Ottoman Turkish', 'لسان عثمانی'].freeze,
|
||||
pdc: ['Pennsylvania Dutch', 'Pennsilfaani-Deitsch'].freeze,
|
||||
sco: ['Scots', 'Scots'].freeze,
|
||||
sma: ['Southern Sami', 'Åarjelsaemien Gïele'].freeze,
|
||||
@ -209,6 +211,7 @@ module LanguagesHelper
|
||||
tok: ['Toki Pona', 'toki pona'].freeze,
|
||||
vai: ['Vai', 'ꕙꔤ'].freeze,
|
||||
xal: ['Kalmyk', 'Хальмг келн'].freeze,
|
||||
xmf: ['Mingrelian', 'მარგალური ნინა'].freeze,
|
||||
zba: ['Balaibalan', 'باليبلن'].freeze,
|
||||
zgh: ['Standard Moroccan Tamazight', 'ⵜⴰⵎⴰⵣⵉⵖⵜ'].freeze,
|
||||
}.freeze
|
||||
|
||||
@ -4,6 +4,7 @@ import { importAccounts } from './accounts';
|
||||
import { importCustomEmoji } from './emoji';
|
||||
import { normalizeStatus } from './normalizer';
|
||||
import { importPolls } from './polls';
|
||||
import { fetchAccountsForCollectionPreview } from '@/flavours/glitch/reducers/slices/collections';
|
||||
|
||||
export const STATUS_IMPORT = 'STATUS_IMPORT';
|
||||
export const STATUSES_IMPORT = 'STATUSES_IMPORT';
|
||||
@ -61,6 +62,7 @@ export function importFetchedStatuses(statuses, options = {}) {
|
||||
const normalStatuses = [];
|
||||
const polls = [];
|
||||
const filters = [];
|
||||
const collections = [];
|
||||
|
||||
function processStatus(status) {
|
||||
pushUnique(normalStatuses, normalizeStatus(status, getState().getIn(['statuses', status.id]), { ...options, settings: getState().get('local_settings') }));
|
||||
@ -82,6 +84,10 @@ export function importFetchedStatuses(statuses, options = {}) {
|
||||
pushUnique(polls, createPollFromServerJSON(status.poll, getState().polls[status.poll.id]));
|
||||
}
|
||||
|
||||
if (status.tagged_collections.length) {
|
||||
status.tagged_collections.forEach(collection => pushUnique(collections, collection));
|
||||
}
|
||||
|
||||
if (status.card) {
|
||||
status.card.authors.forEach(author => author.account && pushUnique(accounts, author.account));
|
||||
}
|
||||
@ -97,5 +103,6 @@ export function importFetchedStatuses(statuses, options = {}) {
|
||||
dispatch(importFetchedAccounts(accounts));
|
||||
dispatch(importStatuses(normalStatuses));
|
||||
dispatch(importFilters(filters));
|
||||
fetchAccountsForCollectionPreview(collections, dispatch);
|
||||
};
|
||||
}
|
||||
|
||||
@ -5,6 +5,7 @@ import {
|
||||
apiFetchNotificationGroups,
|
||||
} from 'flavours/glitch/api/notifications';
|
||||
import type { ApiAccountJSON } from 'flavours/glitch/api_types/accounts';
|
||||
import type { ApiCollectionJSON } from 'flavours/glitch/api_types/collections';
|
||||
import type {
|
||||
ApiNotificationGroupJSON,
|
||||
ApiNotificationJSON,
|
||||
@ -26,6 +27,8 @@ import {
|
||||
createDataLoadingThunk,
|
||||
} from 'flavours/glitch/store/typed_functions';
|
||||
|
||||
import { fetchAccountsForCollectionPreview } from '../reducers/slices/collections';
|
||||
|
||||
import { importFetchedAccounts, importFetchedStatuses } from './importer';
|
||||
import { NOTIFICATIONS_FILTER_SET } from './notifications';
|
||||
import { saveSettings } from './settings';
|
||||
@ -70,6 +73,7 @@ function dispatchAssociatedRecords(
|
||||
) {
|
||||
const fetchedAccounts: ApiAccountJSON[] = [];
|
||||
const fetchedStatuses: ApiStatusJSON[] = [];
|
||||
const collections: ApiCollectionJSON[] = [];
|
||||
|
||||
notifications.forEach((notification) => {
|
||||
if (notification.type === 'admin.report') {
|
||||
@ -83,6 +87,10 @@ function dispatchAssociatedRecords(
|
||||
if ('status' in notification && notification.status) {
|
||||
fetchedStatuses.push(notification.status);
|
||||
}
|
||||
|
||||
if ('collection' in notification && notification.collection) {
|
||||
collections.push(notification.collection);
|
||||
}
|
||||
});
|
||||
|
||||
if (fetchedAccounts.length > 0)
|
||||
@ -90,6 +98,9 @@ function dispatchAssociatedRecords(
|
||||
|
||||
if (fetchedStatuses.length > 0)
|
||||
dispatch(importFetchedStatuses(fetchedStatuses));
|
||||
|
||||
if (collections.length > 0)
|
||||
void fetchAccountsForCollectionPreview(collections, dispatch);
|
||||
}
|
||||
|
||||
function selectNotificationGroupedTypes(state: RootState) {
|
||||
|
||||
@ -12,6 +12,11 @@ import {
|
||||
createAppAsyncThunk,
|
||||
} from 'flavours/glitch/store/typed_functions';
|
||||
|
||||
import {
|
||||
fetchAccountsForCollectionPreview,
|
||||
importFetchedCollections,
|
||||
} from '../reducers/slices/collections';
|
||||
|
||||
import { fetchRelationships } from './accounts';
|
||||
import { importFetchedAccounts, importFetchedStatuses } from './importer';
|
||||
|
||||
@ -29,7 +34,7 @@ export const submitSearch = createDataLoadingThunk(
|
||||
limit: 11,
|
||||
});
|
||||
},
|
||||
(data, { dispatch }) => {
|
||||
async (data, { dispatch }) => {
|
||||
if (data.accounts.length > 0) {
|
||||
dispatch(importFetchedAccounts(data.accounts));
|
||||
dispatch(fetchRelationships(data.accounts.map((account) => account.id)));
|
||||
@ -39,6 +44,11 @@ export const submitSearch = createDataLoadingThunk(
|
||||
dispatch(importFetchedStatuses(data.statuses));
|
||||
}
|
||||
|
||||
if (data.collections.length > 0) {
|
||||
dispatch(importFetchedCollections(data.collections));
|
||||
await fetchAccountsForCollectionPreview(data.collections, dispatch);
|
||||
}
|
||||
|
||||
return data;
|
||||
},
|
||||
{
|
||||
@ -60,7 +70,7 @@ export const expandSearch = createDataLoadingThunk(
|
||||
offset,
|
||||
});
|
||||
},
|
||||
(data, { dispatch }) => {
|
||||
async (data, { dispatch }) => {
|
||||
if (data.accounts.length > 0) {
|
||||
dispatch(importFetchedAccounts(data.accounts));
|
||||
dispatch(fetchRelationships(data.accounts.map((account) => account.id)));
|
||||
@ -70,6 +80,11 @@ export const expandSearch = createDataLoadingThunk(
|
||||
dispatch(importFetchedStatuses(data.statuses));
|
||||
}
|
||||
|
||||
if (data.collections.length > 0) {
|
||||
dispatch(importFetchedCollections(data.collections));
|
||||
await fetchAccountsForCollectionPreview(data.collections, dispatch);
|
||||
}
|
||||
|
||||
return data;
|
||||
},
|
||||
{
|
||||
|
||||
@ -90,22 +90,22 @@ interface ReportNotificationJSON extends BaseNotificationJSON {
|
||||
|
||||
interface AddedToCollectionNotificationGroupJSON extends BaseNotificationGroupJSON {
|
||||
type: 'added_to_collection';
|
||||
collection: ApiCollectionJSON;
|
||||
collection: ApiCollectionJSON | null;
|
||||
}
|
||||
|
||||
interface AddedToCollectionNotificationJSON extends BaseNotificationJSON {
|
||||
type: 'added_to_collection';
|
||||
collection: ApiCollectionJSON;
|
||||
collection: ApiCollectionJSON | null;
|
||||
}
|
||||
|
||||
interface CollectionUpdateNotificationGroupJSON extends BaseNotificationGroupJSON {
|
||||
type: 'collection_update';
|
||||
collection: ApiCollectionJSON;
|
||||
collection: ApiCollectionJSON | null;
|
||||
}
|
||||
|
||||
interface CollectionUpdateNotificationJSON extends BaseNotificationJSON {
|
||||
type: 'collection_update';
|
||||
collection: ApiCollectionJSON;
|
||||
collection: ApiCollectionJSON | null;
|
||||
}
|
||||
|
||||
type SimpleNotificationTypes = 'follow' | 'follow_request' | 'admin.sign_up';
|
||||
|
||||
@ -29,7 +29,7 @@ import { useAppDispatch, useAppSelector } from '@/flavours/glitch/store';
|
||||
import AddIcon from '@/material-icons/400-24px/add.svg?react';
|
||||
|
||||
import { CollectionListItem } from '../collections/components/collection_list_item';
|
||||
import { useCollectionsCreatedBy } from '../collections/overview/created_by_you';
|
||||
import { useCollectionsCreatedBy } from '../collections/overview/created_by_account';
|
||||
|
||||
import { EmptyMessage } from './components/empty_message';
|
||||
import { Subheading, SubheadingLink } from './components/subheading';
|
||||
|
||||
@ -0,0 +1,91 @@
|
||||
// app/javascript/flavours/glitch/features/alt_text_modal/__tests__/index-test.tsx
|
||||
|
||||
import { IntlProvider } from 'react-intl';
|
||||
|
||||
import { List, Map } from 'immutable';
|
||||
|
||||
import { render } from '@testing-library/react';
|
||||
import { vi } from 'vitest';
|
||||
|
||||
import type { RootState } from 'flavours/glitch/store';
|
||||
import { useAppSelector } from 'flavours/glitch/store';
|
||||
|
||||
import { AltTextModal } from '../index';
|
||||
|
||||
vi.mock('flavours/glitch/store', () => ({
|
||||
useAppSelector: vi.fn(),
|
||||
useAppDispatch: () => vi.fn(),
|
||||
}));
|
||||
|
||||
describe('<AltTextModal />', () => {
|
||||
const mediaId = '123';
|
||||
const handleClose = vi.fn();
|
||||
|
||||
beforeEach(() => {
|
||||
vi.clearAllMocks();
|
||||
});
|
||||
|
||||
const renderComponent = () => {
|
||||
return render(
|
||||
<IntlProvider locale='en' messages={{}}>
|
||||
<AltTextModal mediaId={mediaId} onClose={handleClose} />
|
||||
</IntlProvider>,
|
||||
);
|
||||
};
|
||||
|
||||
it('renders thumbnail upload button when video is unattached', () => {
|
||||
vi.mocked(useAppSelector).mockImplementation(
|
||||
(selector: (state: RootState) => unknown) => {
|
||||
const mockState = {
|
||||
compose: Map({
|
||||
language: 'en',
|
||||
media_attachments: List([
|
||||
Map({
|
||||
id: mediaId,
|
||||
type: 'video',
|
||||
unattached: true,
|
||||
meta: Map({ focus: Map({ x: 0, y: 0 }) }),
|
||||
}),
|
||||
]),
|
||||
}),
|
||||
accounts: Map(),
|
||||
} as unknown as RootState;
|
||||
|
||||
return selector(mockState);
|
||||
},
|
||||
);
|
||||
|
||||
const { container } = renderComponent();
|
||||
|
||||
const uploadInput = container.querySelector('#upload-modal__thumbnail');
|
||||
expect(uploadInput).not.toBeNull();
|
||||
});
|
||||
|
||||
it('hides thumbnail upload button when video is attached', () => {
|
||||
vi.mocked(useAppSelector).mockImplementation(
|
||||
(selector: (state: RootState) => unknown) => {
|
||||
const mockState = {
|
||||
compose: Map({
|
||||
language: 'en',
|
||||
media_attachments: List([
|
||||
Map({
|
||||
id: mediaId,
|
||||
type: 'video',
|
||||
unattached: false,
|
||||
meta: Map({ focus: Map({ x: 0, y: 0 }) }),
|
||||
}),
|
||||
]),
|
||||
}),
|
||||
accounts: Map(),
|
||||
} as unknown as RootState;
|
||||
|
||||
return selector(mockState);
|
||||
},
|
||||
);
|
||||
|
||||
const { container } = renderComponent();
|
||||
|
||||
const uploadInput = container.querySelector('#upload-modal__thumbnail');
|
||||
expect(uploadInput).toBeNull();
|
||||
});
|
||||
});
|
||||
@ -283,6 +283,7 @@ export const AltTextModal = forwardRef<ModalRef, Props & Partial<RestoreProps>>(
|
||||
);
|
||||
const type = media?.get('type') as string;
|
||||
const valid = length(description) <= MAX_LENGTH;
|
||||
const unattached = media?.get('unattached') as boolean | undefined;
|
||||
|
||||
const handleDescriptionChange = useCallback(
|
||||
(e: React.ChangeEvent<HTMLTextAreaElement>) => {
|
||||
@ -433,7 +434,8 @@ export const AltTextModal = forwardRef<ModalRef, Props & Partial<RestoreProps>>(
|
||||
onPositionChange={handlePositionChange}
|
||||
/>
|
||||
|
||||
{(type === 'audio' || type === 'video') && (
|
||||
{/* This button is hidden for attached audio/video files, as they are already posted */}
|
||||
{(type === 'audio' || type === 'video') && unattached && (
|
||||
<UploadButton
|
||||
onSelectFile={handleThumbnailChange}
|
||||
mimeTypes='image/jpeg,image/png,image/gif,image/heic,image/heif,image/webp,image/avif'
|
||||
|
||||
@ -15,7 +15,7 @@ import { IconButton } from 'flavours/glitch/components/icon_button';
|
||||
import { useAppDispatch, useAppSelector } from 'flavours/glitch/store';
|
||||
|
||||
import { MAX_COLLECTION_ACCOUNT_COUNT } from '../collections/editor/accounts';
|
||||
import { useCollectionsCreatedBy } from '../collections/overview/created_by_you';
|
||||
import { useCollectionsCreatedBy } from '../collections/overview/created_by_account';
|
||||
|
||||
import { CollectionToggle } from './collection_toggle';
|
||||
|
||||
|
||||
@ -211,6 +211,12 @@ export const CollectionAccounts: React.FC<{
|
||||
const hasItems = editorItems.length > 0;
|
||||
const hasMaxItems = editorItems.length === MAX_COLLECTION_ACCOUNT_COUNT;
|
||||
|
||||
const wasAccountAdded = useCallback(
|
||||
(account: ApiMutedAccountJSON) =>
|
||||
!!editorItems.find((item) => item.account_id === account.id),
|
||||
[editorItems],
|
||||
);
|
||||
|
||||
const {
|
||||
accounts: suggestedAccounts,
|
||||
isLoading: isLoadingSuggestions,
|
||||
@ -220,8 +226,7 @@ export const CollectionAccounts: React.FC<{
|
||||
withRelationships: true,
|
||||
withDefaultFollows: searchValue === '',
|
||||
// Don't suggest accounts that were already added
|
||||
filterResults: (account) =>
|
||||
!editorItems.find((item) => item.account_id === account.id),
|
||||
filterResults: (account) => !wasAccountAdded(account),
|
||||
});
|
||||
|
||||
const relationships = useAppSelector((state) => state.relationships);
|
||||
@ -259,23 +264,25 @@ export const CollectionAccounts: React.FC<{
|
||||
|
||||
const addAccountItem = useCallback(
|
||||
(item: ApiMutedAccountJSON) => {
|
||||
dispatch(
|
||||
updateCollectionEditorField({
|
||||
field: 'items',
|
||||
value: [
|
||||
...editorItems,
|
||||
{
|
||||
account_id: item.id,
|
||||
state:
|
||||
item.feature_approval.current_user === 'manual'
|
||||
? 'pending'
|
||||
: 'accepted',
|
||||
},
|
||||
],
|
||||
}),
|
||||
);
|
||||
if (!wasAccountAdded(item)) {
|
||||
dispatch(
|
||||
updateCollectionEditorField({
|
||||
field: 'items',
|
||||
value: [
|
||||
...editorItems,
|
||||
{
|
||||
account_id: item.id,
|
||||
state:
|
||||
item.feature_approval.current_user === 'manual'
|
||||
? 'pending'
|
||||
: 'accepted',
|
||||
},
|
||||
],
|
||||
}),
|
||||
);
|
||||
}
|
||||
},
|
||||
[editorItems, dispatch],
|
||||
[editorItems, wasAccountAdded, dispatch],
|
||||
);
|
||||
|
||||
const instantRemoveAccountItem = useCallback(
|
||||
@ -302,13 +309,13 @@ export const CollectionAccounts: React.FC<{
|
||||
|
||||
const instantAddAccountItem = useCallback(
|
||||
(item: ApiMutedAccountJSON) => {
|
||||
if (id) {
|
||||
if (id && !wasAccountAdded(item)) {
|
||||
void dispatch(
|
||||
addCollectionItem({ collectionId: id, accountId: item.id }),
|
||||
);
|
||||
}
|
||||
},
|
||||
[dispatch, id],
|
||||
[dispatch, id, wasAccountAdded],
|
||||
);
|
||||
|
||||
const handleRemoveAccountItem = useCallback(
|
||||
|
||||
@ -13,20 +13,21 @@ import {
|
||||
|
||||
import { Helmet } from '@unhead/react/helmet';
|
||||
|
||||
import { Callout } from '@/flavours/glitch/components/callout';
|
||||
import { useCurrentAccountId } from '@/flavours/glitch/hooks/useAccountId';
|
||||
import { initialState } from '@/flavours/glitch/initial_state';
|
||||
import ListAltIcon from '@/material-icons/400-24px/list_alt.svg?react';
|
||||
import { Callout } from 'flavours/glitch/components/callout';
|
||||
import { Column } from 'flavours/glitch/components/column';
|
||||
import { ColumnHeader } from 'flavours/glitch/components/column_header';
|
||||
import { LoadingIndicator } from 'flavours/glitch/components/loading_indicator';
|
||||
import { NotSignedInIndicator } from 'flavours/glitch/components/not_signed_in_indicator';
|
||||
import { useIdentity } from 'flavours/glitch/identity_context';
|
||||
import { initialState } from 'flavours/glitch/initial_state';
|
||||
import {
|
||||
collectionEditorActions,
|
||||
fetchCollection,
|
||||
} from 'flavours/glitch/reducers/slices/collections';
|
||||
import { useAppDispatch, useAppSelector } from 'flavours/glitch/store';
|
||||
|
||||
import { useCollectionsCreatedBy } from '../overview/created_by_you';
|
||||
import { useCollectionsCreatedBy } from '../overview/created_by_account';
|
||||
|
||||
import { CollectionAccounts } from './accounts';
|
||||
import { CollectionDetails } from './details';
|
||||
@ -75,7 +76,7 @@ export const CollectionEditorPage: React.FC<{
|
||||
}> = ({ multiColumn }) => {
|
||||
const intl = useIntl();
|
||||
const dispatch = useAppDispatch();
|
||||
const accountId = useCurrentAccountId();
|
||||
const { accountId, signedIn } = useIdentity();
|
||||
const { id = null } = useParams<{ id?: string }>();
|
||||
const { path } = useRouteMatch();
|
||||
const collection = useAppSelector((state) =>
|
||||
@ -94,13 +95,13 @@ export const CollectionEditorPage: React.FC<{
|
||||
(!isEditMode && collectionListStatus === 'loading');
|
||||
|
||||
const canCreateMoreCollections =
|
||||
isEditMode || collectionList.length < userCollectionLimit;
|
||||
signedIn && (isEditMode || collectionList.length < userCollectionLimit);
|
||||
|
||||
useEffect(() => {
|
||||
if (id) {
|
||||
if (id && signedIn) {
|
||||
void dispatch(fetchCollection({ collectionId: id }));
|
||||
}
|
||||
}, [dispatch, id]);
|
||||
}, [dispatch, id, signedIn]);
|
||||
|
||||
useEffect(() => {
|
||||
if (id !== editorStateId) {
|
||||
@ -129,6 +130,8 @@ export const CollectionEditorPage: React.FC<{
|
||||
<div className='scrollable'>
|
||||
{isLoading ? (
|
||||
<LoadingIndicator />
|
||||
) : !signedIn ? (
|
||||
<NotSignedInIndicator />
|
||||
) : canCreateMoreCollections ? (
|
||||
<Switch>
|
||||
<Route
|
||||
|
||||
@ -4,18 +4,18 @@ import { Route, Switch, useRouteMatch } from 'react-router-dom';
|
||||
|
||||
import { Helmet } from '@unhead/react/helmet';
|
||||
|
||||
import { TabLink, TabList } from '@/flavours/glitch/components/tab_list';
|
||||
import { Column } from 'flavours/glitch/components/column';
|
||||
import { ColumnHeader } from 'flavours/glitch/components/column_header';
|
||||
import { DisplayNameSimple } from 'flavours/glitch/components/display_name/simple';
|
||||
import { Scrollable } from 'flavours/glitch/components/scrollable_list/components';
|
||||
import { TabLink, TabList } from 'flavours/glitch/components/tab_list';
|
||||
import { useAccount } from 'flavours/glitch/hooks/useAccount';
|
||||
import {
|
||||
useAccountId,
|
||||
useCurrentAccountId,
|
||||
} from 'flavours/glitch/hooks/useAccountId';
|
||||
|
||||
import { CollectionsCreatedByYou } from './overview/created_by_you';
|
||||
import { CollectionsCreatedByAccount } from './overview/created_by_account';
|
||||
import { CollectionsFeaturingYou } from './overview/featuring_you';
|
||||
import classes from './styles.module.scss';
|
||||
|
||||
@ -92,7 +92,7 @@ export const Collections: React.FC<{
|
||||
</TabList>
|
||||
</header>
|
||||
<Switch>
|
||||
<Route exact path={path} component={CollectionsCreatedByYou} />
|
||||
<Route exact path={path} component={CollectionsCreatedByAccount} />
|
||||
<Route
|
||||
exact
|
||||
path={`${path}/featuring-you`}
|
||||
|
||||
@ -5,10 +5,12 @@ import { FormattedMessage } from 'react-intl';
|
||||
import { Link } from 'react-router-dom';
|
||||
|
||||
import AddIcon from '@/material-icons/400-24px/add.svg?react';
|
||||
import { DisplayName } from 'flavours/glitch/components/display_name';
|
||||
import { EmptyState } from 'flavours/glitch/components/empty_state';
|
||||
import { Icon } from 'flavours/glitch/components/icon';
|
||||
import { LoadingIndicator } from 'flavours/glitch/components/loading_indicator';
|
||||
import { ItemList } from 'flavours/glitch/components/scrollable_list/components';
|
||||
import { useAccount } from 'flavours/glitch/hooks/useAccount';
|
||||
import {
|
||||
useAccountId,
|
||||
useCurrentAccountId,
|
||||
@ -61,9 +63,10 @@ export function useCollectionsCreatedBy(accountId: string | null | undefined) {
|
||||
);
|
||||
}
|
||||
|
||||
export const CollectionsCreatedByYou: React.FC = () => {
|
||||
export const CollectionsCreatedByAccount: React.FC = () => {
|
||||
const me = useCurrentAccountId();
|
||||
const accountId = useAccountId();
|
||||
const account = useAccount(accountId);
|
||||
|
||||
const { collections, status } = useCollectionsCreatedBy(accountId);
|
||||
|
||||
@ -81,24 +84,40 @@ export const CollectionsCreatedByYou: React.FC = () => {
|
||||
}
|
||||
|
||||
if (collections.length === 0) {
|
||||
return (
|
||||
<EmptyState
|
||||
title={
|
||||
<FormattedMessage
|
||||
id='empty_column.account_featured_self.showcase_accounts'
|
||||
defaultMessage='Showcase your favorite accounts'
|
||||
/>
|
||||
}
|
||||
message={
|
||||
<FormattedMessage
|
||||
id='empty_column.account_featured_self.showcase_accounts_desc'
|
||||
defaultMessage='Collections are curated lists of accounts to help others discover more of the Fediverse.'
|
||||
/>
|
||||
}
|
||||
>
|
||||
<CreateButton />
|
||||
</EmptyState>
|
||||
);
|
||||
if (isOwnCollectionPage) {
|
||||
return (
|
||||
<EmptyState
|
||||
title={
|
||||
<FormattedMessage
|
||||
id='empty_column.account_featured_self.showcase_accounts'
|
||||
defaultMessage='Showcase your favorite accounts'
|
||||
/>
|
||||
}
|
||||
message={
|
||||
<FormattedMessage
|
||||
id='empty_column.account_featured_self.showcase_accounts_desc'
|
||||
defaultMessage='Collections are curated lists of accounts to help others discover more of the Fediverse.'
|
||||
/>
|
||||
}
|
||||
>
|
||||
<CreateButton />
|
||||
</EmptyState>
|
||||
);
|
||||
} else {
|
||||
return (
|
||||
<EmptyState
|
||||
title={
|
||||
<FormattedMessage
|
||||
id='empty_column.collections'
|
||||
defaultMessage='{acct} has not created any collections yet.'
|
||||
values={{
|
||||
acct: <DisplayName variant='simple' account={account} />,
|
||||
}}
|
||||
/>
|
||||
}
|
||||
/>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
return (
|
||||
@ -116,7 +135,7 @@ export const CollectionsCreatedByYou: React.FC = () => {
|
||||
{showCreateButton && <CreateButton />}
|
||||
</div>
|
||||
<ItemList>
|
||||
{!canCreateMoreCollections && (
|
||||
{isOwnCollectionPage && !canCreateMoreCollections && (
|
||||
<MaxCollectionsCallout className={classes.maxCollectionsError} />
|
||||
)}
|
||||
{collections.map((item, index) => (
|
||||
@ -16,7 +16,7 @@ import { useAppSelector, useAppDispatch } from 'flavours/glitch/store';
|
||||
import { CollectionListItem } from '../components/collection_list_item';
|
||||
import classes from '../styles.module.scss';
|
||||
|
||||
import { CollectionListError } from './created_by_you';
|
||||
import { CollectionListError } from './created_by_account';
|
||||
|
||||
function useCollectionsFeaturing(accountId: string | null | undefined) {
|
||||
const dispatch = useAppDispatch();
|
||||
|
||||
@ -6,6 +6,8 @@ import { Link } from 'react-router-dom';
|
||||
|
||||
import { Helmet } from '@unhead/react/helmet';
|
||||
|
||||
import { NotSignedInIndicator } from '@/flavours/glitch/components/not_signed_in_indicator';
|
||||
import { useIdentity } from '@/flavours/glitch/identity_context';
|
||||
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';
|
||||
@ -78,10 +80,13 @@ const Lists: React.FC<{
|
||||
const dispatch = useAppDispatch();
|
||||
const intl = useIntl();
|
||||
const lists = useAppSelector((state) => getOrderedLists(state));
|
||||
const { signedIn } = useIdentity();
|
||||
|
||||
useEffect(() => {
|
||||
void dispatch(fetchLists());
|
||||
}, [dispatch]);
|
||||
if (signedIn) {
|
||||
void dispatch(fetchLists());
|
||||
}
|
||||
}, [signedIn, dispatch]);
|
||||
|
||||
const emptyMessage = (
|
||||
<>
|
||||
@ -112,14 +117,16 @@ const Lists: React.FC<{
|
||||
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>
|
||||
signedIn && (
|
||||
<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>
|
||||
)
|
||||
}
|
||||
/>
|
||||
|
||||
@ -128,9 +135,13 @@ const Lists: React.FC<{
|
||||
emptyMessage={emptyMessage}
|
||||
bindToDocument={!multiColumn}
|
||||
>
|
||||
{lists.map((list) => (
|
||||
<ListItem key={list.id} id={list.id} title={list.title} />
|
||||
))}
|
||||
{signedIn ? (
|
||||
lists.map((list) => (
|
||||
<ListItem key={list.id} id={list.id} title={list.title} />
|
||||
))
|
||||
) : (
|
||||
<NotSignedInIndicator />
|
||||
)}
|
||||
</ScrollableList>
|
||||
|
||||
<Helmet>
|
||||
|
||||
@ -8,6 +8,8 @@ import { isFulfilled } from '@reduxjs/toolkit';
|
||||
|
||||
import { Helmet } from '@unhead/react/helmet';
|
||||
|
||||
import { NotSignedInIndicator } from '@/flavours/glitch/components/not_signed_in_indicator';
|
||||
import { useIdentity } from '@/flavours/glitch/identity_context';
|
||||
import ChevronRightIcon from '@/material-icons/400-24px/chevron_right.svg?react';
|
||||
import ListAltIcon from '@/material-icons/400-24px/list_alt.svg?react';
|
||||
import { fetchList } from 'flavours/glitch/actions/lists';
|
||||
@ -250,16 +252,17 @@ const NewListWrapper: React.FC<{
|
||||
}> = ({ multiColumn }) => {
|
||||
const intl = useIntl();
|
||||
const dispatch = useAppDispatch();
|
||||
const { signedIn } = useIdentity();
|
||||
const { id } = useParams<{ id?: string }>();
|
||||
const list = useAppSelector((state) =>
|
||||
id ? state.lists.get(id) : undefined,
|
||||
);
|
||||
|
||||
useEffect(() => {
|
||||
if (id) {
|
||||
if (signedIn && id) {
|
||||
dispatch(fetchList(id));
|
||||
}
|
||||
}, [dispatch, id]);
|
||||
}, [dispatch, signedIn, id]);
|
||||
|
||||
const isLoading = id && !list;
|
||||
|
||||
@ -277,7 +280,13 @@ const NewListWrapper: React.FC<{
|
||||
/>
|
||||
|
||||
<div className='scrollable'>
|
||||
{isLoading ? <LoadingIndicator /> : <NewList list={list} />}
|
||||
{!signedIn ? (
|
||||
<NotSignedInIndicator />
|
||||
) : isLoading ? (
|
||||
<LoadingIndicator />
|
||||
) : (
|
||||
<NewList list={list} />
|
||||
)}
|
||||
</div>
|
||||
|
||||
<Helmet>
|
||||
|
||||
@ -4,6 +4,7 @@ import { useIntl, defineMessages, FormattedMessage } from 'react-intl';
|
||||
|
||||
import { Helmet } from '@unhead/react/helmet';
|
||||
|
||||
import CollectionsIcon from '@/material-icons/400-24px/category.svg?react';
|
||||
import FindInPageIcon from '@/material-icons/400-24px/find_in_page.svg?react';
|
||||
import PeopleIcon from '@/material-icons/400-24px/group.svg?react';
|
||||
import SearchIcon from '@/material-icons/400-24px/search.svg?react';
|
||||
@ -23,6 +24,8 @@ import { useSearchParam } from 'flavours/glitch/hooks/useSearchParam';
|
||||
import type { Hashtag as HashtagType } from 'flavours/glitch/models/tags';
|
||||
import { useAppDispatch, useAppSelector } from 'flavours/glitch/store';
|
||||
|
||||
import { CollectionListItem } from '../collections/components/collection_list_item';
|
||||
|
||||
import { SearchSection } from './components/search_section';
|
||||
|
||||
const messages = defineMessages({
|
||||
@ -131,7 +134,8 @@ export const SearchResults: React.FC<{ multiColumn: boolean }> = ({
|
||||
filteredResults =
|
||||
results.accounts.length +
|
||||
results.hashtags.length +
|
||||
results.statuses.length >
|
||||
results.statuses.length +
|
||||
results.collections.length >
|
||||
0 ? (
|
||||
<>
|
||||
{results.accounts.length > 0 && (
|
||||
@ -154,6 +158,32 @@ export const SearchResults: React.FC<{ multiColumn: boolean }> = ({
|
||||
</SearchSection>
|
||||
)}
|
||||
|
||||
{results.collections.length > 0 && (
|
||||
<SearchSection
|
||||
key='collections'
|
||||
title={
|
||||
<>
|
||||
<Icon id='collections' icon={CollectionsIcon} />
|
||||
<FormattedMessage
|
||||
id='search_results.collections'
|
||||
defaultMessage='Collections'
|
||||
/>
|
||||
</>
|
||||
}
|
||||
>
|
||||
{results.collections
|
||||
.slice(0, INITIAL_DISPLAY)
|
||||
.map((collection, index, array) => (
|
||||
<CollectionListItem
|
||||
key={collection.id}
|
||||
collection={collection}
|
||||
listSize={array.length}
|
||||
positionInList={index + 1}
|
||||
/>
|
||||
))}
|
||||
</SearchSection>
|
||||
)}
|
||||
|
||||
{results.hashtags.length > 0 && (
|
||||
<SearchSection
|
||||
key='hashtags'
|
||||
|
||||
@ -89,7 +89,7 @@ import {
|
||||
Quotes,
|
||||
} from './util/async-components';
|
||||
import { ColumnsContextProvider } from './util/columns_context';
|
||||
import { focusColumn, getFocusedItemIndex, focusItemSibling, focusFirstItem } from './util/focusUtils';
|
||||
import { focusColumn, getFocusedItemIndex, focusItemSibling, focusFirstItem, getFocusedColumnIndex } from './util/focusUtils';
|
||||
import { WrappedSwitch, WrappedRoute } from './util/react_router_helpers';
|
||||
import { CustomHomepage } from 'flavours/glitch/features/custom_homepage';
|
||||
|
||||
@ -552,20 +552,18 @@ class UI extends PureComponent {
|
||||
handleMoveUp = () => {
|
||||
const currentItemIndex = getFocusedItemIndex();
|
||||
if (currentItemIndex === -1) {
|
||||
focusColumn(1);
|
||||
return focusColumn(getFocusedColumnIndex());
|
||||
} else {
|
||||
const wasHandled = focusItemSibling(currentItemIndex, -1);
|
||||
return wasHandled;
|
||||
return focusItemSibling(currentItemIndex, -1);
|
||||
}
|
||||
};
|
||||
|
||||
handleMoveDown = () => {
|
||||
const currentItemIndex = getFocusedItemIndex();
|
||||
if (currentItemIndex === -1) {
|
||||
focusColumn(1);
|
||||
return focusColumn(getFocusedColumnIndex());
|
||||
} else {
|
||||
const wasHandled = focusItemSibling(currentItemIndex, 1);
|
||||
return wasHandled;
|
||||
return focusItemSibling(currentItemIndex, 1);
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
@ -62,18 +62,17 @@ export function focusColumn(index = 1) {
|
||||
|
||||
function fallback() {
|
||||
focusColumnTitle(index + indexOffset, isMultiColumnLayout);
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!column) {
|
||||
fallback();
|
||||
return;
|
||||
return fallback();
|
||||
}
|
||||
|
||||
const container = column.querySelector('.scrollable');
|
||||
|
||||
if (!container) {
|
||||
fallback();
|
||||
return;
|
||||
return fallback();
|
||||
}
|
||||
|
||||
const focusableItems = Array.from(
|
||||
@ -86,8 +85,7 @@ export function focusColumn(index = 1) {
|
||||
const itemToFocus = findFirstVisibleWithRect(focusableItems);
|
||||
|
||||
if (!itemToFocus) {
|
||||
fallback();
|
||||
return;
|
||||
return fallback();
|
||||
}
|
||||
|
||||
const viewportWidth =
|
||||
@ -110,6 +108,7 @@ export function focusColumn(index = 1) {
|
||||
itemToFocus.item.scrollIntoView(true);
|
||||
}
|
||||
itemToFocus.item.focus();
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
@ -126,6 +125,18 @@ export function getFocusedItemIndex() {
|
||||
return items.indexOf(focusedItem);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the index of the column that contains the user's focus
|
||||
*/
|
||||
export function getFocusedColumnIndex() {
|
||||
const columnWithFocus = document.activeElement?.closest('.column');
|
||||
|
||||
if (!columnWithFocus) return 1;
|
||||
|
||||
const allColumns = Array.from(document.querySelectorAll('.column'));
|
||||
return allColumns.indexOf(columnWithFocus) + 1;
|
||||
}
|
||||
|
||||
/**
|
||||
* Focus the topmost item of the column that currently has focus,
|
||||
* or the first column if none
|
||||
@ -159,12 +170,7 @@ export function focusItemSibling(index: number, direction: 1 | -1) {
|
||||
);
|
||||
|
||||
if (!siblingItem) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// If sibling element is empty, we skip it
|
||||
if (siblingItem.matches(':empty')) {
|
||||
return focusItemSibling(index + direction, direction);
|
||||
return focusListSibling(direction);
|
||||
}
|
||||
|
||||
// Check if the sibling is a post or a 'follow suggestions' widget
|
||||
@ -177,6 +183,52 @@ export function focusItemSibling(index: number, direction: 1 | -1) {
|
||||
targetElement = siblingItem;
|
||||
}
|
||||
|
||||
// If sibling element is empty, we skip it
|
||||
if (!targetElement || siblingItem.matches(':empty')) {
|
||||
return focusItemSibling(index + direction, direction);
|
||||
}
|
||||
|
||||
targetElement.scrollIntoView({
|
||||
block: 'start',
|
||||
});
|
||||
|
||||
targetElement.focus();
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Finds the next or previous .item-list on the page or in the column,
|
||||
* and focuses its first or last item.
|
||||
*/
|
||||
function focusListSibling(direction: 1 | -1) {
|
||||
const container = document.activeElement?.closest('.item-list');
|
||||
|
||||
if (!container) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Get all item lists in the current column or page
|
||||
const currentColumn = container.closest('.column') ?? document;
|
||||
|
||||
const columnItemLists = Array.from(
|
||||
currentColumn.querySelectorAll('.item-list'),
|
||||
);
|
||||
const currentListIndex = columnItemLists.indexOf(container);
|
||||
|
||||
// Find the next or previous item-list
|
||||
const listSibling = columnItemLists[currentListIndex + direction];
|
||||
|
||||
// Depending on the direction, find the first or last focusable in the list
|
||||
let targetElement: HTMLElement | null | undefined;
|
||||
if (direction > 0) {
|
||||
targetElement = listSibling?.querySelector<HTMLElement>('.focusable');
|
||||
} else {
|
||||
const allFocusables =
|
||||
listSibling?.querySelectorAll<HTMLElement>('.focusable');
|
||||
targetElement = allFocusables?.[allFocusables.length - 1];
|
||||
}
|
||||
|
||||
if (targetElement) {
|
||||
targetElement.scrollIntoView({
|
||||
block: 'start',
|
||||
|
||||
@ -51,7 +51,7 @@ export function useSearchAccounts({
|
||||
searchRequestRef.current = new AbortController();
|
||||
|
||||
try {
|
||||
const data = await apiRequest<ApiAccountJSON[]>(
|
||||
const accounts = await apiRequest<ApiAccountJSON[]>(
|
||||
'GET',
|
||||
'v1/accounts/search',
|
||||
{
|
||||
@ -62,7 +62,6 @@ export function useSearchAccounts({
|
||||
},
|
||||
},
|
||||
);
|
||||
const accounts = filterResults ? data.filter(filterResults) : data;
|
||||
const accountIds = accounts.map((a) => a.id);
|
||||
dispatch(importFetchedAccounts(accounts));
|
||||
if (withRelationships) {
|
||||
@ -95,6 +94,7 @@ export function useSearchAccounts({
|
||||
const [defaultAccounts, setDefaultAccounts] = useState<
|
||||
ApiAccountJSON[] | null
|
||||
>(null);
|
||||
|
||||
useEffect(() => {
|
||||
if (
|
||||
!currentUserId ||
|
||||
@ -108,12 +108,11 @@ export function useSearchAccounts({
|
||||
async function doRequest() {
|
||||
setLoadingState('loading');
|
||||
try {
|
||||
const data = await apiRequest<ApiAccountJSON[]>(
|
||||
const accounts = await apiRequest<ApiAccountJSON[]>(
|
||||
'GET',
|
||||
`v1/accounts/${currentUserId}/following`,
|
||||
{ params: { limit: 40 } },
|
||||
);
|
||||
const accounts = filterResults ? data.filter(filterResults) : data;
|
||||
const accountIds = accounts.map((a) => a.id);
|
||||
dispatch(importFetchedAccounts(accounts));
|
||||
if (withRelationships) {
|
||||
@ -137,13 +136,19 @@ export function useSearchAccounts({
|
||||
withDefaultFollows,
|
||||
]);
|
||||
|
||||
const accountsToReturn =
|
||||
accounts.length === 0 && withDefaultFollows
|
||||
? (defaultAccounts ?? [])
|
||||
: accounts;
|
||||
|
||||
const filteredAccounts = filterResults
|
||||
? accountsToReturn.filter(filterResults)
|
||||
: accountsToReturn;
|
||||
|
||||
return {
|
||||
searchAccounts: startSearch,
|
||||
resetAccounts,
|
||||
accounts:
|
||||
accounts.length === 0 && withDefaultFollows
|
||||
? (defaultAccounts ?? [])
|
||||
: accounts,
|
||||
accounts: filteredAccounts,
|
||||
isLoading: loadingState === 'loading',
|
||||
isError: loadingState === 'error',
|
||||
};
|
||||
|
||||
@ -1,5 +1,6 @@
|
||||
import type { ApiSearchResultsJSON } from 'flavours/glitch/api_types/search';
|
||||
import type { ApiHashtagJSON } from 'flavours/glitch/api_types/tags';
|
||||
import type { ApiCollectionJSON } from '@/flavours/glitch/api_types/collections';
|
||||
import type { ApiSearchResultsJSON } from '@/flavours/glitch/api_types/search';
|
||||
import type { ApiHashtagJSON } from '@/flavours/glitch/api_types/tags';
|
||||
|
||||
export type SearchType = 'account' | 'hashtag' | 'accounts' | 'statuses';
|
||||
|
||||
@ -12,10 +13,12 @@ export interface SearchResults {
|
||||
accounts: string[];
|
||||
statuses: string[];
|
||||
hashtags: ApiHashtagJSON[];
|
||||
collections: ApiCollectionJSON[];
|
||||
}
|
||||
|
||||
export const createSearchResults = (serverJSON: ApiSearchResultsJSON) => ({
|
||||
accounts: serverJSON.accounts.map((account) => account.id),
|
||||
statuses: serverJSON.statuses.map((status) => status.id),
|
||||
hashtags: serverJSON.hashtags,
|
||||
collections: serverJSON.collections,
|
||||
});
|
||||
|
||||
@ -52,6 +52,9 @@ export const searchReducer = createReducer(initialState, (builder) => {
|
||||
hashtags: state.results
|
||||
? [...state.results.hashtags, ...results.hashtags]
|
||||
: results.hashtags,
|
||||
collections: state.results
|
||||
? [...state.results.collections, ...results.collections]
|
||||
: results.collections,
|
||||
};
|
||||
state.loading = false;
|
||||
});
|
||||
|
||||
@ -27,6 +27,7 @@ import {
|
||||
createAppSelector,
|
||||
createDataLoadingThunk,
|
||||
} from '@/flavours/glitch/store/typed_functions';
|
||||
import { batchArray } from '@/flavours/glitch/utils/batch_array';
|
||||
import { inputToHashtag } from '@/flavours/glitch/utils/hashtags';
|
||||
|
||||
type QueryStatus = 'idle' | 'loading' | 'error';
|
||||
@ -120,6 +121,15 @@ const collectionSlice = createSlice({
|
||||
const { field, value } = action.payload;
|
||||
state.editor[field] = value;
|
||||
},
|
||||
importFetchedCollections(
|
||||
state,
|
||||
action: PayloadAction<ApiCollectionJSON[]>,
|
||||
) {
|
||||
const collections = action.payload;
|
||||
collections.forEach((collection) => {
|
||||
state.collections[collection.id] = collection;
|
||||
});
|
||||
},
|
||||
},
|
||||
extraReducers(builder) {
|
||||
/**
|
||||
@ -327,7 +337,7 @@ const collectionSlice = createSlice({
|
||||
/**
|
||||
* Prefetch accounts whose avatars will be displayed in the collection list
|
||||
*/
|
||||
async function importAccountsForPreviewCard(
|
||||
export async function fetchAccountsForCollectionPreview(
|
||||
collections: ApiCollectionJSON[],
|
||||
dispatch: AppDispatch,
|
||||
) {
|
||||
@ -337,11 +347,17 @@ async function importAccountsForPreviewCard(
|
||||
)
|
||||
.filter((id): id is string => !!id);
|
||||
|
||||
await dispatch(
|
||||
fetchAccounts({
|
||||
accountIds: previewAccountIds,
|
||||
}),
|
||||
);
|
||||
if (previewAccountIds.length > 0) {
|
||||
// fetchAccounts can only process up to 40 item ids, so we'll
|
||||
// batch the list of ids
|
||||
const batchedAccountIdLists = batchArray(previewAccountIds, 40);
|
||||
|
||||
await Promise.allSettled(
|
||||
batchedAccountIdLists.map((accountIds) =>
|
||||
dispatch(fetchAccounts({ accountIds })),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
export const fetchCollectionsCreatedByAccount = createDataLoadingThunk(
|
||||
@ -349,7 +365,7 @@ export const fetchCollectionsCreatedByAccount = createDataLoadingThunk(
|
||||
({ accountId }: { accountId: string }) =>
|
||||
apiGetCollectionsCreatedByAccount(accountId),
|
||||
async ({ collections }, { dispatch }) => {
|
||||
await importAccountsForPreviewCard(collections, dispatch);
|
||||
await fetchAccountsForCollectionPreview(collections, dispatch);
|
||||
},
|
||||
);
|
||||
|
||||
@ -358,7 +374,7 @@ export const fetchCollectionsFeaturingAccount = createDataLoadingThunk(
|
||||
({ accountId }: { accountId: string }) =>
|
||||
apiGetCollectionsFeaturingAccount(accountId),
|
||||
async ({ collections }, { dispatch }) => {
|
||||
await importAccountsForPreviewCard(collections, dispatch);
|
||||
await fetchAccountsForCollectionPreview(collections, dispatch);
|
||||
},
|
||||
);
|
||||
|
||||
@ -414,6 +430,8 @@ export const collections = collectionSlice.reducer;
|
||||
export const collectionEditorActions = collectionSlice.actions;
|
||||
export const updateCollectionEditorField =
|
||||
collectionSlice.actions.updateEditorField;
|
||||
export const importFetchedCollections =
|
||||
collectionSlice.actions.importFetchedCollections;
|
||||
|
||||
/**
|
||||
* Selectors
|
||||
|
||||
@ -486,6 +486,8 @@ $content-width: 840px;
|
||||
.title {
|
||||
font-weight: 600;
|
||||
margin-bottom: 8px;
|
||||
font-size: inherit;
|
||||
line-height: inherit;
|
||||
}
|
||||
|
||||
a {
|
||||
|
||||
@ -558,7 +558,6 @@ code {
|
||||
display: block;
|
||||
width: 100%;
|
||||
font-family: inherit;
|
||||
resize: vertical;
|
||||
background: var(--color-bg-secondary);
|
||||
border: 1px solid var(--color-border-primary);
|
||||
border-radius: 4px;
|
||||
@ -585,6 +584,10 @@ code {
|
||||
}
|
||||
}
|
||||
|
||||
textarea {
|
||||
resize: vertical;
|
||||
}
|
||||
|
||||
input[type='text'],
|
||||
input[type='number'],
|
||||
input[type='email'],
|
||||
|
||||
15
app/javascript/flavours/glitch/utils/batch_array.ts
Normal file
15
app/javascript/flavours/glitch/utils/batch_array.ts
Normal file
@ -0,0 +1,15 @@
|
||||
/**
|
||||
* Splits a long array so that the resulting nested arrays
|
||||
* never exceed the `maxLength` provided.
|
||||
* Useful when dealing with endpoints that accept a limited number
|
||||
* of parameters
|
||||
*/
|
||||
export function batchArray<T>(array: T[], maxLength: number) {
|
||||
const result: T[][] = [];
|
||||
|
||||
for (let i = 0; i < array.length; i += maxLength) {
|
||||
result.push(array.slice(i, i + maxLength));
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
@ -4,6 +4,7 @@ import { importAccounts } from './accounts';
|
||||
import { importCustomEmoji } from './emoji';
|
||||
import { normalizeStatus } from './normalizer';
|
||||
import { importPolls } from './polls';
|
||||
import { fetchAccountsForCollectionPreview } from '@/mastodon/reducers/slices/collections';
|
||||
|
||||
export const STATUS_IMPORT = 'STATUS_IMPORT';
|
||||
export const STATUSES_IMPORT = 'STATUSES_IMPORT';
|
||||
@ -61,6 +62,7 @@ export function importFetchedStatuses(statuses, options = {}) {
|
||||
const normalStatuses = [];
|
||||
const polls = [];
|
||||
const filters = [];
|
||||
const collections = [];
|
||||
|
||||
function processStatus(status) {
|
||||
pushUnique(normalStatuses, normalizeStatus(status, getState().getIn(['statuses', status.id]), options));
|
||||
@ -82,6 +84,10 @@ export function importFetchedStatuses(statuses, options = {}) {
|
||||
pushUnique(polls, createPollFromServerJSON(status.poll, getState().polls[status.poll.id]));
|
||||
}
|
||||
|
||||
if (status.tagged_collections.length) {
|
||||
status.tagged_collections.forEach(collection => pushUnique(collections, collection));
|
||||
}
|
||||
|
||||
if (status.card) {
|
||||
status.card.authors.forEach(author => author.account && pushUnique(accounts, author.account));
|
||||
}
|
||||
@ -97,5 +103,6 @@ export function importFetchedStatuses(statuses, options = {}) {
|
||||
dispatch(importFetchedAccounts(accounts));
|
||||
dispatch(importStatuses(normalStatuses));
|
||||
dispatch(importFilters(filters));
|
||||
fetchAccountsForCollectionPreview(collections, dispatch);
|
||||
};
|
||||
}
|
||||
|
||||
@ -5,6 +5,7 @@ import {
|
||||
apiFetchNotificationGroups,
|
||||
} from 'mastodon/api/notifications';
|
||||
import type { ApiAccountJSON } from 'mastodon/api_types/accounts';
|
||||
import type { ApiCollectionJSON } from 'mastodon/api_types/collections';
|
||||
import type {
|
||||
ApiNotificationGroupJSON,
|
||||
ApiNotificationJSON,
|
||||
@ -26,6 +27,8 @@ import {
|
||||
createDataLoadingThunk,
|
||||
} from 'mastodon/store/typed_functions';
|
||||
|
||||
import { fetchAccountsForCollectionPreview } from '../reducers/slices/collections';
|
||||
|
||||
import { importFetchedAccounts, importFetchedStatuses } from './importer';
|
||||
import { NOTIFICATIONS_FILTER_SET } from './notifications';
|
||||
import { saveSettings } from './settings';
|
||||
@ -70,6 +73,7 @@ function dispatchAssociatedRecords(
|
||||
) {
|
||||
const fetchedAccounts: ApiAccountJSON[] = [];
|
||||
const fetchedStatuses: ApiStatusJSON[] = [];
|
||||
const collections: ApiCollectionJSON[] = [];
|
||||
|
||||
notifications.forEach((notification) => {
|
||||
if (notification.type === 'admin.report') {
|
||||
@ -83,6 +87,10 @@ function dispatchAssociatedRecords(
|
||||
if ('status' in notification && notification.status) {
|
||||
fetchedStatuses.push(notification.status);
|
||||
}
|
||||
|
||||
if ('collection' in notification && notification.collection) {
|
||||
collections.push(notification.collection);
|
||||
}
|
||||
});
|
||||
|
||||
if (fetchedAccounts.length > 0)
|
||||
@ -90,6 +98,9 @@ function dispatchAssociatedRecords(
|
||||
|
||||
if (fetchedStatuses.length > 0)
|
||||
dispatch(importFetchedStatuses(fetchedStatuses));
|
||||
|
||||
if (collections.length > 0)
|
||||
void fetchAccountsForCollectionPreview(collections, dispatch);
|
||||
}
|
||||
|
||||
function selectNotificationGroupedTypes(state: RootState) {
|
||||
|
||||
@ -12,6 +12,11 @@ import {
|
||||
createAppAsyncThunk,
|
||||
} from 'mastodon/store/typed_functions';
|
||||
|
||||
import {
|
||||
fetchAccountsForCollectionPreview,
|
||||
importFetchedCollections,
|
||||
} from '../reducers/slices/collections';
|
||||
|
||||
import { fetchRelationships } from './accounts';
|
||||
import { importFetchedAccounts, importFetchedStatuses } from './importer';
|
||||
|
||||
@ -29,7 +34,7 @@ export const submitSearch = createDataLoadingThunk(
|
||||
limit: 11,
|
||||
});
|
||||
},
|
||||
(data, { dispatch }) => {
|
||||
async (data, { dispatch }) => {
|
||||
if (data.accounts.length > 0) {
|
||||
dispatch(importFetchedAccounts(data.accounts));
|
||||
dispatch(fetchRelationships(data.accounts.map((account) => account.id)));
|
||||
@ -39,6 +44,11 @@ export const submitSearch = createDataLoadingThunk(
|
||||
dispatch(importFetchedStatuses(data.statuses));
|
||||
}
|
||||
|
||||
if (data.collections.length > 0) {
|
||||
dispatch(importFetchedCollections(data.collections));
|
||||
await fetchAccountsForCollectionPreview(data.collections, dispatch);
|
||||
}
|
||||
|
||||
return data;
|
||||
},
|
||||
{
|
||||
@ -60,7 +70,7 @@ export const expandSearch = createDataLoadingThunk(
|
||||
offset,
|
||||
});
|
||||
},
|
||||
(data, { dispatch }) => {
|
||||
async (data, { dispatch }) => {
|
||||
if (data.accounts.length > 0) {
|
||||
dispatch(importFetchedAccounts(data.accounts));
|
||||
dispatch(fetchRelationships(data.accounts.map((account) => account.id)));
|
||||
@ -70,6 +80,11 @@ export const expandSearch = createDataLoadingThunk(
|
||||
dispatch(importFetchedStatuses(data.statuses));
|
||||
}
|
||||
|
||||
if (data.collections.length > 0) {
|
||||
dispatch(importFetchedCollections(data.collections));
|
||||
await fetchAccountsForCollectionPreview(data.collections, dispatch);
|
||||
}
|
||||
|
||||
return data;
|
||||
},
|
||||
{
|
||||
|
||||
@ -90,22 +90,22 @@ interface ReportNotificationJSON extends BaseNotificationJSON {
|
||||
|
||||
interface AddedToCollectionNotificationGroupJSON extends BaseNotificationGroupJSON {
|
||||
type: 'added_to_collection';
|
||||
collection: ApiCollectionJSON;
|
||||
collection: ApiCollectionJSON | null;
|
||||
}
|
||||
|
||||
interface AddedToCollectionNotificationJSON extends BaseNotificationJSON {
|
||||
type: 'added_to_collection';
|
||||
collection: ApiCollectionJSON;
|
||||
collection: ApiCollectionJSON | null;
|
||||
}
|
||||
|
||||
interface CollectionUpdateNotificationGroupJSON extends BaseNotificationGroupJSON {
|
||||
type: 'collection_update';
|
||||
collection: ApiCollectionJSON;
|
||||
collection: ApiCollectionJSON | null;
|
||||
}
|
||||
|
||||
interface CollectionUpdateNotificationJSON extends BaseNotificationJSON {
|
||||
type: 'collection_update';
|
||||
collection: ApiCollectionJSON;
|
||||
collection: ApiCollectionJSON | null;
|
||||
}
|
||||
|
||||
type SimpleNotificationTypes = 'follow' | 'follow_request' | 'admin.sign_up';
|
||||
|
||||
@ -29,7 +29,7 @@ import { useAppDispatch, useAppSelector } from '@/mastodon/store';
|
||||
import AddIcon from '@/material-icons/400-24px/add.svg?react';
|
||||
|
||||
import { CollectionListItem } from '../collections/components/collection_list_item';
|
||||
import { useCollectionsCreatedBy } from '../collections/overview/created_by_you';
|
||||
import { useCollectionsCreatedBy } from '../collections/overview/created_by_account';
|
||||
|
||||
import { EmptyMessage } from './components/empty_message';
|
||||
import { Subheading, SubheadingLink } from './components/subheading';
|
||||
|
||||
@ -0,0 +1,91 @@
|
||||
// app/javascript/mastodon/features/alt_text_modal/__tests__/index-test.tsx
|
||||
|
||||
import { IntlProvider } from 'react-intl';
|
||||
|
||||
import { List, Map } from 'immutable';
|
||||
|
||||
import { render } from '@testing-library/react';
|
||||
import { vi } from 'vitest';
|
||||
|
||||
import type { RootState } from 'mastodon/store';
|
||||
import { useAppSelector } from 'mastodon/store';
|
||||
|
||||
import { AltTextModal } from '../index';
|
||||
|
||||
vi.mock('mastodon/store', () => ({
|
||||
useAppSelector: vi.fn(),
|
||||
useAppDispatch: () => vi.fn(),
|
||||
}));
|
||||
|
||||
describe('<AltTextModal />', () => {
|
||||
const mediaId = '123';
|
||||
const handleClose = vi.fn();
|
||||
|
||||
beforeEach(() => {
|
||||
vi.clearAllMocks();
|
||||
});
|
||||
|
||||
const renderComponent = () => {
|
||||
return render(
|
||||
<IntlProvider locale='en' messages={{}}>
|
||||
<AltTextModal mediaId={mediaId} onClose={handleClose} />
|
||||
</IntlProvider>,
|
||||
);
|
||||
};
|
||||
|
||||
it('renders thumbnail upload button when video is unattached', () => {
|
||||
vi.mocked(useAppSelector).mockImplementation(
|
||||
(selector: (state: RootState) => unknown) => {
|
||||
const mockState = {
|
||||
compose: Map({
|
||||
language: 'en',
|
||||
media_attachments: List([
|
||||
Map({
|
||||
id: mediaId,
|
||||
type: 'video',
|
||||
unattached: true,
|
||||
meta: Map({ focus: Map({ x: 0, y: 0 }) }),
|
||||
}),
|
||||
]),
|
||||
}),
|
||||
accounts: Map(),
|
||||
} as unknown as RootState;
|
||||
|
||||
return selector(mockState);
|
||||
},
|
||||
);
|
||||
|
||||
const { container } = renderComponent();
|
||||
|
||||
const uploadInput = container.querySelector('#upload-modal__thumbnail');
|
||||
expect(uploadInput).not.toBeNull();
|
||||
});
|
||||
|
||||
it('hides thumbnail upload button when video is attached', () => {
|
||||
vi.mocked(useAppSelector).mockImplementation(
|
||||
(selector: (state: RootState) => unknown) => {
|
||||
const mockState = {
|
||||
compose: Map({
|
||||
language: 'en',
|
||||
media_attachments: List([
|
||||
Map({
|
||||
id: mediaId,
|
||||
type: 'video',
|
||||
unattached: false,
|
||||
meta: Map({ focus: Map({ x: 0, y: 0 }) }),
|
||||
}),
|
||||
]),
|
||||
}),
|
||||
accounts: Map(),
|
||||
} as unknown as RootState;
|
||||
|
||||
return selector(mockState);
|
||||
},
|
||||
);
|
||||
|
||||
const { container } = renderComponent();
|
||||
|
||||
const uploadInput = container.querySelector('#upload-modal__thumbnail');
|
||||
expect(uploadInput).toBeNull();
|
||||
});
|
||||
});
|
||||
@ -283,6 +283,7 @@ export const AltTextModal = forwardRef<ModalRef, Props & Partial<RestoreProps>>(
|
||||
);
|
||||
const type = media?.get('type') as string;
|
||||
const valid = length(description) <= MAX_LENGTH;
|
||||
const unattached = media?.get('unattached') as boolean | undefined;
|
||||
|
||||
const handleDescriptionChange = useCallback(
|
||||
(e: React.ChangeEvent<HTMLTextAreaElement>) => {
|
||||
@ -433,7 +434,8 @@ export const AltTextModal = forwardRef<ModalRef, Props & Partial<RestoreProps>>(
|
||||
onPositionChange={handlePositionChange}
|
||||
/>
|
||||
|
||||
{(type === 'audio' || type === 'video') && (
|
||||
{/* This button is hidden for attached audio/video files, as they are already posted */}
|
||||
{(type === 'audio' || type === 'video') && unattached && (
|
||||
<UploadButton
|
||||
onSelectFile={handleThumbnailChange}
|
||||
mimeTypes='image/jpeg,image/png,image/gif,image/heic,image/heif,image/webp,image/avif'
|
||||
|
||||
@ -15,7 +15,7 @@ import { IconButton } from 'mastodon/components/icon_button';
|
||||
import { useAppDispatch, useAppSelector } from 'mastodon/store';
|
||||
|
||||
import { MAX_COLLECTION_ACCOUNT_COUNT } from '../collections/editor/accounts';
|
||||
import { useCollectionsCreatedBy } from '../collections/overview/created_by_you';
|
||||
import { useCollectionsCreatedBy } from '../collections/overview/created_by_account';
|
||||
|
||||
import { CollectionToggle } from './collection_toggle';
|
||||
|
||||
|
||||
@ -208,6 +208,12 @@ export const CollectionAccounts: React.FC<{
|
||||
const hasItems = editorItems.length > 0;
|
||||
const hasMaxItems = editorItems.length === MAX_COLLECTION_ACCOUNT_COUNT;
|
||||
|
||||
const wasAccountAdded = useCallback(
|
||||
(account: ApiMutedAccountJSON) =>
|
||||
!!editorItems.find((item) => item.account_id === account.id),
|
||||
[editorItems],
|
||||
);
|
||||
|
||||
const {
|
||||
accounts: suggestedAccounts,
|
||||
isLoading: isLoadingSuggestions,
|
||||
@ -217,8 +223,7 @@ export const CollectionAccounts: React.FC<{
|
||||
withRelationships: true,
|
||||
withDefaultFollows: searchValue === '',
|
||||
// Don't suggest accounts that were already added
|
||||
filterResults: (account) =>
|
||||
!editorItems.find((item) => item.account_id === account.id),
|
||||
filterResults: (account) => !wasAccountAdded(account),
|
||||
});
|
||||
|
||||
const relationships = useAppSelector((state) => state.relationships);
|
||||
@ -256,23 +261,25 @@ export const CollectionAccounts: React.FC<{
|
||||
|
||||
const addAccountItem = useCallback(
|
||||
(item: ApiMutedAccountJSON) => {
|
||||
dispatch(
|
||||
updateCollectionEditorField({
|
||||
field: 'items',
|
||||
value: [
|
||||
...editorItems,
|
||||
{
|
||||
account_id: item.id,
|
||||
state:
|
||||
item.feature_approval.current_user === 'manual'
|
||||
? 'pending'
|
||||
: 'accepted',
|
||||
},
|
||||
],
|
||||
}),
|
||||
);
|
||||
if (!wasAccountAdded(item)) {
|
||||
dispatch(
|
||||
updateCollectionEditorField({
|
||||
field: 'items',
|
||||
value: [
|
||||
...editorItems,
|
||||
{
|
||||
account_id: item.id,
|
||||
state:
|
||||
item.feature_approval.current_user === 'manual'
|
||||
? 'pending'
|
||||
: 'accepted',
|
||||
},
|
||||
],
|
||||
}),
|
||||
);
|
||||
}
|
||||
},
|
||||
[editorItems, dispatch],
|
||||
[editorItems, wasAccountAdded, dispatch],
|
||||
);
|
||||
|
||||
const instantRemoveAccountItem = useCallback(
|
||||
@ -299,13 +306,13 @@ export const CollectionAccounts: React.FC<{
|
||||
|
||||
const instantAddAccountItem = useCallback(
|
||||
(item: ApiMutedAccountJSON) => {
|
||||
if (id) {
|
||||
if (id && !wasAccountAdded(item)) {
|
||||
void dispatch(
|
||||
addCollectionItem({ collectionId: id, accountId: item.id }),
|
||||
);
|
||||
}
|
||||
},
|
||||
[dispatch, id],
|
||||
[dispatch, id, wasAccountAdded],
|
||||
);
|
||||
|
||||
const handleRemoveAccountItem = useCallback(
|
||||
|
||||
@ -13,20 +13,21 @@ import {
|
||||
|
||||
import { Helmet } from '@unhead/react/helmet';
|
||||
|
||||
import { Callout } from '@/mastodon/components/callout';
|
||||
import { useCurrentAccountId } from '@/mastodon/hooks/useAccountId';
|
||||
import { initialState } from '@/mastodon/initial_state';
|
||||
import ListAltIcon from '@/material-icons/400-24px/list_alt.svg?react';
|
||||
import { Callout } from 'mastodon/components/callout';
|
||||
import { Column } from 'mastodon/components/column';
|
||||
import { ColumnHeader } from 'mastodon/components/column_header';
|
||||
import { LoadingIndicator } from 'mastodon/components/loading_indicator';
|
||||
import { NotSignedInIndicator } from 'mastodon/components/not_signed_in_indicator';
|
||||
import { useIdentity } from 'mastodon/identity_context';
|
||||
import { initialState } from 'mastodon/initial_state';
|
||||
import {
|
||||
collectionEditorActions,
|
||||
fetchCollection,
|
||||
} from 'mastodon/reducers/slices/collections';
|
||||
import { useAppDispatch, useAppSelector } from 'mastodon/store';
|
||||
|
||||
import { useCollectionsCreatedBy } from '../overview/created_by_you';
|
||||
import { useCollectionsCreatedBy } from '../overview/created_by_account';
|
||||
|
||||
import { CollectionAccounts } from './accounts';
|
||||
import { CollectionDetails } from './details';
|
||||
@ -75,7 +76,7 @@ export const CollectionEditorPage: React.FC<{
|
||||
}> = ({ multiColumn }) => {
|
||||
const intl = useIntl();
|
||||
const dispatch = useAppDispatch();
|
||||
const accountId = useCurrentAccountId();
|
||||
const { accountId, signedIn } = useIdentity();
|
||||
const { id = null } = useParams<{ id?: string }>();
|
||||
const { path } = useRouteMatch();
|
||||
const collection = useAppSelector((state) =>
|
||||
@ -94,13 +95,13 @@ export const CollectionEditorPage: React.FC<{
|
||||
(!isEditMode && collectionListStatus === 'loading');
|
||||
|
||||
const canCreateMoreCollections =
|
||||
isEditMode || collectionList.length < userCollectionLimit;
|
||||
signedIn && (isEditMode || collectionList.length < userCollectionLimit);
|
||||
|
||||
useEffect(() => {
|
||||
if (id) {
|
||||
if (id && signedIn) {
|
||||
void dispatch(fetchCollection({ collectionId: id }));
|
||||
}
|
||||
}, [dispatch, id]);
|
||||
}, [dispatch, id, signedIn]);
|
||||
|
||||
useEffect(() => {
|
||||
if (id !== editorStateId) {
|
||||
@ -129,6 +130,8 @@ export const CollectionEditorPage: React.FC<{
|
||||
<div className='scrollable'>
|
||||
{isLoading ? (
|
||||
<LoadingIndicator />
|
||||
) : !signedIn ? (
|
||||
<NotSignedInIndicator />
|
||||
) : canCreateMoreCollections ? (
|
||||
<Switch>
|
||||
<Route
|
||||
|
||||
@ -4,15 +4,15 @@ import { Route, Switch, useRouteMatch } from 'react-router-dom';
|
||||
|
||||
import { Helmet } from '@unhead/react/helmet';
|
||||
|
||||
import { TabLink, TabList } from '@/mastodon/components/tab_list';
|
||||
import { Column } from 'mastodon/components/column';
|
||||
import { ColumnHeader } from 'mastodon/components/column_header';
|
||||
import { DisplayNameSimple } from 'mastodon/components/display_name/simple';
|
||||
import { Scrollable } from 'mastodon/components/scrollable_list/components';
|
||||
import { TabLink, TabList } from 'mastodon/components/tab_list';
|
||||
import { useAccount } from 'mastodon/hooks/useAccount';
|
||||
import { useAccountId, useCurrentAccountId } from 'mastodon/hooks/useAccountId';
|
||||
|
||||
import { CollectionsCreatedByYou } from './overview/created_by_you';
|
||||
import { CollectionsCreatedByAccount } from './overview/created_by_account';
|
||||
import { CollectionsFeaturingYou } from './overview/featuring_you';
|
||||
import classes from './styles.module.scss';
|
||||
|
||||
@ -89,7 +89,7 @@ export const Collections: React.FC<{
|
||||
</TabList>
|
||||
</header>
|
||||
<Switch>
|
||||
<Route exact path={path} component={CollectionsCreatedByYou} />
|
||||
<Route exact path={path} component={CollectionsCreatedByAccount} />
|
||||
<Route
|
||||
exact
|
||||
path={`${path}/featuring-you`}
|
||||
|
||||
@ -5,10 +5,12 @@ import { FormattedMessage } from 'react-intl';
|
||||
import { Link } from 'react-router-dom';
|
||||
|
||||
import AddIcon from '@/material-icons/400-24px/add.svg?react';
|
||||
import { DisplayName } from 'mastodon/components/display_name';
|
||||
import { EmptyState } from 'mastodon/components/empty_state';
|
||||
import { Icon } from 'mastodon/components/icon';
|
||||
import { LoadingIndicator } from 'mastodon/components/loading_indicator';
|
||||
import { ItemList } from 'mastodon/components/scrollable_list/components';
|
||||
import { useAccount } from 'mastodon/hooks/useAccount';
|
||||
import { useAccountId, useCurrentAccountId } from 'mastodon/hooks/useAccountId';
|
||||
import {
|
||||
fetchCollectionsCreatedByAccount,
|
||||
@ -58,9 +60,10 @@ export function useCollectionsCreatedBy(accountId: string | null | undefined) {
|
||||
);
|
||||
}
|
||||
|
||||
export const CollectionsCreatedByYou: React.FC = () => {
|
||||
export const CollectionsCreatedByAccount: React.FC = () => {
|
||||
const me = useCurrentAccountId();
|
||||
const accountId = useAccountId();
|
||||
const account = useAccount(accountId);
|
||||
|
||||
const { collections, status } = useCollectionsCreatedBy(accountId);
|
||||
|
||||
@ -78,24 +81,40 @@ export const CollectionsCreatedByYou: React.FC = () => {
|
||||
}
|
||||
|
||||
if (collections.length === 0) {
|
||||
return (
|
||||
<EmptyState
|
||||
title={
|
||||
<FormattedMessage
|
||||
id='empty_column.account_featured_self.showcase_accounts'
|
||||
defaultMessage='Showcase your favorite accounts'
|
||||
/>
|
||||
}
|
||||
message={
|
||||
<FormattedMessage
|
||||
id='empty_column.account_featured_self.showcase_accounts_desc'
|
||||
defaultMessage='Collections are curated lists of accounts to help others discover more of the Fediverse.'
|
||||
/>
|
||||
}
|
||||
>
|
||||
<CreateButton />
|
||||
</EmptyState>
|
||||
);
|
||||
if (isOwnCollectionPage) {
|
||||
return (
|
||||
<EmptyState
|
||||
title={
|
||||
<FormattedMessage
|
||||
id='empty_column.account_featured_self.showcase_accounts'
|
||||
defaultMessage='Showcase your favorite accounts'
|
||||
/>
|
||||
}
|
||||
message={
|
||||
<FormattedMessage
|
||||
id='empty_column.account_featured_self.showcase_accounts_desc'
|
||||
defaultMessage='Collections are curated lists of accounts to help others discover more of the Fediverse.'
|
||||
/>
|
||||
}
|
||||
>
|
||||
<CreateButton />
|
||||
</EmptyState>
|
||||
);
|
||||
} else {
|
||||
return (
|
||||
<EmptyState
|
||||
title={
|
||||
<FormattedMessage
|
||||
id='empty_column.collections'
|
||||
defaultMessage='{acct} has not created any collections yet.'
|
||||
values={{
|
||||
acct: <DisplayName variant='simple' account={account} />,
|
||||
}}
|
||||
/>
|
||||
}
|
||||
/>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
return (
|
||||
@ -113,7 +132,7 @@ export const CollectionsCreatedByYou: React.FC = () => {
|
||||
{showCreateButton && <CreateButton />}
|
||||
</div>
|
||||
<ItemList>
|
||||
{!canCreateMoreCollections && (
|
||||
{isOwnCollectionPage && !canCreateMoreCollections && (
|
||||
<MaxCollectionsCallout className={classes.maxCollectionsError} />
|
||||
)}
|
||||
{collections.map((item, index) => (
|
||||
@ -16,7 +16,7 @@ import { useAppSelector, useAppDispatch } from 'mastodon/store';
|
||||
import { CollectionListItem } from '../components/collection_list_item';
|
||||
import classes from '../styles.module.scss';
|
||||
|
||||
import { CollectionListError } from './created_by_you';
|
||||
import { CollectionListError } from './created_by_account';
|
||||
|
||||
function useCollectionsFeaturing(accountId: string | null | undefined) {
|
||||
const dispatch = useAppDispatch();
|
||||
|
||||
@ -6,6 +6,8 @@ import { Link } from 'react-router-dom';
|
||||
|
||||
import { Helmet } from '@unhead/react/helmet';
|
||||
|
||||
import { NotSignedInIndicator } from '@/mastodon/components/not_signed_in_indicator';
|
||||
import { useIdentity } from '@/mastodon/identity_context';
|
||||
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';
|
||||
@ -78,10 +80,13 @@ const Lists: React.FC<{
|
||||
const dispatch = useAppDispatch();
|
||||
const intl = useIntl();
|
||||
const lists = useAppSelector((state) => getOrderedLists(state));
|
||||
const { signedIn } = useIdentity();
|
||||
|
||||
useEffect(() => {
|
||||
void dispatch(fetchLists());
|
||||
}, [dispatch]);
|
||||
if (signedIn) {
|
||||
void dispatch(fetchLists());
|
||||
}
|
||||
}, [signedIn, dispatch]);
|
||||
|
||||
const emptyMessage = (
|
||||
<>
|
||||
@ -112,14 +117,16 @@ const Lists: React.FC<{
|
||||
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>
|
||||
signedIn && (
|
||||
<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>
|
||||
)
|
||||
}
|
||||
/>
|
||||
|
||||
@ -128,9 +135,13 @@ const Lists: React.FC<{
|
||||
emptyMessage={emptyMessage}
|
||||
bindToDocument={!multiColumn}
|
||||
>
|
||||
{lists.map((list) => (
|
||||
<ListItem key={list.id} id={list.id} title={list.title} />
|
||||
))}
|
||||
{signedIn ? (
|
||||
lists.map((list) => (
|
||||
<ListItem key={list.id} id={list.id} title={list.title} />
|
||||
))
|
||||
) : (
|
||||
<NotSignedInIndicator />
|
||||
)}
|
||||
</ScrollableList>
|
||||
|
||||
<Helmet>
|
||||
|
||||
@ -8,6 +8,8 @@ import { isFulfilled } from '@reduxjs/toolkit';
|
||||
|
||||
import { Helmet } from '@unhead/react/helmet';
|
||||
|
||||
import { NotSignedInIndicator } from '@/mastodon/components/not_signed_in_indicator';
|
||||
import { useIdentity } from '@/mastodon/identity_context';
|
||||
import ChevronRightIcon from '@/material-icons/400-24px/chevron_right.svg?react';
|
||||
import ListAltIcon from '@/material-icons/400-24px/list_alt.svg?react';
|
||||
import { fetchList } from 'mastodon/actions/lists';
|
||||
@ -250,16 +252,17 @@ const NewListWrapper: React.FC<{
|
||||
}> = ({ multiColumn }) => {
|
||||
const intl = useIntl();
|
||||
const dispatch = useAppDispatch();
|
||||
const { signedIn } = useIdentity();
|
||||
const { id } = useParams<{ id?: string }>();
|
||||
const list = useAppSelector((state) =>
|
||||
id ? state.lists.get(id) : undefined,
|
||||
);
|
||||
|
||||
useEffect(() => {
|
||||
if (id) {
|
||||
if (signedIn && id) {
|
||||
dispatch(fetchList(id));
|
||||
}
|
||||
}, [dispatch, id]);
|
||||
}, [dispatch, signedIn, id]);
|
||||
|
||||
const isLoading = id && !list;
|
||||
|
||||
@ -277,7 +280,13 @@ const NewListWrapper: React.FC<{
|
||||
/>
|
||||
|
||||
<div className='scrollable'>
|
||||
{isLoading ? <LoadingIndicator /> : <NewList list={list} />}
|
||||
{!signedIn ? (
|
||||
<NotSignedInIndicator />
|
||||
) : isLoading ? (
|
||||
<LoadingIndicator />
|
||||
) : (
|
||||
<NewList list={list} />
|
||||
)}
|
||||
</div>
|
||||
|
||||
<Helmet>
|
||||
|
||||
@ -4,6 +4,7 @@ import { useIntl, defineMessages, FormattedMessage } from 'react-intl';
|
||||
|
||||
import { Helmet } from '@unhead/react/helmet';
|
||||
|
||||
import CollectionsIcon from '@/material-icons/400-24px/category.svg?react';
|
||||
import FindInPageIcon from '@/material-icons/400-24px/find_in_page.svg?react';
|
||||
import PeopleIcon from '@/material-icons/400-24px/group.svg?react';
|
||||
import SearchIcon from '@/material-icons/400-24px/search.svg?react';
|
||||
@ -23,6 +24,8 @@ import { useSearchParam } from 'mastodon/hooks/useSearchParam';
|
||||
import type { Hashtag as HashtagType } from 'mastodon/models/tags';
|
||||
import { useAppDispatch, useAppSelector } from 'mastodon/store';
|
||||
|
||||
import { CollectionListItem } from '../collections/components/collection_list_item';
|
||||
|
||||
import { SearchSection } from './components/search_section';
|
||||
|
||||
const messages = defineMessages({
|
||||
@ -131,7 +134,8 @@ export const SearchResults: React.FC<{ multiColumn: boolean }> = ({
|
||||
filteredResults =
|
||||
results.accounts.length +
|
||||
results.hashtags.length +
|
||||
results.statuses.length >
|
||||
results.statuses.length +
|
||||
results.collections.length >
|
||||
0 ? (
|
||||
<>
|
||||
{results.accounts.length > 0 && (
|
||||
@ -154,6 +158,32 @@ export const SearchResults: React.FC<{ multiColumn: boolean }> = ({
|
||||
</SearchSection>
|
||||
)}
|
||||
|
||||
{results.collections.length > 0 && (
|
||||
<SearchSection
|
||||
key='collections'
|
||||
title={
|
||||
<>
|
||||
<Icon id='collections' icon={CollectionsIcon} />
|
||||
<FormattedMessage
|
||||
id='search_results.collections'
|
||||
defaultMessage='Collections'
|
||||
/>
|
||||
</>
|
||||
}
|
||||
>
|
||||
{results.collections
|
||||
.slice(0, INITIAL_DISPLAY)
|
||||
.map((collection, index, array) => (
|
||||
<CollectionListItem
|
||||
key={collection.id}
|
||||
collection={collection}
|
||||
listSize={array.length}
|
||||
positionInList={index + 1}
|
||||
/>
|
||||
))}
|
||||
</SearchSection>
|
||||
)}
|
||||
|
||||
{results.hashtags.length > 0 && (
|
||||
<SearchSection
|
||||
key='hashtags'
|
||||
|
||||
@ -86,7 +86,7 @@ import {
|
||||
Quotes,
|
||||
} from './util/async-components';
|
||||
import { ColumnsContextProvider } from './util/columns_context';
|
||||
import { focusColumn, getFocusedItemIndex, focusItemSibling, focusFirstItem } from './util/focusUtils';
|
||||
import { focusColumn, getFocusedItemIndex, focusItemSibling, focusFirstItem, getFocusedColumnIndex } from './util/focusUtils';
|
||||
import { WrappedSwitch, WrappedRoute } from './util/react_router_helpers';
|
||||
import { CustomHomepage } from 'mastodon/features/custom_homepage';
|
||||
|
||||
@ -506,20 +506,18 @@ class UI extends PureComponent {
|
||||
handleMoveUp = () => {
|
||||
const currentItemIndex = getFocusedItemIndex();
|
||||
if (currentItemIndex === -1) {
|
||||
focusColumn(1);
|
||||
return focusColumn(getFocusedColumnIndex());
|
||||
} else {
|
||||
const wasHandled = focusItemSibling(currentItemIndex, -1);
|
||||
return wasHandled;
|
||||
return focusItemSibling(currentItemIndex, -1);
|
||||
}
|
||||
};
|
||||
|
||||
handleMoveDown = () => {
|
||||
const currentItemIndex = getFocusedItemIndex();
|
||||
if (currentItemIndex === -1) {
|
||||
focusColumn(1);
|
||||
return focusColumn(getFocusedColumnIndex());
|
||||
} else {
|
||||
const wasHandled = focusItemSibling(currentItemIndex, 1);
|
||||
return wasHandled;
|
||||
return focusItemSibling(currentItemIndex, 1);
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
@ -62,18 +62,17 @@ export function focusColumn(index = 1) {
|
||||
|
||||
function fallback() {
|
||||
focusColumnTitle(index + indexOffset, isMultiColumnLayout);
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!column) {
|
||||
fallback();
|
||||
return;
|
||||
return fallback();
|
||||
}
|
||||
|
||||
const container = column.querySelector('.scrollable');
|
||||
|
||||
if (!container) {
|
||||
fallback();
|
||||
return;
|
||||
return fallback();
|
||||
}
|
||||
|
||||
const focusableItems = Array.from(
|
||||
@ -86,8 +85,7 @@ export function focusColumn(index = 1) {
|
||||
const itemToFocus = findFirstVisibleWithRect(focusableItems);
|
||||
|
||||
if (!itemToFocus) {
|
||||
fallback();
|
||||
return;
|
||||
return fallback();
|
||||
}
|
||||
|
||||
const viewportWidth =
|
||||
@ -110,6 +108,7 @@ export function focusColumn(index = 1) {
|
||||
itemToFocus.item.scrollIntoView(true);
|
||||
}
|
||||
itemToFocus.item.focus();
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
@ -126,6 +125,18 @@ export function getFocusedItemIndex() {
|
||||
return items.indexOf(focusedItem);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the index of the column that contains the user's focus
|
||||
*/
|
||||
export function getFocusedColumnIndex() {
|
||||
const columnWithFocus = document.activeElement?.closest('.column');
|
||||
|
||||
if (!columnWithFocus) return 1;
|
||||
|
||||
const allColumns = Array.from(document.querySelectorAll('.column'));
|
||||
return allColumns.indexOf(columnWithFocus) + 1;
|
||||
}
|
||||
|
||||
/**
|
||||
* Focus the topmost item of the column that currently has focus,
|
||||
* or the first column if none
|
||||
@ -159,12 +170,7 @@ export function focusItemSibling(index: number, direction: 1 | -1) {
|
||||
);
|
||||
|
||||
if (!siblingItem) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// If sibling element is empty, we skip it
|
||||
if (siblingItem.matches(':empty')) {
|
||||
return focusItemSibling(index + direction, direction);
|
||||
return focusListSibling(direction);
|
||||
}
|
||||
|
||||
// Check if the sibling is a post or a 'follow suggestions' widget
|
||||
@ -177,6 +183,52 @@ export function focusItemSibling(index: number, direction: 1 | -1) {
|
||||
targetElement = siblingItem;
|
||||
}
|
||||
|
||||
// If sibling element is empty, we skip it
|
||||
if (!targetElement || siblingItem.matches(':empty')) {
|
||||
return focusItemSibling(index + direction, direction);
|
||||
}
|
||||
|
||||
targetElement.scrollIntoView({
|
||||
block: 'start',
|
||||
});
|
||||
|
||||
targetElement.focus();
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Finds the next or previous .item-list on the page or in the column,
|
||||
* and focuses its first or last item.
|
||||
*/
|
||||
function focusListSibling(direction: 1 | -1) {
|
||||
const container = document.activeElement?.closest('.item-list');
|
||||
|
||||
if (!container) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Get all item lists in the current column or page
|
||||
const currentColumn = container.closest('.column') ?? document;
|
||||
|
||||
const columnItemLists = Array.from(
|
||||
currentColumn.querySelectorAll('.item-list'),
|
||||
);
|
||||
const currentListIndex = columnItemLists.indexOf(container);
|
||||
|
||||
// Find the next or previous item-list
|
||||
const listSibling = columnItemLists[currentListIndex + direction];
|
||||
|
||||
// Depending on the direction, find the first or last focusable in the list
|
||||
let targetElement: HTMLElement | null | undefined;
|
||||
if (direction > 0) {
|
||||
targetElement = listSibling?.querySelector<HTMLElement>('.focusable');
|
||||
} else {
|
||||
const allFocusables =
|
||||
listSibling?.querySelectorAll<HTMLElement>('.focusable');
|
||||
targetElement = allFocusables?.[allFocusables.length - 1];
|
||||
}
|
||||
|
||||
if (targetElement) {
|
||||
targetElement.scrollIntoView({
|
||||
block: 'start',
|
||||
|
||||
@ -51,7 +51,7 @@ export function useSearchAccounts({
|
||||
searchRequestRef.current = new AbortController();
|
||||
|
||||
try {
|
||||
const data = await apiRequest<ApiAccountJSON[]>(
|
||||
const accounts = await apiRequest<ApiAccountJSON[]>(
|
||||
'GET',
|
||||
'v1/accounts/search',
|
||||
{
|
||||
@ -62,7 +62,6 @@ export function useSearchAccounts({
|
||||
},
|
||||
},
|
||||
);
|
||||
const accounts = filterResults ? data.filter(filterResults) : data;
|
||||
const accountIds = accounts.map((a) => a.id);
|
||||
dispatch(importFetchedAccounts(accounts));
|
||||
if (withRelationships) {
|
||||
@ -95,6 +94,7 @@ export function useSearchAccounts({
|
||||
const [defaultAccounts, setDefaultAccounts] = useState<
|
||||
ApiAccountJSON[] | null
|
||||
>(null);
|
||||
|
||||
useEffect(() => {
|
||||
if (
|
||||
!currentUserId ||
|
||||
@ -108,12 +108,11 @@ export function useSearchAccounts({
|
||||
async function doRequest() {
|
||||
setLoadingState('loading');
|
||||
try {
|
||||
const data = await apiRequest<ApiAccountJSON[]>(
|
||||
const accounts = await apiRequest<ApiAccountJSON[]>(
|
||||
'GET',
|
||||
`v1/accounts/${currentUserId}/following`,
|
||||
{ params: { limit: 40 } },
|
||||
);
|
||||
const accounts = filterResults ? data.filter(filterResults) : data;
|
||||
const accountIds = accounts.map((a) => a.id);
|
||||
dispatch(importFetchedAccounts(accounts));
|
||||
if (withRelationships) {
|
||||
@ -137,13 +136,19 @@ export function useSearchAccounts({
|
||||
withDefaultFollows,
|
||||
]);
|
||||
|
||||
const accountsToReturn =
|
||||
accounts.length === 0 && withDefaultFollows
|
||||
? (defaultAccounts ?? [])
|
||||
: accounts;
|
||||
|
||||
const filteredAccounts = filterResults
|
||||
? accountsToReturn.filter(filterResults)
|
||||
: accountsToReturn;
|
||||
|
||||
return {
|
||||
searchAccounts: startSearch,
|
||||
resetAccounts,
|
||||
accounts:
|
||||
accounts.length === 0 && withDefaultFollows
|
||||
? (defaultAccounts ?? [])
|
||||
: accounts,
|
||||
accounts: filteredAccounts,
|
||||
isLoading: loadingState === 'loading',
|
||||
isError: loadingState === 'error',
|
||||
};
|
||||
|
||||
@ -2,7 +2,6 @@
|
||||
"about.blocks": "Gemodereerde bedieners",
|
||||
"about.contact": "Kontak:",
|
||||
"about.default_locale": "Verstek",
|
||||
"about.disclaimer": "Mastodon is gratis oopbronsagteware en ’n handelsmerk van Mastodon gGmbH.",
|
||||
"about.domain_blocks.no_reason_available": "Rede nie beskikbaar nie",
|
||||
"about.domain_blocks.preamble": "Mastodon generally allows you to view content from and interact with users from any other server in the fediverse. These are the exceptions that have been made on this particular server.",
|
||||
"about.domain_blocks.silenced.title": "Beperk",
|
||||
@ -200,7 +199,6 @@
|
||||
"lightbox.next": "Volgende",
|
||||
"lightbox.previous": "Vorige",
|
||||
"limited_account_hint.action": "Vertoon profiel in elk geval",
|
||||
"limited_account_hint.title": "Hierdie profiel is deur moderators van {domain} versteek.",
|
||||
"link_preview.author": "Deur {name}",
|
||||
"lists.delete": "Verwyder lys",
|
||||
"lists.edit": "Redigeer lys",
|
||||
|
||||
@ -1,7 +1,6 @@
|
||||
{
|
||||
"about.blocks": "Servidors moderaus",
|
||||
"about.contact": "Contacto:",
|
||||
"about.disclaimer": "Mastodon ye software de codigo ubierto, y una marca comercial de Mastodon gGmbH.",
|
||||
"about.domain_blocks.no_reason_available": "Razón no disponible",
|
||||
"about.domain_blocks.preamble": "Mastodon normalment te permite veyer lo conteniu y interactuar con os usuarios de qualsequier atro servidor en o fediverso. Estas son las excepcions que s'han feito en este servidor en particular.",
|
||||
"about.domain_blocks.silenced.explanation": "Normalment no veyerás perfils y conteniu d'este servidor, de no estar que lo busques explicitament u sigas bella cuenta.",
|
||||
@ -264,7 +263,6 @@
|
||||
"lightbox.next": "Siguient",
|
||||
"lightbox.previous": "Anterior",
|
||||
"limited_account_hint.action": "Amostrar perfil de totz modos",
|
||||
"limited_account_hint.title": "Este perfil ha estau amagau per los moderadors de {domain}.",
|
||||
"lists.delete": "Borrar lista",
|
||||
"lists.edit": "Editar lista",
|
||||
"lists.replies_policy.followed": "Qualsequier usuario seguiu",
|
||||
|
||||
@ -2,7 +2,6 @@
|
||||
"about.blocks": "خوادم تحت الإشراف",
|
||||
"about.contact": "للاتصال:",
|
||||
"about.default_locale": "افتراضي",
|
||||
"about.disclaimer": "ماستدون برنامج حر ومفتوح المصدر وعلامة تجارية لـ Mastodon GmbH.",
|
||||
"about.domain_blocks.no_reason_available": "السبب غير متوفر",
|
||||
"about.domain_blocks.preamble": "يتيح مَستُدون عمومًا لمستخدميه مطالعة المحتوى من المستخدمين من الخواديم الأخرى في الفدرالية والتفاعل معهم. وهذه هي الاستثناءات التي وضعت على هذا الخادوم.",
|
||||
"about.domain_blocks.silenced.explanation": "لن تظهر لك ملفات التعريف الشخصية والمحتوى من هذا الخادوم، إلا إن بحثت عنه عمدًا أو تابعته.",
|
||||
@ -773,7 +772,6 @@
|
||||
"lightbox.zoom_in": "التكبير إلى الحجم الفعلي",
|
||||
"lightbox.zoom_out": "التكبير ليناسب الحجم",
|
||||
"limited_account_hint.action": "إظهار الملف التعريفي على أي حال",
|
||||
"limited_account_hint.title": "تم إخفاء هذا الملف الشخصي من قبل مشرفي {domain}.",
|
||||
"link_preview.author": "مِن {name}",
|
||||
"link_preview.more_from_author": "المزيد من {name}",
|
||||
"link_preview.shares": "{count, plural, zero {{counter} منشور}one {{counter} منشور} two {{counter} منشور} few {{counter} منشور} many {{counter} منشور} other {{counter} منشور}}",
|
||||
|
||||
@ -1,7 +1,6 @@
|
||||
{
|
||||
"about.blocks": "Sirvidores moderaos",
|
||||
"about.contact": "Contautu:",
|
||||
"about.disclaimer": "Mastodon ye software gratuito y de códigu llibre, y una marca rexistrada de Mastodon gGmbH.",
|
||||
"about.domain_blocks.no_reason_available": "El motivu nun ta disponible",
|
||||
"about.domain_blocks.preamble": "Polo xeneral, Mastodon permítete ver el conteníu ya interactuar colos perfiles d'otros sirvidores nel fediversu. Estes son les esceiciones que se ficieron nesti sirvidor.",
|
||||
"about.domain_blocks.silenced.explanation": "Polo xeneral, nun ves los perfiles y el conteníu d'esti sirvidor sacante que los busques o decidas siguilos.",
|
||||
|
||||
@ -2,7 +2,7 @@
|
||||
"about.blocks": "Moderasiya edilmiş serverlər",
|
||||
"about.contact": "Əlaqə:",
|
||||
"about.default_locale": "İlkin",
|
||||
"about.disclaimer": "Mastodon ödənişsiz, açıq-mənbəli yazılımdır və Mastodon gGmbH-nin əmtəə nişanıdır.",
|
||||
"about.disclaimer": "Mastodon ödənişsiz, açıq-mənbəli yazılımdır və Mastodon GmbH-nin əmtəə nişanıdır.",
|
||||
"about.domain_blocks.no_reason_available": "Səbəb mövcud deyil",
|
||||
"about.domain_blocks.preamble": "Mastodon, adətən fediverse-dəki hər hansısa bir serverdən məzmuna baxmağınıza və istifadəçilərlə qarşılıqlı əlaqədə olmağınıza imkanı verir. Bunlar, bu serverdə edilmiş istisnalardır.",
|
||||
"about.domain_blocks.silenced.explanation": "Siz bu serverdəki profilləri və məzmunu xüsusi olaraq axtarmasanız və ya izləməsəniz ümumiyyətlə görməyəcəksiniz.",
|
||||
@ -86,6 +86,7 @@
|
||||
"account.locked_info": "Bu hesabın məxfilik statusu kilidlənib. Sahibi, onu kimin izləyə biləcəyini manual olaraq incələyir.",
|
||||
"account.media": "Media",
|
||||
"account.mention": "@{name} istifadəçisini teq et",
|
||||
"account.menu.add_to_collection": "Kolleksiyaya əlavə et…",
|
||||
"account.menu.add_to_list": "Siyahıya əlavə et…",
|
||||
"account.menu.block": "Hesabı əngəllə",
|
||||
"account.menu.block_domain": "{domain} - əngəllə",
|
||||
@ -102,14 +103,26 @@
|
||||
"account.menu.share": "Paylaş…",
|
||||
"account.menu.show_reblogs": "Zaman xəttində təkrar paylaşmaları göstər",
|
||||
"account.menu.unblock": "Hesabın əngəlini götür",
|
||||
"account.menu.unblock_domain": "{domain} - əngəli götür",
|
||||
"account.menu.unmute": "Hesabın səsini aç",
|
||||
"account.moved_to": "{name} onun yeni hesabının artıq bu olduğunu bildirdi:",
|
||||
"account.mute": "@{name} istifadəçisini susdur",
|
||||
"account.mute_notifications_short": "Bildirişləri səssizləşdir",
|
||||
"account.mute_short": "Səssizləşdir",
|
||||
"account.muted": "Səssizləşdirilib",
|
||||
"account.mutual": "Bir-birinizi izləyirsiniz",
|
||||
"account.name.help.domain": "{domain}, istifadəçinin profil və göndərişlərinin yerləşdiyi serverdir.",
|
||||
"account.name.help.domain_self": "{domain}, profil və göndərişlərinizin yerləşdiyi serverdir.",
|
||||
"account.name.help.footer": "Necə ki, fərqli e-poçt provayderlərini istifadə edən insanlara e-poçt göndərə bilirsiniz, eynilə digər Mastodon serverlərindəki insanlarla və digər Fediverse tətbiqlərindəki hər kəslə qarşılıqlı əlaqə qura bilərsiniz.",
|
||||
"account.name.help.username": "{username} bu hesabın öz serverindəki istifadəçi adıdır. Başqa bir serverdəki başqa biri də eyni istifadəçi adına sahib ola bilər.",
|
||||
"account.name.help.username_self": "{username} sizin bu serverdəki istifadəçi adınızdır. Başqa bir serverdəki başqa biri də eyni istifadəçi adına sahib ola bilər.",
|
||||
"account.name_info": "Bu nə deməkdir?",
|
||||
"account.no_bio": "Təsvir göstərilməyib.",
|
||||
"account.node_modal.error_unknown": "Not saxlanıla bilmədi",
|
||||
"account.node_modal.save": "Saxla",
|
||||
"account.note.edit_button": "Düzəliş",
|
||||
"account.open_original_page": "Orijinal səhifəni aç",
|
||||
"account.pending": "Gözləmədə",
|
||||
"account.posts": "Paylaşım",
|
||||
"account.remove_from_followers": "{name} - izləyicilərdən çıxart",
|
||||
"account.report": "@{name} istifadəçisini şikayət et",
|
||||
@ -117,6 +130,8 @@
|
||||
"account.share": "@{name} profilini paylaş",
|
||||
"account.show_reblogs": "@{name} - təkrar paylaşımlarını göstər",
|
||||
"account.statuses_counter": "{count, plural, one {{counter} paylaşım} other {{counter} paylaşım}}",
|
||||
"account.timeline.pinned": "Sancıldı",
|
||||
"account.timeline.pinned.view_all": "Bütün sancılmış göndərişlərə bax",
|
||||
"account.unblock": "@{name} blokunu aç",
|
||||
"account.unblock_domain": "{domain} domeninin blokunu aç",
|
||||
"account.unblock_domain_short": "Əngəldən çıxart",
|
||||
@ -126,7 +141,33 @@
|
||||
"account.unmute": "@{name} səssizləşdirmədən çıxart",
|
||||
"account.unmute_notifications_short": "Bildirişlərin səsini aç",
|
||||
"account.unmute_short": "Səssizləşdirmədən çıxart",
|
||||
"account_edit.advanced_settings.bot_label": "Avtomatlaşdırılmış hesab",
|
||||
"account_edit.advanced_settings.title": "Qabaqcıl ayarlar",
|
||||
"account_edit.bio.add_label": "Bio əlavə et",
|
||||
"account_edit.bio.edit_label": "Bioya düzəliş",
|
||||
"account_edit.bio.placeholder": "Başqalarının sizi tanımasına kömək edəcək qısa bir açıqlama əlavə edin.",
|
||||
"account_edit.bio.title": "Bio",
|
||||
"account_edit.bio_modal.add_title": "Bio əlavə et",
|
||||
"account_edit.bio_modal.edit_title": "Bioya düzəliş",
|
||||
"account_edit.column_button": "Hazırdır",
|
||||
"account_edit.column_title": "Profilə düzəliş",
|
||||
"account_edit.custom_fields.add_label": "Xana əlavə et",
|
||||
"account_edit.custom_fields.edit_label": "Xanaya düzəliş",
|
||||
"account_edit.custom_fields.placeholder": "Müraciət formalarını, xarici keçidləri və ya paylaşmaq istədiyiniz digər məlumatları əlavə edin.",
|
||||
"account_edit.custom_fields.reorder_button": "Xanaları təkrar sırala",
|
||||
"account_edit.custom_fields.tip_content": "Sahibi olduğunuz veb saytlara keçidləri doğrulayaraq Mastodon hesabınıza asanlıqla etibarlılıq qazandıra bilərsiniz.",
|
||||
"account_edit.custom_fields.tip_title": "İpucu: Doğrulanmış keçidləri əlavə etmə",
|
||||
"account_edit.custom_fields.title": "Özəl xanalar",
|
||||
"account_edit.custom_fields.verified_hint": "Doğrulanmış keçid necə əlavə olunur?",
|
||||
"account_edit.display_name.add_label": "Ekran adı əlavə et",
|
||||
"account_edit.display_name.edit_label": "Ekran adına düzəliş",
|
||||
"account_edit.display_name.placeholder": "Ekran adınız, profilinizdə və zaman xəttində adınızın görünmə formasıdır.",
|
||||
"account_edit.display_name.title": "Ekran adı",
|
||||
"account_edit.featured_hashtags.edit_label": "Mövzu etiketi əlavə et",
|
||||
"account_edit.featured_hashtags.placeholder": "Başqalarının sevimli mövzularınızı tanımasına və onlara cəld erişməsinə kömək edin.",
|
||||
"account_edit.featured_hashtags.title": "Seçilmiş mövzu etiketləri",
|
||||
"account_edit.field_actions.delete": "Xananı sil",
|
||||
"account_edit.field_actions.edit": "Xanaya düzəliş",
|
||||
"account_edit.field_delete_modal.title": "Özəl xananı sil?",
|
||||
"account_edit.field_edit_modal.add_title": "Özəl xana əlavə et",
|
||||
"account_edit.field_edit_modal.discard_confirm": "İmtina",
|
||||
@ -505,7 +546,6 @@
|
||||
"lightbox.zoom_in": "Həqiqi ölçüyə qayıt",
|
||||
"lightbox.zoom_out": "Sığacaq şəkildə yaxınlaşdır",
|
||||
"limited_account_hint.action": "Yenə də profili göstər",
|
||||
"limited_account_hint.title": "Bu profil, {domain} moderatorları tərəfindən gizlədildi.",
|
||||
"link_preview.author": "Müəllif: {name}",
|
||||
"link_preview.more_from_author": "{name} - daha çox",
|
||||
"link_preview.shares": "{count, plural, one {{counter} göndəriş} other {{counter} göndəriş}}",
|
||||
|
||||
@ -2,7 +2,7 @@
|
||||
"about.blocks": "Мадэраваныя серверы",
|
||||
"about.contact": "Кантакт:",
|
||||
"about.default_locale": "Прадвызначаная",
|
||||
"about.disclaimer": "Mastodon - свабоднае праграмнае забеспячэнне, з адкрытым зыходным кодам, і гандлёвай маркай Mastodon gGmbH.",
|
||||
"about.disclaimer": "Mastodon - свабоднае праграмнае забеспячэнне, з адкрытым зыходным кодам, і гандлёвай маркай Mastodon GmbH.",
|
||||
"about.domain_blocks.no_reason_available": "Прычына недаступная",
|
||||
"about.domain_blocks.preamble": "Mastodon, у асноўным, дазваляе вам праглядаць кантэнт і ўзаемадзейнічаць з карыстальнікамі з іншых сервераў у федэсвету. Гэтыя выключэнні былі зроблены дакладна на гэтым серверы.",
|
||||
"about.domain_blocks.silenced.explanation": "Вы не будзеце бачыць профілі і змесціва гэтага серверу, калі не шукаеце іх мэтанакіравана ці не падпісаны на карыстальнікаў адтуль.",
|
||||
@ -646,6 +646,7 @@
|
||||
"empty_column.account_unavailable": "Профіль недаступны",
|
||||
"empty_column.blocks": "Вы яшчэ нікога не заблакіравалі.",
|
||||
"empty_column.bookmarked_statuses": "У Вашых закладках яшчэ няма допісаў. Калі Вы дадасце закладку, яна з’явіцца тут.",
|
||||
"empty_column.collections": "{acct} пакуль не стварыў(-ла) аніводнай калекцыі.",
|
||||
"empty_column.collections.featured_in": "Вас пакуль не дадалі ў ніякія калекцыі.",
|
||||
"empty_column.collections.featured_in_undiscoverable": "Каб людзі маглі дадаваць Вас у калекцыі, Вам трэба даць ім дазвол знаходзіць Вас у <link>Налады > Прыватнасць і пошук</link>",
|
||||
"empty_column.community": "Мясцовая стужка пустая. Напішыце нешта публічнае, каб разварушыць справу!",
|
||||
@ -819,6 +820,12 @@
|
||||
"keyboard_shortcuts.heading": "Спалучэнні клавіш",
|
||||
"keyboard_shortcuts.home": "Адкрыць хатнюю храналагічную стужку",
|
||||
"keyboard_shortcuts.hotkey": "Спалучэнне клавіш",
|
||||
"keyboard_shortcuts.keys.alt": "Alt",
|
||||
"keyboard_shortcuts.keys.backspace": "Backspace",
|
||||
"keyboard_shortcuts.keys.enter": "Enter",
|
||||
"keyboard_shortcuts.keys.esc": "Esc",
|
||||
"keyboard_shortcuts.keys.page_down": "Page Down",
|
||||
"keyboard_shortcuts.keys.page_up": "Page Up",
|
||||
"keyboard_shortcuts.legend": "Паказаць легенду",
|
||||
"keyboard_shortcuts.load_more": "Навесці на кнопку \"Загрузіць болей\"",
|
||||
"keyboard_shortcuts.local": "Адкрыць хатнюю храналагічную стужку",
|
||||
@ -850,7 +857,7 @@
|
||||
"lightbox.zoom_in": "Маштабаваць да фактычнага памеру",
|
||||
"lightbox.zoom_out": "Дапасаваць усё змесціва пад памеры экрана",
|
||||
"limited_account_hint.action": "Усе роўна паказваць профіль",
|
||||
"limited_account_hint.title": "Гэты профіль быў схаваны мадэратарамі {domain}.",
|
||||
"limited_account_hint.title": "Гэты профіль або сервер быў схаваны мадэратарамі {domain}.",
|
||||
"link_preview.author": "Ад {name}",
|
||||
"link_preview.more_from_author": "Больш ад {name}",
|
||||
"link_preview.shares": "{count, plural, one {{counter} допіс} few {{counter} допісы} many {{counter} допісаў} other {{counter} допісу}}",
|
||||
@ -1192,6 +1199,7 @@
|
||||
"search_popout.user": "карыстальнік",
|
||||
"search_results.accounts": "Профілі",
|
||||
"search_results.all": "Усё",
|
||||
"search_results.collections": "Калекцыі",
|
||||
"search_results.hashtags": "Хэштэгі",
|
||||
"search_results.no_results": "Няма вынікаў.",
|
||||
"search_results.no_search_yet": "Паспрабуйце пашукаць допісы, профілі або хэштэгі.",
|
||||
|
||||
@ -2,7 +2,6 @@
|
||||
"about.blocks": "Модерирани сървъри",
|
||||
"about.contact": "За контакти:",
|
||||
"about.default_locale": "По подразбиране",
|
||||
"about.disclaimer": "Mastodon е безплатен софтуер с отворен изходен код и търговска марка на Mastodon gGmbH.",
|
||||
"about.domain_blocks.no_reason_available": "Няма налична причина",
|
||||
"about.domain_blocks.preamble": "Mastodon обикновено позволява да разглеждате съдържание и да взаимодействате с други потребители от всякакви сървъри във Федивселената. Има изключения, направени конкретно за този сървър.",
|
||||
"about.domain_blocks.silenced.explanation": "Обикновено няма да виждате профили и съдържание, освен ако изрично не го потърсите или се включете в него, следвайки го.",
|
||||
@ -464,7 +463,6 @@
|
||||
"lightbox.zoom_in": "Увеличение до действителната големина",
|
||||
"lightbox.zoom_out": "Увеличение до побиране",
|
||||
"limited_account_hint.action": "Показване на профила въпреки това",
|
||||
"limited_account_hint.title": "Този профил е бил скрит от модераторите на {domain}.",
|
||||
"link_preview.author": "От {name}",
|
||||
"link_preview.more_from_author": "Още от {name}",
|
||||
"link_preview.shares": "{count, plural, one {{counter} публикация} other {{counter} публикации}}",
|
||||
|
||||
@ -1,7 +1,6 @@
|
||||
{
|
||||
"about.blocks": "অনুপলব্ধ সার্ভার",
|
||||
"about.contact": "যোগাযোগ:",
|
||||
"about.disclaimer": "ম্যাস্টোডন একটি ফ্রি, ওপেন সোর্স সফটওয়্যার এবং ম্যাস্টোডন জিজিএমবিএইচ এর একটি ট্রেডমার্ক।",
|
||||
"about.domain_blocks.no_reason_available": "কারণ দর্শানো যাচ্ছে না",
|
||||
"about.domain_blocks.preamble": "ম্যাস্টোডন সাধারণত আপনাকে ফেদিভার্স এ অন্য কোনও সার্ভারের ব্যবহারকারীদের থেকে সামগ্রী দেখতে এবং তাদের সাথে আলাপচারিতা করার সুযোগ দেয়। এই ব্যতিক্রম যে এই বিশেষ সার্ভারে তৈরি করা হয়েছে।",
|
||||
"about.domain_blocks.silenced.explanation": "আপনি সাধারণত এই সার্ভার থেকে প্রোফাইল এবং বিষয়বস্তু দেখতে পারবেন না, যদি না আপনি নিজে থেকেই এটাকে ফলো না করেন.",
|
||||
|
||||
@ -2,7 +2,6 @@
|
||||
"about.blocks": "Servijerioù evezhiet",
|
||||
"about.contact": "Darempred :",
|
||||
"about.default_locale": "Dre ziouer",
|
||||
"about.disclaimer": "Mastodon zo ur meziant frank, open-source hag ur merk marilhet eus Mastodon gGmbH.",
|
||||
"about.domain_blocks.no_reason_available": "Abeg dihegerz",
|
||||
"about.domain_blocks.preamble": "Gant Mastodon e c'hellit gwelet danvez hag eskemm gant implijerien·ezed eus forzh peseurt servijer er fedibed peurliesañ. Setu an nemedennoù a zo bet graet evit ar servijer-mañ e-unan.",
|
||||
"about.domain_blocks.silenced.explanation": "Ne vo ket gwelet profiloù eus ar servijer-mañ ganeoc'h peurliesañ, nemet ma vefec'h o klask war o lec'h pe choazfec'h o heuliañ.",
|
||||
@ -442,7 +441,6 @@
|
||||
"lightbox.next": "Da-heul",
|
||||
"lightbox.previous": "A-raok",
|
||||
"limited_account_hint.action": "Diskouez ar profil memes tra",
|
||||
"limited_account_hint.title": "Kuzhet eo bet ar profil-mañ gant an evezhierien eus {domain}.",
|
||||
"link_preview.author": "Gant {name}",
|
||||
"link_preview.more_from_author": "Muioc'h gant {name}",
|
||||
"link_preview.shares": "{count, plural, one {{counter} embannadur} two {{counter} embannadur} few {{counter} embannadur} many {{counter} embannadur} other {{counter} embannadur}}",
|
||||
|
||||
@ -2,7 +2,6 @@
|
||||
"about.blocks": "Servidors moderats",
|
||||
"about.contact": "Contacte:",
|
||||
"about.default_locale": "Per defecte",
|
||||
"about.disclaimer": "Mastodon és programari lliure de codi obert i una marca comercial de Mastodon gGmbH.",
|
||||
"about.domain_blocks.no_reason_available": "No es disposa del motiu",
|
||||
"about.domain_blocks.preamble": "En general, Mastodon permet de veure el contingut i interaccionar amb els usuaris de qualsevol altre servidor del fedivers. Aquestes són les excepcions d'aquest servidor en particular.",
|
||||
"about.domain_blocks.silenced.explanation": "Generalment no veuràs perfils ni contingut d'aquest servidor, a menys que el cerquis explícitament o optis per seguir-lo.",
|
||||
@ -559,7 +558,6 @@
|
||||
"lightbox.zoom_in": "Amplia fins a la mida real",
|
||||
"lightbox.zoom_out": "Amplia fins a encabir",
|
||||
"limited_account_hint.action": "Mostra el perfil de totes maneres",
|
||||
"limited_account_hint.title": "Aquest perfil l'han amagat els moderadors de {domain}.",
|
||||
"link_preview.author": "Per {name}",
|
||||
"link_preview.more_from_author": "Més de {name}",
|
||||
"link_preview.shares": "{count, plural, one {{counter} publicació} other {{counter} publicacions}}",
|
||||
|
||||
@ -1,7 +1,6 @@
|
||||
{
|
||||
"about.blocks": "ڕاژە سەرپەرشتیکراو",
|
||||
"about.contact": "پەیوەندی کردن:",
|
||||
"about.disclaimer": "ماستودۆن بە خۆڕایە، پرۆگرامێکی سەرچاوە کراوەیە، وە نیشانە بازرگانیەکەی ماستودۆن (gGmbH)ە",
|
||||
"about.domain_blocks.no_reason_available": "هۆکار بەردەست نیە",
|
||||
"about.domain_blocks.preamble": "ماستۆدۆن بە گشتی ڕێگەت پێدەدات بە پیشاندانی ناوەڕۆکەکان و کارلێک کردن لەگەڵ بەکارهێنەران لە هەر ڕاژەیەکی تر بە گشتی. ئەمانە ئەو بەدەرکردنانەن کە کراون لەسەر ئەم ڕاژە تایبەتە.",
|
||||
"about.domain_blocks.silenced.explanation": "بە گشتی ناتوانی زانیاریە تایبەتەکان و ناوەڕۆکی ئەم ڕاژەیە ببینی، مەگەر بە ڕوونی بەدوایدا بگەڕێیت یان هەڵیبژێریت بۆ شوێنکەوتنی.",
|
||||
@ -306,7 +305,6 @@
|
||||
"lightbox.next": "داهاتوو",
|
||||
"lightbox.previous": "پێشوو",
|
||||
"limited_account_hint.action": "بەهەر حاڵ پڕۆفایلی پیشان بدە",
|
||||
"limited_account_hint.title": "ئەم پرۆفایلە لەلایەن بەڕێوەبەرانی {domain} شاراوەتەوە.",
|
||||
"lists.delete": "سڕینەوەی لیست",
|
||||
"lists.edit": "دەستکاری لیست",
|
||||
"lists.replies_policy.followed": "هەر بەکارهێنەرێکی بەدواکەوتوو",
|
||||
|
||||
@ -2,7 +2,6 @@
|
||||
"about.blocks": "Moderované servery",
|
||||
"about.contact": "Kontakt:",
|
||||
"about.default_locale": "Výchozí",
|
||||
"about.disclaimer": "Mastodon je svobodný software s otevřeným zdrojovým kódem a ochranná známka společnosti Mastodon gGmbH.",
|
||||
"about.domain_blocks.no_reason_available": "Důvod není k dispozici",
|
||||
"about.domain_blocks.preamble": "Mastodon umožňuje prohlížet obsah a komunikovat s uživateli jakéhokoliv serveru ve fediversu. Pro tento konkrétní server se vztahují následující výjimky.",
|
||||
"about.domain_blocks.silenced.explanation": "Uživatele a obsah tohoto serveru neuvidíte, pokud je nebudete výslovně hledat nebo je nezačnete sledovat.",
|
||||
@ -619,7 +618,6 @@
|
||||
"lightbox.zoom_in": "Přiblížit na skutečnou velikost",
|
||||
"lightbox.zoom_out": "Přizpůsobit velikost",
|
||||
"limited_account_hint.action": "Přesto profil zobrazit",
|
||||
"limited_account_hint.title": "Tento profil byl skryt moderátory {domain}.",
|
||||
"link_preview.author": "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ů}}",
|
||||
|
||||
@ -2,7 +2,6 @@
|
||||
"about.blocks": "Gweinyddion wedi'u cymedroli",
|
||||
"about.contact": "Cysylltiad:",
|
||||
"about.default_locale": "Rhagosodedig",
|
||||
"about.disclaimer": "Mae Mastodon yn feddalwedd cod agored rhydd ac o dan hawlfraint Mastodon gGmbH.",
|
||||
"about.domain_blocks.no_reason_available": "Dyw'r rheswm ddim ar gael",
|
||||
"about.domain_blocks.preamble": "Fel rheol, mae Mastodon yn caniatáu i chi weld cynnwys gan unrhyw weinyddwr arall yn y ffedysawd a rhyngweithio â hi. Dyma'r eithriadau a wnaed ar y gweinydd penodol hwn.",
|
||||
"about.domain_blocks.silenced.explanation": "Fel rheol, fyddwch chi ddim yn gweld proffiliau a chynnwys o'r gweinydd hwn, oni bai eich bod yn chwilio'n benodol amdano neu yn ymuno drwy ei ddilyn.",
|
||||
@ -840,7 +839,6 @@
|
||||
"lightbox.zoom_in": "Chwyddo i faint gwirioneddol",
|
||||
"lightbox.zoom_out": "Chwyddo i ffitio",
|
||||
"limited_account_hint.action": "Dangos y proffil beth bynnag",
|
||||
"limited_account_hint.title": "Mae'r proffil hwn wedi cael ei guddio gan gymedrolwyr {domain}.",
|
||||
"link_preview.author": "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}}",
|
||||
|
||||
@ -2,7 +2,7 @@
|
||||
"about.blocks": "Modererede servere",
|
||||
"about.contact": "Kontakt:",
|
||||
"about.default_locale": "Standard",
|
||||
"about.disclaimer": "Mastodon er gratis, open-source software og et varemærke tilhørende Mastodon gGmbH.",
|
||||
"about.disclaimer": "Mastodon er gratis, open-source software og et varemærke tilhørende Mastodon GmbH.",
|
||||
"about.domain_blocks.no_reason_available": "Begrundelse ikke tilgængelig",
|
||||
"about.domain_blocks.preamble": "Mastodon tillader generelt, at du ser indhold og interagere med brugere fra enhver anden server i fediverset. Disse er undtagelserne, som er implementeret på netop denne server.",
|
||||
"about.domain_blocks.silenced.explanation": "Du vil generelt ikke se profiler og indhold fra denne server, medmindre du udtrykkeligt slår den op eller vælger den ved at følge.",
|
||||
@ -646,6 +646,7 @@
|
||||
"empty_column.account_unavailable": "Profil utilgængelig",
|
||||
"empty_column.blocks": "Du har ikke blokeret nogle brugere endnu.",
|
||||
"empty_column.bookmarked_statuses": "Du har ingen bogmærkede indlæg endnu. Når du bogmærker ét, vil det dukke op hér.",
|
||||
"empty_column.collections": "{acct} har endnu ikke oprettet nogen samlinger.",
|
||||
"empty_column.collections.featured_in": "Du er ikke blevet tilføjet til nogen samlinger endnu.",
|
||||
"empty_column.collections.featured_in_undiscoverable": "For at andre kan føje dig til samlinger, skal du give tilladelse til at blive vist i opdagelsesfunktioner under <link>Præferencer > Fortrolighed og rækkevidde</link>",
|
||||
"empty_column.community": "Den lokale tidslinje er tom. Skriv noget offentligt for at sætte tingene i gang!",
|
||||
@ -819,6 +820,12 @@
|
||||
"keyboard_shortcuts.heading": "Tastaturgenveje",
|
||||
"keyboard_shortcuts.home": "Åbn hjem-tidslinje",
|
||||
"keyboard_shortcuts.hotkey": "Hurtigtast",
|
||||
"keyboard_shortcuts.keys.alt": "Alt",
|
||||
"keyboard_shortcuts.keys.backspace": "Backspace",
|
||||
"keyboard_shortcuts.keys.enter": "Enter",
|
||||
"keyboard_shortcuts.keys.esc": "Esc",
|
||||
"keyboard_shortcuts.keys.page_down": "Side ned",
|
||||
"keyboard_shortcuts.keys.page_up": "Side op",
|
||||
"keyboard_shortcuts.legend": "Vis dette symbol",
|
||||
"keyboard_shortcuts.load_more": "Fokusér knappen \"Indlæs flere\"",
|
||||
"keyboard_shortcuts.local": "Åbn lokal tidslinje",
|
||||
@ -850,7 +857,7 @@
|
||||
"lightbox.zoom_in": "Zoom til faktisk størrelse",
|
||||
"lightbox.zoom_out": "Zoom for at tilpasse",
|
||||
"limited_account_hint.action": "Vis profil alligevel",
|
||||
"limited_account_hint.title": "Denne profil er blevet skjult af {domain}-moderatorerne.",
|
||||
"limited_account_hint.title": "Denne profil eller server er blevet skjult af moderatorerne på {domain}.",
|
||||
"link_preview.author": "Af {name}",
|
||||
"link_preview.more_from_author": "Mere fra {name}",
|
||||
"link_preview.shares": "{count, plural, one {{counter} indlæg} other {{counter} indlæg}}",
|
||||
@ -1192,6 +1199,7 @@
|
||||
"search_popout.user": "bruger",
|
||||
"search_results.accounts": "Profiler",
|
||||
"search_results.all": "Alle",
|
||||
"search_results.collections": "Samlinger",
|
||||
"search_results.hashtags": "Hashtags",
|
||||
"search_results.no_results": "Ingen resultater.",
|
||||
"search_results.no_search_yet": "Prøv at søge efter indlæg, profiler eller hashtags.",
|
||||
|
||||
@ -2,7 +2,7 @@
|
||||
"about.blocks": "Eingeschränkte Server",
|
||||
"about.contact": "Kontakt:",
|
||||
"about.default_locale": "Standard",
|
||||
"about.disclaimer": "Mastodon ist eine freie, quelloffene Software und eine Marke der Mastodon gGmbH.",
|
||||
"about.disclaimer": "Mastodon ist eine freie, quelloffene Software und eine Marke der Mastodon GmbH.",
|
||||
"about.domain_blocks.no_reason_available": "Keinen Grund angegeben",
|
||||
"about.domain_blocks.preamble": "Mastodon erlaubt es dir grundsätzlich, alle Inhalte von allen Nutzer*innen auf allen Servern im Fediverse zu sehen und mit ihnen zu interagieren. Für diesen Server gibt es aber ein paar Ausnahmen.",
|
||||
"about.domain_blocks.silenced.explanation": "Standardmäßig werden von diesem Server keine Inhalte oder Profile angezeigt. Du kannst die Profile und Inhalte aber dennoch sehen, wenn du explizit nach diesen suchst oder diesen folgst.",
|
||||
@ -646,6 +646,7 @@
|
||||
"empty_column.account_unavailable": "Profil nicht verfügbar",
|
||||
"empty_column.blocks": "Du hast bisher keine Profile blockiert.",
|
||||
"empty_column.bookmarked_statuses": "Du hast bisher keine Beiträge als Lesezeichen abgelegt. Sobald du einen Beitrag als Lesezeichen speicherst, wird er hier erscheinen.",
|
||||
"empty_column.collections": "{acct} hat noch keine Sammlungen erstellt.",
|
||||
"empty_column.collections.featured_in": "Du wurdest noch keiner Sammlung hinzugefügt.",
|
||||
"empty_column.collections.featured_in_undiscoverable": "Damit du zu Sammlungen hinzugefügt werden kannst, muss „Mich beim Entdecken berücksichtigen“ unter <link>Einstellungen > Datenschutz und Reichweite</link> aktiviert werden",
|
||||
"empty_column.community": "Die lokale Timeline ist leer. Schreibe einen öffentlichen Beitrag, um den Stein ins Rollen zu bringen!",
|
||||
@ -819,6 +820,12 @@
|
||||
"keyboard_shortcuts.heading": "Tastenkürzel",
|
||||
"keyboard_shortcuts.home": "Startseite öffnen",
|
||||
"keyboard_shortcuts.hotkey": "Tastenkürzel",
|
||||
"keyboard_shortcuts.keys.alt": "Alt",
|
||||
"keyboard_shortcuts.keys.backspace": "Backspace",
|
||||
"keyboard_shortcuts.keys.enter": "Eingabetaste",
|
||||
"keyboard_shortcuts.keys.esc": "Esc",
|
||||
"keyboard_shortcuts.keys.page_down": "Bild ab",
|
||||
"keyboard_shortcuts.keys.page_up": "Bild auf",
|
||||
"keyboard_shortcuts.legend": "Tastenkürzel anzeigen (diese Seite)",
|
||||
"keyboard_shortcuts.load_more": "Schaltfläche „Mehr laden“ fokussieren",
|
||||
"keyboard_shortcuts.local": "Lokale Timeline öffnen",
|
||||
@ -850,7 +857,7 @@
|
||||
"lightbox.zoom_in": "In Originalgröße anzeigen",
|
||||
"lightbox.zoom_out": "In angepasster Größe anzeigen",
|
||||
"limited_account_hint.action": "Profil trotzdem anzeigen",
|
||||
"limited_account_hint.title": "Dieses Profil wurde von den Moderator*innen von {domain} ausgeblendet.",
|
||||
"limited_account_hint.title": "Dieses Profil oder dieser Server wurde von den Moderator*innen von {domain} ausgeblendet.",
|
||||
"link_preview.author": "Von {name}",
|
||||
"link_preview.more_from_author": "Mehr von {name}",
|
||||
"link_preview.shares": "{count, plural, one {{counter} Beitrag} other {{counter} Beiträge}}",
|
||||
@ -1192,6 +1199,7 @@
|
||||
"search_popout.user": "Profil",
|
||||
"search_results.accounts": "Profile",
|
||||
"search_results.all": "Alles",
|
||||
"search_results.collections": "Sammlungen",
|
||||
"search_results.hashtags": "Hashtags",
|
||||
"search_results.no_results": "Keine Ergebnisse.",
|
||||
"search_results.no_search_yet": "Suche nach Beiträgen, Profilen oder Hashtags.",
|
||||
|
||||
@ -2,7 +2,7 @@
|
||||
"about.blocks": "Συντονιζόμενοι διακομιστές",
|
||||
"about.contact": "Επικοινωνία:",
|
||||
"about.default_locale": "Προεπιλογή",
|
||||
"about.disclaimer": "Το Mastodon είναι ελεύθερο λογισμικό ανοιχτού κώδικα και εμπορικό σήμα της Mastodon gGmbH.",
|
||||
"about.disclaimer": "Το Mastodon είναι ελεύθερο λογισμικό ανοιχτού κώδικα και εμπορικό σήμα της Mastodon GmbH.",
|
||||
"about.domain_blocks.no_reason_available": "Αιτιολογία μη διαθέσιμη",
|
||||
"about.domain_blocks.preamble": "Σε γενικές γραμμές το Mastodon σου επιτρέπει να βλέπεις περιεχόμενο και να αλληλεπιδράς με χρήστες από οποιονδήποτε άλλο διακομιστή σε ένα διασυνδεδεμένο σύμπαν διακομιστών (fediverse). Ακολουθούν οι εξαιρέσεις που ισχύουν για τον συγκεκριμένο διακομιστή.",
|
||||
"about.domain_blocks.silenced.explanation": "Συνήθως δε θα βλέπεις προφίλ και περιεχόμενο απ' αυτόν τον διακομιστή, εκτός αν κάνεις συγκεκριμένη αναζήτηση ή επιλέξεις να τον ακολουθήσεις.",
|
||||
@ -646,6 +646,7 @@
|
||||
"empty_column.account_unavailable": "Μη διαθέσιμο προφίλ",
|
||||
"empty_column.blocks": "Δεν έχεις αποκλείσει κανέναν χρήστη ακόμη.",
|
||||
"empty_column.bookmarked_statuses": "Δεν έχεις καμία ανάρτηση με σελιδοδείκτη ακόμη. Μόλις βάλεις κάποιον, θα εμφανιστεί εδώ.",
|
||||
"empty_column.collections": "Ο χρήστης {acct} δεν έχει δημιουργήσει ακόμη καμία συλλογή.",
|
||||
"empty_column.collections.featured_in": "Δεν έχετε προστεθεί ακόμη σε καμία συλλογή.",
|
||||
"empty_column.collections.featured_in_undiscoverable": "Προκειμένου ο κόσμος να σας προσθέσει σε συλλογές, πρέπει να επιτρέψετε την ανάδειξή σας σε εμπειρίες ανακάλυψης από τις <link>Προτιμήσεις > Ιδιωτικότητα και προσιτότητα</link>",
|
||||
"empty_column.community": "Η τοπική ροή είναι κενή. Γράψε κάτι δημόσια για να αρχίσει να κυλά η μπάλα!",
|
||||
@ -760,10 +761,10 @@
|
||||
"hashtag.counter_by_uses": "{count, plural, one {{counter} ανάρτηση} other {{counter} αναρτήσεις}}",
|
||||
"hashtag.counter_by_uses_today": "{count, plural, one {{counter} ανάρτηση} other {{counter} αναρτήσεις}} σήμερα",
|
||||
"hashtag.feature": "Ανάδειξη στο προφίλ",
|
||||
"hashtag.follow": "Παρακολούθηση ετικέτας",
|
||||
"hashtag.follow": "Ακολούθηση ετικέτας",
|
||||
"hashtag.mute": "Σίγαση #{hashtag}",
|
||||
"hashtag.unfeature": "Να μην αναδεικνύεται στο προφίλ",
|
||||
"hashtag.unfollow": "Διακοπή παρακολούθησης ετικέτας",
|
||||
"hashtag.unfollow": "Άρση ακολούθησης ετικέτας",
|
||||
"hashtags.and_other": "…και {count, plural, other {# ακόμα}}",
|
||||
"hints.profiles.followers_may_be_missing": "Μπορεί να λείπουν ακόλουθοι για αυτό το προφίλ.",
|
||||
"hints.profiles.follows_may_be_missing": "Άτομα που ακολουθούνται μπορεί να λείπουν απ' αυτό το προφίλ.",
|
||||
@ -819,6 +820,12 @@
|
||||
"keyboard_shortcuts.heading": "Συντομεύσεις πληκτρολογίου",
|
||||
"keyboard_shortcuts.home": "Άνοιγμα ροής αρχικής σελίδας",
|
||||
"keyboard_shortcuts.hotkey": "Συντόμευση",
|
||||
"keyboard_shortcuts.keys.alt": "Alt",
|
||||
"keyboard_shortcuts.keys.backspace": "Backspace",
|
||||
"keyboard_shortcuts.keys.enter": "Enter",
|
||||
"keyboard_shortcuts.keys.esc": "Esc",
|
||||
"keyboard_shortcuts.keys.page_down": "Page Down",
|
||||
"keyboard_shortcuts.keys.page_up": "Page Up",
|
||||
"keyboard_shortcuts.legend": "Εμφάνιση αυτού του οδηγού",
|
||||
"keyboard_shortcuts.load_more": "Εστίαση στο κουμπί \"Φόρτωση περισσότερων\"",
|
||||
"keyboard_shortcuts.local": "Άνοιγμα τοπικής ροής",
|
||||
@ -850,7 +857,7 @@
|
||||
"lightbox.zoom_in": "Εστίαση στο πραγματικό μέγεθος",
|
||||
"lightbox.zoom_out": "Εστίαση για προσαρμογή",
|
||||
"limited_account_hint.action": "Εμφάνιση προφίλ ούτως ή άλλως",
|
||||
"limited_account_hint.title": "Αυτό το προφίλ έχει αποκρυφτεί από τους διαχειριστές του διακομιστή {domain}.",
|
||||
"limited_account_hint.title": "Αυτό το προφίλ ή διακομιστής έχει κρυφτεί από τους διαχειριστές του {domain}.",
|
||||
"link_preview.author": "Από {name}",
|
||||
"link_preview.more_from_author": "Περισσότερα από {name}",
|
||||
"link_preview.shares": "{count, plural, one {{counter} ανάρτηση} other {{counter} αναρτήσεις}}",
|
||||
@ -1192,6 +1199,7 @@
|
||||
"search_popout.user": "χρήστης",
|
||||
"search_results.accounts": "Προφίλ",
|
||||
"search_results.all": "Όλα",
|
||||
"search_results.collections": "Συλλογές",
|
||||
"search_results.hashtags": "Ετικέτες",
|
||||
"search_results.no_results": "Κανένα αποτέλεσμα.",
|
||||
"search_results.no_search_yet": "Δοκίμασε να ψάξεις για αναρτήσεις, προφίλ ή ετικέτες.",
|
||||
|
||||
@ -2,7 +2,6 @@
|
||||
"about.blocks": "Moderated servers",
|
||||
"about.contact": "Contact:",
|
||||
"about.default_locale": "Default",
|
||||
"about.disclaimer": "Mastodon is free, open-source software, and a trademark of Mastodon gGmbH.",
|
||||
"about.domain_blocks.no_reason_available": "Reason not available",
|
||||
"about.domain_blocks.preamble": "Mastodon generally allows you to view content from and interact with users from any other server in the Fediverse. These are the exceptions that have been made on this particular server.",
|
||||
"about.domain_blocks.silenced.explanation": "You will generally not see profiles and content from this server, unless you explicitly look it up or opt into it by following.",
|
||||
@ -833,7 +832,6 @@
|
||||
"lightbox.zoom_in": "Zoom to actual size",
|
||||
"lightbox.zoom_out": "Zoom to fit",
|
||||
"limited_account_hint.action": "Show profile anyway",
|
||||
"limited_account_hint.title": "This profile or server has been hidden by the moderators of {domain}.",
|
||||
"link_preview.author": "By {name}",
|
||||
"link_preview.more_from_author": "More from {name}",
|
||||
"link_preview.shares": "{count, plural, one {{counter} post} other {{counter} posts}}",
|
||||
|
||||
@ -646,6 +646,7 @@
|
||||
"empty_column.account_unavailable": "Profile unavailable",
|
||||
"empty_column.blocks": "You haven't blocked any users yet.",
|
||||
"empty_column.bookmarked_statuses": "You don't have any bookmarked posts yet. When you bookmark one, it will show up here.",
|
||||
"empty_column.collections": "{acct} has not created any collections yet.",
|
||||
"empty_column.collections.featured_in": "You have not been added to any collections yet.",
|
||||
"empty_column.collections.featured_in_undiscoverable": "In order for people to add you to collections, you need to allow featuring in discovery experiences from <link>Preferences > Privacy and reach</link>",
|
||||
"empty_column.community": "The local timeline is empty. Write something publicly to get the ball rolling!",
|
||||
@ -1198,6 +1199,7 @@
|
||||
"search_popout.user": "user",
|
||||
"search_results.accounts": "Profiles",
|
||||
"search_results.all": "All",
|
||||
"search_results.collections": "Collections",
|
||||
"search_results.hashtags": "Hashtags",
|
||||
"search_results.no_results": "No results.",
|
||||
"search_results.no_search_yet": "Try searching for posts, profiles or hashtags.",
|
||||
|
||||
@ -2,7 +2,6 @@
|
||||
"about.blocks": "Reguligitaj serviloj",
|
||||
"about.contact": "Kontakto:",
|
||||
"about.default_locale": "Defaŭlta",
|
||||
"about.disclaimer": "Mastodon estas libera, malfermitkoda programo kaj varmarko de la firmao Mastodon gGmbH.",
|
||||
"about.domain_blocks.no_reason_available": "Kialo ne disponeblas",
|
||||
"about.domain_blocks.preamble": "Mastodon ĝenerale rajtigas vidi la enhavojn de uzantoj el aliaj serviloj en la fediverso, kaj komuniki kun ili. Jen la limigoj deciditaj de tiu ĉi servilo mem.",
|
||||
"about.domain_blocks.silenced.explanation": "Vi ne ĝenerale vidos profilojn kaj enhavojn de ĉi tiu servilo, krom se vi eksplice trovas aŭ estas permesita de via sekvato.",
|
||||
@ -478,7 +477,6 @@
|
||||
"lightbox.zoom_in": "Zomi al reala grandeco",
|
||||
"lightbox.zoom_out": "Zomi por konveni",
|
||||
"limited_account_hint.action": "Montru profilon ĉiukaze",
|
||||
"limited_account_hint.title": "La profilo estas kaŝita de la moderigantoj de {domain}.",
|
||||
"link_preview.author": "De {name}",
|
||||
"link_preview.more_from_author": "Pli de {name}",
|
||||
"link_preview.shares": "{count, plural, one {{counter} afiŝo} other {{counter} afiŝoj}}",
|
||||
|
||||
@ -2,7 +2,7 @@
|
||||
"about.blocks": "Servidores moderados",
|
||||
"about.contact": "Contacto:",
|
||||
"about.default_locale": "Predeterminado",
|
||||
"about.disclaimer": "Mastodon es software libre y de código abierto y una marca comercial de Mastodon gGmbH.",
|
||||
"about.disclaimer": "Mastodon es software libre y de código abierto, y una marca comercial de Mastodon GmbH.",
|
||||
"about.domain_blocks.no_reason_available": "Motivo no disponible",
|
||||
"about.domain_blocks.preamble": "Mastodon normalmente te permite ver el contenido e interactuar con los usuarios de cualquier otro servidor en el fediverso. Estas son las excepciones que se han hecho en este servidor en particular.",
|
||||
"about.domain_blocks.silenced.explanation": "Normalmente no verás perfiles y contenido de este servidor, a menos que lo busqués explícitamente o sigás alguna cuenta.",
|
||||
@ -646,6 +646,7 @@
|
||||
"empty_column.account_unavailable": "Perfil no disponible",
|
||||
"empty_column.blocks": "Todavía no bloqueaste a ningún usuario.",
|
||||
"empty_column.bookmarked_statuses": "Todavía no tenés mensajes guardados en \"Marcadores\". Cuando guardés uno en \"Marcadores\", se mostrará acá.",
|
||||
"empty_column.collections": "{acct} todavía no creó ninguna colección.",
|
||||
"empty_column.collections.featured_in": "Todavía no te agregaron a ninguna colección.",
|
||||
"empty_column.collections.featured_in_undiscoverable": "Para que la gente pueda agregarte a colecciones, tenés que permitir que te destacen en las experiencias de descubrimiento en <link>Configuración > Privacidad y alcance</link>",
|
||||
"empty_column.community": "La línea temporal local está vacía. ¡Escribí algo en modo público para que se empiece a correr la bola!",
|
||||
@ -819,6 +820,12 @@
|
||||
"keyboard_shortcuts.heading": "Atajos de teclado",
|
||||
"keyboard_shortcuts.home": "Abrir línea temporal principal",
|
||||
"keyboard_shortcuts.hotkey": "Atajo",
|
||||
"keyboard_shortcuts.keys.alt": "Alt",
|
||||
"keyboard_shortcuts.keys.backspace": "⌫ Retroceso",
|
||||
"keyboard_shortcuts.keys.enter": "↳ Intro",
|
||||
"keyboard_shortcuts.keys.esc": "Escape",
|
||||
"keyboard_shortcuts.keys.page_down": "Re Pág",
|
||||
"keyboard_shortcuts.keys.page_up": "Av Pág",
|
||||
"keyboard_shortcuts.legend": "Mostrar este texto",
|
||||
"keyboard_shortcuts.load_more": "Focalizar el botón «Cargar más»",
|
||||
"keyboard_shortcuts.local": "Abrirlínea temporal local",
|
||||
@ -850,7 +857,7 @@
|
||||
"lightbox.zoom_in": "Ampliar al tamaño real",
|
||||
"lightbox.zoom_out": "Ampliar hasta ajustar",
|
||||
"limited_account_hint.action": "Mostrar perfil de todos modos",
|
||||
"limited_account_hint.title": "Este perfil fue ocultado por los moderadores de {domain}.",
|
||||
"limited_account_hint.title": "Este perfil del servidor fue ocultado por los moderadores de {domain}.",
|
||||
"link_preview.author": "Por {name}",
|
||||
"link_preview.more_from_author": "Más de {name}",
|
||||
"link_preview.shares": "{count, plural, one {{counter} mensaje} other {{counter} mensajes}}",
|
||||
@ -1192,6 +1199,7 @@
|
||||
"search_popout.user": "usuario",
|
||||
"search_results.accounts": "Perfiles",
|
||||
"search_results.all": "Todos",
|
||||
"search_results.collections": "Colecciones",
|
||||
"search_results.hashtags": "Etiquetas",
|
||||
"search_results.no_results": "Sin resultados.",
|
||||
"search_results.no_search_yet": "Intentá buscar publicaciones, perfiles o etiquetas.",
|
||||
|
||||
@ -2,7 +2,7 @@
|
||||
"about.blocks": "Servidores moderados",
|
||||
"about.contact": "Contacto:",
|
||||
"about.default_locale": "Por defecto",
|
||||
"about.disclaimer": "Mastodon es software libre de código abierto, y una marca comercial de Mastodon gGmbH.",
|
||||
"about.disclaimer": "Mastodon es un software libre, de código abierto, y una marca comercial de Mastodon GmbH.",
|
||||
"about.domain_blocks.no_reason_available": "Motivo no disponible",
|
||||
"about.domain_blocks.preamble": "Mastodon generalmente te permite ver contenido e interactuar con usuarios de cualquier otro servidor del fediverso. Estas son las excepciones que se han hecho en este servidor en particular.",
|
||||
"about.domain_blocks.silenced.explanation": "Por lo general, no verás perfiles ni contenidos de este servidor, a menos que los busques explícitamente o que optes por seguirlo.",
|
||||
@ -646,6 +646,7 @@
|
||||
"empty_column.account_unavailable": "Perfil no disponible",
|
||||
"empty_column.blocks": "Aún no has bloqueado a ningún usuario.",
|
||||
"empty_column.bookmarked_statuses": "Aún no tienes ninguna publicación guardada como marcador. Cuando guardes una, se mostrará aquí.",
|
||||
"empty_column.collections": "{acct} aún no ha creado ninguna colección.",
|
||||
"empty_column.collections.featured_in": "Aún no te han añadido a ninguna colección.",
|
||||
"empty_column.collections.featured_in_undiscoverable": "Para que los usuarios puedan añadirte a sus colecciones, debes habilitar la opción de aparecer en las experiencias de descubrimiento desde <link>Preferencias > Privacidad y alcance</link>",
|
||||
"empty_column.community": "La cronología local está vacía. ¡Escribe algo públicamente para ponerla en marcha!",
|
||||
@ -819,6 +820,12 @@
|
||||
"keyboard_shortcuts.heading": "Atajos de teclado",
|
||||
"keyboard_shortcuts.home": "Abrir cronología principal",
|
||||
"keyboard_shortcuts.hotkey": "Tecla de acceso rápido",
|
||||
"keyboard_shortcuts.keys.alt": "Alt",
|
||||
"keyboard_shortcuts.keys.backspace": "Retroceso",
|
||||
"keyboard_shortcuts.keys.enter": "Enter",
|
||||
"keyboard_shortcuts.keys.esc": "Escape",
|
||||
"keyboard_shortcuts.keys.page_down": "Re Pág",
|
||||
"keyboard_shortcuts.keys.page_up": "Av Pág",
|
||||
"keyboard_shortcuts.legend": "Mostrar esta leyenda",
|
||||
"keyboard_shortcuts.load_more": "Enfoque en el botón \"Cargar más\"",
|
||||
"keyboard_shortcuts.local": "Abrir cronología local",
|
||||
@ -850,7 +857,7 @@
|
||||
"lightbox.zoom_in": "Ampliar al tamaño real",
|
||||
"lightbox.zoom_out": "Ampliar para ajustar",
|
||||
"limited_account_hint.action": "Mostrar perfil de todos modos",
|
||||
"limited_account_hint.title": "Este perfil ha sido ocultado por los moderadores de {domain}.",
|
||||
"limited_account_hint.title": "Este perfil o servidor ha sido ocultado por los moderadores de {domain}.",
|
||||
"link_preview.author": "Por {name}",
|
||||
"link_preview.more_from_author": "Más de {name}",
|
||||
"link_preview.shares": "{count, plural, one {{counter} publicación} other {{counter} publicaciones}}",
|
||||
@ -1192,6 +1199,7 @@
|
||||
"search_popout.user": "usuario",
|
||||
"search_results.accounts": "Perfiles",
|
||||
"search_results.all": "Todos",
|
||||
"search_results.collections": "Colecciones",
|
||||
"search_results.hashtags": "Etiquetas",
|
||||
"search_results.no_results": "No hay resultados.",
|
||||
"search_results.no_search_yet": "Intenta buscar publicaciones, perfiles o etiquetas.",
|
||||
|
||||
@ -2,7 +2,7 @@
|
||||
"about.blocks": "Servidores moderados",
|
||||
"about.contact": "Contacto:",
|
||||
"about.default_locale": "Por defecto",
|
||||
"about.disclaimer": "Mastodon es software libre, de código abierto, y una marca comercial de Mastodon gGmbH.",
|
||||
"about.disclaimer": "Mastodon es un software libre, de código abierto, y una marca comercial de Mastodon GmbH.",
|
||||
"about.domain_blocks.no_reason_available": "Razón no disponible",
|
||||
"about.domain_blocks.preamble": "Mastodon normalmente te permite ver el contenido e interactuar con los usuarios de cualquier otro servidor en el fediverso. Estas son las excepciones que se han hecho en este servidor en particular.",
|
||||
"about.domain_blocks.silenced.explanation": "Normalmente no verás perfiles y contenido de este servidor, a menos que lo busques explícitamente o sigas alguna cuenta.",
|
||||
@ -646,6 +646,7 @@
|
||||
"empty_column.account_unavailable": "Perfil no disponible",
|
||||
"empty_column.blocks": "Aún no has bloqueado a ningún usuario.",
|
||||
"empty_column.bookmarked_statuses": "Aún no tienes ninguna publicación guardada como marcador. Cuando guardes una, se mostrará aquí.",
|
||||
"empty_column.collections": "{acct} aún no ha creado ninguna colección.",
|
||||
"empty_column.collections.featured_in": "Aún no te han añadido a ninguna colección.",
|
||||
"empty_column.collections.featured_in_undiscoverable": "Para que la gente pueda añadirte a colecciones, debes permitir ser destacado en algoritmos de descubrimiento desde <link>Preferencias > Privacidad y alcance</link>",
|
||||
"empty_column.community": "La línea de tiempo local está vacía. ¡Escribe algo para empezar la fiesta!",
|
||||
@ -819,6 +820,12 @@
|
||||
"keyboard_shortcuts.heading": "Keyboard Shortcuts",
|
||||
"keyboard_shortcuts.home": "Abrir cronología principal",
|
||||
"keyboard_shortcuts.hotkey": "Tecla rápida",
|
||||
"keyboard_shortcuts.keys.alt": "Alt",
|
||||
"keyboard_shortcuts.keys.backspace": "Retroceso",
|
||||
"keyboard_shortcuts.keys.enter": "Enter",
|
||||
"keyboard_shortcuts.keys.esc": "Escape",
|
||||
"keyboard_shortcuts.keys.page_down": "Re Pág",
|
||||
"keyboard_shortcuts.keys.page_up": "Av Pág",
|
||||
"keyboard_shortcuts.legend": "Mostrar esta leyenda",
|
||||
"keyboard_shortcuts.load_more": "Poner el foco en el botón \"Cargar más\"",
|
||||
"keyboard_shortcuts.local": "Abrir cronología local",
|
||||
@ -850,7 +857,7 @@
|
||||
"lightbox.zoom_in": "Ampliar al tamaño real",
|
||||
"lightbox.zoom_out": "Ampliar para ajustar",
|
||||
"limited_account_hint.action": "Mostrar perfil de todos modos",
|
||||
"limited_account_hint.title": "Este perfil ha sido ocultado por los moderadores de {domain}.",
|
||||
"limited_account_hint.title": "Los moderadores de {domain} han ocultado este perfil o servidor.",
|
||||
"link_preview.author": "Por {name}",
|
||||
"link_preview.more_from_author": "Más de {name}",
|
||||
"link_preview.shares": "{count, plural, one {{counter} publicación} other {{counter} publicaciones}}",
|
||||
@ -1192,6 +1199,7 @@
|
||||
"search_popout.user": "usuario",
|
||||
"search_results.accounts": "Perfiles",
|
||||
"search_results.all": "Todos",
|
||||
"search_results.collections": "Colecciones",
|
||||
"search_results.hashtags": "Etiquetas",
|
||||
"search_results.no_results": "Sin resultados.",
|
||||
"search_results.no_search_yet": "Intenta buscar publicaciones, perfiles o etiquetas.",
|
||||
|
||||
@ -2,7 +2,6 @@
|
||||
"about.blocks": "Modereeritavad serverid",
|
||||
"about.contact": "Kontakt:",
|
||||
"about.default_locale": "Vaikimisi",
|
||||
"about.disclaimer": "Mastodon on vaba, tasuta ja avatud lähtekoodiga tarkvara ning Mastodon gGmbH kaubamärk.",
|
||||
"about.domain_blocks.no_reason_available": "Põhjus on teadmata",
|
||||
"about.domain_blocks.preamble": "Mastodon lubab üldiselt vaadata sisu ning suhelda kasutajatega ükskõik millisest teisest födiversumi serverist. Need on erandid, mis kehtivad selles kindlas serveris.",
|
||||
"about.domain_blocks.silenced.explanation": "Sa üldjuhul ei näe profiile ja sisu sellest serverist, kui sa just tahtlikult neid ei otsi või jälgimise moel nõusolekut ei anna.",
|
||||
@ -850,7 +849,7 @@
|
||||
"lightbox.zoom_in": "Näita algsuuruses",
|
||||
"lightbox.zoom_out": "Näita kõik",
|
||||
"limited_account_hint.action": "Näita profilli sellegipoolest",
|
||||
"limited_account_hint.title": "See profiil on peidetud {domain} moderaatorite poolt.",
|
||||
"limited_account_hint.title": "See profiil või server on {domain}-i moderaatorite poolt peidetud.",
|
||||
"link_preview.author": "{name} poolt",
|
||||
"link_preview.more_from_author": "Veel kasutajalt {name}",
|
||||
"link_preview.shares": "{count, plural, one {{counter} postitus} other {{counter} postitust}}",
|
||||
|
||||
@ -2,7 +2,6 @@
|
||||
"about.blocks": "Moderatutako zerbitzariak",
|
||||
"about.contact": "Kontaktua:",
|
||||
"about.default_locale": "Lehenetsia",
|
||||
"about.disclaimer": "Mastodon software libre eta kode irekikoa da, eta Mastodon gGmbH-ren marka erregistratua.",
|
||||
"about.domain_blocks.no_reason_available": "Arrazoia ez dago eskuragarri",
|
||||
"about.domain_blocks.preamble": "Mastodonek orokorrean aukera ematen dizu fedibertsoko beste zerbitzarietako erabiltzaileen edukia ikusi eta haiekin komunikatzeko. Zerbitzari zehatz honi ezarritako salbuespenak hauek dira.",
|
||||
"about.domain_blocks.silenced.explanation": "Orokorrean ez duzu zerbitzari honetako profil eta edukirik ikusiko. Profilak jarraitzen badituzu edo edukia esplizituki bilatzen baduzu bai.",
|
||||
@ -527,7 +526,6 @@
|
||||
"lightbox.zoom_in": "Zooma egungo tamainara",
|
||||
"lightbox.zoom_out": "Zooma egokitzeko",
|
||||
"limited_account_hint.action": "Erakutsi profila hala ere",
|
||||
"limited_account_hint.title": "Profil hau ezkutatu egin dute {domain} zerbitzariko moderatzaileek.",
|
||||
"link_preview.author": "Egilea: {name}",
|
||||
"link_preview.more_from_author": "{name} erabiltzaileaz gehiago jakin",
|
||||
"link_preview.shares": "{count, plural, one {{counter} bidalketa} other {{counter} bidalketa}}",
|
||||
|
||||
@ -2,7 +2,6 @@
|
||||
"about.blocks": "کارسازهای نظارت شده",
|
||||
"about.contact": "تماس:",
|
||||
"about.default_locale": "پیشگزیده",
|
||||
"about.disclaimer": "ماستودون نرمافزار آزاد و نشان تجاری یک شرکت غیر انتفاعی با مسئولیت محدود آلمانی است.",
|
||||
"about.domain_blocks.no_reason_available": "دلیلی موجود نیست",
|
||||
"about.domain_blocks.preamble": "ماستودون عموماً میگذارد محتوا را از هر کارساز دیگری در دنیای شبکههای اجتماعی غیرمتمرکز دیده و با آنان برهمکنش داشته باشید. اینها استثناهایی هستند که روی این کارساز خاص وضع شدهاند.",
|
||||
"about.domain_blocks.silenced.explanation": "عموماً نمایهها و محتوا از این کارساز را نمیبینید، مگر این که به طور خاص دنبالشان گشته یا با پی گیری، داوطلب دیدنشان شوید.",
|
||||
@ -833,7 +832,6 @@
|
||||
"lightbox.zoom_in": "بزرگنمایی به اندازهٔ اصلی",
|
||||
"lightbox.zoom_out": "بزرگ نمایی برای برازش",
|
||||
"limited_account_hint.action": "به هر روی نمایه نشان داده شود",
|
||||
"limited_account_hint.title": "این نمایه از سوی ناظمهای {domain} پنهان شده.",
|
||||
"link_preview.author": "از {name}",
|
||||
"link_preview.more_from_author": "بیشتر از {name}",
|
||||
"link_preview.shares": "{count, plural, one {{counter} فرسته} other {{counter} فرسته}}",
|
||||
|
||||
@ -2,7 +2,7 @@
|
||||
"about.blocks": "Moderoidut palvelimet",
|
||||
"about.contact": "Yhteydenotto:",
|
||||
"about.default_locale": "Oletus",
|
||||
"about.disclaimer": "Mastodon on vapaa avoimen lähdekoodin ohjelmisto ja Mastodon gGmbH:n tavaramerkki.",
|
||||
"about.disclaimer": "Mastodon on vapaa avoimen lähdekoodin ohjelmisto ja Mastodon GmbH:n tavaramerkki.",
|
||||
"about.domain_blocks.no_reason_available": "Syy ei ole tiedossa",
|
||||
"about.domain_blocks.preamble": "Mastodonin avulla voi yleensä tarkastella minkä tahansa fediversumiin kuuluvan palvelimen sisältöä ja olla yhteyksissä eri palvelinten käyttäjien kanssa. Nämä poikkeukset koskevat yksin tätä palvelinta.",
|
||||
"about.domain_blocks.silenced.explanation": "Et yleensä näe tämän palvelimen profiileja ja sisältöä, jollet erityisesti etsi juuri sitä tai liity siihen seuraamalla.",
|
||||
@ -646,6 +646,7 @@
|
||||
"empty_column.account_unavailable": "Profiili ei saatavilla",
|
||||
"empty_column.blocks": "Et ole vielä estänyt käyttäjiä.",
|
||||
"empty_column.bookmarked_statuses": "Et ole vielä lisännyt julkaisuja kirjanmerkkeihisi. Kun lisäät yhden, se näkyy tässä.",
|
||||
"empty_column.collections": "{acct} ei ole luonut vielä yhtään kokoelmaa.",
|
||||
"empty_column.collections.featured_in": "Sinua ei ole vielä lisätty mihinkään kokoelmaan.",
|
||||
"empty_column.collections.featured_in_undiscoverable": "Jotta sinut voi lisätä kokoelmiin, esittely löydettävyyskokemuksissa on sallittava kohdassa <link>Asetukset > Yksityisyys ja tavoittavuus</link>",
|
||||
"empty_column.community": "Paikallinen aikajana on tyhjä. Kirjoita jotain julkista, niin homma lähtee käyntiin!",
|
||||
@ -819,6 +820,12 @@
|
||||
"keyboard_shortcuts.heading": "Pikanäppäimet",
|
||||
"keyboard_shortcuts.home": "Avaa kotiaikajana",
|
||||
"keyboard_shortcuts.hotkey": "Pikanäppäin",
|
||||
"keyboard_shortcuts.keys.alt": "Alt",
|
||||
"keyboard_shortcuts.keys.backspace": "Askelpalautin",
|
||||
"keyboard_shortcuts.keys.enter": "Enter",
|
||||
"keyboard_shortcuts.keys.esc": "Esc",
|
||||
"keyboard_shortcuts.keys.page_down": "Page Down",
|
||||
"keyboard_shortcuts.keys.page_up": "Page Up",
|
||||
"keyboard_shortcuts.legend": "Näytä tämä ohje",
|
||||
"keyboard_shortcuts.load_more": "Kohdista ”Lataa lisää” -painikkeeseen",
|
||||
"keyboard_shortcuts.local": "Avaa paikallinen aikajana",
|
||||
@ -1191,6 +1198,7 @@
|
||||
"search_popout.user": "käyttäjä",
|
||||
"search_results.accounts": "Profiilit",
|
||||
"search_results.all": "Kaikki",
|
||||
"search_results.collections": "Kokoelmat",
|
||||
"search_results.hashtags": "Aihetunnisteet",
|
||||
"search_results.no_results": "Ei tuloksia.",
|
||||
"search_results.no_search_yet": "Kokeile hakea julkaisuja, profiileja tai aihetunnisteita.",
|
||||
|
||||
@ -2,7 +2,6 @@
|
||||
"about.blocks": "Mga pinatimping server",
|
||||
"about.contact": "Kontak:",
|
||||
"about.default_locale": "Default",
|
||||
"about.disclaimer": "Ang Mastodon ay software na malaya at bukas-na-pinagmulan, at isang tatak-pangkalakal ng Mastodon gGmbH.",
|
||||
"about.domain_blocks.no_reason_available": "Hindi makuha ang dahilan",
|
||||
"about.domain_blocks.preamble": "Sa kadalasan, hinahayaan ka ng Mastodon na makita ang mga content sa, at makipag-interact sa users ng, ibang servers sa fediverse. Narito ang exceptions na ginawa sa partikular na server na ito.",
|
||||
"about.domain_blocks.silenced.explanation": "Sa kadalasan, hindi mo makikita ang profiles at content mula sa server na ito, maliban na lang kung sasadyain mo silang hanapin o piliing magawa ito sa pamamagitan ng mga sumusunod.",
|
||||
|
||||
@ -2,7 +2,6 @@
|
||||
"about.blocks": "Tálmaðir ambætarar",
|
||||
"about.contact": "Samband:",
|
||||
"about.default_locale": "Sjálvvirði",
|
||||
"about.disclaimer": "Mastodon er fríur ritbúnaður við opnari keldu og eitt vørumerki hjá Mastodon gGmbH.",
|
||||
"about.domain_blocks.no_reason_available": "Ókent orsøk",
|
||||
"about.domain_blocks.preamble": "Yvirhøvur, so loyvir Mastodon tær at síggja innihald frá og at samvirka við brúkarar frá ein og hvørjum ambætara í fediverse. Undantøkini, sum eru gjørd á júst hesum ambætaranum, eru hesi.",
|
||||
"about.domain_blocks.silenced.explanation": "Yvirhøvur, so sært tú ikki vangar og innihald frá hesum ambætaranum, uttan so at tú skilliga leitar hesi upp ella velur tey við at fylgja teimum.",
|
||||
@ -662,7 +661,6 @@
|
||||
"lightbox.zoom_in": "Suma til veruliga stødd",
|
||||
"lightbox.zoom_out": "Suma, so tað passar",
|
||||
"limited_account_hint.action": "Vís vangamynd kortini",
|
||||
"limited_account_hint.title": "Hesin vangin er fjaldur av kjakleiðarunum á {domain}.",
|
||||
"link_preview.author": "Av {name}",
|
||||
"link_preview.more_from_author": "Meira frá {name}",
|
||||
"link_preview.shares": "{count, plural, one {{counter} postur} other {{counter} postar}}",
|
||||
|
||||
@ -2,7 +2,7 @@
|
||||
"about.blocks": "Serveurs modérés",
|
||||
"about.contact": "Contact :",
|
||||
"about.default_locale": "Par défaut",
|
||||
"about.disclaimer": "Mastodon est un logiciel open-source gratuit et une marque déposée de Mastodon gGmbH.",
|
||||
"about.disclaimer": "Mastodon est un logiciel libre, open-source et une marque déposée de Mastodon GmbH.",
|
||||
"about.domain_blocks.no_reason_available": "Raison non disponible",
|
||||
"about.domain_blocks.preamble": "Mastodon vous permet généralement de visualiser le contenu et d'interagir avec des comptes de n'importe quel serveur dans le fediverse. Voici les exceptions qui ont été faites sur ce serveur en particulier.",
|
||||
"about.domain_blocks.silenced.explanation": "Vous ne verrez généralement pas de profils ou de contenu de ce serveur, à moins que vous ne les recherchiez explicitement ou que vous ne choisissiez de les suivre.",
|
||||
@ -646,6 +646,7 @@
|
||||
"empty_column.account_unavailable": "Profil non disponible",
|
||||
"empty_column.blocks": "Vous n’avez bloqué aucun compte pour le moment.",
|
||||
"empty_column.bookmarked_statuses": "Vous n'avez pas de publications parmi vos signets. Lorsque vous en ajouterez une, elle apparaîtra ici.",
|
||||
"empty_column.collections": "{acct} n'a pas encore créé de collection.",
|
||||
"empty_column.collections.featured_in": "Vous n'avez pas encore été ajouté·e à une collection.",
|
||||
"empty_column.collections.featured_in_undiscoverable": "Afin que l'on puisse vous ajouter à des collections, vous devez autoriser d'apparaître dans les expériences de découverte depuis <link>Vie privée et visibilité</link>",
|
||||
"empty_column.community": "Le fil local est vide. Écrivez donc quelque chose pour le remplir!",
|
||||
@ -819,6 +820,12 @@
|
||||
"keyboard_shortcuts.heading": "Raccourcis clavier",
|
||||
"keyboard_shortcuts.home": "Ouvrir le fil d’accueil",
|
||||
"keyboard_shortcuts.hotkey": "Raccourci clavier",
|
||||
"keyboard_shortcuts.keys.alt": "Alt",
|
||||
"keyboard_shortcuts.keys.backspace": "Retour arrière",
|
||||
"keyboard_shortcuts.keys.enter": "Entrée",
|
||||
"keyboard_shortcuts.keys.esc": "Échap",
|
||||
"keyboard_shortcuts.keys.page_down": "Page bas",
|
||||
"keyboard_shortcuts.keys.page_up": "Page haut",
|
||||
"keyboard_shortcuts.legend": "Afficher cette légende",
|
||||
"keyboard_shortcuts.load_more": "Bouton Focus \"Charger plus\"",
|
||||
"keyboard_shortcuts.local": "Ouvrir le fil local",
|
||||
@ -850,7 +857,7 @@
|
||||
"lightbox.zoom_in": "Zoomer sur la taille réelle",
|
||||
"lightbox.zoom_out": "Zoomer pour adapter",
|
||||
"limited_account_hint.action": "Afficher le profil quand même",
|
||||
"limited_account_hint.title": "Ce profil a été masqué par la modération de {domain}.",
|
||||
"limited_account_hint.title": "Ce profil ou serveur a été masqué par l'équipe de modération de {domain}.",
|
||||
"link_preview.author": "Par {name}",
|
||||
"link_preview.more_from_author": "Voir plus de {name}",
|
||||
"link_preview.shares": "{count, plural, one {{counter} message} other {{counter} messages}}",
|
||||
@ -1192,6 +1199,7 @@
|
||||
"search_popout.user": "utilisateur·ice",
|
||||
"search_results.accounts": "Profils",
|
||||
"search_results.all": "Tout",
|
||||
"search_results.collections": "Collections",
|
||||
"search_results.hashtags": "Hashtags",
|
||||
"search_results.no_results": "Aucun résultat.",
|
||||
"search_results.no_search_yet": "Essayez de rechercher des messages, des profils ou des hashtags.",
|
||||
@ -1333,12 +1341,12 @@
|
||||
"upload_button.label": "Ajouter des images, une vidéo ou un fichier audio",
|
||||
"upload_error.limit": "Taille maximale d'envoi de fichier dépassée.",
|
||||
"upload_error.poll": "L’envoi de fichiers n’est pas autorisé avec les sondages.",
|
||||
"upload_error.quote": "L’envoi de fichiers n’est pas autorisé avec les sondages.",
|
||||
"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_end": "La pièce jointe du média {item} a été déplacée.",
|
||||
"upload_error.quote": "L’envoi de fichiers n’est pas autorisé avec les citations.",
|
||||
"upload_form.drag_and_drop.instructions": "Pour choisir un média joint, appuyez sur la touche espace ou entrée. Pour 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": "Déplacement 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éposé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_start": "A récupéré la pièce jointe {item}.",
|
||||
"upload_form.drag_and_drop.on_drag_start": "Sélection de la pièce jointe {item}.",
|
||||
"upload_form.edit": "Modifier",
|
||||
"upload_progress.label": "Envoi en cours...",
|
||||
"upload_progress.processing": "Traitement en cours…",
|
||||
|
||||
@ -2,7 +2,7 @@
|
||||
"about.blocks": "Serveurs modérés",
|
||||
"about.contact": "Contact :",
|
||||
"about.default_locale": "Défaut",
|
||||
"about.disclaimer": "Mastodon est un logiciel libre, open-source et une marque déposée de Mastodon gGmbH.",
|
||||
"about.disclaimer": "Mastodon est un logiciel libre, open-source et une marque déposée de Mastodon GmbH.",
|
||||
"about.domain_blocks.no_reason_available": "Raison non disponible",
|
||||
"about.domain_blocks.preamble": "Mastodon vous permet généralement de visualiser le contenu et d'interagir avec les utilisateurs et utilisatrices de n'importe quel autre serveur dans le fédivers. Voici les exceptions qui ont été faites sur ce serveur.",
|
||||
"about.domain_blocks.silenced.explanation": "Vous ne verrez généralement pas les profils et le contenu de ce serveur, à moins que vous ne les recherchiez explicitement ou que vous ne choisissiez de les suivre.",
|
||||
@ -646,6 +646,7 @@
|
||||
"empty_column.account_unavailable": "Profil non disponible",
|
||||
"empty_column.blocks": "Vous n’avez bloqué aucun compte pour le moment.",
|
||||
"empty_column.bookmarked_statuses": "Vous n'avez pas de message en marque-page. Lorsque vous en ajouterez un, il apparaîtra ici.",
|
||||
"empty_column.collections": "{acct} n'a pas encore créé de collection.",
|
||||
"empty_column.collections.featured_in": "Vous n'avez pas encore été ajouté·e à une collection.",
|
||||
"empty_column.collections.featured_in_undiscoverable": "Afin que l'on puisse vous ajouter à des collections, vous devez autoriser d'apparaître dans les expériences de découverte depuis <link>Vie privée et visibilité</link>",
|
||||
"empty_column.community": "Le fil public local est vide. Écrivez donc quelque chose pour le remplir !",
|
||||
@ -819,6 +820,12 @@
|
||||
"keyboard_shortcuts.heading": "Raccourcis clavier",
|
||||
"keyboard_shortcuts.home": "Ouvrir le fil d’accueil",
|
||||
"keyboard_shortcuts.hotkey": "Raccourci clavier",
|
||||
"keyboard_shortcuts.keys.alt": "Alt",
|
||||
"keyboard_shortcuts.keys.backspace": "Retour arrière",
|
||||
"keyboard_shortcuts.keys.enter": "Entrée",
|
||||
"keyboard_shortcuts.keys.esc": "Échap",
|
||||
"keyboard_shortcuts.keys.page_down": "Page bas",
|
||||
"keyboard_shortcuts.keys.page_up": "Page haut",
|
||||
"keyboard_shortcuts.legend": "Afficher cet aide-mémoire",
|
||||
"keyboard_shortcuts.load_more": "Bouton Focus \"Charger plus\"",
|
||||
"keyboard_shortcuts.local": "Ouvrir le fil public local",
|
||||
@ -850,7 +857,7 @@
|
||||
"lightbox.zoom_in": "Zoomer sur la taille réelle",
|
||||
"lightbox.zoom_out": "Zoomer pour adapter",
|
||||
"limited_account_hint.action": "Afficher le profil quand même",
|
||||
"limited_account_hint.title": "Ce profil a été masqué par la modération de {domain}.",
|
||||
"limited_account_hint.title": "Ce profil ou serveur a été masqué par l'équipe de modération de {domain}.",
|
||||
"link_preview.author": "Par {name}",
|
||||
"link_preview.more_from_author": "Voir plus de {name}",
|
||||
"link_preview.shares": "{count, plural, one {{counter} message} other {{counter} messages}}",
|
||||
@ -1192,6 +1199,7 @@
|
||||
"search_popout.user": "utilisateur·ice",
|
||||
"search_results.accounts": "Profils",
|
||||
"search_results.all": "Tous les résultats",
|
||||
"search_results.collections": "Collections",
|
||||
"search_results.hashtags": "Hashtags",
|
||||
"search_results.no_results": "Aucun résultat.",
|
||||
"search_results.no_search_yet": "Essayez de rechercher des messages, des profils ou des hashtags.",
|
||||
@ -1333,12 +1341,12 @@
|
||||
"upload_button.label": "Ajouter des images, une vidéo ou un fichier audio",
|
||||
"upload_error.limit": "Taille maximale d'envoi de fichier dépassée.",
|
||||
"upload_error.poll": "L’envoi de fichiers n’est pas autorisé avec les sondages.",
|
||||
"upload_error.quote": "L’envoi de fichiers n’est pas autorisé avec les sondages.",
|
||||
"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_end": "La pièce jointe du média {item} a été déplacée.",
|
||||
"upload_error.quote": "L’envoi de fichiers n’est pas autorisé avec les citations.",
|
||||
"upload_form.drag_and_drop.instructions": "Pour choisir un média joint, appuyez sur la touche espace ou entrée. Pour 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": "Déplacement 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éposé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_start": "A récupéré la pièce jointe {item}.",
|
||||
"upload_form.drag_and_drop.on_drag_start": "Sélection de la pièce jointe {item}.",
|
||||
"upload_form.edit": "Modifier",
|
||||
"upload_progress.label": "Envoi en cours…",
|
||||
"upload_progress.processing": "En cours…",
|
||||
|
||||
@ -2,7 +2,6 @@
|
||||
"about.blocks": "Moderearre servers",
|
||||
"about.contact": "Kontakt:",
|
||||
"about.default_locale": "Standert",
|
||||
"about.disclaimer": "Mastodon is frije, iepenboarnesoftware en in hannelsmerk fan Mastodon gGmbH.",
|
||||
"about.domain_blocks.no_reason_available": "Reden net beskikber",
|
||||
"about.domain_blocks.preamble": "Yn it algemien kinne jo mei Mastodon berjochten ûntfange fan, en ynteraksje hawwe mei brûkers fan elke server yn de fediverse. Dit binne de útsûnderingen dy’t op dizze spesifike server jilde.",
|
||||
"about.domain_blocks.silenced.explanation": "Yn it algemien sjogge jo gjin berjochten en accounts fan dizze server, útsein as jo berjochten eksplisyt opsikje of derfoar kieze om in account fan dizze server te folgjen.",
|
||||
@ -443,7 +442,6 @@
|
||||
"lightbox.zoom_in": "Oarspronklike grutte toane",
|
||||
"lightbox.zoom_out": "Passend toane",
|
||||
"limited_account_hint.action": "Profyl dochs besjen",
|
||||
"limited_account_hint.title": "Dit profyl is troch de behearders fan {domain} ferstoppe.",
|
||||
"link_preview.author": "Troch {name}",
|
||||
"link_preview.more_from_author": "Mear fan {name}",
|
||||
"link_preview.shares": "{count, plural, one {{counter} berjocht} other {{counter} berjochten}}",
|
||||
|
||||
@ -2,7 +2,7 @@
|
||||
"about.blocks": "Freastalaithe modhnaithe",
|
||||
"about.contact": "Teagmháil:",
|
||||
"about.default_locale": "Réamhshocrú",
|
||||
"about.disclaimer": "Bogearra foinse oscailte saor in aisce is ea Mastodon, agus is le Mastodon gGmbH an trádmharc.",
|
||||
"about.disclaimer": "Is bogearraí foinse oscailte saor in aisce é Mastodon, agus trádmharc de chuid Mastodon GmbH.",
|
||||
"about.domain_blocks.no_reason_available": "Cúis nach bhfuil ar fáil",
|
||||
"about.domain_blocks.preamble": "Go ginearálta, tugann Mastodon deis duit ábhar a fheiceáil ó aon fhreastalaí eile sa fediverse agus idirghníomhú leo. Seo iad na heisceachtaí atá déanta ar an bhfreastalaí seo.",
|
||||
"about.domain_blocks.silenced.explanation": "Go hiondúil ní fheicfidh tú próifílí ná inneachar ón bhfreastalaí seo, ach amháin má bhíonn tú á lorg nó má ghlacann tú lena leanúint d'aon ghnó.",
|
||||
@ -646,6 +646,7 @@
|
||||
"empty_column.account_unavailable": "Níl an phróifíl ar fáil",
|
||||
"empty_column.blocks": "Níl aon úsáideoir bactha agat fós.",
|
||||
"empty_column.bookmarked_statuses": "Níl aon phostáil leabharmharcaithe agat fós. Nuair a dhéanann tú leabharmharc, beidh sé le feiceáil anseo.",
|
||||
"empty_column.collections": "Níl aon bhailiúcháin cruthaithe ag {acct} go fóill.",
|
||||
"empty_column.collections.featured_in": "Níor cuireadh le haon bhailiúchán thú go fóill.",
|
||||
"empty_column.collections.featured_in_undiscoverable": "Chun go mbeidh daoine in ann tú a chur le bailiúcháin, ní mór duit cead a thabhairt duit a bheith le feiceáil i dtaithí fionnachtana ó <link>Sainroghanna > Príobháideacht agus raon feidhme</link>",
|
||||
"empty_column.community": "Tá an amlíne áitiúil folamh. Foilsigh rud éigin go poiblí le tús a chur le cúrsaí!",
|
||||
@ -819,6 +820,12 @@
|
||||
"keyboard_shortcuts.heading": "Aicearraí méarchláir",
|
||||
"keyboard_shortcuts.home": "Oscail amlíne bhaile",
|
||||
"keyboard_shortcuts.hotkey": "Eochair aicearra",
|
||||
"keyboard_shortcuts.keys.alt": "Alt",
|
||||
"keyboard_shortcuts.keys.backspace": "Cúlspás",
|
||||
"keyboard_shortcuts.keys.enter": "Iontráil",
|
||||
"keyboard_shortcuts.keys.esc": "Esc",
|
||||
"keyboard_shortcuts.keys.page_down": "Leathanach Síos",
|
||||
"keyboard_shortcuts.keys.page_up": "Leathanach Suas",
|
||||
"keyboard_shortcuts.legend": "Taispeáin an finscéal seo",
|
||||
"keyboard_shortcuts.load_more": "Dírigh ar an gcnaipe \"Lódáil níos mó\"",
|
||||
"keyboard_shortcuts.local": "Oscail an amlíne áitiúil",
|
||||
@ -850,7 +857,7 @@
|
||||
"lightbox.zoom_in": "Súmáil chuig an méid iarbhír",
|
||||
"lightbox.zoom_out": "Súmáil a d'oirfeadh",
|
||||
"limited_account_hint.action": "Taispeáin an phróifíl ar aon nós",
|
||||
"limited_account_hint.title": "Tá an phróifíl seo curtha i bhfolach ag na modhnóra {domain}.",
|
||||
"limited_account_hint.title": "Tá an phróifíl nó an freastalaí seo i bhfolach ag modhnóirí {domain}.",
|
||||
"link_preview.author": "Le {name}",
|
||||
"link_preview.more_from_author": "Tuilleadh ó {name}",
|
||||
"link_preview.shares": "{count, plural, one {{counter} post} other {{counter} poist}}",
|
||||
@ -1192,6 +1199,7 @@
|
||||
"search_popout.user": "úsáideoir",
|
||||
"search_results.accounts": "Próifílí",
|
||||
"search_results.all": "Gach",
|
||||
"search_results.collections": "Bailiúcháin",
|
||||
"search_results.hashtags": "Haischlib",
|
||||
"search_results.no_results": "Gan torthaí.",
|
||||
"search_results.no_search_yet": "Bain triail as postálacha, próifílí nó hashtags a chuardach.",
|
||||
|
||||
@ -2,7 +2,6 @@
|
||||
"about.blocks": "Frithealaichean fo mhaorsainneachd",
|
||||
"about.contact": "Fios thugainn:",
|
||||
"about.default_locale": "Bun-roghainn",
|
||||
"about.disclaimer": "’S e bathar-bog saor le bun-tùs fosgailte a th’ ann am Mastodon agus ’na chomharra-mhalairt aig Mastodon gGmbH.",
|
||||
"about.domain_blocks.no_reason_available": "Chan eil an t-adhbhar ga thoirt seachad",
|
||||
"about.domain_blocks.preamble": "San fharsaingeachd, leigidh Mastodon leat susbaint o fhrithealaiche sam bith sa cho-shaoghal a shealltainn agus eadar-ghìomh a ghabhail leis na cleachdaichean uapa-san. Seo na h-easgaidhean a tha an sàs air an fhrithealaiche shònraichte seo.",
|
||||
"about.domain_blocks.silenced.explanation": "San fharsaingeachd, chan fhaic thu pròifilean agus susbaint an fhrithealaiche seo ach ma nì thu lorg no ma tha thu ga leantainn.",
|
||||
@ -824,7 +823,6 @@
|
||||
"lightbox.zoom_in": "Sùm dhan fhìor-mheud",
|
||||
"lightbox.zoom_out": "Sùm fèin-obrachail",
|
||||
"limited_account_hint.action": "Seall a’ phròifil co-dhiù",
|
||||
"limited_account_hint.title": "Chaidh a’ phròifil seo fhalach le maoir {domain}.",
|
||||
"link_preview.author": "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}}",
|
||||
|
||||
@ -2,7 +2,7 @@
|
||||
"about.blocks": "Servidores suxeitos a moderación",
|
||||
"about.contact": "Contacto:",
|
||||
"about.default_locale": "Por defecto",
|
||||
"about.disclaimer": "Mastodon é software libre, de código aberto, e unha marca comercial de Mastodon gGmbH.",
|
||||
"about.disclaimer": "Mastodon é software libre, de código aberto, e unha marca comercial de Mastodon GmbH.",
|
||||
"about.domain_blocks.no_reason_available": "Motivo non indicado",
|
||||
"about.domain_blocks.preamble": "Mastodon de xeito xeral permíteche ver contidos doutros servidores do fediverso e interactuar coas súas usuarias. Estas son as excepcións que se estabeleceron neste servidor en particular.",
|
||||
"about.domain_blocks.silenced.explanation": "Por defecto non verás perfís e contido desde este servidor, a menos que mires de xeito explícito ou optes por seguir ese contido ou usuaria.",
|
||||
@ -646,6 +646,7 @@
|
||||
"empty_column.account_unavailable": "Perfil non dispoñible",
|
||||
"empty_column.blocks": "Aínda non bloqueaches a ningún usuaria.",
|
||||
"empty_column.bookmarked_statuses": "Aínda non marcaches ningunha publicación. Cando o fagas, aparecerán aquí.",
|
||||
"empty_column.collections": "{acct} aínda non creou ningunha colección.",
|
||||
"empty_column.collections.featured_in": "Non te engadiron a ningunha colección.",
|
||||
"empty_column.collections.featured_in_undiscoverable": "Para que outras persoas poidan engadirte ás súas coleccións ter que conceder permiso para descubrimento da conta en <link>Preferencias > Privacidade e alcance</link>",
|
||||
"empty_column.community": "A cronoloxía local está baleira. Escribe algo de xeito público para espallalo!",
|
||||
@ -819,6 +820,12 @@
|
||||
"keyboard_shortcuts.heading": "Atallos do teclado",
|
||||
"keyboard_shortcuts.home": "Para abrir a cronoloxía inicial",
|
||||
"keyboard_shortcuts.hotkey": "Tecla de atallo",
|
||||
"keyboard_shortcuts.keys.alt": "Alt",
|
||||
"keyboard_shortcuts.keys.backspace": "Retroceso",
|
||||
"keyboard_shortcuts.keys.enter": "Entrar",
|
||||
"keyboard_shortcuts.keys.esc": "Escape",
|
||||
"keyboard_shortcuts.keys.page_down": "Frecha abaixo",
|
||||
"keyboard_shortcuts.keys.page_up": "Frecha arriba",
|
||||
"keyboard_shortcuts.legend": "Para amosar esta lenda",
|
||||
"keyboard_shortcuts.load_more": "Foco no botón \"Cargar máis\"",
|
||||
"keyboard_shortcuts.local": "Para abrir a cronoloxía local",
|
||||
@ -850,7 +857,7 @@
|
||||
"lightbox.zoom_in": "Ver tamaño real",
|
||||
"lightbox.zoom_out": "Ver tamaño axustado",
|
||||
"limited_account_hint.action": "Mostrar perfil igualmente",
|
||||
"limited_account_hint.title": "Este perfil foi agochado pola moderación de {domain}.",
|
||||
"limited_account_hint.title": "A moderación de {domain} ocultou este perfil ou servidor.",
|
||||
"link_preview.author": "Por {name}",
|
||||
"link_preview.more_from_author": "Máis de {name}",
|
||||
"link_preview.shares": "{count, plural, one {{counter} publicación} other {{counter} publicacións}}",
|
||||
@ -1192,6 +1199,7 @@
|
||||
"search_popout.user": "usuaria",
|
||||
"search_results.accounts": "Perfís",
|
||||
"search_results.all": "Todo",
|
||||
"search_results.collections": "Coleccións",
|
||||
"search_results.hashtags": "Cancelos",
|
||||
"search_results.no_results": "Sen resultados.",
|
||||
"search_results.no_search_yet": "Intenta buscando publicacións, perfís ou cancelos.",
|
||||
|
||||
@ -2,7 +2,6 @@
|
||||
"about.blocks": "שרתים תחת פיקוח תוכן",
|
||||
"about.contact": "יצירת קשר:",
|
||||
"about.default_locale": "ברירת המחדל",
|
||||
"about.disclaimer": "מסטודון היא תוכנת קוד פתוח חינמית וסימן מסחרי של Mastodon gGmbH.",
|
||||
"about.domain_blocks.no_reason_available": "הסיבה אינה זמינה",
|
||||
"about.domain_blocks.preamble": "ככלל מסטודון מאפשרת לך לצפות בתוכן ולתקשר עם משתמשים מכל שרת בפדיברס. אלו הם היוצאים מן הכלל שהוגדרו עבור השרת המסוים הזה.",
|
||||
"about.domain_blocks.silenced.explanation": "ככלל פרופילים ותוכן משרת זה לא יוצגו, אלא אם חיפשת אותם באופן מפורש או בחרת להשתתף בו על ידי מעקב.",
|
||||
@ -850,7 +849,7 @@
|
||||
"lightbox.zoom_in": "הגדלה לגודל מלא",
|
||||
"lightbox.zoom_out": "התאמה לגודל המסך",
|
||||
"limited_account_hint.action": "הצג חשבון בכל זאת",
|
||||
"limited_account_hint.title": "פרופיל המשתמש הזה הוסתר על ידי מנחי הדיון של {domain}.",
|
||||
"limited_account_hint.title": "פרופיל המשתמש או השרת כולו הוסתר על ידי מנחי הדיון של {domain}.",
|
||||
"link_preview.author": "מאת {name}",
|
||||
"link_preview.more_from_author": "עוד מאת {name}",
|
||||
"link_preview.shares": "{count, plural, one {הודעה אחת} two {הודעותיים} many {{counter} הודעות} other {{counter} הודעות}}",
|
||||
|
||||
@ -2,7 +2,6 @@
|
||||
"about.blocks": "मॉडरेट सर्वर",
|
||||
"about.contact": "कांटेक्ट:",
|
||||
"about.default_locale": "Default",
|
||||
"about.disclaimer": "मास्टोडन एक ओपन सोर्स सॉफ्टवेयर है, और मास्टोडन gGmbH का ट्रेडमार्क है।",
|
||||
"about.domain_blocks.no_reason_available": "कारण उपलब्ध नहीं है!",
|
||||
"about.domain_blocks.preamble": "मास्टोडन आम तौर पर आपको कंटेंट को देखने और फेडिवेर्से में किसी अन्य सर्वर से उपयोगकर्ताओं के साथ बातचीत करने की अनुमति देता है। ये अपवाद हैं जो इस विशेष सर्वर पर बनाए गए हैं।",
|
||||
"about.domain_blocks.silenced.explanation": "आप आमतौर पर इस सर्वर से प्रोफ़ाइल और कंटेंट नहीं देख पाएंगे, जब तक कि आप इसे स्पष्ट रूप से नहीं देखते या इसका अनुसरण करके इसका चयन नहीं करते।",
|
||||
@ -326,7 +325,6 @@
|
||||
"lightbox.next": "अगला",
|
||||
"lightbox.previous": "पिछला",
|
||||
"limited_account_hint.action": "फिर भी प्रोफाइल दिखाओ",
|
||||
"limited_account_hint.title": "यह प्रोफ़ाइल {domain} के मॉडरेटर द्वारा छिपाई गई है.",
|
||||
"lists.delete": "सूची हटाएँ",
|
||||
"lists.edit": "सूची संपादित करें",
|
||||
"lists.replies_policy.followed": "अन्य फोल्लोवेद यूजर",
|
||||
|
||||
@ -1,7 +1,6 @@
|
||||
{
|
||||
"about.blocks": "Moderirani poslužitelji",
|
||||
"about.contact": "Kontakt:",
|
||||
"about.disclaimer": "Mastodon je besplatan softver otvorenog koda i zaštitni znak tvrtke Mastodon gGmbH.",
|
||||
"about.domain_blocks.no_reason_available": "Razlog nije dostupan",
|
||||
"about.domain_blocks.preamble": "Mastodon vam općenito omogućuje pregled sadržaja i interakciju s korisnicima s bilo kojeg drugog poslužitelja u fediverse. Ovo su iznimke napravljene na ovom poslužitelju.",
|
||||
"about.domain_blocks.silenced.explanation": "Obično nećete vidjeti profile i sadržaj s ovog poslužitelja, osim ako ga izričito ne potražite ili uključite u njega slijedeći ga.",
|
||||
|
||||
@ -2,7 +2,6 @@
|
||||
"about.blocks": "Moderált kiszolgálók",
|
||||
"about.contact": "Kapcsolat:",
|
||||
"about.default_locale": "Alapértelmezett",
|
||||
"about.disclaimer": "A Mastodon ingyenes, nyílt forráskódú szoftver, a Mastodon gGmbH védjegye.",
|
||||
"about.domain_blocks.no_reason_available": "Nem áll rendelkezésre indoklás",
|
||||
"about.domain_blocks.preamble": "A Mastodon általában mindenféle tartalomcserét és interakciót lehetővé tesz bármelyik másik kiszolgálóval a födiverzumban. Ezek azok a kivételek, amelyek a mi kiszolgálónkon érvényben vannak.",
|
||||
"about.domain_blocks.silenced.explanation": "Általában nem fogsz profilokat és tartalmat látni erről a kiszolgálóról, hacsak közvetlenül fel nem keresed vagy követed.",
|
||||
@ -850,7 +849,7 @@
|
||||
"lightbox.zoom_in": "Nagyítás a tényleges méretre",
|
||||
"lightbox.zoom_out": "Méretre igazítás",
|
||||
"limited_account_hint.action": "Profil megjelenítése mindenképpen",
|
||||
"limited_account_hint.title": "Ezt a profilt {domain} moderátorai elrejtették.",
|
||||
"limited_account_hint.title": "Ezt a profilt vagy kiszolgálót a(z) {domain} moderátorai elrejtették.",
|
||||
"link_preview.author": "{name} szerint",
|
||||
"link_preview.more_from_author": "Több tőle: {name}",
|
||||
"link_preview.shares": "{count, plural, one {{counter} bejegyzés} other {{counter} bejegyzés}}",
|
||||
|
||||
@ -1,7 +1,6 @@
|
||||
{
|
||||
"about.blocks": "Մոդերացուող սպասարկիչներ",
|
||||
"about.contact": "Կապ՝",
|
||||
"about.disclaimer": "Մաստոդոնը ազատ, բաց ելակոդով ծրագրակազմ է, յայտնի Mastodon gGmbH ապրանքանշանով։",
|
||||
"about.domain_blocks.silenced.title": "Սահմանափակ",
|
||||
"about.domain_blocks.suspended.title": "Սպասող",
|
||||
"about.not_available": "Այս տեղեկութիւնը տեսանելի չի այս սերուերում։",
|
||||
|
||||
@ -2,7 +2,6 @@
|
||||
"about.blocks": "Servitores moderate",
|
||||
"about.contact": "Contacto:",
|
||||
"about.default_locale": "Default",
|
||||
"about.disclaimer": "Mastodon es software libere, de codice aperte, e un marca de Mastodon gGmbH.",
|
||||
"about.domain_blocks.no_reason_available": "Ration non disponibile",
|
||||
"about.domain_blocks.preamble": "Mastodon generalmente permitte vider le contento de, e interager con, usatores de qualcunque altere servitor in le fediverso. Istes es le exceptiones que ha essite facite sur iste servitor particular.",
|
||||
"about.domain_blocks.silenced.explanation": "Generalmente, tu non videra le profilos e le contento de iste servitor, excepte si tu expressemente cerca le contento o seque le profilos.",
|
||||
@ -483,7 +482,6 @@
|
||||
"lightbox.zoom_in": "Aggrandir al dimension real",
|
||||
"lightbox.zoom_out": "Diminuer pro adaptar",
|
||||
"limited_account_hint.action": "Monstrar profilo in omne caso",
|
||||
"limited_account_hint.title": "Iste profilo ha essite celate per le moderatores de {domain}.",
|
||||
"link_preview.author": "Per {name}",
|
||||
"link_preview.more_from_author": "Plus de {name}",
|
||||
"link_preview.shares": "{count, plural, one {{counter} message} other {{counter} messages}}",
|
||||
|
||||
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