Merge pull request #3472 from glitch-soc/glitch-soc/merge-upstream
Merge upstream changes up to 73fc8d34d98a80cfb32d3c7c5dc4794fad5470d5
This commit is contained in:
commit
96aa458d22
200
Dockerfile
200
Dockerfile
@ -25,8 +25,8 @@ FROM ${BASE_REGISTRY}/node:${NODE_MAJOR_VERSION}-${DEBIAN_VERSION}-slim AS node
|
||||
FROM ${BASE_REGISTRY}/ruby:${RUBY_VERSION}-slim-${DEBIAN_VERSION} AS ruby
|
||||
|
||||
# Resulting version string is vX.X.X-MASTODON_VERSION_PRERELEASE+MASTODON_VERSION_METADATA
|
||||
# Example: v4.3.0-nightly.2023.11.09+pr-123456
|
||||
# Overwrite existence of 'alpha.X' in version.rb [--build-arg MASTODON_VERSION_PRERELEASE="nightly.2023.11.09"]
|
||||
# Example: v4.3.0-nightly.2023-11-09+pr-123456
|
||||
# Overwrite existence of 'alpha.X' in version.rb [--build-arg MASTODON_VERSION_PRERELEASE="nightly.2023-11-09"]
|
||||
ARG MASTODON_VERSION_PRERELEASE=""
|
||||
# Append build metadata or fork information to version.rb [--build-arg MASTODON_VERSION_METADATA="pr-123456"]
|
||||
ARG MASTODON_VERSION_METADATA=""
|
||||
@ -48,29 +48,27 @@ ARG GID="991"
|
||||
|
||||
# Apply Mastodon build options based on options above
|
||||
ENV \
|
||||
# Apply Mastodon version information
|
||||
MASTODON_VERSION_PRERELEASE="${MASTODON_VERSION_PRERELEASE}" \
|
||||
MASTODON_VERSION_METADATA="${MASTODON_VERSION_METADATA}" \
|
||||
SOURCE_COMMIT="${SOURCE_COMMIT}" \
|
||||
# Apply Mastodon static files and YJIT options
|
||||
RAILS_SERVE_STATIC_FILES=${RAILS_SERVE_STATIC_FILES} \
|
||||
RUBY_YJIT_ENABLE=${RUBY_YJIT_ENABLE} \
|
||||
# Apply timezone
|
||||
TZ=${TZ}
|
||||
RAILS_SERVE_STATIC_FILES="${RAILS_SERVE_STATIC_FILES}" \
|
||||
RUBY_YJIT_ENABLE="${RUBY_YJIT_ENABLE}" \
|
||||
TZ="${TZ}"
|
||||
|
||||
# Configure runtime environment
|
||||
# BIND: IP to bind Mastodon to when serving traffic
|
||||
# NODE_ENV/RAILS_ENV: production settings for Node.js and Ruby on Rails
|
||||
# DEBIAN_FRONTEND: suppress interactive prompts
|
||||
# PATH: add Ruby and Mastodon installation directories
|
||||
# MALLOC_CONF: optimize jemalloc 5.x performance
|
||||
# MASTODON_SIDEKIQ_READY_FILENAME: Sidekiq readiness check filename for Kubernetes
|
||||
ENV \
|
||||
# Configure the IP to bind Mastodon to when serving traffic
|
||||
BIND="0.0.0.0" \
|
||||
# Use production settings for Yarn, Node.js and related tools
|
||||
NODE_ENV="production" \
|
||||
# Use production settings for Ruby on Rails
|
||||
RAILS_ENV="production" \
|
||||
# Add Ruby and Mastodon installation to the PATH
|
||||
DEBIAN_FRONTEND="noninteractive" \
|
||||
PATH="${PATH}:/opt/ruby/bin:/opt/mastodon/bin" \
|
||||
# Optimize jemalloc 5.x performance
|
||||
MALLOC_CONF="narenas:2,background_thread:true,thp:never,dirty_decay_ms:1000,muzzy_decay_ms:0" \
|
||||
# Sidekiq will touch tmp/sidekiq_process_has_started_and_will_begin_processing_jobs to indicate it is ready. This can be used for a readiness check in Kubernetes
|
||||
MASTODON_SIDEKIQ_READY_FILENAME=sidekiq_process_has_started_and_will_begin_processing_jobs
|
||||
|
||||
# Set default shell used for running commands
|
||||
@ -99,10 +97,10 @@ RUN \
|
||||
# Mount Apt cache and lib directories from Docker buildx caches
|
||||
--mount=type=cache,id=apt-cache-${TARGETPLATFORM},target=/var/cache/apt,sharing=locked \
|
||||
--mount=type=cache,id=apt-lib-${TARGETPLATFORM},target=/var/lib/apt,sharing=locked \
|
||||
# Apt update & upgrade to check for security updates to Debian image
|
||||
# Update package list and upgrade system packages
|
||||
apt-get update; \
|
||||
apt-get dist-upgrade -yq; \
|
||||
# Install jemalloc, curl and other necessary components
|
||||
# Install jemalloc and other necessary components
|
||||
apt-get install -y --no-install-recommends \
|
||||
curl \
|
||||
file \
|
||||
@ -112,6 +110,42 @@ RUN \
|
||||
tini \
|
||||
tzdata \
|
||||
wget \
|
||||
# Mastodon components
|
||||
libexpat1 \
|
||||
libglib2.0-0t64 \
|
||||
libicu76 \
|
||||
libidn12 \
|
||||
libpq5 \
|
||||
libreadline8t64 \
|
||||
libssl3t64 \
|
||||
libyaml-0-2 \
|
||||
# libvips components
|
||||
libcgif0 \
|
||||
libexif12 \
|
||||
libheif1 \
|
||||
libhwy1t64 \
|
||||
libimagequant0 \
|
||||
libjpeg62-turbo \
|
||||
liblcms2-2 \
|
||||
libspng0 \
|
||||
libtiff6 \
|
||||
libwebp7 \
|
||||
libwebpdemux2 \
|
||||
libwebpmux3 \
|
||||
# ffmpeg components
|
||||
libdav1d7 \
|
||||
libmp3lame0 \
|
||||
libopencore-amrnb0 \
|
||||
libopencore-amrwb0 \
|
||||
libopus0 \
|
||||
libsnappy1v5 \
|
||||
libtheora0 \
|
||||
libvorbis0a \
|
||||
libvorbisenc2 \
|
||||
libvorbisfile3 \
|
||||
libvpx9 \
|
||||
libx264-164 \
|
||||
libx265-215 \
|
||||
; \
|
||||
# Patch Ruby to use jemalloc
|
||||
patchelf --add-needed libjemalloc.so.2 /usr/local/bin/ruby; \
|
||||
@ -120,42 +154,37 @@ RUN \
|
||||
patchelf \
|
||||
;
|
||||
|
||||
# Create temporary build layer from base image
|
||||
FROM ruby AS build
|
||||
# Build stage for media libraries (libvips, ffmpeg)
|
||||
FROM ${BASE_REGISTRY}/ruby:${RUBY_VERSION}-slim-${DEBIAN_VERSION} AS media-build
|
||||
|
||||
ARG TARGETPLATFORM
|
||||
|
||||
# Set default shell used for running commands
|
||||
SHELL ["/bin/bash", "-o", "pipefail", "-o", "errexit", "-c"]
|
||||
|
||||
# hadolint ignore=DL3008
|
||||
RUN \
|
||||
# Mount Apt cache and lib directories from Docker buildx caches
|
||||
--mount=type=cache,id=apt-cache-${TARGETPLATFORM},target=/var/cache/apt,sharing=locked \
|
||||
--mount=type=cache,id=apt-lib-${TARGETPLATFORM},target=/var/lib/apt,sharing=locked \
|
||||
# Install build tools and bundler dependencies from APT
|
||||
--mount=type=cache,id=apt-native-cache-${TARGETPLATFORM},target=/var/cache/apt,sharing=locked \
|
||||
--mount=type=cache,id=apt-native-lib-${TARGETPLATFORM},target=/var/lib/apt,sharing=locked \
|
||||
# Remove automatic apt cache Docker cleanup scripts
|
||||
rm -f /etc/apt/apt.conf.d/docker-clean; \
|
||||
# Install build tools for native libraries
|
||||
apt-get update; \
|
||||
apt-get install -y --no-install-recommends \
|
||||
autoconf \
|
||||
automake \
|
||||
build-essential \
|
||||
cmake \
|
||||
git \
|
||||
libgdbm-dev \
|
||||
libglib2.0-dev \
|
||||
libgmp-dev \
|
||||
libicu-dev \
|
||||
libidn-dev \
|
||||
libpq-dev \
|
||||
libssl-dev \
|
||||
libtool \
|
||||
libyaml-dev \
|
||||
meson \
|
||||
nasm \
|
||||
pkg-config \
|
||||
shared-mime-info \
|
||||
xz-utils \
|
||||
# libvips components
|
||||
libcgif-dev \
|
||||
libexif-dev \
|
||||
libexpat1-dev \
|
||||
libgirepository1.0-dev \
|
||||
libglib2.0-dev \
|
||||
libheif-dev \
|
||||
libhwy-dev \
|
||||
libimagequant-dev \
|
||||
@ -176,8 +205,8 @@ RUN \
|
||||
libx265-dev \
|
||||
;
|
||||
|
||||
# Create temporary libvips specific build layer from build layer
|
||||
FROM build AS libvips
|
||||
# Create temporary libvips specific build layer
|
||||
FROM media-build AS libvips
|
||||
|
||||
# libvips version to compile, change with [--build-arg VIPS_VERSION="8.15.2"]
|
||||
# renovate: datasource=github-releases depName=libvips packageName=libvips/libvips
|
||||
@ -192,15 +221,16 @@ RUN tar xf vips-${VIPS_VERSION}.tar.xz;
|
||||
|
||||
WORKDIR /usr/local/libvips/src/vips-${VIPS_VERSION}
|
||||
|
||||
# Configure and compile libvips
|
||||
RUN \
|
||||
meson setup build --prefix /usr/local/libvips --libdir=lib -Ddeprecated=false -Dintrospection=disabled -Dmodules=disabled -Dexamples=false; \
|
||||
cd build; \
|
||||
ninja; \
|
||||
ninja install;
|
||||
# Configure libvips
|
||||
RUN meson setup build --prefix /usr/local/libvips --libdir=lib -Ddeprecated=false -Dintrospection=disabled -Dmodules=disabled -Dexamples=false
|
||||
|
||||
# Create temporary ffmpeg specific build layer from build layer
|
||||
FROM build AS ffmpeg
|
||||
WORKDIR /usr/local/libvips/src/vips-${VIPS_VERSION}/build
|
||||
|
||||
# Compile and install libvips
|
||||
RUN ninja && ninja install
|
||||
|
||||
# Create temporary ffmpeg specific build layer
|
||||
FROM media-build AS ffmpeg
|
||||
|
||||
# ffmpeg version to compile, change with [--build-arg FFMPEG_VERSION="7.0.x"]
|
||||
# renovate: datasource=repology depName=ffmpeg packageName=openpkg_current/ffmpeg
|
||||
@ -241,17 +271,48 @@ RUN \
|
||||
--enable-shared \
|
||||
--enable-version3 \
|
||||
; \
|
||||
make -j$(nproc); \
|
||||
make -j"$(nproc)"; \
|
||||
make install;
|
||||
|
||||
# Create temporary build layer from base image for Ruby dependencies
|
||||
FROM ruby AS ruby-build
|
||||
|
||||
ARG TARGETPLATFORM
|
||||
|
||||
# hadolint ignore=DL3008
|
||||
RUN \
|
||||
# Mount Apt cache and lib directories from Docker buildx caches
|
||||
--mount=type=cache,id=apt-cache-${TARGETPLATFORM},target=/var/cache/apt,sharing=locked \
|
||||
--mount=type=cache,id=apt-lib-${TARGETPLATFORM},target=/var/lib/apt,sharing=locked \
|
||||
# Install build tools and bundler dependencies from APT
|
||||
apt-get install -y --no-install-recommends \
|
||||
build-essential \
|
||||
git \
|
||||
libgdbm-dev \
|
||||
libgmp-dev \
|
||||
libicu-dev \
|
||||
libidn-dev \
|
||||
libpq-dev \
|
||||
libssl-dev \
|
||||
libyaml-dev \
|
||||
shared-mime-info \
|
||||
zlib1g-dev \
|
||||
;
|
||||
|
||||
# Create temporary bundler specific build layer from build layer
|
||||
FROM build AS bundler
|
||||
FROM ruby-build AS bundler
|
||||
|
||||
ARG TARGETPLATFORM
|
||||
|
||||
# Copy Gemfile config into working directory
|
||||
COPY Gemfile* /opt/mastodon/
|
||||
|
||||
# Copy libvips for gems that need it during install
|
||||
COPY --from=libvips /usr/local/libvips/lib /usr/local/lib
|
||||
COPY --from=libvips /usr/local/libvips/include /usr/local/include
|
||||
|
||||
RUN ldconfig
|
||||
|
||||
RUN \
|
||||
# Mount Ruby Gem caches
|
||||
--mount=type=cache,id=gem-cache-${TARGETPLATFORM},target=/usr/local/bundle/cache/,sharing=locked \
|
||||
@ -267,7 +328,7 @@ RUN \
|
||||
bundle install -j"$(nproc)";
|
||||
|
||||
# Create temporary assets build layer from build layer
|
||||
FROM build AS precompiler
|
||||
FROM ruby-build AS precompiler
|
||||
|
||||
ARG TARGETPLATFORM
|
||||
|
||||
@ -311,53 +372,6 @@ FROM ruby AS mastodon
|
||||
|
||||
ARG TARGETPLATFORM
|
||||
|
||||
# hadolint ignore=DL3008
|
||||
RUN \
|
||||
# Mount Apt cache and lib directories from Docker buildx caches
|
||||
--mount=type=cache,id=apt-cache-${TARGETPLATFORM},target=/var/cache/apt,sharing=locked \
|
||||
--mount=type=cache,id=apt-lib-${TARGETPLATFORM},target=/var/lib/apt,sharing=locked \
|
||||
# Mount Corepack and Yarn caches from Docker buildx caches
|
||||
--mount=type=cache,id=corepack-cache-${TARGETPLATFORM},target=/usr/local/share/.cache/corepack,sharing=locked \
|
||||
--mount=type=cache,id=yarn-cache-${TARGETPLATFORM},target=/usr/local/share/.cache/yarn,sharing=locked \
|
||||
# Apt update install non-dev versions of necessary components
|
||||
apt-get install -y --no-install-recommends \
|
||||
libexpat1 \
|
||||
libglib2.0-0t64 \
|
||||
libicu76 \
|
||||
libidn12 \
|
||||
libpq5 \
|
||||
libreadline8t64 \
|
||||
libssl3t64 \
|
||||
libyaml-0-2 \
|
||||
# libvips components
|
||||
libcgif0 \
|
||||
libexif12 \
|
||||
libheif1 \
|
||||
libhwy1t64 \
|
||||
libimagequant0 \
|
||||
libjpeg62-turbo \
|
||||
liblcms2-2 \
|
||||
libspng0 \
|
||||
libtiff6 \
|
||||
libwebp7 \
|
||||
libwebpdemux2 \
|
||||
libwebpmux3 \
|
||||
# ffmpeg components
|
||||
libdav1d7 \
|
||||
libmp3lame0 \
|
||||
libopencore-amrnb0 \
|
||||
libopencore-amrwb0 \
|
||||
libopus0 \
|
||||
libsnappy1v5 \
|
||||
libtheora0 \
|
||||
libvorbis0a \
|
||||
libvorbisenc2 \
|
||||
libvorbisfile3 \
|
||||
libvpx9 \
|
||||
libx264-164 \
|
||||
libx265-215 \
|
||||
;
|
||||
|
||||
# Copy Mastodon sources into final layer
|
||||
COPY . /opt/mastodon/
|
||||
|
||||
|
||||
4
Gemfile
4
Gemfile
@ -102,10 +102,10 @@ gem 'rdf-normalize', '~> 0.5'
|
||||
|
||||
gem 'prometheus_exporter', '~> 2.2', require: false
|
||||
|
||||
gem 'opentelemetry-api', '~> 1.8.0'
|
||||
gem 'opentelemetry-api', '~> 1.9.0'
|
||||
|
||||
group :opentelemetry do
|
||||
gem 'opentelemetry-exporter-otlp', '~> 0.32.0', require: false
|
||||
gem 'opentelemetry-exporter-otlp', '~> 0.33.0', require: false
|
||||
gem 'opentelemetry-instrumentation-active_job', '~> 0.10.0', require: false
|
||||
gem 'opentelemetry-instrumentation-active_model_serializers', '~> 0.24.0', require: false
|
||||
gem 'opentelemetry-instrumentation-concurrent_ruby', '~> 0.24.0', require: false
|
||||
|
||||
29
Gemfile.lock
29
Gemfile.lock
@ -99,7 +99,7 @@ GEM
|
||||
ast (2.4.3)
|
||||
attr_required (1.0.2)
|
||||
aws-eventstream (1.4.0)
|
||||
aws-partitions (1.1227.0)
|
||||
aws-partitions (1.1236.0)
|
||||
aws-sdk-core (3.244.0)
|
||||
aws-eventstream (~> 1, >= 1.3.0)
|
||||
aws-partitions (~> 1, >= 1.992.0)
|
||||
@ -111,7 +111,7 @@ GEM
|
||||
aws-sdk-kms (1.123.0)
|
||||
aws-sdk-core (~> 3, >= 3.244.0)
|
||||
aws-sigv4 (~> 1.5)
|
||||
aws-sdk-s3 (1.217.0)
|
||||
aws-sdk-s3 (1.219.0)
|
||||
aws-sdk-core (~> 3, >= 3.244.0)
|
||||
aws-sdk-kms (~> 1)
|
||||
aws-sigv4 (~> 1.5)
|
||||
@ -278,7 +278,7 @@ GEM
|
||||
raabro (~> 1.4)
|
||||
globalid (1.3.0)
|
||||
activesupport (>= 6.1)
|
||||
google-protobuf (4.34.0)
|
||||
google-protobuf (4.34.1)
|
||||
bigdecimal
|
||||
rake (~> 13.3)
|
||||
googleapis-common-protos-types (1.22.0)
|
||||
@ -451,7 +451,7 @@ GEM
|
||||
mime-types-data (3.2026.0317)
|
||||
mini_mime (1.1.5)
|
||||
mini_portile2 (2.8.9)
|
||||
minitest (6.0.2)
|
||||
minitest (6.0.3)
|
||||
drb (~> 2.0)
|
||||
prism (~> 1.5)
|
||||
msgpack (1.8.0)
|
||||
@ -509,11 +509,11 @@ GEM
|
||||
openssl (4.0.1)
|
||||
openssl-signature_algorithm (1.3.0)
|
||||
openssl (> 2.0)
|
||||
opentelemetry-api (1.8.0)
|
||||
opentelemetry-api (1.9.0)
|
||||
logger
|
||||
opentelemetry-common (0.23.0)
|
||||
opentelemetry-common (0.24.0)
|
||||
opentelemetry-api (~> 1.0)
|
||||
opentelemetry-exporter-otlp (0.32.0)
|
||||
opentelemetry-exporter-otlp (0.33.0)
|
||||
google-protobuf (>= 3.18)
|
||||
googleapis-common-protos-types (~> 1.3)
|
||||
opentelemetry-api (~> 1.1)
|
||||
@ -576,14 +576,15 @@ GEM
|
||||
opentelemetry-instrumentation-base (~> 0.25)
|
||||
opentelemetry-instrumentation-sidekiq (0.28.1)
|
||||
opentelemetry-instrumentation-base (~> 0.25)
|
||||
opentelemetry-registry (0.4.0)
|
||||
opentelemetry-registry (0.5.0)
|
||||
opentelemetry-api (~> 1.1)
|
||||
opentelemetry-sdk (1.10.0)
|
||||
opentelemetry-sdk (1.11.0)
|
||||
logger
|
||||
opentelemetry-api (~> 1.1)
|
||||
opentelemetry-common (~> 0.20)
|
||||
opentelemetry-registry (~> 0.2)
|
||||
opentelemetry-semantic_conventions
|
||||
opentelemetry-semantic_conventions (1.36.0)
|
||||
opentelemetry-semantic_conventions (1.37.0)
|
||||
opentelemetry-api (~> 1.0)
|
||||
orm_adapter (0.5.0)
|
||||
ostruct (0.6.3)
|
||||
@ -851,8 +852,8 @@ GEM
|
||||
concurrent-ruby
|
||||
zeitwerk
|
||||
stringio (3.2.0)
|
||||
strong_migrations (2.5.2)
|
||||
activerecord (>= 7.1)
|
||||
strong_migrations (2.6.0)
|
||||
activerecord (>= 7.2)
|
||||
swd (2.0.3)
|
||||
activesupport (>= 3)
|
||||
attr_required (>= 0.0.5)
|
||||
@ -1020,8 +1021,8 @@ DEPENDENCIES
|
||||
omniauth-rails_csrf_protection (~> 2.0)
|
||||
omniauth-saml (~> 2.0)
|
||||
omniauth_openid_connect (~> 0.8.0)
|
||||
opentelemetry-api (~> 1.8.0)
|
||||
opentelemetry-exporter-otlp (~> 0.32.0)
|
||||
opentelemetry-api (~> 1.9.0)
|
||||
opentelemetry-exporter-otlp (~> 0.33.0)
|
||||
opentelemetry-instrumentation-active_job (~> 0.10.0)
|
||||
opentelemetry-instrumentation-active_model_serializers (~> 0.24.0)
|
||||
opentelemetry-instrumentation-concurrent_ruby (~> 0.24.0)
|
||||
|
||||
@ -53,7 +53,7 @@ export interface BaseApiAccountJSON {
|
||||
header_static: string;
|
||||
header_description: string;
|
||||
id: string;
|
||||
last_status_at: string;
|
||||
last_status_at: string | null;
|
||||
locked: boolean;
|
||||
show_media: boolean;
|
||||
show_media_replies: boolean;
|
||||
|
||||
@ -11,7 +11,6 @@ import { useRelationship } from 'flavours/glitch/hooks/useRelationship';
|
||||
import type { Relationship } from 'flavours/glitch/models/relationship';
|
||||
|
||||
import { EmojiHTML } from '../emoji/html';
|
||||
import { FamiliarFollowers } from '../familiar_followers';
|
||||
import { FollowButton } from '../follow_button';
|
||||
import { FormattedDateWrapper } from '../formatted_date';
|
||||
import { NumberFields, NumberFieldsItem } from '../number_fields';
|
||||
@ -31,10 +30,11 @@ interface Props {
|
||||
accountId: string | undefined;
|
||||
stats?: Stat[];
|
||||
renderButton?: (options: RenderButtonOptions) => React.ReactNode;
|
||||
withBio?: boolean;
|
||||
withBorder?: boolean;
|
||||
}
|
||||
|
||||
const DEFAULT_STATS: Stat[] = ['followers', 'following', 'joined'];
|
||||
const DEFAULT_STATS: Stat[] = ['followers', 'posts', 'last-active'];
|
||||
|
||||
/**
|
||||
* Extended account list item with bio, verified link badge,
|
||||
@ -46,6 +46,7 @@ const DEFAULT_STATS: Stat[] = ['followers', 'following', 'joined'];
|
||||
export const AccountListItem: React.FC<Props> = ({
|
||||
accountId,
|
||||
stats = DEFAULT_STATS,
|
||||
withBio = true,
|
||||
withBorder = true,
|
||||
renderButton = defaultRenderButton,
|
||||
}) => {
|
||||
@ -147,11 +148,15 @@ export const AccountListItem: React.FC<Props> = ({
|
||||
/>
|
||||
}
|
||||
>
|
||||
<RelativeTimestamp
|
||||
long
|
||||
timestamp={account.last_status_at}
|
||||
noFuture
|
||||
/>
|
||||
{account.last_status_at ? (
|
||||
<RelativeTimestamp
|
||||
long
|
||||
timestamp={account.last_status_at}
|
||||
noFuture
|
||||
/>
|
||||
) : (
|
||||
'-'
|
||||
)}
|
||||
</NumberFieldsItem>
|
||||
)}
|
||||
{firstVerifiedField && (
|
||||
@ -161,8 +166,7 @@ export const AccountListItem: React.FC<Props> = ({
|
||||
/>
|
||||
)}
|
||||
</NumberFields>
|
||||
<FamiliarFollowers accountId={accountId} />
|
||||
{account.note.length > 0 && (
|
||||
{withBio && account.note.length > 0 && (
|
||||
<EmojiHTML
|
||||
className={classNames(classes.bio, 'translate')}
|
||||
htmlString={account.note_emojified}
|
||||
|
||||
@ -8,13 +8,6 @@
|
||||
&[data-with-border='true'] {
|
||||
border-bottom: 1px solid var(--color-border-primary);
|
||||
}
|
||||
|
||||
:global(.account__note) {
|
||||
display: -webkit-box;
|
||||
-webkit-box-orient: vertical;
|
||||
-webkit-line-clamp: 3;
|
||||
line-clamp: 3;
|
||||
}
|
||||
}
|
||||
|
||||
.header {
|
||||
@ -40,6 +33,12 @@
|
||||
}
|
||||
|
||||
.bio {
|
||||
overflow: hidden;
|
||||
display: -webkit-box;
|
||||
-webkit-box-orient: vertical;
|
||||
-webkit-line-clamp: 3;
|
||||
line-clamp: 3;
|
||||
|
||||
:any-link {
|
||||
color: var(--color-text-status-links);
|
||||
|
||||
|
||||
@ -73,7 +73,7 @@ const BackButton: React.FC<{
|
||||
};
|
||||
|
||||
export interface Props {
|
||||
title?: string;
|
||||
title?: React.ReactNode;
|
||||
icon?: string;
|
||||
iconComponent?: IconProp;
|
||||
active?: boolean;
|
||||
|
||||
@ -3,16 +3,20 @@
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
max-width: 600px;
|
||||
padding: 20px;
|
||||
padding: 24px;
|
||||
gap: 16px;
|
||||
text-align: center;
|
||||
color: var(--color-text-primary);
|
||||
}
|
||||
|
||||
.content {
|
||||
max-width: 370px;
|
||||
|
||||
svg,
|
||||
img {
|
||||
width: 240px;
|
||||
width: 200px;
|
||||
aspect-ratio: 1;
|
||||
object-fit: contain;
|
||||
max-width: 100%;
|
||||
margin-bottom: 16px;
|
||||
}
|
||||
@ -20,11 +24,14 @@
|
||||
h3 {
|
||||
font-size: 17px;
|
||||
font-weight: 500;
|
||||
text-wrap: balance;
|
||||
line-height: 1.2;
|
||||
}
|
||||
|
||||
p {
|
||||
font-size: 15px;
|
||||
margin-top: 8px;
|
||||
font-size: 15px;
|
||||
color: var(--color-text-secondary);
|
||||
text-wrap: pretty;
|
||||
}
|
||||
}
|
||||
|
||||
@ -8,9 +8,9 @@
|
||||
|
||||
.menuButton {
|
||||
position: absolute;
|
||||
inset-inline-end: 0;
|
||||
top: 0;
|
||||
padding: 9px;
|
||||
inset-inline-end: 1px;
|
||||
top: 1px;
|
||||
padding: 7px;
|
||||
|
||||
&::before {
|
||||
// Subtle divider line separating the button from the input field
|
||||
|
||||
@ -8,7 +8,7 @@
|
||||
|
||||
.copyButton {
|
||||
position: absolute;
|
||||
inset-inline-end: 0;
|
||||
top: 0;
|
||||
padding: 9px;
|
||||
inset-inline-end: 1px;
|
||||
top: 1px;
|
||||
padding: 7px;
|
||||
}
|
||||
|
||||
@ -1,13 +1,18 @@
|
||||
import { useCallback } from 'react';
|
||||
|
||||
import { FormattedMessage } from 'react-intl';
|
||||
|
||||
import { useParams } from 'react-router';
|
||||
import { Link } from 'react-router-dom';
|
||||
|
||||
import { openModal } from '@/flavours/glitch/actions/modal';
|
||||
import { Button } from '@/flavours/glitch/components/button';
|
||||
import { EmptyState } from '@/flavours/glitch/components/empty_state';
|
||||
import { LimitedAccountHint } from '@/flavours/glitch/features/account_timeline/components/limited_account_hint';
|
||||
import { areCollectionsEnabled } from '@/flavours/glitch/features/collections/utils';
|
||||
import { useCurrentAccountId } from '@/flavours/glitch/hooks/useAccountId';
|
||||
import { useTheme } from '@/flavours/glitch/hooks/useTheme';
|
||||
import { useAppDispatch } from '@/flavours/glitch/store';
|
||||
import ElephantDarkImage from '@/images/elephant_ui_dark.svg?react';
|
||||
import ElephantLightImage from '@/images/elephant_ui_light.svg?react';
|
||||
|
||||
@ -16,6 +21,7 @@ interface EmptyMessageProps {
|
||||
hidden: boolean;
|
||||
blockedBy: boolean;
|
||||
accountId?: string;
|
||||
withoutAddCollectionButton?: boolean;
|
||||
}
|
||||
|
||||
export const EmptyMessage: React.FC<EmptyMessageProps> = ({
|
||||
@ -23,6 +29,7 @@ export const EmptyMessage: React.FC<EmptyMessageProps> = ({
|
||||
suspended,
|
||||
hidden,
|
||||
blockedBy,
|
||||
withoutAddCollectionButton,
|
||||
}) => {
|
||||
const { acct } = useParams<{ acct?: string }>();
|
||||
const me = useCurrentAccountId();
|
||||
@ -30,6 +37,17 @@ export const EmptyMessage: React.FC<EmptyMessageProps> = ({
|
||||
const ElephantImage =
|
||||
theme === 'dark' ? ElephantDarkImage : ElephantLightImage;
|
||||
|
||||
const dispatch = useAppDispatch();
|
||||
|
||||
const confirmHideFeaturedTab = useCallback(() => {
|
||||
void dispatch(
|
||||
openModal({
|
||||
modalType: 'ACCOUNT_HIDE_FEATURED_TAB',
|
||||
modalProps: {},
|
||||
}),
|
||||
);
|
||||
}, [dispatch]);
|
||||
|
||||
if (!accountId) {
|
||||
return null;
|
||||
}
|
||||
@ -49,17 +67,31 @@ export const EmptyMessage: React.FC<EmptyMessageProps> = ({
|
||||
image={image}
|
||||
title={
|
||||
<FormattedMessage
|
||||
id='empty_column.account_featured_self.no_collections'
|
||||
defaultMessage='No collections yet'
|
||||
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.'
|
||||
/>
|
||||
}
|
||||
>
|
||||
<Link to='/collections/new' className='button'>
|
||||
{!withoutAddCollectionButton && (
|
||||
<Link to='/collections/new' className='button'>
|
||||
<FormattedMessage
|
||||
id='empty_column.account_featured_self.no_collections_button'
|
||||
defaultMessage='Create a collection'
|
||||
/>
|
||||
</Link>
|
||||
)}
|
||||
<Button secondary onClick={confirmHideFeaturedTab}>
|
||||
<FormattedMessage
|
||||
id='empty_column.account_featured_self.no_collections_button'
|
||||
defaultMessage='Create a collection'
|
||||
id='empty_column.account_featured_self.no_collections_hide_tab'
|
||||
defaultMessage='Hide this tab instead'
|
||||
/>
|
||||
</Link>
|
||||
</Button>
|
||||
</EmptyState>
|
||||
);
|
||||
} else {
|
||||
@ -72,7 +104,7 @@ export const EmptyMessage: React.FC<EmptyMessageProps> = ({
|
||||
message = (
|
||||
<FormattedMessage
|
||||
id='empty_column.account_featured_self.pre_collections_desc'
|
||||
defaultMessage='Collections (coming in Mastodon 4.6) allows you to create your own curated lists of accounts to recommend to others.'
|
||||
defaultMessage='Collections (coming in Mastodon 4.6) allow you to create your own curated lists of accounts to recommend to others.'
|
||||
/>
|
||||
);
|
||||
}
|
||||
@ -93,47 +125,21 @@ export const EmptyMessage: React.FC<EmptyMessageProps> = ({
|
||||
/>
|
||||
);
|
||||
} else {
|
||||
// Standard other account empty state.
|
||||
title = (
|
||||
<FormattedMessage
|
||||
id='empty_column.account_featured_other.title'
|
||||
defaultMessage='Nothing to see here'
|
||||
/>
|
||||
);
|
||||
if (hasCollections) {
|
||||
if (acct) {
|
||||
message = (
|
||||
<FormattedMessage
|
||||
id='empty_column.account_featured_other.no_collections_desc'
|
||||
defaultMessage='{acct} hasn’t created any collections yet.'
|
||||
values={{ acct }}
|
||||
/>
|
||||
);
|
||||
} else {
|
||||
message = (
|
||||
<FormattedMessage
|
||||
id='empty_column.account_featured_unknown.no_collections_desc'
|
||||
defaultMessage='This account hasn’t created any collections yet.'
|
||||
/>
|
||||
);
|
||||
}
|
||||
if (acct) {
|
||||
title = (
|
||||
<FormattedMessage
|
||||
id='empty_column.account_featured.other'
|
||||
defaultMessage='{acct} has not featured anything yet.'
|
||||
values={{ acct }}
|
||||
/>
|
||||
);
|
||||
} else {
|
||||
if (acct) {
|
||||
message = (
|
||||
<FormattedMessage
|
||||
id='empty_column.account_featured.other'
|
||||
defaultMessage='{acct} hasn’t featured anything yet.'
|
||||
values={{ acct }}
|
||||
/>
|
||||
);
|
||||
} else {
|
||||
message = (
|
||||
<FormattedMessage
|
||||
id='empty_column.account_featured_unknown.other'
|
||||
defaultMessage='This account hasn’t featured anything yet.'
|
||||
/>
|
||||
);
|
||||
}
|
||||
title = (
|
||||
<FormattedMessage
|
||||
id='empty_column.account_featured_unknown.other'
|
||||
defaultMessage='This account has not featured anything yet.'
|
||||
/>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -0,0 +1,37 @@
|
||||
.subheading {
|
||||
box-sizing: border-box;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
min-height: 52px;
|
||||
padding: 12px 16px;
|
||||
font-size: 13px;
|
||||
font-weight: 500;
|
||||
text-transform: uppercase;
|
||||
color: var(--color-text-primary);
|
||||
background: var(--color-bg-secondary);
|
||||
}
|
||||
|
||||
.link {
|
||||
display: flex;
|
||||
padding-block: 4px;
|
||||
font-size: 13px;
|
||||
font-weight: 500;
|
||||
text-transform: none;
|
||||
text-decoration: none;
|
||||
color: inherit;
|
||||
|
||||
&:hover {
|
||||
text-decoration: underline;
|
||||
}
|
||||
|
||||
&:focus-visible {
|
||||
outline: var(--outline-focus-default);
|
||||
outline-offset: 2px;
|
||||
}
|
||||
|
||||
:global(.icon) {
|
||||
width: 18px;
|
||||
height: 18px;
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,42 @@
|
||||
import type { ComponentPropsWithoutRef } from 'react';
|
||||
|
||||
import classNames from 'classnames';
|
||||
import { Link } from 'react-router-dom';
|
||||
|
||||
import type { IconProp } from '@/flavours/glitch/components/icon';
|
||||
import { Icon } from '@/flavours/glitch/components/icon';
|
||||
import { polymorphicForwardRef } from '@/types/polymorphic';
|
||||
|
||||
import classes from './subheading.module.scss';
|
||||
|
||||
export const Subheading = polymorphicForwardRef<'h2'>(
|
||||
({ as: Component = 'h2', children, className, ...props }, ref) => {
|
||||
return (
|
||||
<Component
|
||||
ref={ref}
|
||||
className={classNames(classes.subheading, className)}
|
||||
{...props}
|
||||
>
|
||||
{children}
|
||||
</Component>
|
||||
);
|
||||
},
|
||||
);
|
||||
|
||||
interface SubheadingLinkProps extends ComponentPropsWithoutRef<typeof Link> {
|
||||
icon: IconProp;
|
||||
}
|
||||
|
||||
export const SubheadingLink: React.FC<SubheadingLinkProps> = ({
|
||||
icon,
|
||||
children,
|
||||
className,
|
||||
...props
|
||||
}) => {
|
||||
return (
|
||||
<Link className={classNames(classes.link, className)} {...props}>
|
||||
<Icon id='subheading-icon' icon={icon} />
|
||||
{children}
|
||||
</Link>
|
||||
);
|
||||
};
|
||||
@ -6,9 +6,10 @@ import { useHistory } from 'react-router';
|
||||
|
||||
import { List as ImmutableList } from 'immutable';
|
||||
|
||||
import { AccountListItem } from '@/flavours/glitch/components/account_list_item';
|
||||
import { useAccount } from '@/flavours/glitch/hooks/useAccount';
|
||||
import AddIcon from '@/material-icons/400-24px/add.svg?react';
|
||||
import { fetchEndorsedAccounts } from 'flavours/glitch/actions/accounts';
|
||||
import { Account } from 'flavours/glitch/components/account';
|
||||
import { ColumnBackButton } from 'flavours/glitch/components/column_back_button';
|
||||
import { LoadingIndicator } from 'flavours/glitch/components/loading_indicator';
|
||||
import { RemoteHint } from 'flavours/glitch/components/remote_hint';
|
||||
@ -28,10 +29,13 @@ import {
|
||||
} from 'flavours/glitch/reducers/slices/collections';
|
||||
import { useAppDispatch, useAppSelector } from 'flavours/glitch/store';
|
||||
|
||||
import { CollectionListItem } from '../collections/detail/collection_list_item';
|
||||
import { CollectionListItem } from '../collections/components/collection_list_item';
|
||||
import { areCollectionsEnabled } from '../collections/utils';
|
||||
|
||||
import { EmptyMessage } from './components/empty_message';
|
||||
import { Subheading, SubheadingLink } from './components/subheading';
|
||||
|
||||
const collectionsEnabled = areCollectionsEnabled();
|
||||
|
||||
const AccountFeatured: React.FC<{ multiColumn: boolean }> = ({
|
||||
multiColumn,
|
||||
@ -54,13 +58,12 @@ const AccountFeatured: React.FC<{ multiColumn: boolean }> = ({
|
||||
if (accountId) {
|
||||
void dispatch(fetchEndorsedAccounts({ accountId }));
|
||||
|
||||
if (areCollectionsEnabled()) {
|
||||
if (collectionsEnabled) {
|
||||
void dispatch(fetchAccountCollections({ accountId }));
|
||||
}
|
||||
}
|
||||
}, [accountId, dispatch]);
|
||||
|
||||
const isLoading = !accountId;
|
||||
const featuredAccountIds = useAppSelector(
|
||||
(state) =>
|
||||
state.user_lists.getIn(
|
||||
@ -68,8 +71,8 @@ const AccountFeatured: React.FC<{ multiColumn: boolean }> = ({
|
||||
ImmutableList(),
|
||||
) as ImmutableList<string>,
|
||||
);
|
||||
const { collections, status } = useAppSelector((state) =>
|
||||
selectAccountCollections(state, accountId ?? null),
|
||||
const { collections, status: collectionsLoadStatus } = useAppSelector(
|
||||
(state) => selectAccountCollections(state, accountId ?? null),
|
||||
);
|
||||
const listedCollections = collections.filter(
|
||||
// Hide unlisted and empty collections to avoid confusion
|
||||
@ -78,6 +81,15 @@ const AccountFeatured: React.FC<{ multiColumn: boolean }> = ({
|
||||
(item) => item.discoverable && !!item.item_count,
|
||||
);
|
||||
|
||||
const hasCollections =
|
||||
collectionsEnabled &&
|
||||
collectionsLoadStatus === 'idle' &&
|
||||
listedCollections.length > 0;
|
||||
|
||||
const hasFeaturedAccounts = !featuredAccountIds.isEmpty();
|
||||
|
||||
const isLoading = !accountId || collectionsLoadStatus !== 'idle';
|
||||
|
||||
if (accountId === null) {
|
||||
return <BundleColumnError multiColumn={multiColumn} errorType='routing' />;
|
||||
}
|
||||
@ -92,7 +104,7 @@ const AccountFeatured: React.FC<{ multiColumn: boolean }> = ({
|
||||
);
|
||||
}
|
||||
|
||||
if (featuredAccountIds.isEmpty() && listedCollections.length === 0) {
|
||||
if (!hasFeaturedAccounts && !hasCollections) {
|
||||
return (
|
||||
<AccountFeaturedWrapper accountId={accountId}>
|
||||
<EmptyMessage
|
||||
@ -114,36 +126,14 @@ const AccountFeatured: React.FC<{ multiColumn: boolean }> = ({
|
||||
{accountId && (
|
||||
<AccountHeader accountId={accountId} hideTabs={forceEmptyState} />
|
||||
)}
|
||||
{listedCollections.length > 0 && status === 'idle' && (
|
||||
<>
|
||||
<h4 className='column-subheading'>
|
||||
<FormattedMessage
|
||||
id='account.featured.collections'
|
||||
defaultMessage='Collections'
|
||||
/>
|
||||
</h4>
|
||||
<ItemList>
|
||||
{listedCollections.map((item, index) => (
|
||||
<CollectionListItem
|
||||
key={item.id}
|
||||
collection={item}
|
||||
withoutBorder={index === listedCollections.length - 1}
|
||||
withAuthorHandle={false}
|
||||
positionInList={index + 1}
|
||||
listSize={listedCollections.length}
|
||||
/>
|
||||
))}
|
||||
</ItemList>
|
||||
</>
|
||||
)}
|
||||
{!featuredAccountIds.isEmpty() && (
|
||||
<>
|
||||
<h4 className='column-subheading'>
|
||||
<Subheading as='h2'>
|
||||
<FormattedMessage
|
||||
id='account.featured.accounts'
|
||||
defaultMessage='Profiles'
|
||||
/>
|
||||
</h4>
|
||||
</Subheading>
|
||||
<ItemList>
|
||||
{featuredAccountIds.map((featuredAccountId, index) => (
|
||||
<Article
|
||||
@ -152,12 +142,52 @@ const AccountFeatured: React.FC<{ multiColumn: boolean }> = ({
|
||||
aria-posinset={index + 1}
|
||||
aria-setsize={featuredAccountIds.size}
|
||||
>
|
||||
<Account id={featuredAccountId} />
|
||||
<AccountListItem accountId={featuredAccountId} />
|
||||
</Article>
|
||||
))}
|
||||
</ItemList>
|
||||
</>
|
||||
)}
|
||||
{collectionsEnabled && (
|
||||
<>
|
||||
<Subheading as='header'>
|
||||
<h2>
|
||||
<FormattedMessage
|
||||
id='account.featured.collections'
|
||||
defaultMessage='Collections'
|
||||
/>
|
||||
</h2>
|
||||
<SubheadingLink to='/collections/new' icon={AddIcon}>
|
||||
<FormattedMessage
|
||||
id='account.featured.new_collection'
|
||||
defaultMessage='New collection'
|
||||
/>
|
||||
</SubheadingLink>
|
||||
</Subheading>
|
||||
{hasCollections ? (
|
||||
<ItemList>
|
||||
{listedCollections.map((item, index) => (
|
||||
<CollectionListItem
|
||||
key={item.id}
|
||||
collection={item}
|
||||
withoutBorder={index === listedCollections.length - 1}
|
||||
withAuthorHandle={false}
|
||||
positionInList={index + 1}
|
||||
listSize={listedCollections.length}
|
||||
/>
|
||||
))}
|
||||
</ItemList>
|
||||
) : (
|
||||
<EmptyMessage
|
||||
withoutAddCollectionButton
|
||||
blockedBy={blockedBy}
|
||||
hidden={hidden}
|
||||
suspended={suspended}
|
||||
accountId={accountId}
|
||||
/>
|
||||
)}
|
||||
</>
|
||||
)}
|
||||
<RemoteHint accountId={accountId} />
|
||||
</Scrollable>
|
||||
</Column>
|
||||
|
||||
@ -8,8 +8,6 @@ import { NavLink } from 'react-router-dom';
|
||||
import { useAccount } from '@/flavours/glitch/hooks/useAccount';
|
||||
import { useAccountId } from '@/flavours/glitch/hooks/useAccountId';
|
||||
|
||||
import { areCollectionsEnabled } from '../../collections/utils';
|
||||
|
||||
import classes from './styles.module.scss';
|
||||
|
||||
const isActive: Required<NavLinkProps>['isActive'] = (match, location) =>
|
||||
@ -41,14 +39,7 @@ export const AccountTabs: FC = () => {
|
||||
)}
|
||||
{show_featured && (
|
||||
<NavLink exact to={`/@${acct}/featured`}>
|
||||
{areCollectionsEnabled() ? (
|
||||
<FormattedMessage
|
||||
id='account.featured.collections'
|
||||
defaultMessage='Collections'
|
||||
/>
|
||||
) : (
|
||||
<FormattedMessage id='account.featured' defaultMessage='Featured' />
|
||||
)}
|
||||
<FormattedMessage id='account.featured' defaultMessage='Featured' />
|
||||
</NavLink>
|
||||
)}
|
||||
</div>
|
||||
|
||||
@ -0,0 +1,20 @@
|
||||
.wrapper {
|
||||
display: flex;
|
||||
align-items: start;
|
||||
padding: 12px 16px;
|
||||
gap: 16px;
|
||||
|
||||
&:not(.wrapperWithoutBorder) {
|
||||
border-bottom: 1px solid var(--color-border-primary);
|
||||
}
|
||||
}
|
||||
|
||||
.menuButton {
|
||||
padding: 4px;
|
||||
margin-top: -2px;
|
||||
|
||||
svg {
|
||||
width: 20px;
|
||||
height: 20px;
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,50 @@
|
||||
import { useId } from 'react';
|
||||
|
||||
import classNames from 'classnames';
|
||||
|
||||
import { Article } from 'flavours/glitch/components/scrollable_list/components';
|
||||
import type { CollectionLockupProps } from 'flavours/glitch/features/collections/components/collection_lockup';
|
||||
import { CollectionLockup } from 'flavours/glitch/features/collections/components/collection_lockup';
|
||||
import { CollectionMenu } from 'flavours/glitch/features/collections/components/collection_menu';
|
||||
|
||||
import classes from './collection_list_item.module.scss';
|
||||
|
||||
interface CollectionListItemProps extends CollectionLockupProps {
|
||||
withoutBorder?: boolean;
|
||||
positionInList: number;
|
||||
listSize: number;
|
||||
}
|
||||
|
||||
export const CollectionListItem: React.FC<CollectionListItemProps> = ({
|
||||
collection,
|
||||
withoutBorder,
|
||||
positionInList,
|
||||
listSize,
|
||||
...otherProps
|
||||
}) => {
|
||||
const uniqueId = useId();
|
||||
const linkId = `${uniqueId}-link`;
|
||||
const infoId = `${uniqueId}-info`;
|
||||
|
||||
return (
|
||||
<Article
|
||||
focusable
|
||||
className={classNames(
|
||||
classes.wrapper,
|
||||
withoutBorder && classes.wrapperWithoutBorder,
|
||||
)}
|
||||
aria-labelledby={linkId}
|
||||
aria-describedby={infoId}
|
||||
aria-posinset={positionInList}
|
||||
aria-setsize={listSize}
|
||||
>
|
||||
<CollectionLockup collection={collection} {...otherProps} />
|
||||
|
||||
<CollectionMenu
|
||||
context='list'
|
||||
collection={collection}
|
||||
className={classes.menuButton}
|
||||
/>
|
||||
</Article>
|
||||
);
|
||||
};
|
||||
@ -1,21 +1,11 @@
|
||||
.wrapper {
|
||||
display: flex;
|
||||
align-items: start;
|
||||
margin-inline: 24px;
|
||||
padding-block: 12px;
|
||||
gap: 16px;
|
||||
|
||||
&:not(.wrapperWithoutBorder) {
|
||||
border-bottom: 1px solid var(--color-border-primary);
|
||||
}
|
||||
}
|
||||
|
||||
.content {
|
||||
position: relative;
|
||||
flex-grow: 1;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
column-gap: 12px;
|
||||
color: var(--color-text-primary);
|
||||
line-height: 1.3;
|
||||
}
|
||||
|
||||
.avatarGrid {
|
||||
@ -70,13 +60,3 @@
|
||||
font-size: 13px;
|
||||
color: var(--color-text-secondary);
|
||||
}
|
||||
|
||||
.menuButton {
|
||||
padding: 4px;
|
||||
margin-top: -2px;
|
||||
|
||||
svg {
|
||||
width: 20px;
|
||||
height: 20px;
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,115 @@
|
||||
import { useId } from 'react';
|
||||
|
||||
import { FormattedMessage } from 'react-intl';
|
||||
|
||||
import classNames from 'classnames';
|
||||
import { Link } from 'react-router-dom';
|
||||
|
||||
import WarningIcon from '@/material-icons/400-24px/warning.svg?react';
|
||||
import type { ApiCollectionJSON } from 'flavours/glitch/api_types/collections';
|
||||
import { AvatarById } from 'flavours/glitch/components/avatar';
|
||||
import { useAccountHandle } from 'flavours/glitch/components/display_name/default';
|
||||
import { RelativeTimestamp } from 'flavours/glitch/components/relative_timestamp';
|
||||
import { useAccount } from 'flavours/glitch/hooks/useAccount';
|
||||
import { domain } from 'flavours/glitch/initial_state';
|
||||
|
||||
import classes from './collection_lockup.module.scss';
|
||||
|
||||
export const AvatarGrid: React.FC<{
|
||||
accountIds: (string | undefined)[];
|
||||
sensitive?: boolean;
|
||||
}> = ({ accountIds: ids, sensitive }) => {
|
||||
const avatarIds = [ids[0], ids[1], ids[2], ids[3]];
|
||||
return (
|
||||
<div
|
||||
className={classNames(
|
||||
classes.avatarGrid,
|
||||
sensitive ? classes.avatarGridSensitive : null,
|
||||
)}
|
||||
>
|
||||
{avatarIds.map((id) => (
|
||||
<AvatarById
|
||||
animate={false}
|
||||
key={id}
|
||||
accountId={id}
|
||||
className={classes.avatar}
|
||||
size={25}
|
||||
/>
|
||||
))}
|
||||
{sensitive && <WarningIcon className={classes.avatarSensitiveBadge} />}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export interface CollectionLockupProps {
|
||||
collection: ApiCollectionJSON;
|
||||
withAuthorHandle?: boolean;
|
||||
withTimestamp?: boolean;
|
||||
}
|
||||
|
||||
export const CollectionLockup: React.FC<CollectionLockupProps> = ({
|
||||
collection,
|
||||
withAuthorHandle = true,
|
||||
withTimestamp,
|
||||
}) => {
|
||||
const { id, name } = collection;
|
||||
const uniqueId = useId();
|
||||
const linkId = `${uniqueId}-link`;
|
||||
const infoId = `${uniqueId}-info`;
|
||||
const authorAccount = useAccount(collection.account_id);
|
||||
const authorHandle = useAccountHandle(authorAccount, domain);
|
||||
|
||||
return (
|
||||
<div className={classes.content}>
|
||||
<AvatarGrid
|
||||
accountIds={collection.items.map((item) => item.account_id)}
|
||||
sensitive={collection.sensitive}
|
||||
/>
|
||||
<div>
|
||||
<h2 id={linkId}>
|
||||
<Link to={`/collections/${id}`} className={classes.link}>
|
||||
{name}
|
||||
</Link>
|
||||
</h2>
|
||||
<ul className={classes.info} id={infoId}>
|
||||
{collection.sensitive && (
|
||||
<li className='sr-only'>
|
||||
<FormattedMessage
|
||||
id='collections.sensitive'
|
||||
defaultMessage='Sensitive'
|
||||
/>
|
||||
</li>
|
||||
)}
|
||||
{withAuthorHandle && authorAccount && (
|
||||
<FormattedMessage
|
||||
id='collections.by_account'
|
||||
defaultMessage='by {account_handle}'
|
||||
values={{
|
||||
account_handle: authorHandle,
|
||||
}}
|
||||
tagName='li'
|
||||
/>
|
||||
)}
|
||||
<FormattedMessage
|
||||
id='collections.account_count'
|
||||
defaultMessage='{count, plural, one {# account} other {# accounts}}'
|
||||
values={{ count: collection.item_count }}
|
||||
tagName='li'
|
||||
/>
|
||||
{withTimestamp && (
|
||||
<FormattedMessage
|
||||
id='collections.last_updated_at'
|
||||
defaultMessage='Last updated: {date}'
|
||||
values={{
|
||||
date: (
|
||||
<RelativeTimestamp timestamp={collection.updated_at} long />
|
||||
),
|
||||
}}
|
||||
tagName='li'
|
||||
/>
|
||||
)}
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
@ -0,0 +1,8 @@
|
||||
.wrapper {
|
||||
display: flex;
|
||||
align-items: start;
|
||||
padding: 12px;
|
||||
gap: 12px;
|
||||
border-radius: 12px;
|
||||
border: 1px solid var(--color-border-primary);
|
||||
}
|
||||
@ -0,0 +1,20 @@
|
||||
import type { CollectionLockupProps } from 'flavours/glitch/features/collections/components/collection_lockup';
|
||||
import { CollectionLockup } from 'flavours/glitch/features/collections/components/collection_lockup';
|
||||
|
||||
import classes from './collection_preview_card.module.scss';
|
||||
|
||||
interface CollectionPreviewCardProps extends CollectionLockupProps {
|
||||
onRemove?: () => void;
|
||||
}
|
||||
|
||||
export const CollectionPreviewCard: React.FC<CollectionPreviewCardProps> = ({
|
||||
collection,
|
||||
onRemove,
|
||||
...otherProps
|
||||
}) => {
|
||||
return (
|
||||
<div className={classes.wrapper}>
|
||||
<CollectionLockup collection={collection} {...otherProps} />
|
||||
</div>
|
||||
);
|
||||
};
|
||||
@ -5,27 +5,7 @@
|
||||
}
|
||||
|
||||
.preview {
|
||||
display: flex;
|
||||
flex-wrap: wrap-reverse;
|
||||
align-items: start;
|
||||
justify-content: space-between;
|
||||
gap: 8px;
|
||||
padding: 16px;
|
||||
margin-bottom: 16px;
|
||||
border-radius: 8px;
|
||||
color: var(--color-text-primary);
|
||||
background: linear-gradient(
|
||||
145deg,
|
||||
var(--color-bg-brand-soft),
|
||||
var(--color-bg-primary)
|
||||
);
|
||||
border: 1px solid var(--color-bg-brand-base);
|
||||
}
|
||||
|
||||
.previewHeading {
|
||||
font-size: 22px;
|
||||
line-height: 1.3;
|
||||
margin-bottom: 4px;
|
||||
}
|
||||
|
||||
.actions {
|
||||
@ -8,8 +8,6 @@ import { me } from '@/flavours/glitch/initial_state';
|
||||
import CloseIcon from '@/material-icons/400-24px/close.svg?react';
|
||||
import { changeCompose, focusCompose } from 'flavours/glitch/actions/compose';
|
||||
import type { ApiCollectionJSON } from 'flavours/glitch/api_types/collections';
|
||||
import { AvatarById } from 'flavours/glitch/components/avatar';
|
||||
import { AvatarGroup } from 'flavours/glitch/components/avatar_group';
|
||||
import { Button } from 'flavours/glitch/components/button';
|
||||
import { CopyLinkField } from 'flavours/glitch/components/form_fields';
|
||||
import { IconButton } from 'flavours/glitch/components/icon_button';
|
||||
@ -20,7 +18,7 @@ import {
|
||||
} from 'flavours/glitch/components/modal_shell';
|
||||
import { useAppDispatch } from 'flavours/glitch/store';
|
||||
|
||||
import { AuthorNote } from '.';
|
||||
import { CollectionPreviewCard } from './collection_preview_card';
|
||||
import classes from './share_modal.module.scss';
|
||||
|
||||
const messages = defineMessages({
|
||||
@ -95,24 +93,13 @@ export const CollectionShareModal: React.FC<{
|
||||
/>
|
||||
|
||||
<div className={classes.preview}>
|
||||
<div>
|
||||
<h2 className={classes.previewHeading}>{collection.name}</h2>
|
||||
<AuthorNote id={collection.account_id} />
|
||||
</div>
|
||||
<AvatarGroup>
|
||||
{collection.items.slice(0, 5).map(({ account_id }) => {
|
||||
if (!account_id) return;
|
||||
return (
|
||||
<AvatarById key={account_id} accountId={account_id} size={28} />
|
||||
);
|
||||
})}
|
||||
</AvatarGroup>
|
||||
<CollectionPreviewCard collection={collection} />
|
||||
</div>
|
||||
|
||||
<CopyLinkField
|
||||
label={intl.formatMessage({
|
||||
id: 'collection.share_modal.share_link_label',
|
||||
defaultMessage: 'Invite share link',
|
||||
defaultMessage: 'Share link',
|
||||
})}
|
||||
value={collectionLink}
|
||||
/>
|
||||
@ -146,7 +146,6 @@ export const CollectionAccountsList: React.FC<{
|
||||
<AccountListItem
|
||||
accountId={account_id}
|
||||
withBorder={index !== items.length - 1}
|
||||
stats={['followers', 'last-active']}
|
||||
renderButton={renderAccountItemButton}
|
||||
/>
|
||||
</Article>
|
||||
|
||||
@ -1,139 +0,0 @@
|
||||
import { useId } from 'react';
|
||||
|
||||
import { FormattedMessage } from 'react-intl';
|
||||
|
||||
import classNames from 'classnames';
|
||||
import { Link } from 'react-router-dom';
|
||||
|
||||
import WarningIcon from '@/material-icons/400-24px/warning.svg?react';
|
||||
import type { ApiCollectionJSON } from 'flavours/glitch/api_types/collections';
|
||||
import { AvatarById } from 'flavours/glitch/components/avatar';
|
||||
import { useAccountHandle } from 'flavours/glitch/components/display_name/default';
|
||||
import { RelativeTimestamp } from 'flavours/glitch/components/relative_timestamp';
|
||||
import { Article } from 'flavours/glitch/components/scrollable_list/components';
|
||||
import { useAccount } from 'flavours/glitch/hooks/useAccount';
|
||||
import { domain } from 'flavours/glitch/initial_state';
|
||||
|
||||
import classes from './collection_list_item.module.scss';
|
||||
import { CollectionMenu } from './collection_menu';
|
||||
|
||||
export const AvatarGrid: React.FC<{
|
||||
accountIds: (string | undefined)[];
|
||||
sensitive?: boolean;
|
||||
}> = ({ accountIds: ids, sensitive }) => {
|
||||
const avatarIds = [ids[0], ids[1], ids[2], ids[3]];
|
||||
return (
|
||||
<div
|
||||
className={classNames(
|
||||
classes.avatarGrid,
|
||||
sensitive ? classes.avatarGridSensitive : null,
|
||||
)}
|
||||
>
|
||||
{avatarIds.map((id) => (
|
||||
<AvatarById
|
||||
animate={false}
|
||||
key={id}
|
||||
accountId={id}
|
||||
className={classes.avatar}
|
||||
size={25}
|
||||
/>
|
||||
))}
|
||||
{sensitive && <WarningIcon className={classes.avatarSensitiveBadge} />}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export const CollectionListItem: React.FC<{
|
||||
collection: ApiCollectionJSON;
|
||||
withoutBorder?: boolean;
|
||||
withAuthorHandle?: boolean;
|
||||
withTimestamp?: boolean;
|
||||
positionInList: number;
|
||||
listSize: number;
|
||||
}> = ({
|
||||
collection,
|
||||
withoutBorder,
|
||||
withAuthorHandle = true,
|
||||
withTimestamp,
|
||||
positionInList,
|
||||
listSize,
|
||||
}) => {
|
||||
const { id, name } = collection;
|
||||
const uniqueId = useId();
|
||||
const linkId = `${uniqueId}-link`;
|
||||
const infoId = `${uniqueId}-info`;
|
||||
const authorAccount = useAccount(collection.account_id);
|
||||
const authorHandle = useAccountHandle(authorAccount, domain);
|
||||
|
||||
return (
|
||||
<Article
|
||||
focusable
|
||||
className={classNames(
|
||||
classes.wrapper,
|
||||
withoutBorder && classes.wrapperWithoutBorder,
|
||||
)}
|
||||
aria-labelledby={linkId}
|
||||
aria-describedby={infoId}
|
||||
aria-posinset={positionInList}
|
||||
aria-setsize={listSize}
|
||||
>
|
||||
<div className={classes.content}>
|
||||
<AvatarGrid
|
||||
accountIds={collection.items.map((item) => item.account_id)}
|
||||
sensitive={collection.sensitive}
|
||||
/>
|
||||
<div>
|
||||
<h2 id={linkId}>
|
||||
<Link to={`/collections/${id}`} className={classes.link}>
|
||||
{name}
|
||||
</Link>
|
||||
</h2>
|
||||
<ul className={classes.info} id={infoId}>
|
||||
{collection.sensitive && (
|
||||
<li className='sr-only'>
|
||||
<FormattedMessage
|
||||
id='collections.sensitive'
|
||||
defaultMessage='Sensitive'
|
||||
/>
|
||||
</li>
|
||||
)}
|
||||
{withAuthorHandle && authorAccount && (
|
||||
<FormattedMessage
|
||||
id='collections.by_account'
|
||||
defaultMessage='by {account_handle}'
|
||||
values={{
|
||||
account_handle: authorHandle,
|
||||
}}
|
||||
tagName='li'
|
||||
/>
|
||||
)}
|
||||
<FormattedMessage
|
||||
id='collections.account_count'
|
||||
defaultMessage='{count, plural, one {# account} other {# accounts}}'
|
||||
values={{ count: collection.item_count }}
|
||||
tagName='li'
|
||||
/>
|
||||
{withTimestamp && (
|
||||
<FormattedMessage
|
||||
id='collections.last_updated_at'
|
||||
defaultMessage='Last updated: {date}'
|
||||
values={{
|
||||
date: (
|
||||
<RelativeTimestamp timestamp={collection.updated_at} long />
|
||||
),
|
||||
}}
|
||||
tagName='li'
|
||||
/>
|
||||
)}
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<CollectionMenu
|
||||
context='list'
|
||||
collection={collection}
|
||||
className={classes.menuButton}
|
||||
/>
|
||||
</Article>
|
||||
);
|
||||
};
|
||||
@ -23,8 +23,9 @@ import { domain, me } from 'flavours/glitch/initial_state';
|
||||
import { fetchCollection } from 'flavours/glitch/reducers/slices/collections';
|
||||
import { useAppDispatch, useAppSelector } from 'flavours/glitch/store';
|
||||
|
||||
import { CollectionMenu } from '../components/collection_menu';
|
||||
|
||||
import { CollectionAccountsList } from './accounts_list';
|
||||
import { CollectionMenu } from './collection_menu';
|
||||
import { useConfirmRevoke } from './revoke_collection_inclusion_modal';
|
||||
import classes from './styles.module.scss';
|
||||
|
||||
@ -66,7 +67,7 @@ export const AuthorNote: React.FC<{ id: string }> = ({ id }) => {
|
||||
);
|
||||
};
|
||||
|
||||
export const RevokeControls: React.FC<{
|
||||
const RevokeControls: React.FC<{
|
||||
collection: ApiCollectionJSON;
|
||||
}> = ({ collection }) => {
|
||||
const authorAccount = useAccount(collection.account_id);
|
||||
|
||||
@ -61,9 +61,11 @@ const AddedAccountItem: React.FC<{
|
||||
onRemove(accountId);
|
||||
}, [accountId, onRemove]);
|
||||
|
||||
const lastStatusAt = account?.last_status_at;
|
||||
|
||||
const lastPostHint = useMemo(
|
||||
() =>
|
||||
isOlderThanAWeek(account?.last_status_at) && (
|
||||
(!lastStatusAt || isOlderThanAWeek(lastStatusAt)) && (
|
||||
<Badge
|
||||
label={
|
||||
<FormattedMessage
|
||||
@ -75,7 +77,7 @@ const AddedAccountItem: React.FC<{
|
||||
className={classes.accountBadge}
|
||||
/>
|
||||
),
|
||||
[account?.last_status_at],
|
||||
[lastStatusAt],
|
||||
);
|
||||
|
||||
return (
|
||||
|
||||
@ -10,22 +10,32 @@ import CollectionsFilledIcon from '@/material-icons/400-24px/category-fill.svg?r
|
||||
import SquigglyArrow from '@/svg-icons/squiggly_arrow.svg?react';
|
||||
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 { Icon } from 'flavours/glitch/components/icon';
|
||||
import {
|
||||
ItemList,
|
||||
Scrollable,
|
||||
} from 'flavours/glitch/components/scrollable_list/components';
|
||||
import { useAccount } from 'flavours/glitch/hooks/useAccount';
|
||||
import {
|
||||
useAccountId,
|
||||
useCurrentAccountId,
|
||||
} from 'flavours/glitch/hooks/useAccountId';
|
||||
import {
|
||||
fetchAccountCollections,
|
||||
selectAccountCollections,
|
||||
} from 'flavours/glitch/reducers/slices/collections';
|
||||
import { useAppSelector, useAppDispatch } from 'flavours/glitch/store';
|
||||
|
||||
import { CollectionListItem } from './detail/collection_list_item';
|
||||
import { CollectionListItem } from './components/collection_list_item';
|
||||
import { messages as editorMessages } from './editor';
|
||||
|
||||
const messages = defineMessages({
|
||||
heading: { id: 'column.collections', defaultMessage: 'My collections' },
|
||||
headingMe: { id: 'column.my_collections', defaultMessage: 'My collections' },
|
||||
headingOther: {
|
||||
id: 'column.other_collections',
|
||||
defaultMessage: 'Collections by {name}',
|
||||
},
|
||||
});
|
||||
|
||||
export const Collections: React.FC<{
|
||||
@ -33,17 +43,22 @@ export const Collections: React.FC<{
|
||||
}> = ({ multiColumn }) => {
|
||||
const dispatch = useAppDispatch();
|
||||
const intl = useIntl();
|
||||
const me = useAppSelector((state) => state.meta.get('me') as string);
|
||||
const me = useCurrentAccountId();
|
||||
const accountId = useAccountId();
|
||||
const account = useAccount(accountId);
|
||||
|
||||
const { collections, status } = useAppSelector((state) =>
|
||||
selectAccountCollections(state, me),
|
||||
selectAccountCollections(state, accountId),
|
||||
);
|
||||
|
||||
useEffect(() => {
|
||||
void dispatch(fetchAccountCollections({ accountId: me }));
|
||||
}, [dispatch, me]);
|
||||
if (accountId) {
|
||||
void dispatch(fetchAccountCollections({ accountId }));
|
||||
}
|
||||
}, [dispatch, accountId]);
|
||||
|
||||
const emptyMessage =
|
||||
status === 'error' ? (
|
||||
status === 'error' || !accountId ? (
|
||||
<FormattedMessage
|
||||
id='collections.error_loading_collections'
|
||||
defaultMessage='There was an error when trying to load your collections.'
|
||||
@ -67,25 +82,36 @@ export const Collections: React.FC<{
|
||||
</>
|
||||
);
|
||||
|
||||
const isOwnCollection = accountId === me;
|
||||
const titleMessage = isOwnCollection
|
||||
? messages.headingMe
|
||||
: messages.headingOther;
|
||||
|
||||
const pageTitle = intl.formatMessage(titleMessage, {
|
||||
name: account?.get('display_name'),
|
||||
});
|
||||
const pageTitleHtml = intl.formatMessage(titleMessage, {
|
||||
name: <DisplayNameSimple account={account} />,
|
||||
});
|
||||
|
||||
return (
|
||||
<Column
|
||||
bindToDocument={!multiColumn}
|
||||
label={intl.formatMessage(messages.heading)}
|
||||
>
|
||||
<Column bindToDocument={!multiColumn} label={pageTitle}>
|
||||
<ColumnHeader
|
||||
title={intl.formatMessage(messages.heading)}
|
||||
title={pageTitleHtml}
|
||||
icon='collections'
|
||||
iconComponent={CollectionsFilledIcon}
|
||||
multiColumn={multiColumn}
|
||||
extraButton={
|
||||
<Link
|
||||
to='/collections/new'
|
||||
className='column-header__button'
|
||||
title={intl.formatMessage(editorMessages.create)}
|
||||
aria-label={intl.formatMessage(editorMessages.create)}
|
||||
>
|
||||
<Icon id='plus' icon={AddIcon} />
|
||||
</Link>
|
||||
isOwnCollection && (
|
||||
<Link
|
||||
to='/collections/new'
|
||||
className='column-header__button'
|
||||
title={intl.formatMessage(editorMessages.create)}
|
||||
aria-label={intl.formatMessage(editorMessages.create)}
|
||||
>
|
||||
<Icon id='plus' icon={AddIcon} />
|
||||
</Link>
|
||||
)
|
||||
}
|
||||
/>
|
||||
|
||||
@ -105,7 +131,7 @@ export const Collections: React.FC<{
|
||||
</Scrollable>
|
||||
|
||||
<Helmet>
|
||||
<title>{intl.formatMessage(messages.heading)}</title>
|
||||
<title>{pageTitle}</title>
|
||||
<meta name='robots' content='noindex' />
|
||||
</Helmet>
|
||||
</Column>
|
||||
|
||||
@ -55,12 +55,20 @@ export const AccountList: FC<AccountListProps> = ({
|
||||
}
|
||||
const children =
|
||||
list?.items.map((followerId) => (
|
||||
<AccountListItem key={followerId} accountId={followerId} />
|
||||
<AccountListItem
|
||||
key={followerId}
|
||||
accountId={followerId}
|
||||
withBio={false}
|
||||
/>
|
||||
)) ?? [];
|
||||
|
||||
if (prependAccountId) {
|
||||
children.unshift(
|
||||
<AccountListItem key={prependAccountId} accountId={prependAccountId} />,
|
||||
<AccountListItem
|
||||
key={prependAccountId}
|
||||
accountId={prependAccountId}
|
||||
withBio={false}
|
||||
/>,
|
||||
);
|
||||
}
|
||||
return children;
|
||||
|
||||
@ -11,6 +11,7 @@ import type { Map as ImmutableMap } from 'immutable';
|
||||
import { animated, useSpring } from '@react-spring/web';
|
||||
import { useDrag } from '@use-gesture/react';
|
||||
|
||||
import { useAccount } from '@/flavours/glitch/hooks/useAccount';
|
||||
import AddIcon from '@/material-icons/400-24px/add.svg?react';
|
||||
import AlternateEmailIcon from '@/material-icons/400-24px/alternate_email.svg?react';
|
||||
import BookmarksActiveIcon from '@/material-icons/400-24px/bookmarks-fill.svg?react';
|
||||
@ -226,6 +227,7 @@ export const NavigationPanel: React.FC<{ multiColumn?: boolean }> = ({
|
||||
const { signedIn, permissions, disabledAccountId } = useIdentity();
|
||||
const location = useLocation();
|
||||
const showSearch = useBreakpoint('full') && !multiColumn;
|
||||
const account = useAccount(me);
|
||||
const dispatch = useAppDispatch();
|
||||
|
||||
let banner: React.ReactNode;
|
||||
@ -361,7 +363,7 @@ export const NavigationPanel: React.FC<{ multiColumn?: boolean }> = ({
|
||||
{areCollectionsEnabled() && (
|
||||
<ColumnLink
|
||||
transparent
|
||||
to='/collections'
|
||||
to={`/@${account?.acct}/collections`}
|
||||
icon='collections'
|
||||
iconComponent={CollectionsIcon}
|
||||
activeIconComponent={CollectionsActiveIcon}
|
||||
|
||||
@ -0,0 +1,69 @@
|
||||
import { FormattedMessage } from 'react-intl';
|
||||
|
||||
import classNames from 'classnames';
|
||||
|
||||
import { DisplayNameSimple } from '@/flavours/glitch/components/display_name/simple';
|
||||
import { Icon } from '@/flavours/glitch/components/icon';
|
||||
import { useAccount } from '@/flavours/glitch/hooks/useAccount';
|
||||
import CollectionsFilledIcon from '@/material-icons/400-24px/category-fill.svg?react';
|
||||
import type {
|
||||
NotificationGroupAddedToCollection,
|
||||
NotificationGroupCollectionUpdate,
|
||||
} from 'flavours/glitch/models/notification_group';
|
||||
|
||||
import { CollectionPreviewCard } from '../../collections/components/collection_preview_card';
|
||||
|
||||
export const NotificationCollection: React.FC<{
|
||||
notification:
|
||||
| NotificationGroupAddedToCollection
|
||||
| NotificationGroupCollectionUpdate;
|
||||
unread: boolean;
|
||||
}> = ({ notification, unread }) => {
|
||||
const { collection, type } = notification;
|
||||
const collectionCreatorAccount = useAccount(collection.account_id);
|
||||
|
||||
return (
|
||||
<div
|
||||
className={classNames(
|
||||
'notification-group',
|
||||
`notification-group--${type}`,
|
||||
{ 'notification-group--unread': unread },
|
||||
)}
|
||||
>
|
||||
<div className='notification-group__icon'>
|
||||
<Icon id='collection' icon={CollectionsFilledIcon} />
|
||||
</div>
|
||||
|
||||
<div className='notification-group__main'>
|
||||
<div className='notification-group__main__header'>
|
||||
<div className='notification-group__main__header__label'>
|
||||
{type === 'added_to_collection' && (
|
||||
<FormattedMessage
|
||||
id='notification.added_to_collection'
|
||||
defaultMessage='{name} added you to a collection'
|
||||
values={{
|
||||
name: (
|
||||
<DisplayNameSimple account={collectionCreatorAccount} />
|
||||
),
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
{type === 'collection_update' && (
|
||||
<FormattedMessage
|
||||
id='notification.collection_update'
|
||||
defaultMessage='{name} edited a collection you’re in'
|
||||
values={{
|
||||
name: (
|
||||
<DisplayNameSimple account={collectionCreatorAccount} />
|
||||
),
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<CollectionPreviewCard collection={collection} />
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
@ -9,6 +9,7 @@ import { useAppSelector, useAppDispatch } from 'flavours/glitch/store';
|
||||
import { NotificationAdminReport } from './notification_admin_report';
|
||||
import { NotificationAdminSignUp } from './notification_admin_sign_up';
|
||||
import { NotificationAnnualReport } from './notification_annual_report';
|
||||
import { NotificationCollection } from './notification_collection';
|
||||
import { NotificationFavourite } from './notification_favourite';
|
||||
import { NotificationFollow } from './notification_follow';
|
||||
import { NotificationFollowRequest } from './notification_follow_request';
|
||||
@ -156,6 +157,15 @@ export const NotificationGroup: React.FC<{
|
||||
/>
|
||||
);
|
||||
break;
|
||||
case 'added_to_collection':
|
||||
case 'collection_update':
|
||||
content = (
|
||||
<NotificationCollection
|
||||
unread={unread}
|
||||
notification={notificationGroup}
|
||||
/>
|
||||
);
|
||||
break;
|
||||
default:
|
||||
return null;
|
||||
}
|
||||
|
||||
@ -1,15 +0,0 @@
|
||||
import PropTypes from 'prop-types';
|
||||
|
||||
const ColumnSubheading = ({ text }) => {
|
||||
return (
|
||||
<div className='column-subheading'>
|
||||
{text}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
ColumnSubheading.propTypes = {
|
||||
text: PropTypes.string.isRequired,
|
||||
};
|
||||
|
||||
export default ColumnSubheading;
|
||||
@ -4,6 +4,8 @@ import { defineMessages, useIntl } from 'react-intl';
|
||||
|
||||
import { useHistory } from 'react-router';
|
||||
|
||||
import { useAccount } from '@/flavours/glitch/hooks/useAccount';
|
||||
import { me } from '@/flavours/glitch/initial_state';
|
||||
import { deleteCollection } from 'flavours/glitch/reducers/slices/collections';
|
||||
import { useAppDispatch } from 'flavours/glitch/store';
|
||||
|
||||
@ -34,11 +36,12 @@ export const ConfirmDeleteCollectionModal: React.FC<
|
||||
const intl = useIntl();
|
||||
const dispatch = useAppDispatch();
|
||||
const history = useHistory();
|
||||
const { acct: currentUserName } = useAccount(me) ?? {};
|
||||
|
||||
const onConfirm = useCallback(() => {
|
||||
void dispatch(deleteCollection({ collectionId: id }));
|
||||
history.push('/collections');
|
||||
}, [dispatch, history, id]);
|
||||
history.push(`/@${currentUserName}/collections`);
|
||||
}, [dispatch, history, id, currentUserName]);
|
||||
|
||||
return (
|
||||
<ConfirmationModal
|
||||
|
||||
@ -0,0 +1,73 @@
|
||||
import { useCallback } from 'react';
|
||||
|
||||
import { defineMessages, useIntl } from 'react-intl';
|
||||
|
||||
import { useHistory } from 'react-router';
|
||||
|
||||
import { useAccount } from '@/flavours/glitch/hooks/useAccount';
|
||||
import { useCurrentAccountId } from '@/flavours/glitch/hooks/useAccountId';
|
||||
import { domain } from '@/flavours/glitch/initial_state';
|
||||
import { patchProfile } from '@/flavours/glitch/reducers/slices/profile_edit';
|
||||
import { useAppDispatch } from 'flavours/glitch/store';
|
||||
|
||||
import type { BaseConfirmationModalProps } from './confirmation_modal';
|
||||
import { ConfirmationModal } from './confirmation_modal';
|
||||
|
||||
const messages = defineMessages({
|
||||
title: {
|
||||
id: 'confirmations.hide_featured_tab.title',
|
||||
defaultMessage: 'Hide "Featured" tab?',
|
||||
},
|
||||
intro: {
|
||||
id: 'confirmations.hide_featured_tab.intro',
|
||||
defaultMessage:
|
||||
'You can change this at any time under <i>Edit profile > Profile tab settings</i>.',
|
||||
},
|
||||
message: {
|
||||
id: 'confirmations.hide_featured_tab.message',
|
||||
defaultMessage:
|
||||
'This will hide the tab for users on {serverName} and other servers running the latest version of Mastodon. Other displays may vary.',
|
||||
},
|
||||
confirm: {
|
||||
id: 'confirmations.hide_featured_tab.confirm',
|
||||
defaultMessage: 'Hide tab',
|
||||
},
|
||||
});
|
||||
|
||||
export const ConfirmHideFeaturedTabModal: React.FC<
|
||||
BaseConfirmationModalProps
|
||||
> = ({ onClose }) => {
|
||||
const intl = useIntl();
|
||||
const dispatch = useAppDispatch();
|
||||
const history = useHistory();
|
||||
const currentAccountId = useCurrentAccountId();
|
||||
const { acct: currentUserName } = useAccount(currentAccountId) ?? {};
|
||||
|
||||
const onConfirm = useCallback(() => {
|
||||
void dispatch(patchProfile({ show_featured: false }));
|
||||
history.push(`/@${currentUserName}`);
|
||||
}, [currentUserName, dispatch, history]);
|
||||
|
||||
return (
|
||||
<ConfirmationModal
|
||||
title={intl.formatMessage(messages.title)}
|
||||
extraContent={
|
||||
<div className='prose'>
|
||||
<p>
|
||||
{intl.formatMessage(messages.intro, {
|
||||
i: (words) => <i>{words}</i>,
|
||||
})}
|
||||
</p>
|
||||
<p>
|
||||
{intl.formatMessage(messages.message, {
|
||||
serverName: domain,
|
||||
})}
|
||||
</p>
|
||||
</div>
|
||||
}
|
||||
confirm={intl.formatMessage(messages.confirm)}
|
||||
onConfirm={onConfirm}
|
||||
onClose={onClose}
|
||||
/>
|
||||
);
|
||||
};
|
||||
@ -85,7 +85,7 @@ export const MODAL_COMPONENTS = {
|
||||
'DOMAIN_BLOCK': DomainBlockModal,
|
||||
'REPORT': ReportModal,
|
||||
'REPORT_COLLECTION': ReportCollectionModal,
|
||||
'SHARE_COLLECTION': () => import('@/flavours/glitch/features/collections/detail/share_modal').then(module => ({ default: module.CollectionShareModal })),
|
||||
'SHARE_COLLECTION': () => import('@/flavours/glitch/features/collections/components/share_modal').then(module => ({ default: module.CollectionShareModal })),
|
||||
'REVOKE_COLLECTION_INCLUSION': () => import('@/flavours/glitch/features/collections/detail/revoke_collection_inclusion_modal').then(module => ({ default: module.RevokeCollectionInclusionModal })),
|
||||
'SETTINGS': SettingsModal,
|
||||
'DEPRECATED_SETTINGS': () => Promise.resolve({ default: DeprecatedSettingsModal }),
|
||||
@ -113,6 +113,7 @@ export const MODAL_COMPONENTS = {
|
||||
'ACCOUNT_EDIT_IMAGE_ALT': accountEditModal('ImageAltModal'),
|
||||
'ACCOUNT_EDIT_IMAGE_DELETE': accountEditModal('ImageDeleteModal'),
|
||||
'ACCOUNT_EDIT_IMAGE_UPLOAD': accountEditModal('ImageUploadModal'),
|
||||
'ACCOUNT_HIDE_FEATURED_TAB': () => import('@/flavours/glitch/features/ui/components/confirmation_modals/hide_featured_tab').then(module => ({ default: module.ConfirmHideFeaturedTabModal })),
|
||||
};
|
||||
|
||||
/** @arg {keyof import('@/flavours/glitch/features/account_edit/modals')} type */
|
||||
|
||||
@ -240,6 +240,13 @@ class SwitchingColumnsArea extends PureComponent {
|
||||
|
||||
<WrappedRoute path={['/@:acct', '/accounts/:id']} exact component={AccountTimeline} content={children} />
|
||||
<WrappedRoute path={['/@:acct/featured', '/accounts/:id/featured']} component={AccountFeatured} content={children} />
|
||||
{areCollectionsEnabled() &&
|
||||
[
|
||||
<WrappedRoute path={['/@:acct/collections']} component={Collections} content={children} key='collections-list' />,
|
||||
<WrappedRoute path={['/collections/new', '/collections/:id/edit']} component={CollectionsEditor} content={children} key='collections-editor' />,
|
||||
<WrappedRoute path='/collections/:id' component={CollectionDetail} content={children} key='collections-detail' />,
|
||||
]
|
||||
}
|
||||
<WrappedRoute path='/@:acct/tagged/:tagged?' exact component={AccountTimeline} content={children} />
|
||||
<WrappedRoute path={['/@:acct/with_replies', '/accounts/:id/with_replies']} component={AccountTimeline} content={children} componentParams={{ withReplies: true }} />
|
||||
<WrappedRoute path={['/accounts/:id/followers', '/users/:acct/followers', '/@:acct/followers']} component={Followers} content={children} />
|
||||
@ -263,13 +270,6 @@ class SwitchingColumnsArea extends PureComponent {
|
||||
<WrappedRoute path='/followed_tags' component={FollowedTags} content={children} />
|
||||
<WrappedRoute path='/mutes' component={Mutes} content={children} />
|
||||
<WrappedRoute path='/lists' component={Lists} content={children} />
|
||||
{areCollectionsEnabled() &&
|
||||
[
|
||||
<WrappedRoute path={['/collections/new', '/collections/:id/edit']} component={CollectionsEditor} content={children} key='collections-editor' />,
|
||||
<WrappedRoute path='/collections/:id' component={CollectionDetail} content={children} key='collections-detail' />,
|
||||
<WrappedRoute path='/collections' component={Collections} content={children} key='collections-list' />
|
||||
]
|
||||
}
|
||||
<Route component={BundleColumnError} />
|
||||
</WrappedSwitch>
|
||||
</ColumnsArea>
|
||||
|
||||
@ -311,7 +311,7 @@ interface AccountCollectionQuery {
|
||||
|
||||
export const selectAccountCollections = createAppSelector(
|
||||
[
|
||||
(_, accountId: string | null) => accountId,
|
||||
(_, accountId?: string | null) => accountId,
|
||||
(state) => state.collections.accountCollections,
|
||||
(state) => state.collections.collections,
|
||||
],
|
||||
|
||||
@ -4170,15 +4170,6 @@ a.account__display-name {
|
||||
margin: -6px 10px;
|
||||
}
|
||||
|
||||
.column-subheading {
|
||||
background: var(--color-bg-secondary);
|
||||
color: var(--color-text-secondary);
|
||||
padding: 12px 24px;
|
||||
font-size: 13px;
|
||||
font-weight: 500;
|
||||
text-transform: uppercase;
|
||||
}
|
||||
|
||||
.getting-started__wrapper {
|
||||
flex: 0 0 auto;
|
||||
}
|
||||
|
||||
@ -1,7 +1,7 @@
|
||||
@use 'variables' as *;
|
||||
|
||||
.modal-layout {
|
||||
background: var(--color-bg-brand-softest);
|
||||
background: var(--color-bg-primary);
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
height: 100vh;
|
||||
@ -37,6 +37,8 @@
|
||||
}
|
||||
|
||||
.with-zig-zag-decoration {
|
||||
isolation: isolate;
|
||||
|
||||
&::after {
|
||||
content: '';
|
||||
position: absolute;
|
||||
|
||||
@ -53,7 +53,7 @@ export interface BaseApiAccountJSON {
|
||||
header_static: string;
|
||||
header_description: string;
|
||||
id: string;
|
||||
last_status_at: string;
|
||||
last_status_at: string | null;
|
||||
locked: boolean;
|
||||
show_media: boolean;
|
||||
show_media_replies: boolean;
|
||||
|
||||
@ -11,7 +11,6 @@ import { useRelationship } from 'mastodon/hooks/useRelationship';
|
||||
import type { Relationship } from 'mastodon/models/relationship';
|
||||
|
||||
import { EmojiHTML } from '../emoji/html';
|
||||
import { FamiliarFollowers } from '../familiar_followers';
|
||||
import { FollowButton } from '../follow_button';
|
||||
import { FormattedDateWrapper } from '../formatted_date';
|
||||
import { NumberFields, NumberFieldsItem } from '../number_fields';
|
||||
@ -31,10 +30,11 @@ interface Props {
|
||||
accountId: string | undefined;
|
||||
stats?: Stat[];
|
||||
renderButton?: (options: RenderButtonOptions) => React.ReactNode;
|
||||
withBio?: boolean;
|
||||
withBorder?: boolean;
|
||||
}
|
||||
|
||||
const DEFAULT_STATS: Stat[] = ['followers', 'following', 'joined'];
|
||||
const DEFAULT_STATS: Stat[] = ['followers', 'posts', 'last-active'];
|
||||
|
||||
/**
|
||||
* Extended account list item with bio, verified link badge,
|
||||
@ -46,6 +46,7 @@ const DEFAULT_STATS: Stat[] = ['followers', 'following', 'joined'];
|
||||
export const AccountListItem: React.FC<Props> = ({
|
||||
accountId,
|
||||
stats = DEFAULT_STATS,
|
||||
withBio = true,
|
||||
withBorder = true,
|
||||
renderButton = defaultRenderButton,
|
||||
}) => {
|
||||
@ -147,11 +148,15 @@ export const AccountListItem: React.FC<Props> = ({
|
||||
/>
|
||||
}
|
||||
>
|
||||
<RelativeTimestamp
|
||||
long
|
||||
timestamp={account.last_status_at}
|
||||
noFuture
|
||||
/>
|
||||
{account.last_status_at ? (
|
||||
<RelativeTimestamp
|
||||
long
|
||||
timestamp={account.last_status_at}
|
||||
noFuture
|
||||
/>
|
||||
) : (
|
||||
'-'
|
||||
)}
|
||||
</NumberFieldsItem>
|
||||
)}
|
||||
{firstVerifiedField && (
|
||||
@ -161,8 +166,7 @@ export const AccountListItem: React.FC<Props> = ({
|
||||
/>
|
||||
)}
|
||||
</NumberFields>
|
||||
<FamiliarFollowers accountId={accountId} />
|
||||
{account.note.length > 0 && (
|
||||
{withBio && account.note.length > 0 && (
|
||||
<EmojiHTML
|
||||
className={classNames(classes.bio, 'translate')}
|
||||
htmlString={account.note_emojified}
|
||||
|
||||
@ -8,13 +8,6 @@
|
||||
&[data-with-border='true'] {
|
||||
border-bottom: 1px solid var(--color-border-primary);
|
||||
}
|
||||
|
||||
:global(.account__note) {
|
||||
display: -webkit-box;
|
||||
-webkit-box-orient: vertical;
|
||||
-webkit-line-clamp: 3;
|
||||
line-clamp: 3;
|
||||
}
|
||||
}
|
||||
|
||||
.header {
|
||||
@ -40,6 +33,12 @@
|
||||
}
|
||||
|
||||
.bio {
|
||||
overflow: hidden;
|
||||
display: -webkit-box;
|
||||
-webkit-box-orient: vertical;
|
||||
-webkit-line-clamp: 3;
|
||||
line-clamp: 3;
|
||||
|
||||
:any-link {
|
||||
color: var(--color-text-status-links);
|
||||
|
||||
|
||||
@ -73,7 +73,7 @@ const BackButton: React.FC<{
|
||||
};
|
||||
|
||||
export interface Props {
|
||||
title?: string;
|
||||
title?: React.ReactNode;
|
||||
icon?: string;
|
||||
iconComponent?: IconProp;
|
||||
active?: boolean;
|
||||
|
||||
@ -3,16 +3,20 @@
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
max-width: 600px;
|
||||
padding: 20px;
|
||||
padding: 24px;
|
||||
gap: 16px;
|
||||
text-align: center;
|
||||
color: var(--color-text-primary);
|
||||
}
|
||||
|
||||
.content {
|
||||
max-width: 370px;
|
||||
|
||||
svg,
|
||||
img {
|
||||
width: 240px;
|
||||
width: 200px;
|
||||
aspect-ratio: 1;
|
||||
object-fit: contain;
|
||||
max-width: 100%;
|
||||
margin-bottom: 16px;
|
||||
}
|
||||
@ -20,11 +24,14 @@
|
||||
h3 {
|
||||
font-size: 17px;
|
||||
font-weight: 500;
|
||||
text-wrap: balance;
|
||||
line-height: 1.2;
|
||||
}
|
||||
|
||||
p {
|
||||
font-size: 15px;
|
||||
margin-top: 8px;
|
||||
font-size: 15px;
|
||||
color: var(--color-text-secondary);
|
||||
text-wrap: pretty;
|
||||
}
|
||||
}
|
||||
|
||||
@ -8,9 +8,9 @@
|
||||
|
||||
.menuButton {
|
||||
position: absolute;
|
||||
inset-inline-end: 0;
|
||||
top: 0;
|
||||
padding: 9px;
|
||||
inset-inline-end: 1px;
|
||||
top: 1px;
|
||||
padding: 7px;
|
||||
|
||||
&::before {
|
||||
// Subtle divider line separating the button from the input field
|
||||
|
||||
@ -8,7 +8,7 @@
|
||||
|
||||
.copyButton {
|
||||
position: absolute;
|
||||
inset-inline-end: 0;
|
||||
top: 0;
|
||||
padding: 9px;
|
||||
inset-inline-end: 1px;
|
||||
top: 1px;
|
||||
padding: 7px;
|
||||
}
|
||||
|
||||
@ -1,3 +1,5 @@
|
||||
import { useCallback } from 'react';
|
||||
|
||||
import { FormattedMessage } from 'react-intl';
|
||||
|
||||
import { useParams } from 'react-router';
|
||||
@ -5,17 +7,21 @@ import { Link } from 'react-router-dom';
|
||||
|
||||
import ElephantDarkImage from '@/images/elephant_ui_dark.svg?react';
|
||||
import ElephantLightImage from '@/images/elephant_ui_light.svg?react';
|
||||
import { openModal } from '@/mastodon/actions/modal';
|
||||
import { Button } from '@/mastodon/components/button';
|
||||
import { EmptyState } from '@/mastodon/components/empty_state';
|
||||
import { LimitedAccountHint } from '@/mastodon/features/account_timeline/components/limited_account_hint';
|
||||
import { areCollectionsEnabled } from '@/mastodon/features/collections/utils';
|
||||
import { useCurrentAccountId } from '@/mastodon/hooks/useAccountId';
|
||||
import { useTheme } from '@/mastodon/hooks/useTheme';
|
||||
import { useAppDispatch } from '@/mastodon/store';
|
||||
|
||||
interface EmptyMessageProps {
|
||||
suspended: boolean;
|
||||
hidden: boolean;
|
||||
blockedBy: boolean;
|
||||
accountId?: string;
|
||||
withoutAddCollectionButton?: boolean;
|
||||
}
|
||||
|
||||
export const EmptyMessage: React.FC<EmptyMessageProps> = ({
|
||||
@ -23,6 +29,7 @@ export const EmptyMessage: React.FC<EmptyMessageProps> = ({
|
||||
suspended,
|
||||
hidden,
|
||||
blockedBy,
|
||||
withoutAddCollectionButton,
|
||||
}) => {
|
||||
const { acct } = useParams<{ acct?: string }>();
|
||||
const me = useCurrentAccountId();
|
||||
@ -30,6 +37,17 @@ export const EmptyMessage: React.FC<EmptyMessageProps> = ({
|
||||
const ElephantImage =
|
||||
theme === 'dark' ? ElephantDarkImage : ElephantLightImage;
|
||||
|
||||
const dispatch = useAppDispatch();
|
||||
|
||||
const confirmHideFeaturedTab = useCallback(() => {
|
||||
void dispatch(
|
||||
openModal({
|
||||
modalType: 'ACCOUNT_HIDE_FEATURED_TAB',
|
||||
modalProps: {},
|
||||
}),
|
||||
);
|
||||
}, [dispatch]);
|
||||
|
||||
if (!accountId) {
|
||||
return null;
|
||||
}
|
||||
@ -49,17 +67,31 @@ export const EmptyMessage: React.FC<EmptyMessageProps> = ({
|
||||
image={image}
|
||||
title={
|
||||
<FormattedMessage
|
||||
id='empty_column.account_featured_self.no_collections'
|
||||
defaultMessage='No collections yet'
|
||||
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.'
|
||||
/>
|
||||
}
|
||||
>
|
||||
<Link to='/collections/new' className='button'>
|
||||
{!withoutAddCollectionButton && (
|
||||
<Link to='/collections/new' className='button'>
|
||||
<FormattedMessage
|
||||
id='empty_column.account_featured_self.no_collections_button'
|
||||
defaultMessage='Create a collection'
|
||||
/>
|
||||
</Link>
|
||||
)}
|
||||
<Button secondary onClick={confirmHideFeaturedTab}>
|
||||
<FormattedMessage
|
||||
id='empty_column.account_featured_self.no_collections_button'
|
||||
defaultMessage='Create a collection'
|
||||
id='empty_column.account_featured_self.no_collections_hide_tab'
|
||||
defaultMessage='Hide this tab instead'
|
||||
/>
|
||||
</Link>
|
||||
</Button>
|
||||
</EmptyState>
|
||||
);
|
||||
} else {
|
||||
@ -72,7 +104,7 @@ export const EmptyMessage: React.FC<EmptyMessageProps> = ({
|
||||
message = (
|
||||
<FormattedMessage
|
||||
id='empty_column.account_featured_self.pre_collections_desc'
|
||||
defaultMessage='Collections (coming in Mastodon 4.6) allows you to create your own curated lists of accounts to recommend to others.'
|
||||
defaultMessage='Collections (coming in Mastodon 4.6) allow you to create your own curated lists of accounts to recommend to others.'
|
||||
/>
|
||||
);
|
||||
}
|
||||
@ -93,47 +125,21 @@ export const EmptyMessage: React.FC<EmptyMessageProps> = ({
|
||||
/>
|
||||
);
|
||||
} else {
|
||||
// Standard other account empty state.
|
||||
title = (
|
||||
<FormattedMessage
|
||||
id='empty_column.account_featured_other.title'
|
||||
defaultMessage='Nothing to see here'
|
||||
/>
|
||||
);
|
||||
if (hasCollections) {
|
||||
if (acct) {
|
||||
message = (
|
||||
<FormattedMessage
|
||||
id='empty_column.account_featured_other.no_collections_desc'
|
||||
defaultMessage='{acct} hasn’t created any collections yet.'
|
||||
values={{ acct }}
|
||||
/>
|
||||
);
|
||||
} else {
|
||||
message = (
|
||||
<FormattedMessage
|
||||
id='empty_column.account_featured_unknown.no_collections_desc'
|
||||
defaultMessage='This account hasn’t created any collections yet.'
|
||||
/>
|
||||
);
|
||||
}
|
||||
if (acct) {
|
||||
title = (
|
||||
<FormattedMessage
|
||||
id='empty_column.account_featured.other'
|
||||
defaultMessage='{acct} has not featured anything yet.'
|
||||
values={{ acct }}
|
||||
/>
|
||||
);
|
||||
} else {
|
||||
if (acct) {
|
||||
message = (
|
||||
<FormattedMessage
|
||||
id='empty_column.account_featured.other'
|
||||
defaultMessage='{acct} hasn’t featured anything yet.'
|
||||
values={{ acct }}
|
||||
/>
|
||||
);
|
||||
} else {
|
||||
message = (
|
||||
<FormattedMessage
|
||||
id='empty_column.account_featured_unknown.other'
|
||||
defaultMessage='This account hasn’t featured anything yet.'
|
||||
/>
|
||||
);
|
||||
}
|
||||
title = (
|
||||
<FormattedMessage
|
||||
id='empty_column.account_featured_unknown.other'
|
||||
defaultMessage='This account has not featured anything yet.'
|
||||
/>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -0,0 +1,37 @@
|
||||
.subheading {
|
||||
box-sizing: border-box;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
min-height: 52px;
|
||||
padding: 12px 16px;
|
||||
font-size: 13px;
|
||||
font-weight: 500;
|
||||
text-transform: uppercase;
|
||||
color: var(--color-text-primary);
|
||||
background: var(--color-bg-secondary);
|
||||
}
|
||||
|
||||
.link {
|
||||
display: flex;
|
||||
padding-block: 4px;
|
||||
font-size: 13px;
|
||||
font-weight: 500;
|
||||
text-transform: none;
|
||||
text-decoration: none;
|
||||
color: inherit;
|
||||
|
||||
&:hover {
|
||||
text-decoration: underline;
|
||||
}
|
||||
|
||||
&:focus-visible {
|
||||
outline: var(--outline-focus-default);
|
||||
outline-offset: 2px;
|
||||
}
|
||||
|
||||
:global(.icon) {
|
||||
width: 18px;
|
||||
height: 18px;
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,42 @@
|
||||
import type { ComponentPropsWithoutRef } from 'react';
|
||||
|
||||
import classNames from 'classnames';
|
||||
import { Link } from 'react-router-dom';
|
||||
|
||||
import type { IconProp } from '@/mastodon/components/icon';
|
||||
import { Icon } from '@/mastodon/components/icon';
|
||||
import { polymorphicForwardRef } from '@/types/polymorphic';
|
||||
|
||||
import classes from './subheading.module.scss';
|
||||
|
||||
export const Subheading = polymorphicForwardRef<'h2'>(
|
||||
({ as: Component = 'h2', children, className, ...props }, ref) => {
|
||||
return (
|
||||
<Component
|
||||
ref={ref}
|
||||
className={classNames(classes.subheading, className)}
|
||||
{...props}
|
||||
>
|
||||
{children}
|
||||
</Component>
|
||||
);
|
||||
},
|
||||
);
|
||||
|
||||
interface SubheadingLinkProps extends ComponentPropsWithoutRef<typeof Link> {
|
||||
icon: IconProp;
|
||||
}
|
||||
|
||||
export const SubheadingLink: React.FC<SubheadingLinkProps> = ({
|
||||
icon,
|
||||
children,
|
||||
className,
|
||||
...props
|
||||
}) => {
|
||||
return (
|
||||
<Link className={classNames(classes.link, className)} {...props}>
|
||||
<Icon id='subheading-icon' icon={icon} />
|
||||
{children}
|
||||
</Link>
|
||||
);
|
||||
};
|
||||
@ -6,9 +6,10 @@ import { useHistory } from 'react-router';
|
||||
|
||||
import { List as ImmutableList } from 'immutable';
|
||||
|
||||
import { AccountListItem } from '@/mastodon/components/account_list_item';
|
||||
import { useAccount } from '@/mastodon/hooks/useAccount';
|
||||
import AddIcon from '@/material-icons/400-24px/add.svg?react';
|
||||
import { fetchEndorsedAccounts } from 'mastodon/actions/accounts';
|
||||
import { Account } from 'mastodon/components/account';
|
||||
import { ColumnBackButton } from 'mastodon/components/column_back_button';
|
||||
import { LoadingIndicator } from 'mastodon/components/loading_indicator';
|
||||
import { RemoteHint } from 'mastodon/components/remote_hint';
|
||||
@ -28,10 +29,13 @@ import {
|
||||
} from 'mastodon/reducers/slices/collections';
|
||||
import { useAppDispatch, useAppSelector } from 'mastodon/store';
|
||||
|
||||
import { CollectionListItem } from '../collections/detail/collection_list_item';
|
||||
import { CollectionListItem } from '../collections/components/collection_list_item';
|
||||
import { areCollectionsEnabled } from '../collections/utils';
|
||||
|
||||
import { EmptyMessage } from './components/empty_message';
|
||||
import { Subheading, SubheadingLink } from './components/subheading';
|
||||
|
||||
const collectionsEnabled = areCollectionsEnabled();
|
||||
|
||||
const AccountFeatured: React.FC<{ multiColumn: boolean }> = ({
|
||||
multiColumn,
|
||||
@ -54,13 +58,12 @@ const AccountFeatured: React.FC<{ multiColumn: boolean }> = ({
|
||||
if (accountId) {
|
||||
void dispatch(fetchEndorsedAccounts({ accountId }));
|
||||
|
||||
if (areCollectionsEnabled()) {
|
||||
if (collectionsEnabled) {
|
||||
void dispatch(fetchAccountCollections({ accountId }));
|
||||
}
|
||||
}
|
||||
}, [accountId, dispatch]);
|
||||
|
||||
const isLoading = !accountId;
|
||||
const featuredAccountIds = useAppSelector(
|
||||
(state) =>
|
||||
state.user_lists.getIn(
|
||||
@ -68,8 +71,8 @@ const AccountFeatured: React.FC<{ multiColumn: boolean }> = ({
|
||||
ImmutableList(),
|
||||
) as ImmutableList<string>,
|
||||
);
|
||||
const { collections, status } = useAppSelector((state) =>
|
||||
selectAccountCollections(state, accountId ?? null),
|
||||
const { collections, status: collectionsLoadStatus } = useAppSelector(
|
||||
(state) => selectAccountCollections(state, accountId ?? null),
|
||||
);
|
||||
const listedCollections = collections.filter(
|
||||
// Hide unlisted and empty collections to avoid confusion
|
||||
@ -78,6 +81,15 @@ const AccountFeatured: React.FC<{ multiColumn: boolean }> = ({
|
||||
(item) => item.discoverable && !!item.item_count,
|
||||
);
|
||||
|
||||
const hasCollections =
|
||||
collectionsEnabled &&
|
||||
collectionsLoadStatus === 'idle' &&
|
||||
listedCollections.length > 0;
|
||||
|
||||
const hasFeaturedAccounts = !featuredAccountIds.isEmpty();
|
||||
|
||||
const isLoading = !accountId || collectionsLoadStatus !== 'idle';
|
||||
|
||||
if (accountId === null) {
|
||||
return <BundleColumnError multiColumn={multiColumn} errorType='routing' />;
|
||||
}
|
||||
@ -92,7 +104,7 @@ const AccountFeatured: React.FC<{ multiColumn: boolean }> = ({
|
||||
);
|
||||
}
|
||||
|
||||
if (featuredAccountIds.isEmpty() && listedCollections.length === 0) {
|
||||
if (!hasFeaturedAccounts && !hasCollections) {
|
||||
return (
|
||||
<AccountFeaturedWrapper accountId={accountId}>
|
||||
<EmptyMessage
|
||||
@ -114,36 +126,14 @@ const AccountFeatured: React.FC<{ multiColumn: boolean }> = ({
|
||||
{accountId && (
|
||||
<AccountHeader accountId={accountId} hideTabs={forceEmptyState} />
|
||||
)}
|
||||
{listedCollections.length > 0 && status === 'idle' && (
|
||||
<>
|
||||
<h4 className='column-subheading'>
|
||||
<FormattedMessage
|
||||
id='account.featured.collections'
|
||||
defaultMessage='Collections'
|
||||
/>
|
||||
</h4>
|
||||
<ItemList>
|
||||
{listedCollections.map((item, index) => (
|
||||
<CollectionListItem
|
||||
key={item.id}
|
||||
collection={item}
|
||||
withoutBorder={index === listedCollections.length - 1}
|
||||
withAuthorHandle={false}
|
||||
positionInList={index + 1}
|
||||
listSize={listedCollections.length}
|
||||
/>
|
||||
))}
|
||||
</ItemList>
|
||||
</>
|
||||
)}
|
||||
{!featuredAccountIds.isEmpty() && (
|
||||
<>
|
||||
<h4 className='column-subheading'>
|
||||
<Subheading as='h2'>
|
||||
<FormattedMessage
|
||||
id='account.featured.accounts'
|
||||
defaultMessage='Profiles'
|
||||
/>
|
||||
</h4>
|
||||
</Subheading>
|
||||
<ItemList>
|
||||
{featuredAccountIds.map((featuredAccountId, index) => (
|
||||
<Article
|
||||
@ -152,12 +142,52 @@ const AccountFeatured: React.FC<{ multiColumn: boolean }> = ({
|
||||
aria-posinset={index + 1}
|
||||
aria-setsize={featuredAccountIds.size}
|
||||
>
|
||||
<Account id={featuredAccountId} />
|
||||
<AccountListItem accountId={featuredAccountId} />
|
||||
</Article>
|
||||
))}
|
||||
</ItemList>
|
||||
</>
|
||||
)}
|
||||
{collectionsEnabled && (
|
||||
<>
|
||||
<Subheading as='header'>
|
||||
<h2>
|
||||
<FormattedMessage
|
||||
id='account.featured.collections'
|
||||
defaultMessage='Collections'
|
||||
/>
|
||||
</h2>
|
||||
<SubheadingLink to='/collections/new' icon={AddIcon}>
|
||||
<FormattedMessage
|
||||
id='account.featured.new_collection'
|
||||
defaultMessage='New collection'
|
||||
/>
|
||||
</SubheadingLink>
|
||||
</Subheading>
|
||||
{hasCollections ? (
|
||||
<ItemList>
|
||||
{listedCollections.map((item, index) => (
|
||||
<CollectionListItem
|
||||
key={item.id}
|
||||
collection={item}
|
||||
withoutBorder={index === listedCollections.length - 1}
|
||||
withAuthorHandle={false}
|
||||
positionInList={index + 1}
|
||||
listSize={listedCollections.length}
|
||||
/>
|
||||
))}
|
||||
</ItemList>
|
||||
) : (
|
||||
<EmptyMessage
|
||||
withoutAddCollectionButton
|
||||
blockedBy={blockedBy}
|
||||
hidden={hidden}
|
||||
suspended={suspended}
|
||||
accountId={accountId}
|
||||
/>
|
||||
)}
|
||||
</>
|
||||
)}
|
||||
<RemoteHint accountId={accountId} />
|
||||
</Scrollable>
|
||||
</Column>
|
||||
|
||||
@ -8,8 +8,6 @@ import { NavLink } from 'react-router-dom';
|
||||
import { useAccount } from '@/mastodon/hooks/useAccount';
|
||||
import { useAccountId } from '@/mastodon/hooks/useAccountId';
|
||||
|
||||
import { areCollectionsEnabled } from '../../collections/utils';
|
||||
|
||||
import classes from './styles.module.scss';
|
||||
|
||||
const isActive: Required<NavLinkProps>['isActive'] = (match, location) =>
|
||||
@ -41,14 +39,7 @@ export const AccountTabs: FC = () => {
|
||||
)}
|
||||
{show_featured && (
|
||||
<NavLink exact to={`/@${acct}/featured`}>
|
||||
{areCollectionsEnabled() ? (
|
||||
<FormattedMessage
|
||||
id='account.featured.collections'
|
||||
defaultMessage='Collections'
|
||||
/>
|
||||
) : (
|
||||
<FormattedMessage id='account.featured' defaultMessage='Featured' />
|
||||
)}
|
||||
<FormattedMessage id='account.featured' defaultMessage='Featured' />
|
||||
</NavLink>
|
||||
)}
|
||||
</div>
|
||||
|
||||
@ -0,0 +1,20 @@
|
||||
.wrapper {
|
||||
display: flex;
|
||||
align-items: start;
|
||||
padding: 12px 16px;
|
||||
gap: 16px;
|
||||
|
||||
&:not(.wrapperWithoutBorder) {
|
||||
border-bottom: 1px solid var(--color-border-primary);
|
||||
}
|
||||
}
|
||||
|
||||
.menuButton {
|
||||
padding: 4px;
|
||||
margin-top: -2px;
|
||||
|
||||
svg {
|
||||
width: 20px;
|
||||
height: 20px;
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,50 @@
|
||||
import { useId } from 'react';
|
||||
|
||||
import classNames from 'classnames';
|
||||
|
||||
import { Article } from 'mastodon/components/scrollable_list/components';
|
||||
import type { CollectionLockupProps } from 'mastodon/features/collections/components/collection_lockup';
|
||||
import { CollectionLockup } from 'mastodon/features/collections/components/collection_lockup';
|
||||
import { CollectionMenu } from 'mastodon/features/collections/components/collection_menu';
|
||||
|
||||
import classes from './collection_list_item.module.scss';
|
||||
|
||||
interface CollectionListItemProps extends CollectionLockupProps {
|
||||
withoutBorder?: boolean;
|
||||
positionInList: number;
|
||||
listSize: number;
|
||||
}
|
||||
|
||||
export const CollectionListItem: React.FC<CollectionListItemProps> = ({
|
||||
collection,
|
||||
withoutBorder,
|
||||
positionInList,
|
||||
listSize,
|
||||
...otherProps
|
||||
}) => {
|
||||
const uniqueId = useId();
|
||||
const linkId = `${uniqueId}-link`;
|
||||
const infoId = `${uniqueId}-info`;
|
||||
|
||||
return (
|
||||
<Article
|
||||
focusable
|
||||
className={classNames(
|
||||
classes.wrapper,
|
||||
withoutBorder && classes.wrapperWithoutBorder,
|
||||
)}
|
||||
aria-labelledby={linkId}
|
||||
aria-describedby={infoId}
|
||||
aria-posinset={positionInList}
|
||||
aria-setsize={listSize}
|
||||
>
|
||||
<CollectionLockup collection={collection} {...otherProps} />
|
||||
|
||||
<CollectionMenu
|
||||
context='list'
|
||||
collection={collection}
|
||||
className={classes.menuButton}
|
||||
/>
|
||||
</Article>
|
||||
);
|
||||
};
|
||||
@ -1,21 +1,11 @@
|
||||
.wrapper {
|
||||
display: flex;
|
||||
align-items: start;
|
||||
margin-inline: 24px;
|
||||
padding-block: 12px;
|
||||
gap: 16px;
|
||||
|
||||
&:not(.wrapperWithoutBorder) {
|
||||
border-bottom: 1px solid var(--color-border-primary);
|
||||
}
|
||||
}
|
||||
|
||||
.content {
|
||||
position: relative;
|
||||
flex-grow: 1;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
column-gap: 12px;
|
||||
color: var(--color-text-primary);
|
||||
line-height: 1.3;
|
||||
}
|
||||
|
||||
.avatarGrid {
|
||||
@ -70,13 +60,3 @@
|
||||
font-size: 13px;
|
||||
color: var(--color-text-secondary);
|
||||
}
|
||||
|
||||
.menuButton {
|
||||
padding: 4px;
|
||||
margin-top: -2px;
|
||||
|
||||
svg {
|
||||
width: 20px;
|
||||
height: 20px;
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,115 @@
|
||||
import { useId } from 'react';
|
||||
|
||||
import { FormattedMessage } from 'react-intl';
|
||||
|
||||
import classNames from 'classnames';
|
||||
import { Link } from 'react-router-dom';
|
||||
|
||||
import WarningIcon from '@/material-icons/400-24px/warning.svg?react';
|
||||
import type { ApiCollectionJSON } from 'mastodon/api_types/collections';
|
||||
import { AvatarById } from 'mastodon/components/avatar';
|
||||
import { useAccountHandle } from 'mastodon/components/display_name/default';
|
||||
import { RelativeTimestamp } from 'mastodon/components/relative_timestamp';
|
||||
import { useAccount } from 'mastodon/hooks/useAccount';
|
||||
import { domain } from 'mastodon/initial_state';
|
||||
|
||||
import classes from './collection_lockup.module.scss';
|
||||
|
||||
export const AvatarGrid: React.FC<{
|
||||
accountIds: (string | undefined)[];
|
||||
sensitive?: boolean;
|
||||
}> = ({ accountIds: ids, sensitive }) => {
|
||||
const avatarIds = [ids[0], ids[1], ids[2], ids[3]];
|
||||
return (
|
||||
<div
|
||||
className={classNames(
|
||||
classes.avatarGrid,
|
||||
sensitive ? classes.avatarGridSensitive : null,
|
||||
)}
|
||||
>
|
||||
{avatarIds.map((id) => (
|
||||
<AvatarById
|
||||
animate={false}
|
||||
key={id}
|
||||
accountId={id}
|
||||
className={classes.avatar}
|
||||
size={25}
|
||||
/>
|
||||
))}
|
||||
{sensitive && <WarningIcon className={classes.avatarSensitiveBadge} />}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export interface CollectionLockupProps {
|
||||
collection: ApiCollectionJSON;
|
||||
withAuthorHandle?: boolean;
|
||||
withTimestamp?: boolean;
|
||||
}
|
||||
|
||||
export const CollectionLockup: React.FC<CollectionLockupProps> = ({
|
||||
collection,
|
||||
withAuthorHandle = true,
|
||||
withTimestamp,
|
||||
}) => {
|
||||
const { id, name } = collection;
|
||||
const uniqueId = useId();
|
||||
const linkId = `${uniqueId}-link`;
|
||||
const infoId = `${uniqueId}-info`;
|
||||
const authorAccount = useAccount(collection.account_id);
|
||||
const authorHandle = useAccountHandle(authorAccount, domain);
|
||||
|
||||
return (
|
||||
<div className={classes.content}>
|
||||
<AvatarGrid
|
||||
accountIds={collection.items.map((item) => item.account_id)}
|
||||
sensitive={collection.sensitive}
|
||||
/>
|
||||
<div>
|
||||
<h2 id={linkId}>
|
||||
<Link to={`/collections/${id}`} className={classes.link}>
|
||||
{name}
|
||||
</Link>
|
||||
</h2>
|
||||
<ul className={classes.info} id={infoId}>
|
||||
{collection.sensitive && (
|
||||
<li className='sr-only'>
|
||||
<FormattedMessage
|
||||
id='collections.sensitive'
|
||||
defaultMessage='Sensitive'
|
||||
/>
|
||||
</li>
|
||||
)}
|
||||
{withAuthorHandle && authorAccount && (
|
||||
<FormattedMessage
|
||||
id='collections.by_account'
|
||||
defaultMessage='by {account_handle}'
|
||||
values={{
|
||||
account_handle: authorHandle,
|
||||
}}
|
||||
tagName='li'
|
||||
/>
|
||||
)}
|
||||
<FormattedMessage
|
||||
id='collections.account_count'
|
||||
defaultMessage='{count, plural, one {# account} other {# accounts}}'
|
||||
values={{ count: collection.item_count }}
|
||||
tagName='li'
|
||||
/>
|
||||
{withTimestamp && (
|
||||
<FormattedMessage
|
||||
id='collections.last_updated_at'
|
||||
defaultMessage='Last updated: {date}'
|
||||
values={{
|
||||
date: (
|
||||
<RelativeTimestamp timestamp={collection.updated_at} long />
|
||||
),
|
||||
}}
|
||||
tagName='li'
|
||||
/>
|
||||
)}
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
@ -0,0 +1,8 @@
|
||||
.wrapper {
|
||||
display: flex;
|
||||
align-items: start;
|
||||
padding: 12px;
|
||||
gap: 12px;
|
||||
border-radius: 12px;
|
||||
border: 1px solid var(--color-border-primary);
|
||||
}
|
||||
@ -0,0 +1,20 @@
|
||||
import type { CollectionLockupProps } from 'mastodon/features/collections/components/collection_lockup';
|
||||
import { CollectionLockup } from 'mastodon/features/collections/components/collection_lockup';
|
||||
|
||||
import classes from './collection_preview_card.module.scss';
|
||||
|
||||
interface CollectionPreviewCardProps extends CollectionLockupProps {
|
||||
onRemove?: () => void;
|
||||
}
|
||||
|
||||
export const CollectionPreviewCard: React.FC<CollectionPreviewCardProps> = ({
|
||||
collection,
|
||||
onRemove,
|
||||
...otherProps
|
||||
}) => {
|
||||
return (
|
||||
<div className={classes.wrapper}>
|
||||
<CollectionLockup collection={collection} {...otherProps} />
|
||||
</div>
|
||||
);
|
||||
};
|
||||
@ -5,27 +5,7 @@
|
||||
}
|
||||
|
||||
.preview {
|
||||
display: flex;
|
||||
flex-wrap: wrap-reverse;
|
||||
align-items: start;
|
||||
justify-content: space-between;
|
||||
gap: 8px;
|
||||
padding: 16px;
|
||||
margin-bottom: 16px;
|
||||
border-radius: 8px;
|
||||
color: var(--color-text-primary);
|
||||
background: linear-gradient(
|
||||
145deg,
|
||||
var(--color-bg-brand-soft),
|
||||
var(--color-bg-primary)
|
||||
);
|
||||
border: 1px solid var(--color-bg-brand-base);
|
||||
}
|
||||
|
||||
.previewHeading {
|
||||
font-size: 22px;
|
||||
line-height: 1.3;
|
||||
margin-bottom: 4px;
|
||||
}
|
||||
|
||||
.actions {
|
||||
@ -8,8 +8,6 @@ import { me } from '@/mastodon/initial_state';
|
||||
import CloseIcon from '@/material-icons/400-24px/close.svg?react';
|
||||
import { changeCompose, focusCompose } from 'mastodon/actions/compose';
|
||||
import type { ApiCollectionJSON } from 'mastodon/api_types/collections';
|
||||
import { AvatarById } from 'mastodon/components/avatar';
|
||||
import { AvatarGroup } from 'mastodon/components/avatar_group';
|
||||
import { Button } from 'mastodon/components/button';
|
||||
import { CopyLinkField } from 'mastodon/components/form_fields';
|
||||
import { IconButton } from 'mastodon/components/icon_button';
|
||||
@ -20,7 +18,7 @@ import {
|
||||
} from 'mastodon/components/modal_shell';
|
||||
import { useAppDispatch } from 'mastodon/store';
|
||||
|
||||
import { AuthorNote } from '.';
|
||||
import { CollectionPreviewCard } from './collection_preview_card';
|
||||
import classes from './share_modal.module.scss';
|
||||
|
||||
const messages = defineMessages({
|
||||
@ -95,24 +93,13 @@ export const CollectionShareModal: React.FC<{
|
||||
/>
|
||||
|
||||
<div className={classes.preview}>
|
||||
<div>
|
||||
<h2 className={classes.previewHeading}>{collection.name}</h2>
|
||||
<AuthorNote id={collection.account_id} />
|
||||
</div>
|
||||
<AvatarGroup>
|
||||
{collection.items.slice(0, 5).map(({ account_id }) => {
|
||||
if (!account_id) return;
|
||||
return (
|
||||
<AvatarById key={account_id} accountId={account_id} size={28} />
|
||||
);
|
||||
})}
|
||||
</AvatarGroup>
|
||||
<CollectionPreviewCard collection={collection} />
|
||||
</div>
|
||||
|
||||
<CopyLinkField
|
||||
label={intl.formatMessage({
|
||||
id: 'collection.share_modal.share_link_label',
|
||||
defaultMessage: 'Invite share link',
|
||||
defaultMessage: 'Share link',
|
||||
})}
|
||||
value={collectionLink}
|
||||
/>
|
||||
@ -146,7 +146,6 @@ export const CollectionAccountsList: React.FC<{
|
||||
<AccountListItem
|
||||
accountId={account_id}
|
||||
withBorder={index !== items.length - 1}
|
||||
stats={['followers', 'last-active']}
|
||||
renderButton={renderAccountItemButton}
|
||||
/>
|
||||
</Article>
|
||||
|
||||
@ -1,139 +0,0 @@
|
||||
import { useId } from 'react';
|
||||
|
||||
import { FormattedMessage } from 'react-intl';
|
||||
|
||||
import classNames from 'classnames';
|
||||
import { Link } from 'react-router-dom';
|
||||
|
||||
import WarningIcon from '@/material-icons/400-24px/warning.svg?react';
|
||||
import type { ApiCollectionJSON } from 'mastodon/api_types/collections';
|
||||
import { AvatarById } from 'mastodon/components/avatar';
|
||||
import { useAccountHandle } from 'mastodon/components/display_name/default';
|
||||
import { RelativeTimestamp } from 'mastodon/components/relative_timestamp';
|
||||
import { Article } from 'mastodon/components/scrollable_list/components';
|
||||
import { useAccount } from 'mastodon/hooks/useAccount';
|
||||
import { domain } from 'mastodon/initial_state';
|
||||
|
||||
import classes from './collection_list_item.module.scss';
|
||||
import { CollectionMenu } from './collection_menu';
|
||||
|
||||
export const AvatarGrid: React.FC<{
|
||||
accountIds: (string | undefined)[];
|
||||
sensitive?: boolean;
|
||||
}> = ({ accountIds: ids, sensitive }) => {
|
||||
const avatarIds = [ids[0], ids[1], ids[2], ids[3]];
|
||||
return (
|
||||
<div
|
||||
className={classNames(
|
||||
classes.avatarGrid,
|
||||
sensitive ? classes.avatarGridSensitive : null,
|
||||
)}
|
||||
>
|
||||
{avatarIds.map((id) => (
|
||||
<AvatarById
|
||||
animate={false}
|
||||
key={id}
|
||||
accountId={id}
|
||||
className={classes.avatar}
|
||||
size={25}
|
||||
/>
|
||||
))}
|
||||
{sensitive && <WarningIcon className={classes.avatarSensitiveBadge} />}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export const CollectionListItem: React.FC<{
|
||||
collection: ApiCollectionJSON;
|
||||
withoutBorder?: boolean;
|
||||
withAuthorHandle?: boolean;
|
||||
withTimestamp?: boolean;
|
||||
positionInList: number;
|
||||
listSize: number;
|
||||
}> = ({
|
||||
collection,
|
||||
withoutBorder,
|
||||
withAuthorHandle = true,
|
||||
withTimestamp,
|
||||
positionInList,
|
||||
listSize,
|
||||
}) => {
|
||||
const { id, name } = collection;
|
||||
const uniqueId = useId();
|
||||
const linkId = `${uniqueId}-link`;
|
||||
const infoId = `${uniqueId}-info`;
|
||||
const authorAccount = useAccount(collection.account_id);
|
||||
const authorHandle = useAccountHandle(authorAccount, domain);
|
||||
|
||||
return (
|
||||
<Article
|
||||
focusable
|
||||
className={classNames(
|
||||
classes.wrapper,
|
||||
withoutBorder && classes.wrapperWithoutBorder,
|
||||
)}
|
||||
aria-labelledby={linkId}
|
||||
aria-describedby={infoId}
|
||||
aria-posinset={positionInList}
|
||||
aria-setsize={listSize}
|
||||
>
|
||||
<div className={classes.content}>
|
||||
<AvatarGrid
|
||||
accountIds={collection.items.map((item) => item.account_id)}
|
||||
sensitive={collection.sensitive}
|
||||
/>
|
||||
<div>
|
||||
<h2 id={linkId}>
|
||||
<Link to={`/collections/${id}`} className={classes.link}>
|
||||
{name}
|
||||
</Link>
|
||||
</h2>
|
||||
<ul className={classes.info} id={infoId}>
|
||||
{collection.sensitive && (
|
||||
<li className='sr-only'>
|
||||
<FormattedMessage
|
||||
id='collections.sensitive'
|
||||
defaultMessage='Sensitive'
|
||||
/>
|
||||
</li>
|
||||
)}
|
||||
{withAuthorHandle && authorAccount && (
|
||||
<FormattedMessage
|
||||
id='collections.by_account'
|
||||
defaultMessage='by {account_handle}'
|
||||
values={{
|
||||
account_handle: authorHandle,
|
||||
}}
|
||||
tagName='li'
|
||||
/>
|
||||
)}
|
||||
<FormattedMessage
|
||||
id='collections.account_count'
|
||||
defaultMessage='{count, plural, one {# account} other {# accounts}}'
|
||||
values={{ count: collection.item_count }}
|
||||
tagName='li'
|
||||
/>
|
||||
{withTimestamp && (
|
||||
<FormattedMessage
|
||||
id='collections.last_updated_at'
|
||||
defaultMessage='Last updated: {date}'
|
||||
values={{
|
||||
date: (
|
||||
<RelativeTimestamp timestamp={collection.updated_at} long />
|
||||
),
|
||||
}}
|
||||
tagName='li'
|
||||
/>
|
||||
)}
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<CollectionMenu
|
||||
context='list'
|
||||
collection={collection}
|
||||
className={classes.menuButton}
|
||||
/>
|
||||
</Article>
|
||||
);
|
||||
};
|
||||
@ -23,8 +23,9 @@ import { domain, me } from 'mastodon/initial_state';
|
||||
import { fetchCollection } from 'mastodon/reducers/slices/collections';
|
||||
import { useAppDispatch, useAppSelector } from 'mastodon/store';
|
||||
|
||||
import { CollectionMenu } from '../components/collection_menu';
|
||||
|
||||
import { CollectionAccountsList } from './accounts_list';
|
||||
import { CollectionMenu } from './collection_menu';
|
||||
import { useConfirmRevoke } from './revoke_collection_inclusion_modal';
|
||||
import classes from './styles.module.scss';
|
||||
|
||||
@ -66,7 +67,7 @@ export const AuthorNote: React.FC<{ id: string }> = ({ id }) => {
|
||||
);
|
||||
};
|
||||
|
||||
export const RevokeControls: React.FC<{
|
||||
const RevokeControls: React.FC<{
|
||||
collection: ApiCollectionJSON;
|
||||
}> = ({ collection }) => {
|
||||
const authorAccount = useAccount(collection.account_id);
|
||||
|
||||
@ -61,9 +61,11 @@ const AddedAccountItem: React.FC<{
|
||||
onRemove(accountId);
|
||||
}, [accountId, onRemove]);
|
||||
|
||||
const lastStatusAt = account?.last_status_at;
|
||||
|
||||
const lastPostHint = useMemo(
|
||||
() =>
|
||||
isOlderThanAWeek(account?.last_status_at) && (
|
||||
(!lastStatusAt || isOlderThanAWeek(lastStatusAt)) && (
|
||||
<Badge
|
||||
label={
|
||||
<FormattedMessage
|
||||
@ -75,7 +77,7 @@ const AddedAccountItem: React.FC<{
|
||||
className={classes.accountBadge}
|
||||
/>
|
||||
),
|
||||
[account?.last_status_at],
|
||||
[lastStatusAt],
|
||||
);
|
||||
|
||||
return (
|
||||
|
||||
@ -10,22 +10,29 @@ import CollectionsFilledIcon from '@/material-icons/400-24px/category-fill.svg?r
|
||||
import SquigglyArrow from '@/svg-icons/squiggly_arrow.svg?react';
|
||||
import { Column } from 'mastodon/components/column';
|
||||
import { ColumnHeader } from 'mastodon/components/column_header';
|
||||
import { DisplayNameSimple } from 'mastodon/components/display_name/simple';
|
||||
import { Icon } from 'mastodon/components/icon';
|
||||
import {
|
||||
ItemList,
|
||||
Scrollable,
|
||||
} from 'mastodon/components/scrollable_list/components';
|
||||
import { useAccount } from 'mastodon/hooks/useAccount';
|
||||
import { useAccountId, useCurrentAccountId } from 'mastodon/hooks/useAccountId';
|
||||
import {
|
||||
fetchAccountCollections,
|
||||
selectAccountCollections,
|
||||
} from 'mastodon/reducers/slices/collections';
|
||||
import { useAppSelector, useAppDispatch } from 'mastodon/store';
|
||||
|
||||
import { CollectionListItem } from './detail/collection_list_item';
|
||||
import { CollectionListItem } from './components/collection_list_item';
|
||||
import { messages as editorMessages } from './editor';
|
||||
|
||||
const messages = defineMessages({
|
||||
heading: { id: 'column.collections', defaultMessage: 'My collections' },
|
||||
headingMe: { id: 'column.my_collections', defaultMessage: 'My collections' },
|
||||
headingOther: {
|
||||
id: 'column.other_collections',
|
||||
defaultMessage: 'Collections by {name}',
|
||||
},
|
||||
});
|
||||
|
||||
export const Collections: React.FC<{
|
||||
@ -33,17 +40,22 @@ export const Collections: React.FC<{
|
||||
}> = ({ multiColumn }) => {
|
||||
const dispatch = useAppDispatch();
|
||||
const intl = useIntl();
|
||||
const me = useAppSelector((state) => state.meta.get('me') as string);
|
||||
const me = useCurrentAccountId();
|
||||
const accountId = useAccountId();
|
||||
const account = useAccount(accountId);
|
||||
|
||||
const { collections, status } = useAppSelector((state) =>
|
||||
selectAccountCollections(state, me),
|
||||
selectAccountCollections(state, accountId),
|
||||
);
|
||||
|
||||
useEffect(() => {
|
||||
void dispatch(fetchAccountCollections({ accountId: me }));
|
||||
}, [dispatch, me]);
|
||||
if (accountId) {
|
||||
void dispatch(fetchAccountCollections({ accountId }));
|
||||
}
|
||||
}, [dispatch, accountId]);
|
||||
|
||||
const emptyMessage =
|
||||
status === 'error' ? (
|
||||
status === 'error' || !accountId ? (
|
||||
<FormattedMessage
|
||||
id='collections.error_loading_collections'
|
||||
defaultMessage='There was an error when trying to load your collections.'
|
||||
@ -67,25 +79,36 @@ export const Collections: React.FC<{
|
||||
</>
|
||||
);
|
||||
|
||||
const isOwnCollection = accountId === me;
|
||||
const titleMessage = isOwnCollection
|
||||
? messages.headingMe
|
||||
: messages.headingOther;
|
||||
|
||||
const pageTitle = intl.formatMessage(titleMessage, {
|
||||
name: account?.get('display_name'),
|
||||
});
|
||||
const pageTitleHtml = intl.formatMessage(titleMessage, {
|
||||
name: <DisplayNameSimple account={account} />,
|
||||
});
|
||||
|
||||
return (
|
||||
<Column
|
||||
bindToDocument={!multiColumn}
|
||||
label={intl.formatMessage(messages.heading)}
|
||||
>
|
||||
<Column bindToDocument={!multiColumn} label={pageTitle}>
|
||||
<ColumnHeader
|
||||
title={intl.formatMessage(messages.heading)}
|
||||
title={pageTitleHtml}
|
||||
icon='collections'
|
||||
iconComponent={CollectionsFilledIcon}
|
||||
multiColumn={multiColumn}
|
||||
extraButton={
|
||||
<Link
|
||||
to='/collections/new'
|
||||
className='column-header__button'
|
||||
title={intl.formatMessage(editorMessages.create)}
|
||||
aria-label={intl.formatMessage(editorMessages.create)}
|
||||
>
|
||||
<Icon id='plus' icon={AddIcon} />
|
||||
</Link>
|
||||
isOwnCollection && (
|
||||
<Link
|
||||
to='/collections/new'
|
||||
className='column-header__button'
|
||||
title={intl.formatMessage(editorMessages.create)}
|
||||
aria-label={intl.formatMessage(editorMessages.create)}
|
||||
>
|
||||
<Icon id='plus' icon={AddIcon} />
|
||||
</Link>
|
||||
)
|
||||
}
|
||||
/>
|
||||
|
||||
@ -105,7 +128,7 @@ export const Collections: React.FC<{
|
||||
</Scrollable>
|
||||
|
||||
<Helmet>
|
||||
<title>{intl.formatMessage(messages.heading)}</title>
|
||||
<title>{pageTitle}</title>
|
||||
<meta name='robots' content='noindex' />
|
||||
</Helmet>
|
||||
</Column>
|
||||
|
||||
@ -53,12 +53,20 @@ export const AccountList: FC<AccountListProps> = ({
|
||||
}
|
||||
const children =
|
||||
list?.items.map((followerId) => (
|
||||
<AccountListItem key={followerId} accountId={followerId} />
|
||||
<AccountListItem
|
||||
key={followerId}
|
||||
accountId={followerId}
|
||||
withBio={false}
|
||||
/>
|
||||
)) ?? [];
|
||||
|
||||
if (prependAccountId) {
|
||||
children.unshift(
|
||||
<AccountListItem key={prependAccountId} accountId={prependAccountId} />,
|
||||
<AccountListItem
|
||||
key={prependAccountId}
|
||||
accountId={prependAccountId}
|
||||
withBio={false}
|
||||
/>,
|
||||
);
|
||||
}
|
||||
return children;
|
||||
|
||||
@ -10,6 +10,7 @@ import type { Map as ImmutableMap } from 'immutable';
|
||||
import { animated, useSpring } from '@react-spring/web';
|
||||
import { useDrag } from '@use-gesture/react';
|
||||
|
||||
import { useAccount } from '@/mastodon/hooks/useAccount';
|
||||
import AddIcon from '@/material-icons/400-24px/add.svg?react';
|
||||
import AlternateEmailIcon from '@/material-icons/400-24px/alternate_email.svg?react';
|
||||
import BookmarksActiveIcon from '@/material-icons/400-24px/bookmarks-fill.svg?react';
|
||||
@ -212,6 +213,7 @@ export const NavigationPanel: React.FC<{ multiColumn?: boolean }> = ({
|
||||
const { signedIn, permissions, disabledAccountId } = useIdentity();
|
||||
const location = useLocation();
|
||||
const showSearch = useBreakpoint('full') && !multiColumn;
|
||||
const account = useAccount(me);
|
||||
|
||||
let banner: React.ReactNode;
|
||||
|
||||
@ -335,7 +337,7 @@ export const NavigationPanel: React.FC<{ multiColumn?: boolean }> = ({
|
||||
{areCollectionsEnabled() && (
|
||||
<ColumnLink
|
||||
transparent
|
||||
to='/collections'
|
||||
to={`/@${account?.acct}/collections`}
|
||||
icon='collections'
|
||||
iconComponent={CollectionsIcon}
|
||||
activeIconComponent={CollectionsActiveIcon}
|
||||
|
||||
@ -0,0 +1,69 @@
|
||||
import { FormattedMessage } from 'react-intl';
|
||||
|
||||
import classNames from 'classnames';
|
||||
|
||||
import { DisplayNameSimple } from '@/mastodon/components/display_name/simple';
|
||||
import { Icon } from '@/mastodon/components/icon';
|
||||
import { useAccount } from '@/mastodon/hooks/useAccount';
|
||||
import CollectionsFilledIcon from '@/material-icons/400-24px/category-fill.svg?react';
|
||||
import type {
|
||||
NotificationGroupAddedToCollection,
|
||||
NotificationGroupCollectionUpdate,
|
||||
} from 'mastodon/models/notification_group';
|
||||
|
||||
import { CollectionPreviewCard } from '../../collections/components/collection_preview_card';
|
||||
|
||||
export const NotificationCollection: React.FC<{
|
||||
notification:
|
||||
| NotificationGroupAddedToCollection
|
||||
| NotificationGroupCollectionUpdate;
|
||||
unread: boolean;
|
||||
}> = ({ notification, unread }) => {
|
||||
const { collection, type } = notification;
|
||||
const collectionCreatorAccount = useAccount(collection.account_id);
|
||||
|
||||
return (
|
||||
<div
|
||||
className={classNames(
|
||||
'notification-group',
|
||||
`notification-group--${type}`,
|
||||
{ 'notification-group--unread': unread },
|
||||
)}
|
||||
>
|
||||
<div className='notification-group__icon'>
|
||||
<Icon id='collection' icon={CollectionsFilledIcon} />
|
||||
</div>
|
||||
|
||||
<div className='notification-group__main'>
|
||||
<div className='notification-group__main__header'>
|
||||
<div className='notification-group__main__header__label'>
|
||||
{type === 'added_to_collection' && (
|
||||
<FormattedMessage
|
||||
id='notification.added_to_collection'
|
||||
defaultMessage='{name} added you to a collection'
|
||||
values={{
|
||||
name: (
|
||||
<DisplayNameSimple account={collectionCreatorAccount} />
|
||||
),
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
{type === 'collection_update' && (
|
||||
<FormattedMessage
|
||||
id='notification.collection_update'
|
||||
defaultMessage='{name} edited a collection you’re in'
|
||||
values={{
|
||||
name: (
|
||||
<DisplayNameSimple account={collectionCreatorAccount} />
|
||||
),
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<CollectionPreviewCard collection={collection} />
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
@ -9,6 +9,7 @@ import { useAppSelector, useAppDispatch } from 'mastodon/store';
|
||||
import { NotificationAdminReport } from './notification_admin_report';
|
||||
import { NotificationAdminSignUp } from './notification_admin_sign_up';
|
||||
import { NotificationAnnualReport } from './notification_annual_report';
|
||||
import { NotificationCollection } from './notification_collection';
|
||||
import { NotificationFavourite } from './notification_favourite';
|
||||
import { NotificationFollow } from './notification_follow';
|
||||
import { NotificationFollowRequest } from './notification_follow_request';
|
||||
@ -156,6 +157,15 @@ export const NotificationGroup: React.FC<{
|
||||
/>
|
||||
);
|
||||
break;
|
||||
case 'added_to_collection':
|
||||
case 'collection_update':
|
||||
content = (
|
||||
<NotificationCollection
|
||||
unread={unread}
|
||||
notification={notificationGroup}
|
||||
/>
|
||||
);
|
||||
break;
|
||||
default:
|
||||
return null;
|
||||
}
|
||||
|
||||
@ -1,15 +0,0 @@
|
||||
import PropTypes from 'prop-types';
|
||||
|
||||
const ColumnSubheading = ({ text }) => {
|
||||
return (
|
||||
<div className='column-subheading'>
|
||||
{text}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
ColumnSubheading.propTypes = {
|
||||
text: PropTypes.string.isRequired,
|
||||
};
|
||||
|
||||
export default ColumnSubheading;
|
||||
@ -4,6 +4,8 @@ import { defineMessages, useIntl } from 'react-intl';
|
||||
|
||||
import { useHistory } from 'react-router';
|
||||
|
||||
import { useAccount } from '@/mastodon/hooks/useAccount';
|
||||
import { me } from '@/mastodon/initial_state';
|
||||
import { deleteCollection } from 'mastodon/reducers/slices/collections';
|
||||
import { useAppDispatch } from 'mastodon/store';
|
||||
|
||||
@ -34,11 +36,12 @@ export const ConfirmDeleteCollectionModal: React.FC<
|
||||
const intl = useIntl();
|
||||
const dispatch = useAppDispatch();
|
||||
const history = useHistory();
|
||||
const { acct: currentUserName } = useAccount(me) ?? {};
|
||||
|
||||
const onConfirm = useCallback(() => {
|
||||
void dispatch(deleteCollection({ collectionId: id }));
|
||||
history.push('/collections');
|
||||
}, [dispatch, history, id]);
|
||||
history.push(`/@${currentUserName}/collections`);
|
||||
}, [dispatch, history, id, currentUserName]);
|
||||
|
||||
return (
|
||||
<ConfirmationModal
|
||||
|
||||
@ -0,0 +1,73 @@
|
||||
import { useCallback } from 'react';
|
||||
|
||||
import { defineMessages, useIntl } from 'react-intl';
|
||||
|
||||
import { useHistory } from 'react-router';
|
||||
|
||||
import { useAccount } from '@/mastodon/hooks/useAccount';
|
||||
import { useCurrentAccountId } from '@/mastodon/hooks/useAccountId';
|
||||
import { domain } from '@/mastodon/initial_state';
|
||||
import { patchProfile } from '@/mastodon/reducers/slices/profile_edit';
|
||||
import { useAppDispatch } from 'mastodon/store';
|
||||
|
||||
import type { BaseConfirmationModalProps } from './confirmation_modal';
|
||||
import { ConfirmationModal } from './confirmation_modal';
|
||||
|
||||
const messages = defineMessages({
|
||||
title: {
|
||||
id: 'confirmations.hide_featured_tab.title',
|
||||
defaultMessage: 'Hide "Featured" tab?',
|
||||
},
|
||||
intro: {
|
||||
id: 'confirmations.hide_featured_tab.intro',
|
||||
defaultMessage:
|
||||
'You can change this at any time under <i>Edit profile > Profile tab settings</i>.',
|
||||
},
|
||||
message: {
|
||||
id: 'confirmations.hide_featured_tab.message',
|
||||
defaultMessage:
|
||||
'This will hide the tab for users on {serverName} and other servers running the latest version of Mastodon. Other displays may vary.',
|
||||
},
|
||||
confirm: {
|
||||
id: 'confirmations.hide_featured_tab.confirm',
|
||||
defaultMessage: 'Hide tab',
|
||||
},
|
||||
});
|
||||
|
||||
export const ConfirmHideFeaturedTabModal: React.FC<
|
||||
BaseConfirmationModalProps
|
||||
> = ({ onClose }) => {
|
||||
const intl = useIntl();
|
||||
const dispatch = useAppDispatch();
|
||||
const history = useHistory();
|
||||
const currentAccountId = useCurrentAccountId();
|
||||
const { acct: currentUserName } = useAccount(currentAccountId) ?? {};
|
||||
|
||||
const onConfirm = useCallback(() => {
|
||||
void dispatch(patchProfile({ show_featured: false }));
|
||||
history.push(`/@${currentUserName}`);
|
||||
}, [currentUserName, dispatch, history]);
|
||||
|
||||
return (
|
||||
<ConfirmationModal
|
||||
title={intl.formatMessage(messages.title)}
|
||||
extraContent={
|
||||
<div className='prose'>
|
||||
<p>
|
||||
{intl.formatMessage(messages.intro, {
|
||||
i: (words) => <i>{words}</i>,
|
||||
})}
|
||||
</p>
|
||||
<p>
|
||||
{intl.formatMessage(messages.message, {
|
||||
serverName: domain,
|
||||
})}
|
||||
</p>
|
||||
</div>
|
||||
}
|
||||
confirm={intl.formatMessage(messages.confirm)}
|
||||
onConfirm={onConfirm}
|
||||
onClose={onClose}
|
||||
/>
|
||||
);
|
||||
};
|
||||
@ -79,7 +79,7 @@ export const MODAL_COMPONENTS = {
|
||||
'DOMAIN_BLOCK': DomainBlockModal,
|
||||
'REPORT': ReportModal,
|
||||
'REPORT_COLLECTION': ReportCollectionModal,
|
||||
'SHARE_COLLECTION': () => import('@/mastodon/features/collections/detail/share_modal').then(module => ({ default: module.CollectionShareModal })),
|
||||
'SHARE_COLLECTION': () => import('@/mastodon/features/collections/components/share_modal').then(module => ({ default: module.CollectionShareModal })),
|
||||
'REVOKE_COLLECTION_INCLUSION': () => import('@/mastodon/features/collections/detail/revoke_collection_inclusion_modal').then(module => ({ default: module.RevokeCollectionInclusionModal })),
|
||||
'ACTIONS': () => Promise.resolve({ default: ActionsModal }),
|
||||
'EMBED': EmbedModal,
|
||||
@ -105,6 +105,7 @@ export const MODAL_COMPONENTS = {
|
||||
'ACCOUNT_EDIT_IMAGE_ALT': accountEditModal('ImageAltModal'),
|
||||
'ACCOUNT_EDIT_IMAGE_DELETE': accountEditModal('ImageDeleteModal'),
|
||||
'ACCOUNT_EDIT_IMAGE_UPLOAD': accountEditModal('ImageUploadModal'),
|
||||
'ACCOUNT_HIDE_FEATURED_TAB': () => import('@/mastodon/features/ui/components/confirmation_modals/hide_featured_tab').then(module => ({ default: module.ConfirmHideFeaturedTabModal })),
|
||||
};
|
||||
|
||||
/** @arg {keyof import('@/mastodon/features/account_edit/modals')} type */
|
||||
|
||||
@ -232,6 +232,13 @@ class SwitchingColumnsArea extends PureComponent {
|
||||
|
||||
<WrappedRoute path={['/@:acct', '/accounts/:id']} exact component={AccountTimeline} content={children} />
|
||||
<WrappedRoute path={['/@:acct/featured', '/accounts/:id/featured']} component={AccountFeatured} content={children} />
|
||||
{areCollectionsEnabled() &&
|
||||
[
|
||||
<WrappedRoute path={['/@:acct/collections']} component={Collections} content={children} key='collections-list' />,
|
||||
<WrappedRoute path={['/collections/new', '/collections/:id/edit']} component={CollectionsEditor} content={children} key='collections-editor' />,
|
||||
<WrappedRoute path='/collections/:id' component={CollectionDetail} content={children} key='collections-detail' />,
|
||||
]
|
||||
}
|
||||
<WrappedRoute path='/@:acct/tagged/:tagged?' exact component={AccountTimeline} content={children} />
|
||||
<WrappedRoute path={['/@:acct/with_replies', '/accounts/:id/with_replies']} component={AccountTimeline} content={children} componentParams={{ withReplies: true }} />
|
||||
<WrappedRoute path={['/accounts/:id/followers', '/users/:acct/followers', '/@:acct/followers']} component={Followers} content={children} />
|
||||
@ -255,13 +262,6 @@ class SwitchingColumnsArea extends PureComponent {
|
||||
<WrappedRoute path='/followed_tags' component={FollowedTags} content={children} />
|
||||
<WrappedRoute path='/mutes' component={Mutes} content={children} />
|
||||
<WrappedRoute path='/lists' component={Lists} content={children} />
|
||||
{areCollectionsEnabled() &&
|
||||
[
|
||||
<WrappedRoute path={['/collections/new', '/collections/:id/edit']} component={CollectionsEditor} content={children} key='collections-editor' />,
|
||||
<WrappedRoute path='/collections/:id' component={CollectionDetail} content={children} key='collections-detail' />,
|
||||
<WrappedRoute path='/collections' component={Collections} content={children} key='collections-list' />
|
||||
]
|
||||
}
|
||||
<Route component={BundleColumnError} />
|
||||
</WrappedSwitch>
|
||||
</ColumnsArea>
|
||||
|
||||
@ -31,6 +31,7 @@
|
||||
"account.disable_notifications": "توقف عن إشعاري عندما ينشر @{name}",
|
||||
"account.domain_blocking": "نطاق محظور",
|
||||
"account.edit_profile": "تعديل الملف الشخصي",
|
||||
"account.edit_profile_short": "تعديل",
|
||||
"account.enable_notifications": "أشعرني عندما ينشر @{name}",
|
||||
"account.endorse": "أوصِ به على صفحتك الشخصية",
|
||||
"account.familiar_followers_many": "يتبعه {name1}، {name2} و{othersCount, plural, one {شخص آخر تعرفه} other {# أشخاص آخرون تعرفهم}}",
|
||||
@ -97,6 +98,27 @@
|
||||
"account.unmute": "إلغاء الكَتم عن @{name}",
|
||||
"account.unmute_notifications_short": "إلغاء كَتم الإشعارات",
|
||||
"account.unmute_short": "إلغاء الكتم",
|
||||
"account_edit.bio.add_label": "إضافة سيرة ذاتية",
|
||||
"account_edit.bio_modal.add_title": "إضافة سيرة ذاتية",
|
||||
"account_edit.custom_fields.add_label": "إضافة حقل",
|
||||
"account_edit.field_actions.edit": "تعديل الحقل",
|
||||
"account_edit.field_delete_modal.delete_button": "حذف",
|
||||
"account_edit.field_edit_modal.value_label": "قيمة",
|
||||
"account_edit.image_alt_modal.text_label": "نص بديل",
|
||||
"account_edit.image_delete_modal.delete_button": "حذف",
|
||||
"account_edit.image_edit.add_button": "إضافة صورة",
|
||||
"account_edit.image_edit.alt_add_button": "إضافة نص بديل",
|
||||
"account_edit.save": "حفظ",
|
||||
"account_edit.upload_modal.back": "العودة",
|
||||
"account_edit.upload_modal.done": "تمّ",
|
||||
"account_edit.upload_modal.next": "التالي",
|
||||
"account_edit.upload_modal.step_upload.dragging": "إسقاط للتحميل",
|
||||
"account_edit.upload_modal.step_upload.header": "اختيار صورة",
|
||||
"account_edit.upload_modal.title_add.avatar": "إضافة صورة الملف الشخصي",
|
||||
"account_edit.upload_modal.title_add.header": "إضافة صورة الغلاف",
|
||||
"account_edit.upload_modal.title_replace.avatar": "استبدال صورة الملف الشخصي",
|
||||
"account_edit.upload_modal.title_replace.header": "استبدال صورة الغلاف",
|
||||
"account_edit_tags.add_tag": "إضافة #{tagName}",
|
||||
"account_note.placeholder": "اضغط لإضافة مُلاحظة",
|
||||
"admin.dashboard.daily_retention": "معدّل بقاء المستخدمين بعد إنشاء الحسابات، بالأيام",
|
||||
"admin.dashboard.monthly_retention": "معدّل بقاء المستخدمين بعد إنشاء الحسابات، بالشهور",
|
||||
@ -120,6 +142,9 @@
|
||||
"alt_text_modal.describe_for_people_with_visual_impairments": "قم بوصفها للأشخاص ذوي الإعاقة البصرية…",
|
||||
"alt_text_modal.done": "تمّ",
|
||||
"announcement.announcement": "إعلان",
|
||||
"annual_report.announcement.action_dismiss": "لا شكراً",
|
||||
"annual_report.nav_item.badge": "جديد",
|
||||
"annual_report.shared_page.donate": "تبرع",
|
||||
"annual_report.summary.archetype.replier.name": "الفراشة",
|
||||
"annual_report.summary.close": "اغلق",
|
||||
"annual_report.summary.copy_link": "نسخ الرابط",
|
||||
@ -162,11 +187,15 @@
|
||||
"closed_registrations_modal.preamble": "ماستدون لامركزي، لذلك بغض النظر عن مكان إنشاء حسابك، سيكون بإمكانك المتابعة والتفاعل مع أي شخص على هذا الخادم. يمكنك حتى أن تستضيفه ذاتياً!",
|
||||
"closed_registrations_modal.title": "إنشاء حساب على ماستدون",
|
||||
"collections.collection_description": "الوصف",
|
||||
"collections.collection_language": "اللغة",
|
||||
"collections.collection_name": "الاسم",
|
||||
"collections.collection_topic": "الموضوع",
|
||||
"collections.content_warning": "تحذير عن المحتوى",
|
||||
"collections.continue": "مواصلة",
|
||||
"collections.create.steps": "الخطوة {step}/{total}",
|
||||
"collections.detail.accounts_heading": "الحسابات",
|
||||
"collections.detail.sensitive_content": "محتوى حساس",
|
||||
"collections.edit_details": "تعديل التفاصيل",
|
||||
"collections.manage_accounts": "إدارة الحسابات",
|
||||
"column.about": "عن",
|
||||
"column.blocks": "المُستَخدِمون المَحظورون",
|
||||
@ -195,6 +224,9 @@
|
||||
"column_header.show_settings": "إظهار الإعدادات",
|
||||
"column_header.unpin": "إلغاء التَّثبيت",
|
||||
"column_search.cancel": "إلغاء",
|
||||
"combobox.close_results": "إغلاق النتائج",
|
||||
"combobox.loading": "جاري التحميل",
|
||||
"combobox.open_results": "فتح النتائج",
|
||||
"community.column_settings.local_only": "المحلي فقط",
|
||||
"community.column_settings.media_only": "الوسائط فقط",
|
||||
"community.column_settings.remote_only": "عن بُعد فقط",
|
||||
@ -268,11 +300,13 @@
|
||||
"content_warning.hide": "إخفاء المنشور",
|
||||
"content_warning.show": "إظهار على أي حال",
|
||||
"content_warning.show_more": "إظهار المزيد",
|
||||
"content_warning.show_short": "إظهار",
|
||||
"conversation.delete": "احذف المحادثة",
|
||||
"conversation.mark_as_read": "اعتبرها كمقروءة",
|
||||
"conversation.open": "اعرض المحادثة",
|
||||
"conversation.with": "مع {names}",
|
||||
"copy_icon_button.copied": "نُسِخ إلى الحافظة",
|
||||
"copy_icon_button.copy_this_text": "نسخ الرابط إلى الحافظة",
|
||||
"copypaste.copied": "تم نسخه",
|
||||
"copypaste.copy_to_clipboard": "نسخ إلى الحافظة",
|
||||
"directory.federated": "مِن الفديفرس المعروف",
|
||||
@ -307,6 +341,8 @@
|
||||
"domain_pill.your_server": "موطِنك الرقمي، حيث توجد فيه كافة منشوراتك. ألا يعجبك المكان؟ يمكنك الانتقال بين الخوادم في أي وقت واصطحاب متابعيك أيضاً.",
|
||||
"domain_pill.your_username": "معرفك الفريد على هذا الخادم. من الممكن العثور على مستخدمين بنفس إسم المستخدم على خوادم مختلفة.",
|
||||
"dropdown.empty": "حدد خيارا",
|
||||
"email_subscriptions.email": "البريد الإلكتروني",
|
||||
"email_subscriptions.form.action": "اشترك",
|
||||
"embed.instructions": "يمكنكم إدماج هذا المنشور على موقعكم الإلكتروني عن طريق نسخ الشفرة أدناه.",
|
||||
"embed.preview": "إليك ما سيبدو عليه:",
|
||||
"emoji_button.activity": "الأنشطة",
|
||||
@ -324,7 +360,6 @@
|
||||
"emoji_button.search_results": "نتائج البحث",
|
||||
"emoji_button.symbols": "رموز",
|
||||
"emoji_button.travel": "الأماكن والسفر",
|
||||
"empty_column.account_featured.other": "{acct} لم يعرض أي شيء حتى الآن. هل تعلم أنه يمكنك عرض الهاشتاقات التي تستخدمها، وحتى حسابات أصدقاءك على ملفك الشخصي؟",
|
||||
"empty_column.account_hides_collections": "اختار هذا المستخدم عدم إتاحة هذه المعلومات للعامة",
|
||||
"empty_column.account_suspended": "حساب معلق",
|
||||
"empty_column.account_timeline": "لا توجد منشورات هنا!",
|
||||
@ -346,6 +381,7 @@
|
||||
"empty_column.notification_requests": "لا يوجد شيء هنا. عندما تتلقى إشعارات جديدة، سوف تظهر هنا وفقًا لإعداداتك.",
|
||||
"empty_column.notifications": "لم تتلق أي إشعار بعدُ. تفاعل مع المستخدمين الآخرين لإنشاء محادثة.",
|
||||
"empty_column.public": "لا يوجد أي شيء هنا! قم بنشر شيء ما للعامة، أو اتبع المستخدمين الآخرين المتواجدين على الخوادم الأخرى لملء خيط المحادثات",
|
||||
"empty_state.no_results": "لا توجد نتائج",
|
||||
"error.unexpected_crash.explanation": "نظرا لوجود خطأ في التعليمات البرمجية أو مشكلة توافق مع المتصفّح، تعذر عرض هذه الصفحة بشكل صحيح.",
|
||||
"error.unexpected_crash.explanation_addons": "لا يمكن عرض هذه الصفحة بشكل صحيح. من المحتمل أن يكون هذا الخطأ بسبب إضافة متصفح أو أدوات ترجمة تلقائية.",
|
||||
"error.unexpected_crash.next_steps": "حاول إعادة إنعاش الصفحة. إن لم تُحلّ المشكلة، يمكنك دائمًا استخدام ماستدون عبر متصفّح آخر أو تطبيق أصلي.",
|
||||
@ -358,6 +394,8 @@
|
||||
"explore.trending_statuses": "المنشورات",
|
||||
"explore.trending_tags": "وُسُوم",
|
||||
"featured_carousel.header": "{count, plural, zero {}one {منشور معروض} two {منشور معروضَين} few {منشورات معروضة} many {منشورات معروضة} other {منشورات معروضة}}",
|
||||
"featured_tags.suggestions.add": "إضافة",
|
||||
"featured_tags.suggestions.dismiss": "لا شكراً",
|
||||
"filter_modal.added.context_mismatch_explanation": "فئة عامل التصفية هذه لا تنطبق على السياق الذي وصلت فيه إلى هذه المشاركة. إذا كنت ترغب في تصفية المنشور في هذا السياق أيضا، فسيتعين عليك تعديل عامل التصفية.",
|
||||
"filter_modal.added.context_mismatch_title": "عدم تطابق السياق!",
|
||||
"filter_modal.added.expired_explanation": "انتهت صلاحية فئة عامل التصفية هذه، سوف تحتاج إلى تغيير تاريخ انتهاء الصلاحية لتطبيقها.",
|
||||
@ -867,6 +905,8 @@
|
||||
"status.cancel_reblog_private": "إلغاء إعادة النشر",
|
||||
"status.cannot_quote": "غير مصرح لك باقتباس هذا المنشور",
|
||||
"status.cannot_reblog": "لا يمكن إعادة نشر هذا المنشور",
|
||||
"status.context.retry": "حاول مجددًا",
|
||||
"status.context.show": "إظهار",
|
||||
"status.continued_thread": "تكملة للخيط",
|
||||
"status.copy": "انسخ رابط الرسالة",
|
||||
"status.delete": "احذف",
|
||||
|
||||
@ -45,6 +45,7 @@
|
||||
"account.featured": "Выбранае",
|
||||
"account.featured.accounts": "Профілі",
|
||||
"account.featured.collections": "Калекцыі",
|
||||
"account.featured.new_collection": "Новая калекцыя",
|
||||
"account.field_overflow": "Паказаць усё змесціва",
|
||||
"account.filters.all": "Уся актыўнасць",
|
||||
"account.filters.boosts_toggle": "Паказваць пашырэнні",
|
||||
@ -416,7 +417,6 @@
|
||||
"column.about": "Пра нас",
|
||||
"column.blocks": "Заблакіраваныя карыстальнікі",
|
||||
"column.bookmarks": "Закладкі",
|
||||
"column.collections": "Мае калекцыі",
|
||||
"column.community": "Лакальная стужка",
|
||||
"column.create_list": "Стварыць спіс",
|
||||
"column.direct": "Прыватныя згадванні",
|
||||
@ -432,7 +432,9 @@
|
||||
"column.list_members": "Кіраванне ўдзельнікамі спіса",
|
||||
"column.lists": "Спісы",
|
||||
"column.mutes": "Ігнараваныя карыстальнікі",
|
||||
"column.my_collections": "Мае калекцыі",
|
||||
"column.notifications": "Апавяшчэнні",
|
||||
"column.other_collections": "Калекцыі {name}",
|
||||
"column.pins": "Замацаваныя допісы",
|
||||
"column.public": "Інтэграваная стужка",
|
||||
"column_back_button.label": "Назад",
|
||||
@ -502,6 +504,10 @@
|
||||
"confirmations.follow_to_list.confirm": "Падпісацца і дадаць у спіс",
|
||||
"confirmations.follow_to_list.message": "Вам трэба падпісацца на {name}, каб дадаць яго/яе ў спіс.",
|
||||
"confirmations.follow_to_list.title": "Падпісацца на карыстальніка?",
|
||||
"confirmations.hide_featured_tab.confirm": "Схаваць укладку",
|
||||
"confirmations.hide_featured_tab.intro": "Вы можаце змяніць гэтую наладу ў любы момант у <i>Рэдагаваць профіль > Налады ўкладкі профілю</i>.",
|
||||
"confirmations.hide_featured_tab.message": "Гэтае дзеянне схавае ўкладку ад карыстальнікаў {serverName} і іншых сервераў з апошняй версіяй Mastodon. Яе адлюстраванне на іншых серверах можа адрознівацца.",
|
||||
"confirmations.hide_featured_tab.title": "Схаваць укладку \"Выбранае\"?",
|
||||
"confirmations.logout.confirm": "Выйсці",
|
||||
"confirmations.logout.message": "Вы ўпэўненыя, што хочаце выйсці?",
|
||||
"confirmations.logout.title": "Выйсці?",
|
||||
@ -606,14 +612,13 @@
|
||||
"emoji_button.search_results": "Вынікі пошуку",
|
||||
"emoji_button.symbols": "Сімвалы",
|
||||
"emoji_button.travel": "Падарожжы і месцы",
|
||||
"empty_column.account_featured.other": "{acct} яшчэ нічога не адабраў для паказу. Ці ведалі Вы, што ў сваім профілі Вы можаце паказаць свае хэштэгі, якімі найбольш карыстаецеся, і нават профілі сваіх сяброў?",
|
||||
"empty_column.account_featured_other.no_collections_desc": "{acct} пакуль не стварыў(-ла) аніводнай калекцыі.",
|
||||
"empty_column.account_featured_other.title": "Тут нічога няма",
|
||||
"empty_column.account_featured_self.no_collections": "Пакуль няма калекцый",
|
||||
"empty_column.account_featured.other": "{acct} пакуль нічога не адабраў(-ла) для паказу.",
|
||||
"empty_column.account_featured_self.no_collections_button": "Стварыць калекцыю",
|
||||
"empty_column.account_featured_self.no_collections_hide_tab": "Або схаваць гэтую ўкладку",
|
||||
"empty_column.account_featured_self.pre_collections": "Рыхтуйцеся да Калекцый",
|
||||
"empty_column.account_featured_self.pre_collections_desc": "Калекцыі (новая функцыя Mastodon 4.6) дазволяць Вам ствараць свае ўласныя спісы ўліковых запісаў, каб раіць іх іншым.",
|
||||
"empty_column.account_featured_unknown.no_collections_desc": "Гэты ўліковы запіс пакуль не стварыў аніводнай калекцыі.",
|
||||
"empty_column.account_featured_self.showcase_accounts": "Паказаць Вашыя ўлюбёныя ўліковыя запісы",
|
||||
"empty_column.account_featured_self.showcase_accounts_desc": "Калекцыі — спісы адабраных уліковых запісаў, якія дапамагаюць іншым знаходзіць нешта новае ў федэральным сусвеце.",
|
||||
"empty_column.account_featured_unknown.other": "Гэты ўліковы запіс пакуль нічога не адабраў для паказу.",
|
||||
"empty_column.account_hides_collections": "Гэты карыстальнік вырашыў схаваць гэтую інфармацыю",
|
||||
"empty_column.account_suspended": "Уліковы запіс прыпынены",
|
||||
|
||||
@ -219,7 +219,6 @@
|
||||
"column.about": "Quant a",
|
||||
"column.blocks": "Usuaris blocats",
|
||||
"column.bookmarks": "Marcadors",
|
||||
"column.collections": "Les meves coŀleccions",
|
||||
"column.community": "Línia de temps local",
|
||||
"column.create_list": "Crea una llista",
|
||||
"column.direct": "Mencions privades",
|
||||
@ -388,7 +387,6 @@
|
||||
"emoji_button.search_results": "Resultats de la cerca",
|
||||
"emoji_button.symbols": "Símbols",
|
||||
"emoji_button.travel": "Viatges i llocs",
|
||||
"empty_column.account_featured.other": "{acct} encara no ha destacat res. Sabeu que podeu destacar les etiquetes que més feu servir i fins i tot els comptes dels vostres amics al vostre perfil?",
|
||||
"empty_column.account_hides_collections": "Aquest usuari ha decidit no mostrar aquesta informació",
|
||||
"empty_column.account_suspended": "Compte suspès",
|
||||
"empty_column.account_timeline": "No hi ha tuts aquí!",
|
||||
|
||||
@ -266,7 +266,6 @@
|
||||
"column.about": "O aplikaci",
|
||||
"column.blocks": "Blokovaní uživatelé",
|
||||
"column.bookmarks": "Záložky",
|
||||
"column.collections": "Mé sbírky",
|
||||
"column.community": "Místní časová osa",
|
||||
"column.create_list": "Vytvořit seznam",
|
||||
"column.direct": "Soukromé zmínky",
|
||||
@ -439,7 +438,6 @@
|
||||
"emoji_button.search_results": "Výsledky hledání",
|
||||
"emoji_button.symbols": "Symboly",
|
||||
"emoji_button.travel": "Cestování a místa",
|
||||
"empty_column.account_featured.other": "{acct} zatím nic nezvýraznili. Věděli jste, že na svém profilu můžete zvýraznit hashtagy, které používáte nejvíce, a dokonce účty vašich přátel?",
|
||||
"empty_column.account_hides_collections": "Tento uživatel se rozhodl tuto informaci nezveřejňovat",
|
||||
"empty_column.account_suspended": "Účet je pozastaven",
|
||||
"empty_column.account_timeline": "Nejsou tu žádné příspěvky!",
|
||||
|
||||
@ -383,7 +383,6 @@
|
||||
"column.about": "Ynghylch",
|
||||
"column.blocks": "Defnyddwyr wedi'u rhwystro",
|
||||
"column.bookmarks": "Llyfrnodau",
|
||||
"column.collections": "Fy nghasgliadau",
|
||||
"column.community": "Ffrwd lleol",
|
||||
"column.create_list": "Creu rhestr",
|
||||
"column.direct": "Crybwylliadau preifat",
|
||||
@ -564,7 +563,6 @@
|
||||
"emoji_button.search_results": "Canlyniadau chwilio",
|
||||
"emoji_button.symbols": "Symbolau",
|
||||
"emoji_button.travel": "Teithio a Llefydd",
|
||||
"empty_column.account_featured.other": "Dyw {acct} heb gynnwys unrhyw beth eto. Oeddech chi'n gwybod y gallwch chi gynnwys yr hashnodau rydych chi'n eu defnyddio fwyaf, a hyd yn oed cyfrifon eich ffrindiau ar eich proffil?",
|
||||
"empty_column.account_hides_collections": "Mae'r defnyddiwr wedi dewis i beidio rhannu'r wybodaeth yma",
|
||||
"empty_column.account_suspended": "Cyfrif wedi'i atal",
|
||||
"empty_column.account_timeline": "Dim postiadau yma!",
|
||||
|
||||
@ -45,6 +45,7 @@
|
||||
"account.featured": "Fremhævet",
|
||||
"account.featured.accounts": "Profiler",
|
||||
"account.featured.collections": "Samlinger",
|
||||
"account.featured.new_collection": "Ny samling",
|
||||
"account.field_overflow": "Vis fuldt indhold",
|
||||
"account.filters.all": "Al aktivitet",
|
||||
"account.filters.boosts_toggle": "Vis fremhævelser",
|
||||
@ -416,7 +417,6 @@
|
||||
"column.about": "Om",
|
||||
"column.blocks": "Blokerede brugere",
|
||||
"column.bookmarks": "Bogmærker",
|
||||
"column.collections": "Mine samlinger",
|
||||
"column.community": "Lokal tidslinje",
|
||||
"column.create_list": "Opret liste",
|
||||
"column.direct": "Private omtaler",
|
||||
@ -432,7 +432,9 @@
|
||||
"column.list_members": "Håndtér listemedlemmer",
|
||||
"column.lists": "Lister",
|
||||
"column.mutes": "Skjulte brugere",
|
||||
"column.my_collections": "Mine samlinger",
|
||||
"column.notifications": "Notifikationer",
|
||||
"column.other_collections": "Samlinger af {name}",
|
||||
"column.pins": "Fastgjorte indlæg",
|
||||
"column.public": "Fælles tidslinje",
|
||||
"column_back_button.label": "Tilbage",
|
||||
@ -502,6 +504,10 @@
|
||||
"confirmations.follow_to_list.confirm": "Følg og føj til liste",
|
||||
"confirmations.follow_to_list.message": "Du skal følge {name} for at føje vedkommende til en liste.",
|
||||
"confirmations.follow_to_list.title": "Følg bruger?",
|
||||
"confirmations.hide_featured_tab.confirm": "Skjul fane",
|
||||
"confirmations.hide_featured_tab.intro": "Du kan til enhver tid ændre dette under <i>Rediger profil > Indstillinger for profil-fane</i>.",
|
||||
"confirmations.hide_featured_tab.message": "Dette vil skjule fanen for brugere på {serverName} og andre servere, der kører den nyeste version af Mastodon. Andre visninger kan variere.",
|
||||
"confirmations.hide_featured_tab.title": "Skjul fanen \"Fremhævet\"?",
|
||||
"confirmations.logout.confirm": "Log ud",
|
||||
"confirmations.logout.message": "Er du sikker på, at du vil logge ud?",
|
||||
"confirmations.logout.title": "Log ud?",
|
||||
@ -606,14 +612,13 @@
|
||||
"emoji_button.search_results": "Søgeresultater",
|
||||
"emoji_button.symbols": "Symboler",
|
||||
"emoji_button.travel": "Rejser og steder",
|
||||
"empty_column.account_featured.other": "{acct} har ikke fremhævet noget endnu. Vidste du, at du kan fremhæve dine mest brugte hashtags og endda din vens konti på din profil?",
|
||||
"empty_column.account_featured_other.no_collections_desc": "{acct} har endnu ikke oprettet nogen samlinger.",
|
||||
"empty_column.account_featured_other.title": "Intet at se her",
|
||||
"empty_column.account_featured_self.no_collections": "Ingen samlinger endnu",
|
||||
"empty_column.account_featured.other": "{acct} har ikke fremhævet noget endnu.",
|
||||
"empty_column.account_featured_self.no_collections_button": "Opret en samling",
|
||||
"empty_column.account_featured_self.no_collections_hide_tab": "Skjul denne fane i stedet",
|
||||
"empty_column.account_featured_self.pre_collections": "Hold udkig efter Samlinger",
|
||||
"empty_column.account_featured_self.pre_collections_desc": "Med samlinger (kommer i Mastodon 4.6) kan du oprette dine egne kuraterede lister over konti, som du kan anbefale til andre.",
|
||||
"empty_column.account_featured_unknown.no_collections_desc": "Denne konto har endnu ikke oprettet nogen samlinger.",
|
||||
"empty_column.account_featured_self.showcase_accounts": "Vis dine favoritkonti",
|
||||
"empty_column.account_featured_self.showcase_accounts_desc": "Samlinger er kuraterede lister over konti, der skal hjælpe andre med at opdage mere af Fediverset.",
|
||||
"empty_column.account_featured_unknown.other": "Denne konto har ikke fremhævet noget endnu.",
|
||||
"empty_column.account_hides_collections": "Brugeren har valgt ikke at gøre denne information tilgængelig",
|
||||
"empty_column.account_suspended": "Konto suspenderet",
|
||||
|
||||
@ -45,6 +45,7 @@
|
||||
"account.featured": "Vorgestellt",
|
||||
"account.featured.accounts": "Profile",
|
||||
"account.featured.collections": "Sammlungen",
|
||||
"account.featured.new_collection": "Neue Sammlung",
|
||||
"account.field_overflow": "Vollständigen Inhalt anzeigen",
|
||||
"account.filters.all": "Alle Aktivitäten",
|
||||
"account.filters.boosts_toggle": "Geteilte Beiträge anzeigen",
|
||||
@ -416,7 +417,6 @@
|
||||
"column.about": "Über",
|
||||
"column.blocks": "Blockierte Profile",
|
||||
"column.bookmarks": "Lesezeichen",
|
||||
"column.collections": "Meine Sammlungen",
|
||||
"column.community": "Lokale Timeline",
|
||||
"column.create_list": "Liste erstellen",
|
||||
"column.direct": "Private Erwähnungen",
|
||||
@ -432,7 +432,9 @@
|
||||
"column.list_members": "Listenmitglieder verwalten",
|
||||
"column.lists": "Listen",
|
||||
"column.mutes": "Stummgeschaltete Profile",
|
||||
"column.my_collections": "Meine Sammlungen",
|
||||
"column.notifications": "Benachrichtigungen",
|
||||
"column.other_collections": "Sammlungen von {name}",
|
||||
"column.pins": "Angeheftete Beiträge",
|
||||
"column.public": "Föderierte Timeline",
|
||||
"column_back_button.label": "Zurück",
|
||||
@ -502,6 +504,10 @@
|
||||
"confirmations.follow_to_list.confirm": "Folgen und zur Liste hinzufügen",
|
||||
"confirmations.follow_to_list.message": "Du musst {name} folgen, um das Profil zu einer Liste hinzufügen zu können.",
|
||||
"confirmations.follow_to_list.title": "Profil folgen?",
|
||||
"confirmations.hide_featured_tab.confirm": "Tab ausblenden",
|
||||
"confirmations.hide_featured_tab.intro": "Du kannst die Einstellungen jederzeit unter <i>Profil bearbeiten > Profil-Tab-Einstellungen</i> ändern.",
|
||||
"confirmations.hide_featured_tab.message": "Dieser Tab wird auf {serverName} und anderen Servern mit der neuesten Mastodon-Version nicht angezeigt. Darstellung kann darüber hinaus abweichen.",
|
||||
"confirmations.hide_featured_tab.title": "„Vorgestellt“-Tab ausblenden?",
|
||||
"confirmations.logout.confirm": "Abmelden",
|
||||
"confirmations.logout.message": "Möchtest du dich wirklich ausloggen?",
|
||||
"confirmations.logout.title": "Abmelden?",
|
||||
@ -606,14 +612,13 @@
|
||||
"emoji_button.search_results": "Suchergebnisse",
|
||||
"emoji_button.symbols": "Symbole",
|
||||
"emoji_button.travel": "Reisen & Orte",
|
||||
"empty_column.account_featured.other": "{acct} hat bisher noch nichts vorgestellt. Wusstest du, dass du deine häufig verwendeten Hashtags und sogar Profile von Freund*innen vorstellen kannst?",
|
||||
"empty_column.account_featured_other.no_collections_desc": "{acct} hat noch keine Sammlungen erstellt.",
|
||||
"empty_column.account_featured_other.title": "Hier gibt es nichts zu sehen",
|
||||
"empty_column.account_featured_self.no_collections": "Noch keine Sammlungen",
|
||||
"empty_column.account_featured.other": "{acct} hat noch nichts vorgestellt.",
|
||||
"empty_column.account_featured_self.no_collections_button": "Sammlung erstellen",
|
||||
"empty_column.account_featured_self.no_collections_hide_tab": "Diesen Tab ausblenden",
|
||||
"empty_column.account_featured_self.pre_collections": "Sammlungen sind bald verfügbar",
|
||||
"empty_column.account_featured_self.pre_collections_desc": "Mit Sammlungen (neue Funktion in Mastodon 4.6) kannst du deine eigenen kuratierten Listen erstellen, um ausgewählte Konten zu empfehlen.",
|
||||
"empty_column.account_featured_unknown.no_collections_desc": "Dieser Account hat noch keine Sammlungen erstellt.",
|
||||
"empty_column.account_featured_self.showcase_accounts": "Präsentiere deine liebsten Konten",
|
||||
"empty_column.account_featured_self.showcase_accounts_desc": "Sammlungen sind kuratierte Listen, die anderen helfen, mehr Konten im Fediverse zu entdecken.",
|
||||
"empty_column.account_featured_unknown.other": "Dieser Account hat noch nichts vorgestellt.",
|
||||
"empty_column.account_hides_collections": "Das Profil hat sich entschieden, diese Information nicht zu veröffentlichen",
|
||||
"empty_column.account_suspended": "Konto dauerhaft gesperrt",
|
||||
|
||||
@ -45,6 +45,7 @@
|
||||
"account.featured": "Αναδεδειγμένα",
|
||||
"account.featured.accounts": "Προφίλ",
|
||||
"account.featured.collections": "Συλλογές",
|
||||
"account.featured.new_collection": "Νέα συλλογή",
|
||||
"account.field_overflow": "Εμφάνιση πλήρους περιεχομένου",
|
||||
"account.filters.all": "Όλη η δραστηριότητα",
|
||||
"account.filters.boosts_toggle": "Εμφάνιση ενισχύσεων",
|
||||
@ -217,7 +218,7 @@
|
||||
"account_edit.profile_tab.show_media_replies.description": "Όταν ενεργοποιηθεί, η καρτέλα Πολυμέσα εμφανίζει τόσο τις αναρτήσεις σας όσο και τις απαντήσεις σας σε αναρτήσεις άλλων ατόμων.",
|
||||
"account_edit.profile_tab.show_media_replies.title": "Συμπερίληψη απαντήσεων στην καρτέλα «Πολυμέσα»",
|
||||
"account_edit.profile_tab.subtitle": "Προσαρμόστε τις καρτέλες στο προφίλ σας και τι εμφανίζουν.",
|
||||
"account_edit.profile_tab.title": "Ρυθμίσεις καρτέλας προφίλ",
|
||||
"account_edit.profile_tab.title": "Ρυθμίσεις καρτελών προφίλ",
|
||||
"account_edit.save": "Αποθήκευση",
|
||||
"account_edit.upload_modal.back": "Πίσω",
|
||||
"account_edit.upload_modal.done": "Έγινε",
|
||||
@ -416,7 +417,6 @@
|
||||
"column.about": "Σχετικά με",
|
||||
"column.blocks": "Αποκλεισμένοι χρήστες",
|
||||
"column.bookmarks": "Σελιδοδείκτες",
|
||||
"column.collections": "Οι συλλογές μου",
|
||||
"column.community": "Τοπική ροή",
|
||||
"column.create_list": "Δημιουργία λίστας",
|
||||
"column.direct": "Ιδιωτικές επισημάνσεις",
|
||||
@ -432,7 +432,9 @@
|
||||
"column.list_members": "Διαχείριση μελών λίστας",
|
||||
"column.lists": "Λίστες",
|
||||
"column.mutes": "Αποσιωπημένοι χρήστες",
|
||||
"column.my_collections": "Οι συλλογές μου",
|
||||
"column.notifications": "Ειδοποιήσεις",
|
||||
"column.other_collections": "Συλλογές από {name}",
|
||||
"column.pins": "Καρφιτσωμένες αναρτήσεις",
|
||||
"column.public": "Ομοσπονδιακή ροή",
|
||||
"column_back_button.label": "Πίσω",
|
||||
@ -502,6 +504,10 @@
|
||||
"confirmations.follow_to_list.confirm": "Ακολούθησε και πρόσθεσε στη λίστα",
|
||||
"confirmations.follow_to_list.message": "Πρέπει να ακολουθήσεις τον χρήστη {name} για να τον προσθέσεις σε μια λίστα.",
|
||||
"confirmations.follow_to_list.title": "Ακολούθηση χρήστη;",
|
||||
"confirmations.hide_featured_tab.confirm": "Απόκρυψη καρτέλας",
|
||||
"confirmations.hide_featured_tab.intro": "Μπορείτε να το αλλάξετε ανά πάσα στιγμή κάτω από το <i>Επεξεργασία προφίλ> Ρυθμίσεις καρτελών προφίλ</i>.",
|
||||
"confirmations.hide_featured_tab.message": "Αυτό θα κρύψει την καρτέλα για τους χρήστες στο {serverName} και σε άλλους διακομιστές που εκτελούν την τελευταία έκδοση του Mastodon. Άλλες εμφανίσεις μπορεί να ποικίλλουν.",
|
||||
"confirmations.hide_featured_tab.title": "Απόκρυψη καρτέλας \"Αναδεδειγμένα\";",
|
||||
"confirmations.logout.confirm": "Αποσύνδεση",
|
||||
"confirmations.logout.message": "Σίγουρα θέλεις να αποσυνδεθείς;",
|
||||
"confirmations.logout.title": "Αποσύνδεση;",
|
||||
@ -606,14 +612,13 @@
|
||||
"emoji_button.search_results": "Αποτελέσματα αναζήτησης",
|
||||
"emoji_button.symbols": "Σύμβολα",
|
||||
"emoji_button.travel": "Ταξίδια & Τοποθεσίες",
|
||||
"empty_column.account_featured.other": "Ο/Η {acct} δεν έχει αναδείξει τίποτα ακόμη. Γνώριζες ότι μπορείς να αναδείξεις τις ετικέτες που χρησιμοποιείς περισσότερο, ακόμη και τους λογαριασμούς των φίλων σου στο προφίλ σου;",
|
||||
"empty_column.account_featured_other.no_collections_desc": "Ο χρήστης {acct} δεν έχει δημιουργήσει ακόμη καμία συλλογή.",
|
||||
"empty_column.account_featured_other.title": "Δεν υπάρχει τίποτα να δείτε εδώ",
|
||||
"empty_column.account_featured_self.no_collections": "Καμία συλλογή ακόμη",
|
||||
"empty_column.account_featured.other": "Ο/Η {acct} δεν έχει αναδείξει τίποτα ακόμη.",
|
||||
"empty_column.account_featured_self.no_collections_button": "Δημιουργήστε μια συλλογή",
|
||||
"empty_column.account_featured_self.no_collections_hide_tab": "Απόκρυψη αυτής της καρτέλας αντ' αυτού",
|
||||
"empty_column.account_featured_self.pre_collections": "Μείνετε συντονισμένοι για τις Συλλογές",
|
||||
"empty_column.account_featured_self.pre_collections_desc": "Οι Συλλογές (που έρχονται στο Mastodon 4.6) σας επιτρέπουν να δημιουργήσετε τις δικές σας επιμελημένες λίστες λογαριασμών για να συστήσετε σε άλλους.",
|
||||
"empty_column.account_featured_unknown.no_collections_desc": "Αυτός ο λογαριασμός δεν έχει δημιουργήσει ακόμη καμία συλλογή.",
|
||||
"empty_column.account_featured_self.pre_collections_desc": "Οι Συλλογές (που έρχονται στο Mastodon 4.6) επιτρέπουν να δημιουργήσετε τις δικές σας επιμελημένες λίστες λογαριασμών για να συστήσετε σε άλλους.",
|
||||
"empty_column.account_featured_self.showcase_accounts": "Παρουσιάστε τους αγαπημένους σας λογαριασμούς",
|
||||
"empty_column.account_featured_self.showcase_accounts_desc": "Οι συλλογές είναι επιμελημένες λίστες λογαριασμών για να βοηθήσουν άλλους να ανακαλύψουν περισσότερα από το Fediverse.",
|
||||
"empty_column.account_featured_unknown.other": "Αυτός ο λογαριασμός δεν έχει αναδείξει τίποτα ακόμη.",
|
||||
"empty_column.account_hides_collections": "Αυτός ο χρήστης έχει επιλέξει να μην καταστήσει αυτές τις πληροφορίες διαθέσιμες",
|
||||
"empty_column.account_suspended": "Λογαριασμός σε αναστολή",
|
||||
|
||||
@ -45,6 +45,7 @@
|
||||
"account.featured": "Featured",
|
||||
"account.featured.accounts": "Profiles",
|
||||
"account.featured.collections": "Collections",
|
||||
"account.featured.new_collection": "New collection",
|
||||
"account.field_overflow": "Show full content",
|
||||
"account.filters.all": "All activity",
|
||||
"account.filters.boosts_toggle": "Show boosts",
|
||||
@ -102,6 +103,7 @@
|
||||
"account.muted": "Muted",
|
||||
"account.muting": "Muting",
|
||||
"account.mutual": "You follow each other",
|
||||
"account.name.copy": "Copy handle",
|
||||
"account.name.help.domain": "{domain} is the server that hosts the user’s profile and posts.",
|
||||
"account.name.help.domain_self": "{domain} is your server that hosts your profile and posts.",
|
||||
"account.name.help.footer": "Just like you can send emails to people using different email clients, you can interact with people on other Mastodon servers – and with anyone on other social apps powered by the same set of rules as Mastodon uses (the ActivityPub protocol).",
|
||||
@ -138,6 +140,9 @@
|
||||
"account.unmute": "Unmute @{name}",
|
||||
"account.unmute_notifications_short": "Unmute notifications",
|
||||
"account.unmute_short": "Unmute",
|
||||
"account_edit.advanced_settings.bot_hint": "Signal to others that the account mainly performs automated actions and might not be monitored",
|
||||
"account_edit.advanced_settings.bot_label": "Automated account",
|
||||
"account_edit.advanced_settings.title": "Advanced settings",
|
||||
"account_edit.bio.add_label": "",
|
||||
"account_edit.bio.edit_label": "Edit bio",
|
||||
"account_edit.bio.placeholder": "Add a short introduction to help others identify you.",
|
||||
@ -346,7 +351,7 @@
|
||||
"closed_registrations_modal.find_another_server": "Find another server",
|
||||
"closed_registrations_modal.preamble": "Mastodon is decentralised, so no matter where you create your account, you will be able to follow and interact with anyone on this server. You can even self-host it!",
|
||||
"closed_registrations_modal.title": "Signing up on Mastodon",
|
||||
"collection.share_modal.share_link_label": "Invite share link",
|
||||
"collection.share_modal.share_link_label": "Share link",
|
||||
"collection.share_modal.share_via_post": "Post on Mastodon",
|
||||
"collection.share_modal.share_via_system": "Share to…",
|
||||
"collection.share_modal.title": "Share collection",
|
||||
@ -380,6 +385,7 @@
|
||||
"collections.detail.sensitive_content": "Sensitive content",
|
||||
"collections.detail.sensitive_note": "This collection contains accounts and content that may be sensitive to some users.",
|
||||
"collections.detail.share": "Share this collection",
|
||||
"collections.detail.you_are_in_this_collection": "You're featured in this collection",
|
||||
"collections.edit_details": "Edit details",
|
||||
"collections.error_loading_collections": "There was an error when trying to load your collections.",
|
||||
"collections.hints.accounts_counter": "{count} / {max} accounts",
|
||||
@ -411,7 +417,6 @@
|
||||
"column.about": "About",
|
||||
"column.blocks": "Blocked users",
|
||||
"column.bookmarks": "Bookmarks",
|
||||
"column.collections": "My collections",
|
||||
"column.community": "Local timeline",
|
||||
"column.create_list": "Create list",
|
||||
"column.direct": "Private mentions",
|
||||
@ -427,7 +432,9 @@
|
||||
"column.list_members": "Manage list members",
|
||||
"column.lists": "Lists",
|
||||
"column.mutes": "Muted users",
|
||||
"column.my_collections": "My collections",
|
||||
"column.notifications": "Notifications",
|
||||
"column.other_collections": "Collections by {name}",
|
||||
"column.pins": "Pinned posts",
|
||||
"column.public": "Federated timeline",
|
||||
"column_back_button.label": "Back",
|
||||
@ -497,6 +504,10 @@
|
||||
"confirmations.follow_to_list.confirm": "Follow and add to list",
|
||||
"confirmations.follow_to_list.message": "You need to be following {name} to add them to a list.",
|
||||
"confirmations.follow_to_list.title": "Follow user?",
|
||||
"confirmations.hide_featured_tab.confirm": "Hide tab",
|
||||
"confirmations.hide_featured_tab.intro": "You can change this at any time under <i>Edit profile > Profile tab settings</i>.",
|
||||
"confirmations.hide_featured_tab.message": "This will hide the tab for users on {serverName} and other servers running the latest version of Mastodon. Other displays may vary.",
|
||||
"confirmations.hide_featured_tab.title": "Hide \"Featured\" tab?",
|
||||
"confirmations.logout.confirm": "Log out",
|
||||
"confirmations.logout.message": "Are you sure you want to log out?",
|
||||
"confirmations.logout.title": "Log out?",
|
||||
@ -535,6 +546,7 @@
|
||||
"content_warning.hide": "Hide post",
|
||||
"content_warning.show": "Show anyway",
|
||||
"content_warning.show_more": "Show more",
|
||||
"content_warning.show_short": "Show",
|
||||
"conversation.delete": "Delete conversation",
|
||||
"conversation.mark_as_read": "Mark as read",
|
||||
"conversation.open": "View conversation",
|
||||
@ -575,6 +587,14 @@
|
||||
"domain_pill.your_server": "Your digital home, where all of your posts live. Don’t like this one? Transfer servers at any time and bring your followers too.",
|
||||
"domain_pill.your_username": "Your unique identifier on this server. It’s possible to find users with the same username on different servers.",
|
||||
"dropdown.empty": "Select an option",
|
||||
"email_subscriptions.email": "Email",
|
||||
"email_subscriptions.form.action": "Subscribe",
|
||||
"email_subscriptions.form.bottom": "Get posts in your inbox without creating a Mastodon account. Unsubscribe at any time. For more information, refer to the <a>Privacy Policy</a>.",
|
||||
"email_subscriptions.form.title": "Sign up for email updates from {name}",
|
||||
"email_subscriptions.submitted.lead": "Check your inbox for an email to finish signing up for email updates.",
|
||||
"email_subscriptions.submitted.title": "One more step",
|
||||
"email_subscriptions.validation.email.blocked": "Blocked email provider",
|
||||
"email_subscriptions.validation.email.invalid": "Invalid email address",
|
||||
"embed.instructions": "Embed this post on your website by copying the code below.",
|
||||
"embed.preview": "Here is what it will look like:",
|
||||
"emoji_button.activity": "Activity",
|
||||
@ -592,7 +612,14 @@
|
||||
"emoji_button.search_results": "Search results",
|
||||
"emoji_button.symbols": "Symbols",
|
||||
"emoji_button.travel": "Travel & Places",
|
||||
"empty_column.account_featured.other": "{acct} has not featured anything yet. Did you know that you can feature your hashtags you use the most, and even your friend’s accounts on your profile?",
|
||||
"empty_column.account_featured.other": "{acct} has not featured anything yet.",
|
||||
"empty_column.account_featured_self.no_collections_button": "Create a collection",
|
||||
"empty_column.account_featured_self.no_collections_hide_tab": "Hide this tab instead",
|
||||
"empty_column.account_featured_self.pre_collections": "Stay tuned for Collections",
|
||||
"empty_column.account_featured_self.pre_collections_desc": "Collections (coming in Mastodon 4.6) allow you to create your own curated lists of accounts to recommend to others.",
|
||||
"empty_column.account_featured_self.showcase_accounts": "Showcase your favourite accounts",
|
||||
"empty_column.account_featured_self.showcase_accounts_desc": "Collections are curated lists of accounts to help others discover more of the Fediverse.",
|
||||
"empty_column.account_featured_unknown.other": "This account hasn’t featured anything yet.",
|
||||
"empty_column.account_hides_collections": "This user has chosen not to make this information available",
|
||||
"empty_column.account_suspended": "Account suspended",
|
||||
"empty_column.account_timeline": "No posts here!",
|
||||
@ -632,6 +659,10 @@
|
||||
"featured_carousel.header": "{count, plural, one {Pinned Post} other {Pinned Posts}}",
|
||||
"featured_carousel.slide": "Post {current, number} of {max, number}",
|
||||
"featured_tags.more_items": "+{count}",
|
||||
"featured_tags.suggestions": "Lately you’ve posted about {items}. Add these as featured hashtags?",
|
||||
"featured_tags.suggestions.add": "Add",
|
||||
"featured_tags.suggestions.added": "Manage your featured hashtags at any time under <link>Edit Profile > Featured hashtags</link>.",
|
||||
"featured_tags.suggestions.dismiss": "No thanks",
|
||||
"filter_modal.added.context_mismatch_explanation": "This filter category does not apply to the context in which you have accessed this post. If you want the post to be filtered in this context too, you will have to edit the filter.",
|
||||
"filter_modal.added.context_mismatch_title": "Context mismatch!",
|
||||
"filter_modal.added.expired_explanation": "This filter category has expired, you will need to change the expiration date for it to apply.",
|
||||
@ -674,7 +705,9 @@
|
||||
"follow_suggestions.who_to_follow": "Who to follow",
|
||||
"followed_tags": "Followed hashtags",
|
||||
"followers.hide_other_followers": "This user has chosen not to make their other followers visible",
|
||||
"followers.title": "Following {name}",
|
||||
"following.hide_other_following": "This user has chosen not to make the rest of who they follow visible",
|
||||
"following.title": "Followed by {name}",
|
||||
"footer.about": "About",
|
||||
"footer.about_mastodon": "About Mastodon",
|
||||
"footer.about_server": "About {domain}",
|
||||
@ -686,6 +719,7 @@
|
||||
"footer.source_code": "View source code",
|
||||
"footer.status": "Status",
|
||||
"footer.terms_of_service": "Terms of service",
|
||||
"form_error.blank": "Field cannot be blank.",
|
||||
"form_field.optional": "(optional)",
|
||||
"generic.saved": "Saved",
|
||||
"getting_started.heading": "Getting started",
|
||||
|
||||
@ -45,6 +45,7 @@
|
||||
"account.featured": "Featured",
|
||||
"account.featured.accounts": "Profiles",
|
||||
"account.featured.collections": "Collections",
|
||||
"account.featured.new_collection": "New collection",
|
||||
"account.field_overflow": "Show full content",
|
||||
"account.filters.all": "All activity",
|
||||
"account.filters.boosts_toggle": "Show boosts",
|
||||
@ -350,7 +351,7 @@
|
||||
"closed_registrations_modal.find_another_server": "Find another server",
|
||||
"closed_registrations_modal.preamble": "Mastodon is decentralized, so no matter where you create your account, you will be able to follow and interact with anyone on this server. You can even self-host it!",
|
||||
"closed_registrations_modal.title": "Signing up on Mastodon",
|
||||
"collection.share_modal.share_link_label": "Invite share link",
|
||||
"collection.share_modal.share_link_label": "Share link",
|
||||
"collection.share_modal.share_via_post": "Post on Mastodon",
|
||||
"collection.share_modal.share_via_system": "Share to…",
|
||||
"collection.share_modal.title": "Share collection",
|
||||
@ -416,7 +417,6 @@
|
||||
"column.about": "About",
|
||||
"column.blocks": "Blocked users",
|
||||
"column.bookmarks": "Bookmarks",
|
||||
"column.collections": "My collections",
|
||||
"column.community": "Local timeline",
|
||||
"column.create_list": "Create list",
|
||||
"column.direct": "Private mentions",
|
||||
@ -432,7 +432,9 @@
|
||||
"column.list_members": "Manage list members",
|
||||
"column.lists": "Lists",
|
||||
"column.mutes": "Muted users",
|
||||
"column.my_collections": "My collections",
|
||||
"column.notifications": "Notifications",
|
||||
"column.other_collections": "Collections by {name}",
|
||||
"column.pins": "Pinned posts",
|
||||
"column.public": "Federated timeline",
|
||||
"column_back_button.label": "Back",
|
||||
@ -502,6 +504,10 @@
|
||||
"confirmations.follow_to_list.confirm": "Follow and add to list",
|
||||
"confirmations.follow_to_list.message": "You need to be following {name} to add them to a list.",
|
||||
"confirmations.follow_to_list.title": "Follow user?",
|
||||
"confirmations.hide_featured_tab.confirm": "Hide tab",
|
||||
"confirmations.hide_featured_tab.intro": "You can change this at any time under <i>Edit profile > Profile tab settings</i>.",
|
||||
"confirmations.hide_featured_tab.message": "This will hide the tab for users on {serverName} and other servers running the latest version of Mastodon. Other displays may vary.",
|
||||
"confirmations.hide_featured_tab.title": "Hide \"Featured\" tab?",
|
||||
"confirmations.logout.confirm": "Log out",
|
||||
"confirmations.logout.message": "Are you sure you want to log out?",
|
||||
"confirmations.logout.title": "Log out?",
|
||||
@ -606,14 +612,13 @@
|
||||
"emoji_button.search_results": "Search results",
|
||||
"emoji_button.symbols": "Symbols",
|
||||
"emoji_button.travel": "Travel & Places",
|
||||
"empty_column.account_featured.other": "{acct} has not featured anything yet. Did you know that you can feature your hashtags you use the most, and even your friend’s accounts on your profile?",
|
||||
"empty_column.account_featured_other.no_collections_desc": "{acct} hasn’t created any collections yet.",
|
||||
"empty_column.account_featured_other.title": "Nothing to see here",
|
||||
"empty_column.account_featured_self.no_collections": "No collections yet",
|
||||
"empty_column.account_featured.other": "{acct} has not featured anything yet.",
|
||||
"empty_column.account_featured_self.no_collections_button": "Create a collection",
|
||||
"empty_column.account_featured_self.no_collections_hide_tab": "Hide this tab instead",
|
||||
"empty_column.account_featured_self.pre_collections": "Stay tuned for Collections",
|
||||
"empty_column.account_featured_self.pre_collections_desc": "Collections (coming in Mastodon 4.6) allows you to create your own curated lists of accounts to recommend to others.",
|
||||
"empty_column.account_featured_unknown.no_collections_desc": "This account hasn’t created any collections yet.",
|
||||
"empty_column.account_featured_self.pre_collections_desc": "Collections (coming in Mastodon 4.6) allow you to create your own curated lists of accounts to recommend to others.",
|
||||
"empty_column.account_featured_self.showcase_accounts": "Showcase your favorite accounts",
|
||||
"empty_column.account_featured_self.showcase_accounts_desc": "Collections are curated lists of accounts to help others discover more of the Fediverse.",
|
||||
"empty_column.account_featured_unknown.other": "This account hasn’t featured anything yet.",
|
||||
"empty_column.account_hides_collections": "This user has chosen to not make this information available",
|
||||
"empty_column.account_suspended": "Account suspended",
|
||||
@ -898,6 +903,7 @@
|
||||
"navigation_panel.expand_followed_tags": "Expand followed hashtags menu",
|
||||
"navigation_panel.expand_lists": "Expand list menu",
|
||||
"not_signed_in_indicator.not_signed_in": "You need to login to access this resource.",
|
||||
"notification.added_to_collection": "{name} added you to a collection",
|
||||
"notification.admin.report": "{name} reported {target}",
|
||||
"notification.admin.report_account": "{name} reported {count, plural, one {one post} other {# posts}} from {target} for {category}",
|
||||
"notification.admin.report_account_other": "{name} reported {count, plural, one {one post} other {# posts}} from {target}",
|
||||
@ -907,6 +913,7 @@
|
||||
"notification.admin.sign_up.name_and_others": "{name} and {count, plural, one {# other} other {# others}} signed up",
|
||||
"notification.annual_report.message": "Your {year} #Wrapstodon awaits! Unveil your year's highlights and memorable moments on Mastodon!",
|
||||
"notification.annual_report.view": "View #Wrapstodon",
|
||||
"notification.collection_update": "{name} edited a collection you’re in",
|
||||
"notification.favourite": "{name} favorited your post",
|
||||
"notification.favourite.name_and_others_with_link": "{name} and <a>{count, plural, one {# other} other {# others}}</a> favorited your post",
|
||||
"notification.favourite_pm": "{name} favorited your private mention",
|
||||
|
||||
@ -45,6 +45,7 @@
|
||||
"account.featured": "Destacados",
|
||||
"account.featured.accounts": "Perfiles",
|
||||
"account.featured.collections": "Colecciones",
|
||||
"account.featured.new_collection": "Nueva colección",
|
||||
"account.field_overflow": "Mostrar contenido completo",
|
||||
"account.filters.all": "Toda la actividad",
|
||||
"account.filters.boosts_toggle": "Mostrar adhesiones",
|
||||
@ -416,7 +417,6 @@
|
||||
"column.about": "Información",
|
||||
"column.blocks": "Usuarios bloqueados",
|
||||
"column.bookmarks": "Marcadores",
|
||||
"column.collections": "Mis colecciones",
|
||||
"column.community": "Línea temporal local",
|
||||
"column.create_list": "Crear lista",
|
||||
"column.direct": "Menciones privadas",
|
||||
@ -432,7 +432,9 @@
|
||||
"column.list_members": "Administrar miembros de la lista",
|
||||
"column.lists": "Listas",
|
||||
"column.mutes": "Usuarios silenciados",
|
||||
"column.my_collections": "Mis colecciones",
|
||||
"column.notifications": "Notificaciones",
|
||||
"column.other_collections": "Colecciones de {name}",
|
||||
"column.pins": "Mensajes fijados",
|
||||
"column.public": "Línea temporal federada",
|
||||
"column_back_button.label": "Volver",
|
||||
@ -502,6 +504,10 @@
|
||||
"confirmations.follow_to_list.confirm": "Seguir y agregar a la lista",
|
||||
"confirmations.follow_to_list.message": "Necesitás seguir a {name} para agregarle a una lista.",
|
||||
"confirmations.follow_to_list.title": "¿Querés seguirle?",
|
||||
"confirmations.hide_featured_tab.confirm": "Ocultar pestaña",
|
||||
"confirmations.hide_featured_tab.intro": "Podés cambiar esto en cualquier momento en <i>Editar perfil > Configuración de pestaña de perfil</i>.",
|
||||
"confirmations.hide_featured_tab.message": "Esto ocultará la pestaña para los usuarios en {serverName} y otros servidores que tengan instalada la última versión de Mastodon. Otras pantallas pueden variar.",
|
||||
"confirmations.hide_featured_tab.title": "¿Ocultar la pestaña «Destacados»?",
|
||||
"confirmations.logout.confirm": "Cerrar sesión",
|
||||
"confirmations.logout.message": "¿De verdad querés cerrar la sesión?",
|
||||
"confirmations.logout.title": "¿Cerrar sesión?",
|
||||
@ -606,14 +612,13 @@
|
||||
"emoji_button.search_results": "Resultados de búsqueda",
|
||||
"emoji_button.symbols": "Símbolos",
|
||||
"emoji_button.travel": "Viajes y lugares",
|
||||
"empty_column.account_featured.other": "{acct} todavía no destacó nada. ¿Sabías que en tu perfil podés destacar tus etiquetas que más usás e incluso las cuentas de tus contactos?",
|
||||
"empty_column.account_featured_other.no_collections_desc": "{acct} todavía no creó ninguna colección.",
|
||||
"empty_column.account_featured_other.title": "No hay nada por acá",
|
||||
"empty_column.account_featured_self.no_collections": "No hay colecciones aún",
|
||||
"empty_column.account_featured.other": "{acct} todavía no destacó nada.",
|
||||
"empty_column.account_featured_self.no_collections_button": "Crear colección",
|
||||
"empty_column.account_featured_self.no_collections_hide_tab": "En su lugar, ocultar esta pestaña",
|
||||
"empty_column.account_featured_self.pre_collections": "Mantenete pendiente de las colecciones",
|
||||
"empty_column.account_featured_self.pre_collections_desc": "Las colecciones (que vendrán en Mastodon 4.6) te permite crear tus propias listas de cuentas seleccionadas para recomendar a otras personas.",
|
||||
"empty_column.account_featured_unknown.no_collections_desc": "Esta cuenta todavía no creó ninguna colección.",
|
||||
"empty_column.account_featured_self.pre_collections_desc": "Las colecciones (que vendrán en Mastodon 4.6) te permiten crear tus propias listas de cuentas seleccionadas para recomendar a otras personas.",
|
||||
"empty_column.account_featured_self.showcase_accounts": "Mostrá tus cuentas favoritas",
|
||||
"empty_column.account_featured_self.showcase_accounts_desc": "Las colecciones son listas de cuentas seleccionadas para ayudar a otras personas a descubrir más del Fediverso.",
|
||||
"empty_column.account_featured_unknown.other": "Esta cuenta todavía no destacó nada.",
|
||||
"empty_column.account_hides_collections": "Este usuario eligió no publicar esta información",
|
||||
"empty_column.account_suspended": "Cuenta suspendida",
|
||||
|
||||
@ -45,6 +45,7 @@
|
||||
"account.featured": "Destacado",
|
||||
"account.featured.accounts": "Perfiles",
|
||||
"account.featured.collections": "Colecciones",
|
||||
"account.featured.new_collection": "Nueva colección",
|
||||
"account.field_overflow": "Mostrar contenido completo",
|
||||
"account.filters.all": "Toda la actividad",
|
||||
"account.filters.boosts_toggle": "Mostrar impulsos",
|
||||
@ -416,7 +417,6 @@
|
||||
"column.about": "Acerca de",
|
||||
"column.blocks": "Usuarios bloqueados",
|
||||
"column.bookmarks": "Marcadores",
|
||||
"column.collections": "Mis colecciones",
|
||||
"column.community": "Cronología local",
|
||||
"column.create_list": "Crear lista",
|
||||
"column.direct": "Menciones privadas",
|
||||
@ -432,7 +432,9 @@
|
||||
"column.list_members": "Administrar miembros de la lista",
|
||||
"column.lists": "Listas",
|
||||
"column.mutes": "Usuarios silenciados",
|
||||
"column.my_collections": "Mis colecciones",
|
||||
"column.notifications": "Notificaciones",
|
||||
"column.other_collections": "Colecciones de {name}",
|
||||
"column.pins": "Publicaciones fijadas",
|
||||
"column.public": "Cronología federada",
|
||||
"column_back_button.label": "Atrás",
|
||||
@ -502,6 +504,10 @@
|
||||
"confirmations.follow_to_list.confirm": "Seguir y agregar a la lista",
|
||||
"confirmations.follow_to_list.message": "Tienes que seguir a {name} para añadirlo a una lista.",
|
||||
"confirmations.follow_to_list.title": "¿Seguir a usuario?",
|
||||
"confirmations.hide_featured_tab.confirm": "Ocultar pestaña",
|
||||
"confirmations.hide_featured_tab.intro": "Puedes cambiar esto en cualquier momento en <i>Editar perfil > Configuración de la pestaña Perfil</i>.",
|
||||
"confirmations.hide_featured_tab.message": "Esto ocultará la pestaña para los usuarios de {serverName} y otros servidores que ejecuten la última versión de Mastodon. La visualización puede variar en otros casos.",
|
||||
"confirmations.hide_featured_tab.title": "¿Deseas ocultar la pestaña \"Destacado\"?",
|
||||
"confirmations.logout.confirm": "Cerrar sesión",
|
||||
"confirmations.logout.message": "¿Estás seguro de que quieres cerrar la sesión?",
|
||||
"confirmations.logout.title": "¿Deseas cerrar sesión?",
|
||||
@ -606,14 +612,13 @@
|
||||
"emoji_button.search_results": "Resultados de búsqueda",
|
||||
"emoji_button.symbols": "Símbolos",
|
||||
"emoji_button.travel": "Viajes y lugares",
|
||||
"empty_column.account_featured.other": "{acct} no ha destacado nada todavía. ¿Sabías que puedes destacar las etiquetas que más usas e incluso las cuentas de tus amigos en tu perfil?",
|
||||
"empty_column.account_featured_other.no_collections_desc": "{acct} aún no ha creado ninguna colección.",
|
||||
"empty_column.account_featured_other.title": "Aquí no hay nada que ver",
|
||||
"empty_column.account_featured_self.no_collections": "Aún no hay colecciones",
|
||||
"empty_column.account_featured.other": "{acct} no ha destacado nada todavía.",
|
||||
"empty_column.account_featured_self.no_collections_button": "Crear una colección",
|
||||
"empty_column.account_featured_self.no_collections_hide_tab": "Ocultar esta pestaña",
|
||||
"empty_column.account_featured_self.pre_collections": "No te pierdas las colecciones",
|
||||
"empty_column.account_featured_self.pre_collections_desc": "Las colecciones (disponibles en Mastodon 4.6) te permiten crear tus propias listas seleccionadas de cuentas para recomendarlas a otras personas.",
|
||||
"empty_column.account_featured_unknown.no_collections_desc": "Esta cuenta aún no ha creado ninguna colección.",
|
||||
"empty_column.account_featured_self.pre_collections_desc": "Las colecciones (que llegarán en Mastodon 4.6) te permiten crear tus propias listas de cuentas seleccionadas para recomendarlas a otras personas.",
|
||||
"empty_column.account_featured_self.showcase_accounts": "Destaca tus cuentas favoritas",
|
||||
"empty_column.account_featured_self.showcase_accounts_desc": "Las colecciones son listas de cuentas seleccionadas que ayudan a otros a descubrir más del Fediverso.",
|
||||
"empty_column.account_featured_unknown.other": "Esta cuenta no ha destacado nada todavía.",
|
||||
"empty_column.account_hides_collections": "Este usuario ha elegido no hacer disponible esta información",
|
||||
"empty_column.account_suspended": "Cuenta suspendida",
|
||||
|
||||
@ -45,6 +45,7 @@
|
||||
"account.featured": "Destacado",
|
||||
"account.featured.accounts": "Perfiles",
|
||||
"account.featured.collections": "Colecciones",
|
||||
"account.featured.new_collection": "Nueva colección",
|
||||
"account.field_overflow": "Mostrar contenido completo",
|
||||
"account.filters.all": "Toda la actividad",
|
||||
"account.filters.boosts_toggle": "Mostrar impulsos",
|
||||
@ -416,7 +417,6 @@
|
||||
"column.about": "Acerca de",
|
||||
"column.blocks": "Usuarios bloqueados",
|
||||
"column.bookmarks": "Marcadores",
|
||||
"column.collections": "Mis colecciones",
|
||||
"column.community": "Cronología local",
|
||||
"column.create_list": "Crear lista",
|
||||
"column.direct": "Menciones privadas",
|
||||
@ -432,7 +432,9 @@
|
||||
"column.list_members": "Administrar miembros de la lista",
|
||||
"column.lists": "Listas",
|
||||
"column.mutes": "Usuarios silenciados",
|
||||
"column.my_collections": "Mis colecciones",
|
||||
"column.notifications": "Notificaciones",
|
||||
"column.other_collections": "Colecciones de {name}",
|
||||
"column.pins": "Publicaciones fijadas",
|
||||
"column.public": "Cronología federada",
|
||||
"column_back_button.label": "Atrás",
|
||||
@ -502,6 +504,10 @@
|
||||
"confirmations.follow_to_list.confirm": "Seguir y añadir a la lista",
|
||||
"confirmations.follow_to_list.message": "Necesitas seguir a {name} para agregarlo a una lista.",
|
||||
"confirmations.follow_to_list.title": "¿Seguir usuario?",
|
||||
"confirmations.hide_featured_tab.confirm": "Ocultar pestaña",
|
||||
"confirmations.hide_featured_tab.intro": "Puedes cambiar esto en cualquier momento en <i>Editar perfil > Configuración de pestañas del perfil</i>.",
|
||||
"confirmations.hide_featured_tab.message": "Esto esconderá la pestaña a los usuarios en {serverName} y otros servidores con la última versión de Mastodon. En otros lugares, puede variar.",
|
||||
"confirmations.hide_featured_tab.title": "¿Ocultar la pestaña \"Destacado\"?",
|
||||
"confirmations.logout.confirm": "Cerrar sesión",
|
||||
"confirmations.logout.message": "¿Seguro que quieres cerrar la sesión?",
|
||||
"confirmations.logout.title": "¿Cerrar sesión?",
|
||||
@ -606,14 +612,13 @@
|
||||
"emoji_button.search_results": "Resultados de búsqueda",
|
||||
"emoji_button.symbols": "Símbolos",
|
||||
"emoji_button.travel": "Viajes y lugares",
|
||||
"empty_column.account_featured.other": "{acct} aún no ha destacado nada. ¿Sabías que puedes destacar las etiquetas que más usas e incluso las cuentas de tus amigos en tu perfil?",
|
||||
"empty_column.account_featured_other.no_collections_desc": "{acct} aún no ha creado ninguna colección.",
|
||||
"empty_column.account_featured_other.title": "Nada que ver aquí",
|
||||
"empty_column.account_featured_self.no_collections": "Aún no hay colecciones",
|
||||
"empty_column.account_featured.other": "{acct} aún no ha destacado nada.",
|
||||
"empty_column.account_featured_self.no_collections_button": "Crear una colección",
|
||||
"empty_column.account_featured_self.no_collections_hide_tab": "Ocultar esta pestaña",
|
||||
"empty_column.account_featured_self.pre_collections": "No te pierdas las Colecciones",
|
||||
"empty_column.account_featured_self.pre_collections_desc": "Las colecciones (disponibles en Mastodon 4.6) te permiten crear tus propias listas seleccionadas de cuentas para recomendarlas a otras personas.",
|
||||
"empty_column.account_featured_unknown.no_collections_desc": "Esta cuenta aún no ha creado ninguna colección.",
|
||||
"empty_column.account_featured_self.pre_collections_desc": "Las colecciones (disponibles a partir de Mastodon 4.6) te permiten crear tus propias listas de cuentas seleccionadas para recomendarlas a otros.",
|
||||
"empty_column.account_featured_self.showcase_accounts": "Muestra tus cuentas favoritas",
|
||||
"empty_column.account_featured_self.showcase_accounts_desc": "Las colecciones son listas de cuentas seleccionadas para ayudar a otros a descubrir más del Fediverso.",
|
||||
"empty_column.account_featured_unknown.other": "Esta cuenta no ha destacado nada todavía.",
|
||||
"empty_column.account_hides_collections": "Este usuario ha decidido no mostrar esta información",
|
||||
"empty_column.account_suspended": "Cuenta suspendida",
|
||||
|
||||
@ -386,7 +386,6 @@
|
||||
"column.about": "Teave",
|
||||
"column.blocks": "Blokeeritud kasutajad",
|
||||
"column.bookmarks": "Järjehoidjad",
|
||||
"column.collections": "Minu kogumikud",
|
||||
"column.community": "Kohalik ajajoon",
|
||||
"column.create_list": "Loo loend",
|
||||
"column.direct": "Privaatsed mainimised",
|
||||
@ -560,7 +559,6 @@
|
||||
"emoji_button.search_results": "Otsitulemused",
|
||||
"emoji_button.symbols": "Sümbolid",
|
||||
"emoji_button.travel": "Reisimine & kohad",
|
||||
"empty_column.account_featured.other": "{acct} pole veel midagi esile tõstnud. Kas teadsid, et oma profiilis saad esile tõsta rohkem kasutatud teemaviiteid või sõbra kasutajakontot?",
|
||||
"empty_column.account_hides_collections": "See kasutaja otsustas mitte teha seda infot saadavaks",
|
||||
"empty_column.account_suspended": "Konto kustutatud",
|
||||
"empty_column.account_timeline": "Siin postitusi ei ole!",
|
||||
|
||||
@ -355,7 +355,6 @@
|
||||
"emoji_button.search_results": "Bilaketaren emaitzak",
|
||||
"emoji_button.symbols": "Sinboloak",
|
||||
"emoji_button.travel": "Bidaiak eta tokiak",
|
||||
"empty_column.account_featured.other": "{acct}-ek ez du ezer nabarmendu oraindik. Ba al zenekien gehien erabiltzen dituzun traolak eta baita zure lagunen kontuak ere zure profilean nabarmendu ditzakezula?",
|
||||
"empty_column.account_hides_collections": "Erabiltzaile honek informazio hau erabilgarri ez egotea aukeratu du.",
|
||||
"empty_column.account_suspended": "Kanporatutako kontua",
|
||||
"empty_column.account_timeline": "Ez dago bidalketarik hemen!",
|
||||
|
||||
@ -355,7 +355,6 @@
|
||||
"emoji_button.search_results": "نتایج جستوجو",
|
||||
"emoji_button.symbols": "نمادها",
|
||||
"emoji_button.travel": "سفر و مکان",
|
||||
"empty_column.account_featured.other": "{acct} هنوز هیچ چیزی را پیشنهاد نکرده است. آیا میدانستید که میتوانید برچسبهایی را که بیشتر استفاده میکنید و حتی حسابهای کاربری دوستانتان را در نمایه خود پیشنهاد کنید؟",
|
||||
"empty_column.account_hides_collections": "کاربر خواسته که این اطّلاعات در دسترس نباشند",
|
||||
"empty_column.account_suspended": "حساب معلق شد",
|
||||
"empty_column.account_timeline": "هیچ فرستهای اینجا نیست!",
|
||||
|
||||
@ -45,6 +45,7 @@
|
||||
"account.featured": "Esittelyssä",
|
||||
"account.featured.accounts": "Profiilit",
|
||||
"account.featured.collections": "Kokoelmat",
|
||||
"account.featured.new_collection": "Uusi kokoelma",
|
||||
"account.field_overflow": "Näytä koko sisältö",
|
||||
"account.filters.all": "Kaikki toiminta",
|
||||
"account.filters.boosts_toggle": "Näytä tehostukset",
|
||||
@ -416,7 +417,6 @@
|
||||
"column.about": "Tietoja",
|
||||
"column.blocks": "Estetyt käyttäjät",
|
||||
"column.bookmarks": "Kirjanmerkit",
|
||||
"column.collections": "Omat kokoelmat",
|
||||
"column.community": "Paikallinen aikajana",
|
||||
"column.create_list": "Luo lista",
|
||||
"column.direct": "Yksityismaininnat",
|
||||
@ -432,7 +432,9 @@
|
||||
"column.list_members": "Hallitse listan jäseniä",
|
||||
"column.lists": "Listat",
|
||||
"column.mutes": "Mykistetyt käyttäjät",
|
||||
"column.my_collections": "Omat kokoelmat",
|
||||
"column.notifications": "Ilmoitukset",
|
||||
"column.other_collections": "Käyttäjän {name} kokoelmat",
|
||||
"column.pins": "Kiinnitetyt julkaisut",
|
||||
"column.public": "Yleinen aikajana",
|
||||
"column_back_button.label": "Takaisin",
|
||||
@ -502,6 +504,10 @@
|
||||
"confirmations.follow_to_list.confirm": "Seuraa ja lisää listaan",
|
||||
"confirmations.follow_to_list.message": "Sinun on seurattava käyttäjää {name}, jotta voit lisätä hänet listaan.",
|
||||
"confirmations.follow_to_list.title": "Seurataanko käyttäjää?",
|
||||
"confirmations.hide_featured_tab.confirm": "Piilota välilehti",
|
||||
"confirmations.hide_featured_tab.intro": "Voit muuttaa tätä milloin tahansa kohdasta <i>Muokkaa profiilia > Profiilin välilehtien asetukset</i>.",
|
||||
"confirmations.hide_featured_tab.message": "Tämä piilottaa välilehden palvelimen {serverName} käyttäjiltä ja muilta palvelimilta, jotka toimivat Mastodonin uusimmalla versiolla. Muut näkymät voivat vaihdella.",
|
||||
"confirmations.hide_featured_tab.title": "Piilotetaanko Esittelyssä-välilehti?",
|
||||
"confirmations.logout.confirm": "Kirjaudu ulos",
|
||||
"confirmations.logout.message": "Haluatko varmasti kirjautua ulos?",
|
||||
"confirmations.logout.title": "Kirjaudutaanko ulos?",
|
||||
@ -606,14 +612,13 @@
|
||||
"emoji_button.search_results": "Hakutulokset",
|
||||
"emoji_button.symbols": "Symbolit",
|
||||
"emoji_button.travel": "Matkailu ja paikat",
|
||||
"empty_column.account_featured.other": "{acct} ei esittele vielä mitään. Tiesitkö, että voit esitellä profiilissasi eniten käyttämiäsi aihetunnisteita ja jopa ystäviesi tilejä?",
|
||||
"empty_column.account_featured_other.no_collections_desc": "{acct} ei ole luonut vielä yhtään kokoelmaa.",
|
||||
"empty_column.account_featured_other.title": "Täällä ei mitään nähtävää",
|
||||
"empty_column.account_featured_self.no_collections": "Ei vielä kokoelmia",
|
||||
"empty_column.account_featured.other": "{acct} ei esittele vielä mitään.",
|
||||
"empty_column.account_featured_self.no_collections_button": "Luo kokoelma",
|
||||
"empty_column.account_featured_self.no_collections_hide_tab": "Piilota tämä välilehti sen sijaan",
|
||||
"empty_column.account_featured_self.pre_collections": "Ole valmiina kokoelmiin",
|
||||
"empty_column.account_featured_self.pre_collections_desc": "Kokoelmien (tulossa Mastodon 4.6:ssa) avulla voit luoda valikoidun listan muille suosittelemiasi tilejä.",
|
||||
"empty_column.account_featured_unknown.no_collections_desc": "Tämä tili ei ole luonut vielä yhtään kokoelmaa.",
|
||||
"empty_column.account_featured_self.pre_collections_desc": "Kokoelmien (tulossa Mastodon 4.6:ssa) avulla voit luoda valikoidun listan muille suosittelemistasi tileistä.",
|
||||
"empty_column.account_featured_self.showcase_accounts": "Esittele suosikkitilejäsi",
|
||||
"empty_column.account_featured_self.showcase_accounts_desc": "Kokoelmat ovat valikoituja listoja tileistä, jotka auttavat muita löytämään enemmän fediversumissa.",
|
||||
"empty_column.account_featured_unknown.other": "Tämä tili ei esittele vielä mitään.",
|
||||
"empty_column.account_hides_collections": "Käyttäjä on päättänyt pitää nämä tiedot yksityisinä",
|
||||
"empty_column.account_suspended": "Tili jäädytetty",
|
||||
|
||||
@ -326,7 +326,6 @@
|
||||
"column.about": "Um",
|
||||
"column.blocks": "Bannaðir brúkarar",
|
||||
"column.bookmarks": "Bókamerki",
|
||||
"column.collections": "Míni søvn",
|
||||
"column.community": "Lokal tíðarlinja",
|
||||
"column.create_list": "Ger lista",
|
||||
"column.direct": "Privatar umrøður",
|
||||
@ -504,7 +503,6 @@
|
||||
"emoji_button.search_results": "Leitiúrslit",
|
||||
"emoji_button.symbols": "Ímyndir",
|
||||
"emoji_button.travel": "Ferðing og støð",
|
||||
"empty_column.account_featured.other": "{acct} hevur ikki tikið nakað fram enn. Visti tú, at tú kanst taka fram tey frámerki, tú brúkar mest, og sjálvt kontur hjá vinum tínum á vangan hjá tær?",
|
||||
"empty_column.account_hides_collections": "Hesin brúkarin hevur valt, at hesar upplýsingarnar ikki skulu vera tøkar",
|
||||
"empty_column.account_suspended": "Kontan gjørd óvirkin",
|
||||
"empty_column.account_timeline": "Einki uppslag her!",
|
||||
|
||||
@ -45,6 +45,7 @@
|
||||
"account.featured": "En vedette",
|
||||
"account.featured.accounts": "Profils",
|
||||
"account.featured.collections": "Collections",
|
||||
"account.featured.new_collection": "Nouvelle collection",
|
||||
"account.field_overflow": "Voir tout",
|
||||
"account.filters.all": "Toutes les activités",
|
||||
"account.filters.boosts_toggle": "Afficher les partages",
|
||||
@ -416,7 +417,6 @@
|
||||
"column.about": "À propos",
|
||||
"column.blocks": "Comptes bloqués",
|
||||
"column.bookmarks": "Signets",
|
||||
"column.collections": "Mes collections",
|
||||
"column.community": "Fil local",
|
||||
"column.create_list": "Créer une liste",
|
||||
"column.direct": "Mention privée",
|
||||
@ -502,6 +502,10 @@
|
||||
"confirmations.follow_to_list.confirm": "Suivre et ajouter à la liste",
|
||||
"confirmations.follow_to_list.message": "Vous devez suivre {name} pour l'ajouter à une liste.",
|
||||
"confirmations.follow_to_list.title": "Suivre l'utilisateur·rice ?",
|
||||
"confirmations.hide_featured_tab.confirm": "Cacher l'onglet",
|
||||
"confirmations.hide_featured_tab.intro": "Vous pouvez modifier ce paramétrage à tout moment depuis <i>Modifier le profil > Paramètres de l'onglet du profil</i>.",
|
||||
"confirmations.hide_featured_tab.message": "Cela masquera l'onglet pour les utilisateur·rice·s de {serverName} et des autres serveurs utilisant la dernière version de Mastodon. Pour les autres l'affichage peut varier.",
|
||||
"confirmations.hide_featured_tab.title": "Cacher l'onglet « En vedette » ?",
|
||||
"confirmations.logout.confirm": "Se déconnecter",
|
||||
"confirmations.logout.message": "Voulez-vous vraiment vous déconnecter?",
|
||||
"confirmations.logout.title": "Se déconnecter ?",
|
||||
@ -606,14 +610,13 @@
|
||||
"emoji_button.search_results": "Résultats",
|
||||
"emoji_button.symbols": "Symboles",
|
||||
"emoji_button.travel": "Voyage et lieux",
|
||||
"empty_column.account_featured.other": "{acct} n'a pas encore mis de contenu en avant. Saviez-vous que vous pouviez mettre en avant les hashtags que vous utilisez le plus, et même les comptes de vos amis sur votre profil ?",
|
||||
"empty_column.account_featured_other.no_collections_desc": "{acct} n'a pas encore créé de collection.",
|
||||
"empty_column.account_featured_other.title": "Il n'y a rien à voir",
|
||||
"empty_column.account_featured_self.no_collections": "Aucune collection pour le moment",
|
||||
"empty_column.account_featured.other": "{acct} n'a pas encore mis de contenu en avant.",
|
||||
"empty_column.account_featured_self.no_collections_button": "Créer une collection",
|
||||
"empty_column.account_featured_self.no_collections_hide_tab": "Cacher cet onglet",
|
||||
"empty_column.account_featured_self.pre_collections": "Restez à l'écoute pour les collections",
|
||||
"empty_column.account_featured_self.pre_collections_desc": "Les collections (à venir dans Mastodon 4.6) vous permettent d'organiser vos propres listes de comptes à recommander aux autres.",
|
||||
"empty_column.account_featured_unknown.no_collections_desc": "Ce compte n'a pas encore créé de collection.",
|
||||
"empty_column.account_featured_self.pre_collections_desc": "Les collections (à venir dans Mastodon 4.6) permettent d'organiser vos propres listes de comptes à recommander aux autres.",
|
||||
"empty_column.account_featured_self.showcase_accounts": "Présentez vos comptes préférés",
|
||||
"empty_column.account_featured_self.showcase_accounts_desc": "Les collections sont des listes de comptes sélectionnés pour aider les autres à découvrir le Fédivers.",
|
||||
"empty_column.account_featured_unknown.other": "Ce compte n'a mis aucun contenu en avant pour l'instant.",
|
||||
"empty_column.account_hides_collections": "Cet utilisateur·ice préfère ne pas rendre publiques ces informations",
|
||||
"empty_column.account_suspended": "Compte suspendu",
|
||||
|
||||
@ -45,6 +45,7 @@
|
||||
"account.featured": "En vedette",
|
||||
"account.featured.accounts": "Profils",
|
||||
"account.featured.collections": "Collections",
|
||||
"account.featured.new_collection": "Nouvelle collection",
|
||||
"account.field_overflow": "Voir tout",
|
||||
"account.filters.all": "Toutes les activités",
|
||||
"account.filters.boosts_toggle": "Afficher les partages",
|
||||
@ -416,7 +417,6 @@
|
||||
"column.about": "À propos",
|
||||
"column.blocks": "Comptes bloqués",
|
||||
"column.bookmarks": "Marque-pages",
|
||||
"column.collections": "Mes collections",
|
||||
"column.community": "Fil local",
|
||||
"column.create_list": "Créer une liste",
|
||||
"column.direct": "Mentions privées",
|
||||
@ -502,6 +502,10 @@
|
||||
"confirmations.follow_to_list.confirm": "Suivre et ajouter à la liste",
|
||||
"confirmations.follow_to_list.message": "Vous devez suivre {name} pour l'ajouter à une liste.",
|
||||
"confirmations.follow_to_list.title": "Suivre l'utilisateur·rice ?",
|
||||
"confirmations.hide_featured_tab.confirm": "Cacher l'onglet",
|
||||
"confirmations.hide_featured_tab.intro": "Vous pouvez modifier ce paramétrage à tout moment depuis <i>Modifier le profil > Paramètres de l'onglet du profil</i>.",
|
||||
"confirmations.hide_featured_tab.message": "Cela masquera l'onglet pour les utilisateur·rice·s de {serverName} et des autres serveurs utilisant la dernière version de Mastodon. Pour les autres l'affichage peut varier.",
|
||||
"confirmations.hide_featured_tab.title": "Cacher l'onglet « En vedette » ?",
|
||||
"confirmations.logout.confirm": "Se déconnecter",
|
||||
"confirmations.logout.message": "Voulez-vous vraiment vous déconnecter ?",
|
||||
"confirmations.logout.title": "Se déconnecter ?",
|
||||
@ -606,14 +610,13 @@
|
||||
"emoji_button.search_results": "Résultats de la recherche",
|
||||
"emoji_button.symbols": "Symboles",
|
||||
"emoji_button.travel": "Voyage et lieux",
|
||||
"empty_column.account_featured.other": "{acct} n'a pas encore mis de contenu en avant. Saviez-vous que vous pouviez mettre en avant les hashtags que vous utilisez le plus, et même les comptes de vos amis sur votre profil ?",
|
||||
"empty_column.account_featured_other.no_collections_desc": "{acct} n'a pas encore créé de collection.",
|
||||
"empty_column.account_featured_other.title": "Il n'y a rien à voir",
|
||||
"empty_column.account_featured_self.no_collections": "Aucune collection pour le moment",
|
||||
"empty_column.account_featured.other": "{acct} n'a pas encore mis de contenu en avant.",
|
||||
"empty_column.account_featured_self.no_collections_button": "Créer une collection",
|
||||
"empty_column.account_featured_self.no_collections_hide_tab": "Cacher cet onglet",
|
||||
"empty_column.account_featured_self.pre_collections": "Restez à l'écoute pour les collections",
|
||||
"empty_column.account_featured_self.pre_collections_desc": "Les collections (à venir dans Mastodon 4.6) vous permettent d'organiser vos propres listes de comptes à recommander aux autres.",
|
||||
"empty_column.account_featured_unknown.no_collections_desc": "Ce compte n'a pas encore créé de collection.",
|
||||
"empty_column.account_featured_self.pre_collections_desc": "Les collections (à venir dans Mastodon 4.6) permettent d'organiser vos propres listes de comptes à recommander aux autres.",
|
||||
"empty_column.account_featured_self.showcase_accounts": "Présentez vos comptes préférés",
|
||||
"empty_column.account_featured_self.showcase_accounts_desc": "Les collections sont des listes de comptes sélectionnés pour aider les autres à découvrir le Fédivers.",
|
||||
"empty_column.account_featured_unknown.other": "Ce compte n'a mis aucun contenu en avant pour l'instant.",
|
||||
"empty_column.account_hides_collections": "Cet utilisateur·ice préfère ne pas rendre publiques ces informations",
|
||||
"empty_column.account_suspended": "Compte suspendu",
|
||||
|
||||
@ -285,7 +285,6 @@
|
||||
"emoji_button.search_results": "Sykresultaten",
|
||||
"emoji_button.symbols": "Symboalen",
|
||||
"emoji_button.travel": "Reizgje en lokaasjes",
|
||||
"empty_column.account_featured.other": "{acct} hat noch neat útljochte. Wisten jo dat jo jo hashtags dy’t jo it meast brûke, en sels de accounts fan dyn freonen fermelde kinne op jo profyl?",
|
||||
"empty_column.account_hides_collections": "Dizze brûker hat derfoar keazen dizze ynformaasje net beskikber te meitsjen",
|
||||
"empty_column.account_suspended": "Account beskoattele",
|
||||
"empty_column.account_timeline": "Hjir binne gjin berjochten!",
|
||||
|
||||
@ -416,7 +416,6 @@
|
||||
"column.about": "Maidir le",
|
||||
"column.blocks": "Úsáideoirí blocáilte",
|
||||
"column.bookmarks": "Leabharmharcanna",
|
||||
"column.collections": "Mo bhailiúcháin",
|
||||
"column.community": "Amlíne áitiúil",
|
||||
"column.create_list": "Cruthaigh liosta",
|
||||
"column.direct": "Luann príobháideach",
|
||||
@ -581,7 +580,9 @@
|
||||
"domain_pill.your_server": "Do theach digiteach, áit a bhfuil do phoist go léir ina gcónaí. Nach maith leat an ceann seo? Aistrigh freastalaithe am ar bith agus tabhair leat do leantóirí freisin.",
|
||||
"domain_pill.your_username": "D'aitheantóir uathúil ar an bhfreastalaí seo. Is féidir teacht ar úsáideoirí leis an ainm úsáideora céanna ar fhreastalaithe éagsúla.",
|
||||
"dropdown.empty": "Roghnaigh rogha",
|
||||
"email_subscriptions.email": "Ríomhphost",
|
||||
"email_subscriptions.form.action": "Liostáil",
|
||||
"email_subscriptions.form.bottom": "Faigh poist i do bhosca isteach gan cuntas Mastodon a chruthú. Díliostáil ag am ar bith. Le haghaidh tuilleadh eolais, féach ar an <a>Polasaí Príobháideachta</a>.",
|
||||
"email_subscriptions.form.title": "Cláraigh le haghaidh nuashonruithe ríomhphoist ó {name}",
|
||||
"email_subscriptions.submitted.lead": "Seiceáil do bhosca isteach le haghaidh ríomhphoist chun clárú le haghaidh nuashonruithe ríomhphoist a chríochnú.",
|
||||
"email_subscriptions.submitted.title": "Céim amháin eile",
|
||||
@ -604,14 +605,8 @@
|
||||
"emoji_button.search_results": "Torthaí cuardaigh",
|
||||
"emoji_button.symbols": "Comharthaí",
|
||||
"emoji_button.travel": "Taisteal ⁊ Áiteanna",
|
||||
"empty_column.account_featured.other": "Níl aon rud feicthe ag {acct} go fóill. An raibh a fhios agat gur féidir leat na hashtags is mó a úsáideann tú, agus fiú cuntais do chairde, a chur ar do phróifíl?",
|
||||
"empty_column.account_featured_other.no_collections_desc": "Níl aon bhailiúcháin cruthaithe ag {acct} go fóill.",
|
||||
"empty_column.account_featured_other.title": "Níl aon rud le feiceáil anseo",
|
||||
"empty_column.account_featured_self.no_collections": "Gan aon bhailiúcháin fós",
|
||||
"empty_column.account_featured_self.no_collections_button": "Cruthaigh bailiúchán",
|
||||
"empty_column.account_featured_self.pre_collections": "Fan tiúnta le haghaidh bailiúcháin",
|
||||
"empty_column.account_featured_self.pre_collections_desc": "Le bailiúcháin (atá ag teacht i Mastodon 4.6) is féidir leat do liostaí cuntas féin a chruthú le moladh do dhaoine eile.",
|
||||
"empty_column.account_featured_unknown.no_collections_desc": "Níl aon bhailiúcháin cruthaithe ag an gcuntas seo go fóill.",
|
||||
"empty_column.account_featured_unknown.other": "Níl aon rud le feiceáil ar an gcuntas seo go fóill.",
|
||||
"empty_column.account_hides_collections": "Roghnaigh an t-úsáideoir seo gan an fhaisnéis seo a chur ar fáil",
|
||||
"empty_column.account_suspended": "Cuntas ar fionraí",
|
||||
|
||||
@ -416,7 +416,6 @@
|
||||
"column.about": "Mu dhèidhinn",
|
||||
"column.blocks": "Cleachdaichean bacte",
|
||||
"column.bookmarks": "Comharran-lìn",
|
||||
"column.collections": "Na cruinneachaidhean agam",
|
||||
"column.community": "Loidhne-ama ionadail",
|
||||
"column.create_list": "Cruthaich liosta",
|
||||
"column.direct": "Iomraidhean prìobhaideach",
|
||||
@ -605,12 +604,7 @@
|
||||
"emoji_button.search_results": "Toraidhean an luirg",
|
||||
"emoji_button.symbols": "Samhlaidhean",
|
||||
"emoji_button.travel": "Siubhal ⁊ àitichean",
|
||||
"empty_column.account_featured.other": "Chan eil {acct} a’ brosnachadh dad fhathast. An robh fios agad gur urrainn dhut na tagaichean hais a chleachdas tu as trice agus fiù ’s cunntasan do charaidean a bhrosnachadh air a’ phròifil agad?",
|
||||
"empty_column.account_featured_other.no_collections_desc": "Cha do chruthaich {acct} cruinneachadh sam bith fhathast.",
|
||||
"empty_column.account_featured_other.title": "Chan eil dad ri fhaicinn an-seo",
|
||||
"empty_column.account_featured_self.no_collections": "Chan eil cruinneachadh ann fhathast",
|
||||
"empty_column.account_featured_self.no_collections_button": "Cruthaich cruinneachadh",
|
||||
"empty_column.account_featured_unknown.no_collections_desc": "Cha do chruthaich an cunntas seo cruinneachadh sam bith fhathast.",
|
||||
"empty_column.account_featured_unknown.other": "Chan eil an cunntas seo a’ brosnachadh dad fhathast.",
|
||||
"empty_column.account_hides_collections": "Chuir an cleachdaiche seo roimhe nach eil am fiosrachadh seo ri fhaighinn",
|
||||
"empty_column.account_suspended": "Chaidh an cunntas a chur à rèim",
|
||||
|
||||
@ -45,6 +45,7 @@
|
||||
"account.featured": "Destacado",
|
||||
"account.featured.accounts": "Perfís",
|
||||
"account.featured.collections": "Coleccións",
|
||||
"account.featured.new_collection": "Nova colección",
|
||||
"account.field_overflow": "Mostrar contido completo",
|
||||
"account.filters.all": "Toda actividade",
|
||||
"account.filters.boosts_toggle": "Mostrar promocións",
|
||||
@ -416,7 +417,6 @@
|
||||
"column.about": "Sobre",
|
||||
"column.blocks": "Usuarias bloqueadas",
|
||||
"column.bookmarks": "Marcadores",
|
||||
"column.collections": "As miñas coleccións",
|
||||
"column.community": "Cronoloxía local",
|
||||
"column.create_list": "Crear lista",
|
||||
"column.direct": "Mencións privadas",
|
||||
@ -432,7 +432,9 @@
|
||||
"column.list_members": "Xestionar membros da lista",
|
||||
"column.lists": "Listaxes",
|
||||
"column.mutes": "Usuarias acaladas",
|
||||
"column.my_collections": "As miñas coleccións",
|
||||
"column.notifications": "Notificacións",
|
||||
"column.other_collections": "Coleccións de {name}",
|
||||
"column.pins": "Publicacións fixadas",
|
||||
"column.public": "Cronoloxía federada",
|
||||
"column_back_button.label": "Volver",
|
||||
@ -502,6 +504,10 @@
|
||||
"confirmations.follow_to_list.confirm": "Seguir e engadir á lista",
|
||||
"confirmations.follow_to_list.message": "Tes que seguir a {name} para poder engadila a unha lista.",
|
||||
"confirmations.follow_to_list.title": "Seguir á usuaria?",
|
||||
"confirmations.hide_featured_tab.confirm": "Ocultar pestana",
|
||||
"confirmations.hide_featured_tab.intro": "Podes cambiar isto cando queiras nos axustes en <i>Editar perfil > pestana Perfil</i>.",
|
||||
"confirmations.hide_featured_tab.message": "Vas ocultar a pestana para as usuarias de {serverName} e outros servidores que usen a última versión de Mastodon. Noutros lugares podería ser diferente.",
|
||||
"confirmations.hide_featured_tab.title": "Ocultar a pestana «Destacado»?",
|
||||
"confirmations.logout.confirm": "Pechar sesión",
|
||||
"confirmations.logout.message": "Desexas pechar a sesión?",
|
||||
"confirmations.logout.title": "Pechar sesión?",
|
||||
@ -583,6 +589,7 @@
|
||||
"dropdown.empty": "Escolle unha opción",
|
||||
"email_subscriptions.email": "Correo electrónico",
|
||||
"email_subscriptions.form.action": "Subscribirse",
|
||||
"email_subscriptions.form.bottom": "Recibe publicacións no teu correo sen precisar crear unha conta de Mastodon. Date de baixa cando queiras. Máis información na <a>Directiva de privacidade</a>.",
|
||||
"email_subscriptions.form.title": "Subscríbete para recibir actualizacións por correo de {name}",
|
||||
"email_subscriptions.submitted.lead": "Comproba a túa caixa de correo para completar a subscrición ás actualizacións por correo.",
|
||||
"email_subscriptions.submitted.title": "Un último paso",
|
||||
@ -605,14 +612,13 @@
|
||||
"emoji_button.search_results": "Resultados da procura",
|
||||
"emoji_button.symbols": "Símbolos",
|
||||
"emoji_button.travel": "Viaxes e Lugares",
|
||||
"empty_column.account_featured.other": "{acct} aínda non escolleu nada para destacar. Sabías que podes facer destacatar no teu perfil os cancelos que máis usas, incluso os perfís das túas amizades?",
|
||||
"empty_column.account_featured_other.no_collections_desc": "{acct} aínda non creou ningunha colección.",
|
||||
"empty_column.account_featured_other.title": "Non hai nada que ver aquí",
|
||||
"empty_column.account_featured_self.no_collections": "Aínda non tes coleccións",
|
||||
"empty_column.account_featured.other": "{acct} aínda non destacou nada.",
|
||||
"empty_column.account_featured_self.no_collections_button": "Crea unha colección",
|
||||
"empty_column.account_featured_self.no_collections_hide_tab": "Ocultar esta pestana entonces",
|
||||
"empty_column.account_featured_self.pre_collections": "Van chegar as Coleccións",
|
||||
"empty_column.account_featured_self.pre_collections_desc": "Coas Coleccións (a partir de Mastodon 4.6) podes crear as túas listas persoais de contas para recomendar a outras persoas.",
|
||||
"empty_column.account_featured_unknown.no_collections_desc": "Esta conta aínda non creou ningunha colección.",
|
||||
"empty_column.account_featured_self.showcase_accounts": "Salienta as túas contas favoritas",
|
||||
"empty_column.account_featured_self.showcase_accounts_desc": "As coleccións son listas de contas para que outras persoas as coñezan e desfruten aquí no Fediverso.",
|
||||
"empty_column.account_featured_unknown.other": "Esta conta aínda non destacou nada.",
|
||||
"empty_column.account_hides_collections": "A usuaria decideu non facer pública esta información",
|
||||
"empty_column.account_suspended": "Conta suspendida",
|
||||
|
||||
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