forked from treehouse/mastodon
Merge branch 'origin/master' into sync/upstream
Conflicts: app/javascript/mastodon/components/status_list.js app/javascript/mastodon/features/notifications/index.js app/javascript/mastodon/features/ui/components/modal_root.js app/javascript/mastodon/features/ui/components/onboarding_modal.js app/javascript/mastodon/features/ui/index.js app/javascript/styles/about.scss app/javascript/styles/accounts.scss app/javascript/styles/components.scss app/presenters/instance_presenter.rb app/services/post_status_service.rb app/services/reblog_service.rb app/views/about/more.html.haml app/views/about/show.html.haml app/views/accounts/_header.html.haml config/webpack/loaders/babel.js spec/controllers/api/v1/accounts/credentials_controller_spec.rbrebase/4.0.0rc2
commit
b9f7bc149b
|
@ -49,6 +49,7 @@ rules:
|
||||||
- warn
|
- warn
|
||||||
- allow:
|
- allow:
|
||||||
- error
|
- error
|
||||||
|
- warn
|
||||||
no-fallthrough: error
|
no-fallthrough: error
|
||||||
no-irregular-whitespace: error
|
no-irregular-whitespace: error
|
||||||
no-mixed-spaces-and-tabs: warn
|
no-mixed-spaces-and-tabs: warn
|
||||||
|
|
|
@ -10,6 +10,7 @@ AllCops:
|
||||||
- 'node_modules/**/*'
|
- 'node_modules/**/*'
|
||||||
- 'Vagrantfile'
|
- 'Vagrantfile'
|
||||||
- 'vendor/**/*'
|
- 'vendor/**/*'
|
||||||
|
- 'lib/json_ld/*'
|
||||||
|
|
||||||
Bundler/OrderedGems:
|
Bundler/OrderedGems:
|
||||||
Enabled: false
|
Enabled: false
|
||||||
|
|
|
@ -0,0 +1,15 @@
|
||||||
|
# CODEOWNERS for tootsuite/mastodon
|
||||||
|
|
||||||
|
# Translators
|
||||||
|
# To add translator, copy these lines, replace `fr` with appropriate language code and replace `@żelipapą` with user's GitHub nickname preceded by `@` sign or e-mail address.
|
||||||
|
# /app/javascript/mastodon/locales/fr.json @żelipapą
|
||||||
|
# /app/views/user_mailer/*.fr.html.erb @żelipapą
|
||||||
|
# /app/views/user_mailer/*.fr.text.erb @żelipapą
|
||||||
|
# /config/locales/*.fr.yml @żelipapą
|
||||||
|
# /config/locales/fr.yml @żelipapą
|
||||||
|
|
||||||
|
/app/javascript/mastodon/locales/pl.json @m4sk1n
|
||||||
|
/app/views/user_mailer/*.pl.html.erb @m4sk1n
|
||||||
|
/app/views/user_mailer/*.pl.text.erb @m4sk1n
|
||||||
|
/config/locales/*.pl.yml @m4sk1n
|
||||||
|
/config/locales/pl.yml @m4sk1n
|
14
Dockerfile
14
Dockerfile
|
@ -1,4 +1,4 @@
|
||||||
FROM ruby:2.4.1-alpine
|
FROM ruby:2.4.1-alpine3.6
|
||||||
|
|
||||||
LABEL maintainer="https://github.com/tootsuite/mastodon" \
|
LABEL maintainer="https://github.com/tootsuite/mastodon" \
|
||||||
description="A GNU Social-compatible microblogging server"
|
description="A GNU Social-compatible microblogging server"
|
||||||
|
@ -14,9 +14,7 @@ EXPOSE 3000 4000
|
||||||
|
|
||||||
WORKDIR /mastodon
|
WORKDIR /mastodon
|
||||||
|
|
||||||
RUN echo "@edge https://nl.alpinelinux.org/alpine/edge/main" >> /etc/apk/repositories \
|
RUN apk -U upgrade \
|
||||||
&& echo "@edge https://nl.alpinelinux.org/alpine/edge/community" >> /etc/apk/repositories \
|
|
||||||
&& apk -U upgrade \
|
|
||||||
&& apk add -t build-dependencies \
|
&& apk add -t build-dependencies \
|
||||||
build-base \
|
build-base \
|
||||||
icu-dev \
|
icu-dev \
|
||||||
|
@ -31,15 +29,15 @@ RUN echo "@edge https://nl.alpinelinux.org/alpine/edge/main" >> /etc/apk/reposit
|
||||||
file \
|
file \
|
||||||
git \
|
git \
|
||||||
icu-libs \
|
icu-libs \
|
||||||
imagemagick@edge \
|
imagemagick \
|
||||||
libidn \
|
libidn \
|
||||||
libpq \
|
libpq \
|
||||||
nodejs-npm@edge \
|
nodejs-npm \
|
||||||
nodejs@edge \
|
nodejs \
|
||||||
protobuf \
|
protobuf \
|
||||||
su-exec \
|
su-exec \
|
||||||
tini \
|
tini \
|
||||||
yarn@edge \
|
yarn \
|
||||||
&& update-ca-certificates \
|
&& update-ca-certificates \
|
||||||
&& wget -O libiconv.tar.gz "http://ftp.gnu.org/pub/gnu/libiconv/libiconv-$LIBICONV_VERSION.tar.gz" \
|
&& wget -O libiconv.tar.gz "http://ftp.gnu.org/pub/gnu/libiconv/libiconv-$LIBICONV_VERSION.tar.gz" \
|
||||||
&& echo "$LIBICONV_DOWNLOAD_SHA256 *libiconv.tar.gz" | sha256sum -c - \
|
&& echo "$LIBICONV_DOWNLOAD_SHA256 *libiconv.tar.gz" | sha256sum -c - \
|
||||||
|
|
5
Gemfile
5
Gemfile
|
@ -22,7 +22,7 @@ gem 'active_model_serializers', '~> 0.10'
|
||||||
gem 'addressable', '~> 2.5'
|
gem 'addressable', '~> 2.5'
|
||||||
gem 'bootsnap'
|
gem 'bootsnap'
|
||||||
gem 'browser'
|
gem 'browser'
|
||||||
gem 'charlock_holmes', '~> 0.7.3'
|
gem 'charlock_holmes', '~> 0.7.5'
|
||||||
gem 'cld3', '~> 3.1'
|
gem 'cld3', '~> 3.1'
|
||||||
gem 'devise', '~> 4.2'
|
gem 'devise', '~> 4.2'
|
||||||
gem 'devise-two-factor', '~> 3.0'
|
gem 'devise-two-factor', '~> 3.0'
|
||||||
|
@ -68,6 +68,9 @@ gem 'tzinfo-data', '~> 1.2017'
|
||||||
gem 'webpacker', '~> 2.0'
|
gem 'webpacker', '~> 2.0'
|
||||||
gem 'webpush'
|
gem 'webpush'
|
||||||
|
|
||||||
|
gem 'json-ld-preloaded', '~> 2.2.1'
|
||||||
|
gem 'rdf-normalize', '~> 0.3.1'
|
||||||
|
|
||||||
group :development, :test do
|
group :development, :test do
|
||||||
gem 'fabrication', '~> 2.16'
|
gem 'fabrication', '~> 2.16'
|
||||||
gem 'fuubar', '~> 2.2'
|
gem 'fuubar', '~> 2.2'
|
||||||
|
|
34
Gemfile.lock
34
Gemfile.lock
|
@ -44,8 +44,8 @@ GEM
|
||||||
i18n (~> 0.7)
|
i18n (~> 0.7)
|
||||||
minitest (~> 5.1)
|
minitest (~> 5.1)
|
||||||
tzinfo (~> 1.1)
|
tzinfo (~> 1.1)
|
||||||
addressable (2.5.1)
|
addressable (2.5.2)
|
||||||
public_suffix (~> 2.0, >= 2.0.2)
|
public_suffix (>= 2.0.2, < 4.0)
|
||||||
airbrussh (1.3.0)
|
airbrussh (1.3.0)
|
||||||
sshkit (>= 1.6.1, != 1.7.0)
|
sshkit (>= 1.6.1, != 1.7.0)
|
||||||
annotate (2.7.2)
|
annotate (2.7.2)
|
||||||
|
@ -74,13 +74,13 @@ GEM
|
||||||
debug_inspector (>= 0.0.1)
|
debug_inspector (>= 0.0.1)
|
||||||
bootsnap (1.1.2)
|
bootsnap (1.1.2)
|
||||||
msgpack (~> 1.0)
|
msgpack (~> 1.0)
|
||||||
brakeman (3.6.2)
|
brakeman (3.7.2)
|
||||||
browser (2.4.0)
|
browser (2.4.0)
|
||||||
builder (3.2.3)
|
builder (3.2.3)
|
||||||
bullet (5.5.1)
|
bullet (5.5.1)
|
||||||
activesupport (>= 3.0.0)
|
activesupport (>= 3.0.0)
|
||||||
uniform_notifier (~> 1.10.0)
|
uniform_notifier (~> 1.10.0)
|
||||||
bundler-audit (0.5.0)
|
bundler-audit (0.6.0)
|
||||||
bundler (~> 1.2)
|
bundler (~> 1.2)
|
||||||
thor (~> 0.18)
|
thor (~> 0.18)
|
||||||
capistrano (3.8.2)
|
capistrano (3.8.2)
|
||||||
|
@ -108,7 +108,7 @@ GEM
|
||||||
xpath (~> 2.0)
|
xpath (~> 2.0)
|
||||||
case_transform (0.2)
|
case_transform (0.2)
|
||||||
activesupport
|
activesupport
|
||||||
charlock_holmes (0.7.3)
|
charlock_holmes (0.7.5)
|
||||||
chunky_png (1.3.8)
|
chunky_png (1.3.8)
|
||||||
cld3 (3.1.3)
|
cld3 (3.1.3)
|
||||||
ffi (>= 1.1.0, < 1.10.0)
|
ffi (>= 1.1.0, < 1.10.0)
|
||||||
|
@ -179,6 +179,8 @@ GEM
|
||||||
activesupport (>= 4.0.1)
|
activesupport (>= 4.0.1)
|
||||||
hamlit (>= 1.2.0)
|
hamlit (>= 1.2.0)
|
||||||
railties (>= 4.0.1)
|
railties (>= 4.0.1)
|
||||||
|
hamster (3.0.0)
|
||||||
|
concurrent-ruby (~> 1.0)
|
||||||
hashdiff (0.3.5)
|
hashdiff (0.3.5)
|
||||||
highline (1.7.8)
|
highline (1.7.8)
|
||||||
hiredis (0.6.1)
|
hiredis (0.6.1)
|
||||||
|
@ -211,6 +213,13 @@ GEM
|
||||||
idn-ruby (0.1.0)
|
idn-ruby (0.1.0)
|
||||||
jmespath (1.3.1)
|
jmespath (1.3.1)
|
||||||
json (2.1.0)
|
json (2.1.0)
|
||||||
|
json-ld (2.1.5)
|
||||||
|
multi_json (~> 1.12)
|
||||||
|
rdf (~> 2.2)
|
||||||
|
json-ld-preloaded (2.2.1)
|
||||||
|
json-ld (~> 2.1, >= 2.1.5)
|
||||||
|
multi_json (~> 1.11)
|
||||||
|
rdf (~> 2.2)
|
||||||
jsonapi-renderer (0.1.3)
|
jsonapi-renderer (0.1.3)
|
||||||
jwt (1.5.6)
|
jwt (1.5.6)
|
||||||
kaminari (1.0.1)
|
kaminari (1.0.1)
|
||||||
|
@ -298,7 +307,7 @@ GEM
|
||||||
slop (~> 3.4)
|
slop (~> 3.4)
|
||||||
pry-rails (0.3.6)
|
pry-rails (0.3.6)
|
||||||
pry (>= 0.10.4)
|
pry (>= 0.10.4)
|
||||||
public_suffix (2.0.5)
|
public_suffix (3.0.0)
|
||||||
puma (3.9.1)
|
puma (3.9.1)
|
||||||
pundit (1.1.0)
|
pundit (1.1.0)
|
||||||
activesupport (>= 3.0.0)
|
activesupport (>= 3.0.0)
|
||||||
|
@ -348,6 +357,11 @@ GEM
|
||||||
rainbow (2.2.2)
|
rainbow (2.2.2)
|
||||||
rake
|
rake
|
||||||
rake (12.0.0)
|
rake (12.0.0)
|
||||||
|
rdf (2.2.8)
|
||||||
|
hamster (~> 3.0)
|
||||||
|
link_header (~> 0.0, >= 0.0.8)
|
||||||
|
rdf-normalize (0.3.2)
|
||||||
|
rdf (~> 2.0)
|
||||||
redis (3.3.3)
|
redis (3.3.3)
|
||||||
redis-actionpack (5.0.1)
|
redis-actionpack (5.0.1)
|
||||||
actionpack (>= 4.0, < 6)
|
actionpack (>= 4.0, < 6)
|
||||||
|
@ -454,7 +468,7 @@ GEM
|
||||||
temple (0.8.0)
|
temple (0.8.0)
|
||||||
terminal-table (1.8.0)
|
terminal-table (1.8.0)
|
||||||
unicode-display_width (~> 1.1, >= 1.1.1)
|
unicode-display_width (~> 1.1, >= 1.1.1)
|
||||||
thor (0.19.4)
|
thor (0.20.0)
|
||||||
thread (0.2.2)
|
thread (0.2.2)
|
||||||
thread_safe (0.3.6)
|
thread_safe (0.3.6)
|
||||||
tilt (2.0.8)
|
tilt (2.0.8)
|
||||||
|
@ -511,7 +525,7 @@ DEPENDENCIES
|
||||||
capistrano-rbenv (~> 2.1)
|
capistrano-rbenv (~> 2.1)
|
||||||
capistrano-yarn (~> 2.0)
|
capistrano-yarn (~> 2.0)
|
||||||
capybara (~> 2.14)
|
capybara (~> 2.14)
|
||||||
charlock_holmes (~> 0.7.3)
|
charlock_holmes (~> 0.7.5)
|
||||||
cld3 (~> 3.1)
|
cld3 (~> 3.1)
|
||||||
climate_control (~> 0.2)
|
climate_control (~> 0.2)
|
||||||
devise (~> 4.2)
|
devise (~> 4.2)
|
||||||
|
@ -531,6 +545,7 @@ DEPENDENCIES
|
||||||
httplog (~> 0.99)
|
httplog (~> 0.99)
|
||||||
i18n-tasks (~> 0.9)
|
i18n-tasks (~> 0.9)
|
||||||
idn-ruby
|
idn-ruby
|
||||||
|
json-ld-preloaded (~> 2.2.1)
|
||||||
kaminari (~> 1.0)
|
kaminari (~> 1.0)
|
||||||
letter_opener (~> 1.4)
|
letter_opener (~> 1.4)
|
||||||
letter_opener_web (~> 1.3)
|
letter_opener_web (~> 1.3)
|
||||||
|
@ -560,6 +575,7 @@ DEPENDENCIES
|
||||||
rails-controller-testing (~> 1.0)
|
rails-controller-testing (~> 1.0)
|
||||||
rails-i18n (~> 5.0)
|
rails-i18n (~> 5.0)
|
||||||
rails-settings-cached (~> 0.6)
|
rails-settings-cached (~> 0.6)
|
||||||
|
rdf-normalize (~> 0.3.1)
|
||||||
redis (~> 3.3)
|
redis (~> 3.3)
|
||||||
redis-namespace (~> 1.5)
|
redis-namespace (~> 1.5)
|
||||||
redis-rails (~> 5.0)
|
redis-rails (~> 5.0)
|
||||||
|
@ -590,4 +606,4 @@ RUBY VERSION
|
||||||
ruby 2.4.1p111
|
ruby 2.4.1p111
|
||||||
|
|
||||||
BUNDLED WITH
|
BUNDLED WITH
|
||||||
1.15.3
|
1.15.4
|
||||||
|
|
|
@ -7,8 +7,17 @@ class AccountsController < ApplicationController
|
||||||
def show
|
def show
|
||||||
respond_to do |format|
|
respond_to do |format|
|
||||||
format.html do
|
format.html do
|
||||||
@statuses = @account.statuses.permitted_for(@account, current_account).paginate_by_max_id(20, params[:max_id], params[:since_id])
|
@pinned_statuses = []
|
||||||
|
|
||||||
|
if current_account && @account.blocking?(current_account)
|
||||||
|
@statuses = []
|
||||||
|
return
|
||||||
|
end
|
||||||
|
|
||||||
|
@pinned_statuses = cache_collection(@account.pinned_statuses, Status) unless media_requested?
|
||||||
|
@statuses = filtered_statuses.paginate_by_max_id(20, params[:max_id], params[:since_id])
|
||||||
@statuses = cache_collection(@statuses, Status)
|
@statuses = cache_collection(@statuses, Status)
|
||||||
|
@next_url = next_url unless @statuses.empty?
|
||||||
end
|
end
|
||||||
|
|
||||||
format.atom do
|
format.atom do
|
||||||
|
@ -17,14 +26,55 @@ class AccountsController < ApplicationController
|
||||||
end
|
end
|
||||||
|
|
||||||
format.json do
|
format.json do
|
||||||
render json: @account, serializer: ActivityPub::ActorSerializer, adapter: ActivityPub::Adapter
|
render json: @account, serializer: ActivityPub::ActorSerializer, adapter: ActivityPub::Adapter, content_type: 'application/activity+json'
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
private
|
private
|
||||||
|
|
||||||
|
def filtered_statuses
|
||||||
|
default_statuses.tap do |statuses|
|
||||||
|
statuses.merge!(only_media_scope) if media_requested?
|
||||||
|
statuses.merge!(no_replies_scope) unless replies_requested?
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
def default_statuses
|
||||||
|
@account.statuses.where(visibility: [:public, :unlisted])
|
||||||
|
end
|
||||||
|
|
||||||
|
def only_media_scope
|
||||||
|
Status.where(id: account_media_status_ids)
|
||||||
|
end
|
||||||
|
|
||||||
|
def account_media_status_ids
|
||||||
|
@account.media_attachments.attached.reorder(nil).select(:status_id).distinct
|
||||||
|
end
|
||||||
|
|
||||||
|
def no_replies_scope
|
||||||
|
Status.without_replies
|
||||||
|
end
|
||||||
|
|
||||||
def set_account
|
def set_account
|
||||||
@account = Account.find_local!(params[:username])
|
@account = Account.find_local!(params[:username])
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def next_url
|
||||||
|
if media_requested?
|
||||||
|
short_account_media_url(@account, max_id: @statuses.last.id)
|
||||||
|
elsif replies_requested?
|
||||||
|
short_account_with_replies_url(@account, max_id: @statuses.last.id)
|
||||||
|
else
|
||||||
|
short_account_url(@account, max_id: @statuses.last.id)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
def media_requested?
|
||||||
|
request.path.ends_with?('/media')
|
||||||
|
end
|
||||||
|
|
||||||
|
def replies_requested?
|
||||||
|
request.path.ends_with?('/with_replies')
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
|
@ -0,0 +1,36 @@
|
||||||
|
# frozen_string_literal: true
|
||||||
|
|
||||||
|
class ActivityPub::InboxesController < Api::BaseController
|
||||||
|
include SignatureVerification
|
||||||
|
|
||||||
|
before_action :set_account
|
||||||
|
|
||||||
|
def create
|
||||||
|
if signed_request_account
|
||||||
|
upgrade_account
|
||||||
|
process_payload
|
||||||
|
head 201
|
||||||
|
else
|
||||||
|
head 202
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
private
|
||||||
|
|
||||||
|
def set_account
|
||||||
|
@account = Account.find_local!(params[:account_username]) if params[:account_username]
|
||||||
|
end
|
||||||
|
|
||||||
|
def body
|
||||||
|
@body ||= request.body.read
|
||||||
|
end
|
||||||
|
|
||||||
|
def upgrade_account
|
||||||
|
return unless signed_request_account.subscribed?
|
||||||
|
Pubsubhubbub::UnsubscribeWorker.perform_async(signed_request_account.id)
|
||||||
|
end
|
||||||
|
|
||||||
|
def process_payload
|
||||||
|
ActivityPub::ProcessingWorker.perform_async(signed_request_account.id, body.force_encoding('UTF-8'))
|
||||||
|
end
|
||||||
|
end
|
|
@ -7,7 +7,7 @@ class ActivityPub::OutboxesController < Api::BaseController
|
||||||
@statuses = @account.statuses.permitted_for(@account, current_account).paginate_by_max_id(20, params[:max_id], params[:since_id])
|
@statuses = @account.statuses.permitted_for(@account, current_account).paginate_by_max_id(20, params[:max_id], params[:since_id])
|
||||||
@statuses = cache_collection(@statuses, Status)
|
@statuses = cache_collection(@statuses, Status)
|
||||||
|
|
||||||
render json: outbox_presenter, serializer: ActivityPub::CollectionSerializer, adapter: ActivityPub::Adapter
|
render json: outbox_presenter, serializer: ActivityPub::CollectionSerializer, adapter: ActivityPub::Adapter, content_type: 'application/activity+json'
|
||||||
end
|
end
|
||||||
|
|
||||||
private
|
private
|
||||||
|
|
|
@ -17,7 +17,7 @@ module Admin
|
||||||
end
|
end
|
||||||
|
|
||||||
def unsubscribe
|
def unsubscribe
|
||||||
UnsubscribeService.new.call(@account)
|
Pubsubhubbub::UnsubscribeWorker.perform_async(@account.id)
|
||||||
redirect_to admin_account_path(@account.id)
|
redirect_to admin_account_path(@account.id)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
|
@ -9,7 +9,7 @@ module Admin
|
||||||
before_action :set_account
|
before_action :set_account
|
||||||
before_action :set_status, only: [:update, :destroy]
|
before_action :set_status, only: [:update, :destroy]
|
||||||
|
|
||||||
PAR_PAGE = 20
|
PER_PAGE = 20
|
||||||
|
|
||||||
def index
|
def index
|
||||||
@statuses = @account.statuses
|
@statuses = @account.statuses
|
||||||
|
@ -17,7 +17,7 @@ module Admin
|
||||||
account_media_status_ids = @account.media_attachments.attached.reorder(nil).select(:status_id).distinct
|
account_media_status_ids = @account.media_attachments.attached.reorder(nil).select(:status_id).distinct
|
||||||
@statuses.merge!(Status.where(id: account_media_status_ids))
|
@statuses.merge!(Status.where(id: account_media_status_ids))
|
||||||
end
|
end
|
||||||
@statuses = @statuses.preload(:media_attachments, :mentions).page(params[:page]).per(PAR_PAGE)
|
@statuses = @statuses.preload(:media_attachments, :mentions).page(params[:page]).per(PER_PAGE)
|
||||||
|
|
||||||
@form = Form::StatusBatch.new
|
@form = Form::StatusBatch.new
|
||||||
end
|
end
|
||||||
|
|
|
@ -43,7 +43,7 @@ class Api::BaseController < ApplicationController
|
||||||
links = []
|
links = []
|
||||||
links << [next_path, [%w(rel next)]] if next_path
|
links << [next_path, [%w(rel next)]] if next_path
|
||||||
links << [prev_path, [%w(rel prev)]] if prev_path
|
links << [prev_path, [%w(rel prev)]] if prev_path
|
||||||
response.headers['Link'] = LinkHeader.new(links)
|
response.headers['Link'] = LinkHeader.new(links) unless links.empty?
|
||||||
end
|
end
|
||||||
|
|
||||||
def limit_param(default_limit)
|
def limit_param(default_limit)
|
||||||
|
@ -62,11 +62,12 @@ class Api::BaseController < ApplicationController
|
||||||
end
|
end
|
||||||
|
|
||||||
def require_user!
|
def require_user!
|
||||||
current_resource_owner
|
if current_user
|
||||||
set_user_activity
|
set_user_activity
|
||||||
rescue ActiveRecord::RecordNotFound
|
else
|
||||||
render json: { error: 'This method requires an authenticated user' }, status: 422
|
render json: { error: 'This method requires an authenticated user' }, status: 422
|
||||||
end
|
end
|
||||||
|
end
|
||||||
|
|
||||||
def render_empty
|
def render_empty
|
||||||
render json: {}, status: 200
|
render json: {}, status: 200
|
||||||
|
|
|
@ -4,14 +4,14 @@ class Api::OEmbedController < Api::BaseController
|
||||||
respond_to :json
|
respond_to :json
|
||||||
|
|
||||||
def show
|
def show
|
||||||
@stream_entry = find_stream_entry.stream_entry
|
@status = status_finder.status
|
||||||
render json: @stream_entry, serializer: OEmbedSerializer, width: maxwidth_or_default, height: maxheight_or_default
|
render json: @status, serializer: OEmbedSerializer, width: maxwidth_or_default, height: maxheight_or_default
|
||||||
end
|
end
|
||||||
|
|
||||||
private
|
private
|
||||||
|
|
||||||
def find_stream_entry
|
def status_finder
|
||||||
StreamEntryFinder.new(params[:url])
|
StatusFinder.new(params[:url])
|
||||||
end
|
end
|
||||||
|
|
||||||
def maxwidth_or_default
|
def maxwidth_or_default
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
# frozen_string_literal: true
|
# frozen_string_literal: true
|
||||||
|
|
||||||
class Api::V1::Accounts::CredentialsController < Api::BaseController
|
class Api::V1::Accounts::CredentialsController < Api::BaseController
|
||||||
|
before_action -> { doorkeeper_authorize! :read }, except: [:update]
|
||||||
before_action -> { doorkeeper_authorize! :write }, only: [:update]
|
before_action -> { doorkeeper_authorize! :write }, only: [:update]
|
||||||
before_action :require_user!
|
before_action :require_user!
|
||||||
|
|
||||||
|
@ -10,8 +11,9 @@ class Api::V1::Accounts::CredentialsController < Api::BaseController
|
||||||
end
|
end
|
||||||
|
|
||||||
def update
|
def update
|
||||||
current_account.update!(account_params)
|
|
||||||
@account = current_account
|
@account = current_account
|
||||||
|
UpdateAccountService.new.call(@account, account_params, raise_error: true)
|
||||||
|
ActivityPub::UpdateDistributionWorker.perform_async(@account.id)
|
||||||
render json: @account, serializer: REST::CredentialAccountSerializer
|
render json: @account, serializer: REST::CredentialAccountSerializer
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
|
@ -29,6 +29,7 @@ class Api::V1::Accounts::StatusesController < Api::BaseController
|
||||||
def account_statuses
|
def account_statuses
|
||||||
default_statuses.tap do |statuses|
|
default_statuses.tap do |statuses|
|
||||||
statuses.merge!(only_media_scope) if params[:only_media]
|
statuses.merge!(only_media_scope) if params[:only_media]
|
||||||
|
statuses.merge!(pinned_scope) if params[:pinned]
|
||||||
statuses.merge!(no_replies_scope) if params[:exclude_replies]
|
statuses.merge!(no_replies_scope) if params[:exclude_replies]
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
@ -53,6 +54,10 @@ class Api::V1::Accounts::StatusesController < Api::BaseController
|
||||||
@account.media_attachments.attached.reorder(nil).select(:status_id).distinct
|
@account.media_attachments.attached.reorder(nil).select(:status_id).distinct
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def pinned_scope
|
||||||
|
@account.pinned_statuses
|
||||||
|
end
|
||||||
|
|
||||||
def no_replies_scope
|
def no_replies_scope
|
||||||
Status.without_replies
|
Status.without_replies
|
||||||
end
|
end
|
||||||
|
|
|
@ -0,0 +1,28 @@
|
||||||
|
# frozen_string_literal: true
|
||||||
|
|
||||||
|
class Api::V1::Statuses::PinsController < Api::BaseController
|
||||||
|
include Authorization
|
||||||
|
|
||||||
|
before_action -> { doorkeeper_authorize! :write }
|
||||||
|
before_action :require_user!
|
||||||
|
before_action :set_status
|
||||||
|
|
||||||
|
respond_to :json
|
||||||
|
|
||||||
|
def create
|
||||||
|
StatusPin.create!(account: current_account, status: @status)
|
||||||
|
render json: @status, serializer: REST::StatusSerializer
|
||||||
|
end
|
||||||
|
|
||||||
|
def destroy
|
||||||
|
pin = StatusPin.find_by(account: current_account, status: @status)
|
||||||
|
pin&.destroy!
|
||||||
|
render json: @status, serializer: REST::StatusSerializer
|
||||||
|
end
|
||||||
|
|
||||||
|
private
|
||||||
|
|
||||||
|
def set_status
|
||||||
|
@status = Status.find(params[:status_id])
|
||||||
|
end
|
||||||
|
end
|
|
@ -29,7 +29,7 @@ class Api::V1::StatusesController < Api::BaseController
|
||||||
end
|
end
|
||||||
|
|
||||||
def card
|
def card
|
||||||
@card = PreviewCard.find_by(status: @status)
|
@card = @status.preview_cards.first
|
||||||
|
|
||||||
if @card.nil?
|
if @card.nil?
|
||||||
render_empty
|
render_empty
|
||||||
|
|
|
@ -0,0 +1,17 @@
|
||||||
|
# frozen_string_literal: true
|
||||||
|
|
||||||
|
class Api::Web::EmbedsController < Api::BaseController
|
||||||
|
respond_to :json
|
||||||
|
|
||||||
|
before_action :require_user!
|
||||||
|
|
||||||
|
def create
|
||||||
|
status = StatusFinder.new(params[:url]).status
|
||||||
|
render json: status, serializer: OEmbedSerializer, width: 400
|
||||||
|
rescue ActiveRecord::RecordNotFound
|
||||||
|
oembed = OEmbed::Providers.get(params[:url])
|
||||||
|
render json: Oj.dump(oembed.fields)
|
||||||
|
rescue OEmbed::NotFound
|
||||||
|
render json: {}, status: :not_found
|
||||||
|
end
|
||||||
|
end
|
|
@ -23,6 +23,7 @@ module AccountControllerConcern
|
||||||
[
|
[
|
||||||
webfinger_account_link,
|
webfinger_account_link,
|
||||||
atom_account_url_link,
|
atom_account_url_link,
|
||||||
|
actor_url_link,
|
||||||
]
|
]
|
||||||
)
|
)
|
||||||
end
|
end
|
||||||
|
@ -41,6 +42,13 @@ module AccountControllerConcern
|
||||||
]
|
]
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def actor_url_link
|
||||||
|
[
|
||||||
|
ActivityPub::TagManager.instance.uri_for(@account),
|
||||||
|
[%w(rel alternate), %w(type application/activity+json)],
|
||||||
|
]
|
||||||
|
end
|
||||||
|
|
||||||
def webfinger_account_url
|
def webfinger_account_url
|
||||||
webfinger_url(resource: @account.to_webfinger_s)
|
webfinger_url(resource: @account.to_webfinger_s)
|
||||||
end
|
end
|
||||||
|
|
|
@ -31,7 +31,7 @@ module SignatureVerification
|
||||||
return
|
return
|
||||||
end
|
end
|
||||||
|
|
||||||
account = ResolveRemoteAccountService.new.call(signature_params['keyId'].gsub(/\Aacct:/, ''))
|
account = account_from_key_id(signature_params['keyId'])
|
||||||
|
|
||||||
if account.nil?
|
if account.nil?
|
||||||
@signed_request_account = nil
|
@signed_request_account = nil
|
||||||
|
@ -49,6 +49,10 @@ module SignatureVerification
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def request_body
|
||||||
|
@request_body ||= request.raw_post
|
||||||
|
end
|
||||||
|
|
||||||
private
|
private
|
||||||
|
|
||||||
def build_signed_string(signed_headers)
|
def build_signed_string(signed_headers)
|
||||||
|
@ -57,6 +61,8 @@ module SignatureVerification
|
||||||
signed_headers.split(' ').map do |signed_header|
|
signed_headers.split(' ').map do |signed_header|
|
||||||
if signed_header == Request::REQUEST_TARGET
|
if signed_header == Request::REQUEST_TARGET
|
||||||
"#{Request::REQUEST_TARGET}: #{request.method.downcase} #{request.path}"
|
"#{Request::REQUEST_TARGET}: #{request.method.downcase} #{request.path}"
|
||||||
|
elsif signed_header == 'digest'
|
||||||
|
"digest: #{body_digest}"
|
||||||
else
|
else
|
||||||
"#{signed_header}: #{request.headers[to_header_name(signed_header)]}"
|
"#{signed_header}: #{request.headers[to_header_name(signed_header)]}"
|
||||||
end
|
end
|
||||||
|
@ -73,6 +79,10 @@ module SignatureVerification
|
||||||
(Time.now.utc - time_sent).abs <= 30
|
(Time.now.utc - time_sent).abs <= 30
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def body_digest
|
||||||
|
"SHA-256=#{Digest::SHA256.base64digest(request_body)}"
|
||||||
|
end
|
||||||
|
|
||||||
def to_header_name(name)
|
def to_header_name(name)
|
||||||
name.split(/-/).map(&:capitalize).join('-')
|
name.split(/-/).map(&:capitalize).join('-')
|
||||||
end
|
end
|
||||||
|
@ -81,7 +91,16 @@ module SignatureVerification
|
||||||
signature_params['keyId'].blank? ||
|
signature_params['keyId'].blank? ||
|
||||||
signature_params['signature'].blank? ||
|
signature_params['signature'].blank? ||
|
||||||
signature_params['algorithm'].blank? ||
|
signature_params['algorithm'].blank? ||
|
||||||
signature_params['algorithm'] != 'rsa-sha256' ||
|
signature_params['algorithm'] != 'rsa-sha256'
|
||||||
!signature_params['keyId'].start_with?('acct:')
|
end
|
||||||
|
|
||||||
|
def account_from_key_id(key_id)
|
||||||
|
if key_id.start_with?('acct:')
|
||||||
|
ResolveRemoteAccountService.new.call(key_id.gsub(/\Aacct:/, ''))
|
||||||
|
elsif !ActivityPub::TagManager.instance.local_uri?(key_id)
|
||||||
|
account = ActivityPub::TagManager.instance.uri_to_resource(key_id, Account)
|
||||||
|
account ||= ActivityPub::FetchRemoteKeyService.new.call(key_id)
|
||||||
|
account
|
||||||
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
|
@ -10,7 +10,7 @@ class FollowerAccountsController < ApplicationController
|
||||||
format.html
|
format.html
|
||||||
|
|
||||||
format.json do
|
format.json do
|
||||||
render json: collection_presenter, serializer: ActivityPub::CollectionSerializer, adapter: ActivityPub::Adapter
|
render json: collection_presenter, serializer: ActivityPub::CollectionSerializer, adapter: ActivityPub::Adapter, content_type: 'application/activity+json'
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
|
@ -10,7 +10,7 @@ class FollowingAccountsController < ApplicationController
|
||||||
format.html
|
format.html
|
||||||
|
|
||||||
format.json do
|
format.json do
|
||||||
render json: collection_presenter, serializer: ActivityPub::CollectionSerializer, adapter: ActivityPub::Adapter
|
render json: collection_presenter, serializer: ActivityPub::CollectionSerializer, adapter: ActivityPub::Adapter, content_type: 'application/activity+json'
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
|
@ -0,0 +1,18 @@
|
||||||
|
# frozen_string_literal: true
|
||||||
|
|
||||||
|
class IntentsController < ApplicationController
|
||||||
|
def show
|
||||||
|
uri = Addressable::URI.parse(params[:uri])
|
||||||
|
|
||||||
|
if uri.scheme == 'web+mastodon'
|
||||||
|
case uri.host
|
||||||
|
when 'follow'
|
||||||
|
return redirect_to authorize_follow_path(acct: uri.query_values['uri'].gsub(/\Aacct:/, ''))
|
||||||
|
when 'share'
|
||||||
|
return redirect_to share_path(text: uri.query_values['text'])
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
not_found
|
||||||
|
end
|
||||||
|
end
|
|
@ -0,0 +1,72 @@
|
||||||
|
# frozen_string_literal: true
|
||||||
|
|
||||||
|
class Settings::ApplicationsController < ApplicationController
|
||||||
|
layout 'admin'
|
||||||
|
|
||||||
|
before_action :authenticate_user!
|
||||||
|
before_action :set_application, only: [:show, :update, :destroy, :regenerate]
|
||||||
|
before_action :prepare_scopes, only: [:create, :update]
|
||||||
|
|
||||||
|
def index
|
||||||
|
@applications = current_user.applications.page(params[:page])
|
||||||
|
end
|
||||||
|
|
||||||
|
def new
|
||||||
|
@application = Doorkeeper::Application.new(
|
||||||
|
redirect_uri: Doorkeeper.configuration.native_redirect_uri,
|
||||||
|
scopes: 'read write follow'
|
||||||
|
)
|
||||||
|
end
|
||||||
|
|
||||||
|
def show; end
|
||||||
|
|
||||||
|
def create
|
||||||
|
@application = current_user.applications.build(application_params)
|
||||||
|
|
||||||
|
if @application.save
|
||||||
|
redirect_to settings_applications_path, notice: I18n.t('applications.created')
|
||||||
|
else
|
||||||
|
render :new
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
def update
|
||||||
|
if @application.update(application_params)
|
||||||
|
redirect_to settings_applications_path, notice: I18n.t('generic.changes_saved_msg')
|
||||||
|
else
|
||||||
|
render :show
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
def destroy
|
||||||
|
@application.destroy
|
||||||
|
redirect_to settings_applications_path, notice: I18n.t('applications.destroyed')
|
||||||
|
end
|
||||||
|
|
||||||
|
def regenerate
|
||||||
|
@access_token = current_user.token_for_app(@application)
|
||||||
|
@access_token.destroy
|
||||||
|
|
||||||
|
redirect_to settings_application_path(@application), notice: I18n.t('applications.token_regenerated')
|
||||||
|
end
|
||||||
|
|
||||||
|
private
|
||||||
|
|
||||||
|
def set_application
|
||||||
|
@application = current_user.applications.find(params[:id])
|
||||||
|
end
|
||||||
|
|
||||||
|
def application_params
|
||||||
|
params.require(:doorkeeper_application).permit(
|
||||||
|
:name,
|
||||||
|
:redirect_uri,
|
||||||
|
:scopes,
|
||||||
|
:website
|
||||||
|
)
|
||||||
|
end
|
||||||
|
|
||||||
|
def prepare_scopes
|
||||||
|
scopes = params.fetch(:doorkeeper_application, {}).fetch(:scopes, nil)
|
||||||
|
params[:doorkeeper_application][:scopes] = scopes.join(' ') if scopes.is_a? Array
|
||||||
|
end
|
||||||
|
end
|
|
@ -14,7 +14,8 @@ class Settings::ProfilesController < ApplicationController
|
||||||
def show; end
|
def show; end
|
||||||
|
|
||||||
def update
|
def update
|
||||||
if @account.update(account_params)
|
if UpdateAccountService.new.call(@account, account_params)
|
||||||
|
ActivityPub::UpdateDistributionWorker.perform_async(@account.id)
|
||||||
redirect_to settings_profile_path, notice: I18n.t('generic.changes_saved_msg')
|
redirect_to settings_profile_path, notice: I18n.t('generic.changes_saved_msg')
|
||||||
else
|
else
|
||||||
render :show
|
render :show
|
||||||
|
|
|
@ -0,0 +1,30 @@
|
||||||
|
# frozen_string_literal: true
|
||||||
|
|
||||||
|
class SharesController < ApplicationController
|
||||||
|
layout 'modal'
|
||||||
|
|
||||||
|
before_action :authenticate_user!
|
||||||
|
before_action :set_body_classes
|
||||||
|
|
||||||
|
def show
|
||||||
|
serializable_resource = ActiveModelSerializers::SerializableResource.new(InitialStatePresenter.new(initial_state_params), serializer: InitialStateSerializer)
|
||||||
|
@initial_state_json = serializable_resource.to_json
|
||||||
|
end
|
||||||
|
|
||||||
|
private
|
||||||
|
|
||||||
|
def initial_state_params
|
||||||
|
{
|
||||||
|
settings: Web::Setting.find_by(user: current_user)&.data || {},
|
||||||
|
push_subscription: current_account.user.web_push_subscription(current_session),
|
||||||
|
current_account: current_account,
|
||||||
|
token: current_session.token,
|
||||||
|
admin: Account.find_local(Setting.site_contact_username),
|
||||||
|
text: params[:text],
|
||||||
|
}
|
||||||
|
end
|
||||||
|
|
||||||
|
def set_body_classes
|
||||||
|
@body_classes = 'compose-standalone'
|
||||||
|
end
|
||||||
|
end
|
|
@ -9,6 +9,7 @@ class StatusesController < ApplicationController
|
||||||
before_action :set_status
|
before_action :set_status
|
||||||
before_action :set_link_headers
|
before_action :set_link_headers
|
||||||
before_action :check_account_suspension
|
before_action :check_account_suspension
|
||||||
|
before_action :redirect_to_original, only: [:show]
|
||||||
|
|
||||||
def show
|
def show
|
||||||
respond_to do |format|
|
respond_to do |format|
|
||||||
|
@ -20,13 +21,18 @@ class StatusesController < ApplicationController
|
||||||
end
|
end
|
||||||
|
|
||||||
format.json do
|
format.json do
|
||||||
render json: @status, serializer: ActivityPub::NoteSerializer, adapter: ActivityPub::Adapter
|
render json: @status, serializer: ActivityPub::NoteSerializer, adapter: ActivityPub::Adapter, content_type: 'application/activity+json'
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
def activity
|
def activity
|
||||||
render json: @status, serializer: ActivityPub::ActivitySerializer, adapter: ActivityPub::Adapter
|
render json: @status, serializer: ActivityPub::ActivitySerializer, adapter: ActivityPub::Adapter, content_type: 'application/activity+json'
|
||||||
|
end
|
||||||
|
|
||||||
|
def embed
|
||||||
|
response.headers['X-Frame-Options'] = 'ALLOWALL'
|
||||||
|
render 'stream_entries/embed', layout: 'embedded'
|
||||||
end
|
end
|
||||||
|
|
||||||
private
|
private
|
||||||
|
@ -36,7 +42,12 @@ class StatusesController < ApplicationController
|
||||||
end
|
end
|
||||||
|
|
||||||
def set_link_headers
|
def set_link_headers
|
||||||
response.headers['Link'] = LinkHeader.new([[account_stream_entry_url(@account, @status.stream_entry, format: 'atom'), [%w(rel alternate), %w(type application/atom+xml)]]])
|
response.headers['Link'] = LinkHeader.new(
|
||||||
|
[
|
||||||
|
[account_stream_entry_url(@account, @status.stream_entry, format: 'atom'), [%w(rel alternate), %w(type application/atom+xml)]],
|
||||||
|
[ActivityPub::TagManager.instance.uri_for(@status), [%w(rel alternate), %w(type application/activity+json)]],
|
||||||
|
]
|
||||||
|
)
|
||||||
end
|
end
|
||||||
|
|
||||||
def set_status
|
def set_status
|
||||||
|
@ -53,4 +64,8 @@ class StatusesController < ApplicationController
|
||||||
def check_account_suspension
|
def check_account_suspension
|
||||||
gone if @account.suspended?
|
gone if @account.suspended?
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def redirect_to_original
|
||||||
|
redirect_to ::TagManager.instance.url_for(@status.reblog) if @status.reblog?
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
|
@ -25,10 +25,7 @@ class StreamEntriesController < ApplicationController
|
||||||
end
|
end
|
||||||
|
|
||||||
def embed
|
def embed
|
||||||
response.headers['X-Frame-Options'] = 'ALLOWALL'
|
redirect_to embed_short_account_status_url(@account, @stream_entry.activity), status: 301
|
||||||
return gone if @stream_entry.activity.nil?
|
|
||||||
|
|
||||||
render layout: 'embedded'
|
|
||||||
end
|
end
|
||||||
|
|
||||||
private
|
private
|
||||||
|
@ -38,7 +35,12 @@ class StreamEntriesController < ApplicationController
|
||||||
end
|
end
|
||||||
|
|
||||||
def set_link_headers
|
def set_link_headers
|
||||||
response.headers['Link'] = LinkHeader.new([[account_stream_entry_url(@account, @stream_entry, format: 'atom'), [%w(rel alternate), %w(type application/atom+xml)]]])
|
response.headers['Link'] = LinkHeader.new(
|
||||||
|
[
|
||||||
|
[account_stream_entry_url(@account, @stream_entry, format: 'atom'), [%w(rel alternate), %w(type application/atom+xml)]],
|
||||||
|
[ActivityPub::TagManager.instance.uri_for(@stream_entry.activity), [%w(rel alternate), %w(type application/activity+json)]],
|
||||||
|
]
|
||||||
|
)
|
||||||
end
|
end
|
||||||
|
|
||||||
def set_stream_entry
|
def set_stream_entry
|
||||||
|
|
|
@ -12,7 +12,7 @@ class TagsController < ApplicationController
|
||||||
format.html
|
format.html
|
||||||
|
|
||||||
format.json do
|
format.json do
|
||||||
render json: collection_presenter, serializer: ActivityPub::CollectionSerializer, adapter: ActivityPub::Adapter
|
render json: collection_presenter, serializer: ActivityPub::CollectionSerializer, adapter: ActivityPub::Adapter, content_type: 'application/activity+json'
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
|
@ -5,6 +5,10 @@ module ApplicationHelper
|
||||||
current_page?(path) ? 'active' : ''
|
current_page?(path) ? 'active' : ''
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def active_link_to(label, path, options = {})
|
||||||
|
link_to label, path, options.merge(class: active_nav_class(path))
|
||||||
|
end
|
||||||
|
|
||||||
def show_landing_strip?
|
def show_landing_strip?
|
||||||
!user_signed_in? && !single_user_mode?
|
!user_signed_in? && !single_user_mode?
|
||||||
end
|
end
|
||||||
|
|
|
@ -0,0 +1,52 @@
|
||||||
|
# frozen_string_literal: true
|
||||||
|
|
||||||
|
module JsonLdHelper
|
||||||
|
def equals_or_includes?(haystack, needle)
|
||||||
|
haystack.is_a?(Array) ? haystack.include?(needle) : haystack == needle
|
||||||
|
end
|
||||||
|
|
||||||
|
def first_of_value(value)
|
||||||
|
value.is_a?(Array) ? value.first : value
|
||||||
|
end
|
||||||
|
|
||||||
|
def value_or_id(value)
|
||||||
|
value.is_a?(String) || value.nil? ? value : value['id']
|
||||||
|
end
|
||||||
|
|
||||||
|
def supported_context?(json)
|
||||||
|
!json.nil? && equals_or_includes?(json['@context'], ActivityPub::TagManager::CONTEXT)
|
||||||
|
end
|
||||||
|
|
||||||
|
def canonicalize(json)
|
||||||
|
graph = RDF::Graph.new << JSON::LD::API.toRdf(json)
|
||||||
|
graph.dump(:normalize)
|
||||||
|
end
|
||||||
|
|
||||||
|
def fetch_resource(uri)
|
||||||
|
response = build_request(uri).perform
|
||||||
|
return if response.code != 200
|
||||||
|
body_to_json(response.to_s)
|
||||||
|
end
|
||||||
|
|
||||||
|
def body_to_json(body)
|
||||||
|
body.is_a?(String) ? Oj.load(body, mode: :strict) : body
|
||||||
|
rescue Oj::ParseError
|
||||||
|
nil
|
||||||
|
end
|
||||||
|
|
||||||
|
def merge_context(context, new_context)
|
||||||
|
if context.is_a?(Array)
|
||||||
|
context << new_context
|
||||||
|
else
|
||||||
|
[context, new_context]
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
private
|
||||||
|
|
||||||
|
def build_request(uri)
|
||||||
|
request = Request.new(:get, uri)
|
||||||
|
request.add_headers('Accept' => 'application/activity+json, application/ld+json')
|
||||||
|
request
|
||||||
|
end
|
||||||
|
end
|
|
@ -12,6 +12,8 @@ module RoutingHelper
|
||||||
end
|
end
|
||||||
|
|
||||||
def full_asset_url(source, options = {})
|
def full_asset_url(source, options = {})
|
||||||
Rails.configuration.x.use_s3 ? source : URI.join(root_url, ActionController::Base.helpers.asset_url(source, options)).to_s
|
source = ActionController::Base.helpers.asset_url(source, options) unless Rails.configuration.x.use_s3
|
||||||
|
|
||||||
|
URI.join(root_url, source).to_s
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
# frozen_string_literal: true
|
# frozen_string_literal: true
|
||||||
|
|
||||||
module StreamEntriesHelper
|
module StreamEntriesHelper
|
||||||
EMBEDDED_CONTROLLER = 'stream_entries'
|
EMBEDDED_CONTROLLER = 'statuses'
|
||||||
EMBEDDED_ACTION = 'embed'
|
EMBEDDED_ACTION = 'embed'
|
||||||
|
|
||||||
def display_name(account)
|
def display_name(account)
|
||||||
|
|
|
@ -24,6 +24,14 @@ export const FAVOURITES_FETCH_REQUEST = 'FAVOURITES_FETCH_REQUEST';
|
||||||
export const FAVOURITES_FETCH_SUCCESS = 'FAVOURITES_FETCH_SUCCESS';
|
export const FAVOURITES_FETCH_SUCCESS = 'FAVOURITES_FETCH_SUCCESS';
|
||||||
export const FAVOURITES_FETCH_FAIL = 'FAVOURITES_FETCH_FAIL';
|
export const FAVOURITES_FETCH_FAIL = 'FAVOURITES_FETCH_FAIL';
|
||||||
|
|
||||||
|
export const PIN_REQUEST = 'PIN_REQUEST';
|
||||||
|
export const PIN_SUCCESS = 'PIN_SUCCESS';
|
||||||
|
export const PIN_FAIL = 'PIN_FAIL';
|
||||||
|
|
||||||
|
export const UNPIN_REQUEST = 'UNPIN_REQUEST';
|
||||||
|
export const UNPIN_SUCCESS = 'UNPIN_SUCCESS';
|
||||||
|
export const UNPIN_FAIL = 'UNPIN_FAIL';
|
||||||
|
|
||||||
export function reblog(status) {
|
export function reblog(status) {
|
||||||
return function (dispatch, getState) {
|
return function (dispatch, getState) {
|
||||||
dispatch(reblogRequest(status));
|
dispatch(reblogRequest(status));
|
||||||
|
@ -233,3 +241,73 @@ export function fetchFavouritesFail(id, error) {
|
||||||
error,
|
error,
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export function pin(status) {
|
||||||
|
return (dispatch, getState) => {
|
||||||
|
dispatch(pinRequest(status));
|
||||||
|
|
||||||
|
api(getState).post(`/api/v1/statuses/${status.get('id')}/pin`).then(response => {
|
||||||
|
dispatch(pinSuccess(status, response.data));
|
||||||
|
}).catch(error => {
|
||||||
|
dispatch(pinFail(status, error));
|
||||||
|
});
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
export function pinRequest(status) {
|
||||||
|
return {
|
||||||
|
type: PIN_REQUEST,
|
||||||
|
status,
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
export function pinSuccess(status, response) {
|
||||||
|
return {
|
||||||
|
type: PIN_SUCCESS,
|
||||||
|
status,
|
||||||
|
response,
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
export function pinFail(status, error) {
|
||||||
|
return {
|
||||||
|
type: PIN_FAIL,
|
||||||
|
status,
|
||||||
|
error,
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
export function unpin (status) {
|
||||||
|
return (dispatch, getState) => {
|
||||||
|
dispatch(unpinRequest(status));
|
||||||
|
|
||||||
|
api(getState).post(`/api/v1/statuses/${status.get('id')}/unpin`).then(response => {
|
||||||
|
dispatch(unpinSuccess(status, response.data));
|
||||||
|
}).catch(error => {
|
||||||
|
dispatch(unpinFail(status, error));
|
||||||
|
});
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
export function unpinRequest(status) {
|
||||||
|
return {
|
||||||
|
type: UNPIN_REQUEST,
|
||||||
|
status,
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
export function unpinSuccess(status, response) {
|
||||||
|
return {
|
||||||
|
type: UNPIN_SUCCESS,
|
||||||
|
status,
|
||||||
|
response,
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
export function unpinFail(status, error) {
|
||||||
|
return {
|
||||||
|
type: UNPIN_FAIL,
|
||||||
|
status,
|
||||||
|
error,
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
|
@ -0,0 +1,94 @@
|
||||||
|
import createStream from '../stream';
|
||||||
|
import {
|
||||||
|
updateTimeline,
|
||||||
|
deleteFromTimelines,
|
||||||
|
refreshHomeTimeline,
|
||||||
|
connectTimeline,
|
||||||
|
disconnectTimeline,
|
||||||
|
} from './timelines';
|
||||||
|
import { updateNotifications, refreshNotifications } from './notifications';
|
||||||
|
import { getLocale } from '../locales';
|
||||||
|
|
||||||
|
const { messages } = getLocale();
|
||||||
|
|
||||||
|
export function connectTimelineStream (timelineId, path, pollingRefresh = null) {
|
||||||
|
return (dispatch, getState) => {
|
||||||
|
const streamingAPIBaseURL = getState().getIn(['meta', 'streaming_api_base_url']);
|
||||||
|
const accessToken = getState().getIn(['meta', 'access_token']);
|
||||||
|
const locale = getState().getIn(['meta', 'locale']);
|
||||||
|
let polling = null;
|
||||||
|
|
||||||
|
const setupPolling = () => {
|
||||||
|
polling = setInterval(() => {
|
||||||
|
pollingRefresh(dispatch);
|
||||||
|
}, 20000);
|
||||||
|
};
|
||||||
|
|
||||||
|
const clearPolling = () => {
|
||||||
|
if (polling) {
|
||||||
|
clearInterval(polling);
|
||||||
|
polling = null;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const subscription = createStream(streamingAPIBaseURL, accessToken, path, {
|
||||||
|
|
||||||
|
connected () {
|
||||||
|
if (pollingRefresh) {
|
||||||
|
clearPolling();
|
||||||
|
}
|
||||||
|
dispatch(connectTimeline(timelineId));
|
||||||
|
},
|
||||||
|
|
||||||
|
disconnected () {
|
||||||
|
if (pollingRefresh) {
|
||||||
|
setupPolling();
|
||||||
|
}
|
||||||
|
dispatch(disconnectTimeline(timelineId));
|
||||||
|
},
|
||||||
|
|
||||||
|
received (data) {
|
||||||
|
switch(data.event) {
|
||||||
|
case 'update':
|
||||||
|
dispatch(updateTimeline(timelineId, JSON.parse(data.payload)));
|
||||||
|
break;
|
||||||
|
case 'delete':
|
||||||
|
dispatch(deleteFromTimelines(data.payload));
|
||||||
|
break;
|
||||||
|
case 'notification':
|
||||||
|
dispatch(updateNotifications(JSON.parse(data.payload), messages, locale));
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
reconnected () {
|
||||||
|
if (pollingRefresh) {
|
||||||
|
clearPolling();
|
||||||
|
pollingRefresh(dispatch);
|
||||||
|
}
|
||||||
|
dispatch(connectTimeline(timelineId));
|
||||||
|
},
|
||||||
|
|
||||||
|
});
|
||||||
|
|
||||||
|
const disconnect = () => {
|
||||||
|
if (subscription) {
|
||||||
|
subscription.close();
|
||||||
|
}
|
||||||
|
clearPolling();
|
||||||
|
};
|
||||||
|
|
||||||
|
return disconnect;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
function refreshHomeTimelineAndNotification (dispatch) {
|
||||||
|
dispatch(refreshHomeTimeline());
|
||||||
|
dispatch(refreshNotifications());
|
||||||
|
}
|
||||||
|
|
||||||
|
export const connectUserStream = () => connectTimelineStream('home', 'user', refreshHomeTimelineAndNotification);
|
||||||
|
export const connectCommunityStream = () => connectTimelineStream('community', 'public:local');
|
||||||
|
export const connectMediaStream = () => connectTimelineStream('community', 'public:local');
|
||||||
|
export const connectPublicStream = () => connectTimelineStream('public', 'public');
|
||||||
|
export const connectHashtagStream = (tag) => connectTimelineStream(`hashtag:${tag}`, `hashtag&tag=${tag}`);
|
|
@ -26,6 +26,7 @@ export default class Account extends ImmutablePureComponent {
|
||||||
onBlock: PropTypes.func.isRequired,
|
onBlock: PropTypes.func.isRequired,
|
||||||
onMute: PropTypes.func.isRequired,
|
onMute: PropTypes.func.isRequired,
|
||||||
intl: PropTypes.object.isRequired,
|
intl: PropTypes.object.isRequired,
|
||||||
|
hidden: PropTypes.bool,
|
||||||
};
|
};
|
||||||
|
|
||||||
handleFollow = () => {
|
handleFollow = () => {
|
||||||
|
@ -41,12 +42,21 @@ export default class Account extends ImmutablePureComponent {
|
||||||
}
|
}
|
||||||
|
|
||||||
render () {
|
render () {
|
||||||
const { account, me, intl } = this.props;
|
const { account, me, intl, hidden } = this.props;
|
||||||
|
|
||||||
if (!account) {
|
if (!account) {
|
||||||
return <div />;
|
return <div />;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (hidden) {
|
||||||
|
return (
|
||||||
|
<div>
|
||||||
|
{account.get('display_name')}
|
||||||
|
{account.get('username')}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
let buttons;
|
let buttons;
|
||||||
|
|
||||||
if (account.get('id') !== me && account.get('relationship', null) !== null) {
|
if (account.get('id') !== me && account.get('relationship', null) !== null) {
|
||||||
|
|
|
@ -33,7 +33,7 @@ export default class Column extends React.PureComponent {
|
||||||
}
|
}
|
||||||
|
|
||||||
componentDidMount () {
|
componentDidMount () {
|
||||||
this.node.addEventListener('wheel', this.handleWheel, detectPassiveEvents ? { passive: true } : false);
|
this.node.addEventListener('wheel', this.handleWheel, detectPassiveEvents.hasSupport ? { passive: true } : false);
|
||||||
}
|
}
|
||||||
|
|
||||||
componentWillUnmount () {
|
componentWillUnmount () {
|
||||||
|
|
|
@ -0,0 +1,122 @@
|
||||||
|
import React from 'react';
|
||||||
|
import PropTypes from 'prop-types';
|
||||||
|
import ImmutablePureComponent from 'react-immutable-pure-component';
|
||||||
|
import scheduleIdleTask from '../features/ui/util/schedule_idle_task';
|
||||||
|
import getRectFromEntry from '../features/ui/util/get_rect_from_entry';
|
||||||
|
|
||||||
|
export default class IntersectionObserverArticle extends ImmutablePureComponent {
|
||||||
|
|
||||||
|
static propTypes = {
|
||||||
|
intersectionObserverWrapper: PropTypes.object,
|
||||||
|
id: PropTypes.oneOfType([PropTypes.string, PropTypes.number]),
|
||||||
|
index: PropTypes.oneOfType([PropTypes.string, PropTypes.number]),
|
||||||
|
listLength: PropTypes.oneOfType([PropTypes.string, PropTypes.number]),
|
||||||
|
children: PropTypes.node,
|
||||||
|
};
|
||||||
|
|
||||||
|
state = {
|
||||||
|
isHidden: false, // set to true in requestIdleCallback to trigger un-render
|
||||||
|
}
|
||||||
|
|
||||||
|
shouldComponentUpdate (nextProps, nextState) {
|
||||||
|
if (!nextState.isIntersecting && nextState.isHidden) {
|
||||||
|
// It's only if we're not intersecting (i.e. offscreen) and isHidden is true
|
||||||
|
// that either "isIntersecting" or "isHidden" matter, and then they're
|
||||||
|
// the only things that matter (and updated ARIA attributes).
|
||||||
|
return this.state.isIntersecting || !this.state.isHidden || nextProps.listLength !== this.props.listLength;
|
||||||
|
} else if (nextState.isIntersecting && !this.state.isIntersecting) {
|
||||||
|
// If we're going from a non-intersecting state to an intersecting state,
|
||||||
|
// (i.e. offscreen to onscreen), then we definitely need to re-render
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
// Otherwise, diff based on "updateOnProps" and "updateOnStates"
|
||||||
|
return super.shouldComponentUpdate(nextProps, nextState);
|
||||||
|
}
|
||||||
|
|
||||||
|
componentDidMount () {
|
||||||
|
if (!this.props.intersectionObserverWrapper) {
|
||||||
|
// TODO: enable IntersectionObserver optimization for notification statuses.
|
||||||
|
// These are managed in notifications/index.js rather than status_list.js
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
this.props.intersectionObserverWrapper.observe(
|
||||||
|
this.props.id,
|
||||||
|
this.node,
|
||||||
|
this.handleIntersection
|
||||||
|
);
|
||||||
|
|
||||||
|
this.componentMounted = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
componentWillUnmount () {
|
||||||
|
if (this.props.intersectionObserverWrapper) {
|
||||||
|
this.props.intersectionObserverWrapper.unobserve(this.props.id, this.node);
|
||||||
|
}
|
||||||
|
|
||||||
|
this.componentMounted = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
handleIntersection = (entry) => {
|
||||||
|
if (this.node && this.node.children.length !== 0) {
|
||||||
|
// save the height of the fully-rendered element
|
||||||
|
this.height = getRectFromEntry(entry).height;
|
||||||
|
|
||||||
|
if (this.props.onHeightChange) {
|
||||||
|
this.props.onHeightChange(this.props.status, this.height);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
this.setState((prevState) => {
|
||||||
|
if (prevState.isIntersecting && !entry.isIntersecting) {
|
||||||
|
scheduleIdleTask(this.hideIfNotIntersecting);
|
||||||
|
}
|
||||||
|
return {
|
||||||
|
isIntersecting: entry.isIntersecting,
|
||||||
|
isHidden: false,
|
||||||
|
};
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
hideIfNotIntersecting = () => {
|
||||||
|
if (!this.componentMounted) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// When the browser gets a chance, test if we're still not intersecting,
|
||||||
|
// and if so, set our isHidden to true to trigger an unrender. The point of
|
||||||
|
// this is to save DOM nodes and avoid using up too much memory.
|
||||||
|
// See: https://github.com/tootsuite/mastodon/issues/2900
|
||||||
|
this.setState((prevState) => ({ isHidden: !prevState.isIntersecting }));
|
||||||
|
}
|
||||||
|
|
||||||
|
handleRef = (node) => {
|
||||||
|
this.node = node;
|
||||||
|
}
|
||||||
|
|
||||||
|
render () {
|
||||||
|
const { children, id, index, listLength } = this.props;
|
||||||
|
const { isIntersecting, isHidden } = this.state;
|
||||||
|
|
||||||
|
if (!isIntersecting && isHidden) {
|
||||||
|
return (
|
||||||
|
<article
|
||||||
|
ref={this.handleRef}
|
||||||
|
aria-posinset={index}
|
||||||
|
aria-setsize={listLength}
|
||||||
|
style={{ height: `${this.height}px`, opacity: 0, overflow: 'hidden' }}
|
||||||
|
data-id={id}
|
||||||
|
tabIndex='0'
|
||||||
|
>
|
||||||
|
{children && React.cloneElement(children, { hidden: true })}
|
||||||
|
</article>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<article ref={this.handleRef} aria-posinset={index} aria-setsize={listLength} data-id={id} tabIndex='0'>
|
||||||
|
{children && React.cloneElement(children, { hidden: false })}
|
||||||
|
</article>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -0,0 +1,179 @@
|
||||||
|
import React, { PureComponent } from 'react';
|
||||||
|
import { ScrollContainer } from 'react-router-scroll';
|
||||||
|
import PropTypes from 'prop-types';
|
||||||
|
import IntersectionObserverArticle from './intersection_observer_article';
|
||||||
|
import LoadMore from './load_more';
|
||||||
|
import IntersectionObserverWrapper from '../features/ui/util/intersection_observer_wrapper';
|
||||||
|
import { throttle } from 'lodash';
|
||||||
|
|
||||||
|
export default class ScrollableList extends PureComponent {
|
||||||
|
|
||||||
|
static propTypes = {
|
||||||
|
scrollKey: PropTypes.string.isRequired,
|
||||||
|
onScrollToBottom: PropTypes.func,
|
||||||
|
onScrollToTop: PropTypes.func,
|
||||||
|
onScroll: PropTypes.func,
|
||||||
|
trackScroll: PropTypes.bool,
|
||||||
|
shouldUpdateScroll: PropTypes.func,
|
||||||
|
isLoading: PropTypes.bool,
|
||||||
|
hasMore: PropTypes.bool,
|
||||||
|
prepend: PropTypes.node,
|
||||||
|
emptyMessage: PropTypes.node,
|
||||||
|
children: PropTypes.node,
|
||||||
|
};
|
||||||
|
|
||||||
|
static defaultProps = {
|
||||||
|
trackScroll: true,
|
||||||
|
};
|
||||||
|
|
||||||
|
intersectionObserverWrapper = new IntersectionObserverWrapper();
|
||||||
|
|
||||||
|
handleScroll = throttle(() => {
|
||||||
|
if (this.node) {
|
||||||
|
const { scrollTop, scrollHeight, clientHeight } = this.node;
|
||||||
|
const offset = scrollHeight - scrollTop - clientHeight;
|
||||||
|
this._oldScrollPosition = scrollHeight - scrollTop;
|
||||||
|
|
||||||
|
if (400 > offset && this.props.onScrollToBottom && !this.props.isLoading) {
|
||||||
|
this.props.onScrollToBottom();
|
||||||
|
} else if (scrollTop < 100 && this.props.onScrollToTop) {
|
||||||
|
this.props.onScrollToTop();
|
||||||
|
} else if (this.props.onScroll) {
|
||||||
|
this.props.onScroll();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}, 150, {
|
||||||
|
trailing: true,
|
||||||
|
});
|
||||||
|
|
||||||
|
componentDidMount () {
|
||||||
|
this.attachScrollListener();
|
||||||
|
this.attachIntersectionObserver();
|
||||||
|
|
||||||
|
// Handle initial scroll posiiton
|
||||||
|
this.handleScroll();
|
||||||
|
}
|
||||||
|
|
||||||
|
componentDidUpdate (prevProps) {
|
||||||
|
// Reset the scroll position when a new child comes in in order not to
|
||||||
|
// jerk the scrollbar around if you're already scrolled down the page.
|
||||||
|
if (React.Children.count(prevProps.children) < React.Children.count(this.props.children) && this._oldScrollPosition && this.node.scrollTop > 0) {
|
||||||
|
if (this.getFirstChildKey(prevProps) !== this.getFirstChildKey(this.props)) {
|
||||||
|
const newScrollTop = this.node.scrollHeight - this._oldScrollPosition;
|
||||||
|
if (this.node.scrollTop !== newScrollTop) {
|
||||||
|
this.node.scrollTop = newScrollTop;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
this._oldScrollPosition = this.node.scrollHeight - this.node.scrollTop;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
componentWillUnmount () {
|
||||||
|
this.detachScrollListener();
|
||||||
|
this.detachIntersectionObserver();
|
||||||
|
}
|
||||||
|
|
||||||
|
attachIntersectionObserver () {
|
||||||
|
this.intersectionObserverWrapper.connect({
|
||||||
|
root: this.node,
|
||||||
|
rootMargin: '300% 0px',
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
detachIntersectionObserver () {
|
||||||
|
this.intersectionObserverWrapper.disconnect();
|
||||||
|
}
|
||||||
|
|
||||||
|
attachScrollListener () {
|
||||||
|
this.node.addEventListener('scroll', this.handleScroll);
|
||||||
|
}
|
||||||
|
|
||||||
|
detachScrollListener () {
|
||||||
|
this.node.removeEventListener('scroll', this.handleScroll);
|
||||||
|
}
|
||||||
|
|
||||||
|
getFirstChildKey (props) {
|
||||||
|
const { children } = props;
|
||||||
|
const firstChild = Array.isArray(children) ? children[0] : children;
|
||||||
|
return firstChild && firstChild.key;
|
||||||
|
}
|
||||||
|
|
||||||
|
setRef = (c) => {
|
||||||
|
this.node = c;
|
||||||
|
}
|
||||||
|
|
||||||
|
handleLoadMore = (e) => {
|
||||||
|
e.preventDefault();
|
||||||
|
this.props.onScrollToBottom();
|
||||||
|
}
|
||||||
|
|
||||||
|
handleKeyDown = (e) => {
|
||||||
|
if (['PageDown', 'PageUp'].includes(e.key) || (e.ctrlKey && ['End', 'Home'].includes(e.key))) {
|
||||||
|
const article = (() => {
|
||||||
|
switch (e.key) {
|
||||||
|
case 'PageDown':
|
||||||
|
return e.target.nodeName === 'ARTICLE' && e.target.nextElementSibling;
|
||||||
|
case 'PageUp':
|
||||||
|
return e.target.nodeName === 'ARTICLE' && e.target.previousElementSibling;
|
||||||
|
case 'End':
|
||||||
|
return this.node.querySelector('[role="feed"] > article:last-of-type');
|
||||||
|
case 'Home':
|
||||||
|
return this.node.querySelector('[role="feed"] > article:first-of-type');
|
||||||
|
default:
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
})();
|
||||||
|
|
||||||
|
|
||||||
|
if (article) {
|
||||||
|
e.preventDefault();
|
||||||
|
article.focus();
|
||||||
|
article.scrollIntoView();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
render () {
|
||||||
|
const { children, scrollKey, trackScroll, shouldUpdateScroll, isLoading, hasMore, prepend, emptyMessage } = this.props;
|
||||||
|
const childrenCount = React.Children.count(children);
|
||||||
|
|
||||||
|
const loadMore = <LoadMore visible={!isLoading && childrenCount > 0 && hasMore} onClick={this.handleLoadMore} />;
|
||||||
|
let scrollableArea = null;
|
||||||
|
|
||||||
|
if (isLoading || childrenCount > 0 || !emptyMessage) {
|
||||||
|
scrollableArea = (
|
||||||
|
<div className='scrollable' ref={this.setRef}>
|
||||||
|
<div role='feed' className='item-list' onKeyDown={this.handleKeyDown}>
|
||||||
|
{prepend}
|
||||||
|
|
||||||
|
{React.Children.map(this.props.children, (child, index) => (
|
||||||
|
<IntersectionObserverArticle key={child.key} id={child.key} index={index} listLength={childrenCount} intersectionObserverWrapper={this.intersectionObserverWrapper}>
|
||||||
|
{child}
|
||||||
|
</IntersectionObserverArticle>
|
||||||
|
))}
|
||||||
|
|
||||||
|
{loadMore}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
scrollableArea = (
|
||||||
|
<div className='empty-column-indicator' ref={this.setRef}>
|
||||||
|
{emptyMessage}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (trackScroll) {
|
||||||
|
return (
|
||||||
|
<ScrollContainer scrollKey={scrollKey} shouldUpdateScroll={shouldUpdateScroll}>
|
||||||
|
{scrollableArea}
|
||||||
|
</ScrollContainer>
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
return scrollableArea;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -12,13 +12,11 @@ import StatusContent from './status_content';
|
||||||
import StatusActionBar from './status_action_bar';
|
import StatusActionBar from './status_action_bar';
|
||||||
import { FormattedMessage } from 'react-intl';
|
import { FormattedMessage } from 'react-intl';
|
||||||
import ImmutablePureComponent from 'react-immutable-pure-component';
|
import ImmutablePureComponent from 'react-immutable-pure-component';
|
||||||
import scheduleIdleTask from '../features/ui/util/schedule_idle_task';
|
|
||||||
import { MediaGallery, VideoPlayer } from '../features/ui/util/async-components';
|
import { MediaGallery, VideoPlayer } from '../features/ui/util/async-components';
|
||||||
|
|
||||||
// We use the component (and not the container) since we do not want
|
// We use the component (and not the container) since we do not want
|
||||||
// to use the progress bar to show download progress
|
// to use the progress bar to show download progress
|
||||||
import Bundle from '../features/ui/components/bundle';
|
import Bundle from '../features/ui/components/bundle';
|
||||||
import getRectFromEntry from '../features/ui/util/get_rect_from_entry';
|
|
||||||
|
|
||||||
export default class Status extends ImmutablePureComponent {
|
export default class Status extends ImmutablePureComponent {
|
||||||
|
|
||||||
|
@ -29,27 +27,25 @@ export default class Status extends ImmutablePureComponent {
|
||||||
static propTypes = {
|
static propTypes = {
|
||||||
status: ImmutablePropTypes.map,
|
status: ImmutablePropTypes.map,
|
||||||
account: ImmutablePropTypes.map,
|
account: ImmutablePropTypes.map,
|
||||||
wrapped: PropTypes.bool,
|
|
||||||
onReply: PropTypes.func,
|
onReply: PropTypes.func,
|
||||||
onFavourite: PropTypes.func,
|
onFavourite: PropTypes.func,
|
||||||
onReblog: PropTypes.func,
|
onReblog: PropTypes.func,
|
||||||
onDelete: PropTypes.func,
|
onDelete: PropTypes.func,
|
||||||
|
onPin: PropTypes.func,
|
||||||
onOpenMedia: PropTypes.func,
|
onOpenMedia: PropTypes.func,
|
||||||
onOpenVideo: PropTypes.func,
|
onOpenVideo: PropTypes.func,
|
||||||
onBlock: PropTypes.func,
|
onBlock: PropTypes.func,
|
||||||
|
onEmbed: PropTypes.func,
|
||||||
onHeightChange: PropTypes.func,
|
onHeightChange: PropTypes.func,
|
||||||
me: PropTypes.number,
|
me: PropTypes.number,
|
||||||
boostModal: PropTypes.bool,
|
boostModal: PropTypes.bool,
|
||||||
autoPlayGif: PropTypes.bool,
|
autoPlayGif: PropTypes.bool,
|
||||||
muted: PropTypes.bool,
|
muted: PropTypes.bool,
|
||||||
intersectionObserverWrapper: PropTypes.object,
|
hidden: PropTypes.bool,
|
||||||
index: PropTypes.oneOfType([PropTypes.string, PropTypes.number]),
|
|
||||||
listLength: PropTypes.oneOfType([PropTypes.string, PropTypes.number]),
|
|
||||||
};
|
};
|
||||||
|
|
||||||
state = {
|
state = {
|
||||||
isExpanded: false,
|
isExpanded: false,
|
||||||
isHidden: false, // set to true in requestIdleCallback to trigger un-render
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Avoid checking props that are functions (and whose equality will always
|
// Avoid checking props that are functions (and whose equality will always
|
||||||
|
@ -57,91 +53,15 @@ export default class Status extends ImmutablePureComponent {
|
||||||
updateOnProps = [
|
updateOnProps = [
|
||||||
'status',
|
'status',
|
||||||
'account',
|
'account',
|
||||||
'wrapped',
|
|
||||||
'me',
|
'me',
|
||||||
'boostModal',
|
'boostModal',
|
||||||
'autoPlayGif',
|
'autoPlayGif',
|
||||||
'muted',
|
'muted',
|
||||||
'listLength',
|
'hidden',
|
||||||
]
|
]
|
||||||
|
|
||||||
updateOnStates = ['isExpanded']
|
updateOnStates = ['isExpanded']
|
||||||
|
|
||||||
shouldComponentUpdate (nextProps, nextState) {
|
|
||||||
if (!nextState.isIntersecting && nextState.isHidden) {
|
|
||||||
// It's only if we're not intersecting (i.e. offscreen) and isHidden is true
|
|
||||||
// that either "isIntersecting" or "isHidden" matter, and then they're
|
|
||||||
// the only things that matter (and updated ARIA attributes).
|
|
||||||
return this.state.isIntersecting || !this.state.isHidden || nextProps.listLength !== this.props.listLength;
|
|
||||||
} else if (nextState.isIntersecting && !this.state.isIntersecting) {
|
|
||||||
// If we're going from a non-intersecting state to an intersecting state,
|
|
||||||
// (i.e. offscreen to onscreen), then we definitely need to re-render
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
// Otherwise, diff based on "updateOnProps" and "updateOnStates"
|
|
||||||
return super.shouldComponentUpdate(nextProps, nextState);
|
|
||||||
}
|
|
||||||
|
|
||||||
componentDidMount () {
|
|
||||||
if (!this.props.intersectionObserverWrapper) {
|
|
||||||
// TODO: enable IntersectionObserver optimization for notification statuses.
|
|
||||||
// These are managed in notifications/index.js rather than status_list.js
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
this.props.intersectionObserverWrapper.observe(
|
|
||||||
this.props.id,
|
|
||||||
this.node,
|
|
||||||
this.handleIntersection
|
|
||||||
);
|
|
||||||
|
|
||||||
this.componentMounted = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
componentWillUnmount () {
|
|
||||||
if (this.props.intersectionObserverWrapper) {
|
|
||||||
this.props.intersectionObserverWrapper.unobserve(this.props.id, this.node);
|
|
||||||
}
|
|
||||||
|
|
||||||
this.componentMounted = false;
|
|
||||||
}
|
|
||||||
|
|
||||||
handleIntersection = (entry) => {
|
|
||||||
if (this.node && this.node.children.length !== 0) {
|
|
||||||
// save the height of the fully-rendered element
|
|
||||||
this.height = getRectFromEntry(entry).height;
|
|
||||||
|
|
||||||
if (this.props.onHeightChange) {
|
|
||||||
this.props.onHeightChange(this.props.status, this.height);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
this.setState((prevState) => {
|
|
||||||
if (prevState.isIntersecting && !entry.isIntersecting) {
|
|
||||||
scheduleIdleTask(this.hideIfNotIntersecting);
|
|
||||||
}
|
|
||||||
return {
|
|
||||||
isIntersecting: entry.isIntersecting,
|
|
||||||
isHidden: false,
|
|
||||||
};
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
hideIfNotIntersecting = () => {
|
|
||||||
if (!this.componentMounted) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// When the browser gets a chance, test if we're still not intersecting,
|
|
||||||
// and if so, set our isHidden to true to trigger an unrender. The point of
|
|
||||||
// this is to save DOM nodes and avoid using up too much memory.
|
|
||||||
// See: https://github.com/tootsuite/mastodon/issues/2900
|
|
||||||
this.setState((prevState) => ({ isHidden: !prevState.isIntersecting }));
|
|
||||||
}
|
|
||||||
|
|
||||||
handleRef = (node) => {
|
|
||||||
this.node = node;
|
|
||||||
}
|
|
||||||
|
|
||||||
handleClick = () => {
|
handleClick = () => {
|
||||||
if (!this.context.router) {
|
if (!this.context.router) {
|
||||||
return;
|
return;
|
||||||
|
@ -175,25 +95,19 @@ export default class Status extends ImmutablePureComponent {
|
||||||
let media = null;
|
let media = null;
|
||||||
let statusAvatar;
|
let statusAvatar;
|
||||||
|
|
||||||
// Exclude intersectionObserverWrapper from `other` variable
|
const { status, account, hidden, ...other } = this.props;
|
||||||
// because intersection is managed in here.
|
const { isExpanded } = this.state;
|
||||||
const { status, account, intersectionObserverWrapper, index, listLength, wrapped, ...other } = this.props;
|
|
||||||
const { isExpanded, isIntersecting, isHidden } = this.state;
|
|
||||||
|
|
||||||
if (status === null) {
|
if (status === null) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
const hasIntersectionObserverWrapper = !!this.props.intersectionObserverWrapper;
|
if (hidden) {
|
||||||
const isHiddenForSure = isIntersecting === false && isHidden;
|
|
||||||
const visibilityUnknownButHeightIsCached = isIntersecting === undefined && status.has('height');
|
|
||||||
|
|
||||||
if (hasIntersectionObserverWrapper && (isHiddenForSure || visibilityUnknownButHeightIsCached)) {
|
|
||||||
return (
|
return (
|
||||||
<article ref={this.handleRef} data-id={status.get('id')} aria-posinset={index} aria-setsize={listLength} tabIndex='0' style={{ height: `${this.height || status.get('height')}px`, opacity: 0, overflow: 'hidden' }}>
|
<div>
|
||||||
{status.getIn(['account', 'display_name']) || status.getIn(['account', 'username'])}
|
{status.getIn(['account', 'display_name']) || status.getIn(['account', 'username'])}
|
||||||
{status.get('content')}
|
{status.get('content')}
|
||||||
</article>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -201,14 +115,14 @@ export default class Status extends ImmutablePureComponent {
|
||||||
const display_name_html = { __html: status.getIn(['account', 'display_name_html']) };
|
const display_name_html = { __html: status.getIn(['account', 'display_name_html']) };
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<article className='status__wrapper' ref={this.handleRef} data-id={status.get('id')} aria-posinset={index} aria-setsize={listLength} tabIndex='0'>
|
<div className='status__wrapper' data-id={status.get('id')} >
|
||||||
<div className='status__prepend'>
|
<div className='status__prepend'>
|
||||||
<div className='status__prepend-icon-wrapper'><i className='fa fa-fw fa-retweet status__prepend-icon' /></div>
|
<div className='status__prepend-icon-wrapper'><i className='fa fa-fw fa-retweet status__prepend-icon' /></div>
|
||||||
<FormattedMessage id='status.reblogged_by' defaultMessage='{name} boosted' values={{ name: <a onClick={this.handleAccountClick} data-id={status.getIn(['account', 'id'])} href={status.getIn(['account', 'url'])} className='status__display-name muted'><strong dangerouslySetInnerHTML={display_name_html} /></a> }} />
|
<FormattedMessage id='status.reblogged_by' defaultMessage='{name} boosted' values={{ name: <a onClick={this.handleAccountClick} data-id={status.getIn(['account', 'id'])} href={status.getIn(['account', 'url'])} className='status__display-name muted'><strong dangerouslySetInnerHTML={display_name_html} /></a> }} />
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<Status {...other} wrapped status={status.get('reblog')} account={status.get('account')} />
|
<Status {...other} status={status.get('reblog')} account={status.get('account')} />
|
||||||
</article>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -237,7 +151,7 @@ export default class Status extends ImmutablePureComponent {
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<article aria-posinset={index} aria-setsize={listLength} className={`status ${this.props.muted ? 'muted' : ''} status-${status.get('visibility')}`} data-id={status.get('id')} tabIndex={wrapped ? null : '0'} ref={this.handleRef}>
|
<div className={`status ${this.props.muted ? 'muted' : ''} status-${status.get('visibility')}`} data-id={status.get('id')}>
|
||||||
<div className='status__info'>
|
<div className='status__info'>
|
||||||
<a href={status.get('url')} className='status__relative-time' target='_blank' rel='noopener'><RelativeTimestamp timestamp={status.get('created_at')} /></a>
|
<a href={status.get('url')} className='status__relative-time' target='_blank' rel='noopener'><RelativeTimestamp timestamp={status.get('created_at')} /></a>
|
||||||
|
|
||||||
|
@ -255,7 +169,7 @@ export default class Status extends ImmutablePureComponent {
|
||||||
{media}
|
{media}
|
||||||
|
|
||||||
<StatusActionBar {...this.props} />
|
<StatusActionBar {...this.props} />
|
||||||
</article>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -24,6 +24,9 @@ const messages = defineMessages({
|
||||||
report: { id: 'status.report', defaultMessage: 'Report @{name}' },
|
report: { id: 'status.report', defaultMessage: 'Report @{name}' },
|
||||||
muteConversation: { id: 'status.mute_conversation', defaultMessage: 'Mute conversation' },
|
muteConversation: { id: 'status.mute_conversation', defaultMessage: 'Mute conversation' },
|
||||||
unmuteConversation: { id: 'status.unmute_conversation', defaultMessage: 'Unmute conversation' },
|
unmuteConversation: { id: 'status.unmute_conversation', defaultMessage: 'Unmute conversation' },
|
||||||
|
pin: { id: 'status.pin', defaultMessage: 'Pin on profile' },
|
||||||
|
unpin: { id: 'status.unpin', defaultMessage: 'Unpin from profile' },
|
||||||
|
embed: { id: 'status.embed', defaultMessage: 'Embed' },
|
||||||
});
|
});
|
||||||
|
|
||||||
@injectIntl
|
@injectIntl
|
||||||
|
@ -43,7 +46,9 @@ export default class StatusActionBar extends ImmutablePureComponent {
|
||||||
onMute: PropTypes.func,
|
onMute: PropTypes.func,
|
||||||
onBlock: PropTypes.func,
|
onBlock: PropTypes.func,
|
||||||
onReport: PropTypes.func,
|
onReport: PropTypes.func,
|
||||||
|
onEmbed: PropTypes.func,
|
||||||
onMuteConversation: PropTypes.func,
|
onMuteConversation: PropTypes.func,
|
||||||
|
onPin: PropTypes.func,
|
||||||
me: PropTypes.number,
|
me: PropTypes.number,
|
||||||
withDismiss: PropTypes.bool,
|
withDismiss: PropTypes.bool,
|
||||||
intl: PropTypes.object.isRequired,
|
intl: PropTypes.object.isRequired,
|
||||||
|
@ -80,6 +85,10 @@ export default class StatusActionBar extends ImmutablePureComponent {
|
||||||
this.props.onDelete(this.props.status);
|
this.props.onDelete(this.props.status);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
handlePinClick = () => {
|
||||||
|
this.props.onPin(this.props.status);
|
||||||
|
}
|
||||||
|
|
||||||
handleMentionClick = () => {
|
handleMentionClick = () => {
|
||||||
this.props.onMention(this.props.status.get('account'), this.context.router.history);
|
this.props.onMention(this.props.status.get('account'), this.context.router.history);
|
||||||
}
|
}
|
||||||
|
@ -96,6 +105,10 @@ export default class StatusActionBar extends ImmutablePureComponent {
|
||||||
this.context.router.history.push(`/statuses/${this.props.status.get('id')}`);
|
this.context.router.history.push(`/statuses/${this.props.status.get('id')}`);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
handleEmbed = () => {
|
||||||
|
this.props.onEmbed(this.props.status);
|
||||||
|
}
|
||||||
|
|
||||||
handleReport = () => {
|
handleReport = () => {
|
||||||
this.props.onReport(this.props.status);
|
this.props.onReport(this.props.status);
|
||||||
}
|
}
|
||||||
|
@ -106,9 +119,10 @@ export default class StatusActionBar extends ImmutablePureComponent {
|
||||||
|
|
||||||
render () {
|
render () {
|
||||||
const { status, me, intl, withDismiss } = this.props;
|
const { status, me, intl, withDismiss } = this.props;
|
||||||
const reblogDisabled = status.get('visibility') === 'private' || status.get('visibility') === 'direct';
|
|
||||||
const mutingConversation = status.get('muted');
|
const mutingConversation = status.get('muted');
|
||||||
const anonymousAccess = !me;
|
const anonymousAccess = !me;
|
||||||
|
const publicStatus = ['public', 'unlisted'].includes(status.get('visibility'));
|
||||||
|
|
||||||
let menu = [];
|
let menu = [];
|
||||||
let reblogIcon = 'retweet';
|
let reblogIcon = 'retweet';
|
||||||
|
@ -116,6 +130,11 @@ export default class StatusActionBar extends ImmutablePureComponent {
|
||||||
let replyTitle;
|
let replyTitle;
|
||||||
|
|
||||||
menu.push({ text: intl.formatMessage(messages.open), action: this.handleOpen });
|
menu.push({ text: intl.formatMessage(messages.open), action: this.handleOpen });
|
||||||
|
|
||||||
|
if (publicStatus) {
|
||||||
|
menu.push({ text: intl.formatMessage(messages.embed), action: this.handleEmbed });
|
||||||
|
}
|
||||||
|
|
||||||
menu.push(null);
|
menu.push(null);
|
||||||
|
|
||||||
if (withDismiss) {
|
if (withDismiss) {
|
||||||
|
@ -124,6 +143,10 @@ export default class StatusActionBar extends ImmutablePureComponent {
|
||||||
}
|
}
|
||||||
|
|
||||||
if (status.getIn(['account', 'id']) === me) {
|
if (status.getIn(['account', 'id']) === me) {
|
||||||
|
if (publicStatus) {
|
||||||
|
menu.push({ text: intl.formatMessage(status.get('pinned') ? messages.unpin : messages.pin), action: this.handlePinClick });
|
||||||
|
}
|
||||||
|
|
||||||
menu.push({ text: intl.formatMessage(messages.delete), action: this.handleDeleteClick });
|
menu.push({ text: intl.formatMessage(messages.delete), action: this.handleDeleteClick });
|
||||||
} else {
|
} else {
|
||||||
menu.push({ text: intl.formatMessage(messages.mention, { name: status.getIn(['account', 'username']) }), action: this.handleMentionClick });
|
menu.push({ text: intl.formatMessage(messages.mention, { name: status.getIn(['account', 'username']) }), action: this.handleMentionClick });
|
||||||
|
@ -154,7 +177,7 @@ export default class StatusActionBar extends ImmutablePureComponent {
|
||||||
return (
|
return (
|
||||||
<div className='status__action-bar'>
|
<div className='status__action-bar'>
|
||||||
<IconButton className='status__action-bar-button' disabled={anonymousAccess} title={replyTitle} icon={replyIcon} onClick={this.handleReplyClick} />
|
<IconButton className='status__action-bar-button' disabled={anonymousAccess} title={replyTitle} icon={replyIcon} onClick={this.handleReplyClick} />
|
||||||
<IconButton className='status__action-bar-button' disabled={anonymousAccess || reblogDisabled} active={status.get('reblogged')} pressed={status.get('reblogged')} title={reblogDisabled ? intl.formatMessage(messages.cannot_reblog) : intl.formatMessage(messages.reblog)} icon={reblogIcon} onClick={this.handleReblogClick} />
|
<IconButton className='status__action-bar-button' disabled={anonymousAccess || !publicStatus} active={status.get('reblogged')} pressed={status.get('reblogged')} title={!publicStatus ? intl.formatMessage(messages.cannot_reblog) : intl.formatMessage(messages.reblog)} icon={reblogIcon} onClick={this.handleReblogClick} />
|
||||||
<IconButton className='status__action-bar-button star-icon' disabled={anonymousAccess} animate active={status.get('favourited')} pressed={status.get('favourited')} title={intl.formatMessage(messages.favourite)} icon='star' onClick={this.handleFavouriteClick} />
|
<IconButton className='status__action-bar-button star-icon' disabled={anonymousAccess} animate active={status.get('favourited')} pressed={status.get('favourited')} title={intl.formatMessage(messages.favourite)} icon='star' onClick={this.handleFavouriteClick} />
|
||||||
{shareButton}
|
{shareButton}
|
||||||
|
|
||||||
|
|
|
@ -1,12 +1,10 @@
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
import ImmutablePropTypes from 'react-immutable-proptypes';
|
import ImmutablePropTypes from 'react-immutable-proptypes';
|
||||||
import { ScrollContainer } from 'react-router-scroll';
|
|
||||||
import PropTypes from 'prop-types';
|
import PropTypes from 'prop-types';
|
||||||
import StatusContainer from '../../glitch/components/status/container';
|
import StatusContainer from '../../glitch/components/status/container';
|
||||||
import LoadMore from './load_more';
|
import LoadMore from './load_more';
|
||||||
import ImmutablePureComponent from 'react-immutable-pure-component';
|
import ImmutablePureComponent from 'react-immutable-pure-component';
|
||||||
import IntersectionObserverWrapper from '../features/ui/util/intersection_observer_wrapper';
|
import ScrollableList from './scrollable_list';
|
||||||
import { throttle } from 'lodash';
|
|
||||||
|
|
||||||
export default class StatusList extends ImmutablePureComponent {
|
export default class StatusList extends ImmutablePureComponent {
|
||||||
|
|
||||||
|
@ -28,145 +26,21 @@ export default class StatusList extends ImmutablePureComponent {
|
||||||
trackScroll: true,
|
trackScroll: true,
|
||||||
};
|
};
|
||||||
|
|
||||||
intersectionObserverWrapper = new IntersectionObserverWrapper();
|
|
||||||
|
|
||||||
handleScroll = throttle(() => {
|
|
||||||
if (this.node) {
|
|
||||||
const { scrollTop, scrollHeight, clientHeight } = this.node;
|
|
||||||
const offset = scrollHeight - scrollTop - clientHeight;
|
|
||||||
this._oldScrollPosition = scrollHeight - scrollTop;
|
|
||||||
|
|
||||||
if (400 > offset && this.props.onScrollToBottom && !this.props.isLoading) {
|
|
||||||
this.props.onScrollToBottom();
|
|
||||||
} else if (scrollTop < 100 && this.props.onScrollToTop) {
|
|
||||||
this.props.onScrollToTop();
|
|
||||||
} else if (this.props.onScroll) {
|
|
||||||
this.props.onScroll();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}, 150, {
|
|
||||||
trailing: true,
|
|
||||||
});
|
|
||||||
|
|
||||||
componentDidMount () {
|
|
||||||
this.attachScrollListener();
|
|
||||||
this.attachIntersectionObserver();
|
|
||||||
|
|
||||||
// Handle initial scroll posiiton
|
|
||||||
this.handleScroll();
|
|
||||||
}
|
|
||||||
|
|
||||||
componentDidUpdate (prevProps) {
|
|
||||||
// Reset the scroll position when a new toot comes in in order not to
|
|
||||||
// jerk the scrollbar around if you're already scrolled down the page.
|
|
||||||
if (prevProps.statusIds.size < this.props.statusIds.size && this._oldScrollPosition && this.node.scrollTop > 0) {
|
|
||||||
if (prevProps.statusIds.first() !== this.props.statusIds.first()) {
|
|
||||||
let newScrollTop = this.node.scrollHeight - this._oldScrollPosition;
|
|
||||||
if (this.node.scrollTop !== newScrollTop) {
|
|
||||||
this.node.scrollTop = newScrollTop;
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
this._oldScrollPosition = this.node.scrollHeight - this.node.scrollTop;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
componentWillUnmount () {
|
|
||||||
this.detachScrollListener();
|
|
||||||
this.detachIntersectionObserver();
|
|
||||||
}
|
|
||||||
|
|
||||||
attachIntersectionObserver () {
|
|
||||||
this.intersectionObserverWrapper.connect({
|
|
||||||
root: this.node,
|
|
||||||
rootMargin: '300% 0px',
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
detachIntersectionObserver () {
|
|
||||||
this.intersectionObserverWrapper.disconnect();
|
|
||||||
}
|
|
||||||
|
|
||||||
attachScrollListener () {
|
|
||||||
this.node.addEventListener('scroll', this.handleScroll);
|
|
||||||
}
|
|
||||||
|
|
||||||
detachScrollListener () {
|
|
||||||
this.node.removeEventListener('scroll', this.handleScroll);
|
|
||||||
}
|
|
||||||
|
|
||||||
setRef = (c) => {
|
|
||||||
this.node = c;
|
|
||||||
}
|
|
||||||
|
|
||||||
handleLoadMore = (e) => {
|
|
||||||
e.preventDefault();
|
|
||||||
this.props.onScrollToBottom();
|
|
||||||
}
|
|
||||||
|
|
||||||
handleKeyDown = (e) => {
|
|
||||||
if (['PageDown', 'PageUp'].includes(e.key) || (e.ctrlKey && ['End', 'Home'].includes(e.key))) {
|
|
||||||
const article = (() => {
|
|
||||||
switch (e.key) {
|
|
||||||
case 'PageDown':
|
|
||||||
return e.target.nodeName === 'ARTICLE' && e.target.nextElementSibling;
|
|
||||||
case 'PageUp':
|
|
||||||
return e.target.nodeName === 'ARTICLE' && e.target.previousElementSibling;
|
|
||||||
case 'End':
|
|
||||||
return this.node.querySelector('[role="feed"] > article:last-of-type');
|
|
||||||
case 'Home':
|
|
||||||
return this.node.querySelector('[role="feed"] > article:first-of-type');
|
|
||||||
default:
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
})();
|
|
||||||
|
|
||||||
|
|
||||||
if (article) {
|
|
||||||
e.preventDefault();
|
|
||||||
article.focus();
|
|
||||||
article.scrollIntoView();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
render () {
|
render () {
|
||||||
const { statusIds, scrollKey, trackScroll, shouldUpdateScroll, isLoading, hasMore, prepend, emptyMessage } = this.props;
|
const { statusIds, ...other } = this.props;
|
||||||
|
const { isLoading } = other;
|
||||||
|
|
||||||
const loadMore = <LoadMore visible={!isLoading && statusIds.size > 0 && hasMore} onClick={this.handleLoadMore} />;
|
const scrollableContent = (isLoading || statusIds.size > 0) ? (
|
||||||
let scrollableArea = null;
|
statusIds.map((statusId) => (
|
||||||
|
<StatusContainer key={statusId} id={statusId} />
|
||||||
|
))
|
||||||
|
) : null;
|
||||||
|
|
||||||
if (isLoading || statusIds.size > 0 || !emptyMessage) {
|
|
||||||
scrollableArea = (
|
|
||||||
<div className='scrollable' ref={this.setRef}>
|
|
||||||
<div role='feed' className='status-list' onKeyDown={this.handleKeyDown}>
|
|
||||||
{prepend}
|
|
||||||
|
|
||||||
{statusIds.map((statusId, index) => {
|
|
||||||
return <StatusContainer key={statusId} id={statusId} index={index} listLength={statusIds.size} intersectionObserverWrapper={this.intersectionObserverWrapper} />;
|
|
||||||
})}
|
|
||||||
|
|
||||||
{loadMore}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
} else {
|
|
||||||
scrollableArea = (
|
|
||||||
<div className='empty-column-indicator' ref={this.setRef}>
|
|
||||||
{emptyMessage}
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (trackScroll) {
|
|
||||||
return (
|
return (
|
||||||
<ScrollContainer scrollKey={scrollKey} shouldUpdateScroll={shouldUpdateScroll}>
|
<ScrollableList {...other}>
|
||||||
{scrollableArea}
|
{scrollableContent}
|
||||||
</ScrollContainer>
|
</ScrollableList>
|
||||||
);
|
);
|
||||||
} else {
|
|
||||||
return scrollableArea;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -32,7 +32,7 @@ const makeMapStateToProps = () => {
|
||||||
const mapDispatchToProps = (dispatch, { intl }) => ({
|
const mapDispatchToProps = (dispatch, { intl }) => ({
|
||||||
|
|
||||||
onFollow (account) {
|
onFollow (account) {
|
||||||
if (account.getIn(['relationship', 'following'])) {
|
if (account.getIn(['relationship', 'following']) || account.getIn(['relationship', 'requested'])) {
|
||||||
if (this.unfollowModal) {
|
if (this.unfollowModal) {
|
||||||
dispatch(openModal('CONFIRM', {
|
dispatch(openModal('CONFIRM', {
|
||||||
message: <FormattedMessage id='confirmations.unfollow.message' defaultMessage='Are you sure you want to unfollow {name}?' values={{ name: <strong>@{account.get('acct')}</strong> }} />,
|
message: <FormattedMessage id='confirmations.unfollow.message' defaultMessage='Are you sure you want to unfollow {name}?' values={{ name: <strong>@{account.get('acct')}</strong> }} />,
|
||||||
|
|
|
@ -0,0 +1,39 @@
|
||||||
|
import React from 'react';
|
||||||
|
import { Provider } from 'react-redux';
|
||||||
|
import PropTypes from 'prop-types';
|
||||||
|
import configureStore from '../store/configureStore';
|
||||||
|
import { hydrateStore } from '../actions/store';
|
||||||
|
import { IntlProvider, addLocaleData } from 'react-intl';
|
||||||
|
import { getLocale } from '../locales';
|
||||||
|
import Compose from '../features/standalone/compose';
|
||||||
|
|
||||||
|
const { localeData, messages } = getLocale();
|
||||||
|
addLocaleData(localeData);
|
||||||
|
|
||||||
|
const store = configureStore();
|
||||||
|
const initialStateContainer = document.getElementById('initial-state');
|
||||||
|
|
||||||
|
if (initialStateContainer !== null) {
|
||||||
|
const initialState = JSON.parse(initialStateContainer.textContent);
|
||||||
|
store.dispatch(hydrateStore(initialState));
|
||||||
|
}
|
||||||
|
|
||||||
|
export default class TimelineContainer extends React.PureComponent {
|
||||||
|
|
||||||
|
static propTypes = {
|
||||||
|
locale: PropTypes.string.isRequired,
|
||||||
|
};
|
||||||
|
|
||||||
|
render () {
|
||||||
|
const { locale } = this.props;
|
||||||
|
|
||||||
|
return (
|
||||||
|
<IntlProvider locale={locale} messages={messages}>
|
||||||
|
<Provider store={store}>
|
||||||
|
<Compose />
|
||||||
|
</Provider>
|
||||||
|
</IntlProvider>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -2,21 +2,13 @@ import React from 'react';
|
||||||
import { Provider } from 'react-redux';
|
import { Provider } from 'react-redux';
|
||||||
import PropTypes from 'prop-types';
|
import PropTypes from 'prop-types';
|
||||||
import configureStore from '../store/configureStore';
|
import configureStore from '../store/configureStore';
|
||||||
import {
|
|
||||||
updateTimeline,
|
|
||||||
deleteFromTimelines,
|
|
||||||
refreshHomeTimeline,
|
|
||||||
connectTimeline,
|
|
||||||
disconnectTimeline,
|
|
||||||
} from '../actions/timelines';
|
|
||||||
import { showOnboardingOnce } from '../actions/onboarding';
|
import { showOnboardingOnce } from '../actions/onboarding';
|
||||||
import { updateNotifications, refreshNotifications } from '../actions/notifications';
|
|
||||||
import BrowserRouter from 'react-router-dom/BrowserRouter';
|
import BrowserRouter from 'react-router-dom/BrowserRouter';
|
||||||
import Route from 'react-router-dom/Route';
|
import Route from 'react-router-dom/Route';
|
||||||
import ScrollContext from 'react-router-scroll/lib/ScrollBehaviorContext';
|
import ScrollContext from 'react-router-scroll/lib/ScrollBehaviorContext';
|
||||||
import UI from '../features/ui';
|
import UI from '../features/ui';
|
||||||
import { hydrateStore } from '../actions/store';
|
import { hydrateStore } from '../actions/store';
|
||||||
import createStream from '../stream';
|
import { connectUserStream } from '../actions/streaming';
|
||||||
import { IntlProvider, addLocaleData } from 'react-intl';
|
import { IntlProvider, addLocaleData } from 'react-intl';
|
||||||
import { getLocale } from '../locales';
|
import { getLocale } from '../locales';
|
||||||
const { localeData, messages } = getLocale();
|
const { localeData, messages } = getLocale();
|
||||||
|
@ -39,74 +31,28 @@ export default class Mastodon extends React.PureComponent {
|
||||||
};
|
};
|
||||||
|
|
||||||
componentDidMount() {
|
componentDidMount() {
|
||||||
const { locale } = this.props;
|
this.disconnect = store.dispatch(connectUserStream());
|
||||||
const streamingAPIBaseURL = store.getState().getIn(['meta', 'streaming_api_base_url']);
|
|
||||||
const accessToken = store.getState().getIn(['meta', 'access_token']);
|
|
||||||
|
|
||||||
const setupPolling = () => {
|
|
||||||
this.polling = setInterval(() => {
|
|
||||||
store.dispatch(refreshHomeTimeline());
|
|
||||||
store.dispatch(refreshNotifications());
|
|
||||||
}, 20000);
|
|
||||||
};
|
|
||||||
|
|
||||||
const clearPolling = () => {
|
|
||||||
clearInterval(this.polling);
|
|
||||||
this.polling = undefined;
|
|
||||||
};
|
|
||||||
|
|
||||||
this.subscription = createStream(streamingAPIBaseURL, accessToken, 'user', {
|
|
||||||
|
|
||||||
connected () {
|
|
||||||
clearPolling();
|
|
||||||
store.dispatch(connectTimeline('home'));
|
|
||||||
},
|
|
||||||
|
|
||||||
disconnected () {
|
|
||||||
setupPolling();
|
|
||||||
store.dispatch(disconnectTimeline('home'));
|
|
||||||
},
|
|
||||||
|
|
||||||
received (data) {
|
|
||||||
switch(data.event) {
|
|
||||||
case 'update':
|
|
||||||
store.dispatch(updateTimeline('home', JSON.parse(data.payload)));
|
|
||||||
break;
|
|
||||||
case 'delete':
|
|
||||||
store.dispatch(deleteFromTimelines(data.payload));
|
|
||||||
break;
|
|
||||||
case 'notification':
|
|
||||||
store.dispatch(updateNotifications(JSON.parse(data.payload), messages, locale));
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
reconnected () {
|
|
||||||
clearPolling();
|
|
||||||
store.dispatch(connectTimeline('home'));
|
|
||||||
store.dispatch(refreshHomeTimeline());
|
|
||||||
store.dispatch(refreshNotifications());
|
|
||||||
},
|
|
||||||
|
|
||||||
});
|
|
||||||
|
|
||||||
// Desktop notifications
|
// Desktop notifications
|
||||||
|
// Ask after 1 minute
|
||||||
if (typeof window.Notification !== 'undefined' && Notification.permission === 'default') {
|
if (typeof window.Notification !== 'undefined' && Notification.permission === 'default') {
|
||||||
Notification.requestPermission();
|
window.setTimeout(() => Notification.requestPermission(), 60 * 1000);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Protocol handler
|
||||||
|
// Ask after 5 minutes
|
||||||
|
if (typeof navigator.registerProtocolHandler !== 'undefined') {
|
||||||
|
const handlerUrl = window.location.protocol + '//' + window.location.host + '/intent?uri=%s';
|
||||||
|
window.setTimeout(() => navigator.registerProtocolHandler('web+mastodon', handlerUrl, 'Mastodon'), 5 * 60 * 1000);
|
||||||
}
|
}
|
||||||
|
|
||||||
store.dispatch(showOnboardingOnce());
|
store.dispatch(showOnboardingOnce());
|
||||||
}
|
}
|
||||||
|
|
||||||
componentWillUnmount () {
|
componentWillUnmount () {
|
||||||
if (typeof this.subscription !== 'undefined') {
|
if (this.disconnect) {
|
||||||
this.subscription.close();
|
this.disconnect();
|
||||||
this.subscription = null;
|
this.disconnect = null;
|
||||||
}
|
|
||||||
|
|
||||||
if (typeof this.polling !== 'undefined') {
|
|
||||||
clearInterval(this.polling);
|
|
||||||
this.polling = null;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -14,6 +14,8 @@ import {
|
||||||
favourite,
|
favourite,
|
||||||
unreblog,
|
unreblog,
|
||||||
unfavourite,
|
unfavourite,
|
||||||
|
pin,
|
||||||
|
unpin,
|
||||||
} from '../actions/interactions';
|
} from '../actions/interactions';
|
||||||
import {
|
import {
|
||||||
blockAccount,
|
blockAccount,
|
||||||
|
@ -75,6 +77,18 @@ const mapDispatchToProps = (dispatch, { intl }) => ({
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
|
onPin (status) {
|
||||||
|
if (status.get('pinned')) {
|
||||||
|
dispatch(unpin(status));
|
||||||
|
} else {
|
||||||
|
dispatch(pin(status));
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
onEmbed (status) {
|
||||||
|
dispatch(openModal('EMBED', { url: status.get('url') }));
|
||||||
|
},
|
||||||
|
|
||||||
onDelete (status) {
|
onDelete (status) {
|
||||||
if (!this.deleteModal) {
|
if (!this.deleteModal) {
|
||||||
dispatch(deleteStatus(status.get('id')));
|
dispatch(deleteStatus(status.get('id')));
|
||||||
|
|
|
@ -14,7 +14,7 @@ import ImmutablePureComponent from 'react-immutable-pure-component';
|
||||||
const messages = defineMessages({
|
const messages = defineMessages({
|
||||||
unfollow: { id: 'account.unfollow', defaultMessage: 'Unfollow' },
|
unfollow: { id: 'account.unfollow', defaultMessage: 'Unfollow' },
|
||||||
follow: { id: 'account.follow', defaultMessage: 'Follow' },
|
follow: { id: 'account.follow', defaultMessage: 'Follow' },
|
||||||
requested: { id: 'account.requested', defaultMessage: 'Awaiting approval' },
|
requested: { id: 'account.requested', defaultMessage: 'Awaiting approval. Click to cancel follow request' },
|
||||||
});
|
});
|
||||||
|
|
||||||
const makeMapStateToProps = () => {
|
const makeMapStateToProps = () => {
|
||||||
|
@ -105,7 +105,7 @@ export default class Header extends ImmutablePureComponent {
|
||||||
if (account.getIn(['relationship', 'requested'])) {
|
if (account.getIn(['relationship', 'requested'])) {
|
||||||
actionBtn = (
|
actionBtn = (
|
||||||
<div className='account--action-button'>
|
<div className='account--action-button'>
|
||||||
<IconButton size={26} disabled icon='hourglass' title={intl.formatMessage(messages.requested)} />
|
<IconButton size={26} active icon='hourglass' title={intl.formatMessage(messages.requested)} onClick={this.props.onFollow} />
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
} else if (!account.getIn(['relationship', 'blocking'])) {
|
} else if (!account.getIn(['relationship', 'blocking'])) {
|
||||||
|
|
|
@ -38,7 +38,7 @@ const makeMapStateToProps = () => {
|
||||||
const mapDispatchToProps = (dispatch, { intl }) => ({
|
const mapDispatchToProps = (dispatch, { intl }) => ({
|
||||||
|
|
||||||
onFollow (account) {
|
onFollow (account) {
|
||||||
if (account.getIn(['relationship', 'following'])) {
|
if (account.getIn(['relationship', 'following']) || account.getIn(['relationship', 'requested'])) {
|
||||||
if (this.unfollowModal) {
|
if (this.unfollowModal) {
|
||||||
dispatch(openModal('CONFIRM', {
|
dispatch(openModal('CONFIRM', {
|
||||||
message: <FormattedMessage id='confirmations.unfollow.message' defaultMessage='Are you sure you want to unfollow {name}?' values={{ name: <strong>@{account.get('acct')}</strong> }} />,
|
message: <FormattedMessage id='confirmations.unfollow.message' defaultMessage='Are you sure you want to unfollow {name}?' values={{ name: <strong>@{account.get('acct')}</strong> }} />,
|
||||||
|
|
|
@ -7,15 +7,11 @@ import ColumnHeader from '../../components/column_header';
|
||||||
import {
|
import {
|
||||||
refreshCommunityTimeline,
|
refreshCommunityTimeline,
|
||||||
expandCommunityTimeline,
|
expandCommunityTimeline,
|
||||||
updateTimeline,
|
|
||||||
deleteFromTimelines,
|
|
||||||
connectTimeline,
|
|
||||||
disconnectTimeline,
|
|
||||||
} from '../../actions/timelines';
|
} from '../../actions/timelines';
|
||||||
import { addColumn, removeColumn, moveColumn } from '../../actions/columns';
|
import { addColumn, removeColumn, moveColumn } from '../../actions/columns';
|
||||||
import { defineMessages, injectIntl, FormattedMessage } from 'react-intl';
|
import { defineMessages, injectIntl, FormattedMessage } from 'react-intl';
|
||||||
import ColumnSettingsContainer from './containers/column_settings_container';
|
import ColumnSettingsContainer from './containers/column_settings_container';
|
||||||
import createStream from '../../stream';
|
import { connectCommunityStream } from '../../actions/streaming';
|
||||||
|
|
||||||
const messages = defineMessages({
|
const messages = defineMessages({
|
||||||
title: { id: 'column.community', defaultMessage: 'Local timeline' },
|
title: { id: 'column.community', defaultMessage: 'Local timeline' },
|
||||||
|
@ -23,8 +19,6 @@ const messages = defineMessages({
|
||||||
|
|
||||||
const mapStateToProps = state => ({
|
const mapStateToProps = state => ({
|
||||||
hasUnread: state.getIn(['timelines', 'community', 'unread']) > 0,
|
hasUnread: state.getIn(['timelines', 'community', 'unread']) > 0,
|
||||||
streamingAPIBaseURL: state.getIn(['meta', 'streaming_api_base_url']),
|
|
||||||
accessToken: state.getIn(['meta', 'access_token']),
|
|
||||||
});
|
});
|
||||||
|
|
||||||
@connect(mapStateToProps)
|
@connect(mapStateToProps)
|
||||||
|
@ -35,8 +29,6 @@ export default class CommunityTimeline extends React.PureComponent {
|
||||||
dispatch: PropTypes.func.isRequired,
|
dispatch: PropTypes.func.isRequired,
|
||||||
columnId: PropTypes.string,
|
columnId: PropTypes.string,
|
||||||
intl: PropTypes.object.isRequired,
|
intl: PropTypes.object.isRequired,
|
||||||
streamingAPIBaseURL: PropTypes.string.isRequired,
|
|
||||||
accessToken: PropTypes.string.isRequired,
|
|
||||||
hasUnread: PropTypes.bool,
|
hasUnread: PropTypes.bool,
|
||||||
multiColumn: PropTypes.bool,
|
multiColumn: PropTypes.bool,
|
||||||
};
|
};
|
||||||
|
@ -61,46 +53,16 @@ export default class CommunityTimeline extends React.PureComponent {
|
||||||
}
|
}
|
||||||
|
|
||||||
componentDidMount () {
|
componentDidMount () {
|
||||||
const { dispatch, streamingAPIBaseURL, accessToken } = this.props;
|
const { dispatch } = this.props;
|
||||||
|
|
||||||
dispatch(refreshCommunityTimeline());
|
dispatch(refreshCommunityTimeline());
|
||||||
|
this.disconnect = dispatch(connectCommunityStream());
|
||||||
if (typeof this._subscription !== 'undefined') {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
this._subscription = createStream(streamingAPIBaseURL, accessToken, 'public:local', {
|
|
||||||
|
|
||||||
connected () {
|
|
||||||
dispatch(connectTimeline('community'));
|
|
||||||
},
|
|
||||||
|
|
||||||
reconnected () {
|
|
||||||
dispatch(connectTimeline('community'));
|
|
||||||
},
|
|
||||||
|
|
||||||
disconnected () {
|
|
||||||
dispatch(disconnectTimeline('community'));
|
|
||||||
},
|
|
||||||
|
|
||||||
received (data) {
|
|
||||||
switch(data.event) {
|
|
||||||
case 'update':
|
|
||||||
dispatch(updateTimeline('community', JSON.parse(data.payload)));
|
|
||||||
break;
|
|
||||||
case 'delete':
|
|
||||||
dispatch(deleteFromTimelines(data.payload));
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
componentWillUnmount () {
|
componentWillUnmount () {
|
||||||
if (typeof this._subscription !== 'undefined') {
|
if (this.disconnect) {
|
||||||
this._subscription.close();
|
this.disconnect();
|
||||||
this._subscription = null;
|
this.disconnect = null;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -16,6 +16,7 @@ const messages = defineMessages({
|
||||||
|
|
||||||
const mapStateToProps = state => ({
|
const mapStateToProps = state => ({
|
||||||
statusIds: state.getIn(['status_lists', 'favourites', 'items']),
|
statusIds: state.getIn(['status_lists', 'favourites', 'items']),
|
||||||
|
hasMore: !!state.getIn(['status_lists', 'favourites', 'next']),
|
||||||
});
|
});
|
||||||
|
|
||||||
@connect(mapStateToProps)
|
@connect(mapStateToProps)
|
||||||
|
@ -28,6 +29,7 @@ export default class Favourites extends ImmutablePureComponent {
|
||||||
intl: PropTypes.object.isRequired,
|
intl: PropTypes.object.isRequired,
|
||||||
columnId: PropTypes.string,
|
columnId: PropTypes.string,
|
||||||
multiColumn: PropTypes.bool,
|
multiColumn: PropTypes.bool,
|
||||||
|
hasMore: PropTypes.bool,
|
||||||
};
|
};
|
||||||
|
|
||||||
componentWillMount () {
|
componentWillMount () {
|
||||||
|
@ -62,7 +64,7 @@ export default class Favourites extends ImmutablePureComponent {
|
||||||
}
|
}
|
||||||
|
|
||||||
render () {
|
render () {
|
||||||
const { intl, statusIds, columnId, multiColumn } = this.props;
|
const { intl, statusIds, columnId, multiColumn, hasMore } = this.props;
|
||||||
const pinned = !!columnId;
|
const pinned = !!columnId;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
@ -81,6 +83,7 @@ export default class Favourites extends ImmutablePureComponent {
|
||||||
trackScroll={!pinned}
|
trackScroll={!pinned}
|
||||||
statusIds={statusIds}
|
statusIds={statusIds}
|
||||||
scrollKey={`favourited_statuses-${columnId}`}
|
scrollKey={`favourited_statuses-${columnId}`}
|
||||||
|
hasMore={hasMore}
|
||||||
onScrollToBottom={this.handleScrollToBottom}
|
onScrollToBottom={this.handleScrollToBottom}
|
||||||
/>
|
/>
|
||||||
</Column>
|
</Column>
|
||||||
|
|
|
@ -7,17 +7,13 @@ import ColumnHeader from '../../components/column_header';
|
||||||
import {
|
import {
|
||||||
refreshHashtagTimeline,
|
refreshHashtagTimeline,
|
||||||
expandHashtagTimeline,
|
expandHashtagTimeline,
|
||||||
updateTimeline,
|
|
||||||
deleteFromTimelines,
|
|
||||||
} from '../../actions/timelines';
|
} from '../../actions/timelines';
|
||||||
import { addColumn, removeColumn, moveColumn } from '../../actions/columns';
|
import { addColumn, removeColumn, moveColumn } from '../../actions/columns';
|
||||||
import { FormattedMessage } from 'react-intl';
|
import { FormattedMessage } from 'react-intl';
|
||||||
import createStream from '../../stream';
|
import { connectHashtagStream } from '../../actions/streaming';
|
||||||
|
|
||||||
const mapStateToProps = state => ({
|
const mapStateToProps = (state, props) => ({
|
||||||
hasUnread: state.getIn(['timelines', 'tag', 'unread']) > 0,
|
hasUnread: state.getIn(['timelines', `hashtag:${props.params.id}`, 'unread']) > 0,
|
||||||
streamingAPIBaseURL: state.getIn(['meta', 'streaming_api_base_url']),
|
|
||||||
accessToken: state.getIn(['meta', 'access_token']),
|
|
||||||
});
|
});
|
||||||
|
|
||||||
@connect(mapStateToProps)
|
@connect(mapStateToProps)
|
||||||
|
@ -27,8 +23,6 @@ export default class HashtagTimeline extends React.PureComponent {
|
||||||
params: PropTypes.object.isRequired,
|
params: PropTypes.object.isRequired,
|
||||||
columnId: PropTypes.string,
|
columnId: PropTypes.string,
|
||||||
dispatch: PropTypes.func.isRequired,
|
dispatch: PropTypes.func.isRequired,
|
||||||
streamingAPIBaseURL: PropTypes.string.isRequired,
|
|
||||||
accessToken: PropTypes.string.isRequired,
|
|
||||||
hasUnread: PropTypes.bool,
|
hasUnread: PropTypes.bool,
|
||||||
multiColumn: PropTypes.bool,
|
multiColumn: PropTypes.bool,
|
||||||
};
|
};
|
||||||
|
@ -53,28 +47,13 @@ export default class HashtagTimeline extends React.PureComponent {
|
||||||
}
|
}
|
||||||
|
|
||||||
_subscribe (dispatch, id) {
|
_subscribe (dispatch, id) {
|
||||||
const { streamingAPIBaseURL, accessToken } = this.props;
|
this.disconnect = dispatch(connectHashtagStream(id));
|
||||||
|
|
||||||
this.subscription = createStream(streamingAPIBaseURL, accessToken, `hashtag&tag=${id}`, {
|
|
||||||
|
|
||||||
received (data) {
|
|
||||||
switch(data.event) {
|
|
||||||
case 'update':
|
|
||||||
dispatch(updateTimeline(`hashtag:${id}`, JSON.parse(data.payload)));
|
|
||||||
break;
|
|
||||||
case 'delete':
|
|
||||||
dispatch(deleteFromTimelines(data.payload));
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
_unsubscribe () {
|
_unsubscribe () {
|
||||||
if (typeof this.subscription !== 'undefined') {
|
if (this.disconnect) {
|
||||||
this.subscription.close();
|
this.disconnect();
|
||||||
this.subscription = null;
|
this.disconnect = null;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -2,6 +2,7 @@
|
||||||
// SEE INSTEAD : glitch/components/notification
|
// SEE INSTEAD : glitch/components/notification
|
||||||
|
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
|
import PropTypes from 'prop-types';
|
||||||
import ImmutablePropTypes from 'react-immutable-proptypes';
|
import ImmutablePropTypes from 'react-immutable-proptypes';
|
||||||
import StatusContainer from '../../../containers/status_container';
|
import StatusContainer from '../../../containers/status_container';
|
||||||
import AccountContainer from '../../../containers/account_container';
|
import AccountContainer from '../../../containers/account_container';
|
||||||
|
@ -13,6 +14,7 @@ export default class Notification extends ImmutablePureComponent {
|
||||||
|
|
||||||
static propTypes = {
|
static propTypes = {
|
||||||
notification: ImmutablePropTypes.map.isRequired,
|
notification: ImmutablePropTypes.map.isRequired,
|
||||||
|
hidden: PropTypes.bool,
|
||||||
};
|
};
|
||||||
|
|
||||||
renderFollow (account, link) {
|
renderFollow (account, link) {
|
||||||
|
@ -26,13 +28,13 @@ export default class Notification extends ImmutablePureComponent {
|
||||||
<FormattedMessage id='notification.follow' defaultMessage='{name} followed you' values={{ name: link }} />
|
<FormattedMessage id='notification.follow' defaultMessage='{name} followed you' values={{ name: link }} />
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<AccountContainer id={account.get('id')} withNote={false} />
|
<AccountContainer id={account.get('id')} withNote={false} hidden={this.props.hidden} />
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
renderMention (notification) {
|
renderMention (notification) {
|
||||||
return <StatusContainer id={notification.get('status')} withDismiss />;
|
return <StatusContainer id={notification.get('status')} withDismiss hidden={this.props.hidden} />;
|
||||||
}
|
}
|
||||||
|
|
||||||
renderFavourite (notification, link) {
|
renderFavourite (notification, link) {
|
||||||
|
@ -45,7 +47,7 @@ export default class Notification extends ImmutablePureComponent {
|
||||||
<FormattedMessage id='notification.favourite' defaultMessage='{name} favourited your status' values={{ name: link }} />
|
<FormattedMessage id='notification.favourite' defaultMessage='{name} favourited your status' values={{ name: link }} />
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<StatusContainer id={notification.get('status')} account={notification.get('account')} muted withDismiss />
|
<StatusContainer id={notification.get('status')} account={notification.get('account')} muted withDismiss hidden={!!this.props.hidden} />
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@ -60,7 +62,7 @@ export default class Notification extends ImmutablePureComponent {
|
||||||
<FormattedMessage id='notification.reblog' defaultMessage='{name} boosted your status' values={{ name: link }} />
|
<FormattedMessage id='notification.reblog' defaultMessage='{name} boosted your status' values={{ name: link }} />
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<StatusContainer id={notification.get('status')} account={notification.get('account')} muted withDismiss />
|
<StatusContainer id={notification.get('status')} account={notification.get('account')} muted withDismiss hidden={this.props.hidden} />
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
@ -16,8 +16,8 @@ import { defineMessages, injectIntl, FormattedMessage } from 'react-intl';
|
||||||
import ColumnSettingsContainer from './containers/column_settings_container';
|
import ColumnSettingsContainer from './containers/column_settings_container';
|
||||||
import { createSelector } from 'reselect';
|
import { createSelector } from 'reselect';
|
||||||
import { List as ImmutableList } from 'immutable';
|
import { List as ImmutableList } from 'immutable';
|
||||||
import LoadMore from '../../components/load_more';
|
|
||||||
import { debounce } from 'lodash';
|
import { debounce } from 'lodash';
|
||||||
|
import ScrollableList from '../../components/scrollable_list';
|
||||||
|
|
||||||
const messages = defineMessages({
|
const messages = defineMessages({
|
||||||
title: { id: 'column.notifications', defaultMessage: 'Notifications' },
|
title: { id: 'column.notifications', defaultMessage: 'Notifications' },
|
||||||
|
@ -68,40 +68,18 @@ export default class Notifications extends React.PureComponent {
|
||||||
trackScroll: true,
|
trackScroll: true,
|
||||||
};
|
};
|
||||||
|
|
||||||
dispatchExpandNotifications = debounce(() => {
|
handleScrollToBottom = debounce(() => {
|
||||||
|
this.props.dispatch(scrollTopNotifications(false));
|
||||||
this.props.dispatch(expandNotifications());
|
this.props.dispatch(expandNotifications());
|
||||||
}, 300, { leading: true });
|
}, 300, { leading: true });
|
||||||
|
|
||||||
dispatchScrollToTop = debounce((top) => {
|
handleScrollToTop = debounce(() => {
|
||||||
this.props.dispatch(scrollTopNotifications(top));
|
this.props.dispatch(scrollTopNotifications(true));
|
||||||
}, 100);
|
}, 100);
|
||||||
|
|
||||||
handleScroll = (e) => {
|
handleScroll = debounce(() => {
|
||||||
const { scrollTop, scrollHeight, clientHeight } = e.target;
|
this.props.dispatch(scrollTopNotifications(false));
|
||||||
const offset = scrollHeight - scrollTop - clientHeight;
|
}, 100);
|
||||||
this._oldScrollPosition = scrollHeight - scrollTop;
|
|
||||||
|
|
||||||
if (250 > offset && this.props.hasMore && !this.props.isLoading) {
|
|
||||||
this.dispatchExpandNotifications();
|
|
||||||
}
|
|
||||||
|
|
||||||
if (scrollTop < 100) {
|
|
||||||
this.dispatchScrollToTop(true);
|
|
||||||
} else {
|
|
||||||
this.dispatchScrollToTop(false);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
componentDidUpdate (prevProps) {
|
|
||||||
if (this.node.scrollTop > 0 && (prevProps.notifications.size < this.props.notifications.size && prevProps.notifications.first() !== this.props.notifications.first() && !!this._oldScrollPosition)) {
|
|
||||||
this.node.scrollTop = this.node.scrollHeight - this._oldScrollPosition;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
handleLoadMore = (e) => {
|
|
||||||
e.preventDefault();
|
|
||||||
this.dispatchExpandNotifications();
|
|
||||||
}
|
|
||||||
|
|
||||||
handlePin = () => {
|
handlePin = () => {
|
||||||
const { columnId, dispatch } = this.props;
|
const { columnId, dispatch } = this.props;
|
||||||
|
@ -122,10 +100,6 @@ export default class Notifications extends React.PureComponent {
|
||||||
this.column.scrollTop();
|
this.column.scrollTop();
|
||||||
}
|
}
|
||||||
|
|
||||||
setRef = (c) => {
|
|
||||||
this.node = c;
|
|
||||||
}
|
|
||||||
|
|
||||||
setColumnRef = c => {
|
setColumnRef = c => {
|
||||||
this.column = c;
|
this.column = c;
|
||||||
}
|
}
|
||||||
|
@ -133,52 +107,34 @@ export default class Notifications extends React.PureComponent {
|
||||||
render () {
|
render () {
|
||||||
const { intl, notifications, shouldUpdateScroll, isLoading, isUnread, columnId, multiColumn, hasMore } = this.props;
|
const { intl, notifications, shouldUpdateScroll, isLoading, isUnread, columnId, multiColumn, hasMore } = this.props;
|
||||||
const pinned = !!columnId;
|
const pinned = !!columnId;
|
||||||
|
const emptyMessage = <FormattedMessage id='empty_column.notifications' defaultMessage="You don't have any notifications yet. Interact with others to start the conversation." />;
|
||||||
|
|
||||||
let loadMore = '';
|
let scrollableContent = null;
|
||||||
let scrollableArea = '';
|
|
||||||
let unread = '';
|
|
||||||
let scrollContainer = '';
|
|
||||||
|
|
||||||
if (!isLoading && hasMore) {
|
if (isLoading && this.scrollableContent) {
|
||||||
loadMore = <LoadMore onClick={this.handleLoadMore} />;
|
scrollableContent = this.scrollableContent;
|
||||||
}
|
|
||||||
|
|
||||||
if (isUnread) {
|
|
||||||
unread = <div className='notifications__unread-indicator' />;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (isLoading && this.scrollableArea) {
|
|
||||||
scrollableArea = this.scrollableArea;
|
|
||||||
} else if (notifications.size > 0 || hasMore) {
|
} else if (notifications.size > 0 || hasMore) {
|
||||||
scrollableArea = (
|
scrollableContent = notifications.map((item) => <NotificationContainer key={item.get('id')} notification={item} accountId={item.get('account')} />);
|
||||||
<div className='scrollable' onScroll={this.handleScroll} ref={this.setRef}>
|
|
||||||
{unread}
|
|
||||||
|
|
||||||
<div>
|
|
||||||
{notifications.map(item => <NotificationContainer key={item.get('id')} notification={item} accountId={item.get('account')} />)}
|
|
||||||
{loadMore}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
} else {
|
} else {
|
||||||
scrollableArea = (
|
scrollableContent = null;
|
||||||
<div className='empty-column-indicator' ref={this.setRef}>
|
|
||||||
<FormattedMessage id='empty_column.notifications' defaultMessage="You don't have any notifications yet. Interact with others to start the conversation." />
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (pinned) {
|
this.scrollableContent = scrollableContent;
|
||||||
scrollContainer = scrollableArea;
|
|
||||||
} else {
|
|
||||||
scrollContainer = (
|
|
||||||
<ScrollContainer scrollKey={`notifications-${columnId}`} shouldUpdateScroll={shouldUpdateScroll}>
|
|
||||||
{scrollableArea}
|
|
||||||
</ScrollContainer>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
this.scrollableArea = scrollableArea;
|
const scrollContainer = (
|
||||||
|
<ScrollableList
|
||||||
|
scrollKey={`notifications-${columnId}`}
|
||||||
|
isLoading={isLoading}
|
||||||
|
hasMore={hasMore}
|
||||||
|
emptyMessage={emptyMessage}
|
||||||
|
onScrollToBottom={this.handleScrollToBottom}
|
||||||
|
onScrollToTop={this.handleScrollToTop}
|
||||||
|
onScroll={this.handleScroll}
|
||||||
|
shouldUpdateScroll={shouldUpdateScroll}
|
||||||
|
>
|
||||||
|
{scrollableContent}
|
||||||
|
</ScrollableList>
|
||||||
|
);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Column
|
<Column
|
||||||
|
|
|
@ -7,15 +7,11 @@ import ColumnHeader from '../../components/column_header';
|
||||||
import {
|
import {
|
||||||
refreshPublicTimeline,
|
refreshPublicTimeline,
|
||||||
expandPublicTimeline,
|
expandPublicTimeline,
|
||||||
updateTimeline,
|
|
||||||
deleteFromTimelines,
|
|
||||||
connectTimeline,
|
|
||||||
disconnectTimeline,
|
|
||||||
} from '../../actions/timelines';
|
} from '../../actions/timelines';
|
||||||
import { addColumn, removeColumn, moveColumn } from '../../actions/columns';
|
import { addColumn, removeColumn, moveColumn } from '../../actions/columns';
|
||||||
import { defineMessages, injectIntl, FormattedMessage } from 'react-intl';
|
import { defineMessages, injectIntl, FormattedMessage } from 'react-intl';
|
||||||
import ColumnSettingsContainer from './containers/column_settings_container';
|
import ColumnSettingsContainer from './containers/column_settings_container';
|
||||||
import createStream from '../../stream';
|
import { connectPublicStream } from '../../actions/streaming';
|
||||||
|
|
||||||
const messages = defineMessages({
|
const messages = defineMessages({
|
||||||
title: { id: 'column.public', defaultMessage: 'Federated timeline' },
|
title: { id: 'column.public', defaultMessage: 'Federated timeline' },
|
||||||
|
@ -23,8 +19,6 @@ const messages = defineMessages({
|
||||||
|
|
||||||
const mapStateToProps = state => ({
|
const mapStateToProps = state => ({
|
||||||
hasUnread: state.getIn(['timelines', 'public', 'unread']) > 0,
|
hasUnread: state.getIn(['timelines', 'public', 'unread']) > 0,
|
||||||
streamingAPIBaseURL: state.getIn(['meta', 'streaming_api_base_url']),
|
|
||||||
accessToken: state.getIn(['meta', 'access_token']),
|
|
||||||
});
|
});
|
||||||
|
|
||||||
@connect(mapStateToProps)
|
@connect(mapStateToProps)
|
||||||
|
@ -36,8 +30,6 @@ export default class PublicTimeline extends React.PureComponent {
|
||||||
intl: PropTypes.object.isRequired,
|
intl: PropTypes.object.isRequired,
|
||||||
columnId: PropTypes.string,
|
columnId: PropTypes.string,
|
||||||
multiColumn: PropTypes.bool,
|
multiColumn: PropTypes.bool,
|
||||||
streamingAPIBaseURL: PropTypes.string.isRequired,
|
|
||||||
accessToken: PropTypes.string.isRequired,
|
|
||||||
hasUnread: PropTypes.bool,
|
hasUnread: PropTypes.bool,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -61,46 +53,16 @@ export default class PublicTimeline extends React.PureComponent {
|
||||||
}
|
}
|
||||||
|
|
||||||
componentDidMount () {
|
componentDidMount () {
|
||||||
const { dispatch, streamingAPIBaseURL, accessToken } = this.props;
|
const { dispatch } = this.props;
|
||||||
|
|
||||||
dispatch(refreshPublicTimeline());
|
dispatch(refreshPublicTimeline());
|
||||||
|
this.disconnect = dispatch(connectPublicStream());
|
||||||
if (typeof this._subscription !== 'undefined') {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
this._subscription = createStream(streamingAPIBaseURL, accessToken, 'public', {
|
|
||||||
|
|
||||||
connected () {
|
|
||||||
dispatch(connectTimeline('public'));
|
|
||||||
},
|
|
||||||
|
|
||||||
reconnected () {
|
|
||||||
dispatch(connectTimeline('public'));
|
|
||||||
},
|
|
||||||
|
|
||||||
disconnected () {
|
|
||||||
dispatch(disconnectTimeline('public'));
|
|
||||||
},
|
|
||||||
|
|
||||||
received (data) {
|
|
||||||
switch(data.event) {
|
|
||||||
case 'update':
|
|
||||||
dispatch(updateTimeline('public', JSON.parse(data.payload)));
|
|
||||||
break;
|
|
||||||
case 'delete':
|
|
||||||
dispatch(deleteFromTimelines(data.payload));
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
componentWillUnmount () {
|
componentWillUnmount () {
|
||||||
if (typeof this._subscription !== 'undefined') {
|
if (this.disconnect) {
|
||||||
this._subscription.close();
|
this.disconnect();
|
||||||
this._subscription = null;
|
this.disconnect = null;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,18 @@
|
||||||
|
import React from 'react';
|
||||||
|
import ComposeFormContainer from '../../compose/containers/compose_form_container';
|
||||||
|
import NotificationsContainer from '../../ui/containers/notifications_container';
|
||||||
|
import LoadingBarContainer from '../../ui/containers/loading_bar_container';
|
||||||
|
|
||||||
|
export default class Compose extends React.PureComponent {
|
||||||
|
|
||||||
|
render () {
|
||||||
|
return (
|
||||||
|
<div>
|
||||||
|
<ComposeFormContainer />
|
||||||
|
<NotificationsContainer />
|
||||||
|
<LoadingBarContainer className='loading-bar' />
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -14,6 +14,9 @@ const messages = defineMessages({
|
||||||
favourite: { id: 'status.favourite', defaultMessage: 'Favourite' },
|
favourite: { id: 'status.favourite', defaultMessage: 'Favourite' },
|
||||||
report: { id: 'status.report', defaultMessage: 'Report @{name}' },
|
report: { id: 'status.report', defaultMessage: 'Report @{name}' },
|
||||||
share: { id: 'status.share', defaultMessage: 'Share' },
|
share: { id: 'status.share', defaultMessage: 'Share' },
|
||||||
|
pin: { id: 'status.pin', defaultMessage: 'Pin on profile' },
|
||||||
|
unpin: { id: 'status.unpin', defaultMessage: 'Unpin from profile' },
|
||||||
|
embed: { id: 'status.embed', defaultMessage: 'Embed' },
|
||||||
});
|
});
|
||||||
|
|
||||||
@injectIntl
|
@injectIntl
|
||||||
|
@ -31,6 +34,8 @@ export default class ActionBar extends React.PureComponent {
|
||||||
onDelete: PropTypes.func.isRequired,
|
onDelete: PropTypes.func.isRequired,
|
||||||
onMention: PropTypes.func.isRequired,
|
onMention: PropTypes.func.isRequired,
|
||||||
onReport: PropTypes.func,
|
onReport: PropTypes.func,
|
||||||
|
onPin: PropTypes.func,
|
||||||
|
onEmbed: PropTypes.func,
|
||||||
me: PropTypes.number.isRequired,
|
me: PropTypes.number.isRequired,
|
||||||
intl: PropTypes.object.isRequired,
|
intl: PropTypes.object.isRequired,
|
||||||
};
|
};
|
||||||
|
@ -59,6 +64,10 @@ export default class ActionBar extends React.PureComponent {
|
||||||
this.props.onReport(this.props.status);
|
this.props.onReport(this.props.status);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
handlePinClick = () => {
|
||||||
|
this.props.onPin(this.props.status);
|
||||||
|
}
|
||||||
|
|
||||||
handleShare = () => {
|
handleShare = () => {
|
||||||
navigator.share({
|
navigator.share({
|
||||||
text: this.props.status.get('search_index'),
|
text: this.props.status.get('search_index'),
|
||||||
|
@ -66,12 +75,26 @@ export default class ActionBar extends React.PureComponent {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
handleEmbed = () => {
|
||||||
|
this.props.onEmbed(this.props.status);
|
||||||
|
}
|
||||||
|
|
||||||
render () {
|
render () {
|
||||||
const { status, me, intl } = this.props;
|
const { status, me, intl } = this.props;
|
||||||
|
|
||||||
|
const publicStatus = ['public', 'unlisted'].includes(status.get('visibility'));
|
||||||
|
|
||||||
let menu = [];
|
let menu = [];
|
||||||
|
|
||||||
|
if (publicStatus) {
|
||||||
|
menu.push({ text: intl.formatMessage(messages.embed), action: this.handleEmbed });
|
||||||
|
}
|
||||||
|
|
||||||
if (me === status.getIn(['account', 'id'])) {
|
if (me === status.getIn(['account', 'id'])) {
|
||||||
|
if (publicStatus) {
|
||||||
|
menu.push({ text: intl.formatMessage(status.get('pinned') ? messages.unpin : messages.pin), action: this.handlePinClick });
|
||||||
|
}
|
||||||
|
|
||||||
menu.push({ text: intl.formatMessage(messages.delete), action: this.handleDeleteClick });
|
menu.push({ text: intl.formatMessage(messages.delete), action: this.handleDeleteClick });
|
||||||
} else {
|
} else {
|
||||||
menu.push({ text: intl.formatMessage(messages.mention, { name: status.getIn(['account', 'username']) }), action: this.handleMentionClick });
|
menu.push({ text: intl.formatMessage(messages.mention, { name: status.getIn(['account', 'username']) }), action: this.handleMentionClick });
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
import ImmutablePropTypes from 'react-immutable-proptypes';
|
import ImmutablePropTypes from 'react-immutable-proptypes';
|
||||||
import punycode from 'punycode';
|
import punycode from 'punycode';
|
||||||
|
import classnames from 'classnames';
|
||||||
|
|
||||||
const IDNA_PREFIX = 'xn--';
|
const IDNA_PREFIX = 'xn--';
|
||||||
|
|
||||||
|
@ -32,7 +33,7 @@ export default class Card extends React.PureComponent {
|
||||||
if (card.get('image')) {
|
if (card.get('image')) {
|
||||||
image = (
|
image = (
|
||||||
<div className='status-card__image'>
|
<div className='status-card__image'>
|
||||||
<img src={card.get('image')} alt={card.get('title')} className='status-card__image-image' />
|
<img src={card.get('image')} alt={card.get('title')} className='status-card__image-image' width={card.get('width')} height={card.get('height')} />
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@ -41,8 +42,12 @@ export default class Card extends React.PureComponent {
|
||||||
provider = decodeIDNA(getHostname(card.get('url')));
|
provider = decodeIDNA(getHostname(card.get('url')));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const className = classnames('status-card', {
|
||||||
|
'horizontal': card.get('width') > card.get('height'),
|
||||||
|
});
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<a href={card.get('url')} className='status-card' target='_blank' rel='noopener'>
|
<a href={card.get('url')} className={className} target='_blank' rel='noopener'>
|
||||||
{image}
|
{image}
|
||||||
|
|
||||||
<div className='status-card__content'>
|
<div className='status-card__content'>
|
||||||
|
|
|
@ -12,6 +12,8 @@ import {
|
||||||
unfavourite,
|
unfavourite,
|
||||||
reblog,
|
reblog,
|
||||||
unreblog,
|
unreblog,
|
||||||
|
pin,
|
||||||
|
unpin,
|
||||||
} from '../../actions/interactions';
|
} from '../../actions/interactions';
|
||||||
import {
|
import {
|
||||||
replyCompose,
|
replyCompose,
|
||||||
|
@ -89,6 +91,14 @@ export default class Status extends ImmutablePureComponent {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
handlePin = (status) => {
|
||||||
|
if (status.get('pinned')) {
|
||||||
|
this.props.dispatch(unpin(status));
|
||||||
|
} else {
|
||||||
|
this.props.dispatch(pin(status));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
handleReplyClick = (status) => {
|
handleReplyClick = (status) => {
|
||||||
this.props.dispatch(replyCompose(status, this.context.router.history));
|
this.props.dispatch(replyCompose(status, this.context.router.history));
|
||||||
}
|
}
|
||||||
|
@ -139,6 +149,10 @@ export default class Status extends ImmutablePureComponent {
|
||||||
this.props.dispatch(initReport(status.get('account'), status));
|
this.props.dispatch(initReport(status.get('account'), status));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
handleEmbed = (status) => {
|
||||||
|
this.props.dispatch(openModal('EMBED', { url: status.get('url') }));
|
||||||
|
}
|
||||||
|
|
||||||
renderChildren (list) {
|
renderChildren (list) {
|
||||||
return list.map(id => <StatusContainer key={id} id={id} />);
|
return list.map(id => <StatusContainer key={id} id={id} />);
|
||||||
}
|
}
|
||||||
|
@ -190,6 +204,8 @@ export default class Status extends ImmutablePureComponent {
|
||||||
onDelete={this.handleDeleteClick}
|
onDelete={this.handleDeleteClick}
|
||||||
onMention={this.handleMentionClick}
|
onMention={this.handleMentionClick}
|
||||||
onReport={this.handleReport}
|
onReport={this.handleReport}
|
||||||
|
onPin={this.handlePin}
|
||||||
|
onEmbed={this.handleEmbed}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
{descendants}
|
{descendants}
|
||||||
|
|
|
@ -25,6 +25,17 @@ export default class Column extends React.PureComponent {
|
||||||
this._interruptScrollAnimation = scrollTop(scrollable);
|
this._interruptScrollAnimation = scrollTop(scrollable);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
scrollTop () {
|
||||||
|
const scrollable = this.node.querySelector('.scrollable');
|
||||||
|
|
||||||
|
if (!scrollable) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
this._interruptScrollAnimation = scrollTop(scrollable);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
handleScroll = debounce(() => {
|
handleScroll = debounce(() => {
|
||||||
if (typeof this._interruptScrollAnimation !== 'undefined') {
|
if (typeof this._interruptScrollAnimation !== 'undefined') {
|
||||||
this._interruptScrollAnimation();
|
this._interruptScrollAnimation();
|
||||||
|
|
|
@ -12,6 +12,7 @@ import ColumnLoading from './column_loading';
|
||||||
import BundleColumnError from './bundle_column_error';
|
import BundleColumnError from './bundle_column_error';
|
||||||
import { Compose, Notifications, HomeTimeline, CommunityTimeline, PublicTimeline, HashtagTimeline, FavouritedStatuses } from '../../ui/util/async-components';
|
import { Compose, Notifications, HomeTimeline, CommunityTimeline, PublicTimeline, HashtagTimeline, FavouritedStatuses } from '../../ui/util/async-components';
|
||||||
|
|
||||||
|
import detectPassiveEvents from 'detect-passive-events';
|
||||||
import { scrollRight } from '../../../scroll';
|
import { scrollRight } from '../../../scroll';
|
||||||
|
|
||||||
const componentMap = {
|
const componentMap = {
|
||||||
|
@ -24,7 +25,7 @@ const componentMap = {
|
||||||
'FAVOURITES': FavouritedStatuses,
|
'FAVOURITES': FavouritedStatuses,
|
||||||
};
|
};
|
||||||
|
|
||||||
@injectIntl
|
@component => injectIntl(component, { withRef: true })
|
||||||
export default class ColumnsArea extends ImmutablePureComponent {
|
export default class ColumnsArea extends ImmutablePureComponent {
|
||||||
|
|
||||||
static contextTypes = {
|
static contextTypes = {
|
||||||
|
@ -47,16 +48,36 @@ export default class ColumnsArea extends ImmutablePureComponent {
|
||||||
}
|
}
|
||||||
|
|
||||||
componentDidMount() {
|
componentDidMount() {
|
||||||
|
if (!this.props.singleColumn) {
|
||||||
|
this.node.addEventListener('wheel', this.handleWheel, detectPassiveEvents.hasSupport ? { passive: true } : false);
|
||||||
|
}
|
||||||
this.lastIndex = getIndex(this.context.router.history.location.pathname);
|
this.lastIndex = getIndex(this.context.router.history.location.pathname);
|
||||||
this.setState({ shouldAnimate: true });
|
this.setState({ shouldAnimate: true });
|
||||||
}
|
}
|
||||||
|
|
||||||
|
componentWillUpdate(nextProps) {
|
||||||
|
if (this.props.singleColumn !== nextProps.singleColumn && nextProps.singleColumn) {
|
||||||
|
this.node.removeEventListener('wheel', this.handleWheel);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
componentDidUpdate(prevProps) {
|
componentDidUpdate(prevProps) {
|
||||||
|
if (this.props.singleColumn !== prevProps.singleColumn && !this.props.singleColumn) {
|
||||||
|
this.node.addEventListener('wheel', this.handleWheel, detectPassiveEvents.hasSupport ? { passive: true } : false);
|
||||||
|
}
|
||||||
this.lastIndex = getIndex(this.context.router.history.location.pathname);
|
this.lastIndex = getIndex(this.context.router.history.location.pathname);
|
||||||
this.setState({ shouldAnimate: true });
|
this.setState({ shouldAnimate: true });
|
||||||
|
}
|
||||||
|
|
||||||
if (this.props.children !== prevProps.children && !this.props.singleColumn) {
|
componentWillUnmount () {
|
||||||
scrollRight(this.node);
|
if (!this.props.singleColumn) {
|
||||||
|
this.node.removeEventListener('wheel', this.handleWheel);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
handleChildrenContentChange() {
|
||||||
|
if (!this.props.singleColumn) {
|
||||||
|
scrollRight(this.node, this.node.scrollWidth - window.innerWidth);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -80,6 +101,14 @@ export default class ColumnsArea extends ImmutablePureComponent {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
handleWheel = () => {
|
||||||
|
if (typeof this._interruptScrollAnimation !== 'function') {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
this._interruptScrollAnimation();
|
||||||
|
}
|
||||||
|
|
||||||
setRef = (node) => {
|
setRef = (node) => {
|
||||||
this.node = node;
|
this.node = node;
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,84 @@
|
||||||
|
import React from 'react';
|
||||||
|
import PropTypes from 'prop-types';
|
||||||
|
import ImmutablePureComponent from 'react-immutable-pure-component';
|
||||||
|
import { FormattedMessage, injectIntl } from 'react-intl';
|
||||||
|
import axios from 'axios';
|
||||||
|
|
||||||
|
@injectIntl
|
||||||
|
export default class EmbedModal extends ImmutablePureComponent {
|
||||||
|
|
||||||
|
static propTypes = {
|
||||||
|
url: PropTypes.string.isRequired,
|
||||||
|
onClose: PropTypes.func.isRequired,
|
||||||
|
intl: PropTypes.object.isRequired,
|
||||||
|
}
|
||||||
|
|
||||||
|
state = {
|
||||||
|
loading: false,
|
||||||
|
oembed: null,
|
||||||
|
};
|
||||||
|
|
||||||
|
componentDidMount () {
|
||||||
|
const { url } = this.props;
|
||||||
|
|
||||||
|
this.setState({ loading: true });
|
||||||
|
|
||||||
|
axios.post('/api/web/embed', { url }).then(res => {
|
||||||
|
this.setState({ loading: false, oembed: res.data });
|
||||||
|
|
||||||
|
const iframeDocument = this.iframe.contentWindow.document;
|
||||||
|
|
||||||
|
iframeDocument.open();
|
||||||
|
iframeDocument.write(res.data.html);
|
||||||
|
iframeDocument.close();
|
||||||
|
|
||||||
|
iframeDocument.body.style.margin = 0;
|
||||||
|
this.iframe.height = iframeDocument.body.scrollHeight + 'px';
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
setIframeRef = c => {
|
||||||
|
this.iframe = c;
|
||||||
|
}
|
||||||
|
|
||||||
|
handleTextareaClick = (e) => {
|
||||||
|
e.target.select();
|
||||||
|
}
|
||||||
|
|
||||||
|
render () {
|
||||||
|
const { oembed } = this.state;
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className='modal-root__modal embed-modal'>
|
||||||
|
<h4><FormattedMessage id='status.embed' defaultMessage='Embed' /></h4>
|
||||||
|
|
||||||
|
<div className='embed-modal__container'>
|
||||||
|
<p className='hint'>
|
||||||
|
<FormattedMessage id='embed.instructions' defaultMessage='Embed this status on your website by copying the code below.' />
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<input
|
||||||
|
type='text'
|
||||||
|
className='embed-modal__html'
|
||||||
|
readOnly
|
||||||
|
value={oembed && oembed.html || ''}
|
||||||
|
onClick={this.handleTextareaClick}
|
||||||
|
/>
|
||||||
|
|
||||||
|
<p className='hint'>
|
||||||
|
<FormattedMessage id='embed.preview' defaultMessage='Here is what it will look like:' />
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<iframe
|
||||||
|
className='embed-modal__iframe'
|
||||||
|
scrolling='no'
|
||||||
|
frameBorder='0'
|
||||||
|
ref={this.setIframeRef}
|
||||||
|
title='preview'
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -14,6 +14,7 @@ import {
|
||||||
ConfirmationModal,
|
ConfirmationModal,
|
||||||
ReportModal,
|
ReportModal,
|
||||||
SettingsModal,
|
SettingsModal,
|
||||||
|
EmbedModal,
|
||||||
} from '../../../features/ui/util/async-components';
|
} from '../../../features/ui/util/async-components';
|
||||||
|
|
||||||
const MODAL_COMPONENTS = {
|
const MODAL_COMPONENTS = {
|
||||||
|
@ -25,6 +26,7 @@ const MODAL_COMPONENTS = {
|
||||||
'REPORT': ReportModal,
|
'REPORT': ReportModal,
|
||||||
'SETTINGS': SettingsModal,
|
'SETTINGS': SettingsModal,
|
||||||
'ACTIONS': () => Promise.resolve({ default: ActionsModal }),
|
'ACTIONS': () => Promise.resolve({ default: ActionsModal }),
|
||||||
|
'EMBED': EmbedModal,
|
||||||
};
|
};
|
||||||
|
|
||||||
export default class ModalRoot extends React.PureComponent {
|
export default class ModalRoot extends React.PureComponent {
|
||||||
|
|
|
@ -30,7 +30,7 @@ const PageOne = ({ acct, domain }) => (
|
||||||
<div>
|
<div>
|
||||||
<h1><FormattedMessage id='onboarding.page_one.welcome' defaultMessage='Welcome to {domain}!' values={{ domain }} /></h1>
|
<h1><FormattedMessage id='onboarding.page_one.welcome' defaultMessage='Welcome to {domain}!' values={{ domain }} /></h1>
|
||||||
<p><FormattedMessage id='onboarding.page_one.federation' defaultMessage='{domain} is an "instance" of Mastodon. Mastodon is a network of independent servers joining up to make one larger social network. We call these servers instances.' values={{ domain }} /></p>
|
<p><FormattedMessage id='onboarding.page_one.federation' defaultMessage='{domain} is an "instance" of Mastodon. Mastodon is a network of independent servers joining up to make one larger social network. We call these servers instances.' values={{ domain }} /></p>
|
||||||
<p><FormattedMessage id='onboarding.page_one.handle' defaultMessage='You are on {domain}, so your full handle is {handle}' values={{ domain, handle: <strong>{acct}@{domain}</strong> }} /></p>
|
<p><FormattedMessage id='onboarding.page_one.handle' defaultMessage='You are on {domain}, so your full handle is {handle}' values={{ domain, handle: <strong>@{acct}@{domain}</strong> }} /></p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
|
|
@ -5,4 +5,4 @@ const mapStateToProps = state => ({
|
||||||
columns: state.getIn(['settings', 'columns']),
|
columns: state.getIn(['settings', 'columns']),
|
||||||
});
|
});
|
||||||
|
|
||||||
export default connect(mapStateToProps)(ColumnsArea);
|
export default connect(mapStateToProps, null, null, { withRef: true })(ColumnsArea);
|
||||||
|
|
|
@ -1,12 +1,11 @@
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
import classNames from 'classnames';
|
|
||||||
import Redirect from 'react-router-dom/Redirect';
|
|
||||||
import NotificationsContainer from './containers/notifications_container';
|
import NotificationsContainer from './containers/notifications_container';
|
||||||
import PropTypes from 'prop-types';
|
import PropTypes from 'prop-types';
|
||||||
import LoadingBarContainer from './containers/loading_bar_container';
|
import LoadingBarContainer from './containers/loading_bar_container';
|
||||||
import TabsBar from './components/tabs_bar';
|
import TabsBar from './components/tabs_bar';
|
||||||
import ModalContainer from './containers/modal_container';
|
import ModalContainer from './containers/modal_container';
|
||||||
import { connect } from 'react-redux';
|
import { connect } from 'react-redux';
|
||||||
|
import { Redirect, withRouter } from 'react-router-dom';
|
||||||
import { isMobile } from '../../is_mobile';
|
import { isMobile } from '../../is_mobile';
|
||||||
import { debounce } from 'lodash';
|
import { debounce } from 'lodash';
|
||||||
import { uploadCompose } from '../../actions/compose';
|
import { uploadCompose } from '../../actions/compose';
|
||||||
|
@ -51,6 +50,7 @@ const mapStateToProps = state => ({
|
||||||
});
|
});
|
||||||
|
|
||||||
@connect(mapStateToProps)
|
@connect(mapStateToProps)
|
||||||
|
@withRouter
|
||||||
export default class UI extends React.PureComponent {
|
export default class UI extends React.PureComponent {
|
||||||
|
|
||||||
static contextTypes = {
|
static contextTypes = {
|
||||||
|
@ -65,6 +65,7 @@ export default class UI extends React.PureComponent {
|
||||||
systemFontUi: PropTypes.bool,
|
systemFontUi: PropTypes.bool,
|
||||||
navbarUnder: PropTypes.bool,
|
navbarUnder: PropTypes.bool,
|
||||||
isComposing: PropTypes.bool,
|
isComposing: PropTypes.bool,
|
||||||
|
location: PropTypes.object,
|
||||||
};
|
};
|
||||||
|
|
||||||
state = {
|
state = {
|
||||||
|
@ -141,7 +142,7 @@ export default class UI extends React.PureComponent {
|
||||||
if (data.type === 'navigate') {
|
if (data.type === 'navigate') {
|
||||||
this.context.router.history.push(data.path);
|
this.context.router.history.push(data.path);
|
||||||
} else {
|
} else {
|
||||||
console.warn('Unknown message type:', data.type); // eslint-disable-line no-console
|
console.warn('Unknown message type:', data.type);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -175,6 +176,12 @@ export default class UI extends React.PureComponent {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
componentDidUpdate (prevProps) {
|
||||||
|
if (![this.props.location.pathname, '/'].includes(prevProps.location.pathname)) {
|
||||||
|
this.columnsAreaNode.handleChildrenContentChange();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
componentWillUnmount () {
|
componentWillUnmount () {
|
||||||
window.removeEventListener('resize', this.handleResize);
|
window.removeEventListener('resize', this.handleResize);
|
||||||
document.removeEventListener('dragenter', this.handleDragEnter);
|
document.removeEventListener('dragenter', this.handleDragEnter);
|
||||||
|
@ -188,6 +195,10 @@ export default class UI extends React.PureComponent {
|
||||||
this.node = c;
|
this.node = c;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
setColumnsAreaRef = (c) => {
|
||||||
|
this.columnsAreaNode = c.getWrappedInstance().getWrappedInstance();
|
||||||
|
}
|
||||||
|
|
||||||
render () {
|
render () {
|
||||||
const { width, draggingOver } = this.state;
|
const { width, draggingOver } = this.state;
|
||||||
const { children, layout, isWide, navbarUnder } = this.props;
|
const { children, layout, isWide, navbarUnder } = this.props;
|
||||||
|
@ -212,7 +223,7 @@ export default class UI extends React.PureComponent {
|
||||||
return (
|
return (
|
||||||
<div className={className} ref={this.setRef}>
|
<div className={className} ref={this.setRef}>
|
||||||
{navbarUnder ? null : (<TabsBar />)}
|
{navbarUnder ? null : (<TabsBar />)}
|
||||||
<ColumnsAreaContainer singleColumn={isMobile(width, layout)}>
|
<ColumnsAreaContainer ref={this.setColumnsAreaRef} singleColumn={isMobile(width, layout)}>
|
||||||
<WrappedSwitch>
|
<WrappedSwitch>
|
||||||
<Redirect from='/' to='/getting-started' exact />
|
<Redirect from='/' to='/getting-started' exact />
|
||||||
<WrappedRoute path='/getting-started' component={GettingStarted} content={children} />
|
<WrappedRoute path='/getting-started' component={GettingStarted} content={children} />
|
||||||
|
|
|
@ -116,3 +116,7 @@ export function MediaGallery () {
|
||||||
export function VideoPlayer () {
|
export function VideoPlayer () {
|
||||||
return import(/* webpackChunkName: "status/video_player" */'../../../components/video_player');
|
return import(/* webpackChunkName: "status/video_player" */'../../../components/video_player');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function EmbedModal () {
|
||||||
|
return import(/* webpackChunkName: "modals/embed_modal" */'../components/embed_modal');
|
||||||
|
}
|
||||||
|
|
|
@ -47,7 +47,7 @@
|
||||||
"compose_form.lock_disclaimer.lock": "مقفل",
|
"compose_form.lock_disclaimer.lock": "مقفل",
|
||||||
"compose_form.placeholder": "فيمَ تفكّر؟",
|
"compose_form.placeholder": "فيمَ تفكّر؟",
|
||||||
"compose_form.privacy_disclaimer": "Your private status will be delivered to mentioned users on {domains}. Do you trust {domainsCount, plural, one {that server} other {those servers}}? Post privacy only works on Mastodon instances. If {domains} {domainsCount, plural, one {is not a Mastodon instance} other {are not Mastodon instances}}, there will be no indication that your post is private, and it may be boosted or otherwise made visible to unintended recipients.",
|
"compose_form.privacy_disclaimer": "Your private status will be delivered to mentioned users on {domains}. Do you trust {domainsCount, plural, one {that server} other {those servers}}? Post privacy only works on Mastodon instances. If {domains} {domainsCount, plural, one {is not a Mastodon instance} other {are not Mastodon instances}}, there will be no indication that your post is private, and it may be boosted or otherwise made visible to unintended recipients.",
|
||||||
"compose_form.publish": "بوّق !",
|
"compose_form.publish": "بوّق",
|
||||||
"compose_form.publish_loud": "{publish}!",
|
"compose_form.publish_loud": "{publish}!",
|
||||||
"compose_form.sensitive": "ضع علامة على الوسيط باعتباره حسّاس",
|
"compose_form.sensitive": "ضع علامة على الوسيط باعتباره حسّاس",
|
||||||
"compose_form.spoiler": "أخفِ النص واعرض تحذيرا",
|
"compose_form.spoiler": "أخفِ النص واعرض تحذيرا",
|
||||||
|
@ -63,6 +63,8 @@
|
||||||
"confirmations.mute.message": "هل أنت متأكد أنك تريد كتم {name} ؟",
|
"confirmations.mute.message": "هل أنت متأكد أنك تريد كتم {name} ؟",
|
||||||
"confirmations.unfollow.confirm": "Unfollow",
|
"confirmations.unfollow.confirm": "Unfollow",
|
||||||
"confirmations.unfollow.message": "Are you sure you want to unfollow {name}?",
|
"confirmations.unfollow.message": "Are you sure you want to unfollow {name}?",
|
||||||
|
"embed.instructions": "Embed this status on your website by copying the code below.",
|
||||||
|
"embed.preview": "Here is what it will look like:",
|
||||||
"emoji_button.activity": "الأنشطة",
|
"emoji_button.activity": "الأنشطة",
|
||||||
"emoji_button.flags": "الأعلام",
|
"emoji_button.flags": "الأعلام",
|
||||||
"emoji_button.food": "الطعام والشراب",
|
"emoji_button.food": "الطعام والشراب",
|
||||||
|
@ -162,12 +164,14 @@
|
||||||
"standalone.public_title": "A look inside...",
|
"standalone.public_title": "A look inside...",
|
||||||
"status.cannot_reblog": "تعذرت ترقية هذا المنشور",
|
"status.cannot_reblog": "تعذرت ترقية هذا المنشور",
|
||||||
"status.delete": "إحذف",
|
"status.delete": "إحذف",
|
||||||
|
"status.embed": "Embed",
|
||||||
"status.favourite": "أضف إلى المفضلة",
|
"status.favourite": "أضف إلى المفضلة",
|
||||||
"status.load_more": "حمّل المزيد",
|
"status.load_more": "حمّل المزيد",
|
||||||
"status.media_hidden": "الصورة مستترة",
|
"status.media_hidden": "الصورة مستترة",
|
||||||
"status.mention": "أذكُر @{name}",
|
"status.mention": "أذكُر @{name}",
|
||||||
"status.mute_conversation": "Mute conversation",
|
"status.mute_conversation": "Mute conversation",
|
||||||
"status.open": "وسع هذه المشاركة",
|
"status.open": "وسع هذه المشاركة",
|
||||||
|
"status.pin": "Pin on profile",
|
||||||
"status.reblog": "رَقِّي",
|
"status.reblog": "رَقِّي",
|
||||||
"status.reblogged_by": "{name} رقى",
|
"status.reblogged_by": "{name} رقى",
|
||||||
"status.reply": "ردّ",
|
"status.reply": "ردّ",
|
||||||
|
@ -179,6 +183,7 @@
|
||||||
"status.show_less": "إعرض أقلّ",
|
"status.show_less": "إعرض أقلّ",
|
||||||
"status.show_more": "أظهر المزيد",
|
"status.show_more": "أظهر المزيد",
|
||||||
"status.unmute_conversation": "Unmute conversation",
|
"status.unmute_conversation": "Unmute conversation",
|
||||||
|
"status.unpin": "Unpin from profile",
|
||||||
"tabs_bar.compose": "تحرير",
|
"tabs_bar.compose": "تحرير",
|
||||||
"tabs_bar.federated_timeline": "الموحَّد",
|
"tabs_bar.federated_timeline": "الموحَّد",
|
||||||
"tabs_bar.home": "الرئيسية",
|
"tabs_bar.home": "الرئيسية",
|
||||||
|
|
|
@ -63,6 +63,8 @@
|
||||||
"confirmations.mute.message": "Are you sure you want to mute {name}?",
|
"confirmations.mute.message": "Are you sure you want to mute {name}?",
|
||||||
"confirmations.unfollow.confirm": "Unfollow",
|
"confirmations.unfollow.confirm": "Unfollow",
|
||||||
"confirmations.unfollow.message": "Are you sure you want to unfollow {name}?",
|
"confirmations.unfollow.message": "Are you sure you want to unfollow {name}?",
|
||||||
|
"embed.instructions": "Embed this status on your website by copying the code below.",
|
||||||
|
"embed.preview": "Here is what it will look like:",
|
||||||
"emoji_button.activity": "Activity",
|
"emoji_button.activity": "Activity",
|
||||||
"emoji_button.flags": "Flags",
|
"emoji_button.flags": "Flags",
|
||||||
"emoji_button.food": "Food & Drink",
|
"emoji_button.food": "Food & Drink",
|
||||||
|
@ -162,12 +164,14 @@
|
||||||
"standalone.public_title": "A look inside...",
|
"standalone.public_title": "A look inside...",
|
||||||
"status.cannot_reblog": "This post cannot be boosted",
|
"status.cannot_reblog": "This post cannot be boosted",
|
||||||
"status.delete": "Изтриване",
|
"status.delete": "Изтриване",
|
||||||
|
"status.embed": "Embed",
|
||||||
"status.favourite": "Предпочитани",
|
"status.favourite": "Предпочитани",
|
||||||
"status.load_more": "Load more",
|
"status.load_more": "Load more",
|
||||||
"status.media_hidden": "Media hidden",
|
"status.media_hidden": "Media hidden",
|
||||||
"status.mention": "Споменаване",
|
"status.mention": "Споменаване",
|
||||||
"status.mute_conversation": "Mute conversation",
|
"status.mute_conversation": "Mute conversation",
|
||||||
"status.open": "Expand this status",
|
"status.open": "Expand this status",
|
||||||
|
"status.pin": "Pin on profile",
|
||||||
"status.reblog": "Споделяне",
|
"status.reblog": "Споделяне",
|
||||||
"status.reblogged_by": "{name} сподели",
|
"status.reblogged_by": "{name} сподели",
|
||||||
"status.reply": "Отговор",
|
"status.reply": "Отговор",
|
||||||
|
@ -179,6 +183,7 @@
|
||||||
"status.show_less": "Show less",
|
"status.show_less": "Show less",
|
||||||
"status.show_more": "Show more",
|
"status.show_more": "Show more",
|
||||||
"status.unmute_conversation": "Unmute conversation",
|
"status.unmute_conversation": "Unmute conversation",
|
||||||
|
"status.unpin": "Unpin from profile",
|
||||||
"tabs_bar.compose": "Съставяне",
|
"tabs_bar.compose": "Съставяне",
|
||||||
"tabs_bar.federated_timeline": "Federated",
|
"tabs_bar.federated_timeline": "Federated",
|
||||||
"tabs_bar.home": "Начало",
|
"tabs_bar.home": "Начало",
|
||||||
|
|
|
@ -63,6 +63,8 @@
|
||||||
"confirmations.mute.message": "Estàs segur que vols silenciar {name}?",
|
"confirmations.mute.message": "Estàs segur que vols silenciar {name}?",
|
||||||
"confirmations.unfollow.confirm": "Unfollow",
|
"confirmations.unfollow.confirm": "Unfollow",
|
||||||
"confirmations.unfollow.message": "Are you sure you want to unfollow {name}?",
|
"confirmations.unfollow.message": "Are you sure you want to unfollow {name}?",
|
||||||
|
"embed.instructions": "Embed this status on your website by copying the code below.",
|
||||||
|
"embed.preview": "Here is what it will look like:",
|
||||||
"emoji_button.activity": "Activitat",
|
"emoji_button.activity": "Activitat",
|
||||||
"emoji_button.flags": "Flags",
|
"emoji_button.flags": "Flags",
|
||||||
"emoji_button.food": "Menjar i Beure",
|
"emoji_button.food": "Menjar i Beure",
|
||||||
|
@ -162,12 +164,14 @@
|
||||||
"standalone.public_title": "A look inside...",
|
"standalone.public_title": "A look inside...",
|
||||||
"status.cannot_reblog": "Aquesta publicació no pot ser retootejada",
|
"status.cannot_reblog": "Aquesta publicació no pot ser retootejada",
|
||||||
"status.delete": "Esborrar",
|
"status.delete": "Esborrar",
|
||||||
|
"status.embed": "Embed",
|
||||||
"status.favourite": "Favorit",
|
"status.favourite": "Favorit",
|
||||||
"status.load_more": "Carrega més",
|
"status.load_more": "Carrega més",
|
||||||
"status.media_hidden": "Multimèdia amagat",
|
"status.media_hidden": "Multimèdia amagat",
|
||||||
"status.mention": "Esmentar @{name}",
|
"status.mention": "Esmentar @{name}",
|
||||||
"status.mute_conversation": "Silenciar conversació",
|
"status.mute_conversation": "Silenciar conversació",
|
||||||
"status.open": "Ampliar aquest estat",
|
"status.open": "Ampliar aquest estat",
|
||||||
|
"status.pin": "Pin on profile",
|
||||||
"status.reblog": "Boost",
|
"status.reblog": "Boost",
|
||||||
"status.reblogged_by": "{name} ha retootejat",
|
"status.reblogged_by": "{name} ha retootejat",
|
||||||
"status.reply": "Respondre",
|
"status.reply": "Respondre",
|
||||||
|
@ -179,6 +183,7 @@
|
||||||
"status.show_less": "Mostra menys",
|
"status.show_less": "Mostra menys",
|
||||||
"status.show_more": "Mostra més",
|
"status.show_more": "Mostra més",
|
||||||
"status.unmute_conversation": "Activar conversació",
|
"status.unmute_conversation": "Activar conversació",
|
||||||
|
"status.unpin": "Unpin from profile",
|
||||||
"tabs_bar.compose": "Compondre",
|
"tabs_bar.compose": "Compondre",
|
||||||
"tabs_bar.federated_timeline": "Federada",
|
"tabs_bar.federated_timeline": "Federada",
|
||||||
"tabs_bar.home": "Inici",
|
"tabs_bar.home": "Inici",
|
||||||
|
|
|
@ -1,31 +1,31 @@
|
||||||
{
|
{
|
||||||
"account.block": "@{name} blocken",
|
"account.block": "@{name} blocken",
|
||||||
"account.block_domain": "Hide everything from {domain}",
|
"account.block_domain": "Alles von {domain} verstecken",
|
||||||
"account.disclaimer_full": "Information below may reflect the user's profile incompletely.",
|
"account.disclaimer_full": "Hier aufgeführten Informationen können unvollständig sein.",
|
||||||
"account.edit_profile": "Profil bearbeiten",
|
"account.edit_profile": "Profil bearbeiten",
|
||||||
"account.follow": "Folgen",
|
"account.follow": "Folgen",
|
||||||
"account.followers": "Folgende",
|
"account.followers": "Folgende",
|
||||||
"account.follows": "Folgt",
|
"account.follows": "Folgt",
|
||||||
"account.follows_you": "Folgt dir",
|
"account.follows_you": "Folgt dir",
|
||||||
"account.media": "Media",
|
"account.media": "Medien",
|
||||||
"account.mention": "@{name} erwähnen",
|
"account.mention": "@{name} erwähnen",
|
||||||
"account.mute": "@{name} stummschalten",
|
"account.mute": "@{name} stummschalten",
|
||||||
"account.posts": "Beiträge",
|
"account.posts": "Beiträge",
|
||||||
"account.report": "@{name} melden",
|
"account.report": "@{name} melden",
|
||||||
"account.requested": "Warte auf Erlaubnis",
|
"account.requested": "Warte auf Erlaubnis. Klicke zum Abbrechen",
|
||||||
"account.share": "Share @{name}'s profile",
|
"account.share": "Profil von @{name} teilen",
|
||||||
"account.unblock": "@{name} entblocken",
|
"account.unblock": "@{name} entblocken",
|
||||||
"account.unblock_domain": "Unhide {domain}",
|
"account.unblock_domain": "{domain} wieder anzeigen",
|
||||||
"account.unfollow": "Entfolgen",
|
"account.unfollow": "Entfolgen",
|
||||||
"account.unmute": "@{name} nicht mehr stummschalten",
|
"account.unmute": "@{name} nicht mehr stummschalten",
|
||||||
"account.view_full_profile": "View full profile",
|
"account.view_full_profile": "Komplettes Profil anzeigen",
|
||||||
"boost_modal.combo": "Du kannst {combo} drücken, um dies beim nächsten Mal zu überspringen",
|
"boost_modal.combo": "Du kannst {combo} drücken, um dies beim nächsten Mal zu überspringen",
|
||||||
"bundle_column_error.body": "Something went wrong while loading this component.",
|
"bundle_column_error.body": "Etwas ist beim Laden schiefgelaufen.",
|
||||||
"bundle_column_error.retry": "Try again",
|
"bundle_column_error.retry": "Erneut versuchen",
|
||||||
"bundle_column_error.title": "Network error",
|
"bundle_column_error.title": "Netzwerkfehlher",
|
||||||
"bundle_modal_error.close": "Close",
|
"bundle_modal_error.close": "Schließen",
|
||||||
"bundle_modal_error.message": "Something went wrong while loading this component.",
|
"bundle_modal_error.message": "Etwas ist beim Laden schiefgelaufen.",
|
||||||
"bundle_modal_error.retry": "Try again",
|
"bundle_modal_error.retry": "Erneut versuchen",
|
||||||
"column.blocks": "Blockierte Benutzer",
|
"column.blocks": "Blockierte Benutzer",
|
||||||
"column.community": "Lokale Zeitleiste",
|
"column.community": "Lokale Zeitleiste",
|
||||||
"column.favourites": "Favoriten",
|
"column.favourites": "Favoriten",
|
||||||
|
@ -35,16 +35,16 @@
|
||||||
"column.notifications": "Mitteilungen",
|
"column.notifications": "Mitteilungen",
|
||||||
"column.public": "Gesamtes bekanntes Netz",
|
"column.public": "Gesamtes bekanntes Netz",
|
||||||
"column_back_button.label": "Zurück",
|
"column_back_button.label": "Zurück",
|
||||||
"column_header.hide_settings": "Hide settings",
|
"column_header.hide_settings": "Einstellungen verbergen",
|
||||||
"column_header.moveLeft_settings": "Move column to the left",
|
"column_header.moveLeft_settings": "Spalte links verschieben",
|
||||||
"column_header.moveRight_settings": "Move column to the right",
|
"column_header.moveRight_settings": "Spalte rechts verschieben",
|
||||||
"column_header.pin": "Pin",
|
"column_header.pin": "Anheften",
|
||||||
"column_header.show_settings": "Show settings",
|
"column_header.show_settings": "Einstellungen anzeigen",
|
||||||
"column_header.unpin": "Unpin",
|
"column_header.unpin": "Lösen",
|
||||||
"column_subheading.navigation": "Navigation",
|
"column_subheading.navigation": "Navigation",
|
||||||
"column_subheading.settings": "Settings",
|
"column_subheading.settings": "Einstellungen",
|
||||||
"compose_form.lock_disclaimer": "Your account is not {locked}. Anyone can follow you to view your follower-only posts.",
|
"compose_form.lock_disclaimer": "Dein Profil ist nicht {locked}. Jeder kann dir jederzeit folgen, um deine privaten Beiträge einzusehen.",
|
||||||
"compose_form.lock_disclaimer.lock": "locked",
|
"compose_form.lock_disclaimer.lock": "gesperrt",
|
||||||
"compose_form.placeholder": "Worüber möchtest du schreiben?",
|
"compose_form.placeholder": "Worüber möchtest du schreiben?",
|
||||||
"compose_form.privacy_disclaimer": "Dein privater Status wird an die genannten Benutzer auf den Domains {domains} zugestellt. Vertraust du {domainsCount, plural, one {diesem Server} other {diesen Servern}}? Private Beiträge funktionieren nur auf Mastodon-Instanzen. Wenn {domains} {domainsCount, plural, one {keine Mastodon-Instanz ist} other {keine Mastodon-Instanzen sind}}, wird es dort kein Anzeichen geben, dass dein Beitrag privat ist und er könnte geteilt oder anderweitig für unerwünschte Empfänger sichtbar gemacht werden.",
|
"compose_form.privacy_disclaimer": "Dein privater Status wird an die genannten Benutzer auf den Domains {domains} zugestellt. Vertraust du {domainsCount, plural, one {diesem Server} other {diesen Servern}}? Private Beiträge funktionieren nur auf Mastodon-Instanzen. Wenn {domains} {domainsCount, plural, one {keine Mastodon-Instanz ist} other {keine Mastodon-Instanzen sind}}, wird es dort kein Anzeichen geben, dass dein Beitrag privat ist und er könnte geteilt oder anderweitig für unerwünschte Empfänger sichtbar gemacht werden.",
|
||||||
"compose_form.publish": "Tröt",
|
"compose_form.publish": "Tröt",
|
||||||
|
@ -52,41 +52,43 @@
|
||||||
"compose_form.sensitive": "Medien als heikel markieren",
|
"compose_form.sensitive": "Medien als heikel markieren",
|
||||||
"compose_form.spoiler": "Text hinter Warnung verbergen",
|
"compose_form.spoiler": "Text hinter Warnung verbergen",
|
||||||
"compose_form.spoiler_placeholder": "Inhaltswarnung",
|
"compose_form.spoiler_placeholder": "Inhaltswarnung",
|
||||||
"confirmation_modal.cancel": "Cancel",
|
"confirmation_modal.cancel": "Abbrechen",
|
||||||
"confirmations.block.confirm": "Block",
|
"confirmations.block.confirm": "Blockieren",
|
||||||
"confirmations.block.message": "Are you sure you want to block {name}?",
|
"confirmations.block.message": "Bist du dir sicher, dass du {name} blockieren möchtest?",
|
||||||
"confirmations.delete.confirm": "Delete",
|
"confirmations.delete.confirm": "Löschen",
|
||||||
"confirmations.delete.message": "Are you sure you want to delete this status?",
|
"confirmations.delete.message": "Bist du dir sicher, dass du diesen Beitrag löschen möchstest?",
|
||||||
"confirmations.domain_block.confirm": "Hide entire domain",
|
"confirmations.domain_block.confirm": "Die ganze Domain verbergen",
|
||||||
"confirmations.domain_block.message": "Are you really, really sure you want to block the entire {domain}? In most cases a few targeted blocks or mutes are sufficient and preferable.",
|
"confirmations.domain_block.message": "Bist du dir wirklich sicher, dass du die ganze Domain {domain} verbergen willst? In den meisten Fällen sind ein paar gezielte Blocks genug.",
|
||||||
"confirmations.mute.confirm": "Mute",
|
"confirmations.mute.confirm": "Stummschalten",
|
||||||
"confirmations.mute.message": "Are you sure you want to mute {name}?",
|
"confirmations.mute.message": "Bist du dir sicher, dass du {name} stummschalten möchstest?",
|
||||||
"confirmations.unfollow.confirm": "Unfollow",
|
"confirmations.unfollow.confirm": "Entfolgen",
|
||||||
"confirmations.unfollow.message": "Are you sure you want to unfollow {name}?",
|
"confirmations.unfollow.message": "Bist du dir sicher, dass du {name} entfolgen möchstest?",
|
||||||
"emoji_button.activity": "Activity",
|
"embed.instructions": "Du kannst diesen Beitrag auf deiner Webseite einbetten, in dem du den folgenden Code einfügst.",
|
||||||
"emoji_button.flags": "Flags",
|
"embed.preview": "So wird es aussehen:",
|
||||||
"emoji_button.food": "Food & Drink",
|
"emoji_button.activity": "Aktivitäten",
|
||||||
|
"emoji_button.flags": "Flaggen",
|
||||||
|
"emoji_button.food": "Essen und Trinken",
|
||||||
"emoji_button.label": "Emoji einfügen",
|
"emoji_button.label": "Emoji einfügen",
|
||||||
"emoji_button.nature": "Nature",
|
"emoji_button.nature": "Natur",
|
||||||
"emoji_button.objects": "Objects",
|
"emoji_button.objects": "Dinge",
|
||||||
"emoji_button.people": "People",
|
"emoji_button.people": "Leute",
|
||||||
"emoji_button.search": "Search...",
|
"emoji_button.search": "Suche…",
|
||||||
"emoji_button.symbols": "Symbols",
|
"emoji_button.symbols": "Symbole",
|
||||||
"emoji_button.travel": "Travel & Places",
|
"emoji_button.travel": "Reise und Orte",
|
||||||
"empty_column.community": "Die lokale Zeitleiste ist leer. Schreibe etwas öffentlich, um den Ball ins Rollen zu bringen!",
|
"empty_column.community": "Die lokale Zeitleiste ist leer. Schreibe etwas öffentlich, um den Ball ins Rollen zu bringen!",
|
||||||
"empty_column.hashtag": "Es gibt noch nichts unter diesem Hashtag.",
|
"empty_column.hashtag": "Es gibt noch nichts unter diesem Hashtag.",
|
||||||
"empty_column.home": "Du folgst noch niemandem. Besuche {public} oder benutze die Suche, um zu starten oder andere Benutzer anzutreffen.",
|
"empty_column.home": "Du folgst noch niemandem. Besuche {public} oder benutze die Suche, um zu starten oder andere Benutzer anzutreffen.",
|
||||||
"empty_column.home.inactivity": "Your home feed is empty. If you have been inactive for a while, it will be regenerated for you soon.",
|
"empty_column.home.inactivity": "Deine Zeitleiste ist leer. Falls du eine längere Zeit inaktiv gewesen bist, wird sie für dich so schnell wie möglich wiedererstellt.",
|
||||||
"empty_column.home.public_timeline": "die öffentliche Zeitleiste",
|
"empty_column.home.public_timeline": "die öffentliche Zeitleiste",
|
||||||
"empty_column.notifications": "Du hast noch keine Mitteilungen. Interagiere mit anderen, um die Konversation zu starten.",
|
"empty_column.notifications": "Du hast noch keine Mitteilungen. Interagiere mit anderen, um die Konversation zu starten.",
|
||||||
"empty_column.public": "Hier ist nichts zu sehen! Schreibe etwas öffentlich oder folge Benutzern von anderen Instanzen, um es aufzufüllen.",
|
"empty_column.public": "Hier ist nichts zu sehen! Schreibe etwas öffentlich oder folge Benutzern von anderen Instanzen, um es aufzufüllen.",
|
||||||
"follow_request.authorize": "Erlauben",
|
"follow_request.authorize": "Erlauben",
|
||||||
"follow_request.reject": "Ablehnen",
|
"follow_request.reject": "Ablehnen",
|
||||||
"getting_started.appsshort": "Apps",
|
"getting_started.appsshort": "Anwendungen",
|
||||||
"getting_started.faq": "FAQ",
|
"getting_started.faq": "Häufig gestellte Fragen",
|
||||||
"getting_started.heading": "Erste Schritte",
|
"getting_started.heading": "Erste Schritte",
|
||||||
"getting_started.open_source_notice": "Mastodon ist quelloffene Software. Du kannst auf {github} dazu beitragen oder Probleme melden.",
|
"getting_started.open_source_notice": "Mastodon ist quelloffene Software. Du kannst auf {github} dazu beitragen oder Probleme melden.",
|
||||||
"getting_started.userguide": "User Guide",
|
"getting_started.userguide": "Nutzeranleitung",
|
||||||
"home.column_settings.advanced": "Fortgeschritten",
|
"home.column_settings.advanced": "Fortgeschritten",
|
||||||
"home.column_settings.basic": "Einfach",
|
"home.column_settings.basic": "Einfach",
|
||||||
"home.column_settings.filter_regex": "Filter durch reguläre Ausdrücke",
|
"home.column_settings.filter_regex": "Filter durch reguläre Ausdrücke",
|
||||||
|
@ -94,8 +96,8 @@
|
||||||
"home.column_settings.show_replies": "Antworten anzeigen",
|
"home.column_settings.show_replies": "Antworten anzeigen",
|
||||||
"home.settings": "Spalteneinstellungen",
|
"home.settings": "Spalteneinstellungen",
|
||||||
"lightbox.close": "Schließen",
|
"lightbox.close": "Schließen",
|
||||||
"lightbox.next": "Next",
|
"lightbox.next": "Weiter",
|
||||||
"lightbox.previous": "Previous",
|
"lightbox.previous": "Zurück",
|
||||||
"loading_indicator.label": "Lade…",
|
"loading_indicator.label": "Lade…",
|
||||||
"media_gallery.toggle_visible": "Sichtbarkeit einstellen",
|
"media_gallery.toggle_visible": "Sichtbarkeit einstellen",
|
||||||
"missing_indicator.label": "Nicht gefunden",
|
"missing_indicator.label": "Nicht gefunden",
|
||||||
|
@ -113,8 +115,8 @@
|
||||||
"notification.follow": "{name} folgt dir",
|
"notification.follow": "{name} folgt dir",
|
||||||
"notification.mention": "{name} erwähnte dich",
|
"notification.mention": "{name} erwähnte dich",
|
||||||
"notification.reblog": "{name} teilte deinen Status",
|
"notification.reblog": "{name} teilte deinen Status",
|
||||||
"notifications.clear": "Mitteilungen beseitigen",
|
"notifications.clear": "Mitteilungen löschen",
|
||||||
"notifications.clear_confirmation": "Bist du sicher, dass du alle Mitteilungen beseitigen willst?",
|
"notifications.clear_confirmation": "Bist du dir sicher, dass du alle Mitteilungen löschen möchstest?",
|
||||||
"notifications.column_settings.alert": "Desktop-Benachrichtigungen",
|
"notifications.column_settings.alert": "Desktop-Benachrichtigungen",
|
||||||
"notifications.column_settings.favourite": "Favorisierungen:",
|
"notifications.column_settings.favourite": "Favorisierungen:",
|
||||||
"notifications.column_settings.follow": "Neue Folgende:",
|
"notifications.column_settings.follow": "Neue Folgende:",
|
||||||
|
@ -124,26 +126,26 @@
|
||||||
"notifications.column_settings.reblog": "Geteilte Beiträge:",
|
"notifications.column_settings.reblog": "Geteilte Beiträge:",
|
||||||
"notifications.column_settings.show": "In der Spalte anzeigen",
|
"notifications.column_settings.show": "In der Spalte anzeigen",
|
||||||
"notifications.column_settings.sound": "Ton abspielen",
|
"notifications.column_settings.sound": "Ton abspielen",
|
||||||
"onboarding.done": "Done",
|
"onboarding.done": "Fertig",
|
||||||
"onboarding.next": "Next",
|
"onboarding.next": "Weiter",
|
||||||
"onboarding.page_five.public_timelines": "The local timeline shows public posts from everyone on {domain}. The federated timeline shows public posts from everyone who people on {domain} follow. These are the Public Timelines, a great way to discover new people.",
|
"onboarding.page_five.public_timelines": "Die lokale Zeitleiste zeigt alle Beiträge von Leuten, die auch auf deiner Instanz {domain} sind. Das gesamte bekannte Netz zeigt Beiträge von allen, die kollektiv aus deiner Instanz heraus gefolgt werden. Zusammen werden die beiden Leisten auch öffentliche Zeitleisten genannt, durch sie kannst du viel neues entdecken.",
|
||||||
"onboarding.page_four.home": "The home timeline shows posts from people you follow.",
|
"onboarding.page_four.home": "Die Startseite zeigt dir Beiträge von Leuten, denen du folgst.",
|
||||||
"onboarding.page_four.notifications": "The notifications column shows when someone interacts with you.",
|
"onboarding.page_four.notifications": "Wenn jemand mir dir interagiert, bekommst du eine Mitteilung.",
|
||||||
"onboarding.page_one.federation": "Mastodon is a network of independent servers joining up to make one larger social network. We call these servers instances.",
|
"onboarding.page_one.federation": "Mastodon ist ein soziales Netzwerk, das aus unabhängigen Servern besteht. Diese Server nennen wir auch Instanzen.",
|
||||||
"onboarding.page_one.handle": "You are on {domain}, so your full handle is {handle}",
|
"onboarding.page_one.handle": "Du bist auf der Instanz {domain}, also ist dein vollständiger Nutzername im Netzwerk {handle}",
|
||||||
"onboarding.page_one.welcome": "Welcome to Mastodon!",
|
"onboarding.page_one.welcome": "Willkommen bei Mastodon!",
|
||||||
"onboarding.page_six.admin": "Your instance's admin is {admin}.",
|
"onboarding.page_six.admin": "Für deine Instanz ist {admin} zuständig.",
|
||||||
"onboarding.page_six.almost_done": "Almost done...",
|
"onboarding.page_six.almost_done": "Fast fertig…",
|
||||||
"onboarding.page_six.appetoot": "Bon Appetoot!",
|
"onboarding.page_six.appetoot": "Guten Appetröt!",
|
||||||
"onboarding.page_six.apps_available": "There are {apps} available for iOS, Android and other platforms.",
|
"onboarding.page_six.apps_available": "Es gibt verschiedene {apps} für iOS, Android und andere Plattformen.",
|
||||||
"onboarding.page_six.github": "Mastodon is free open-source software. You can report bugs, request features, or contribute to the code on {github}.",
|
"onboarding.page_six.github": "Mastodon ist freie, quelloffene Software. Du kannst auf {github} dazu beitragen oder Probleme melden.",
|
||||||
"onboarding.page_six.guidelines": "community guidelines",
|
"onboarding.page_six.guidelines": "Richtlinien",
|
||||||
"onboarding.page_six.read_guidelines": "Please read {domain}'s {guidelines}!",
|
"onboarding.page_six.read_guidelines": "Bitte mach dich mit den {guidelines} von {domain} vertraut!",
|
||||||
"onboarding.page_six.various_app": "mobile apps",
|
"onboarding.page_six.various_app": "mobile Anwendungen",
|
||||||
"onboarding.page_three.profile": "Edit your profile to change your avatar, bio, and display name. There, you will also find other preferences.",
|
"onboarding.page_three.profile": "Bearbeite dein Profil, um dein Bild, deinen Namen oder deine Beschreibung anzupassen. Dort findest du auch andere Einstellungen.",
|
||||||
"onboarding.page_three.search": "Use the search bar to find people and look at hashtags, such as {illustration} and {introductions}. To look for a person who is not on this instance, use their full handle.",
|
"onboarding.page_three.search": "Benutze die Suchfunktion, um Leute oder Themen zu finden. Zum Beispiel, die Hashtags {illustration} oder {introductions}. Um eine Person zu finden, die auf einer anderen Instanz ist, benutze den vollständigen Nutzernamen.",
|
||||||
"onboarding.page_two.compose": "Write posts from the compose column. You can upload images, change privacy settings, and add content warnings with the icons below.",
|
"onboarding.page_two.compose": "Schreibe Beiträge aus der Schreiben-Spalte. Du kannst Bilder und kurze Videos hochladen, Sichtbarkeitseinstellungen ändern und Inhaltswarnungen hinzufügen.",
|
||||||
"onboarding.skip": "Skip",
|
"onboarding.skip": "Überspringen",
|
||||||
"privacy.change": "Privatsphäre des Status anpassen",
|
"privacy.change": "Privatsphäre des Status anpassen",
|
||||||
"privacy.direct.long": "Beitrag nur an erwähnte Benutzer",
|
"privacy.direct.long": "Beitrag nur an erwähnte Benutzer",
|
||||||
"privacy.direct.short": "Direkt",
|
"privacy.direct.short": "Direkt",
|
||||||
|
@ -159,15 +161,17 @@
|
||||||
"report.target": "Melden",
|
"report.target": "Melden",
|
||||||
"search.placeholder": "Suche",
|
"search.placeholder": "Suche",
|
||||||
"search_results.total": "{count, number} {count, plural, one {Ergebnis} other {Ergebnisse}}",
|
"search_results.total": "{count, number} {count, plural, one {Ergebnis} other {Ergebnisse}}",
|
||||||
"standalone.public_title": "A look inside...",
|
"standalone.public_title": "Vorschau…",
|
||||||
"status.cannot_reblog": "This post cannot be boosted",
|
"status.cannot_reblog": "Dieser Beitrag kann nicht geteilt werden",
|
||||||
"status.delete": "Löschen",
|
"status.delete": "Löschen",
|
||||||
|
"status.embed": "Einbetten",
|
||||||
"status.favourite": "Favorisieren",
|
"status.favourite": "Favorisieren",
|
||||||
"status.load_more": "Weitere laden",
|
"status.load_more": "Weitere laden",
|
||||||
"status.media_hidden": "Medien versteckt",
|
"status.media_hidden": "Medien versteckt",
|
||||||
"status.mention": "Erwähnen",
|
"status.mention": "Erwähnen",
|
||||||
"status.mute_conversation": "Mute conversation",
|
"status.mute_conversation": "Thread stummschalten",
|
||||||
"status.open": "Öffnen",
|
"status.open": "Öffnen",
|
||||||
|
"status.pin": "Auf dem Profil anheften",
|
||||||
"status.reblog": "Teilen",
|
"status.reblog": "Teilen",
|
||||||
"status.reblogged_by": "{name} teilte",
|
"status.reblogged_by": "{name} teilte",
|
||||||
"status.reply": "Antworten",
|
"status.reply": "Antworten",
|
||||||
|
@ -175,13 +179,14 @@
|
||||||
"status.report": "@{name} melden",
|
"status.report": "@{name} melden",
|
||||||
"status.sensitive_toggle": "Klicke, um sie zu sehen",
|
"status.sensitive_toggle": "Klicke, um sie zu sehen",
|
||||||
"status.sensitive_warning": "Heikle Inhalte",
|
"status.sensitive_warning": "Heikle Inhalte",
|
||||||
"status.share": "Share",
|
"status.share": "Teilen",
|
||||||
"status.show_less": "Weniger anzeigen",
|
"status.show_less": "Weniger anzeigen",
|
||||||
"status.show_more": "Mehr anzeigen",
|
"status.show_more": "Mehr anzeigen",
|
||||||
"status.unmute_conversation": "Unmute conversation",
|
"status.unmute_conversation": "Stummschaltung von Thread aufheben",
|
||||||
|
"status.unpin": "Vom Profil lösen",
|
||||||
"tabs_bar.compose": "Schreiben",
|
"tabs_bar.compose": "Schreiben",
|
||||||
"tabs_bar.federated_timeline": "Föderation",
|
"tabs_bar.federated_timeline": "Föderation",
|
||||||
"tabs_bar.home": "Home",
|
"tabs_bar.home": "Startseite",
|
||||||
"tabs_bar.local_timeline": "Lokal",
|
"tabs_bar.local_timeline": "Lokal",
|
||||||
"tabs_bar.notifications": "Mitteilungen",
|
"tabs_bar.notifications": "Mitteilungen",
|
||||||
"upload_area.title": "Hereinziehen zum Hochladen",
|
"upload_area.title": "Hereinziehen zum Hochladen",
|
||||||
|
|
|
@ -189,6 +189,18 @@
|
||||||
{
|
{
|
||||||
"defaultMessage": "Unmute conversation",
|
"defaultMessage": "Unmute conversation",
|
||||||
"id": "status.unmute_conversation"
|
"id": "status.unmute_conversation"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"defaultMessage": "Pin on profile",
|
||||||
|
"id": "status.pin"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"defaultMessage": "Unpin from profile",
|
||||||
|
"id": "status.unpin"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"defaultMessage": "Embed",
|
||||||
|
"id": "status.embed"
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"path": "app/javascript/mastodon/components/status_action_bar.json"
|
"path": "app/javascript/mastodon/components/status_action_bar.json"
|
||||||
|
@ -424,7 +436,7 @@
|
||||||
"id": "account.follow"
|
"id": "account.follow"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"defaultMessage": "Awaiting approval",
|
"defaultMessage": "Awaiting approval. Click to cancel follow request",
|
||||||
"id": "account.requested"
|
"id": "account.requested"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
@ -1035,6 +1047,18 @@
|
||||||
{
|
{
|
||||||
"defaultMessage": "Share",
|
"defaultMessage": "Share",
|
||||||
"id": "status.share"
|
"id": "status.share"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"defaultMessage": "Pin on profile",
|
||||||
|
"id": "status.pin"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"defaultMessage": "Unpin from profile",
|
||||||
|
"id": "status.unpin"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"defaultMessage": "Embed",
|
||||||
|
"id": "status.embed"
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"path": "app/javascript/mastodon/features/status/components/action_bar.json"
|
"path": "app/javascript/mastodon/features/status/components/action_bar.json"
|
||||||
|
@ -1108,6 +1132,23 @@
|
||||||
],
|
],
|
||||||
"path": "app/javascript/mastodon/features/ui/components/confirmation_modal.json"
|
"path": "app/javascript/mastodon/features/ui/components/confirmation_modal.json"
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"descriptors": [
|
||||||
|
{
|
||||||
|
"defaultMessage": "Embed",
|
||||||
|
"id": "status.embed"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"defaultMessage": "Embed this status on your website by copying the code below.",
|
||||||
|
"id": "embed.instructions"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"defaultMessage": "Here is what it will look like:",
|
||||||
|
"id": "embed.preview"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"path": "app/javascript/mastodon/features/ui/components/embed_modal.json"
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"descriptors": [
|
"descriptors": [
|
||||||
{
|
{
|
||||||
|
|
|
@ -12,7 +12,7 @@
|
||||||
"account.mute": "Mute @{name}",
|
"account.mute": "Mute @{name}",
|
||||||
"account.posts": "Posts",
|
"account.posts": "Posts",
|
||||||
"account.report": "Report @{name}",
|
"account.report": "Report @{name}",
|
||||||
"account.requested": "Awaiting approval",
|
"account.requested": "Awaiting approval. Click to cancel follow request",
|
||||||
"account.share": "Share @{name}'s profile",
|
"account.share": "Share @{name}'s profile",
|
||||||
"account.unblock": "Unblock @{name}",
|
"account.unblock": "Unblock @{name}",
|
||||||
"account.unblock_domain": "Unhide {domain}",
|
"account.unblock_domain": "Unhide {domain}",
|
||||||
|
@ -63,6 +63,8 @@
|
||||||
"confirmations.mute.message": "Are you sure you want to mute {name}?",
|
"confirmations.mute.message": "Are you sure you want to mute {name}?",
|
||||||
"confirmations.unfollow.confirm": "Unfollow",
|
"confirmations.unfollow.confirm": "Unfollow",
|
||||||
"confirmations.unfollow.message": "Are you sure you want to unfollow {name}?",
|
"confirmations.unfollow.message": "Are you sure you want to unfollow {name}?",
|
||||||
|
"embed.instructions": "Embed this status on your website by copying the code below.",
|
||||||
|
"embed.preview": "Here is what it will look like:",
|
||||||
"emoji_button.activity": "Activity",
|
"emoji_button.activity": "Activity",
|
||||||
"emoji_button.flags": "Flags",
|
"emoji_button.flags": "Flags",
|
||||||
"emoji_button.food": "Food & Drink",
|
"emoji_button.food": "Food & Drink",
|
||||||
|
@ -162,12 +164,14 @@
|
||||||
"standalone.public_title": "A look inside...",
|
"standalone.public_title": "A look inside...",
|
||||||
"status.cannot_reblog": "This post cannot be boosted",
|
"status.cannot_reblog": "This post cannot be boosted",
|
||||||
"status.delete": "Delete",
|
"status.delete": "Delete",
|
||||||
|
"status.embed": "Embed",
|
||||||
"status.favourite": "Favourite",
|
"status.favourite": "Favourite",
|
||||||
"status.load_more": "Load more",
|
"status.load_more": "Load more",
|
||||||
"status.media_hidden": "Media hidden",
|
"status.media_hidden": "Media hidden",
|
||||||
"status.mention": "Mention @{name}",
|
"status.mention": "Mention @{name}",
|
||||||
"status.mute_conversation": "Mute conversation",
|
"status.mute_conversation": "Mute conversation",
|
||||||
"status.open": "Expand this status",
|
"status.open": "Expand this status",
|
||||||
|
"status.pin": "Pin on profile",
|
||||||
"status.reblog": "Boost",
|
"status.reblog": "Boost",
|
||||||
"status.reblogged_by": "{name} boosted",
|
"status.reblogged_by": "{name} boosted",
|
||||||
"status.reply": "Reply",
|
"status.reply": "Reply",
|
||||||
|
@ -179,6 +183,7 @@
|
||||||
"status.show_less": "Show less",
|
"status.show_less": "Show less",
|
||||||
"status.show_more": "Show more",
|
"status.show_more": "Show more",
|
||||||
"status.unmute_conversation": "Unmute conversation",
|
"status.unmute_conversation": "Unmute conversation",
|
||||||
|
"status.unpin": "Unpin from profile",
|
||||||
"tabs_bar.compose": "Compose",
|
"tabs_bar.compose": "Compose",
|
||||||
"tabs_bar.federated_timeline": "Federated",
|
"tabs_bar.federated_timeline": "Federated",
|
||||||
"tabs_bar.home": "Home",
|
"tabs_bar.home": "Home",
|
||||||
|
|
|
@ -63,6 +63,8 @@
|
||||||
"confirmations.mute.message": "Are you sure you want to mute {name}?",
|
"confirmations.mute.message": "Are you sure you want to mute {name}?",
|
||||||
"confirmations.unfollow.confirm": "Unfollow",
|
"confirmations.unfollow.confirm": "Unfollow",
|
||||||
"confirmations.unfollow.message": "Are you sure you want to unfollow {name}?",
|
"confirmations.unfollow.message": "Are you sure you want to unfollow {name}?",
|
||||||
|
"embed.instructions": "Embed this status on your website by copying the code below.",
|
||||||
|
"embed.preview": "Here is what it will look like:",
|
||||||
"emoji_button.activity": "Activity",
|
"emoji_button.activity": "Activity",
|
||||||
"emoji_button.flags": "Flags",
|
"emoji_button.flags": "Flags",
|
||||||
"emoji_button.food": "Food & Drink",
|
"emoji_button.food": "Food & Drink",
|
||||||
|
@ -162,12 +164,14 @@
|
||||||
"standalone.public_title": "A look inside...",
|
"standalone.public_title": "A look inside...",
|
||||||
"status.cannot_reblog": "This post cannot be boosted",
|
"status.cannot_reblog": "This post cannot be boosted",
|
||||||
"status.delete": "Forigi",
|
"status.delete": "Forigi",
|
||||||
|
"status.embed": "Embed",
|
||||||
"status.favourite": "Favori",
|
"status.favourite": "Favori",
|
||||||
"status.load_more": "Load more",
|
"status.load_more": "Load more",
|
||||||
"status.media_hidden": "Media hidden",
|
"status.media_hidden": "Media hidden",
|
||||||
"status.mention": "Mencii @{name}",
|
"status.mention": "Mencii @{name}",
|
||||||
"status.mute_conversation": "Mute conversation",
|
"status.mute_conversation": "Mute conversation",
|
||||||
"status.open": "Expand this status",
|
"status.open": "Expand this status",
|
||||||
|
"status.pin": "Pin on profile",
|
||||||
"status.reblog": "Diskonigi",
|
"status.reblog": "Diskonigi",
|
||||||
"status.reblogged_by": "{name} diskonigita",
|
"status.reblogged_by": "{name} diskonigita",
|
||||||
"status.reply": "Respondi",
|
"status.reply": "Respondi",
|
||||||
|
@ -179,6 +183,7 @@
|
||||||
"status.show_less": "Show less",
|
"status.show_less": "Show less",
|
||||||
"status.show_more": "Show more",
|
"status.show_more": "Show more",
|
||||||
"status.unmute_conversation": "Unmute conversation",
|
"status.unmute_conversation": "Unmute conversation",
|
||||||
|
"status.unpin": "Unpin from profile",
|
||||||
"tabs_bar.compose": "Ekskribi",
|
"tabs_bar.compose": "Ekskribi",
|
||||||
"tabs_bar.federated_timeline": "Federated",
|
"tabs_bar.federated_timeline": "Federated",
|
||||||
"tabs_bar.home": "Hejmo",
|
"tabs_bar.home": "Hejmo",
|
||||||
|
|
|
@ -63,6 +63,8 @@
|
||||||
"confirmations.mute.message": "Are you sure you want to mute {name}?",
|
"confirmations.mute.message": "Are you sure you want to mute {name}?",
|
||||||
"confirmations.unfollow.confirm": "Unfollow",
|
"confirmations.unfollow.confirm": "Unfollow",
|
||||||
"confirmations.unfollow.message": "Are you sure you want to unfollow {name}?",
|
"confirmations.unfollow.message": "Are you sure you want to unfollow {name}?",
|
||||||
|
"embed.instructions": "Embed this status on your website by copying the code below.",
|
||||||
|
"embed.preview": "Here is what it will look like:",
|
||||||
"emoji_button.activity": "Activity",
|
"emoji_button.activity": "Activity",
|
||||||
"emoji_button.flags": "Flags",
|
"emoji_button.flags": "Flags",
|
||||||
"emoji_button.food": "Food & Drink",
|
"emoji_button.food": "Food & Drink",
|
||||||
|
@ -162,12 +164,14 @@
|
||||||
"standalone.public_title": "A look inside...",
|
"standalone.public_title": "A look inside...",
|
||||||
"status.cannot_reblog": "This post cannot be boosted",
|
"status.cannot_reblog": "This post cannot be boosted",
|
||||||
"status.delete": "Borrar",
|
"status.delete": "Borrar",
|
||||||
|
"status.embed": "Embed",
|
||||||
"status.favourite": "Favorito",
|
"status.favourite": "Favorito",
|
||||||
"status.load_more": "Load more",
|
"status.load_more": "Load more",
|
||||||
"status.media_hidden": "Media hidden",
|
"status.media_hidden": "Media hidden",
|
||||||
"status.mention": "Mencionar",
|
"status.mention": "Mencionar",
|
||||||
"status.mute_conversation": "Mute conversation",
|
"status.mute_conversation": "Mute conversation",
|
||||||
"status.open": "Expandir estado",
|
"status.open": "Expandir estado",
|
||||||
|
"status.pin": "Pin on profile",
|
||||||
"status.reblog": "Retoot",
|
"status.reblog": "Retoot",
|
||||||
"status.reblogged_by": "Retooteado por {name}",
|
"status.reblogged_by": "Retooteado por {name}",
|
||||||
"status.reply": "Responder",
|
"status.reply": "Responder",
|
||||||
|
@ -179,6 +183,7 @@
|
||||||
"status.show_less": "Mostrar menos",
|
"status.show_less": "Mostrar menos",
|
||||||
"status.show_more": "Mostrar más",
|
"status.show_more": "Mostrar más",
|
||||||
"status.unmute_conversation": "Unmute conversation",
|
"status.unmute_conversation": "Unmute conversation",
|
||||||
|
"status.unpin": "Unpin from profile",
|
||||||
"tabs_bar.compose": "Redactar",
|
"tabs_bar.compose": "Redactar",
|
||||||
"tabs_bar.federated_timeline": "Federated",
|
"tabs_bar.federated_timeline": "Federated",
|
||||||
"tabs_bar.home": "Inicio",
|
"tabs_bar.home": "Inicio",
|
||||||
|
|
|
@ -63,6 +63,8 @@
|
||||||
"confirmations.mute.message": "آیا واقعاً میخواهید {name} را بیصدا کنید؟",
|
"confirmations.mute.message": "آیا واقعاً میخواهید {name} را بیصدا کنید؟",
|
||||||
"confirmations.unfollow.confirm": "لغو پیگیری",
|
"confirmations.unfollow.confirm": "لغو پیگیری",
|
||||||
"confirmations.unfollow.message": "آیا واقعاً میخواهید به پیگیری از {name} پایان دهید؟",
|
"confirmations.unfollow.message": "آیا واقعاً میخواهید به پیگیری از {name} پایان دهید؟",
|
||||||
|
"embed.instructions": "Embed this status on your website by copying the code below.",
|
||||||
|
"embed.preview": "Here is what it will look like:",
|
||||||
"emoji_button.activity": "فعالیت",
|
"emoji_button.activity": "فعالیت",
|
||||||
"emoji_button.flags": "پرچمها",
|
"emoji_button.flags": "پرچمها",
|
||||||
"emoji_button.food": "غذا و نوشیدنی",
|
"emoji_button.food": "غذا و نوشیدنی",
|
||||||
|
@ -162,12 +164,14 @@
|
||||||
"standalone.public_title": "نگاهی به کاربران این سرور...",
|
"standalone.public_title": "نگاهی به کاربران این سرور...",
|
||||||
"status.cannot_reblog": "این نوشته را نمیشود بازبوقید",
|
"status.cannot_reblog": "این نوشته را نمیشود بازبوقید",
|
||||||
"status.delete": "پاککردن",
|
"status.delete": "پاککردن",
|
||||||
|
"status.embed": "Embed",
|
||||||
"status.favourite": "پسندیدن",
|
"status.favourite": "پسندیدن",
|
||||||
"status.load_more": "بیشتر نشان بده",
|
"status.load_more": "بیشتر نشان بده",
|
||||||
"status.media_hidden": "تصویر پنهان شده",
|
"status.media_hidden": "تصویر پنهان شده",
|
||||||
"status.mention": "نامبردن از @{name}",
|
"status.mention": "نامبردن از @{name}",
|
||||||
"status.mute_conversation": "بیصداکردن گفتگو",
|
"status.mute_conversation": "بیصداکردن گفتگو",
|
||||||
"status.open": "این نوشته را باز کن",
|
"status.open": "این نوشته را باز کن",
|
||||||
|
"status.pin": "Pin on profile",
|
||||||
"status.reblog": "بازبوقیدن",
|
"status.reblog": "بازبوقیدن",
|
||||||
"status.reblogged_by": "{name} بازبوقید",
|
"status.reblogged_by": "{name} بازبوقید",
|
||||||
"status.reply": "پاسخ",
|
"status.reply": "پاسخ",
|
||||||
|
@ -179,6 +183,7 @@
|
||||||
"status.show_less": "نهفتن",
|
"status.show_less": "نهفتن",
|
||||||
"status.show_more": "نمایش",
|
"status.show_more": "نمایش",
|
||||||
"status.unmute_conversation": "باصداکردن گفتگو",
|
"status.unmute_conversation": "باصداکردن گفتگو",
|
||||||
|
"status.unpin": "Unpin from profile",
|
||||||
"tabs_bar.compose": "بنویسید",
|
"tabs_bar.compose": "بنویسید",
|
||||||
"tabs_bar.federated_timeline": "همگانی",
|
"tabs_bar.federated_timeline": "همگانی",
|
||||||
"tabs_bar.home": "خانه",
|
"tabs_bar.home": "خانه",
|
||||||
|
|
|
@ -63,6 +63,8 @@
|
||||||
"confirmations.mute.message": "Are you sure you want to mute {name}?",
|
"confirmations.mute.message": "Are you sure you want to mute {name}?",
|
||||||
"confirmations.unfollow.confirm": "Unfollow",
|
"confirmations.unfollow.confirm": "Unfollow",
|
||||||
"confirmations.unfollow.message": "Are you sure you want to unfollow {name}?",
|
"confirmations.unfollow.message": "Are you sure you want to unfollow {name}?",
|
||||||
|
"embed.instructions": "Embed this status on your website by copying the code below.",
|
||||||
|
"embed.preview": "Here is what it will look like:",
|
||||||
"emoji_button.activity": "Activity",
|
"emoji_button.activity": "Activity",
|
||||||
"emoji_button.flags": "Flags",
|
"emoji_button.flags": "Flags",
|
||||||
"emoji_button.food": "Food & Drink",
|
"emoji_button.food": "Food & Drink",
|
||||||
|
@ -162,12 +164,14 @@
|
||||||
"standalone.public_title": "A look inside...",
|
"standalone.public_title": "A look inside...",
|
||||||
"status.cannot_reblog": "This post cannot be boosted",
|
"status.cannot_reblog": "This post cannot be boosted",
|
||||||
"status.delete": "Poista",
|
"status.delete": "Poista",
|
||||||
|
"status.embed": "Embed",
|
||||||
"status.favourite": "Tykkää",
|
"status.favourite": "Tykkää",
|
||||||
"status.load_more": "Load more",
|
"status.load_more": "Load more",
|
||||||
"status.media_hidden": "Media hidden",
|
"status.media_hidden": "Media hidden",
|
||||||
"status.mention": "Mainitse @{name}",
|
"status.mention": "Mainitse @{name}",
|
||||||
"status.mute_conversation": "Mute conversation",
|
"status.mute_conversation": "Mute conversation",
|
||||||
"status.open": "Expand this status",
|
"status.open": "Expand this status",
|
||||||
|
"status.pin": "Pin on profile",
|
||||||
"status.reblog": "Buustaa",
|
"status.reblog": "Buustaa",
|
||||||
"status.reblogged_by": "{name} buustasi",
|
"status.reblogged_by": "{name} buustasi",
|
||||||
"status.reply": "Vastaa",
|
"status.reply": "Vastaa",
|
||||||
|
@ -179,6 +183,7 @@
|
||||||
"status.show_less": "Show less",
|
"status.show_less": "Show less",
|
||||||
"status.show_more": "Show more",
|
"status.show_more": "Show more",
|
||||||
"status.unmute_conversation": "Unmute conversation",
|
"status.unmute_conversation": "Unmute conversation",
|
||||||
|
"status.unpin": "Unpin from profile",
|
||||||
"tabs_bar.compose": "Luo",
|
"tabs_bar.compose": "Luo",
|
||||||
"tabs_bar.federated_timeline": "Federated",
|
"tabs_bar.federated_timeline": "Federated",
|
||||||
"tabs_bar.home": "Koti",
|
"tabs_bar.home": "Koti",
|
||||||
|
|
|
@ -20,11 +20,11 @@
|
||||||
"account.unmute": "Ne plus masquer",
|
"account.unmute": "Ne plus masquer",
|
||||||
"account.view_full_profile": "Afficher le profil complet",
|
"account.view_full_profile": "Afficher le profil complet",
|
||||||
"boost_modal.combo": "Vous pouvez appuyer sur {combo} pour pouvoir passer ceci, la prochaine fois",
|
"boost_modal.combo": "Vous pouvez appuyer sur {combo} pour pouvoir passer ceci, la prochaine fois",
|
||||||
"bundle_column_error.body": "Une erreur s'est produite lors du chargement de ce composant.",
|
"bundle_column_error.body": "Une erreur s’est produite lors du chargement de ce composant.",
|
||||||
"bundle_column_error.retry": "Réessayer",
|
"bundle_column_error.retry": "Réessayer",
|
||||||
"bundle_column_error.title": "Erreur réseau",
|
"bundle_column_error.title": "Erreur réseau",
|
||||||
"bundle_modal_error.close": "Fermer",
|
"bundle_modal_error.close": "Fermer",
|
||||||
"bundle_modal_error.message": "Une erreur s'est produite lors du chargement de ce composant.",
|
"bundle_modal_error.message": "Une erreur s’est produite lors du chargement de ce composant.",
|
||||||
"bundle_modal_error.retry": "Réessayer",
|
"bundle_modal_error.retry": "Réessayer",
|
||||||
"column.blocks": "Comptes bloqués",
|
"column.blocks": "Comptes bloqués",
|
||||||
"column.community": "Fil public local",
|
"column.community": "Fil public local",
|
||||||
|
@ -43,12 +43,12 @@
|
||||||
"column_header.unpin": "Retirer",
|
"column_header.unpin": "Retirer",
|
||||||
"column_subheading.navigation": "Navigation",
|
"column_subheading.navigation": "Navigation",
|
||||||
"column_subheading.settings": "Paramètres",
|
"column_subheading.settings": "Paramètres",
|
||||||
"compose_form.lock_disclaimer": "Votre compte n’est pas {locked}. Tout le monde peut vous suivre et voir vos pouets restreints.",
|
"compose_form.lock_disclaimer": "Votre compte n’est pas {locked}. Tout le monde peut vous suivre et voir vos pouets privés.",
|
||||||
"compose_form.lock_disclaimer.lock": "verrouillé",
|
"compose_form.lock_disclaimer.lock": "verrouillé",
|
||||||
"compose_form.placeholder": "Qu’avez-vous en tête ?",
|
"compose_form.placeholder": "Qu’avez-vous en tête ?",
|
||||||
"compose_form.privacy_disclaimer": "Votre statut privé va être transmis aux personnes mentionnées sur {domains}. Avez-vous confiance en {domainsCount, plural, one {ce serveur} other {ces serveurs}} pour ne pas divulguer votre statut ? Les statuts privés ne fonctionnent que sur les instances de Mastodon. Si {domains} {domainsCount, plural, one {n’est pas une instance de Mastodon} other {ne sont pas des instances de Mastodon}}, il n’y aura aucune indication que votre statut est privé, et il pourrait être partagé ou rendu visible d’une autre manière à d’autres personnes imprévues.",
|
"compose_form.privacy_disclaimer": "Votre statut privé va être transmis aux personnes mentionnées sur {domains}. Avez-vous confiance en {domainsCount, plural, one {ce serveur} other {ces serveurs}} pour ne pas divulguer votre statut ? Les statuts privés ne fonctionnent que sur les instances de Mastodon. Si {domains} {domainsCount, plural, one {n’est pas une instance de Mastodon} other {ne sont pas des instances de Mastodon}}, il n’y aura aucune indication que votre statut est privé, et il pourrait être partagé ou rendu visible d’une autre manière à d’autres personnes imprévues.",
|
||||||
"compose_form.publish": "Pouet ",
|
"compose_form.publish": "Pouet ",
|
||||||
"compose_form.publish_loud": "{publish}!",
|
"compose_form.publish_loud": "{publish} !",
|
||||||
"compose_form.sensitive": "Marquer le média comme sensible",
|
"compose_form.sensitive": "Marquer le média comme sensible",
|
||||||
"compose_form.spoiler": "Masquer le texte derrière un avertissement",
|
"compose_form.spoiler": "Masquer le texte derrière un avertissement",
|
||||||
"compose_form.spoiler_placeholder": "Écrivez ici votre avertissement",
|
"compose_form.spoiler_placeholder": "Écrivez ici votre avertissement",
|
||||||
|
@ -62,7 +62,9 @@
|
||||||
"confirmations.mute.confirm": "Masquer",
|
"confirmations.mute.confirm": "Masquer",
|
||||||
"confirmations.mute.message": "Confirmez vous le masquage de {name} ?",
|
"confirmations.mute.message": "Confirmez vous le masquage de {name} ?",
|
||||||
"confirmations.unfollow.confirm": "Ne plus suivre",
|
"confirmations.unfollow.confirm": "Ne plus suivre",
|
||||||
"confirmations.unfollow.message": "Vous voulez-vous arrêter de suivre {name} ?",
|
"confirmations.unfollow.message": "Vous voulez-vous arrêter de suivre {name} ?",
|
||||||
|
"embed.instructions": "Embed this status on your website by copying the code below.",
|
||||||
|
"embed.preview": "Here is what it will look like:",
|
||||||
"emoji_button.activity": "Activités",
|
"emoji_button.activity": "Activités",
|
||||||
"emoji_button.flags": "Drapeaux",
|
"emoji_button.flags": "Drapeaux",
|
||||||
"emoji_button.food": "Boire et manger",
|
"emoji_button.food": "Boire et manger",
|
||||||
|
@ -134,8 +136,8 @@
|
||||||
"onboarding.page_one.welcome": "Bienvenue sur Mastodon !",
|
"onboarding.page_one.welcome": "Bienvenue sur Mastodon !",
|
||||||
"onboarding.page_six.admin": "L’administrateur⋅trice de votre instance est {admin}",
|
"onboarding.page_six.admin": "L’administrateur⋅trice de votre instance est {admin}",
|
||||||
"onboarding.page_six.almost_done": "Nous y sommes presque…",
|
"onboarding.page_six.almost_done": "Nous y sommes presque…",
|
||||||
"onboarding.page_six.appetoot": "Bon Appétoot!",
|
"onboarding.page_six.appetoot": "Bon appouétit !",
|
||||||
"onboarding.page_six.apps_available": "De nombreuses {apps} sont disponibles pour iOS, Android et autres. Et maintenant… Bon Appétoot!",
|
"onboarding.page_six.apps_available": "De nombreuses {apps} sont disponibles pour iOS, Android et autres. Et maintenant… Bon appouétit !",
|
||||||
"onboarding.page_six.github": "Mastodon est un logiciel libre, gratuit et open-source. Vous pouvez rapporter des bogues, suggérer des fonctionnalités, ou contribuer à son développement sur {github}.",
|
"onboarding.page_six.github": "Mastodon est un logiciel libre, gratuit et open-source. Vous pouvez rapporter des bogues, suggérer des fonctionnalités, ou contribuer à son développement sur {github}.",
|
||||||
"onboarding.page_six.guidelines": "règles de la communauté",
|
"onboarding.page_six.guidelines": "règles de la communauté",
|
||||||
"onboarding.page_six.read_guidelines": "S’il vous plaît, n’oubliez pas de lire les {guidelines} !",
|
"onboarding.page_six.read_guidelines": "S’il vous plaît, n’oubliez pas de lire les {guidelines} !",
|
||||||
|
@ -159,15 +161,17 @@
|
||||||
"report.target": "Signalement",
|
"report.target": "Signalement",
|
||||||
"search.placeholder": "Rechercher",
|
"search.placeholder": "Rechercher",
|
||||||
"search_results.total": "{count, number} {count, plural, one {résultat} other {résultats}}",
|
"search_results.total": "{count, number} {count, plural, one {résultat} other {résultats}}",
|
||||||
"standalone.public_title": "Coup d'œil",
|
"standalone.public_title": "Jeter un coup d’œil…",
|
||||||
"status.cannot_reblog": "Cette publication ne peut être boostée",
|
"status.cannot_reblog": "Cette publication ne peut être boostée",
|
||||||
"status.delete": "Effacer",
|
"status.delete": "Effacer",
|
||||||
|
"status.embed": "Embed",
|
||||||
"status.favourite": "Ajouter aux favoris",
|
"status.favourite": "Ajouter aux favoris",
|
||||||
"status.load_more": "Charger plus",
|
"status.load_more": "Charger plus",
|
||||||
"status.media_hidden": "Média caché",
|
"status.media_hidden": "Média caché",
|
||||||
"status.mention": "Mentionner",
|
"status.mention": "Mentionner",
|
||||||
"status.mute_conversation": "Masquer la conversation",
|
"status.mute_conversation": "Masquer la conversation",
|
||||||
"status.open": "Déplier ce statut",
|
"status.open": "Déplier ce statut",
|
||||||
|
"status.pin": "Épingler sur le profil",
|
||||||
"status.reblog": "Partager",
|
"status.reblog": "Partager",
|
||||||
"status.reblogged_by": "{name} a partagé :",
|
"status.reblogged_by": "{name} a partagé :",
|
||||||
"status.reply": "Répondre",
|
"status.reply": "Répondre",
|
||||||
|
@ -179,6 +183,7 @@
|
||||||
"status.show_less": "Replier",
|
"status.show_less": "Replier",
|
||||||
"status.show_more": "Déplier",
|
"status.show_more": "Déplier",
|
||||||
"status.unmute_conversation": "Ne plus masquer la conversation",
|
"status.unmute_conversation": "Ne plus masquer la conversation",
|
||||||
|
"status.unpin": "Retirer du profil",
|
||||||
"tabs_bar.compose": "Composer",
|
"tabs_bar.compose": "Composer",
|
||||||
"tabs_bar.federated_timeline": "Fil public global",
|
"tabs_bar.federated_timeline": "Fil public global",
|
||||||
"tabs_bar.home": "Accueil",
|
"tabs_bar.home": "Accueil",
|
||||||
|
|
|
@ -63,6 +63,8 @@
|
||||||
"confirmations.mute.message": "להשתיק את {name}?",
|
"confirmations.mute.message": "להשתיק את {name}?",
|
||||||
"confirmations.unfollow.confirm": "Unfollow",
|
"confirmations.unfollow.confirm": "Unfollow",
|
||||||
"confirmations.unfollow.message": "Are you sure you want to unfollow {name}?",
|
"confirmations.unfollow.message": "Are you sure you want to unfollow {name}?",
|
||||||
|
"embed.instructions": "Embed this status on your website by copying the code below.",
|
||||||
|
"embed.preview": "Here is what it will look like:",
|
||||||
"emoji_button.activity": "פעילות",
|
"emoji_button.activity": "פעילות",
|
||||||
"emoji_button.flags": "דגלים",
|
"emoji_button.flags": "דגלים",
|
||||||
"emoji_button.food": "אוכל ושתיה",
|
"emoji_button.food": "אוכל ושתיה",
|
||||||
|
@ -162,12 +164,14 @@
|
||||||
"standalone.public_title": "A look inside...",
|
"standalone.public_title": "A look inside...",
|
||||||
"status.cannot_reblog": "לא ניתן להדהד הודעה זו",
|
"status.cannot_reblog": "לא ניתן להדהד הודעה זו",
|
||||||
"status.delete": "מחיקה",
|
"status.delete": "מחיקה",
|
||||||
|
"status.embed": "Embed",
|
||||||
"status.favourite": "חיבוב",
|
"status.favourite": "חיבוב",
|
||||||
"status.load_more": "עוד",
|
"status.load_more": "עוד",
|
||||||
"status.media_hidden": "מדיה מוסתרת",
|
"status.media_hidden": "מדיה מוסתרת",
|
||||||
"status.mention": "פניה אל @{name}",
|
"status.mention": "פניה אל @{name}",
|
||||||
"status.mute_conversation": "השתקת שיחה",
|
"status.mute_conversation": "השתקת שיחה",
|
||||||
"status.open": "הרחבת הודעה",
|
"status.open": "הרחבת הודעה",
|
||||||
|
"status.pin": "Pin on profile",
|
||||||
"status.reblog": "הדהוד",
|
"status.reblog": "הדהוד",
|
||||||
"status.reblogged_by": "הודהד על ידי {name}",
|
"status.reblogged_by": "הודהד על ידי {name}",
|
||||||
"status.reply": "תגובה",
|
"status.reply": "תגובה",
|
||||||
|
@ -179,6 +183,7 @@
|
||||||
"status.show_less": "הראה פחות",
|
"status.show_less": "הראה פחות",
|
||||||
"status.show_more": "הראה יותר",
|
"status.show_more": "הראה יותר",
|
||||||
"status.unmute_conversation": "הסרת השתקת שיחה",
|
"status.unmute_conversation": "הסרת השתקת שיחה",
|
||||||
|
"status.unpin": "Unpin from profile",
|
||||||
"tabs_bar.compose": "חיבור",
|
"tabs_bar.compose": "חיבור",
|
||||||
"tabs_bar.federated_timeline": "ציר זמן בין-קהילתי",
|
"tabs_bar.federated_timeline": "ציר זמן בין-קהילתי",
|
||||||
"tabs_bar.home": "בבית",
|
"tabs_bar.home": "בבית",
|
||||||
|
|
|
@ -63,6 +63,8 @@
|
||||||
"confirmations.mute.message": "Jesi li siguran da želiš utišati {name}?",
|
"confirmations.mute.message": "Jesi li siguran da želiš utišati {name}?",
|
||||||
"confirmations.unfollow.confirm": "Unfollow",
|
"confirmations.unfollow.confirm": "Unfollow",
|
||||||
"confirmations.unfollow.message": "Are you sure you want to unfollow {name}?",
|
"confirmations.unfollow.message": "Are you sure you want to unfollow {name}?",
|
||||||
|
"embed.instructions": "Embed this status on your website by copying the code below.",
|
||||||
|
"embed.preview": "Here is what it will look like:",
|
||||||
"emoji_button.activity": "Aktivnost",
|
"emoji_button.activity": "Aktivnost",
|
||||||
"emoji_button.flags": "Zastave",
|
"emoji_button.flags": "Zastave",
|
||||||
"emoji_button.food": "Hrana & Piće",
|
"emoji_button.food": "Hrana & Piće",
|
||||||
|
@ -162,12 +164,14 @@
|
||||||
"standalone.public_title": "A look inside...",
|
"standalone.public_title": "A look inside...",
|
||||||
"status.cannot_reblog": "Ovaj post ne može biti podignut",
|
"status.cannot_reblog": "Ovaj post ne može biti podignut",
|
||||||
"status.delete": "Obriši",
|
"status.delete": "Obriši",
|
||||||
|
"status.embed": "Embed",
|
||||||
"status.favourite": "Označi omiljenim",
|
"status.favourite": "Označi omiljenim",
|
||||||
"status.load_more": "Učitaj više",
|
"status.load_more": "Učitaj više",
|
||||||
"status.media_hidden": "Sakriven media sadržaj",
|
"status.media_hidden": "Sakriven media sadržaj",
|
||||||
"status.mention": "Spomeni @{name}",
|
"status.mention": "Spomeni @{name}",
|
||||||
"status.mute_conversation": "Utišaj razgovor",
|
"status.mute_conversation": "Utišaj razgovor",
|
||||||
"status.open": "Proširi ovaj status",
|
"status.open": "Proširi ovaj status",
|
||||||
|
"status.pin": "Pin on profile",
|
||||||
"status.reblog": "Podigni",
|
"status.reblog": "Podigni",
|
||||||
"status.reblogged_by": "{name} je podigao",
|
"status.reblogged_by": "{name} je podigao",
|
||||||
"status.reply": "Odgovori",
|
"status.reply": "Odgovori",
|
||||||
|
@ -179,6 +183,7 @@
|
||||||
"status.show_less": "Pokaži manje",
|
"status.show_less": "Pokaži manje",
|
||||||
"status.show_more": "Pokaži više",
|
"status.show_more": "Pokaži više",
|
||||||
"status.unmute_conversation": "Poništi utišavanje razgovora",
|
"status.unmute_conversation": "Poništi utišavanje razgovora",
|
||||||
|
"status.unpin": "Unpin from profile",
|
||||||
"tabs_bar.compose": "Sastavi",
|
"tabs_bar.compose": "Sastavi",
|
||||||
"tabs_bar.federated_timeline": "Federalni",
|
"tabs_bar.federated_timeline": "Federalni",
|
||||||
"tabs_bar.home": "Dom",
|
"tabs_bar.home": "Dom",
|
||||||
|
|
|
@ -63,6 +63,8 @@
|
||||||
"confirmations.mute.message": "Are you sure you want to mute {name}?",
|
"confirmations.mute.message": "Are you sure you want to mute {name}?",
|
||||||
"confirmations.unfollow.confirm": "Unfollow",
|
"confirmations.unfollow.confirm": "Unfollow",
|
||||||
"confirmations.unfollow.message": "Are you sure you want to unfollow {name}?",
|
"confirmations.unfollow.message": "Are you sure you want to unfollow {name}?",
|
||||||
|
"embed.instructions": "Embed this status on your website by copying the code below.",
|
||||||
|
"embed.preview": "Here is what it will look like:",
|
||||||
"emoji_button.activity": "Activity",
|
"emoji_button.activity": "Activity",
|
||||||
"emoji_button.flags": "Flags",
|
"emoji_button.flags": "Flags",
|
||||||
"emoji_button.food": "Food & Drink",
|
"emoji_button.food": "Food & Drink",
|
||||||
|
@ -162,12 +164,14 @@
|
||||||
"standalone.public_title": "A look inside...",
|
"standalone.public_title": "A look inside...",
|
||||||
"status.cannot_reblog": "This post cannot be boosted",
|
"status.cannot_reblog": "This post cannot be boosted",
|
||||||
"status.delete": "Törlés",
|
"status.delete": "Törlés",
|
||||||
|
"status.embed": "Embed",
|
||||||
"status.favourite": "Kedvenc",
|
"status.favourite": "Kedvenc",
|
||||||
"status.load_more": "Load more",
|
"status.load_more": "Load more",
|
||||||
"status.media_hidden": "Media hidden",
|
"status.media_hidden": "Media hidden",
|
||||||
"status.mention": "Említés",
|
"status.mention": "Említés",
|
||||||
"status.mute_conversation": "Mute conversation",
|
"status.mute_conversation": "Mute conversation",
|
||||||
"status.open": "Expand this status",
|
"status.open": "Expand this status",
|
||||||
|
"status.pin": "Pin on profile",
|
||||||
"status.reblog": "Reblog",
|
"status.reblog": "Reblog",
|
||||||
"status.reblogged_by": "{name} reblogolta",
|
"status.reblogged_by": "{name} reblogolta",
|
||||||
"status.reply": "Válasz",
|
"status.reply": "Válasz",
|
||||||
|
@ -179,6 +183,7 @@
|
||||||
"status.show_less": "Show less",
|
"status.show_less": "Show less",
|
||||||
"status.show_more": "Show more",
|
"status.show_more": "Show more",
|
||||||
"status.unmute_conversation": "Unmute conversation",
|
"status.unmute_conversation": "Unmute conversation",
|
||||||
|
"status.unpin": "Unpin from profile",
|
||||||
"tabs_bar.compose": "Összeállítás",
|
"tabs_bar.compose": "Összeállítás",
|
||||||
"tabs_bar.federated_timeline": "Federated",
|
"tabs_bar.federated_timeline": "Federated",
|
||||||
"tabs_bar.home": "Kezdőlap",
|
"tabs_bar.home": "Kezdőlap",
|
||||||
|
|
|
@ -63,6 +63,8 @@
|
||||||
"confirmations.mute.message": "Apa anda yakin ingin membisukan {name}?",
|
"confirmations.mute.message": "Apa anda yakin ingin membisukan {name}?",
|
||||||
"confirmations.unfollow.confirm": "Unfollow",
|
"confirmations.unfollow.confirm": "Unfollow",
|
||||||
"confirmations.unfollow.message": "Are you sure you want to unfollow {name}?",
|
"confirmations.unfollow.message": "Are you sure you want to unfollow {name}?",
|
||||||
|
"embed.instructions": "Embed this status on your website by copying the code below.",
|
||||||
|
"embed.preview": "Here is what it will look like:",
|
||||||
"emoji_button.activity": "Aktivitas",
|
"emoji_button.activity": "Aktivitas",
|
||||||
"emoji_button.flags": "Bendera",
|
"emoji_button.flags": "Bendera",
|
||||||
"emoji_button.food": "Makanan & Minuman",
|
"emoji_button.food": "Makanan & Minuman",
|
||||||
|
@ -162,12 +164,14 @@
|
||||||
"standalone.public_title": "A look inside...",
|
"standalone.public_title": "A look inside...",
|
||||||
"status.cannot_reblog": "This post cannot be boosted",
|
"status.cannot_reblog": "This post cannot be boosted",
|
||||||
"status.delete": "Hapus",
|
"status.delete": "Hapus",
|
||||||
|
"status.embed": "Embed",
|
||||||
"status.favourite": "Difavoritkan",
|
"status.favourite": "Difavoritkan",
|
||||||
"status.load_more": "Tampilkan semua",
|
"status.load_more": "Tampilkan semua",
|
||||||
"status.media_hidden": "Media disembunyikan",
|
"status.media_hidden": "Media disembunyikan",
|
||||||
"status.mention": "Balasan @{name}",
|
"status.mention": "Balasan @{name}",
|
||||||
"status.mute_conversation": "Mute conversation",
|
"status.mute_conversation": "Mute conversation",
|
||||||
"status.open": "Tampilkan status ini",
|
"status.open": "Tampilkan status ini",
|
||||||
|
"status.pin": "Pin on profile",
|
||||||
"status.reblog": "Boost",
|
"status.reblog": "Boost",
|
||||||
"status.reblogged_by": "di-boost {name}",
|
"status.reblogged_by": "di-boost {name}",
|
||||||
"status.reply": "Balas",
|
"status.reply": "Balas",
|
||||||
|
@ -179,6 +183,7 @@
|
||||||
"status.show_less": "Tampilkan lebih sedikit",
|
"status.show_less": "Tampilkan lebih sedikit",
|
||||||
"status.show_more": "Tampilkan semua",
|
"status.show_more": "Tampilkan semua",
|
||||||
"status.unmute_conversation": "Unmute conversation",
|
"status.unmute_conversation": "Unmute conversation",
|
||||||
|
"status.unpin": "Unpin from profile",
|
||||||
"tabs_bar.compose": "Tulis",
|
"tabs_bar.compose": "Tulis",
|
||||||
"tabs_bar.federated_timeline": "Gabungan",
|
"tabs_bar.federated_timeline": "Gabungan",
|
||||||
"tabs_bar.home": "Beranda",
|
"tabs_bar.home": "Beranda",
|
||||||
|
|
|
@ -63,6 +63,8 @@
|
||||||
"confirmations.mute.message": "Are you sure you want to mute {name}?",
|
"confirmations.mute.message": "Are you sure you want to mute {name}?",
|
||||||
"confirmations.unfollow.confirm": "Unfollow",
|
"confirmations.unfollow.confirm": "Unfollow",
|
||||||
"confirmations.unfollow.message": "Are you sure you want to unfollow {name}?",
|
"confirmations.unfollow.message": "Are you sure you want to unfollow {name}?",
|
||||||
|
"embed.instructions": "Embed this status on your website by copying the code below.",
|
||||||
|
"embed.preview": "Here is what it will look like:",
|
||||||
"emoji_button.activity": "Activity",
|
"emoji_button.activity": "Activity",
|
||||||
"emoji_button.flags": "Flags",
|
"emoji_button.flags": "Flags",
|
||||||
"emoji_button.food": "Food & Drink",
|
"emoji_button.food": "Food & Drink",
|
||||||
|
@ -162,12 +164,14 @@
|
||||||
"standalone.public_title": "A look inside...",
|
"standalone.public_title": "A look inside...",
|
||||||
"status.cannot_reblog": "This post cannot be boosted",
|
"status.cannot_reblog": "This post cannot be boosted",
|
||||||
"status.delete": "Efacar",
|
"status.delete": "Efacar",
|
||||||
|
"status.embed": "Embed",
|
||||||
"status.favourite": "Favorizar",
|
"status.favourite": "Favorizar",
|
||||||
"status.load_more": "Kargar pluse",
|
"status.load_more": "Kargar pluse",
|
||||||
"status.media_hidden": "Kontenajo celita",
|
"status.media_hidden": "Kontenajo celita",
|
||||||
"status.mention": "Mencionar @{name}",
|
"status.mention": "Mencionar @{name}",
|
||||||
"status.mute_conversation": "Mute conversation",
|
"status.mute_conversation": "Mute conversation",
|
||||||
"status.open": "Detaligar ca mesajo",
|
"status.open": "Detaligar ca mesajo",
|
||||||
|
"status.pin": "Pin on profile",
|
||||||
"status.reblog": "Repetar",
|
"status.reblog": "Repetar",
|
||||||
"status.reblogged_by": "{name} repetita",
|
"status.reblogged_by": "{name} repetita",
|
||||||
"status.reply": "Respondar",
|
"status.reply": "Respondar",
|
||||||
|
@ -179,6 +183,7 @@
|
||||||
"status.show_less": "Montrar mine",
|
"status.show_less": "Montrar mine",
|
||||||
"status.show_more": "Montrar plue",
|
"status.show_more": "Montrar plue",
|
||||||
"status.unmute_conversation": "Unmute conversation",
|
"status.unmute_conversation": "Unmute conversation",
|
||||||
|
"status.unpin": "Unpin from profile",
|
||||||
"tabs_bar.compose": "Kompozar",
|
"tabs_bar.compose": "Kompozar",
|
||||||
"tabs_bar.federated_timeline": "Federata",
|
"tabs_bar.federated_timeline": "Federata",
|
||||||
"tabs_bar.home": "Hemo",
|
"tabs_bar.home": "Hemo",
|
||||||
|
|
|
@ -63,6 +63,8 @@
|
||||||
"confirmations.mute.message": "Are you sure you want to mute {name}?",
|
"confirmations.mute.message": "Are you sure you want to mute {name}?",
|
||||||
"confirmations.unfollow.confirm": "Unfollow",
|
"confirmations.unfollow.confirm": "Unfollow",
|
||||||
"confirmations.unfollow.message": "Are you sure you want to unfollow {name}?",
|
"confirmations.unfollow.message": "Are you sure you want to unfollow {name}?",
|
||||||
|
"embed.instructions": "Embed this status on your website by copying the code below.",
|
||||||
|
"embed.preview": "Here is what it will look like:",
|
||||||
"emoji_button.activity": "Activity",
|
"emoji_button.activity": "Activity",
|
||||||
"emoji_button.flags": "Flags",
|
"emoji_button.flags": "Flags",
|
||||||
"emoji_button.food": "Food & Drink",
|
"emoji_button.food": "Food & Drink",
|
||||||
|
@ -162,12 +164,14 @@
|
||||||
"standalone.public_title": "A look inside...",
|
"standalone.public_title": "A look inside...",
|
||||||
"status.cannot_reblog": "This post cannot be boosted",
|
"status.cannot_reblog": "This post cannot be boosted",
|
||||||
"status.delete": "Elimina",
|
"status.delete": "Elimina",
|
||||||
|
"status.embed": "Embed",
|
||||||
"status.favourite": "Apprezzato",
|
"status.favourite": "Apprezzato",
|
||||||
"status.load_more": "Mostra di più",
|
"status.load_more": "Mostra di più",
|
||||||
"status.media_hidden": "Allegato nascosto",
|
"status.media_hidden": "Allegato nascosto",
|
||||||
"status.mention": "Nomina @{name}",
|
"status.mention": "Nomina @{name}",
|
||||||
"status.mute_conversation": "Mute conversation",
|
"status.mute_conversation": "Mute conversation",
|
||||||
"status.open": "Espandi questo post",
|
"status.open": "Espandi questo post",
|
||||||
|
"status.pin": "Pin on profile",
|
||||||
"status.reblog": "Condividi",
|
"status.reblog": "Condividi",
|
||||||
"status.reblogged_by": "{name} ha condiviso",
|
"status.reblogged_by": "{name} ha condiviso",
|
||||||
"status.reply": "Rispondi",
|
"status.reply": "Rispondi",
|
||||||
|
@ -179,6 +183,7 @@
|
||||||
"status.show_less": "Mostra meno",
|
"status.show_less": "Mostra meno",
|
||||||
"status.show_more": "Mostra di più",
|
"status.show_more": "Mostra di più",
|
||||||
"status.unmute_conversation": "Unmute conversation",
|
"status.unmute_conversation": "Unmute conversation",
|
||||||
|
"status.unpin": "Unpin from profile",
|
||||||
"tabs_bar.compose": "Scrivi",
|
"tabs_bar.compose": "Scrivi",
|
||||||
"tabs_bar.federated_timeline": "Federazione",
|
"tabs_bar.federated_timeline": "Federazione",
|
||||||
"tabs_bar.home": "Home",
|
"tabs_bar.home": "Home",
|
||||||
|
|
|
@ -63,6 +63,8 @@
|
||||||
"confirmations.mute.message": "本当に{name}をミュートしますか?",
|
"confirmations.mute.message": "本当に{name}をミュートしますか?",
|
||||||
"confirmations.unfollow.confirm": "フォロー解除",
|
"confirmations.unfollow.confirm": "フォロー解除",
|
||||||
"confirmations.unfollow.message": "本当に{name}をフォロー解除しますか?",
|
"confirmations.unfollow.message": "本当に{name}をフォロー解除しますか?",
|
||||||
|
"embed.instructions": "下記のコードをコピーしてウェブサイトに埋め込みます。",
|
||||||
|
"embed.preview": "表示例:",
|
||||||
"emoji_button.activity": "活動",
|
"emoji_button.activity": "活動",
|
||||||
"emoji_button.flags": "国旗",
|
"emoji_button.flags": "国旗",
|
||||||
"emoji_button.food": "食べ物",
|
"emoji_button.food": "食べ物",
|
||||||
|
@ -159,15 +161,17 @@
|
||||||
"report.target": "{target} を通報する",
|
"report.target": "{target} を通報する",
|
||||||
"search.placeholder": "検索",
|
"search.placeholder": "検索",
|
||||||
"search_results.total": "{count, number}件の結果",
|
"search_results.total": "{count, number}件の結果",
|
||||||
"standalone.public_title": "連合タイムライン",
|
"standalone.public_title": "今こんな話をしています",
|
||||||
"status.cannot_reblog": "この投稿はブーストできません",
|
"status.cannot_reblog": "この投稿はブーストできません",
|
||||||
"status.delete": "削除",
|
"status.delete": "削除",
|
||||||
|
"status.embed": "埋め込み",
|
||||||
"status.favourite": "お気に入り",
|
"status.favourite": "お気に入り",
|
||||||
"status.load_more": "もっと見る",
|
"status.load_more": "もっと見る",
|
||||||
"status.media_hidden": "非表示のメディア",
|
"status.media_hidden": "非表示のメディア",
|
||||||
"status.mention": "返信",
|
"status.mention": "返信",
|
||||||
"status.mute_conversation": "会話をミュート",
|
"status.mute_conversation": "会話をミュート",
|
||||||
"status.open": "詳細を表示",
|
"status.open": "詳細を表示",
|
||||||
|
"status.pin": "プロフィールに固定表示",
|
||||||
"status.reblog": "ブースト",
|
"status.reblog": "ブースト",
|
||||||
"status.reblogged_by": "{name}さんにブーストされました",
|
"status.reblogged_by": "{name}さんにブーストされました",
|
||||||
"status.reply": "返信",
|
"status.reply": "返信",
|
||||||
|
@ -179,6 +183,7 @@
|
||||||
"status.show_less": "隠す",
|
"status.show_less": "隠す",
|
||||||
"status.show_more": "もっと見る",
|
"status.show_more": "もっと見る",
|
||||||
"status.unmute_conversation": "会話のミュートを解除",
|
"status.unmute_conversation": "会話のミュートを解除",
|
||||||
|
"status.unpin": "プロフィールの固定表示を解除",
|
||||||
"tabs_bar.compose": "投稿",
|
"tabs_bar.compose": "投稿",
|
||||||
"tabs_bar.federated_timeline": "連合",
|
"tabs_bar.federated_timeline": "連合",
|
||||||
"tabs_bar.home": "ホーム",
|
"tabs_bar.home": "ホーム",
|
||||||
|
|
|
@ -63,6 +63,8 @@
|
||||||
"confirmations.mute.message": "정말로 {name}를 뮤트하시겠습니까?",
|
"confirmations.mute.message": "정말로 {name}를 뮤트하시겠습니까?",
|
||||||
"confirmations.unfollow.confirm": "Unfollow",
|
"confirmations.unfollow.confirm": "Unfollow",
|
||||||
"confirmations.unfollow.message": "Are you sure you want to unfollow {name}?",
|
"confirmations.unfollow.message": "Are you sure you want to unfollow {name}?",
|
||||||
|
"embed.instructions": "Embed this status on your website by copying the code below.",
|
||||||
|
"embed.preview": "Here is what it will look like:",
|
||||||
"emoji_button.activity": "활동",
|
"emoji_button.activity": "활동",
|
||||||
"emoji_button.flags": "국기",
|
"emoji_button.flags": "국기",
|
||||||
"emoji_button.food": "음식",
|
"emoji_button.food": "음식",
|
||||||
|
@ -162,12 +164,14 @@
|
||||||
"standalone.public_title": "A look inside...",
|
"standalone.public_title": "A look inside...",
|
||||||
"status.cannot_reblog": "이 포스트는 부스트 할 수 없습니다",
|
"status.cannot_reblog": "이 포스트는 부스트 할 수 없습니다",
|
||||||
"status.delete": "삭제",
|
"status.delete": "삭제",
|
||||||
|
"status.embed": "Embed",
|
||||||
"status.favourite": "즐겨찾기",
|
"status.favourite": "즐겨찾기",
|
||||||
"status.load_more": "더 보기",
|
"status.load_more": "더 보기",
|
||||||
"status.media_hidden": "미디어 숨겨짐",
|
"status.media_hidden": "미디어 숨겨짐",
|
||||||
"status.mention": "답장",
|
"status.mention": "답장",
|
||||||
"status.mute_conversation": "이 대화를 뮤트",
|
"status.mute_conversation": "이 대화를 뮤트",
|
||||||
"status.open": "상세 정보 표시",
|
"status.open": "상세 정보 표시",
|
||||||
|
"status.pin": "Pin on profile",
|
||||||
"status.reblog": "부스트",
|
"status.reblog": "부스트",
|
||||||
"status.reblogged_by": "{name}님이 부스트 했습니다",
|
"status.reblogged_by": "{name}님이 부스트 했습니다",
|
||||||
"status.reply": "답장",
|
"status.reply": "답장",
|
||||||
|
@ -179,6 +183,7 @@
|
||||||
"status.show_less": "숨기기",
|
"status.show_less": "숨기기",
|
||||||
"status.show_more": "더 보기",
|
"status.show_more": "더 보기",
|
||||||
"status.unmute_conversation": "이 대화의 뮤트 해제하기",
|
"status.unmute_conversation": "이 대화의 뮤트 해제하기",
|
||||||
|
"status.unpin": "Unpin from profile",
|
||||||
"tabs_bar.compose": "포스트",
|
"tabs_bar.compose": "포스트",
|
||||||
"tabs_bar.federated_timeline": "연합",
|
"tabs_bar.federated_timeline": "연합",
|
||||||
"tabs_bar.home": "홈",
|
"tabs_bar.home": "홈",
|
||||||
|
|
|
@ -63,6 +63,8 @@
|
||||||
"confirmations.mute.message": "Weet je het zeker dat je {name} wilt negeren?",
|
"confirmations.mute.message": "Weet je het zeker dat je {name} wilt negeren?",
|
||||||
"confirmations.unfollow.confirm": "Ontvolgen",
|
"confirmations.unfollow.confirm": "Ontvolgen",
|
||||||
"confirmations.unfollow.message": "Weet je het zeker dat je {name} wilt ontvolgen?",
|
"confirmations.unfollow.message": "Weet je het zeker dat je {name} wilt ontvolgen?",
|
||||||
|
"embed.instructions": "Embed this status on your website by copying the code below.",
|
||||||
|
"embed.preview": "Here is what it will look like:",
|
||||||
"emoji_button.activity": "Activiteiten",
|
"emoji_button.activity": "Activiteiten",
|
||||||
"emoji_button.flags": "Vlaggen",
|
"emoji_button.flags": "Vlaggen",
|
||||||
"emoji_button.food": "Eten en drinken",
|
"emoji_button.food": "Eten en drinken",
|
||||||
|
@ -162,12 +164,14 @@
|
||||||
"standalone.public_title": "Een kijkje binnenin...",
|
"standalone.public_title": "Een kijkje binnenin...",
|
||||||
"status.cannot_reblog": "Deze toot kan niet geboost worden",
|
"status.cannot_reblog": "Deze toot kan niet geboost worden",
|
||||||
"status.delete": "Verwijderen",
|
"status.delete": "Verwijderen",
|
||||||
|
"status.embed": "Embed",
|
||||||
"status.favourite": "Favoriet",
|
"status.favourite": "Favoriet",
|
||||||
"status.load_more": "Meer laden",
|
"status.load_more": "Meer laden",
|
||||||
"status.media_hidden": "Media verborgen",
|
"status.media_hidden": "Media verborgen",
|
||||||
"status.mention": "Vermeld @{name}",
|
"status.mention": "Vermeld @{name}",
|
||||||
"status.mute_conversation": "Negeer conversatie",
|
"status.mute_conversation": "Negeer conversatie",
|
||||||
"status.open": "Toot volledig tonen",
|
"status.open": "Toot volledig tonen",
|
||||||
|
"status.pin": "Pin on profile",
|
||||||
"status.reblog": "Boost",
|
"status.reblog": "Boost",
|
||||||
"status.reblogged_by": "{name} boostte",
|
"status.reblogged_by": "{name} boostte",
|
||||||
"status.reply": "Reageren",
|
"status.reply": "Reageren",
|
||||||
|
@ -179,6 +183,7 @@
|
||||||
"status.show_less": "Minder tonen",
|
"status.show_less": "Minder tonen",
|
||||||
"status.show_more": "Meer tonen",
|
"status.show_more": "Meer tonen",
|
||||||
"status.unmute_conversation": "Conversatie niet meer negeren",
|
"status.unmute_conversation": "Conversatie niet meer negeren",
|
||||||
|
"status.unpin": "Unpin from profile",
|
||||||
"tabs_bar.compose": "Schrijven",
|
"tabs_bar.compose": "Schrijven",
|
||||||
"tabs_bar.federated_timeline": "Globaal",
|
"tabs_bar.federated_timeline": "Globaal",
|
||||||
"tabs_bar.home": "Start",
|
"tabs_bar.home": "Start",
|
||||||
|
|
|
@ -63,6 +63,8 @@
|
||||||
"confirmations.mute.message": "Er du sikker på at du vil dempe {name}?",
|
"confirmations.mute.message": "Er du sikker på at du vil dempe {name}?",
|
||||||
"confirmations.unfollow.confirm": "Unfollow",
|
"confirmations.unfollow.confirm": "Unfollow",
|
||||||
"confirmations.unfollow.message": "Are you sure you want to unfollow {name}?",
|
"confirmations.unfollow.message": "Are you sure you want to unfollow {name}?",
|
||||||
|
"embed.instructions": "Embed this status on your website by copying the code below.",
|
||||||
|
"embed.preview": "Here is what it will look like:",
|
||||||
"emoji_button.activity": "Aktivitet",
|
"emoji_button.activity": "Aktivitet",
|
||||||
"emoji_button.flags": "Flagg",
|
"emoji_button.flags": "Flagg",
|
||||||
"emoji_button.food": "Mat og drikke",
|
"emoji_button.food": "Mat og drikke",
|
||||||
|
@ -162,12 +164,14 @@
|
||||||
"standalone.public_title": "A look inside...",
|
"standalone.public_title": "A look inside...",
|
||||||
"status.cannot_reblog": "Denne posten kan ikke fremheves",
|
"status.cannot_reblog": "Denne posten kan ikke fremheves",
|
||||||
"status.delete": "Slett",
|
"status.delete": "Slett",
|
||||||
|
"status.embed": "Embed",
|
||||||
"status.favourite": "Lik",
|
"status.favourite": "Lik",
|
||||||
"status.load_more": "Last mer",
|
"status.load_more": "Last mer",
|
||||||
"status.media_hidden": "Media skjult",
|
"status.media_hidden": "Media skjult",
|
||||||
"status.mention": "Nevn @{name}",
|
"status.mention": "Nevn @{name}",
|
||||||
"status.mute_conversation": "Demp samtale",
|
"status.mute_conversation": "Demp samtale",
|
||||||
"status.open": "Utvid denne statusen",
|
"status.open": "Utvid denne statusen",
|
||||||
|
"status.pin": "Pin on profile",
|
||||||
"status.reblog": "Fremhev",
|
"status.reblog": "Fremhev",
|
||||||
"status.reblogged_by": "Fremhevd av {name}",
|
"status.reblogged_by": "Fremhevd av {name}",
|
||||||
"status.reply": "Svar",
|
"status.reply": "Svar",
|
||||||
|
@ -179,6 +183,7 @@
|
||||||
"status.show_less": "Vis mindre",
|
"status.show_less": "Vis mindre",
|
||||||
"status.show_more": "Vis mer",
|
"status.show_more": "Vis mer",
|
||||||
"status.unmute_conversation": "Ikke demp samtale",
|
"status.unmute_conversation": "Ikke demp samtale",
|
||||||
|
"status.unpin": "Unpin from profile",
|
||||||
"tabs_bar.compose": "Komponer",
|
"tabs_bar.compose": "Komponer",
|
||||||
"tabs_bar.federated_timeline": "Felles",
|
"tabs_bar.federated_timeline": "Felles",
|
||||||
"tabs_bar.home": "Hjem",
|
"tabs_bar.home": "Hjem",
|
||||||
|
|
|
@ -45,24 +45,26 @@
|
||||||
"column_subheading.settings": "Paramètres",
|
"column_subheading.settings": "Paramètres",
|
||||||
"compose_form.lock_disclaimer": "Vòstre compte es pas {locked}. Tot lo mond pòt vos sègre e veire los estatuts reservats als seguidors.",
|
"compose_form.lock_disclaimer": "Vòstre compte es pas {locked}. Tot lo mond pòt vos sègre e veire los estatuts reservats als seguidors.",
|
||||||
"compose_form.lock_disclaimer.lock": "clavat",
|
"compose_form.lock_disclaimer.lock": "clavat",
|
||||||
"compose_form.placeholder": "A de qué pensatz ?",
|
"compose_form.placeholder": "A de qué pensatz ?",
|
||||||
"compose_form.privacy_disclaimer": "Vòstre estatut privat serà enviat a las personas mencionadas sus {domains}. Vos fisatz d’aqueste {domainsCount, plural, one { servidor} other {s servidors}} per divulgar pas vòstre estatut ? Los estatuts privats foncionan pas que sus las instàncias de Mastodon. Se {domains} {domainsCount, plural, one {es pas una instància a Mastodon} other {son pas d'instàncias a Mastodon}}, i aurà pas d’indicacion disent que vòstre estatut es privat e poirà èsser partejat o èsser visible a de mond pas prevists",
|
"compose_form.privacy_disclaimer": "Vòstre estatut privat serà enviat a las personas mencionadas sus {domains}. Vos fisatz d’aqueste {domainsCount, plural, one { servidor} other {s servidors}} per divulgar pas vòstre estatut ? Los estatuts privats foncionan pas que sus las instàncias de Mastodon. Se {domains} {domainsCount, plural, one {es pas una instància a Mastodon} other {son pas d'instàncias a Mastodon}}, i aurà pas d’indicacion disent que vòstre estatut es privat e poirà èsser partejat o èsser visible a de mond pas prevists",
|
||||||
"compose_form.publish": "Tut",
|
"compose_form.publish": "Tut",
|
||||||
"compose_form.publish_loud": "{publish} !",
|
"compose_form.publish_loud": "{publish} !",
|
||||||
"compose_form.sensitive": "Marcar lo mèdia coma sensible",
|
"compose_form.sensitive": "Marcar lo mèdia coma sensible",
|
||||||
"compose_form.spoiler": "Rescondre lo tèxte darrièr un avertiment",
|
"compose_form.spoiler": "Rescondre lo tèxte darrièr un avertiment",
|
||||||
"compose_form.spoiler_placeholder": "Escrivètz l’avertiment aquí",
|
"compose_form.spoiler_placeholder": "Escrivètz l’avertiment aquí",
|
||||||
"confirmation_modal.cancel": "Anullar",
|
"confirmation_modal.cancel": "Anullar",
|
||||||
"confirmations.block.confirm": "Blocar",
|
"confirmations.block.confirm": "Blocar",
|
||||||
"confirmations.block.message": "Sètz segur de voler blocar {name} ?",
|
"confirmations.block.message": "Sètz segur de voler blocar {name} ?",
|
||||||
"confirmations.delete.confirm": "Suprimir",
|
"confirmations.delete.confirm": "Suprimir",
|
||||||
"confirmations.delete.message": "Sètz segur de voler suprimir l’estatut ?",
|
"confirmations.delete.message": "Sètz segur de voler suprimir l’estatut ?",
|
||||||
"confirmations.domain_block.confirm": "Amagar tot lo domeni",
|
"confirmations.domain_block.confirm": "Amagar tot lo domeni",
|
||||||
"confirmations.domain_block.message": "Sètz segur segur de voler blocar completament {domain} ? De còps cal pas que blocar o rescondre unas personas solament.",
|
"confirmations.domain_block.message": "Sètz segur segur de voler blocar completament {domain} ? De còps cal pas que blocar o rescondre unas personas solament.",
|
||||||
"confirmations.mute.confirm": "Metre en silenci",
|
"confirmations.mute.confirm": "Metre en silenci",
|
||||||
"confirmations.mute.message": "Sètz segur de voler metre en silenci {name} ?",
|
"confirmations.mute.message": "Sètz segur de voler metre en silenci {name} ?",
|
||||||
"confirmations.unfollow.confirm": "Quitar de sègre",
|
"confirmations.unfollow.confirm": "Quitar de sègre",
|
||||||
"confirmations.unfollow.message": "Volètz vertadièrament quitar de sègre {name} ?",
|
"confirmations.unfollow.message": "Volètz vertadièrament quitar de sègre {name} ?",
|
||||||
|
"embed.instructions": "Embed this status on your website by copying the code below.",
|
||||||
|
"embed.preview": "Here is what it will look like:",
|
||||||
"emoji_button.activity": "Activitats",
|
"emoji_button.activity": "Activitats",
|
||||||
"emoji_button.flags": "Drapèus",
|
"emoji_button.flags": "Drapèus",
|
||||||
"emoji_button.food": "Beure e manjar",
|
"emoji_button.food": "Beure e manjar",
|
||||||
|
@ -73,13 +75,13 @@
|
||||||
"emoji_button.search": "Cercar…",
|
"emoji_button.search": "Cercar…",
|
||||||
"emoji_button.symbols": "Simbòls",
|
"emoji_button.symbols": "Simbòls",
|
||||||
"emoji_button.travel": "Viatges & lòcs",
|
"emoji_button.travel": "Viatges & lòcs",
|
||||||
"empty_column.community": "Lo flux public local es void. Escrivètz quicòm per lo garnir !",
|
"empty_column.community": "Lo flux public local es void. Escrivètz quicòm per lo garnir !",
|
||||||
"empty_column.hashtag": "I a pas encara de contengut ligat a aqueste hashtag",
|
"empty_column.hashtag": "I a pas encara de contengut ligat a aqueste hashtag",
|
||||||
"empty_column.home": "Pel moment seguètz pas degun. Visitatz {public} o utilizatz la recèrca per vos connectar a d’autras personas.",
|
"empty_column.home": "Pel moment seguètz pas degun. Visitatz {public} o utilizatz la recèrca per vos connectar a d’autras personas.",
|
||||||
"empty_column.home.inactivity": "Vòstra pagina d’acuèlh es voida. Se sètz estat inactiu per un moment, serà tornada generar per vos dins una estona.",
|
"empty_column.home.inactivity": "Vòstra pagina d’acuèlh es voida. Se sètz estat inactiu per un moment, serà tornada generar per vos dins una estona.",
|
||||||
"empty_column.home.public_timeline": "lo flux public",
|
"empty_column.home.public_timeline": "lo flux public",
|
||||||
"empty_column.notifications": "Avètz pas encara de notificacions. Respondètz a qualqu’un per començar una conversacion.",
|
"empty_column.notifications": "Avètz pas encara de notificacions. Respondètz a qualqu’un per començar una conversacion.",
|
||||||
"empty_column.public": "I a pas res aquí ! Escrivètz quicòm de public, o seguètz de personas d’autras instàncias per garnir lo flux public.",
|
"empty_column.public": "I a pas res aquí ! Escrivètz quicòm de public, o seguètz de personas d’autras instàncias per garnir lo flux public.",
|
||||||
"follow_request.authorize": "Autorizar",
|
"follow_request.authorize": "Autorizar",
|
||||||
"follow_request.reject": "Regetar",
|
"follow_request.reject": "Regetar",
|
||||||
"getting_started.appsshort": "Apps",
|
"getting_started.appsshort": "Apps",
|
||||||
|
@ -109,19 +111,19 @@
|
||||||
"navigation_bar.mutes": "Personas rescondudas",
|
"navigation_bar.mutes": "Personas rescondudas",
|
||||||
"navigation_bar.preferences": "Preferéncias",
|
"navigation_bar.preferences": "Preferéncias",
|
||||||
"navigation_bar.public_timeline": "Flux public global",
|
"navigation_bar.public_timeline": "Flux public global",
|
||||||
"notification.favourite": "{name} a ajustat a sos favorits :",
|
"notification.favourite": "{name} a ajustat a sos favorits :",
|
||||||
"notification.follow": "{name} vos sèc",
|
"notification.follow": "{name} vos sèc",
|
||||||
"notification.mention": "{name} vos a mencionat :",
|
"notification.mention": "{name} vos a mencionat :",
|
||||||
"notification.reblog": "{name} a partejat vòstre estatut :",
|
"notification.reblog": "{name} a partejat vòstre estatut :",
|
||||||
"notifications.clear": "Escafar",
|
"notifications.clear": "Escafar",
|
||||||
"notifications.clear_confirmation": "Volètz vertadièrament escafar totas vòstras las notificacions ?",
|
"notifications.clear_confirmation": "Volètz vertadièrament escafar totas vòstras las notificacions ?",
|
||||||
"notifications.column_settings.alert": "Notificacions localas",
|
"notifications.column_settings.alert": "Notificacions localas",
|
||||||
"notifications.column_settings.favourite": "Favorits :",
|
"notifications.column_settings.favourite": "Favorits :",
|
||||||
"notifications.column_settings.follow": "Nòus seguidors :",
|
"notifications.column_settings.follow": "Nòus seguidors :",
|
||||||
"notifications.column_settings.mention": "Mencions :",
|
"notifications.column_settings.mention": "Mencions :",
|
||||||
"notifications.column_settings.push": "Notificacions",
|
"notifications.column_settings.push": "Notificacions",
|
||||||
"notifications.column_settings.push_meta": "Aqueste periferic",
|
"notifications.column_settings.push_meta": "Aqueste periferic",
|
||||||
"notifications.column_settings.reblog": "Partatges :",
|
"notifications.column_settings.reblog": "Partatges :",
|
||||||
"notifications.column_settings.show": "Mostrar dins la colomna",
|
"notifications.column_settings.show": "Mostrar dins la colomna",
|
||||||
"notifications.column_settings.sound": "Emetre un son",
|
"notifications.column_settings.sound": "Emetre un son",
|
||||||
"onboarding.done": "Fach",
|
"onboarding.done": "Fach",
|
||||||
|
@ -131,14 +133,14 @@
|
||||||
"onboarding.page_four.notifications": "La colomna de notificacions vos fa veire quand qualqu’un interagís amb vos",
|
"onboarding.page_four.notifications": "La colomna de notificacions vos fa veire quand qualqu’un interagís amb vos",
|
||||||
"onboarding.page_one.federation": "Mastodon es un malhum de servidors independents que comunican per bastir un malhum ma larg. Òm los apèla instàncias.",
|
"onboarding.page_one.federation": "Mastodon es un malhum de servidors independents que comunican per bastir un malhum ma larg. Òm los apèla instàncias.",
|
||||||
"onboarding.page_one.handle": "Sètz sus {domain}, doncas vòstre identificant complet es {handle}",
|
"onboarding.page_one.handle": "Sètz sus {domain}, doncas vòstre identificant complet es {handle}",
|
||||||
"onboarding.page_one.welcome": "Benvengut a Mastodon !",
|
"onboarding.page_one.welcome": "Benvengut a Mastodon !",
|
||||||
"onboarding.page_six.admin": "Vòstre administrator d’instància es {admin}.",
|
"onboarding.page_six.admin": "Vòstre administrator d’instància es {admin}.",
|
||||||
"onboarding.page_six.almost_done": "Gaireben acabat…",
|
"onboarding.page_six.almost_done": "Gaireben acabat…",
|
||||||
"onboarding.page_six.appetoot": "Bon Appetut!",
|
"onboarding.page_six.appetoot": "Bon Appetut!",
|
||||||
"onboarding.page_six.apps_available": "I a d’aplicacions per mobil per iOS, Android e mai.",
|
"onboarding.page_six.apps_available": "I a d’aplicacions per mobil per iOS, Android e mai.",
|
||||||
"onboarding.page_six.github": "Mastodon es un logicial liure e open-source. Podètz senhalar de bugs, demandar de foncionalitats e contribuir al còdi sus {github}.",
|
"onboarding.page_six.github": "Mastodon es un logicial liure e open-source. Podètz senhalar de bugs, demandar de foncionalitats e contribuir al còdi sus {github}.",
|
||||||
"onboarding.page_six.guidelines": "guida de la comunitat",
|
"onboarding.page_six.guidelines": "guida de la comunitat",
|
||||||
"onboarding.page_six.read_guidelines": "Mercés de legir la {guidelines} a {domain} !",
|
"onboarding.page_six.read_guidelines": "Mercés de legir la {guidelines} a {domain} !",
|
||||||
"onboarding.page_six.various_app": "aplicacions per mobil",
|
"onboarding.page_six.various_app": "aplicacions per mobil",
|
||||||
"onboarding.page_three.profile": "Modificatz vòstre perfil per cambiar vòstre avatar, bio e escais-nom. I a enlà totas las preferéncias.",
|
"onboarding.page_three.profile": "Modificatz vòstre perfil per cambiar vòstre avatar, bio e escais-nom. I a enlà totas las preferéncias.",
|
||||||
"onboarding.page_three.search": "Emplegatz la barra de recèrca per trobar de mond e engachatz las etiquetas coma {illustration} e {introductions}. Per trobar una persona d’una autra instància, picatz son identificant complet.",
|
"onboarding.page_three.search": "Emplegatz la barra de recèrca per trobar de mond e engachatz las etiquetas coma {illustration} e {introductions}. Per trobar una persona d’una autra instància, picatz son identificant complet.",
|
||||||
|
@ -162,14 +164,16 @@
|
||||||
"standalone.public_title": "Una ulhada dedins…",
|
"standalone.public_title": "Una ulhada dedins…",
|
||||||
"status.cannot_reblog": "Aqueste estatut pòt pas èsser partejat",
|
"status.cannot_reblog": "Aqueste estatut pòt pas èsser partejat",
|
||||||
"status.delete": "Escafar",
|
"status.delete": "Escafar",
|
||||||
|
"status.embed": "Embed",
|
||||||
"status.favourite": "Apondre als favorits",
|
"status.favourite": "Apondre als favorits",
|
||||||
"status.load_more": "Cargar mai",
|
"status.load_more": "Cargar mai",
|
||||||
"status.media_hidden": "Mèdia rescondut",
|
"status.media_hidden": "Mèdia rescondut",
|
||||||
"status.mention": "Mencionar",
|
"status.mention": "Mencionar",
|
||||||
"status.mute_conversation": "Rescondre la conversacion",
|
"status.mute_conversation": "Rescondre la conversacion",
|
||||||
"status.open": "Desplegar aqueste estatut",
|
"status.open": "Desplegar aqueste estatut",
|
||||||
|
"status.pin": "Penjar al perfil",
|
||||||
"status.reblog": "Partejar",
|
"status.reblog": "Partejar",
|
||||||
"status.reblogged_by": "{name} a partejat :",
|
"status.reblogged_by": "{name} a partejat :",
|
||||||
"status.reply": "Respondre",
|
"status.reply": "Respondre",
|
||||||
"status.replyAll": "Respondre a la conversacion",
|
"status.replyAll": "Respondre a la conversacion",
|
||||||
"status.report": "Senhalar @{name}",
|
"status.report": "Senhalar @{name}",
|
||||||
|
@ -179,6 +183,7 @@
|
||||||
"status.show_less": "Tornar plegar",
|
"status.show_less": "Tornar plegar",
|
||||||
"status.show_more": "Desplegar",
|
"status.show_more": "Desplegar",
|
||||||
"status.unmute_conversation": "Conversacions amb silenci levat",
|
"status.unmute_conversation": "Conversacions amb silenci levat",
|
||||||
|
"status.unpin": "Despenjar del perfil",
|
||||||
"tabs_bar.compose": "Compausar",
|
"tabs_bar.compose": "Compausar",
|
||||||
"tabs_bar.federated_timeline": "Flux public global",
|
"tabs_bar.federated_timeline": "Flux public global",
|
||||||
"tabs_bar.home": "Acuèlh",
|
"tabs_bar.home": "Acuèlh",
|
||||||
|
|
|
@ -10,7 +10,7 @@
|
||||||
"account.media": "Media",
|
"account.media": "Media",
|
||||||
"account.mention": "Wspomnij o @{name}",
|
"account.mention": "Wspomnij o @{name}",
|
||||||
"account.mute": "Wycisz @{name}",
|
"account.mute": "Wycisz @{name}",
|
||||||
"account.posts": "Posty",
|
"account.posts": "Wpisy",
|
||||||
"account.report": "Zgłoś @{name}",
|
"account.report": "Zgłoś @{name}",
|
||||||
"account.requested": "Oczekująca prośba",
|
"account.requested": "Oczekująca prośba",
|
||||||
"account.share": "Udostępnij profil @{name}",
|
"account.share": "Udostępnij profil @{name}",
|
||||||
|
@ -43,10 +43,10 @@
|
||||||
"column_header.unpin": "Cofnij przypięcie",
|
"column_header.unpin": "Cofnij przypięcie",
|
||||||
"column_subheading.navigation": "Nawigacja",
|
"column_subheading.navigation": "Nawigacja",
|
||||||
"column_subheading.settings": "Ustawienia",
|
"column_subheading.settings": "Ustawienia",
|
||||||
"compose_form.lock_disclaimer": "Twoje konto nie jest {locked}. Każdy, kto Cię śledzi, może wyświetlać Twoje posty przeznaczone tylko dla śledzących.",
|
"compose_form.lock_disclaimer": "Twoje konto nie jest {locked}. Każdy, kto Cię śledzi, może wyświetlać Twoje wpisy przeznaczone tylko dla śledzących.",
|
||||||
"compose_form.lock_disclaimer.lock": "zablokowane",
|
"compose_form.lock_disclaimer.lock": "zablokowane",
|
||||||
"compose_form.placeholder": "Co Ci chodzi po głowie?",
|
"compose_form.placeholder": "Co Ci chodzi po głowie?",
|
||||||
"compose_form.privacy_disclaimer": "Twój post zostanie dostarczony do użytkowników z {domains}. Czy ufasz {domainsCount, plural, one {temu serwerowi} other {tym serwerom}}? Prywatność postów obowiązuje tylko na instancjach Mastodona. Jeżeli {domains} {domainsCount, plural, one {nie jest instancją Mastodona} other {nie są instancjami Mastodona}}, post może być widoczny dla niewłaściwych osób.",
|
"compose_form.privacy_disclaimer": "Twój wpis zostanie dostarczony do użytkowników z {domains}. Czy ufasz {domainsCount, plural, one {temu serwerowi} other {tym serwerom}}? Prywatność wpisów obowiązuje tylko na instancjach Mastodona. Jeżeli {domains} {domainsCount, plural, one {nie jest instancją Mastodona} other {nie są instancjami Mastodona}}, wpis może być widoczny dla niewłaściwych osób.",
|
||||||
"compose_form.publish": "Wyślij",
|
"compose_form.publish": "Wyślij",
|
||||||
"compose_form.publish_loud": "{publish}!",
|
"compose_form.publish_loud": "{publish}!",
|
||||||
"compose_form.sensitive": "Oznacz treści jako wrażliwe",
|
"compose_form.sensitive": "Oznacz treści jako wrażliwe",
|
||||||
|
@ -63,6 +63,8 @@
|
||||||
"confirmations.mute.message": "Czy na pewno chcesz wyciszyć {name}?",
|
"confirmations.mute.message": "Czy na pewno chcesz wyciszyć {name}?",
|
||||||
"confirmations.unfollow.confirm": "Przestań śledzić",
|
"confirmations.unfollow.confirm": "Przestań śledzić",
|
||||||
"confirmations.unfollow.message": "Czy na pewno zamierzasz przestać śledzić {name}?",
|
"confirmations.unfollow.message": "Czy na pewno zamierzasz przestać śledzić {name}?",
|
||||||
|
"embed.instructions": "Osadź ten status na swojej stronie wklejając poniższy kod.",
|
||||||
|
"embed.preview": "Tak będzie to wyglądać:",
|
||||||
"emoji_button.activity": "Aktywność",
|
"emoji_button.activity": "Aktywność",
|
||||||
"emoji_button.flags": "Flagi",
|
"emoji_button.flags": "Flagi",
|
||||||
"emoji_button.food": "Żywność i napoje",
|
"emoji_button.food": "Żywność i napoje",
|
||||||
|
@ -70,11 +72,11 @@
|
||||||
"emoji_button.nature": "Natura",
|
"emoji_button.nature": "Natura",
|
||||||
"emoji_button.objects": "Objekty",
|
"emoji_button.objects": "Objekty",
|
||||||
"emoji_button.people": "Ludzie",
|
"emoji_button.people": "Ludzie",
|
||||||
"emoji_button.search": "Szukaj...",
|
"emoji_button.search": "Szukaj…",
|
||||||
"emoji_button.symbols": "Symbole",
|
"emoji_button.symbols": "Symbole",
|
||||||
"emoji_button.travel": "Podróże i miejsca",
|
"emoji_button.travel": "Podróże i miejsca",
|
||||||
"empty_column.community": "Lokalna oś czasu jest pusta. Napisz coś publicznie, aby zagaić!",
|
"empty_column.community": "Lokalna oś czasu jest pusta. Napisz coś publicznie, aby zagaić!",
|
||||||
"empty_column.hashtag": "Nie ma postów oznaczonych tym hashtagiem. Możesz napisać pierwszy!",
|
"empty_column.hashtag": "Nie ma wpisów oznaczonych tym hashtagiem. Możesz napisać pierwszy!",
|
||||||
"empty_column.home": "Nie śledzisz nikogo. Odwiedź publiczną oś czasu lub użyj wyszukiwarki, aby znaleźć interesujące Cię profile.",
|
"empty_column.home": "Nie śledzisz nikogo. Odwiedź publiczną oś czasu lub użyj wyszukiwarki, aby znaleźć interesujące Cię profile.",
|
||||||
"empty_column.home.inactivity": "Strumień jest pusty. Jeżeli nie było Cię tu ostatnio, zostanie on wypełniony wkrótce.",
|
"empty_column.home.inactivity": "Strumień jest pusty. Jeżeli nie było Cię tu ostatnio, zostanie on wypełniony wkrótce.",
|
||||||
"empty_column.home.public_timeline": "publiczna oś czasu",
|
"empty_column.home.public_timeline": "publiczna oś czasu",
|
||||||
|
@ -85,7 +87,7 @@
|
||||||
"getting_started.appsshort": "Aplikacje",
|
"getting_started.appsshort": "Aplikacje",
|
||||||
"getting_started.faq": "FAQ",
|
"getting_started.faq": "FAQ",
|
||||||
"getting_started.heading": "Naucz się korzystać",
|
"getting_started.heading": "Naucz się korzystać",
|
||||||
"getting_started.open_source_notice": "Mastodon jest oprogramowaniem o otwartym źródle. Możesz pomóc w rozwoju lub zgłaszać błędy na GitHubie tutaj {github}.",
|
"getting_started.open_source_notice": "Mastodon jest oprogramowaniem o otwartym źródle. Możesz pomóc w rozwoju lub zgłaszać błędy na GitHubie tutaj: {github}.",
|
||||||
"getting_started.userguide": "Podręcznik użytkownika",
|
"getting_started.userguide": "Podręcznik użytkownika",
|
||||||
"home.column_settings.advanced": "Zaawansowane",
|
"home.column_settings.advanced": "Zaawansowane",
|
||||||
"home.column_settings.basic": "Podstawowe",
|
"home.column_settings.basic": "Podstawowe",
|
||||||
|
@ -96,7 +98,7 @@
|
||||||
"lightbox.close": "Zamknij",
|
"lightbox.close": "Zamknij",
|
||||||
"lightbox.next": "Następne",
|
"lightbox.next": "Następne",
|
||||||
"lightbox.previous": "Poprzednie",
|
"lightbox.previous": "Poprzednie",
|
||||||
"loading_indicator.label": "Ładowanie...",
|
"loading_indicator.label": "Ładowanie…",
|
||||||
"media_gallery.toggle_visible": "Przełącz widoczność",
|
"media_gallery.toggle_visible": "Przełącz widoczność",
|
||||||
"missing_indicator.label": "Nie znaleziono",
|
"missing_indicator.label": "Nie znaleziono",
|
||||||
"navigation_bar.blocks": "Zablokowani użytkownicy",
|
"navigation_bar.blocks": "Zablokowani użytkownicy",
|
||||||
|
@ -116,12 +118,12 @@
|
||||||
"notifications.clear": "Wyczyść powiadomienia",
|
"notifications.clear": "Wyczyść powiadomienia",
|
||||||
"notifications.clear_confirmation": "Czy na pewno chcesz bezpowrotnie usunąć wszystkie powiadomienia?",
|
"notifications.clear_confirmation": "Czy na pewno chcesz bezpowrotnie usunąć wszystkie powiadomienia?",
|
||||||
"notifications.column_settings.alert": "Powiadomienia na pulpicie",
|
"notifications.column_settings.alert": "Powiadomienia na pulpicie",
|
||||||
"notifications.column_settings.favourite": "Ulubione:",
|
"notifications.column_settings.favourite": "Dodanie do ulubionych:",
|
||||||
"notifications.column_settings.follow": "Nowi śledzący:",
|
"notifications.column_settings.follow": "Nowi śledzący:",
|
||||||
"notifications.column_settings.mention": "Wspomniali:",
|
"notifications.column_settings.mention": "Wspomnienia:",
|
||||||
"notifications.column_settings.push": "Powiadomienia push",
|
"notifications.column_settings.push": "Powiadomienia push",
|
||||||
"notifications.column_settings.push_meta": "To urządzenie",
|
"notifications.column_settings.push_meta": "To urządzenie",
|
||||||
"notifications.column_settings.reblog": "Podbili:",
|
"notifications.column_settings.reblog": "Podbicia:",
|
||||||
"notifications.column_settings.show": "Pokaż w kolumnie",
|
"notifications.column_settings.show": "Pokaż w kolumnie",
|
||||||
"notifications.column_settings.sound": "Odtwarzaj dźwięk",
|
"notifications.column_settings.sound": "Odtwarzaj dźwięk",
|
||||||
"onboarding.done": "Gotowe",
|
"onboarding.done": "Gotowe",
|
||||||
|
@ -142,15 +144,15 @@
|
||||||
"onboarding.page_six.various_app": "aplikacje mobilne",
|
"onboarding.page_six.various_app": "aplikacje mobilne",
|
||||||
"onboarding.page_three.profile": "Edytuj profil, aby zmienić obraz profilowy, biografię, wyświetlaną nazwę i inne ustawienia.",
|
"onboarding.page_three.profile": "Edytuj profil, aby zmienić obraz profilowy, biografię, wyświetlaną nazwę i inne ustawienia.",
|
||||||
"onboarding.page_three.search": "Użyj paska wyszukiwania aby znaleźć ludzi i hashtagi, takie jak {illustration} i {introductions}. Aby znaleźć osobę spoza tej instancji, musisz użyć pełnego adresu.",
|
"onboarding.page_three.search": "Użyj paska wyszukiwania aby znaleźć ludzi i hashtagi, takie jak {illustration} i {introductions}. Aby znaleźć osobę spoza tej instancji, musisz użyć pełnego adresu.",
|
||||||
"onboarding.page_two.compose": "Napisz posty, aby wypełnić kolumnę. Możesz wysłać zdjęcia, zmienić ustawienia prywatności lub dodać ostrzeżenie o zawartości.",
|
"onboarding.page_two.compose": "Utwórz wpisy, aby wypełnić kolumnę. Możesz wysłać zdjęcia, zmienić ustawienia prywatności lub dodać ostrzeżenie o zawartości.",
|
||||||
"onboarding.skip": "Pomiń",
|
"onboarding.skip": "Pomiń",
|
||||||
"privacy.change": "Dostosuj widoczność postów",
|
"privacy.change": "Dostosuj widoczność wpisów",
|
||||||
"privacy.direct.long": "Widoczne tylko dla oznaczonych",
|
"privacy.direct.long": "Widoczny tylko dla wspomnianych",
|
||||||
"privacy.direct.short": "Bezpośrednio",
|
"privacy.direct.short": "Bezpośrednio",
|
||||||
"privacy.private.long": "Widoczne tylko dla śledzących",
|
"privacy.private.long": "Widoczny tylko dla osób, które Cię śledzą",
|
||||||
"privacy.private.short": "Tylko śledzący",
|
"privacy.private.short": "Tylko dla śledzących",
|
||||||
"privacy.public.long": "Widoczne na publicznych osiach czasu",
|
"privacy.public.long": "Widoczny na publicznych osiach czasu",
|
||||||
"privacy.public.short": "Publiczne",
|
"privacy.public.short": "Publiczny",
|
||||||
"privacy.unlisted.long": "Niewidoczne na publicznych osiach czasu",
|
"privacy.unlisted.long": "Niewidoczne na publicznych osiach czasu",
|
||||||
"privacy.unlisted.short": "Niewidoczne",
|
"privacy.unlisted.short": "Niewidoczne",
|
||||||
"reply_indicator.cancel": "Anuluj",
|
"reply_indicator.cancel": "Anuluj",
|
||||||
|
@ -160,14 +162,16 @@
|
||||||
"search.placeholder": "Szukaj",
|
"search.placeholder": "Szukaj",
|
||||||
"search_results.total": "{count, number} {count, plural, one {wynik} more {wyniki}}",
|
"search_results.total": "{count, number} {count, plural, one {wynik} more {wyniki}}",
|
||||||
"standalone.public_title": "Spojrzenie w głąb…",
|
"standalone.public_title": "Spojrzenie w głąb…",
|
||||||
"status.cannot_reblog": "Ten post nie może zostać podbity",
|
"status.cannot_reblog": "Ten wpis nie może zostać podbity",
|
||||||
"status.delete": "Usuń",
|
"status.delete": "Usuń",
|
||||||
|
"status.embed": "Osadź",
|
||||||
"status.favourite": "Ulubione",
|
"status.favourite": "Ulubione",
|
||||||
"status.load_more": "Załaduj więcej",
|
"status.load_more": "Załaduj więcej",
|
||||||
"status.media_hidden": "Zawartość multimedialna ukryta",
|
"status.media_hidden": "Zawartość multimedialna ukryta",
|
||||||
"status.mention": "Wspomnij o @{name}",
|
"status.mention": "Wspomnij o @{name}",
|
||||||
"status.mute_conversation": "Wycisz konwersację",
|
"status.mute_conversation": "Wycisz konwersację",
|
||||||
"status.open": "Rozszerz ten status",
|
"status.open": "Rozszerz ten status",
|
||||||
|
"status.pin": "Przypnij do profilu",
|
||||||
"status.reblog": "Podbij",
|
"status.reblog": "Podbij",
|
||||||
"status.reblogged_by": "{name} podbił",
|
"status.reblogged_by": "{name} podbił",
|
||||||
"status.reply": "Odpowiedz",
|
"status.reply": "Odpowiedz",
|
||||||
|
@ -179,6 +183,7 @@
|
||||||
"status.show_less": "Pokaż mniej",
|
"status.show_less": "Pokaż mniej",
|
||||||
"status.show_more": "Pokaż więcej",
|
"status.show_more": "Pokaż więcej",
|
||||||
"status.unmute_conversation": "Cofnij wyciszenie konwersacji",
|
"status.unmute_conversation": "Cofnij wyciszenie konwersacji",
|
||||||
|
"status.unpin": "Odepnij z profilu",
|
||||||
"tabs_bar.compose": "Napisz",
|
"tabs_bar.compose": "Napisz",
|
||||||
"tabs_bar.federated_timeline": "Globalne",
|
"tabs_bar.federated_timeline": "Globalne",
|
||||||
"tabs_bar.home": "Strona główna",
|
"tabs_bar.home": "Strona główna",
|
||||||
|
|
|
@ -1,68 +1,70 @@
|
||||||
{
|
{
|
||||||
"account.block": "Bloquear @{name}",
|
"account.block": "Bloquear @{name}",
|
||||||
"account.block_domain": "Hide everything from {domain}",
|
"account.block_domain": "Esconder tudo de {domain}",
|
||||||
"account.disclaimer_full": "Information below may reflect the user's profile incompletely.",
|
"account.disclaimer_full": "As informações abaixo podem refletir o perfil do usuário de maneira incompleta.",
|
||||||
"account.edit_profile": "Editar perfil",
|
"account.edit_profile": "Editar perfil",
|
||||||
"account.follow": "Seguir",
|
"account.follow": "Seguir",
|
||||||
"account.followers": "Seguidores",
|
"account.followers": "Seguidores",
|
||||||
"account.follows": "Segue",
|
"account.follows": "Segue",
|
||||||
"account.follows_you": "É teu seguidor",
|
"account.follows_you": "É seu seguidor",
|
||||||
"account.media": "Media",
|
"account.media": "Mídia",
|
||||||
"account.mention": "Mencionar @{name}",
|
"account.mention": "Mencionar @{name}",
|
||||||
"account.mute": "Silenciar @{name}",
|
"account.mute": "Silenciar @{name}",
|
||||||
"account.posts": "Posts",
|
"account.posts": "Posts",
|
||||||
"account.report": "Denunciar @{name}",
|
"account.report": "Denunciar @{name}",
|
||||||
"account.requested": "A aguardar aprovação",
|
"account.requested": "Aguardando aprovação",
|
||||||
"account.share": "Share @{name}'s profile",
|
"account.share": "Compartilhar perfil de @{name}",
|
||||||
"account.unblock": "Não bloquear @{name}",
|
"account.unblock": "Não bloquear @{name}",
|
||||||
"account.unblock_domain": "Unhide {domain}",
|
"account.unblock_domain": "Desbloquear {domain}",
|
||||||
"account.unfollow": "Deixar de seguir",
|
"account.unfollow": "Deixar de seguir",
|
||||||
"account.unmute": "Não silenciar @{name}",
|
"account.unmute": "Não silenciar @{name}",
|
||||||
"account.view_full_profile": "View full profile",
|
"account.view_full_profile": "Ver perfil completo",
|
||||||
"boost_modal.combo": "Pode clicar {combo} para não voltar a ver",
|
"boost_modal.combo": "Pode clicar {combo} para não voltar a ver",
|
||||||
"bundle_column_error.body": "Something went wrong while loading this component.",
|
"bundle_column_error.body": "Something went wrong while loading this component.",
|
||||||
"bundle_column_error.retry": "Try again",
|
"bundle_column_error.retry": "Tente novamente",
|
||||||
"bundle_column_error.title": "Network error",
|
"bundle_column_error.title": "Network error",
|
||||||
"bundle_modal_error.close": "Close",
|
"bundle_modal_error.close": "Fechar",
|
||||||
"bundle_modal_error.message": "Something went wrong while loading this component.",
|
"bundle_modal_error.message": "Something went wrong while loading this component.",
|
||||||
"bundle_modal_error.retry": "Try again",
|
"bundle_modal_error.retry": "Tente novamente",
|
||||||
"column.blocks": "Utilizadores Bloqueados",
|
"column.blocks": "Usuários bloqueados",
|
||||||
"column.community": "Local",
|
"column.community": "Local",
|
||||||
"column.favourites": "Favoritos",
|
"column.favourites": "Favoritos",
|
||||||
"column.follow_requests": "Seguidores Pendentes",
|
"column.follow_requests": "Seguidores pendentes",
|
||||||
"column.home": "Home",
|
"column.home": "Página inicial",
|
||||||
"column.mutes": "Utilizadores silenciados",
|
"column.mutes": "Usuários silenciados",
|
||||||
"column.notifications": "Notificações",
|
"column.notifications": "Notificações",
|
||||||
"column.public": "Global",
|
"column.public": "Global",
|
||||||
"column_back_button.label": "Voltar",
|
"column_back_button.label": "Voltar",
|
||||||
"column_header.hide_settings": "Hide settings",
|
"column_header.hide_settings": "Esconder configurações",
|
||||||
"column_header.moveLeft_settings": "Move column to the left",
|
"column_header.moveLeft_settings": "Mover coluna para a esquerda",
|
||||||
"column_header.moveRight_settings": "Move column to the right",
|
"column_header.moveRight_settings": "Mover coluna para a direita",
|
||||||
"column_header.pin": "Pin",
|
"column_header.pin": "Fixar",
|
||||||
"column_header.show_settings": "Show settings",
|
"column_header.show_settings": "Mostrar configurações",
|
||||||
"column_header.unpin": "Unpin",
|
"column_header.unpin": "Desafixar",
|
||||||
"column_subheading.navigation": "Navigation",
|
"column_subheading.navigation": "Navegação",
|
||||||
"column_subheading.settings": "Settings",
|
"column_subheading.settings": "Configurações",
|
||||||
"compose_form.lock_disclaimer": "Your account is not {locked}. Anyone can follow you to view your follower-only posts.",
|
"compose_form.lock_disclaimer": "A sua conta não está {locked}. Qualquer pessoa pode te seguir e visualizar as suas postagens só para seguidores.",
|
||||||
"compose_form.lock_disclaimer.lock": "locked",
|
"compose_form.lock_disclaimer.lock": "locked",
|
||||||
"compose_form.placeholder": "Em que estás a pensar?",
|
"compose_form.placeholder": "No que você está pensando?",
|
||||||
"compose_form.privacy_disclaimer": "O teu conteúdo privado vai ser partilhado com os utilizadores do {domains}. Confias {domainsCount, plural, one {neste servidor} other {nestes servidores}}? A privacidade só funciona em instâncias do Mastodon. Se {domains} {domainsCount, plural, one {não é uma instância} other {não são instâncias}}, não existem indicadores da privacidade da tua partilha, e podem ser partilhados com outros.",
|
"compose_form.privacy_disclaimer": "O seu conteúdo privado será compartilhado com os usuários do {domains}. Você confia {domainsCount, plural, one {neste servidor} other {nestes servidores}}? As configurações de privacidade só funcionam em instâncias do Mastodon. Se {domains} {domainsCount, plural, one {não é uma instância} other {não são instâncias}}, não há como garantir a privacidade de suas postagens, e elas podem ser compartilhadas com outros.",
|
||||||
"compose_form.publish": "Publicar",
|
"compose_form.publish": "Publicar",
|
||||||
"compose_form.publish_loud": "{publish}!",
|
"compose_form.publish_loud": "{publish}!",
|
||||||
"compose_form.sensitive": "Marcar media como conteúdo sensível",
|
"compose_form.sensitive": "Marcar mídia como conteúdo sensível",
|
||||||
"compose_form.spoiler": "Esconder texto com aviso",
|
"compose_form.spoiler": "Esconder texto com aviso",
|
||||||
"compose_form.spoiler_placeholder": "Aviso de conteúdo",
|
"compose_form.spoiler_placeholder": "Aviso de conteúdo",
|
||||||
"confirmation_modal.cancel": "Cancel",
|
"confirmation_modal.cancel": "Cancelar",
|
||||||
"confirmations.block.confirm": "Block",
|
"confirmations.block.confirm": "Bloquear",
|
||||||
"confirmations.block.message": "Are you sure you want to block {name}?",
|
"confirmations.block.message": "Você tem certeza de que quer bloquear {name}?",
|
||||||
"confirmations.delete.confirm": "Delete",
|
"confirmations.delete.confirm": "Excluir",
|
||||||
"confirmations.delete.message": "Are you sure you want to delete this status?",
|
"confirmations.delete.message": "Você tem certeza de que quer excluir este status?",
|
||||||
"confirmations.domain_block.confirm": "Hide entire domain",
|
"confirmations.domain_block.confirm": "Esconder o domínio inteiro",
|
||||||
"confirmations.domain_block.message": "Are you really, really sure you want to block the entire {domain}? In most cases a few targeted blocks or mutes are sufficient and preferable.",
|
"confirmations.domain_block.message": "Você quer mesmo bloquear {domain} inteiro? Na maioria dos casos, silenciar ou bloquear alguns usuários é o suficiente e o recomendado.",
|
||||||
"confirmations.mute.confirm": "Mute",
|
"confirmations.mute.confirm": "Silenciar",
|
||||||
"confirmations.mute.message": "Are you sure you want to mute {name}?",
|
"confirmations.mute.message": "Você tem certeza de que quer silenciar {name}?",
|
||||||
"confirmations.unfollow.confirm": "Unfollow",
|
"confirmations.unfollow.confirm": "Deixar de seguir",
|
||||||
"confirmations.unfollow.message": "Are you sure you want to unfollow {name}?",
|
"confirmations.unfollow.message": "Você tem certeza de que quer deixar de seguir {name}?",
|
||||||
|
"embed.instructions": "Embed this status on your website by copying the code below.",
|
||||||
|
"embed.preview": "Here is what it will look like:",
|
||||||
"emoji_button.activity": "Activity",
|
"emoji_button.activity": "Activity",
|
||||||
"emoji_button.flags": "Flags",
|
"emoji_button.flags": "Flags",
|
||||||
"emoji_button.food": "Food & Drink",
|
"emoji_button.food": "Food & Drink",
|
||||||
|
@ -162,12 +164,14 @@
|
||||||
"standalone.public_title": "A look inside...",
|
"standalone.public_title": "A look inside...",
|
||||||
"status.cannot_reblog": "This post cannot be boosted",
|
"status.cannot_reblog": "This post cannot be boosted",
|
||||||
"status.delete": "Eliminar",
|
"status.delete": "Eliminar",
|
||||||
|
"status.embed": "Embed",
|
||||||
"status.favourite": "Adicionar aos favoritos",
|
"status.favourite": "Adicionar aos favoritos",
|
||||||
"status.load_more": "Carregar mais",
|
"status.load_more": "Carregar mais",
|
||||||
"status.media_hidden": "Media escondida",
|
"status.media_hidden": "Media escondida",
|
||||||
"status.mention": "Mencionar @{name}",
|
"status.mention": "Mencionar @{name}",
|
||||||
"status.mute_conversation": "Mute conversation",
|
"status.mute_conversation": "Mute conversation",
|
||||||
"status.open": "Expandir",
|
"status.open": "Expandir",
|
||||||
|
"status.pin": "Pin on profile",
|
||||||
"status.reblog": "Partilhar",
|
"status.reblog": "Partilhar",
|
||||||
"status.reblogged_by": "{name} partilhou",
|
"status.reblogged_by": "{name} partilhou",
|
||||||
"status.reply": "Responder",
|
"status.reply": "Responder",
|
||||||
|
@ -179,6 +183,7 @@
|
||||||
"status.show_less": "Mostrar menos",
|
"status.show_less": "Mostrar menos",
|
||||||
"status.show_more": "Mostrar mais",
|
"status.show_more": "Mostrar mais",
|
||||||
"status.unmute_conversation": "Unmute conversation",
|
"status.unmute_conversation": "Unmute conversation",
|
||||||
|
"status.unpin": "Unpin from profile",
|
||||||
"tabs_bar.compose": "Criar",
|
"tabs_bar.compose": "Criar",
|
||||||
"tabs_bar.federated_timeline": "Global",
|
"tabs_bar.federated_timeline": "Global",
|
||||||
"tabs_bar.home": "Home",
|
"tabs_bar.home": "Home",
|
||||||
|
|
|
@ -63,6 +63,8 @@
|
||||||
"confirmations.mute.message": "Are you sure you want to mute {name}?",
|
"confirmations.mute.message": "Are you sure you want to mute {name}?",
|
||||||
"confirmations.unfollow.confirm": "Unfollow",
|
"confirmations.unfollow.confirm": "Unfollow",
|
||||||
"confirmations.unfollow.message": "Are you sure you want to unfollow {name}?",
|
"confirmations.unfollow.message": "Are you sure you want to unfollow {name}?",
|
||||||
|
"embed.instructions": "Embed this status on your website by copying the code below.",
|
||||||
|
"embed.preview": "Here is what it will look like:",
|
||||||
"emoji_button.activity": "Activity",
|
"emoji_button.activity": "Activity",
|
||||||
"emoji_button.flags": "Flags",
|
"emoji_button.flags": "Flags",
|
||||||
"emoji_button.food": "Food & Drink",
|
"emoji_button.food": "Food & Drink",
|
||||||
|
@ -162,12 +164,14 @@
|
||||||
"standalone.public_title": "A look inside...",
|
"standalone.public_title": "A look inside...",
|
||||||
"status.cannot_reblog": "This post cannot be boosted",
|
"status.cannot_reblog": "This post cannot be boosted",
|
||||||
"status.delete": "Eliminar",
|
"status.delete": "Eliminar",
|
||||||
|
"status.embed": "Embed",
|
||||||
"status.favourite": "Adicionar aos favoritos",
|
"status.favourite": "Adicionar aos favoritos",
|
||||||
"status.load_more": "Carregar mais",
|
"status.load_more": "Carregar mais",
|
||||||
"status.media_hidden": "Media escondida",
|
"status.media_hidden": "Media escondida",
|
||||||
"status.mention": "Mencionar @{name}",
|
"status.mention": "Mencionar @{name}",
|
||||||
"status.mute_conversation": "Mute conversation",
|
"status.mute_conversation": "Mute conversation",
|
||||||
"status.open": "Expandir",
|
"status.open": "Expandir",
|
||||||
|
"status.pin": "Pin on profile",
|
||||||
"status.reblog": "Partilhar",
|
"status.reblog": "Partilhar",
|
||||||
"status.reblogged_by": "{name} partilhou",
|
"status.reblogged_by": "{name} partilhou",
|
||||||
"status.reply": "Responder",
|
"status.reply": "Responder",
|
||||||
|
@ -179,6 +183,7 @@
|
||||||
"status.show_less": "Mostrar menos",
|
"status.show_less": "Mostrar menos",
|
||||||
"status.show_more": "Mostrar mais",
|
"status.show_more": "Mostrar mais",
|
||||||
"status.unmute_conversation": "Unmute conversation",
|
"status.unmute_conversation": "Unmute conversation",
|
||||||
|
"status.unpin": "Unpin from profile",
|
||||||
"tabs_bar.compose": "Criar",
|
"tabs_bar.compose": "Criar",
|
||||||
"tabs_bar.federated_timeline": "Global",
|
"tabs_bar.federated_timeline": "Global",
|
||||||
"tabs_bar.home": "Home",
|
"tabs_bar.home": "Home",
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
{
|
{
|
||||||
"account.block": "Блокировать",
|
"account.block": "Блокировать",
|
||||||
"account.block_domain": "Блокировать все с {domain}",
|
"account.block_domain": "Блокировать все с {domain}",
|
||||||
"account.disclaimer_full": "Information below may reflect the user's profile incompletely.",
|
"account.disclaimer_full": "Нижеуказанная информация может не полностью отражать профиль пользователя.",
|
||||||
"account.edit_profile": "Изменить профиль",
|
"account.edit_profile": "Изменить профиль",
|
||||||
"account.follow": "Подписаться",
|
"account.follow": "Подписаться",
|
||||||
"account.followers": "Подписаны",
|
"account.followers": "Подписаны",
|
||||||
|
@ -13,19 +13,19 @@
|
||||||
"account.posts": "Посты",
|
"account.posts": "Посты",
|
||||||
"account.report": "Пожаловаться",
|
"account.report": "Пожаловаться",
|
||||||
"account.requested": "Ожидает подтверждения",
|
"account.requested": "Ожидает подтверждения",
|
||||||
"account.share": "Share @{name}'s profile",
|
"account.share": "Поделиться профилем @{name}",
|
||||||
"account.unblock": "Разблокировать",
|
"account.unblock": "Разблокировать",
|
||||||
"account.unblock_domain": "Разблокировать {domain}",
|
"account.unblock_domain": "Разблокировать {domain}",
|
||||||
"account.unfollow": "Отписаться",
|
"account.unfollow": "Отписаться",
|
||||||
"account.unmute": "Снять глушение",
|
"account.unmute": "Снять глушение",
|
||||||
"account.view_full_profile": "View full profile",
|
"account.view_full_profile": "Показать полный профиль",
|
||||||
"boost_modal.combo": "Нажмите {combo}, чтобы пропустить это в следующий раз",
|
"boost_modal.combo": "Нажмите {combo}, чтобы пропустить это в следующий раз",
|
||||||
"bundle_column_error.body": "Something went wrong while loading this component.",
|
"bundle_column_error.body": "Что-то пошло не так при загрузке этого компонента.",
|
||||||
"bundle_column_error.retry": "Try again",
|
"bundle_column_error.retry": "Попробовать снова",
|
||||||
"bundle_column_error.title": "Network error",
|
"bundle_column_error.title": "Ошибка сети",
|
||||||
"bundle_modal_error.close": "Close",
|
"bundle_modal_error.close": "Закрыть",
|
||||||
"bundle_modal_error.message": "Something went wrong while loading this component.",
|
"bundle_modal_error.message": "Что-то пошло не так при загрузке этого компонента.",
|
||||||
"bundle_modal_error.retry": "Try again",
|
"bundle_modal_error.retry": "Попробовать снова",
|
||||||
"column.blocks": "Список блокировки",
|
"column.blocks": "Список блокировки",
|
||||||
"column.community": "Локальная лента",
|
"column.community": "Локальная лента",
|
||||||
"column.favourites": "Понравившееся",
|
"column.favourites": "Понравившееся",
|
||||||
|
@ -35,11 +35,11 @@
|
||||||
"column.notifications": "Уведомления",
|
"column.notifications": "Уведомления",
|
||||||
"column.public": "Глобальная лента",
|
"column.public": "Глобальная лента",
|
||||||
"column_back_button.label": "Назад",
|
"column_back_button.label": "Назад",
|
||||||
"column_header.hide_settings": "Hide settings",
|
"column_header.hide_settings": "Скрыть настройки",
|
||||||
"column_header.moveLeft_settings": "Move column to the left",
|
"column_header.moveLeft_settings": "Передвинуть колонку влево",
|
||||||
"column_header.moveRight_settings": "Move column to the right",
|
"column_header.moveRight_settings": "Передвинуть колонку вправо",
|
||||||
"column_header.pin": "Закрепить",
|
"column_header.pin": "Закрепить",
|
||||||
"column_header.show_settings": "Show settings",
|
"column_header.show_settings": "Показать настройки",
|
||||||
"column_header.unpin": "Открепить",
|
"column_header.unpin": "Открепить",
|
||||||
"column_subheading.navigation": "Навигация",
|
"column_subheading.navigation": "Навигация",
|
||||||
"column_subheading.settings": "Настройки",
|
"column_subheading.settings": "Настройки",
|
||||||
|
@ -61,8 +61,10 @@
|
||||||
"confirmations.domain_block.message": "Вы на самом деле уверены, что хотите блокировать весь {domain}? В большинстве случаев нескольких отдельных блокировок или глушений достаточно.",
|
"confirmations.domain_block.message": "Вы на самом деле уверены, что хотите блокировать весь {domain}? В большинстве случаев нескольких отдельных блокировок или глушений достаточно.",
|
||||||
"confirmations.mute.confirm": "Заглушить",
|
"confirmations.mute.confirm": "Заглушить",
|
||||||
"confirmations.mute.message": "Вы уверены, что хотите заглушить {name}?",
|
"confirmations.mute.message": "Вы уверены, что хотите заглушить {name}?",
|
||||||
"confirmations.unfollow.confirm": "Unfollow",
|
"confirmations.unfollow.confirm": "Отписаться",
|
||||||
"confirmations.unfollow.message": "Are you sure you want to unfollow {name}?",
|
"confirmations.unfollow.message": "Вы уверены, что хотите отписаться от {name}?",
|
||||||
|
"embed.instructions": "Embed this status on your website by copying the code below.",
|
||||||
|
"embed.preview": "Here is what it will look like:",
|
||||||
"emoji_button.activity": "Занятия",
|
"emoji_button.activity": "Занятия",
|
||||||
"emoji_button.flags": "Флаги",
|
"emoji_button.flags": "Флаги",
|
||||||
"emoji_button.food": "Еда и напитки",
|
"emoji_button.food": "Еда и напитки",
|
||||||
|
@ -94,8 +96,8 @@
|
||||||
"home.column_settings.show_replies": "Показывать ответы",
|
"home.column_settings.show_replies": "Показывать ответы",
|
||||||
"home.settings": "Настройки колонки",
|
"home.settings": "Настройки колонки",
|
||||||
"lightbox.close": "Закрыть",
|
"lightbox.close": "Закрыть",
|
||||||
"lightbox.next": "Next",
|
"lightbox.next": "Далее",
|
||||||
"lightbox.previous": "Previous",
|
"lightbox.previous": "Назад",
|
||||||
"loading_indicator.label": "Загрузка...",
|
"loading_indicator.label": "Загрузка...",
|
||||||
"media_gallery.toggle_visible": "Показать/скрыть",
|
"media_gallery.toggle_visible": "Показать/скрыть",
|
||||||
"missing_indicator.label": "Не найдено",
|
"missing_indicator.label": "Не найдено",
|
||||||
|
@ -119,8 +121,8 @@
|
||||||
"notifications.column_settings.favourite": "Нравится:",
|
"notifications.column_settings.favourite": "Нравится:",
|
||||||
"notifications.column_settings.follow": "Новые подписчики:",
|
"notifications.column_settings.follow": "Новые подписчики:",
|
||||||
"notifications.column_settings.mention": "Упоминания:",
|
"notifications.column_settings.mention": "Упоминания:",
|
||||||
"notifications.column_settings.push": "Push notifications",
|
"notifications.column_settings.push": "Push-уведомления",
|
||||||
"notifications.column_settings.push_meta": "This device",
|
"notifications.column_settings.push_meta": "Это устройство",
|
||||||
"notifications.column_settings.reblog": "Продвижения:",
|
"notifications.column_settings.reblog": "Продвижения:",
|
||||||
"notifications.column_settings.show": "Показывать в колонке",
|
"notifications.column_settings.show": "Показывать в колонке",
|
||||||
"notifications.column_settings.sound": "Проигрывать звук",
|
"notifications.column_settings.sound": "Проигрывать звук",
|
||||||
|
@ -162,12 +164,14 @@
|
||||||
"standalone.public_title": "A look inside...",
|
"standalone.public_title": "A look inside...",
|
||||||
"status.cannot_reblog": "Этот статус не может быть продвинут",
|
"status.cannot_reblog": "Этот статус не может быть продвинут",
|
||||||
"status.delete": "Удалить",
|
"status.delete": "Удалить",
|
||||||
|
"status.embed": "Embed",
|
||||||
"status.favourite": "Нравится",
|
"status.favourite": "Нравится",
|
||||||
"status.load_more": "Показать еще",
|
"status.load_more": "Показать еще",
|
||||||
"status.media_hidden": "Медиаконтент скрыт",
|
"status.media_hidden": "Медиаконтент скрыт",
|
||||||
"status.mention": "Упомянуть @{name}",
|
"status.mention": "Упомянуть @{name}",
|
||||||
"status.mute_conversation": "Заглушить тред",
|
"status.mute_conversation": "Заглушить тред",
|
||||||
"status.open": "Развернуть статус",
|
"status.open": "Развернуть статус",
|
||||||
|
"status.pin": "Pin on profile",
|
||||||
"status.reblog": "Продвинуть",
|
"status.reblog": "Продвинуть",
|
||||||
"status.reblogged_by": "{name} продвинул(а)",
|
"status.reblogged_by": "{name} продвинул(а)",
|
||||||
"status.reply": "Ответить",
|
"status.reply": "Ответить",
|
||||||
|
@ -179,6 +183,7 @@
|
||||||
"status.show_less": "Свернуть",
|
"status.show_less": "Свернуть",
|
||||||
"status.show_more": "Развернуть",
|
"status.show_more": "Развернуть",
|
||||||
"status.unmute_conversation": "Снять глушение с треда",
|
"status.unmute_conversation": "Снять глушение с треда",
|
||||||
|
"status.unpin": "Unpin from profile",
|
||||||
"tabs_bar.compose": "Написать",
|
"tabs_bar.compose": "Написать",
|
||||||
"tabs_bar.federated_timeline": "Глобальная",
|
"tabs_bar.federated_timeline": "Глобальная",
|
||||||
"tabs_bar.home": "Главная",
|
"tabs_bar.home": "Главная",
|
||||||
|
|
|
@ -63,6 +63,8 @@
|
||||||
"confirmations.mute.message": "Are you sure you want to mute {name}?",
|
"confirmations.mute.message": "Are you sure you want to mute {name}?",
|
||||||
"confirmations.unfollow.confirm": "Unfollow",
|
"confirmations.unfollow.confirm": "Unfollow",
|
||||||
"confirmations.unfollow.message": "Are you sure you want to unfollow {name}?",
|
"confirmations.unfollow.message": "Are you sure you want to unfollow {name}?",
|
||||||
|
"embed.instructions": "Embed this status on your website by copying the code below.",
|
||||||
|
"embed.preview": "Here is what it will look like:",
|
||||||
"emoji_button.activity": "Activity",
|
"emoji_button.activity": "Activity",
|
||||||
"emoji_button.flags": "Flags",
|
"emoji_button.flags": "Flags",
|
||||||
"emoji_button.food": "Food & Drink",
|
"emoji_button.food": "Food & Drink",
|
||||||
|
@ -162,12 +164,14 @@
|
||||||
"standalone.public_title": "A look inside...",
|
"standalone.public_title": "A look inside...",
|
||||||
"status.cannot_reblog": "This post cannot be boosted",
|
"status.cannot_reblog": "This post cannot be boosted",
|
||||||
"status.delete": "Delete",
|
"status.delete": "Delete",
|
||||||
|
"status.embed": "Embed",
|
||||||
"status.favourite": "Favourite",
|
"status.favourite": "Favourite",
|
||||||
"status.load_more": "Load more",
|
"status.load_more": "Load more",
|
||||||
"status.media_hidden": "Media hidden",
|
"status.media_hidden": "Media hidden",
|
||||||
"status.mention": "Mention @{name}",
|
"status.mention": "Mention @{name}",
|
||||||
"status.mute_conversation": "Mute conversation",
|
"status.mute_conversation": "Mute conversation",
|
||||||
"status.open": "Expand this status",
|
"status.open": "Expand this status",
|
||||||
|
"status.pin": "Pin on profile",
|
||||||
"status.reblog": "Boost",
|
"status.reblog": "Boost",
|
||||||
"status.reblogged_by": "{name} boosted",
|
"status.reblogged_by": "{name} boosted",
|
||||||
"status.reply": "Reply",
|
"status.reply": "Reply",
|
||||||
|
@ -179,6 +183,7 @@
|
||||||
"status.show_less": "Show less",
|
"status.show_less": "Show less",
|
||||||
"status.show_more": "Show more",
|
"status.show_more": "Show more",
|
||||||
"status.unmute_conversation": "Unmute conversation",
|
"status.unmute_conversation": "Unmute conversation",
|
||||||
|
"status.unpin": "Unpin from profile",
|
||||||
"tabs_bar.compose": "Compose",
|
"tabs_bar.compose": "Compose",
|
||||||
"tabs_bar.federated_timeline": "Federated",
|
"tabs_bar.federated_timeline": "Federated",
|
||||||
"tabs_bar.home": "Home",
|
"tabs_bar.home": "Home",
|
||||||
|
|
|
@ -63,6 +63,8 @@
|
||||||
"confirmations.mute.message": "{name} kullanıcısını sessize almak istiyor musunuz?",
|
"confirmations.mute.message": "{name} kullanıcısını sessize almak istiyor musunuz?",
|
||||||
"confirmations.unfollow.confirm": "Unfollow",
|
"confirmations.unfollow.confirm": "Unfollow",
|
||||||
"confirmations.unfollow.message": "Are you sure you want to unfollow {name}?",
|
"confirmations.unfollow.message": "Are you sure you want to unfollow {name}?",
|
||||||
|
"embed.instructions": "Embed this status on your website by copying the code below.",
|
||||||
|
"embed.preview": "Here is what it will look like:",
|
||||||
"emoji_button.activity": "Aktivite",
|
"emoji_button.activity": "Aktivite",
|
||||||
"emoji_button.flags": "Bayraklar",
|
"emoji_button.flags": "Bayraklar",
|
||||||
"emoji_button.food": "Yiyecek ve İçecek",
|
"emoji_button.food": "Yiyecek ve İçecek",
|
||||||
|
@ -162,12 +164,14 @@
|
||||||
"standalone.public_title": "A look inside...",
|
"standalone.public_title": "A look inside...",
|
||||||
"status.cannot_reblog": "Bu gönderi boost edilemez",
|
"status.cannot_reblog": "Bu gönderi boost edilemez",
|
||||||
"status.delete": "Sil",
|
"status.delete": "Sil",
|
||||||
|
"status.embed": "Embed",
|
||||||
"status.favourite": "Favorilere ekle",
|
"status.favourite": "Favorilere ekle",
|
||||||
"status.load_more": "Daha fazla",
|
"status.load_more": "Daha fazla",
|
||||||
"status.media_hidden": "Gizli görsel",
|
"status.media_hidden": "Gizli görsel",
|
||||||
"status.mention": "Bahset @{name}",
|
"status.mention": "Bahset @{name}",
|
||||||
"status.mute_conversation": "Mute conversation",
|
"status.mute_conversation": "Mute conversation",
|
||||||
"status.open": "Bu gönderiyi genişlet",
|
"status.open": "Bu gönderiyi genişlet",
|
||||||
|
"status.pin": "Pin on profile",
|
||||||
"status.reblog": "Boost'la",
|
"status.reblog": "Boost'la",
|
||||||
"status.reblogged_by": "{name} boost etti",
|
"status.reblogged_by": "{name} boost etti",
|
||||||
"status.reply": "Cevapla",
|
"status.reply": "Cevapla",
|
||||||
|
@ -179,6 +183,7 @@
|
||||||
"status.show_less": "Daha azı",
|
"status.show_less": "Daha azı",
|
||||||
"status.show_more": "Daha fazlası",
|
"status.show_more": "Daha fazlası",
|
||||||
"status.unmute_conversation": "Unmute conversation",
|
"status.unmute_conversation": "Unmute conversation",
|
||||||
|
"status.unpin": "Unpin from profile",
|
||||||
"tabs_bar.compose": "Oluştur",
|
"tabs_bar.compose": "Oluştur",
|
||||||
"tabs_bar.federated_timeline": "Federe",
|
"tabs_bar.federated_timeline": "Federe",
|
||||||
"tabs_bar.home": "Ana sayfa",
|
"tabs_bar.home": "Ana sayfa",
|
||||||
|
|
|
@ -63,6 +63,8 @@
|
||||||
"confirmations.mute.message": "Ви впевнені, що хочете заглушити {name}?",
|
"confirmations.mute.message": "Ви впевнені, що хочете заглушити {name}?",
|
||||||
"confirmations.unfollow.confirm": "Unfollow",
|
"confirmations.unfollow.confirm": "Unfollow",
|
||||||
"confirmations.unfollow.message": "Are you sure you want to unfollow {name}?",
|
"confirmations.unfollow.message": "Are you sure you want to unfollow {name}?",
|
||||||
|
"embed.instructions": "Embed this status on your website by copying the code below.",
|
||||||
|
"embed.preview": "Here is what it will look like:",
|
||||||
"emoji_button.activity": "Заняття",
|
"emoji_button.activity": "Заняття",
|
||||||
"emoji_button.flags": "Прапори",
|
"emoji_button.flags": "Прапори",
|
||||||
"emoji_button.food": "Їжа та напої",
|
"emoji_button.food": "Їжа та напої",
|
||||||
|
@ -162,12 +164,14 @@
|
||||||
"standalone.public_title": "A look inside...",
|
"standalone.public_title": "A look inside...",
|
||||||
"status.cannot_reblog": "Цей допис не може бути передмухнутий",
|
"status.cannot_reblog": "Цей допис не може бути передмухнутий",
|
||||||
"status.delete": "Видалити",
|
"status.delete": "Видалити",
|
||||||
|
"status.embed": "Embed",
|
||||||
"status.favourite": "Подобається",
|
"status.favourite": "Подобається",
|
||||||
"status.load_more": "Завантажити більше",
|
"status.load_more": "Завантажити більше",
|
||||||
"status.media_hidden": "Медіаконтент приховано",
|
"status.media_hidden": "Медіаконтент приховано",
|
||||||
"status.mention": "Згадати",
|
"status.mention": "Згадати",
|
||||||
"status.mute_conversation": "Заглушити діалог",
|
"status.mute_conversation": "Заглушити діалог",
|
||||||
"status.open": "Розгорнути допис",
|
"status.open": "Розгорнути допис",
|
||||||
|
"status.pin": "Pin on profile",
|
||||||
"status.reblog": "Передмухнути",
|
"status.reblog": "Передмухнути",
|
||||||
"status.reblogged_by": "{name} передмухнув(-ла)",
|
"status.reblogged_by": "{name} передмухнув(-ла)",
|
||||||
"status.reply": "Відповісти",
|
"status.reply": "Відповісти",
|
||||||
|
@ -179,6 +183,7 @@
|
||||||
"status.show_less": "Згорнути",
|
"status.show_less": "Згорнути",
|
||||||
"status.show_more": "Розгорнути",
|
"status.show_more": "Розгорнути",
|
||||||
"status.unmute_conversation": "Зняти глушення з діалогу",
|
"status.unmute_conversation": "Зняти глушення з діалогу",
|
||||||
|
"status.unpin": "Unpin from profile",
|
||||||
"tabs_bar.compose": "Написати",
|
"tabs_bar.compose": "Написати",
|
||||||
"tabs_bar.federated_timeline": "Глобальна",
|
"tabs_bar.federated_timeline": "Глобальна",
|
||||||
"tabs_bar.home": "Головна",
|
"tabs_bar.home": "Головна",
|
||||||
|
|
|
@ -5,7 +5,7 @@
|
||||||
"account.edit_profile": "修改个人资料",
|
"account.edit_profile": "修改个人资料",
|
||||||
"account.follow": "关注",
|
"account.follow": "关注",
|
||||||
"account.followers": "关注者",
|
"account.followers": "关注者",
|
||||||
"account.follows": "正关注",
|
"account.follows": "正在关注",
|
||||||
"account.follows_you": "关注你",
|
"account.follows_you": "关注你",
|
||||||
"account.media": "Media",
|
"account.media": "Media",
|
||||||
"account.mention": "提及 @{name}",
|
"account.mention": "提及 @{name}",
|
||||||
|
@ -13,19 +13,19 @@
|
||||||
"account.posts": "嘟文",
|
"account.posts": "嘟文",
|
||||||
"account.report": "举报 @{name}",
|
"account.report": "举报 @{name}",
|
||||||
"account.requested": "等待审批",
|
"account.requested": "等待审批",
|
||||||
"account.share": "Share @{name}'s profile",
|
"account.share": "分享 @{name}的个人资料",
|
||||||
"account.unblock": "解除对 @{name} 的屏蔽",
|
"account.unblock": "解除对 @{name} 的屏蔽",
|
||||||
"account.unblock_domain": "Unhide {domain}",
|
"account.unblock_domain": "解除封锁 {domain}",
|
||||||
"account.unfollow": "取消关注",
|
"account.unfollow": "取消关注",
|
||||||
"account.unmute": "取消 @{name} 的静音",
|
"account.unmute": "取消 @{name} 的静音",
|
||||||
"account.view_full_profile": "View full profile",
|
"account.view_full_profile": "查看完整资料",
|
||||||
"boost_modal.combo": "如你想在下次路过时显示,请按{combo},",
|
"boost_modal.combo": "如你想在下次路过时显示,请按{combo},",
|
||||||
"bundle_column_error.body": "Something went wrong while loading this component.",
|
"bundle_column_error.body": "载入组件出错。",
|
||||||
"bundle_column_error.retry": "Try again",
|
"bundle_column_error.retry": "再次尝试",
|
||||||
"bundle_column_error.title": "Network error",
|
"bundle_column_error.title": "网络错误",
|
||||||
"bundle_modal_error.close": "Close",
|
"bundle_modal_error.close": "关闭",
|
||||||
"bundle_modal_error.message": "Something went wrong while loading this component.",
|
"bundle_modal_error.message": "载入组件出错。",
|
||||||
"bundle_modal_error.retry": "Try again",
|
"bundle_modal_error.retry": "再次尝试",
|
||||||
"column.blocks": "屏蔽用户",
|
"column.blocks": "屏蔽用户",
|
||||||
"column.community": "本站时间轴",
|
"column.community": "本站时间轴",
|
||||||
"column.favourites": "赞过的嘟文",
|
"column.favourites": "赞过的嘟文",
|
||||||
|
@ -34,7 +34,7 @@
|
||||||
"column.mutes": "被静音的用户",
|
"column.mutes": "被静音的用户",
|
||||||
"column.notifications": "通知",
|
"column.notifications": "通知",
|
||||||
"column.public": "跨站公共时间轴",
|
"column.public": "跨站公共时间轴",
|
||||||
"column_back_button.label": "Back",
|
"column_back_button.label": "返回",
|
||||||
"column_header.hide_settings": "Hide settings",
|
"column_header.hide_settings": "Hide settings",
|
||||||
"column_header.moveLeft_settings": "Move column to the left",
|
"column_header.moveLeft_settings": "Move column to the left",
|
||||||
"column_header.moveRight_settings": "Move column to the right",
|
"column_header.moveRight_settings": "Move column to the right",
|
||||||
|
@ -61,8 +61,10 @@
|
||||||
"confirmations.domain_block.message": "Are you really, really sure you want to block the entire {domain}? In most cases a few targeted blocks or mutes are sufficient and preferable.",
|
"confirmations.domain_block.message": "Are you really, really sure you want to block the entire {domain}? In most cases a few targeted blocks or mutes are sufficient and preferable.",
|
||||||
"confirmations.mute.confirm": "静音",
|
"confirmations.mute.confirm": "静音",
|
||||||
"confirmations.mute.message": "想好了,真的要静音 {name}?",
|
"confirmations.mute.message": "想好了,真的要静音 {name}?",
|
||||||
"confirmations.unfollow.confirm": "Unfollow",
|
"confirmations.unfollow.confirm": "取消关注",
|
||||||
"confirmations.unfollow.message": "Are you sure you want to unfollow {name}?",
|
"confirmations.unfollow.message": "确定要取消关注 {name}吗?",
|
||||||
|
"embed.instructions": "Embed this status on your website by copying the code below.",
|
||||||
|
"embed.preview": "Here is what it will look like:",
|
||||||
"emoji_button.activity": "活动",
|
"emoji_button.activity": "活动",
|
||||||
"emoji_button.flags": "旗帜",
|
"emoji_button.flags": "旗帜",
|
||||||
"emoji_button.food": "食物和饮料",
|
"emoji_button.food": "食物和饮料",
|
||||||
|
@ -86,7 +88,7 @@
|
||||||
"getting_started.faq": "FAQ",
|
"getting_started.faq": "FAQ",
|
||||||
"getting_started.heading": "开始使用",
|
"getting_started.heading": "开始使用",
|
||||||
"getting_started.open_source_notice": "Mastodon 是一个开放源码的软件。你可以在官方 GitHub ({github}) 贡献或者回报问题。",
|
"getting_started.open_source_notice": "Mastodon 是一个开放源码的软件。你可以在官方 GitHub ({github}) 贡献或者回报问题。",
|
||||||
"getting_started.userguide": "User Guide",
|
"getting_started.userguide": "用户指南",
|
||||||
"home.column_settings.advanced": "高端",
|
"home.column_settings.advanced": "高端",
|
||||||
"home.column_settings.basic": "基本",
|
"home.column_settings.basic": "基本",
|
||||||
"home.column_settings.filter_regex": "使用正则表达式 (regex) 过滤",
|
"home.column_settings.filter_regex": "使用正则表达式 (regex) 过滤",
|
||||||
|
@ -162,12 +164,14 @@
|
||||||
"standalone.public_title": "A look inside...",
|
"standalone.public_title": "A look inside...",
|
||||||
"status.cannot_reblog": "没法转嘟这条嘟文啦……",
|
"status.cannot_reblog": "没法转嘟这条嘟文啦……",
|
||||||
"status.delete": "删除",
|
"status.delete": "删除",
|
||||||
|
"status.embed": "Embed",
|
||||||
"status.favourite": "赞",
|
"status.favourite": "赞",
|
||||||
"status.load_more": "加载更多",
|
"status.load_more": "加载更多",
|
||||||
"status.media_hidden": "隐藏媒体内容",
|
"status.media_hidden": "隐藏媒体内容",
|
||||||
"status.mention": "提及 @{name}",
|
"status.mention": "提及 @{name}",
|
||||||
"status.mute_conversation": "Mute conversation",
|
"status.mute_conversation": "Mute conversation",
|
||||||
"status.open": "展开嘟文",
|
"status.open": "展开嘟文",
|
||||||
|
"status.pin": "Pin on profile",
|
||||||
"status.reblog": "转嘟",
|
"status.reblog": "转嘟",
|
||||||
"status.reblogged_by": "{name} 转嘟",
|
"status.reblogged_by": "{name} 转嘟",
|
||||||
"status.reply": "回应",
|
"status.reply": "回应",
|
||||||
|
@ -179,6 +183,7 @@
|
||||||
"status.show_less": "减少显示",
|
"status.show_less": "减少显示",
|
||||||
"status.show_more": "显示更多",
|
"status.show_more": "显示更多",
|
||||||
"status.unmute_conversation": "Unmute conversation",
|
"status.unmute_conversation": "Unmute conversation",
|
||||||
|
"status.unpin": "Unpin from profile",
|
||||||
"tabs_bar.compose": "撰写",
|
"tabs_bar.compose": "撰写",
|
||||||
"tabs_bar.federated_timeline": "跨站",
|
"tabs_bar.federated_timeline": "跨站",
|
||||||
"tabs_bar.home": "主页",
|
"tabs_bar.home": "主页",
|
||||||
|
|
|
@ -63,6 +63,8 @@
|
||||||
"confirmations.mute.message": "你確定要將{name}靜音嗎?",
|
"confirmations.mute.message": "你確定要將{name}靜音嗎?",
|
||||||
"confirmations.unfollow.confirm": "Unfollow",
|
"confirmations.unfollow.confirm": "Unfollow",
|
||||||
"confirmations.unfollow.message": "Are you sure you want to unfollow {name}?",
|
"confirmations.unfollow.message": "Are you sure you want to unfollow {name}?",
|
||||||
|
"embed.instructions": "Embed this status on your website by copying the code below.",
|
||||||
|
"embed.preview": "Here is what it will look like:",
|
||||||
"emoji_button.activity": "活動",
|
"emoji_button.activity": "活動",
|
||||||
"emoji_button.flags": "旗幟",
|
"emoji_button.flags": "旗幟",
|
||||||
"emoji_button.food": "飲飲食食",
|
"emoji_button.food": "飲飲食食",
|
||||||
|
@ -162,12 +164,14 @@
|
||||||
"standalone.public_title": "A look inside...",
|
"standalone.public_title": "A look inside...",
|
||||||
"status.cannot_reblog": "這篇文章無法被轉推",
|
"status.cannot_reblog": "這篇文章無法被轉推",
|
||||||
"status.delete": "刪除",
|
"status.delete": "刪除",
|
||||||
|
"status.embed": "Embed",
|
||||||
"status.favourite": "喜歡",
|
"status.favourite": "喜歡",
|
||||||
"status.load_more": "載入更多",
|
"status.load_more": "載入更多",
|
||||||
"status.media_hidden": "隱藏媒體內容",
|
"status.media_hidden": "隱藏媒體內容",
|
||||||
"status.mention": "提及 @{name}",
|
"status.mention": "提及 @{name}",
|
||||||
"status.mute_conversation": "Mute conversation",
|
"status.mute_conversation": "Mute conversation",
|
||||||
"status.open": "展開文章",
|
"status.open": "展開文章",
|
||||||
|
"status.pin": "Pin on profile",
|
||||||
"status.reblog": "轉推",
|
"status.reblog": "轉推",
|
||||||
"status.reblogged_by": "{name} 轉推",
|
"status.reblogged_by": "{name} 轉推",
|
||||||
"status.reply": "回應",
|
"status.reply": "回應",
|
||||||
|
@ -179,6 +183,7 @@
|
||||||
"status.show_less": "減少顯示",
|
"status.show_less": "減少顯示",
|
||||||
"status.show_more": "顯示更多",
|
"status.show_more": "顯示更多",
|
||||||
"status.unmute_conversation": "Unmute conversation",
|
"status.unmute_conversation": "Unmute conversation",
|
||||||
|
"status.unpin": "Unpin from profile",
|
||||||
"tabs_bar.compose": "撰寫",
|
"tabs_bar.compose": "撰寫",
|
||||||
"tabs_bar.federated_timeline": "跨站",
|
"tabs_bar.federated_timeline": "跨站",
|
||||||
"tabs_bar.home": "主頁",
|
"tabs_bar.home": "主頁",
|
||||||
|
|
|
@ -63,6 +63,8 @@
|
||||||
"confirmations.mute.message": "你確定要消音 {name} ?",
|
"confirmations.mute.message": "你確定要消音 {name} ?",
|
||||||
"confirmations.unfollow.confirm": "Unfollow",
|
"confirmations.unfollow.confirm": "Unfollow",
|
||||||
"confirmations.unfollow.message": "Are you sure you want to unfollow {name}?",
|
"confirmations.unfollow.message": "Are you sure you want to unfollow {name}?",
|
||||||
|
"embed.instructions": "Embed this status on your website by copying the code below.",
|
||||||
|
"embed.preview": "Here is what it will look like:",
|
||||||
"emoji_button.activity": "活動",
|
"emoji_button.activity": "活動",
|
||||||
"emoji_button.flags": "旗幟",
|
"emoji_button.flags": "旗幟",
|
||||||
"emoji_button.food": "食物與飲料",
|
"emoji_button.food": "食物與飲料",
|
||||||
|
@ -162,12 +164,14 @@
|
||||||
"standalone.public_title": "A look inside...",
|
"standalone.public_title": "A look inside...",
|
||||||
"status.cannot_reblog": "此貼文無法轉推",
|
"status.cannot_reblog": "此貼文無法轉推",
|
||||||
"status.delete": "刪除",
|
"status.delete": "刪除",
|
||||||
|
"status.embed": "Embed",
|
||||||
"status.favourite": "喜愛",
|
"status.favourite": "喜愛",
|
||||||
"status.load_more": "載入更多",
|
"status.load_more": "載入更多",
|
||||||
"status.media_hidden": "媒體已隱藏",
|
"status.media_hidden": "媒體已隱藏",
|
||||||
"status.mention": "提到 @{name}",
|
"status.mention": "提到 @{name}",
|
||||||
"status.mute_conversation": "消音對話",
|
"status.mute_conversation": "消音對話",
|
||||||
"status.open": "展開這個狀態",
|
"status.open": "展開這個狀態",
|
||||||
|
"status.pin": "Pin on profile",
|
||||||
"status.reblog": "轉推",
|
"status.reblog": "轉推",
|
||||||
"status.reblogged_by": "{name} 轉推了",
|
"status.reblogged_by": "{name} 轉推了",
|
||||||
"status.reply": "回應",
|
"status.reply": "回應",
|
||||||
|
@ -179,6 +183,7 @@
|
||||||
"status.show_less": "看少點",
|
"status.show_less": "看少點",
|
||||||
"status.show_more": "看更多",
|
"status.show_more": "看更多",
|
||||||
"status.unmute_conversation": "不消音對話",
|
"status.unmute_conversation": "不消音對話",
|
||||||
|
"status.unpin": "Unpin from profile",
|
||||||
"tabs_bar.compose": "編輯",
|
"tabs_bar.compose": "編輯",
|
||||||
"tabs_bar.federated_timeline": "聯盟",
|
"tabs_bar.federated_timeline": "聯盟",
|
||||||
"tabs_bar.home": "家",
|
"tabs_bar.home": "家",
|
||||||
|
|
|
@ -149,10 +149,20 @@ const privacyPreference = (a, b) => {
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const hydrate = (state, hydratedState) => {
|
||||||
|
state = clearAll(state.merge(hydratedState));
|
||||||
|
|
||||||
|
if (hydratedState.has('text')) {
|
||||||
|
state = state.set('text', hydratedState.get('text'));
|
||||||
|
}
|
||||||
|
|
||||||
|
return state;
|
||||||
|
};
|
||||||
|
|
||||||
export default function compose(state = initialState, action) {
|
export default function compose(state = initialState, action) {
|
||||||
switch(action.type) {
|
switch(action.type) {
|
||||||
case STORE_HYDRATE:
|
case STORE_HYDRATE:
|
||||||
return clearAll(state.merge(action.state.get('compose')));
|
return hydrate(state, action.state.get('compose'));
|
||||||
case COMPOSE_MOUNT:
|
case COMPOSE_MOUNT:
|
||||||
return state.set('mounted', true);
|
return state.set('mounted', true);
|
||||||
case COMPOSE_UNMOUNT:
|
case COMPOSE_UNMOUNT:
|
||||||
|
|
|
@ -3,6 +3,10 @@ import {
|
||||||
FAVOURITED_STATUSES_EXPAND_SUCCESS,
|
FAVOURITED_STATUSES_EXPAND_SUCCESS,
|
||||||
} from '../actions/favourites';
|
} from '../actions/favourites';
|
||||||
import { Map as ImmutableMap, List as ImmutableList } from 'immutable';
|
import { Map as ImmutableMap, List as ImmutableList } from 'immutable';
|
||||||
|
import {
|
||||||
|
FAVOURITE_SUCCESS,
|
||||||
|
UNFAVOURITE_SUCCESS,
|
||||||
|
} from '../actions/interactions';
|
||||||
|
|
||||||
const initialState = ImmutableMap({
|
const initialState = ImmutableMap({
|
||||||
favourites: ImmutableMap({
|
favourites: ImmutableMap({
|
||||||
|
@ -27,12 +31,28 @@ const appendToList = (state, listType, statuses, next) => {
|
||||||
}));
|
}));
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const prependOneToList = (state, listType, status) => {
|
||||||
|
return state.update(listType, listMap => listMap.withMutations(map => {
|
||||||
|
map.set('items', map.get('items').unshift(status.get('id')));
|
||||||
|
}));
|
||||||
|
};
|
||||||
|
|
||||||
|
const removeOneFromList = (state, listType, status) => {
|
||||||
|
return state.update(listType, listMap => listMap.withMutations(map => {
|
||||||
|
map.set('items', map.get('items').filter(item => item !== status.get('id')));
|
||||||
|
}));
|
||||||
|
};
|
||||||
|
|
||||||
export default function statusLists(state = initialState, action) {
|
export default function statusLists(state = initialState, action) {
|
||||||
switch(action.type) {
|
switch(action.type) {
|
||||||
case FAVOURITED_STATUSES_FETCH_SUCCESS:
|
case FAVOURITED_STATUSES_FETCH_SUCCESS:
|
||||||
return normalizeList(state, 'favourites', action.statuses, action.next);
|
return normalizeList(state, 'favourites', action.statuses, action.next);
|
||||||
case FAVOURITED_STATUSES_EXPAND_SUCCESS:
|
case FAVOURITED_STATUSES_EXPAND_SUCCESS:
|
||||||
return appendToList(state, 'favourites', action.statuses, action.next);
|
return appendToList(state, 'favourites', action.statuses, action.next);
|
||||||
|
case FAVOURITE_SUCCESS:
|
||||||
|
return prependOneToList(state, 'favourites', action.status);
|
||||||
|
case UNFAVOURITE_SUCCESS:
|
||||||
|
return removeOneFromList(state, 'favourites', action.status);
|
||||||
default:
|
default:
|
||||||
return state;
|
return state;
|
||||||
}
|
}
|
||||||
|
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue