Merge commit '4aea3f88a6d30f102a79c2da7fcfac96465ba1a8' into merging-upstream
|
@ -101,11 +101,19 @@ SMTP_FROM_ADDRESS=notifications@example.com
|
|||
# Swift (optional)
|
||||
# SWIFT_ENABLED=true
|
||||
# SWIFT_USERNAME=
|
||||
# For Keystone V3, the value for SWIFT_TENANT should be the project name
|
||||
# SWIFT_TENANT=
|
||||
# SWIFT_PASSWORD=
|
||||
# Keystone V2 and V3 URLs are supported. Use a V3 URL if possible to avoid
|
||||
# issues with token rate-limiting during high load.
|
||||
# SWIFT_AUTH_URL=
|
||||
# SWIFT_CONTAINER=
|
||||
# SWIFT_OBJECT_URL=
|
||||
# SWIFT_REGION=
|
||||
# Defaults to 'default'
|
||||
# SWIFT_DOMAIN_NAME=
|
||||
# Defaults to 60 seconds. Set to 0 to disable
|
||||
# SWIFT_CACHE_TTL=
|
||||
|
||||
# Optional alias for S3 if you want to use Cloudfront or Cloudflare in front
|
||||
# S3_CLOUDFRONT_HOST=
|
||||
|
|
|
@ -1 +1 @@
|
|||
2.4.1
|
||||
2.4.2
|
||||
|
|
|
@ -26,18 +26,16 @@ addons:
|
|||
postgresql: 9.4
|
||||
apt:
|
||||
sources:
|
||||
- ubuntu-toolchain-r-test
|
||||
- trusty-media
|
||||
packages:
|
||||
- ffmpeg
|
||||
- g++-6
|
||||
- libprotobuf-dev
|
||||
- protobuf-compiler
|
||||
- libicu-dev
|
||||
|
||||
rvm:
|
||||
- 2.3.4
|
||||
- 2.4.1
|
||||
- 2.4.2
|
||||
|
||||
services:
|
||||
- redis-server
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
FROM ruby:2.4.1-alpine3.6
|
||||
FROM ruby:2.4.2-alpine3.6
|
||||
|
||||
LABEL maintainer="https://github.com/tootsuite/mastodon" \
|
||||
description="A GNU Social-compatible microblogging server"
|
||||
|
|
6
Gemfile
|
@ -5,8 +5,8 @@ ruby '>= 2.3.0', '< 2.5.0'
|
|||
|
||||
gem 'pkg-config', '~> 1.2'
|
||||
|
||||
gem 'puma', '~> 3.8'
|
||||
gem 'rails', '~> 5.1.0'
|
||||
gem 'puma', '~> 3.10'
|
||||
gem 'rails', '~> 5.1.4'
|
||||
gem 'uglifier', '~> 3.2'
|
||||
|
||||
gem 'hamlit-rails', '~> 0.2'
|
||||
|
@ -25,7 +25,7 @@ gem 'bootsnap'
|
|||
gem 'browser'
|
||||
gem 'charlock_holmes', '~> 0.7.5'
|
||||
gem 'iso-639'
|
||||
gem 'cld3', '~> 3.1'
|
||||
gem 'cld3', '~> 3.2.0'
|
||||
gem 'devise', '~> 4.2'
|
||||
gem 'devise-two-factor', '~> 3.0'
|
||||
gem 'doorkeeper', '~> 4.2'
|
||||
|
|
174
Gemfile.lock
|
@ -1,25 +1,25 @@
|
|||
GEM
|
||||
remote: https://rubygems.org/
|
||||
specs:
|
||||
actioncable (5.1.3)
|
||||
actionpack (= 5.1.3)
|
||||
actioncable (5.1.4)
|
||||
actionpack (= 5.1.4)
|
||||
nio4r (~> 2.0)
|
||||
websocket-driver (~> 0.6.1)
|
||||
actionmailer (5.1.3)
|
||||
actionpack (= 5.1.3)
|
||||
actionview (= 5.1.3)
|
||||
activejob (= 5.1.3)
|
||||
actionmailer (5.1.4)
|
||||
actionpack (= 5.1.4)
|
||||
actionview (= 5.1.4)
|
||||
activejob (= 5.1.4)
|
||||
mail (~> 2.5, >= 2.5.4)
|
||||
rails-dom-testing (~> 2.0)
|
||||
actionpack (5.1.3)
|
||||
actionview (= 5.1.3)
|
||||
activesupport (= 5.1.3)
|
||||
actionpack (5.1.4)
|
||||
actionview (= 5.1.4)
|
||||
activesupport (= 5.1.4)
|
||||
rack (~> 2.0)
|
||||
rack-test (~> 0.6.3)
|
||||
rack-test (>= 0.6.3)
|
||||
rails-dom-testing (~> 2.0)
|
||||
rails-html-sanitizer (~> 1.0, >= 1.0.2)
|
||||
actionview (5.1.3)
|
||||
activesupport (= 5.1.3)
|
||||
actionview (5.1.4)
|
||||
activesupport (= 5.1.4)
|
||||
builder (~> 3.1)
|
||||
erubi (~> 1.4)
|
||||
rails-dom-testing (~> 2.0)
|
||||
|
@ -30,16 +30,16 @@ GEM
|
|||
case_transform (>= 0.2)
|
||||
jsonapi-renderer (>= 0.1.1.beta1, < 0.2)
|
||||
active_record_query_trace (1.5.4)
|
||||
activejob (5.1.3)
|
||||
activesupport (= 5.1.3)
|
||||
activejob (5.1.4)
|
||||
activesupport (= 5.1.4)
|
||||
globalid (>= 0.3.6)
|
||||
activemodel (5.1.3)
|
||||
activesupport (= 5.1.3)
|
||||
activerecord (5.1.3)
|
||||
activemodel (= 5.1.3)
|
||||
activesupport (= 5.1.3)
|
||||
activemodel (5.1.4)
|
||||
activesupport (= 5.1.4)
|
||||
activerecord (5.1.4)
|
||||
activemodel (= 5.1.4)
|
||||
activesupport (= 5.1.4)
|
||||
arel (~> 8.0)
|
||||
activesupport (5.1.3)
|
||||
activesupport (5.1.4)
|
||||
concurrent-ruby (~> 1.0, >= 1.0.2)
|
||||
i18n (~> 0.7)
|
||||
minitest (~> 5.1)
|
||||
|
@ -57,33 +57,33 @@ GEM
|
|||
encryptor (~> 3.0.0)
|
||||
av (0.9.0)
|
||||
cocaine (~> 0.5.3)
|
||||
aws-sdk (2.10.21)
|
||||
aws-sdk-resources (= 2.10.21)
|
||||
aws-sdk-core (2.10.21)
|
||||
aws-sdk (2.10.46)
|
||||
aws-sdk-resources (= 2.10.46)
|
||||
aws-sdk-core (2.10.46)
|
||||
aws-sigv4 (~> 1.0)
|
||||
jmespath (~> 1.0)
|
||||
aws-sdk-resources (2.10.21)
|
||||
aws-sdk-core (= 2.10.21)
|
||||
aws-sigv4 (1.0.1)
|
||||
aws-sdk-resources (2.10.46)
|
||||
aws-sdk-core (= 2.10.46)
|
||||
aws-sigv4 (1.0.2)
|
||||
bcrypt (3.1.11)
|
||||
better_errors (2.1.1)
|
||||
better_errors (2.3.0)
|
||||
coderay (>= 1.0.0)
|
||||
erubis (>= 2.6.6)
|
||||
erubi (>= 1.0.0)
|
||||
rack (>= 0.9.0)
|
||||
binding_of_caller (0.7.2)
|
||||
debug_inspector (>= 0.0.1)
|
||||
bootsnap (1.1.2)
|
||||
bootsnap (1.1.3)
|
||||
msgpack (~> 1.0)
|
||||
brakeman (3.7.2)
|
||||
browser (2.4.0)
|
||||
browser (2.5.1)
|
||||
builder (3.2.3)
|
||||
bullet (5.5.1)
|
||||
bullet (5.6.1)
|
||||
activesupport (>= 3.0.0)
|
||||
uniform_notifier (~> 1.10.0)
|
||||
bundler-audit (0.6.0)
|
||||
bundler (~> 1.2)
|
||||
thor (~> 0.18)
|
||||
capistrano (3.8.2)
|
||||
capistrano (3.9.1)
|
||||
airbrussh (>= 1.0.0)
|
||||
i18n
|
||||
rake (>= 10.0.0)
|
||||
|
@ -99,9 +99,9 @@ GEM
|
|||
sshkit (~> 1.3)
|
||||
capistrano-yarn (2.0.2)
|
||||
capistrano (~> 3.0)
|
||||
capybara (2.14.4)
|
||||
capybara (2.15.1)
|
||||
addressable
|
||||
mime-types (>= 1.16)
|
||||
mini_mime (>= 0.1.3)
|
||||
nokogiri (>= 1.3.3)
|
||||
rack (>= 1.0.0)
|
||||
rack-test (>= 0.5.4)
|
||||
|
@ -110,12 +110,12 @@ GEM
|
|||
activesupport
|
||||
charlock_holmes (0.7.5)
|
||||
chunky_png (1.3.8)
|
||||
cld3 (3.1.3)
|
||||
cld3 (3.2.0)
|
||||
ffi (>= 1.1.0, < 1.10.0)
|
||||
climate_control (0.2.0)
|
||||
cocaine (0.5.8)
|
||||
climate_control (>= 0.0.3, < 1.0)
|
||||
coderay (1.1.1)
|
||||
coderay (1.1.2)
|
||||
colorize (0.8.1)
|
||||
concurrent-ruby (1.0.5)
|
||||
connection_pool (2.2.1)
|
||||
|
@ -151,13 +151,12 @@ GEM
|
|||
thread_safe
|
||||
encryptor (3.0.0)
|
||||
erubi (1.6.1)
|
||||
erubis (2.7.0)
|
||||
et-orbi (1.0.5)
|
||||
tzinfo
|
||||
excon (0.58.0)
|
||||
excon (0.59.0)
|
||||
execjs (2.7.0)
|
||||
fabrication (2.16.2)
|
||||
faker (1.7.3)
|
||||
fabrication (2.16.3)
|
||||
faker (1.8.4)
|
||||
i18n (~> 0.5)
|
||||
fast_blank (1.0.0)
|
||||
ffi (1.9.18)
|
||||
|
@ -194,7 +193,7 @@ GEM
|
|||
railties (>= 4.0.1)
|
||||
hamster (3.0.0)
|
||||
concurrent-ruby (~> 1.0)
|
||||
hashdiff (0.3.5)
|
||||
hashdiff (0.3.6)
|
||||
highline (1.7.8)
|
||||
hiredis (0.6.1)
|
||||
hkdf (0.3.0)
|
||||
|
@ -213,11 +212,11 @@ GEM
|
|||
colorize
|
||||
rack
|
||||
i18n (0.8.6)
|
||||
i18n-tasks (0.9.16)
|
||||
i18n-tasks (0.9.18)
|
||||
activesupport (>= 4.0.2)
|
||||
ast (>= 2.1.0)
|
||||
easy_translate (>= 0.5.0)
|
||||
erubis
|
||||
erubi
|
||||
highline (>= 1.7.3)
|
||||
i18n
|
||||
parser (>= 2.2.3.0)
|
||||
|
@ -231,7 +230,7 @@ GEM
|
|||
json-ld (2.1.5)
|
||||
multi_json (~> 1.12)
|
||||
rdf (~> 2.2)
|
||||
json-ld-preloaded (2.2.1)
|
||||
json-ld-preloaded (2.2.2)
|
||||
json-ld (~> 2.1, >= 2.1.5)
|
||||
multi_json (~> 1.11)
|
||||
rdf (~> 2.2)
|
||||
|
@ -258,10 +257,11 @@ GEM
|
|||
letter_opener (~> 1.0)
|
||||
railties (>= 3.2)
|
||||
link_header (0.0.8)
|
||||
lograge (0.5.1)
|
||||
lograge (0.6.0)
|
||||
actionpack (>= 4, < 5.2)
|
||||
activesupport (>= 4, < 5.2)
|
||||
railties (>= 4, < 5.2)
|
||||
request_store (~> 1.0)
|
||||
loofah (2.0.3)
|
||||
nokogiri (>= 1.5.9)
|
||||
mail (2.6.6)
|
||||
|
@ -276,27 +276,28 @@ GEM
|
|||
mime-types-data (~> 3.2015)
|
||||
mime-types-data (3.2016.0521)
|
||||
mimemagic (0.3.2)
|
||||
mini_mime (0.1.4)
|
||||
mini_portile2 (2.2.0)
|
||||
minitest (5.10.3)
|
||||
msgpack (1.1.0)
|
||||
multi_json (1.12.1)
|
||||
multi_json (1.12.2)
|
||||
net-scp (1.2.1)
|
||||
net-ssh (>= 2.6.5)
|
||||
net-ssh (4.1.0)
|
||||
net-ssh (4.2.0)
|
||||
nio4r (2.1.0)
|
||||
nokogiri (1.8.0)
|
||||
mini_portile2 (~> 2.2.0)
|
||||
nokogumbo (1.4.13)
|
||||
nokogiri
|
||||
oj (3.3.4)
|
||||
openssl (2.0.4)
|
||||
oj (3.3.5)
|
||||
openssl (2.0.5)
|
||||
orm_adapter (0.5.0)
|
||||
ostatus2 (2.0.1)
|
||||
addressable (~> 2.4)
|
||||
http (~> 2.0)
|
||||
nokogiri (~> 1.6)
|
||||
openssl (~> 2.0)
|
||||
ox (2.5.0)
|
||||
ox (2.6.0)
|
||||
paperclip (5.1.0)
|
||||
activemodel (>= 4.2.0)
|
||||
activesupport (>= 4.2.0)
|
||||
|
@ -306,15 +307,15 @@ GEM
|
|||
paperclip-av-transcoder (0.6.4)
|
||||
av (~> 0.9.0)
|
||||
paperclip (>= 2.5.2)
|
||||
parallel (1.11.2)
|
||||
parallel_tests (2.14.2)
|
||||
parallel (1.12.0)
|
||||
parallel_tests (2.15.0)
|
||||
parallel
|
||||
parser (2.4.0.0)
|
||||
ast (~> 2.2)
|
||||
pg (0.21.0)
|
||||
pghero (1.7.0)
|
||||
activerecord
|
||||
pkg-config (1.2.4)
|
||||
pkg-config (1.2.7)
|
||||
powerpack (0.1.1)
|
||||
pry (0.10.4)
|
||||
coderay (~> 1.1.0)
|
||||
|
@ -323,7 +324,7 @@ GEM
|
|||
pry-rails (0.3.6)
|
||||
pry (>= 0.10.4)
|
||||
public_suffix (3.0.0)
|
||||
puma (3.9.1)
|
||||
puma (3.10.0)
|
||||
pundit (1.1.0)
|
||||
activesupport (>= 3.0.0)
|
||||
rabl (0.13.1)
|
||||
|
@ -334,20 +335,20 @@ GEM
|
|||
rack-cors (0.4.1)
|
||||
rack-protection (2.0.0)
|
||||
rack
|
||||
rack-test (0.6.3)
|
||||
rack (>= 1.0)
|
||||
rack-test (0.7.0)
|
||||
rack (>= 1.0, < 3)
|
||||
rack-timeout (0.4.2)
|
||||
rails (5.1.3)
|
||||
actioncable (= 5.1.3)
|
||||
actionmailer (= 5.1.3)
|
||||
actionpack (= 5.1.3)
|
||||
actionview (= 5.1.3)
|
||||
activejob (= 5.1.3)
|
||||
activemodel (= 5.1.3)
|
||||
activerecord (= 5.1.3)
|
||||
activesupport (= 5.1.3)
|
||||
rails (5.1.4)
|
||||
actioncable (= 5.1.4)
|
||||
actionmailer (= 5.1.4)
|
||||
actionpack (= 5.1.4)
|
||||
actionview (= 5.1.4)
|
||||
activejob (= 5.1.4)
|
||||
activemodel (= 5.1.4)
|
||||
activerecord (= 5.1.4)
|
||||
activesupport (= 5.1.4)
|
||||
bundler (>= 1.3.0)
|
||||
railties (= 5.1.3)
|
||||
railties (= 5.1.4)
|
||||
sprockets-rails (>= 2.0.0)
|
||||
rails-controller-testing (1.0.2)
|
||||
actionpack (~> 5.x, >= 5.0.1)
|
||||
|
@ -363,16 +364,16 @@ GEM
|
|||
railties (~> 5.0)
|
||||
rails-settings-cached (0.6.6)
|
||||
rails (>= 4.2.0)
|
||||
railties (5.1.3)
|
||||
actionpack (= 5.1.3)
|
||||
activesupport (= 5.1.3)
|
||||
railties (5.1.4)
|
||||
actionpack (= 5.1.4)
|
||||
activesupport (= 5.1.4)
|
||||
method_source
|
||||
rake (>= 0.8.7)
|
||||
thor (>= 0.18.1, < 2.0)
|
||||
rainbow (2.2.2)
|
||||
rake
|
||||
rake (12.0.0)
|
||||
rdf (2.2.8)
|
||||
rake (12.1.0)
|
||||
rdf (2.2.9)
|
||||
hamster (~> 3.0)
|
||||
link_header (~> 0.0, >= 0.0.8)
|
||||
rdf-normalize (0.3.2)
|
||||
|
@ -396,6 +397,7 @@ GEM
|
|||
redis-store (>= 1.2, < 2)
|
||||
redis-store (1.3.0)
|
||||
redis (>= 2.2)
|
||||
request_store (1.3.2)
|
||||
responders (2.4.0)
|
||||
actionpack (>= 4.2.0, < 5.3)
|
||||
railties (>= 4.2.0, < 5.3)
|
||||
|
@ -410,7 +412,7 @@ GEM
|
|||
rspec-mocks (3.6.0)
|
||||
diff-lcs (>= 1.2.0, < 2.0)
|
||||
rspec-support (~> 3.6.0)
|
||||
rspec-rails (3.6.0)
|
||||
rspec-rails (3.6.1)
|
||||
actionpack (>= 3.0)
|
||||
activesupport (>= 3.0)
|
||||
railties (>= 3.0)
|
||||
|
@ -422,15 +424,15 @@ GEM
|
|||
rspec-core (~> 3.0, >= 3.0.0)
|
||||
sidekiq (>= 2.4.0)
|
||||
rspec-support (3.6.0)
|
||||
rubocop (0.49.1)
|
||||
rubocop (0.50.0)
|
||||
parallel (~> 1.10)
|
||||
parser (>= 2.3.3.1, < 3.0)
|
||||
powerpack (~> 0.1)
|
||||
rainbow (>= 1.99.1, < 3.0)
|
||||
rainbow (>= 2.2.2, < 3.0)
|
||||
ruby-progressbar (~> 1.7)
|
||||
unicode-display_width (~> 1.0, >= 1.0.1)
|
||||
ruby-oembed (0.12.0)
|
||||
ruby-progressbar (1.8.1)
|
||||
ruby-progressbar (1.8.3)
|
||||
rufus-scheduler (3.4.2)
|
||||
et-orbi (~> 1.0)
|
||||
safe_yaml (1.0.4)
|
||||
|
@ -438,7 +440,7 @@ GEM
|
|||
crass (~> 1.0.2)
|
||||
nokogiri (>= 1.4.4)
|
||||
nokogumbo (~> 1.4.1)
|
||||
sass (3.4.24)
|
||||
sass (3.4.25)
|
||||
scss_lint (0.54.0)
|
||||
rake (>= 0.9, < 13)
|
||||
sass (~> 3.4.20)
|
||||
|
@ -450,12 +452,12 @@ GEM
|
|||
sidekiq-bulk (0.1.1)
|
||||
activesupport
|
||||
sidekiq
|
||||
sidekiq-scheduler (2.1.8)
|
||||
sidekiq-scheduler (2.1.9)
|
||||
redis (~> 3)
|
||||
rufus-scheduler (~> 3.2)
|
||||
sidekiq (>= 3)
|
||||
tilt (>= 1.4.0)
|
||||
sidekiq-unique-jobs (5.0.9)
|
||||
sidekiq-unique-jobs (5.0.10)
|
||||
sidekiq (>= 4.0, <= 6.0)
|
||||
thor (~> 0)
|
||||
simple-navigation (4.0.5)
|
||||
|
@ -463,20 +465,20 @@ GEM
|
|||
simple_form (3.5.0)
|
||||
actionpack (> 4, < 5.2)
|
||||
activemodel (> 4, < 5.2)
|
||||
simplecov (0.14.1)
|
||||
simplecov (0.15.1)
|
||||
docile (~> 1.1.0)
|
||||
json (>= 1.8, < 3)
|
||||
simplecov-html (~> 0.10.0)
|
||||
simplecov-html (0.10.1)
|
||||
simplecov-html (0.10.2)
|
||||
slop (3.6.0)
|
||||
sprockets (3.7.1)
|
||||
concurrent-ruby (~> 1.0)
|
||||
rack (> 1, < 3)
|
||||
sprockets-rails (3.2.0)
|
||||
sprockets-rails (3.2.1)
|
||||
actionpack (>= 4.0)
|
||||
activesupport (>= 4.0)
|
||||
sprockets (>= 3.0.0)
|
||||
sshkit (1.13.1)
|
||||
sshkit (1.14.0)
|
||||
net-scp (>= 1.1.2)
|
||||
net-ssh (>= 2.8.0)
|
||||
statsd-instrument (2.1.4)
|
||||
|
@ -541,7 +543,7 @@ DEPENDENCIES
|
|||
capistrano-yarn (~> 2.0)
|
||||
capybara (~> 2.14)
|
||||
charlock_holmes (~> 0.7.5)
|
||||
cld3 (~> 3.1)
|
||||
cld3 (~> 3.2.0)
|
||||
climate_control (~> 0.2)
|
||||
devise (~> 4.2)
|
||||
devise-two-factor (~> 3.0)
|
||||
|
@ -582,13 +584,13 @@ DEPENDENCIES
|
|||
pghero (~> 1.7)
|
||||
pkg-config (~> 1.2)
|
||||
pry-rails (~> 0.3)
|
||||
puma (~> 3.8)
|
||||
puma (~> 3.10)
|
||||
pundit (~> 1.1)
|
||||
rabl (~> 0.13)
|
||||
rack-attack (~> 5.0)
|
||||
rack-cors (~> 0.4)
|
||||
rack-timeout (~> 0.4)
|
||||
rails (~> 5.1.0)
|
||||
rails (~> 5.1.4)
|
||||
rails-controller-testing (~> 1.0)
|
||||
rails-i18n (~> 5.0)
|
||||
rails-settings-cached (~> 0.6)
|
||||
|
@ -620,7 +622,7 @@ DEPENDENCIES
|
|||
webpush
|
||||
|
||||
RUBY VERSION
|
||||
ruby 2.4.1p111
|
||||
ruby 2.4.2p198
|
||||
|
||||
BUNDLED WITH
|
||||
1.15.4
|
||||
|
|
|
@ -0,0 +1,34 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
module Admin
|
||||
class CustomEmojisController < BaseController
|
||||
def index
|
||||
@custom_emojis = CustomEmoji.where(domain: nil)
|
||||
end
|
||||
|
||||
def new
|
||||
@custom_emoji = CustomEmoji.new
|
||||
end
|
||||
|
||||
def create
|
||||
@custom_emoji = CustomEmoji.new(resource_params)
|
||||
|
||||
if @custom_emoji.save
|
||||
redirect_to admin_custom_emojis_path, notice: I18n.t('admin.custom_emojis.created_msg')
|
||||
else
|
||||
render :new
|
||||
end
|
||||
end
|
||||
|
||||
def destroy
|
||||
CustomEmoji.find(params[:id]).destroy
|
||||
redirect_to admin_custom_emojis_path, notice: I18n.t('admin.custom_emojis.destroyed_msg')
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def resource_params
|
||||
params.require(:custom_emoji).permit(:shortcode, :image)
|
||||
end
|
||||
end
|
||||
end
|
|
@ -14,8 +14,12 @@ module Admin
|
|||
|
||||
private
|
||||
|
||||
def filtered_instances
|
||||
InstanceFilter.new(filter_params).results
|
||||
end
|
||||
|
||||
def paginated_instances
|
||||
Account.remote.by_domain_accounts.page(params[:page])
|
||||
filtered_instances.page(params[:page])
|
||||
end
|
||||
|
||||
helper_method :paginated_instances
|
||||
|
@ -27,5 +31,11 @@ module Admin
|
|||
def subscribeable_accounts
|
||||
Account.with_followers.remote.where(domain: params[:by_domain])
|
||||
end
|
||||
|
||||
def filter_params
|
||||
params.permit(
|
||||
:domain_name
|
||||
)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
@ -14,6 +14,7 @@ module Admin
|
|||
open_deletion
|
||||
timeline_preview
|
||||
bootstrap_timeline_accounts
|
||||
thumbnail
|
||||
).freeze
|
||||
|
||||
BOOLEAN_SETTINGS = %w(
|
||||
|
@ -22,15 +23,24 @@ module Admin
|
|||
timeline_preview
|
||||
).freeze
|
||||
|
||||
UPLOAD_SETTINGS = %w(
|
||||
thumbnail
|
||||
).freeze
|
||||
|
||||
def edit
|
||||
@admin_settings = Form::AdminSettings.new
|
||||
end
|
||||
|
||||
def update
|
||||
settings_params.each do |key, value|
|
||||
if UPLOAD_SETTINGS.include?(key)
|
||||
upload = SiteUpload.where(var: key).first_or_initialize(var: key)
|
||||
upload.update(file: value)
|
||||
else
|
||||
setting = Setting.where(var: key).first_or_initialize(var: key)
|
||||
setting.update(value: value_for_update(key, value))
|
||||
end
|
||||
end
|
||||
|
||||
flash[:notice] = I18n.t('generic.changes_saved_msg')
|
||||
redirect_to edit_admin_settings_path
|
||||
|
|
|
@ -12,7 +12,30 @@ class HomeController < ApplicationController
|
|||
private
|
||||
|
||||
def authenticate_user!
|
||||
redirect_to(single_user_mode? ? account_path(Account.first) : about_path) unless user_signed_in?
|
||||
return if user_signed_in?
|
||||
|
||||
matches = request.path.match(/\A\/web\/(statuses|accounts)\/([\d]+)\z/)
|
||||
|
||||
if matches
|
||||
case matches[1]
|
||||
when 'statuses'
|
||||
status = Status.find_by(id: matches[2])
|
||||
|
||||
if status && (status.public_visibility? || status.unlisted_visibility?)
|
||||
redirect_to(ActivityPub::TagManager.instance.url_for(status))
|
||||
return
|
||||
end
|
||||
when 'accounts'
|
||||
account = Account.find_by(id: matches[2])
|
||||
|
||||
if account
|
||||
redirect_to(ActivityPub::TagManager.instance.url_for(account))
|
||||
return
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
redirect_to(default_redirect_path)
|
||||
end
|
||||
|
||||
def set_initial_state_json
|
||||
|
@ -29,4 +52,14 @@ class HomeController < ApplicationController
|
|||
admin: Account.find_local(Setting.site_contact_username),
|
||||
}
|
||||
end
|
||||
|
||||
def default_redirect_path
|
||||
if request.path.start_with?('/web')
|
||||
new_user_session_path
|
||||
elsif single_user_mode?
|
||||
short_account_path(Account.first)
|
||||
else
|
||||
about_path
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
@ -0,0 +1,40 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
class MediaProxyController < ApplicationController
|
||||
include RoutingHelper
|
||||
|
||||
def show
|
||||
RedisLock.acquire(lock_options) do |lock|
|
||||
if lock.acquired?
|
||||
@media_attachment = MediaAttachment.remote.find(params[:id])
|
||||
redownload! if @media_attachment.needs_redownload? && !reject_media?
|
||||
end
|
||||
end
|
||||
|
||||
redirect_to full_asset_url(@media_attachment.file.url(version))
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def redownload!
|
||||
@media_attachment.file_remote_url = @media_attachment.remote_url
|
||||
@media_attachment.created_at = Time.now.utc
|
||||
@media_attachment.save!
|
||||
end
|
||||
|
||||
def version
|
||||
if request.path.ends_with?('/small')
|
||||
:small
|
||||
else
|
||||
:original
|
||||
end
|
||||
end
|
||||
|
||||
def lock_options
|
||||
{ redis: Redis.current, key: "media_download:#{params[:id]}" }
|
||||
end
|
||||
|
||||
def reject_media?
|
||||
DomainBlock.find_by(domain: @media_attachment.account.domain)&.reject_media?
|
||||
end
|
||||
end
|
|
@ -42,4 +42,8 @@ module ApplicationHelper
|
|||
|
||||
content_tag(:i, nil, attributes.merge(class: class_names.join(' ')))
|
||||
end
|
||||
|
||||
def opengraph(property, content)
|
||||
tag(:meta, content: content, property: property)
|
||||
end
|
||||
end
|
||||
|
|
|
@ -41,7 +41,7 @@ module SettingsHelper
|
|||
end
|
||||
|
||||
def filterable_languages
|
||||
I18n.available_locales.map { |locale| locale.to_s.split('-').first.to_sym }.uniq
|
||||
LanguageDetector.instance.language_names.select(&HUMAN_LOCALES.method(:key?))
|
||||
end
|
||||
|
||||
def hash_to_object(hash)
|
||||
|
|
|
@ -1 +1 @@
|
|||
<svg xmlns="http://www.w3.org/2000/svg" width="61.076954mm" height="65.47831mm" viewBox="0 0 216.4144 232.00976"><path d="M211.80734 139.0875c-3.18125 16.36625-28.4925 34.2775-57.5625 37.74875-15.15875 1.80875-30.08375 3.47125-45.99875 2.74125-26.0275-1.1925-46.565-6.2125-46.565-6.2125 0 2.53375.15625 4.94625.46875 7.2025 3.38375 25.68625 25.47 27.225 46.39125 27.9425 21.11625.7225 39.91875-5.20625 39.91875-5.20625l.8675 19.09s-14.77 7.93125-41.08125 9.39c-14.50875.7975-32.52375-.365-53.50625-5.91875C9.23234 213.82 1.40609 165.31125.20859 116.09125c-.365-14.61375-.14-28.39375-.14-39.91875 0-50.33 32.97625-65.0825 32.97625-65.0825C49.67234 3.45375 78.20359.2425 107.86484 0h.72875c29.66125.2425 58.21125 3.45375 74.8375 11.09 0 0 32.975 14.7525 32.975 65.0825 0 0 .41375 37.13375-4.59875 62.915" fill="#3088d4"/><path d="M177.50984 80.077v60.94125h-24.14375v-59.15c0-12.46875-5.24625-18.7975-15.74-18.7975-11.6025 0-17.4175 7.5075-17.4175 22.3525v32.37625H96.20734V85.42325c0-14.845-5.81625-22.3525-17.41875-22.3525-10.49375 0-15.74 6.32875-15.74 18.7975v59.15H38.90484V80.077c0-12.455 3.17125-22.3525 9.54125-29.675 6.56875-7.3225 15.17125-11.07625 25.85-11.07625 12.355 0 21.71125 4.74875 27.8975 14.2475l6.01375 10.08125 6.015-10.08125c6.185-9.49875 15.54125-14.2475 27.8975-14.2475 10.6775 0 19.28 3.75375 25.85 11.07625 6.36875 7.3225 9.54 17.22 9.54 29.675" fill="#fff"/></svg>
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 216.4144 232.00976"><path d="M211.80734 139.0875c-3.18125 16.36625-28.4925 34.2775-57.5625 37.74875-15.15875 1.80875-30.08375 3.47125-45.99875 2.74125-26.0275-1.1925-46.565-6.2125-46.565-6.2125 0 2.53375.15625 4.94625.46875 7.2025 3.38375 25.68625 25.47 27.225 46.39125 27.9425 21.11625.7225 39.91875-5.20625 39.91875-5.20625l.8675 19.09s-14.77 7.93125-41.08125 9.39c-14.50875.7975-32.52375-.365-53.50625-5.91875C9.23234 213.82 1.40609 165.31125.20859 116.09125c-.365-14.61375-.14-28.39375-.14-39.91875 0-50.33 32.97625-65.0825 32.97625-65.0825C49.67234 3.45375 78.20359.2425 107.86484 0h.72875c29.66125.2425 58.21125 3.45375 74.8375 11.09 0 0 32.975 14.7525 32.975 65.0825 0 0 .41375 37.13375-4.59875 62.915" fill="#3088d4"/><path d="M177.50984 80.077v60.94125h-24.14375v-59.15c0-12.46875-5.24625-18.7975-15.74-18.7975-11.6025 0-17.4175 7.5075-17.4175 22.3525v32.37625H96.20734V85.42325c0-14.845-5.81625-22.3525-17.41875-22.3525-10.49375 0-15.74 6.32875-15.74 18.7975v59.15H38.90484V80.077c0-12.455 3.17125-22.3525 9.54125-29.675 6.56875-7.3225 15.17125-11.07625 25.85-11.07625 12.355 0 21.71125 4.74875 27.8975 14.2475l6.01375 10.08125 6.015-10.08125c6.185-9.49875 15.54125-14.2475 27.8975-14.2475 10.6775 0 19.28 3.75375 25.85 11.07625 6.36875 7.3225 9.54 17.22 9.54 29.675" fill="#fff"/></svg>
|
||||
|
|
Before Width: | Height: | Size: 1.4 KiB After Width: | Height: | Size: 1.3 KiB |
|
@ -1 +1 @@
|
|||
<svg xmlns="http://www.w3.org/2000/svg" width="61.077141mm" height="65.47831mm" viewBox="0 0 216.41507 232.00976"><path d="M211.80683 139.0875c-3.1825 16.36625-28.4925 34.2775-57.5625 37.74875-15.16 1.80875-30.0825 3.47125-45.99875 2.74125-26.0275-1.1925-46.565-6.2125-46.565-6.2125 0 2.53375.15625 4.94625.46875 7.2025 3.38375 25.68625 25.47 27.225 46.3925 27.9425 21.115.7225 39.91625-5.20625 39.91625-5.20625l.86875 19.09s-14.77 7.93125-41.08125 9.39c-14.50875.7975-32.52375-.365-53.50625-5.91875C9.23183 213.82 1.40558 165.31125.20808 116.09125c-.36375-14.61375-.14-28.39375-.14-39.91875 0-50.33 32.97625-65.0825 32.97625-65.0825C49.67058 3.45375 78.20308.2425 107.86433 0h.72875c29.66125.2425 58.21125 3.45375 74.8375 11.09 0 0 32.97625 14.7525 32.97625 65.0825 0 0 .4125 37.13375-4.6 62.915" fill="#3088d4"/><path d="M65.68743 96.45938c0 9.01375-7.3075 16.32125-16.3225 16.32125-9.01375 0-16.32-7.3075-16.32-16.32125 0-9.01375 7.30625-16.3225 16.32-16.3225 9.015 0 16.3225 7.30875 16.3225 16.3225M124.52893 96.45938c0 9.01375-7.30875 16.32125-16.3225 16.32125-9.01375 0-16.32125-7.3075-16.32125-16.32125 0-9.01375 7.3075-16.3225 16.32125-16.3225 9.01375 0 16.3225 7.30875 16.3225 16.3225M183.36933 96.45938c0 9.01375-7.3075 16.32125-16.32125 16.32125-9.01375 0-16.32125-7.3075-16.32125-16.32125 0-9.01375 7.3075-16.3225 16.32125-16.3225 9.01375 0 16.32125 7.30875 16.32125 16.3225" fill="#fff"/></svg>
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 216.41507 232.00976"><path d="M211.80683 139.0875c-3.1825 16.36625-28.4925 34.2775-57.5625 37.74875-15.16 1.80875-30.0825 3.47125-45.99875 2.74125-26.0275-1.1925-46.565-6.2125-46.565-6.2125 0 2.53375.15625 4.94625.46875 7.2025 3.38375 25.68625 25.47 27.225 46.3925 27.9425 21.115.7225 39.91625-5.20625 39.91625-5.20625l.86875 19.09s-14.77 7.93125-41.08125 9.39c-14.50875.7975-32.52375-.365-53.50625-5.91875C9.23183 213.82 1.40558 165.31125.20808 116.09125c-.36375-14.61375-.14-28.39375-.14-39.91875 0-50.33 32.97625-65.0825 32.97625-65.0825C49.67058 3.45375 78.20308.2425 107.86433 0h.72875c29.66125.2425 58.21125 3.45375 74.8375 11.09 0 0 32.97625 14.7525 32.97625 65.0825 0 0 .4125 37.13375-4.6 62.915" fill="#3088d4"/><path d="M65.68743 96.45938c0 9.01375-7.3075 16.32125-16.3225 16.32125-9.01375 0-16.32-7.3075-16.32-16.32125 0-9.01375 7.30625-16.3225 16.32-16.3225 9.015 0 16.3225 7.30875 16.3225 16.3225M124.52893 96.45938c0 9.01375-7.30875 16.32125-16.3225 16.32125-9.01375 0-16.32125-7.3075-16.32125-16.32125 0-9.01375 7.3075-16.3225 16.32125-16.3225 9.01375 0 16.3225 7.30875 16.3225 16.3225M183.36933 96.45938c0 9.01375-7.3075 16.32125-16.32125 16.32125-9.01375 0-16.32125-7.3075-16.32125-16.32125 0-9.01375 7.3075-16.3225 16.32125-16.3225 9.01375 0 16.32125 7.30875 16.32125 16.3225" fill="#fff"/></svg>
|
||||
|
|
Before Width: | Height: | Size: 1.4 KiB After Width: | Height: | Size: 1.3 KiB |
Before Width: | Height: | Size: 5.6 KiB After Width: | Height: | Size: 5.5 KiB |
Before Width: | Height: | Size: 25 KiB |
After Width: | Height: | Size: 285 KiB |
|
@ -0,0 +1,17 @@
|
|||
export const HEIGHT_CACHE_SET = 'HEIGHT_CACHE_SET';
|
||||
export const HEIGHT_CACHE_CLEAR = 'HEIGHT_CACHE_CLEAR';
|
||||
|
||||
export function setHeight (key, id, height) {
|
||||
return {
|
||||
type: HEIGHT_CACHE_SET,
|
||||
key,
|
||||
id,
|
||||
height,
|
||||
};
|
||||
};
|
||||
|
||||
export function clearHeight () {
|
||||
return {
|
||||
type: HEIGHT_CACHE_CLEAR,
|
||||
};
|
||||
};
|
|
@ -23,9 +23,6 @@ export const STATUS_UNMUTE_REQUEST = 'STATUS_UNMUTE_REQUEST';
|
|||
export const STATUS_UNMUTE_SUCCESS = 'STATUS_UNMUTE_SUCCESS';
|
||||
export const STATUS_UNMUTE_FAIL = 'STATUS_UNMUTE_FAIL';
|
||||
|
||||
export const STATUS_SET_HEIGHT = 'STATUS_SET_HEIGHT';
|
||||
export const STATUSES_CLEAR_HEIGHT = 'STATUSES_CLEAR_HEIGHT';
|
||||
|
||||
export function fetchStatusRequest(id, skipLoading) {
|
||||
return {
|
||||
type: STATUS_FETCH_REQUEST,
|
||||
|
@ -218,17 +215,3 @@ export function unmuteStatusFail(id, error) {
|
|||
error,
|
||||
};
|
||||
};
|
||||
|
||||
export function setStatusHeight (id, height) {
|
||||
return {
|
||||
type: STATUS_SET_HEIGHT,
|
||||
id,
|
||||
height,
|
||||
};
|
||||
};
|
||||
|
||||
export function clearStatusesHeight () {
|
||||
return {
|
||||
type: STATUSES_CLEAR_HEIGHT,
|
||||
};
|
||||
};
|
||||
|
|
|
@ -7,10 +7,13 @@ import getRectFromEntry from '../features/ui/util/get_rect_from_entry';
|
|||
export default class IntersectionObserverArticle extends ImmutablePureComponent {
|
||||
|
||||
static propTypes = {
|
||||
intersectionObserverWrapper: PropTypes.object,
|
||||
intersectionObserverWrapper: PropTypes.object.isRequired,
|
||||
id: PropTypes.oneOfType([PropTypes.string, PropTypes.number]),
|
||||
index: PropTypes.oneOfType([PropTypes.string, PropTypes.number]),
|
||||
listLength: PropTypes.oneOfType([PropTypes.string, PropTypes.number]),
|
||||
saveHeightKey: PropTypes.string,
|
||||
cachedHeight: PropTypes.number,
|
||||
onHeightChange: PropTypes.func,
|
||||
children: PropTypes.node,
|
||||
};
|
||||
|
||||
|
@ -34,13 +37,10 @@ export default class IntersectionObserverArticle extends ImmutablePureComponent
|
|||
}
|
||||
|
||||
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,
|
||||
const { intersectionObserverWrapper, id } = this.props;
|
||||
|
||||
intersectionObserverWrapper.observe(
|
||||
id,
|
||||
this.node,
|
||||
this.handleIntersection
|
||||
);
|
||||
|
@ -49,20 +49,21 @@ export default class IntersectionObserverArticle extends ImmutablePureComponent
|
|||
}
|
||||
|
||||
componentWillUnmount () {
|
||||
if (this.props.intersectionObserverWrapper) {
|
||||
this.props.intersectionObserverWrapper.unobserve(this.props.id, this.node);
|
||||
}
|
||||
const { intersectionObserverWrapper, id } = this.props;
|
||||
intersectionObserverWrapper.unobserve(id, this.node);
|
||||
|
||||
this.componentMounted = false;
|
||||
}
|
||||
|
||||
handleIntersection = (entry) => {
|
||||
const { onHeightChange, saveHeightKey, id } = this.props;
|
||||
|
||||
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);
|
||||
if (onHeightChange && saveHeightKey) {
|
||||
onHeightChange(saveHeightKey, id, this.height);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -94,16 +95,16 @@ export default class IntersectionObserverArticle extends ImmutablePureComponent
|
|||
}
|
||||
|
||||
render () {
|
||||
const { children, id, index, listLength } = this.props;
|
||||
const { children, id, index, listLength, cachedHeight } = this.props;
|
||||
const { isIntersecting, isHidden } = this.state;
|
||||
|
||||
if (!isIntersecting && isHidden) {
|
||||
if (!isIntersecting && (isHidden || cachedHeight)) {
|
||||
return (
|
||||
<article
|
||||
ref={this.handleRef}
|
||||
aria-posinset={index}
|
||||
aria-setsize={listLength}
|
||||
style={{ height: `${this.height}px`, opacity: 0, overflow: 'hidden' }}
|
||||
style={{ height: `${this.height || cachedHeight}px`, opacity: 0, overflow: 'hidden' }}
|
||||
data-id={id}
|
||||
tabIndex='0'
|
||||
>
|
||||
|
|
|
@ -17,7 +17,7 @@ export default class LoadMore extends React.PureComponent {
|
|||
const { visible } = this.props;
|
||||
|
||||
return (
|
||||
<button className='load-more' disabled={!visible} style={{ opacity: visible ? 1 : 0 }} onClick={this.props.onClick}>
|
||||
<button className='load-more' disabled={!visible} style={{ visibility: visible ? 'visible' : 'hidden' }} onClick={this.props.onClick}>
|
||||
<FormattedMessage id='status.load_more' defaultMessage='Load more' />
|
||||
</button>
|
||||
);
|
||||
|
|
|
@ -122,8 +122,8 @@ class Item extends React.PureComponent {
|
|||
|
||||
const hasSize = typeof originalWidth === 'number' && typeof previewWidth === 'number';
|
||||
|
||||
const srcSet = hasSize && `${originalUrl} ${originalWidth}w, ${previewUrl} ${previewWidth}w`;
|
||||
const sizes = hasSize && `(min-width: 1025px) ${320 * (width / 100)}px, ${width}vw`;
|
||||
const srcSet = hasSize ? `${originalUrl} ${originalWidth}w, ${previewUrl} ${previewWidth}w` : null;
|
||||
const sizes = hasSize ? `(min-width: 1025px) ${320 * (width / 100)}px, ${width}vw` : null;
|
||||
|
||||
thumbnail = (
|
||||
<a
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
import React, { PureComponent } from 'react';
|
||||
import { ScrollContainer } from 'react-router-scroll';
|
||||
import PropTypes from 'prop-types';
|
||||
import IntersectionObserverArticle from './intersection_observer_article';
|
||||
import IntersectionObserverArticleContainer from '../containers/intersection_observer_article_container';
|
||||
import LoadMore from './load_more';
|
||||
import IntersectionObserverWrapper from '../features/ui/util/intersection_observer_wrapper';
|
||||
import { throttle } from 'lodash';
|
||||
|
@ -9,6 +9,10 @@ import { List as ImmutableList } from 'immutable';
|
|||
|
||||
export default class ScrollableList extends PureComponent {
|
||||
|
||||
static contextTypes = {
|
||||
router: PropTypes.object,
|
||||
};
|
||||
|
||||
static propTypes = {
|
||||
scrollKey: PropTypes.string.isRequired,
|
||||
onScrollToBottom: PropTypes.func,
|
||||
|
@ -163,7 +167,7 @@ export default class ScrollableList extends PureComponent {
|
|||
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} />;
|
||||
const loadMore = (hasMore && childrenCount > 0) ? <LoadMore visible={!isLoading} onClick={this.handleLoadMore} /> : null;
|
||||
let scrollableArea = null;
|
||||
|
||||
if (isLoading || childrenCount > 0 || !emptyMessage) {
|
||||
|
@ -173,9 +177,16 @@ export default class ScrollableList extends PureComponent {
|
|||
{prepend}
|
||||
|
||||
{React.Children.map(this.props.children, (child, index) => (
|
||||
<IntersectionObserverArticle key={child.key} id={child.key} index={index} listLength={childrenCount} intersectionObserverWrapper={this.intersectionObserverWrapper}>
|
||||
<IntersectionObserverArticleContainer
|
||||
key={child.key}
|
||||
id={child.key}
|
||||
index={index}
|
||||
listLength={childrenCount}
|
||||
intersectionObserverWrapper={this.intersectionObserverWrapper}
|
||||
saveHeightKey={trackScroll ? `${this.context.router.route.location.key}:${scrollKey}` : null}
|
||||
>
|
||||
{child}
|
||||
</IntersectionObserverArticle>
|
||||
</IntersectionObserverArticleContainer>
|
||||
))}
|
||||
|
||||
{loadMore}
|
||||
|
|
|
@ -12,7 +12,7 @@ import StatusContent from './status_content';
|
|||
import StatusActionBar from './status_action_bar';
|
||||
import { FormattedMessage } from 'react-intl';
|
||||
import ImmutablePureComponent from 'react-immutable-pure-component';
|
||||
import { MediaGallery, VideoPlayer } from '../features/ui/util/async-components';
|
||||
import { MediaGallery, Video } from '../features/ui/util/async-components';
|
||||
|
||||
// We use the component (and not the container) since we do not want
|
||||
// to use the progress bar to show download progress
|
||||
|
@ -91,6 +91,10 @@ export default class Status extends ImmutablePureComponent {
|
|||
return <div className='media-spoiler-video' style={{ height: '110px' }} />;
|
||||
}
|
||||
|
||||
handleOpenVideo = startTime => {
|
||||
this.props.onOpenVideo(this.props.status.getIn(['media_attachments', 0]), startTime);
|
||||
}
|
||||
|
||||
render () {
|
||||
let media = null;
|
||||
let statusAvatar;
|
||||
|
@ -130,9 +134,18 @@ export default class Status extends ImmutablePureComponent {
|
|||
if (status.get('media_attachments').some(item => item.get('type') === 'unknown')) {
|
||||
|
||||
} else if (status.getIn(['media_attachments', 0, 'type']) === 'video') {
|
||||
const video = status.getIn(['media_attachments', 0]);
|
||||
|
||||
media = (
|
||||
<Bundle fetchComponent={VideoPlayer} loading={this.renderLoadingVideoPlayer} >
|
||||
{Component => <Component media={status.getIn(['media_attachments', 0])} sensitive={status.get('sensitive')} onOpenVideo={this.props.onOpenVideo} />}
|
||||
<Bundle fetchComponent={Video} loading={this.renderLoadingVideoPlayer} >
|
||||
{Component => <Component
|
||||
preview={video.get('preview_url')}
|
||||
src={video.get('url')}
|
||||
width={239}
|
||||
height={110}
|
||||
sensitive={status.get('sensitive')}
|
||||
onOpenVideo={this.handleOpenVideo}
|
||||
/>}
|
||||
</Bundle>
|
||||
);
|
||||
} else {
|
||||
|
|
|
@ -0,0 +1,18 @@
|
|||
import React from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import Card from '../features/status/components/card';
|
||||
import { fromJS } from 'immutable';
|
||||
|
||||
export default class CardContainer extends React.PureComponent {
|
||||
|
||||
static propTypes = {
|
||||
locale: PropTypes.string,
|
||||
card: PropTypes.array.isRequired,
|
||||
};
|
||||
|
||||
render () {
|
||||
const { card, ...props } = this.props;
|
||||
return <Card card={fromJS(card)} {...props} />;
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,17 @@
|
|||
import { connect } from 'react-redux';
|
||||
import IntersectionObserverArticle from '../components/intersection_observer_article';
|
||||
import { setHeight } from '../actions/height_cache';
|
||||
|
||||
const makeMapStateToProps = (state, props) => ({
|
||||
cachedHeight: state.getIn(['height_cache', props.saveHeightKey, props.id]),
|
||||
});
|
||||
|
||||
const mapDispatchToProps = (dispatch) => ({
|
||||
|
||||
onHeightChange (key, id, height) {
|
||||
dispatch(setHeight(key, id, height));
|
||||
},
|
||||
|
||||
});
|
||||
|
||||
export default connect(makeMapStateToProps, mapDispatchToProps)(IntersectionObserverArticle);
|
|
@ -0,0 +1,34 @@
|
|||
import React from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import { IntlProvider, addLocaleData } from 'react-intl';
|
||||
import { getLocale } from '../locales';
|
||||
import MediaGallery from '../components/media_gallery';
|
||||
import { fromJS } from 'immutable';
|
||||
|
||||
const { localeData, messages } = getLocale();
|
||||
addLocaleData(localeData);
|
||||
|
||||
export default class MediaGalleryContainer extends React.PureComponent {
|
||||
|
||||
static propTypes = {
|
||||
locale: PropTypes.string.isRequired,
|
||||
media: PropTypes.array.isRequired,
|
||||
};
|
||||
|
||||
handleOpenMedia = () => {}
|
||||
|
||||
render () {
|
||||
const { locale, media, ...props } = this.props;
|
||||
|
||||
return (
|
||||
<IntlProvider locale={locale} messages={messages}>
|
||||
<MediaGallery
|
||||
{...props}
|
||||
media={fromJS(media)}
|
||||
onOpenMedia={this.handleOpenMedia}
|
||||
/>
|
||||
</IntlProvider>
|
||||
);
|
||||
}
|
||||
|
||||
}
|
|
@ -21,7 +21,7 @@ import {
|
|||
blockAccount,
|
||||
muteAccount,
|
||||
} from '../actions/accounts';
|
||||
import { muteStatus, unmuteStatus, deleteStatus, setStatusHeight } from '../actions/statuses';
|
||||
import { muteStatus, unmuteStatus, deleteStatus } from '../actions/statuses';
|
||||
import { initReport } from '../actions/reports';
|
||||
import { openModal } from '../actions/modal';
|
||||
import { defineMessages, injectIntl, FormattedMessage } from 'react-intl';
|
||||
|
@ -141,10 +141,6 @@ const mapDispatchToProps = (dispatch, { intl }) => ({
|
|||
}
|
||||
},
|
||||
|
||||
onHeightChange (status, height) {
|
||||
dispatch(setStatusHeight(status.get('id'), height));
|
||||
},
|
||||
|
||||
});
|
||||
|
||||
export default injectIntl(connect(makeMapStateToProps, mapDispatchToProps)(Status));
|
||||
|
|
|
@ -0,0 +1,26 @@
|
|||
import React from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import { IntlProvider, addLocaleData } from 'react-intl';
|
||||
import { getLocale } from '../locales';
|
||||
import Video from '../features/video';
|
||||
|
||||
const { localeData, messages } = getLocale();
|
||||
addLocaleData(localeData);
|
||||
|
||||
export default class VideoContainer extends React.PureComponent {
|
||||
|
||||
static propTypes = {
|
||||
locale: PropTypes.string.isRequired,
|
||||
};
|
||||
|
||||
render () {
|
||||
const { locale, ...props } = this.props;
|
||||
|
||||
return (
|
||||
<IntlProvider locale={locale} messages={messages}>
|
||||
<Video {...props} />
|
||||
</IntlProvider>
|
||||
);
|
||||
}
|
||||
|
||||
}
|
|
@ -3,28 +3,48 @@ import Trie from 'substring-trie';
|
|||
|
||||
const trie = new Trie(Object.keys(unicodeMapping));
|
||||
|
||||
const emojify = str => {
|
||||
let rtn = '';
|
||||
for (;;) {
|
||||
let match, i = 0;
|
||||
while (i < str.length && str[i] !== '<' && !(match = trie.search(str.slice(i)))) {
|
||||
i += str.codePointAt(i) < 65536 ? 1 : 2;
|
||||
}
|
||||
if (i === str.length)
|
||||
break;
|
||||
else if (str[i] === '<') {
|
||||
let tagend = str.indexOf('>', i + 1) + 1;
|
||||
if (!tagend)
|
||||
break;
|
||||
rtn += str.slice(0, tagend);
|
||||
str = str.slice(tagend);
|
||||
const emojify = (str, customEmojis = {}) => {
|
||||
// This walks through the string from start to end, ignoring any tags (<p>, <br>, etc.)
|
||||
// and replacing valid unicode strings
|
||||
// that _aren't_ within tags with an <img> version.
|
||||
// The goal is to be the same as an emojione.regUnicode replacement, but faster.
|
||||
let i = -1;
|
||||
let insideTag = false;
|
||||
let insideShortname = false;
|
||||
let shortnameStartIndex = -1;
|
||||
let match;
|
||||
while (++i < str.length) {
|
||||
const char = str.charAt(i);
|
||||
if (insideShortname && char === ':') {
|
||||
const shortname = str.substring(shortnameStartIndex, i + 1);
|
||||
if (shortname in customEmojis) {
|
||||
const replacement = `<img draggable="false" class="emojione" alt="${shortname}" title="${shortname}" src="${customEmojis[shortname]}" />`;
|
||||
str = str.substring(0, shortnameStartIndex) + replacement + str.substring(i + 1);
|
||||
i += (replacement.length - shortname.length - 1); // jump ahead the length we've added to the string
|
||||
} else {
|
||||
const [filename, shortCode] = unicodeMapping[match];
|
||||
rtn += str.slice(0, i) + `<img draggable="false" class="emojione" alt="${match}" title=":${shortCode}:" src="/emoji/${filename}.svg" />`;
|
||||
str = str.slice(i + match.length);
|
||||
i--;
|
||||
}
|
||||
insideShortname = false;
|
||||
} else if (insideTag && char === '>') {
|
||||
insideTag = false;
|
||||
} else if (char === '<') {
|
||||
insideTag = true;
|
||||
insideShortname = false;
|
||||
} else if (!insideTag && char === ':') {
|
||||
insideShortname = true;
|
||||
shortnameStartIndex = i;
|
||||
} else if (!insideTag && (match = trie.search(str.substring(i)))) {
|
||||
const unicodeStr = match;
|
||||
if (unicodeStr in unicodeMapping) {
|
||||
const [filename, shortCode] = unicodeMapping[unicodeStr];
|
||||
const alt = unicodeStr;
|
||||
const replacement = `<img draggable="false" class="emojione" alt="${alt}" title=":${shortCode}:" src="/emoji/${filename}.svg" />`;
|
||||
str = str.substring(0, i) + replacement + str.substring(i + unicodeStr.length);
|
||||
i += (replacement.length - unicodeStr.length); // jump ahead the length we've added to the string
|
||||
}
|
||||
}
|
||||
return rtn + str;
|
||||
}
|
||||
return str;
|
||||
};
|
||||
|
||||
export default emojify;
|
||||
|
|
|
@ -1,7 +1,9 @@
|
|||
import { urlRegex } from './url_regex';
|
||||
|
||||
const urlPlaceholder = 'xxxxxxxxxxxxxxxxxxxxxxx';
|
||||
|
||||
export function countableText(inputText) {
|
||||
return inputText
|
||||
.replace(/https?:\/\/\S+/g, urlPlaceholder)
|
||||
.replace(urlRegex, urlPlaceholder)
|
||||
.replace(/(?:^|[^\/\w])@(([a-z0-9_]+)@[a-z0-9\.\-]+)/ig, '@$2');
|
||||
};
|
||||
|
|
|
@ -0,0 +1,196 @@
|
|||
const regexen = {};
|
||||
|
||||
const regexSupplant = function(regex, flags) {
|
||||
flags = flags || '';
|
||||
if (typeof regex !== 'string') {
|
||||
if (regex.global && flags.indexOf('g') < 0) {
|
||||
flags += 'g';
|
||||
}
|
||||
if (regex.ignoreCase && flags.indexOf('i') < 0) {
|
||||
flags += 'i';
|
||||
}
|
||||
if (regex.multiline && flags.indexOf('m') < 0) {
|
||||
flags += 'm';
|
||||
}
|
||||
|
||||
regex = regex.source;
|
||||
}
|
||||
return new RegExp(regex.replace(/#\{(\w+)\}/g, function(match, name) {
|
||||
var newRegex = regexen[name] || '';
|
||||
if (typeof newRegex !== 'string') {
|
||||
newRegex = newRegex.source;
|
||||
}
|
||||
return newRegex;
|
||||
}), flags);
|
||||
};
|
||||
|
||||
const stringSupplant = function(str, values) {
|
||||
return str.replace(/#\{(\w+)\}/g, function(match, name) {
|
||||
return values[name] || '';
|
||||
});
|
||||
};
|
||||
|
||||
export const urlRegex = (function() {
|
||||
regexen.spaces_group = /\x09-\x0D\x20\x85\xA0\u1680\u180E\u2000-\u200A\u2028\u2029\u202F\u205F\u3000/;
|
||||
regexen.invalid_chars_group = /\uFFFE\uFEFF\uFFFF\u202A-\u202E/;
|
||||
regexen.punct = /\!'#%&'\(\)*\+,\\\-\.\/:;<=>\?@\[\]\^_{|}~\$/;
|
||||
regexen.validUrlPrecedingChars = regexSupplant(/(?:[^A-Za-z0-9@@$###{invalid_chars_group}]|^)/);
|
||||
regexen.invalidDomainChars = stringSupplant('#{punct}#{spaces_group}#{invalid_chars_group}', regexen);
|
||||
regexen.validDomainChars = regexSupplant(/[^#{invalidDomainChars}]/);
|
||||
regexen.validSubdomain = regexSupplant(/(?:(?:#{validDomainChars}(?:[_-]|#{validDomainChars})*)?#{validDomainChars}\.)/);
|
||||
regexen.validDomainName = regexSupplant(/(?:(?:#{validDomainChars}(?:-|#{validDomainChars})*)?#{validDomainChars}\.)/);
|
||||
regexen.validGTLD = regexSupplant(RegExp(
|
||||
'(?:(?:' +
|
||||
'삼성|닷컴|닷넷|香格里拉|餐厅|食品|飞利浦|電訊盈科|集团|通販|购物|谷歌|诺基亚|联通|网络|网站|网店|网址|组织机构|移动|珠宝|点看|游戏|淡马锡|机构|書籍|时尚|新闻|政府|' +
|
||||
'政务|手表|手机|我爱你|慈善|微博|广东|工行|家電|娱乐|天主教|大拿|大众汽车|在线|嘉里大酒店|嘉里|商标|商店|商城|公益|公司|八卦|健康|信息|佛山|企业|中文网|中信|世界|' +
|
||||
'ポイント|ファッション|セール|ストア|コム|グーグル|クラウド|みんな|คอม|संगठन|नेट|कॉम|همراه|موقع|موبايلي|كوم|كاثوليك|عرب|شبكة|' +
|
||||
'بيتك|بازار|العليان|ارامكو|اتصالات|ابوظبي|קום|сайт|рус|орг|онлайн|москва|ком|католик|дети|' +
|
||||
'zuerich|zone|zippo|zip|zero|zara|zappos|yun|youtube|you|yokohama|yoga|yodobashi|yandex|yamaxun|' +
|
||||
'yahoo|yachts|xyz|xxx|xperia|xin|xihuan|xfinity|xerox|xbox|wtf|wtc|wow|world|works|work|woodside|' +
|
||||
'wolterskluwer|wme|winners|wine|windows|win|williamhill|wiki|wien|whoswho|weir|weibo|wedding|wed|' +
|
||||
'website|weber|webcam|weatherchannel|weather|watches|watch|warman|wanggou|wang|walter|walmart|' +
|
||||
'wales|vuelos|voyage|voto|voting|vote|volvo|volkswagen|vodka|vlaanderen|vivo|viva|vistaprint|' +
|
||||
'vista|vision|visa|virgin|vip|vin|villas|viking|vig|video|viajes|vet|versicherung|' +
|
||||
'vermögensberatung|vermögensberater|verisign|ventures|vegas|vanguard|vana|vacations|ups|uol|uno|' +
|
||||
'university|unicom|uconnect|ubs|ubank|tvs|tushu|tunes|tui|tube|trv|trust|travelersinsurance|' +
|
||||
'travelers|travelchannel|travel|training|trading|trade|toys|toyota|town|tours|total|toshiba|' +
|
||||
'toray|top|tools|tokyo|today|tmall|tkmaxx|tjx|tjmaxx|tirol|tires|tips|tiffany|tienda|tickets|' +
|
||||
'tiaa|theatre|theater|thd|teva|tennis|temasek|telefonica|telecity|tel|technology|tech|team|tdk|' +
|
||||
'tci|taxi|tax|tattoo|tatar|tatamotors|target|taobao|talk|taipei|tab|systems|symantec|sydney|' +
|
||||
'swiss|swiftcover|swatch|suzuki|surgery|surf|support|supply|supplies|sucks|style|study|studio|' +
|
||||
'stream|store|storage|stockholm|stcgroup|stc|statoil|statefarm|statebank|starhub|star|staples|' +
|
||||
'stada|srt|srl|spreadbetting|spot|spiegel|space|soy|sony|song|solutions|solar|sohu|software|' +
|
||||
'softbank|social|soccer|sncf|smile|smart|sling|skype|sky|skin|ski|site|singles|sina|silk|shriram|' +
|
||||
'showtime|show|shouji|shopping|shop|shoes|shiksha|shia|shell|shaw|sharp|shangrila|sfr|sexy|sex|' +
|
||||
'sew|seven|ses|services|sener|select|seek|security|secure|seat|search|scot|scor|scjohnson|' +
|
||||
'science|schwarz|schule|school|scholarships|schmidt|schaeffler|scb|sca|sbs|sbi|saxo|save|sas|' +
|
||||
'sarl|sapo|sap|sanofi|sandvikcoromant|sandvik|samsung|samsclub|salon|sale|sakura|safety|safe|' +
|
||||
'saarland|ryukyu|rwe|run|ruhr|rugby|rsvp|room|rogers|rodeo|rocks|rocher|rmit|rip|rio|ril|' +
|
||||
'rightathome|ricoh|richardli|rich|rexroth|reviews|review|restaurant|rest|republican|report|' +
|
||||
'repair|rentals|rent|ren|reliance|reit|reisen|reise|rehab|redumbrella|redstone|red|recipes|' +
|
||||
'realty|realtor|realestate|read|raid|radio|racing|qvc|quest|quebec|qpon|pwc|pub|prudential|pru|' +
|
||||
'protection|property|properties|promo|progressive|prof|productions|prod|pro|prime|press|praxi|' +
|
||||
'pramerica|post|porn|politie|poker|pohl|pnc|plus|plumbing|playstation|play|place|pizza|pioneer|' +
|
||||
'pink|ping|pin|pid|pictures|pictet|pics|piaget|physio|photos|photography|photo|phone|philips|phd|' +
|
||||
'pharmacy|pfizer|pet|pccw|pay|passagens|party|parts|partners|pars|paris|panerai|panasonic|' +
|
||||
'pamperedchef|page|ovh|ott|otsuka|osaka|origins|orientexpress|organic|org|orange|oracle|open|ooo|' +
|
||||
'onyourside|online|onl|ong|one|omega|ollo|oldnavy|olayangroup|olayan|okinawa|office|off|observer|' +
|
||||
'obi|nyc|ntt|nrw|nra|nowtv|nowruz|now|norton|northwesternmutual|nokia|nissay|nissan|ninja|nikon|' +
|
||||
'nike|nico|nhk|ngo|nfl|nexus|nextdirect|next|news|newholland|new|neustar|network|netflix|netbank|' +
|
||||
'net|nec|nba|navy|natura|nationwide|name|nagoya|nadex|nab|mutuelle|mutual|museum|mtr|mtpc|mtn|' +
|
||||
'msd|movistar|movie|mov|motorcycles|moto|moscow|mortgage|mormon|mopar|montblanc|monster|money|' +
|
||||
'monash|mom|moi|moe|moda|mobily|mobile|mobi|mma|mls|mlb|mitsubishi|mit|mint|mini|mil|microsoft|' +
|
||||
'miami|metlife|merckmsd|meo|menu|men|memorial|meme|melbourne|meet|media|med|mckinsey|mcdonalds|' +
|
||||
'mcd|mba|mattel|maserati|marshalls|marriott|markets|marketing|market|map|mango|management|man|' +
|
||||
'makeup|maison|maif|madrid|macys|luxury|luxe|lupin|lundbeck|ltda|ltd|lplfinancial|lpl|love|lotto|' +
|
||||
'lotte|london|lol|loft|locus|locker|loans|loan|lixil|living|live|lipsy|link|linde|lincoln|limo|' +
|
||||
'limited|lilly|like|lighting|lifestyle|lifeinsurance|life|lidl|liaison|lgbt|lexus|lego|legal|' +
|
||||
'lefrak|leclerc|lease|lds|lawyer|law|latrobe|latino|lat|lasalle|lanxess|landrover|land|lancome|' +
|
||||
'lancia|lancaster|lamer|lamborghini|ladbrokes|lacaixa|kyoto|kuokgroup|kred|krd|kpn|kpmg|kosher|' +
|
||||
'komatsu|koeln|kiwi|kitchen|kindle|kinder|kim|kia|kfh|kerryproperties|kerrylogistics|kerryhotels|' +
|
||||
'kddi|kaufen|juniper|juegos|jprs|jpmorgan|joy|jot|joburg|jobs|jnj|jmp|jll|jlc|jio|jewelry|jetzt|' +
|
||||
'jeep|jcp|jcb|java|jaguar|iwc|iveco|itv|itau|istanbul|ist|ismaili|iselect|irish|ipiranga|' +
|
||||
'investments|intuit|international|intel|int|insure|insurance|institute|ink|ing|info|infiniti|' +
|
||||
'industries|immobilien|immo|imdb|imamat|ikano|iinet|ifm|ieee|icu|ice|icbc|ibm|hyundai|hyatt|' +
|
||||
'hughes|htc|hsbc|how|house|hotmail|hotels|hoteles|hot|hosting|host|hospital|horse|honeywell|' +
|
||||
'honda|homesense|homes|homegoods|homedepot|holiday|holdings|hockey|hkt|hiv|hitachi|hisamitsu|' +
|
||||
'hiphop|hgtv|hermes|here|helsinki|help|healthcare|health|hdfcbank|hdfc|hbo|haus|hangout|hamburg|' +
|
||||
'hair|guru|guitars|guide|guge|gucci|guardian|group|grocery|gripe|green|gratis|graphics|grainger|' +
|
||||
'gov|got|gop|google|goog|goodyear|goodhands|goo|golf|goldpoint|gold|godaddy|gmx|gmo|gmbh|gmail|' +
|
||||
'globo|global|gle|glass|glade|giving|gives|gifts|gift|ggee|george|genting|gent|gea|gdn|gbiz|' +
|
||||
'garden|gap|games|game|gallup|gallo|gallery|gal|fyi|futbol|furniture|fund|fun|fujixerox|fujitsu|' +
|
||||
'ftr|frontier|frontdoor|frogans|frl|fresenius|free|fox|foundation|forum|forsale|forex|ford|' +
|
||||
'football|foodnetwork|food|foo|fly|flsmidth|flowers|florist|flir|flights|flickr|fitness|fit|' +
|
||||
'fishing|fish|firmdale|firestone|fire|financial|finance|final|film|fido|fidelity|fiat|ferrero|' +
|
||||
'ferrari|feedback|fedex|fast|fashion|farmers|farm|fans|fan|family|faith|fairwinds|fail|fage|' +
|
||||
'extraspace|express|exposed|expert|exchange|everbank|events|eus|eurovision|etisalat|esurance|' +
|
||||
'estate|esq|erni|ericsson|equipment|epson|epost|enterprises|engineering|engineer|energy|emerck|' +
|
||||
'email|education|edu|edeka|eco|eat|earth|dvr|dvag|durban|dupont|duns|dunlop|duck|dubai|dtv|drive|' +
|
||||
'download|dot|doosan|domains|doha|dog|dodge|doctor|docs|dnp|diy|dish|discover|discount|directory|' +
|
||||
'direct|digital|diet|diamonds|dhl|dev|design|desi|dentist|dental|democrat|delta|deloitte|dell|' +
|
||||
'delivery|degree|deals|dealer|deal|dds|dclk|day|datsun|dating|date|data|dance|dad|dabur|cyou|' +
|
||||
'cymru|cuisinella|csc|cruises|cruise|crs|crown|cricket|creditunion|creditcard|credit|courses|' +
|
||||
'coupons|coupon|country|corsica|coop|cool|cookingchannel|cooking|contractors|contact|consulting|' +
|
||||
'construction|condos|comsec|computer|compare|company|community|commbank|comcast|com|cologne|' +
|
||||
'college|coffee|codes|coach|clubmed|club|cloud|clothing|clinique|clinic|click|cleaning|claims|' +
|
||||
'cityeats|city|citic|citi|citadel|cisco|circle|cipriani|church|chrysler|chrome|christmas|chloe|' +
|
||||
'chintai|cheap|chat|chase|channel|chanel|cfd|cfa|cern|ceo|center|ceb|cbs|cbre|cbn|cba|catholic|' +
|
||||
'catering|cat|casino|cash|caseih|case|casa|cartier|cars|careers|career|care|cards|caravan|car|' +
|
||||
'capitalone|capital|capetown|canon|cancerresearch|camp|camera|cam|calvinklein|call|cal|cafe|cab|' +
|
||||
'bzh|buzz|buy|business|builders|build|bugatti|budapest|brussels|brother|broker|broadway|' +
|
||||
'bridgestone|bradesco|box|boutique|bot|boston|bostik|bosch|boots|booking|book|boo|bond|bom|bofa|' +
|
||||
'boehringer|boats|bnpparibas|bnl|bmw|bms|blue|bloomberg|blog|blockbuster|blanco|blackfriday|' +
|
||||
'black|biz|bio|bingo|bing|bike|bid|bible|bharti|bet|bestbuy|best|berlin|bentley|beer|beauty|' +
|
||||
'beats|bcn|bcg|bbva|bbt|bbc|bayern|bauhaus|basketball|baseball|bargains|barefoot|barclays|' +
|
||||
'barclaycard|barcelona|bar|bank|band|bananarepublic|banamex|baidu|baby|azure|axa|aws|avianca|' +
|
||||
'autos|auto|author|auspost|audio|audible|audi|auction|attorney|athleta|associates|asia|asda|arte|' +
|
||||
'art|arpa|army|archi|aramco|arab|aquarelle|apple|app|apartments|aol|anz|anquan|android|analytics|' +
|
||||
'amsterdam|amica|amfam|amex|americanfamily|americanexpress|alstom|alsace|ally|allstate|allfinanz|' +
|
||||
'alipay|alibaba|alfaromeo|akdn|airtel|airforce|airbus|aigo|aig|agency|agakhan|africa|afl|' +
|
||||
'afamilycompany|aetna|aero|aeg|adult|ads|adac|actor|active|aco|accountants|accountant|accenture|' +
|
||||
'academy|abudhabi|abogado|able|abc|abbvie|abbott|abb|abarth|aarp|aaa|onion' +
|
||||
')(?=[^0-9a-zA-Z@]|$))'));
|
||||
regexen.validCCTLD = regexSupplant(RegExp(
|
||||
'(?:(?:' +
|
||||
'한국|香港|澳門|新加坡|台灣|台湾|中國|中国|გე|ไทย|ලංකා|ഭാരതം|ಭಾರತ|భారత్|சிங்கப்பூர்|இலங்கை|இந்தியா|ଭାରତ|ભારત|ਭਾਰਤ|' +
|
||||
'ভাৰত|ভারত|বাংলা|भारोत|भारतम्|भारत|ڀارت|پاکستان|مليسيا|مصر|قطر|فلسطين|عمان|عراق|سورية|سودان|تونس|' +
|
||||
'بھارت|بارت|ایران|امارات|المغرب|السعودية|الجزائر|الاردن|հայ|қаз|укр|срб|рф|мон|мкд|ею|бел|бг|ελ|' +
|
||||
'zw|zm|za|yt|ye|ws|wf|vu|vn|vi|vg|ve|vc|va|uz|uy|us|um|uk|ug|ua|tz|tw|tv|tt|tr|tp|to|tn|tm|tl|tk|' +
|
||||
'tj|th|tg|tf|td|tc|sz|sy|sx|sv|su|st|ss|sr|so|sn|sm|sl|sk|sj|si|sh|sg|se|sd|sc|sb|sa|rw|ru|rs|ro|' +
|
||||
're|qa|py|pw|pt|ps|pr|pn|pm|pl|pk|ph|pg|pf|pe|pa|om|nz|nu|nr|np|no|nl|ni|ng|nf|ne|nc|na|mz|my|mx|' +
|
||||
'mw|mv|mu|mt|ms|mr|mq|mp|mo|mn|mm|ml|mk|mh|mg|mf|me|md|mc|ma|ly|lv|lu|lt|ls|lr|lk|li|lc|lb|la|kz|' +
|
||||
'ky|kw|kr|kp|kn|km|ki|kh|kg|ke|jp|jo|jm|je|it|is|ir|iq|io|in|im|il|ie|id|hu|ht|hr|hn|hm|hk|gy|gw|' +
|
||||
'gu|gt|gs|gr|gq|gp|gn|gm|gl|gi|gh|gg|gf|ge|gd|gb|ga|fr|fo|fm|fk|fj|fi|eu|et|es|er|eh|eg|ee|ec|dz|' +
|
||||
'do|dm|dk|dj|de|cz|cy|cx|cw|cv|cu|cr|co|cn|cm|cl|ck|ci|ch|cg|cf|cd|cc|ca|bz|by|bw|bv|bt|bs|br|bq|' +
|
||||
'bo|bn|bm|bl|bj|bi|bh|bg|bf|be|bd|bb|ba|az|ax|aw|au|at|as|ar|aq|ao|an|am|al|ai|ag|af|ae|ad|ac' +
|
||||
')(?=[^0-9a-zA-Z@]|$))'));
|
||||
regexen.validPunycode = /(?:xn--[0-9a-z]+)/;
|
||||
regexen.validSpecialCCTLD = /(?:(?:co|tv)(?=[^0-9a-zA-Z@]|$))/;
|
||||
regexen.validDomain = regexSupplant(/(?:#{validSubdomain}*#{validDomainName}(?:#{validGTLD}|#{validCCTLD}|#{validPunycode}))/);
|
||||
regexen.validPortNumber = /[0-9]+/;
|
||||
regexen.pd = /\u002d\u058a\u05be\u1400\u1806\u2010-\u2015\u2e17\u2e1a\u2e3a\u2e40\u301c\u3030\u30a0\ufe31\ufe58\ufe63\uff0d/;
|
||||
regexen.validGeneralUrlPathChars = regexSupplant(/[^#{spaces_group}\(\)\?]/i);
|
||||
// Allow URL paths to contain up to two nested levels of balanced parens
|
||||
// 1. Used in Wikipedia URLs like /Primer_(film)
|
||||
// 2. Used in IIS sessions like /S(dfd346)/
|
||||
// 3. Used in Rdio URLs like /track/We_Up_(Album_Version_(Edited))/
|
||||
regexen.validUrlBalancedParens = regexSupplant(
|
||||
'\\(' +
|
||||
'(?:' +
|
||||
'#{validGeneralUrlPathChars}+' +
|
||||
'|' +
|
||||
// allow one nested level of balanced parentheses
|
||||
'(?:' +
|
||||
'#{validGeneralUrlPathChars}*' +
|
||||
'\\(' +
|
||||
'#{validGeneralUrlPathChars}+' +
|
||||
'\\)' +
|
||||
'#{validGeneralUrlPathChars}*' +
|
||||
')' +
|
||||
')' +
|
||||
'\\)'
|
||||
, 'i');
|
||||
// Valid end-of-path chracters (so /foo. does not gobble the period).
|
||||
// 1. Allow =&# for empty URL parameters and other URL-join artifacts
|
||||
regexen.validUrlPathEndingChars = regexSupplant(/[^#{spaces_group}\(\)\?!\*';:=\,\.\$%\[\]#{pd}~&\|@]|(?:#{validUrlBalancedParens})/i);
|
||||
// Allow @ in a url, but only in the middle. Catch things like http://example.com/@user/
|
||||
regexen.validUrlPath = regexSupplant('(?:' +
|
||||
'(?:' +
|
||||
'#{validGeneralUrlPathChars}*' +
|
||||
'(?:#{validUrlBalancedParens}#{validGeneralUrlPathChars}*)*' +
|
||||
'#{validUrlPathEndingChars}'+
|
||||
')|(?:@#{validGeneralUrlPathChars}+\/)'+
|
||||
')', 'i');
|
||||
regexen.validUrlQueryChars = /[a-z0-9!?\*'@\(\);:&=\+\$\/%#\[\]\-_\.,~|]/i;
|
||||
regexen.validUrlQueryEndingChars = /[a-z0-9_&=#\/]/i;
|
||||
regexen.validUrl = regexSupplant(
|
||||
'(' + // $1 URL
|
||||
'(https?:\\/\\/)' + // $2 Protocol
|
||||
'(#{validDomain})' + // $3 Domain(s)
|
||||
'(?::(#{validPortNumber}))?' + // $4 Port number (optional)
|
||||
'(\\/#{validUrlPath}*)?' + // $5 URL Path
|
||||
'(\\?#{validUrlQueryChars}*#{validUrlQueryEndingChars})?' + // $6 Query String
|
||||
')'
|
||||
, 'gi');
|
||||
return regexen.validUrl;
|
||||
}());
|
|
@ -2,6 +2,7 @@ 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';
|
||||
import ModalContainer from '../../ui/containers/modal_container';
|
||||
|
||||
export default class Compose extends React.PureComponent {
|
||||
|
||||
|
@ -10,6 +11,7 @@ export default class Compose extends React.PureComponent {
|
|||
<div>
|
||||
<ComposeFormContainer />
|
||||
<NotificationsContainer />
|
||||
<ModalContainer />
|
||||
<LoadingBarContainer className='loading-bar' />
|
||||
</div>
|
||||
);
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
import React from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import ImmutablePropTypes from 'react-immutable-proptypes';
|
||||
import punycode from 'punycode';
|
||||
import classnames from 'classnames';
|
||||
|
@ -22,10 +23,15 @@ export default class Card extends React.PureComponent {
|
|||
|
||||
static propTypes = {
|
||||
card: ImmutablePropTypes.map,
|
||||
maxDescription: PropTypes.number,
|
||||
};
|
||||
|
||||
static defaultProps = {
|
||||
maxDescription: 50,
|
||||
};
|
||||
|
||||
renderLink () {
|
||||
const { card } = this.props;
|
||||
const { card, maxDescription } = this.props;
|
||||
|
||||
let image = '';
|
||||
let provider = card.get('provider_name');
|
||||
|
@ -52,7 +58,7 @@ export default class Card extends React.PureComponent {
|
|||
|
||||
<div className='status-card__content'>
|
||||
<strong className='status-card__title' title={card.get('title')}>{card.get('title')}</strong>
|
||||
<p className='status-card__description'>{(card.get('description') || '').substring(0, 50)}</p>
|
||||
<p className='status-card__description'>{(card.get('description') || '').substring(0, maxDescription)}</p>
|
||||
<span className='status-card__host'>{provider}</span>
|
||||
</div>
|
||||
</a>
|
||||
|
|
|
@ -11,6 +11,7 @@ import Link from 'react-router-dom/Link';
|
|||
import { FormattedDate, FormattedNumber } from 'react-intl';
|
||||
import CardContainer from '../containers/card_container';
|
||||
import ImmutablePureComponent from 'react-immutable-pure-component';
|
||||
import Video from '../../video';
|
||||
import VisibilityIcon from '../../../../glitch/components/status/visibility_icon';
|
||||
|
||||
export default class DetailedStatus extends ImmutablePureComponent {
|
||||
|
@ -36,6 +37,10 @@ export default class DetailedStatus extends ImmutablePureComponent {
|
|||
e.stopPropagation();
|
||||
}
|
||||
|
||||
handleOpenVideo = startTime => {
|
||||
this.props.onOpenVideo(this.props.status.getIn(['media_attachments', 0]), startTime);
|
||||
}
|
||||
|
||||
render () {
|
||||
const status = this.props.status.get('reblog') ? this.props.status.get('reblog') : this.props.status;
|
||||
const { settings } = this.props;
|
||||
|
|
|
@ -78,7 +78,7 @@ export default class ColumnsArea extends ImmutablePureComponent {
|
|||
|
||||
handleChildrenContentChange() {
|
||||
if (!this.props.singleColumn) {
|
||||
scrollRight(this.node, this.node.scrollWidth - window.innerWidth);
|
||||
this._interruptScrollAnimation = scrollRight(this.node, this.node.scrollWidth - window.innerWidth);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -1,35 +1,29 @@
|
|||
import React from 'react';
|
||||
import ImmutablePropTypes from 'react-immutable-proptypes';
|
||||
import PropTypes from 'prop-types';
|
||||
import ExtendedVideoPlayer from '../../../components/extended_video_player';
|
||||
import { defineMessages, injectIntl } from 'react-intl';
|
||||
import IconButton from '../../../components/icon_button';
|
||||
import Video from '../../video';
|
||||
import ImmutablePureComponent from 'react-immutable-pure-component';
|
||||
|
||||
const messages = defineMessages({
|
||||
close: { id: 'lightbox.close', defaultMessage: 'Close' },
|
||||
});
|
||||
|
||||
@injectIntl
|
||||
export default class VideoModal extends ImmutablePureComponent {
|
||||
|
||||
static propTypes = {
|
||||
media: ImmutablePropTypes.map.isRequired,
|
||||
time: PropTypes.number,
|
||||
onClose: PropTypes.func.isRequired,
|
||||
intl: PropTypes.object.isRequired,
|
||||
};
|
||||
|
||||
render () {
|
||||
const { media, intl, time, onClose } = this.props;
|
||||
|
||||
const url = media.get('url');
|
||||
const { media, time, onClose } = this.props;
|
||||
|
||||
return (
|
||||
<div className='modal-root__modal media-modal'>
|
||||
<div>
|
||||
<div className='media-modal__close'><IconButton title={intl.formatMessage(messages.close)} icon='times' overlay onClick={onClose} /></div>
|
||||
<ExtendedVideoPlayer src={url} muted={false} controls time={time} />
|
||||
<Video
|
||||
preview={media.get('preview_url')}
|
||||
src={media.get('url')}
|
||||
startTime={time}
|
||||
onCloseVideo={onClose}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
|
|
|
@ -11,7 +11,7 @@ import { debounce } from 'lodash';
|
|||
import { uploadCompose } from '../../actions/compose';
|
||||
import { refreshHomeTimeline } from '../../actions/timelines';
|
||||
import { refreshNotifications } from '../../actions/notifications';
|
||||
import { clearStatusesHeight } from '../../actions/statuses';
|
||||
import { clearHeight } from '../../actions/height_cache';
|
||||
import { WrappedSwitch, WrappedRoute } from './util/react_router_helpers';
|
||||
import UploadArea from './components/upload_area';
|
||||
import ColumnsAreaContainer from './containers/columns_area_container';
|
||||
|
@ -77,7 +77,7 @@ export default class UI extends React.PureComponent {
|
|||
|
||||
handleResize = debounce(() => {
|
||||
// The cached heights are no longer accurate, invalidate
|
||||
this.props.dispatch(clearStatusesHeight());
|
||||
this.props.dispatch(clearHeight());
|
||||
|
||||
this.setState({ width: window.innerWidth });
|
||||
}, 500, {
|
||||
|
|
|
@ -109,6 +109,10 @@ export function VideoPlayer () {
|
|||
return import(/* webpackChunkName: "status/video_player" */'../../../components/video_player');
|
||||
}
|
||||
|
||||
export function Video () {
|
||||
return import(/* webpackChunkName: "features/video" */'../../video');
|
||||
}
|
||||
|
||||
export function EmbedModal () {
|
||||
return import(/* webpackChunkName: "modals/embed_modal" */'../components/embed_modal');
|
||||
}
|
||||
|
|
|
@ -0,0 +1,304 @@
|
|||
import React from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import { defineMessages, injectIntl, FormattedMessage } from 'react-intl';
|
||||
import { throttle } from 'lodash';
|
||||
import classNames from 'classnames';
|
||||
|
||||
const messages = defineMessages({
|
||||
play: { id: 'video.play', defaultMessage: 'Play' },
|
||||
pause: { id: 'video.pause', defaultMessage: 'Pause' },
|
||||
mute: { id: 'video.mute', defaultMessage: 'Mute sound' },
|
||||
unmute: { id: 'video.unmute', defaultMessage: 'Unmute sound' },
|
||||
hide: { id: 'video.hide', defaultMessage: 'Hide video' },
|
||||
expand: { id: 'video.expand', defaultMessage: 'Expand video' },
|
||||
close: { id: 'video.close', defaultMessage: 'Close video' },
|
||||
fullscreen: { id: 'video.fullscreen', defaultMessage: 'Full screen' },
|
||||
exit_fullscreen: { id: 'video.exit_fullscreen', defaultMessage: 'Exit full screen' },
|
||||
});
|
||||
|
||||
const findElementPosition = el => {
|
||||
let box;
|
||||
|
||||
if (el.getBoundingClientRect && el.parentNode) {
|
||||
box = el.getBoundingClientRect();
|
||||
}
|
||||
|
||||
if (!box) {
|
||||
return {
|
||||
left: 0,
|
||||
top: 0,
|
||||
};
|
||||
}
|
||||
|
||||
const docEl = document.documentElement;
|
||||
const body = document.body;
|
||||
|
||||
const clientLeft = docEl.clientLeft || body.clientLeft || 0;
|
||||
const scrollLeft = window.pageXOffset || body.scrollLeft;
|
||||
const left = (box.left + scrollLeft) - clientLeft;
|
||||
|
||||
const clientTop = docEl.clientTop || body.clientTop || 0;
|
||||
const scrollTop = window.pageYOffset || body.scrollTop;
|
||||
const top = (box.top + scrollTop) - clientTop;
|
||||
|
||||
return {
|
||||
left: Math.round(left),
|
||||
top: Math.round(top),
|
||||
};
|
||||
};
|
||||
|
||||
const getPointerPosition = (el, event) => {
|
||||
const position = {};
|
||||
const box = findElementPosition(el);
|
||||
const boxW = el.offsetWidth;
|
||||
const boxH = el.offsetHeight;
|
||||
const boxY = box.top;
|
||||
const boxX = box.left;
|
||||
|
||||
let pageY = event.pageY;
|
||||
let pageX = event.pageX;
|
||||
|
||||
if (event.changedTouches) {
|
||||
pageX = event.changedTouches[0].pageX;
|
||||
pageY = event.changedTouches[0].pageY;
|
||||
}
|
||||
|
||||
position.y = Math.max(0, Math.min(1, ((boxY - pageY) + boxH) / boxH));
|
||||
position.x = Math.max(0, Math.min(1, (pageX - boxX) / boxW));
|
||||
|
||||
return position;
|
||||
};
|
||||
|
||||
const isFullscreen = () => document.fullscreenElement ||
|
||||
document.webkitFullscreenElement ||
|
||||
document.mozFullScreenElement ||
|
||||
document.msFullscreenElement;
|
||||
|
||||
const exitFullscreen = () => {
|
||||
if (document.exitFullscreen) {
|
||||
document.exitFullscreen();
|
||||
} else if (document.webkitExitFullscreen) {
|
||||
document.webkitExitFullscreen();
|
||||
} else if (document.mozCancelFullScreen) {
|
||||
document.mozCancelFullScreen();
|
||||
} else if (document.msExitFullscreen) {
|
||||
document.msExitFullscreen();
|
||||
}
|
||||
};
|
||||
|
||||
const requestFullscreen = el => {
|
||||
if (el.requestFullscreen) {
|
||||
el.requestFullscreen();
|
||||
} else if (el.webkitRequestFullscreen) {
|
||||
el.webkitRequestFullscreen();
|
||||
} else if (el.mozRequestFullScreen) {
|
||||
el.mozRequestFullScreen();
|
||||
} else if (el.msRequestFullscreen) {
|
||||
el.msRequestFullscreen();
|
||||
}
|
||||
};
|
||||
|
||||
@injectIntl
|
||||
export default class Video extends React.PureComponent {
|
||||
|
||||
static propTypes = {
|
||||
preview: PropTypes.string,
|
||||
src: PropTypes.string.isRequired,
|
||||
width: PropTypes.number,
|
||||
height: PropTypes.number,
|
||||
sensitive: PropTypes.bool,
|
||||
startTime: PropTypes.number,
|
||||
onOpenVideo: PropTypes.func,
|
||||
onCloseVideo: PropTypes.func,
|
||||
intl: PropTypes.object.isRequired,
|
||||
};
|
||||
|
||||
state = {
|
||||
progress: 0,
|
||||
paused: true,
|
||||
dragging: false,
|
||||
fullscreen: false,
|
||||
hovered: false,
|
||||
muted: false,
|
||||
revealed: !this.props.sensitive,
|
||||
};
|
||||
|
||||
setPlayerRef = c => {
|
||||
this.player = c;
|
||||
}
|
||||
|
||||
setVideoRef = c => {
|
||||
this.video = c;
|
||||
}
|
||||
|
||||
setSeekRef = c => {
|
||||
this.seek = c;
|
||||
}
|
||||
|
||||
handlePlay = () => {
|
||||
this.setState({ paused: false });
|
||||
}
|
||||
|
||||
handlePause = () => {
|
||||
this.setState({ paused: true });
|
||||
}
|
||||
|
||||
handleTimeUpdate = () => {
|
||||
this.setState({ progress: 100 * (this.video.currentTime / this.video.duration) });
|
||||
}
|
||||
|
||||
handleMouseDown = e => {
|
||||
document.addEventListener('mousemove', this.handleMouseMove, true);
|
||||
document.addEventListener('mouseup', this.handleMouseUp, true);
|
||||
document.addEventListener('touchmove', this.handleMouseMove, true);
|
||||
document.addEventListener('touchend', this.handleMouseUp, true);
|
||||
|
||||
this.setState({ dragging: true });
|
||||
this.video.pause();
|
||||
this.handleMouseMove(e);
|
||||
}
|
||||
|
||||
handleMouseUp = () => {
|
||||
document.removeEventListener('mousemove', this.handleMouseMove, true);
|
||||
document.removeEventListener('mouseup', this.handleMouseUp, true);
|
||||
document.removeEventListener('touchmove', this.handleMouseMove, true);
|
||||
document.removeEventListener('touchend', this.handleMouseUp, true);
|
||||
|
||||
this.setState({ dragging: false });
|
||||
this.video.play();
|
||||
}
|
||||
|
||||
handleMouseMove = throttle(e => {
|
||||
const { x } = getPointerPosition(this.seek, e);
|
||||
this.video.currentTime = this.video.duration * x;
|
||||
this.setState({ progress: x * 100 });
|
||||
}, 60);
|
||||
|
||||
togglePlay = () => {
|
||||
if (this.state.paused) {
|
||||
this.video.play();
|
||||
} else {
|
||||
this.video.pause();
|
||||
}
|
||||
}
|
||||
|
||||
toggleFullscreen = () => {
|
||||
if (isFullscreen()) {
|
||||
exitFullscreen();
|
||||
} else {
|
||||
requestFullscreen(this.player);
|
||||
}
|
||||
}
|
||||
|
||||
componentDidMount () {
|
||||
document.addEventListener('fullscreenchange', this.handleFullscreenChange, true);
|
||||
document.addEventListener('webkitfullscreenchange', this.handleFullscreenChange, true);
|
||||
document.addEventListener('mozfullscreenchange', this.handleFullscreenChange, true);
|
||||
document.addEventListener('MSFullscreenChange', this.handleFullscreenChange, true);
|
||||
}
|
||||
|
||||
componentWillUnmount () {
|
||||
document.removeEventListener('fullscreenchange', this.handleFullscreenChange, true);
|
||||
document.removeEventListener('webkitfullscreenchange', this.handleFullscreenChange, true);
|
||||
document.removeEventListener('mozfullscreenchange', this.handleFullscreenChange, true);
|
||||
document.removeEventListener('MSFullscreenChange', this.handleFullscreenChange, true);
|
||||
}
|
||||
|
||||
handleFullscreenChange = () => {
|
||||
this.setState({ fullscreen: isFullscreen() });
|
||||
}
|
||||
|
||||
handleMouseEnter = () => {
|
||||
this.setState({ hovered: true });
|
||||
}
|
||||
|
||||
handleMouseLeave = () => {
|
||||
this.setState({ hovered: false });
|
||||
}
|
||||
|
||||
toggleMute = () => {
|
||||
this.video.muted = !this.video.muted;
|
||||
this.setState({ muted: this.video.muted });
|
||||
}
|
||||
|
||||
toggleReveal = () => {
|
||||
if (this.state.revealed) {
|
||||
this.video.pause();
|
||||
}
|
||||
|
||||
this.setState({ revealed: !this.state.revealed });
|
||||
}
|
||||
|
||||
handleLoadedData = () => {
|
||||
if (this.props.startTime) {
|
||||
this.video.currentTime = this.props.startTime;
|
||||
this.video.play();
|
||||
}
|
||||
}
|
||||
|
||||
handleOpenVideo = () => {
|
||||
this.video.pause();
|
||||
this.props.onOpenVideo(this.video.currentTime);
|
||||
}
|
||||
|
||||
handleCloseVideo = () => {
|
||||
this.video.pause();
|
||||
this.props.onCloseVideo();
|
||||
}
|
||||
|
||||
render () {
|
||||
const { preview, src, width, height, startTime, onOpenVideo, onCloseVideo, intl } = this.props;
|
||||
const { progress, dragging, paused, fullscreen, hovered, muted, revealed } = this.state;
|
||||
|
||||
return (
|
||||
<div className={classNames('video-player', { inactive: !revealed, inline: width && height && !fullscreen, fullscreen })} style={{ width, height }} ref={this.setPlayerRef} onMouseEnter={this.handleMouseEnter} onMouseLeave={this.handleMouseLeave}>
|
||||
<video
|
||||
ref={this.setVideoRef}
|
||||
src={src}
|
||||
poster={preview}
|
||||
preload={!!startTime}
|
||||
loop
|
||||
role='button'
|
||||
tabIndex='0'
|
||||
width={width}
|
||||
height={height}
|
||||
onClick={this.togglePlay}
|
||||
onPlay={this.handlePlay}
|
||||
onPause={this.handlePause}
|
||||
onTimeUpdate={this.handleTimeUpdate}
|
||||
onLoadedData={this.handleLoadedData}
|
||||
/>
|
||||
|
||||
<button className={classNames('video-player__spoiler', { active: !revealed })} onClick={this.toggleReveal}>
|
||||
<span className='video-player__spoiler__title'><FormattedMessage id='status.sensitive_warning' defaultMessage='Sensitive content' /></span>
|
||||
<span className='video-player__spoiler__subtitle'><FormattedMessage id='status.sensitive_toggle' defaultMessage='Click to view' /></span>
|
||||
</button>
|
||||
|
||||
<div className={classNames('video-player__controls', { active: paused || hovered })}>
|
||||
<div className='video-player__seek' onMouseDown={this.handleMouseDown} ref={this.setSeekRef}>
|
||||
<div className='video-player__seek__progress' style={{ width: `${progress}%` }} />
|
||||
|
||||
<span
|
||||
className={classNames('video-player__seek__handle', { active: dragging })}
|
||||
tabIndex='0'
|
||||
style={{ left: `${progress}%` }}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div className='video-player__buttons left'>
|
||||
<button aria-label={intl.formatMessage(paused ? messages.play : messages.pause)} onClick={this.togglePlay}><i className={classNames('fa fa-fw', { 'fa-play': paused, 'fa-pause': !paused })} /></button>
|
||||
<button aria-label={intl.formatMessage(muted ? messages.unmute : messages.mute)} onClick={this.toggleMute}><i className={classNames('fa fa-fw', { 'fa-volume-off': muted, 'fa-volume-up': !muted })} /></button>
|
||||
{!onCloseVideo && <button aria-label={intl.formatMessage(messages.hide)} onClick={this.toggleReveal}><i className='fa fa-fw fa-eye' /></button>}
|
||||
</div>
|
||||
|
||||
<div className='video-player__buttons right'>
|
||||
{(!fullscreen && onOpenVideo) && <button aria-label={intl.formatMessage(messages.expand)} onClick={this.handleOpenVideo}><i className='fa fa-fw fa-expand' /></button>}
|
||||
{onCloseVideo && <button aria-label={intl.formatMessage(messages.close)} onClick={this.handleCloseVideo}><i className='fa fa-fw fa-times' /></button>}
|
||||
<button aria-label={intl.formatMessage(fullscreen ? messages.exit_fullscreen : messages.fullscreen)} onClick={this.toggleFullscreen}><i className={classNames('fa fa-fw', { 'fa-arrows-alt': !fullscreen, 'fa-compress': fullscreen })} /></button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
}
|
|
@ -33,6 +33,7 @@
|
|||
"column.home": "الرئيسية",
|
||||
"column.mutes": "الحسابات المكتومة",
|
||||
"column.notifications": "الإشعارات",
|
||||
"column.pins": "Pinned toot",
|
||||
"column.public": "الخيط العام الموحد",
|
||||
"column_back_button.label": "العودة",
|
||||
"column_header.hide_settings": "Hide settings",
|
||||
|
@ -109,6 +110,7 @@
|
|||
"navigation_bar.info": "معلومات إضافية",
|
||||
"navigation_bar.logout": "خروج",
|
||||
"navigation_bar.mutes": "الحسابات المكتومة",
|
||||
"navigation_bar.pins": "Pinned toots",
|
||||
"navigation_bar.preferences": "التفضيلات",
|
||||
"navigation_bar.public_timeline": "الخيط العام الموحد",
|
||||
"notification.favourite": "{name} أعجب بمنشورك",
|
||||
|
@ -193,6 +195,15 @@
|
|||
"upload_button.label": "إضافة وسائط",
|
||||
"upload_form.undo": "إلغاء",
|
||||
"upload_progress.label": "يرفع...",
|
||||
"video.close": "Close video",
|
||||
"video.exit_fullscreen": "Exit full screen",
|
||||
"video.expand": "Expand video",
|
||||
"video.fullscreen": "Full screen",
|
||||
"video.hide": "Hide video",
|
||||
"video.mute": "Mute sound",
|
||||
"video.pause": "Pause",
|
||||
"video.play": "Play",
|
||||
"video.unmute": "Unmute sound",
|
||||
"video_player.expand": "وسّع الفيديو",
|
||||
"video_player.toggle_sound": "تبديل الصوت",
|
||||
"video_player.toggle_visible": "إظهار / إخفاء الفيديو",
|
||||
|
|
|
@ -33,6 +33,7 @@
|
|||
"column.home": "Начало",
|
||||
"column.mutes": "Muted users",
|
||||
"column.notifications": "Известия",
|
||||
"column.pins": "Pinned toot",
|
||||
"column.public": "Публичен канал",
|
||||
"column_back_button.label": "Назад",
|
||||
"column_header.hide_settings": "Hide settings",
|
||||
|
@ -109,6 +110,7 @@
|
|||
"navigation_bar.info": "Extended information",
|
||||
"navigation_bar.logout": "Излизане",
|
||||
"navigation_bar.mutes": "Muted users",
|
||||
"navigation_bar.pins": "Pinned toots",
|
||||
"navigation_bar.preferences": "Предпочитания",
|
||||
"navigation_bar.public_timeline": "Публичен канал",
|
||||
"notification.favourite": "{name} хареса твоята публикация",
|
||||
|
@ -193,6 +195,15 @@
|
|||
"upload_button.label": "Добави медия",
|
||||
"upload_form.undo": "Отмяна",
|
||||
"upload_progress.label": "Uploading...",
|
||||
"video.close": "Close video",
|
||||
"video.exit_fullscreen": "Exit full screen",
|
||||
"video.expand": "Expand video",
|
||||
"video.fullscreen": "Full screen",
|
||||
"video.hide": "Hide video",
|
||||
"video.mute": "Mute sound",
|
||||
"video.pause": "Pause",
|
||||
"video.play": "Play",
|
||||
"video.unmute": "Unmute sound",
|
||||
"video_player.expand": "Expand video",
|
||||
"video_player.toggle_sound": "Звук",
|
||||
"video_player.toggle_visible": "Toggle visibility",
|
||||
|
|
|
@ -33,6 +33,7 @@
|
|||
"column.home": "Inici",
|
||||
"column.mutes": "Usuaris silenciats",
|
||||
"column.notifications": "Notificacions",
|
||||
"column.pins": "Pinned toot",
|
||||
"column.public": "Línia de temps federada",
|
||||
"column_back_button.label": "Enrere",
|
||||
"column_header.hide_settings": "Hide settings",
|
||||
|
@ -109,6 +110,7 @@
|
|||
"navigation_bar.info": "Informació addicional",
|
||||
"navigation_bar.logout": "Tancar sessió",
|
||||
"navigation_bar.mutes": "Usuaris silenciats",
|
||||
"navigation_bar.pins": "Pinned toots",
|
||||
"navigation_bar.preferences": "Preferències",
|
||||
"navigation_bar.public_timeline": "Línia de temps federada",
|
||||
"notification.favourite": "{name} ha afavorit el teu estat",
|
||||
|
@ -193,6 +195,15 @@
|
|||
"upload_button.label": "Afegir multimèdia",
|
||||
"upload_form.undo": "Desfer",
|
||||
"upload_progress.label": "Pujant...",
|
||||
"video.close": "Close video",
|
||||
"video.exit_fullscreen": "Exit full screen",
|
||||
"video.expand": "Expand video",
|
||||
"video.fullscreen": "Full screen",
|
||||
"video.hide": "Hide video",
|
||||
"video.mute": "Mute sound",
|
||||
"video.pause": "Pause",
|
||||
"video.play": "Play",
|
||||
"video.unmute": "Unmute sound",
|
||||
"video_player.expand": "Ampliar el vídeo",
|
||||
"video_player.toggle_sound": "Alternar so",
|
||||
"video_player.toggle_visible": "Alternar visibilitat",
|
||||
|
|
|
@ -33,6 +33,7 @@
|
|||
"column.home": "Startseite",
|
||||
"column.mutes": "Stummgeschaltete Profile",
|
||||
"column.notifications": "Mitteilungen",
|
||||
"column.pins": "Pinned toot",
|
||||
"column.public": "Gesamtes bekanntes Netz",
|
||||
"column_back_button.label": "Zurück",
|
||||
"column_header.hide_settings": "Einstellungen verbergen",
|
||||
|
@ -109,6 +110,7 @@
|
|||
"navigation_bar.info": "Erweiterte Informationen",
|
||||
"navigation_bar.logout": "Abmelden",
|
||||
"navigation_bar.mutes": "Stummgeschaltete Profile",
|
||||
"navigation_bar.pins": "Pinned toots",
|
||||
"navigation_bar.preferences": "Einstellungen",
|
||||
"navigation_bar.public_timeline": "Föderierte Zeitleiste",
|
||||
"notification.favourite": "{name} favorisierte deinen Status",
|
||||
|
@ -193,6 +195,15 @@
|
|||
"upload_button.label": "Mediendatei hinzufügen",
|
||||
"upload_form.undo": "Entfernen",
|
||||
"upload_progress.label": "Lade hoch…",
|
||||
"video.close": "Close video",
|
||||
"video.exit_fullscreen": "Exit full screen",
|
||||
"video.expand": "Expand video",
|
||||
"video.fullscreen": "Full screen",
|
||||
"video.hide": "Hide video",
|
||||
"video.mute": "Mute sound",
|
||||
"video.pause": "Pause",
|
||||
"video.play": "Play",
|
||||
"video.unmute": "Unmute sound",
|
||||
"video_player.expand": "Videoanzeige vergrößern",
|
||||
"video_player.toggle_sound": "Ton umschalten",
|
||||
"video_player.toggle_visible": "Sichtbarkeit umschalten",
|
||||
|
|
|
@ -812,6 +812,10 @@
|
|||
"defaultMessage": "Extended information",
|
||||
"id": "navigation_bar.info"
|
||||
},
|
||||
{
|
||||
"defaultMessage": "Pinned toots",
|
||||
"id": "navigation_bar.pins"
|
||||
},
|
||||
{
|
||||
"defaultMessage": "FAQ",
|
||||
"id": "getting_started.faq"
|
||||
|
@ -992,6 +996,15 @@
|
|||
],
|
||||
"path": "app/javascript/mastodon/features/notifications/index.json"
|
||||
},
|
||||
{
|
||||
"descriptors": [
|
||||
{
|
||||
"defaultMessage": "Pinned toot",
|
||||
"id": "column.pins"
|
||||
}
|
||||
],
|
||||
"path": "app/javascript/mastodon/features/pinned_statuses/index.json"
|
||||
},
|
||||
{
|
||||
"descriptors": [
|
||||
{
|
||||
|
@ -1326,5 +1339,54 @@
|
|||
}
|
||||
],
|
||||
"path": "app/javascript/mastodon/features/ui/components/video_modal.json"
|
||||
},
|
||||
{
|
||||
"descriptors": [
|
||||
{
|
||||
"defaultMessage": "Play",
|
||||
"id": "video.play"
|
||||
},
|
||||
{
|
||||
"defaultMessage": "Pause",
|
||||
"id": "video.pause"
|
||||
},
|
||||
{
|
||||
"defaultMessage": "Mute sound",
|
||||
"id": "video.mute"
|
||||
},
|
||||
{
|
||||
"defaultMessage": "Unmute sound",
|
||||
"id": "video.unmute"
|
||||
},
|
||||
{
|
||||
"defaultMessage": "Hide video",
|
||||
"id": "video.hide"
|
||||
},
|
||||
{
|
||||
"defaultMessage": "Expand video",
|
||||
"id": "video.expand"
|
||||
},
|
||||
{
|
||||
"defaultMessage": "Close video",
|
||||
"id": "video.close"
|
||||
},
|
||||
{
|
||||
"defaultMessage": "Full screen",
|
||||
"id": "video.fullscreen"
|
||||
},
|
||||
{
|
||||
"defaultMessage": "Exit full screen",
|
||||
"id": "video.exit_fullscreen"
|
||||
},
|
||||
{
|
||||
"defaultMessage": "Sensitive content",
|
||||
"id": "status.sensitive_warning"
|
||||
},
|
||||
{
|
||||
"defaultMessage": "Click to view",
|
||||
"id": "status.sensitive_toggle"
|
||||
}
|
||||
],
|
||||
"path": "app/javascript/mastodon/features/video/index.json"
|
||||
}
|
||||
]
|
||||
|
|
|
@ -33,8 +33,8 @@
|
|||
"column.home": "Home",
|
||||
"column.mutes": "Muted users",
|
||||
"column.notifications": "Notifications",
|
||||
"column.public": "Federated timeline",
|
||||
"column.pins": "Pinned toots",
|
||||
"column.public": "Federated timeline",
|
||||
"column_back_button.label": "Back",
|
||||
"column_header.hide_settings": "Hide settings",
|
||||
"column_header.moveLeft_settings": "Move column to the left",
|
||||
|
@ -110,9 +110,9 @@
|
|||
"navigation_bar.info": "About this instance",
|
||||
"navigation_bar.logout": "Logout",
|
||||
"navigation_bar.mutes": "Muted users",
|
||||
"navigation_bar.pins": "Pinned toots",
|
||||
"navigation_bar.preferences": "Preferences",
|
||||
"navigation_bar.public_timeline": "Federated timeline",
|
||||
"navigation_bar.pins": "Pinned toots",
|
||||
"notification.favourite": "{name} favourited your status",
|
||||
"notification.follow": "{name} followed you",
|
||||
"notification.mention": "{name} mentioned you",
|
||||
|
@ -195,6 +195,15 @@
|
|||
"upload_button.label": "Add media",
|
||||
"upload_form.undo": "Undo",
|
||||
"upload_progress.label": "Uploading...",
|
||||
"video.close": "Close video",
|
||||
"video.exit_fullscreen": "Exit full screen",
|
||||
"video.expand": "Expand video",
|
||||
"video.fullscreen": "Full screen",
|
||||
"video.hide": "Hide video",
|
||||
"video.mute": "Mute sound",
|
||||
"video.pause": "Pause",
|
||||
"video.play": "Play",
|
||||
"video.unmute": "Unmute sound",
|
||||
"video_player.expand": "Expand video",
|
||||
"video_player.toggle_sound": "Toggle sound",
|
||||
"video_player.toggle_visible": "Toggle visibility",
|
||||
|
|
|
@ -33,6 +33,7 @@
|
|||
"column.home": "Hejmo",
|
||||
"column.mutes": "Muted users",
|
||||
"column.notifications": "Sciigoj",
|
||||
"column.pins": "Pinned toot",
|
||||
"column.public": "Fratara tempolinio",
|
||||
"column_back_button.label": "Reveni",
|
||||
"column_header.hide_settings": "Hide settings",
|
||||
|
@ -109,6 +110,7 @@
|
|||
"navigation_bar.info": "Extended information",
|
||||
"navigation_bar.logout": "Elsaluti",
|
||||
"navigation_bar.mutes": "Muted users",
|
||||
"navigation_bar.pins": "Pinned toots",
|
||||
"navigation_bar.preferences": "Preferoj",
|
||||
"navigation_bar.public_timeline": "Fratara tempolinio",
|
||||
"notification.favourite": "{name} favoris vian mesaĝon",
|
||||
|
@ -193,6 +195,15 @@
|
|||
"upload_button.label": "Aldoni enhavaĵon",
|
||||
"upload_form.undo": "Malfari",
|
||||
"upload_progress.label": "Uploading...",
|
||||
"video.close": "Close video",
|
||||
"video.exit_fullscreen": "Exit full screen",
|
||||
"video.expand": "Expand video",
|
||||
"video.fullscreen": "Full screen",
|
||||
"video.hide": "Hide video",
|
||||
"video.mute": "Mute sound",
|
||||
"video.pause": "Pause",
|
||||
"video.play": "Play",
|
||||
"video.unmute": "Unmute sound",
|
||||
"video_player.expand": "Expand video",
|
||||
"video_player.toggle_sound": "Aktivigi sonojn",
|
||||
"video_player.toggle_visible": "Toggle visibility",
|
||||
|
|
|
@ -1,106 +1,107 @@
|
|||
{
|
||||
"account.block": "Bloquear",
|
||||
"account.block_domain": "Hide everything from {domain}",
|
||||
"account.disclaimer_full": "Information below may reflect the user's profile incompletely.",
|
||||
"account.block_domain": "Ocultar todo de {domain}",
|
||||
"account.disclaimer_full": "La siguiente información del usuario puede estar incompleta.",
|
||||
"account.edit_profile": "Editar perfil",
|
||||
"account.follow": "Seguir",
|
||||
"account.followers": "Seguidores",
|
||||
"account.follows": "Seguir",
|
||||
"account.follows": "Sigue",
|
||||
"account.follows_you": "Te sigue",
|
||||
"account.media": "Media",
|
||||
"account.mention": "Mencionar",
|
||||
"account.mute": "Silenciar",
|
||||
"account.mention": "Mencionar a @{name}",
|
||||
"account.mute": "Silenciar a @{name}",
|
||||
"account.posts": "Publicaciones",
|
||||
"account.report": "Report @{name}",
|
||||
"account.report": "Reportar a @{name}",
|
||||
"account.requested": "Esperando aprobación",
|
||||
"account.share": "Share @{name}'s profile",
|
||||
"account.unblock": "Desbloquear",
|
||||
"account.unblock_domain": "Unhide {domain}",
|
||||
"account.share": "Compartir el perfil de @{name}",
|
||||
"account.unblock": "Desbloquear a @{name}",
|
||||
"account.unblock_domain": "Mostrar a {domain}",
|
||||
"account.unfollow": "Dejar de seguir",
|
||||
"account.unmute": "Unmute @{name}",
|
||||
"account.view_full_profile": "View full profile",
|
||||
"boost_modal.combo": "You can press {combo} to skip this next time",
|
||||
"bundle_column_error.body": "Something went wrong while loading this component.",
|
||||
"bundle_column_error.retry": "Try again",
|
||||
"bundle_column_error.title": "Network error",
|
||||
"bundle_modal_error.close": "Close",
|
||||
"bundle_modal_error.message": "Something went wrong while loading this component.",
|
||||
"bundle_modal_error.retry": "Try again",
|
||||
"account.unmute": "Dejar de silenciar a @{name}",
|
||||
"account.view_full_profile": "Ver perfil completo",
|
||||
"boost_modal.combo": "Puedes presionar {combo} para saltear este aviso la próxima vez",
|
||||
"bundle_column_error.body": "Algo salió mal al cargar este componente.",
|
||||
"bundle_column_error.retry": "Inténtalo de nuevo",
|
||||
"bundle_column_error.title": "Error de red",
|
||||
"bundle_modal_error.close": "Cerrar",
|
||||
"bundle_modal_error.message": "Algo salió mal al cargar este componente.",
|
||||
"bundle_modal_error.retry": "Inténtalo de nuevo",
|
||||
"column.blocks": "Usuarios bloqueados",
|
||||
"column.community": "Historia local",
|
||||
"column.community": "Línea de tiempo local",
|
||||
"column.favourites": "Favoritos",
|
||||
"column.follow_requests": "Solicitudes para seguirte",
|
||||
"column.follow_requests": "Solicitudes de seguimiento",
|
||||
"column.home": "Inicio",
|
||||
"column.mutes": "Usuarios silenciados",
|
||||
"column.notifications": "Notificaciones",
|
||||
"column.pins": "Toot fijado",
|
||||
"column.public": "Historia federada",
|
||||
"column_back_button.label": "Atrás",
|
||||
"column_header.hide_settings": "Hide settings",
|
||||
"column_header.moveLeft_settings": "Move column to the left",
|
||||
"column_header.moveRight_settings": "Move column to the right",
|
||||
"column_header.pin": "Pin",
|
||||
"column_header.show_settings": "Show settings",
|
||||
"column_header.unpin": "Unpin",
|
||||
"column_subheading.navigation": "Navigation",
|
||||
"column_subheading.settings": "Settings",
|
||||
"compose_form.lock_disclaimer": "Your account is not {locked}. Anyone can follow you to view your follower-only posts.",
|
||||
"compose_form.lock_disclaimer.lock": "locked",
|
||||
"column_header.hide_settings": "Ocultar ajustes",
|
||||
"column_header.moveLeft_settings": "Mover columna a la izquierda",
|
||||
"column_header.moveRight_settings": "Mover columna a la derecha",
|
||||
"column_header.pin": "Fijar",
|
||||
"column_header.show_settings": "Mostrar ajustes",
|
||||
"column_header.unpin": "Dejar de fijar",
|
||||
"column_subheading.navigation": "Navegación",
|
||||
"column_subheading.settings": "Ajustes",
|
||||
"compose_form.lock_disclaimer": "Tu cuenta no está bloqueada. Todos pueden seguirte para ver tus toots solo para seguidores.",
|
||||
"compose_form.lock_disclaimer.lock": "bloqueado",
|
||||
"compose_form.placeholder": "¿En qué estás pensando?",
|
||||
"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": "Tu toot privado será enviado a usuario/s mencionados de {domains}. ¿Confías en {domainsCount, plural, one {ese servidor} other {esos servidores}}? La privacidad del toot funcionará solamente en instancias de Mastodon. Si {domains} {domainsCount, plural, one {no es una instancia de Mastodon} other {no son instancias de Mastodon}}, no habrá indicación de que tu toot es privado, y puede hacerse visible a remitentes inesperados.",
|
||||
"compose_form.publish": "Tootear",
|
||||
"compose_form.publish_loud": "{publish}!",
|
||||
"compose_form.sensitive": "Marcar contenido como sensible",
|
||||
"compose_form.spoiler": "Ocultar texto tras advertencia",
|
||||
"compose_form.spoiler": "Ocultar texto tras una advertencia",
|
||||
"compose_form.spoiler_placeholder": "Advertencia de contenido",
|
||||
"confirmation_modal.cancel": "Cancel",
|
||||
"confirmations.block.confirm": "Block",
|
||||
"confirmations.block.message": "Are you sure you want to block {name}?",
|
||||
"confirmations.delete.confirm": "Delete",
|
||||
"confirmations.delete.message": "Are you sure you want to delete this status?",
|
||||
"confirmations.domain_block.confirm": "Hide entire domain",
|
||||
"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": "Mute",
|
||||
"confirmations.mute.message": "Are you sure you want to mute {name}?",
|
||||
"confirmations.unfollow.confirm": "Unfollow",
|
||||
"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.flags": "Flags",
|
||||
"emoji_button.food": "Food & Drink",
|
||||
"confirmation_modal.cancel": "Cancelar",
|
||||
"confirmations.block.confirm": "Bloquear",
|
||||
"confirmations.block.message": "¿Estás seguro de que quieres bloquear a {name}?",
|
||||
"confirmations.delete.confirm": "Eliminar",
|
||||
"confirmations.delete.message": "¿Estás seguro de que quieres borrar este toot?",
|
||||
"confirmations.domain_block.confirm": "Ocultar dominio entero",
|
||||
"confirmations.domain_block.message": "¿Seguro de que quieres bloquear al dominio entero? En algunos casos es preferible bloquear o silenciar objetivos determinados.",
|
||||
"confirmations.mute.confirm": "Silenciar",
|
||||
"confirmations.mute.message": "¿Estás seguro de que quieres silenciar a {name}?",
|
||||
"confirmations.unfollow.confirm": "Dejar de seguir",
|
||||
"confirmations.unfollow.message": "¿Estás seguro de que quieres dejar de seguir a {name}?",
|
||||
"embed.instructions": "Añade este toot a tu sitio web con el siguiente código.",
|
||||
"embed.preview": "Así es como se verá:",
|
||||
"emoji_button.activity": "Actividad",
|
||||
"emoji_button.flags": "Marcas",
|
||||
"emoji_button.food": "Comida y bebida",
|
||||
"emoji_button.label": "Insertar emoji",
|
||||
"emoji_button.nature": "Nature",
|
||||
"emoji_button.objects": "Objects",
|
||||
"emoji_button.people": "People",
|
||||
"emoji_button.search": "Search...",
|
||||
"emoji_button.symbols": "Symbols",
|
||||
"emoji_button.travel": "Travel & Places",
|
||||
"empty_column.community": "The local timeline is empty. Write something publicly to get the ball rolling!",
|
||||
"empty_column.hashtag": "There is nothing in this hashtag yet.",
|
||||
"empty_column.home": "You aren't following anyone yet. Visit {public} or use search to get started and meet other users.",
|
||||
"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.public_timeline": "the public timeline",
|
||||
"empty_column.notifications": "You don't have any notifications yet. Interact with others to start the conversation.",
|
||||
"empty_column.public": "There is nothing here! Write something publicly, or manually follow users from other instances to fill it up",
|
||||
"follow_request.authorize": "Authorize",
|
||||
"follow_request.reject": "Reject",
|
||||
"getting_started.appsshort": "Apps",
|
||||
"emoji_button.nature": "Naturaleza",
|
||||
"emoji_button.objects": "Objetos",
|
||||
"emoji_button.people": "Gente",
|
||||
"emoji_button.search": "Buscar…",
|
||||
"emoji_button.symbols": "Símbolos",
|
||||
"emoji_button.travel": "Viajes y lugares",
|
||||
"empty_column.community": "La línea de tiempo local está vacía. ¡Escribe algo para empezar la fiesta!",
|
||||
"empty_column.hashtag": "No hay nada en este hashtag aún.",
|
||||
"empty_column.home": "No estás siguiendo a nadie aún. Visita {public} o haz búsquedas para empezar y conocer gente nueva.",
|
||||
"empty_column.home.inactivity": "Tus notificaciones están vacías. Si has estado inactivo por un tiempo, se regenerará para ti pronto.",
|
||||
"empty_column.home.public_timeline": "la línea de tiempo pública",
|
||||
"empty_column.notifications": "No tienes ninguna notificación aún. Interactúa con otros para empezar una conversación.",
|
||||
"empty_column.public": "¡No hay nada aquí! Escribe algo públicamente, o sigue usuarios de otras instancias manualmente para llenarlo.",
|
||||
"follow_request.authorize": "Autorizar",
|
||||
"follow_request.reject": "Rechazar",
|
||||
"getting_started.appsshort": "Aplicaciones",
|
||||
"getting_started.faq": "FAQ",
|
||||
"getting_started.heading": "Primeros pasos",
|
||||
"getting_started.open_source_notice": "Mastodon es software libre. Puedes contribuir o reportar errores en {github}.",
|
||||
"getting_started.userguide": "User Guide",
|
||||
"home.column_settings.advanced": "Advanced",
|
||||
"home.column_settings.basic": "Basic",
|
||||
"home.column_settings.filter_regex": "Filter out by regular expressions",
|
||||
"home.column_settings.show_reblogs": "Show boosts",
|
||||
"home.column_settings.show_replies": "Show replies",
|
||||
"home.settings": "Column settings",
|
||||
"getting_started.userguide": "Guía de usuario",
|
||||
"home.column_settings.advanced": "Avanzado",
|
||||
"home.column_settings.basic": "Básico",
|
||||
"home.column_settings.filter_regex": "Filtrar con expresiones regulares",
|
||||
"home.column_settings.show_reblogs": "Mostrar retoots",
|
||||
"home.column_settings.show_replies": "Mostrar respuestas",
|
||||
"home.settings": "Ajustes de columna",
|
||||
"lightbox.close": "Cerrar",
|
||||
"lightbox.next": "Next",
|
||||
"lightbox.previous": "Previous",
|
||||
"loading_indicator.label": "Cargando...",
|
||||
"media_gallery.toggle_visible": "Toggle visibility",
|
||||
"missing_indicator.label": "Not found",
|
||||
"lightbox.next": "Siguiente",
|
||||
"lightbox.previous": "Anterior",
|
||||
"loading_indicator.label": "Cargando…",
|
||||
"media_gallery.toggle_visible": "Cambiar visibilidad",
|
||||
"missing_indicator.label": "No encontrado",
|
||||
"navigation_bar.blocks": "Usuarios bloqueados",
|
||||
"navigation_bar.community_timeline": "Historia local",
|
||||
"navigation_bar.edit_profile": "Editar perfil",
|
||||
|
@ -109,43 +110,44 @@
|
|||
"navigation_bar.info": "Información adicional",
|
||||
"navigation_bar.logout": "Cerrar sesión",
|
||||
"navigation_bar.mutes": "Usuarios silenciados",
|
||||
"navigation_bar.pins": "Toots fijados",
|
||||
"navigation_bar.preferences": "Preferencias",
|
||||
"navigation_bar.public_timeline": "Historia federada",
|
||||
"notification.favourite": "{name} marcó tu estado como favorito",
|
||||
"notification.follow": "{name} te empezó a seguir",
|
||||
"notification.mention": "{name} te ha mencionado",
|
||||
"notification.reblog": "{name} ha retooteado tu estado",
|
||||
"notifications.clear": "Clear notifications",
|
||||
"notifications.clear_confirmation": "Are you sure you want to permanently clear all your notifications?",
|
||||
"notifications.clear": "Limpiar notificaciones",
|
||||
"notifications.clear_confirmation": "¿Seguro que quieres limpiar permanentemente todas tus notificaciones?",
|
||||
"notifications.column_settings.alert": "Notificaciones de escritorio",
|
||||
"notifications.column_settings.favourite": "Favoritos:",
|
||||
"notifications.column_settings.follow": "Nuevos seguidores:",
|
||||
"notifications.column_settings.mention": "Menciones:",
|
||||
"notifications.column_settings.push": "Push notifications",
|
||||
"notifications.column_settings.push_meta": "This device",
|
||||
"notifications.column_settings.push": "Notificaciones push:",
|
||||
"notifications.column_settings.push_meta": "Este dispositivo:",
|
||||
"notifications.column_settings.reblog": "Retoots:",
|
||||
"notifications.column_settings.show": "Mostrar en columna",
|
||||
"notifications.column_settings.sound": "Play sound",
|
||||
"onboarding.done": "Done",
|
||||
"onboarding.next": "Next",
|
||||
"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_four.home": "The home timeline shows posts from people you follow.",
|
||||
"onboarding.page_four.notifications": "The notifications column shows when someone interacts with you.",
|
||||
"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.handle": "You are on {domain}, so your full handle is {handle}",
|
||||
"onboarding.page_one.welcome": "Welcome to Mastodon!",
|
||||
"onboarding.page_six.admin": "Your instance's admin is {admin}.",
|
||||
"onboarding.page_six.almost_done": "Almost done...",
|
||||
"onboarding.page_six.appetoot": "Bon Appetoot!",
|
||||
"onboarding.page_six.apps_available": "There are {apps} available for iOS, Android and other platforms.",
|
||||
"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.guidelines": "community guidelines",
|
||||
"onboarding.page_six.read_guidelines": "Please read {domain}'s {guidelines}!",
|
||||
"onboarding.page_six.various_app": "mobile apps",
|
||||
"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.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_two.compose": "Write posts from the compose column. You can upload images, change privacy settings, and add content warnings with the icons below.",
|
||||
"onboarding.skip": "Skip",
|
||||
"notifications.column_settings.sound": "Reproducir sonido",
|
||||
"onboarding.done": "Listo",
|
||||
"onboarding.next": "Siguiente",
|
||||
"onboarding.page_five.public_timelines": "La línea de tiempo local muestra toots públicos de todos en {domain}. La línea de tiempo federada muestra toots públicos de cualquiera a quien la gente de {domain} siga. Estas son las líneas de tiempo públicas, una buena forma de conocer gente nueva.",
|
||||
"onboarding.page_four.home": "La línea de tiempo principal muestra toots de gente que sigues.",
|
||||
"onboarding.page_four.notifications": "Las notificaciones se muestran cuando alguien interactúa contigo.",
|
||||
"onboarding.page_one.federation": "Mastodon es una red de servidores federados que conforman una red social aún más grande. Llamamos a estos servidores instancias.",
|
||||
"onboarding.page_one.handle": "Estás en {domain}, así que tu nombre de usuario completo es {handle}",
|
||||
"onboarding.page_one.welcome": "¡Bienvenido a Mastodon!",
|
||||
"onboarding.page_six.admin": "El administrador de tu instancia es {admin}.",
|
||||
"onboarding.page_six.almost_done": "Ya casi…",
|
||||
"onboarding.page_six.appetoot": "¡Bon Appetoot!",
|
||||
"onboarding.page_six.apps_available": "Hay {apps} disponibles para iOS, Android y otras plataformas.",
|
||||
"onboarding.page_six.github": "Mastodon es software libre. Puedes reportar errores, pedir funciones nuevas, o contribuir al código en {github}.",
|
||||
"onboarding.page_six.guidelines": "guías de la comunidad",
|
||||
"onboarding.page_six.read_guidelines": "¡Por favor lee las {guidelines} de {domain}!",
|
||||
"onboarding.page_six.various_app": "aplicaciones móviles",
|
||||
"onboarding.page_three.profile": "Edita tu perfil para cambiar tu avatar, biografía y nombre de cabecera. Ahí, también encontrarás otros ajustes.",
|
||||
"onboarding.page_three.search": "Usa la barra de búsqueda y revisa hashtags, como {illustration} y {introductions}. Para ver a alguien que no es de tu propia instancia, usa su nombre de usuario completo.",
|
||||
"onboarding.page_two.compose": "Escribe toots en la columna de redacción. Puedes subir imágenes, cambiar ajustes de privacidad, y añadir advertencias de contenido con los siguientes íconos.",
|
||||
"onboarding.skip": "Saltar",
|
||||
"privacy.change": "Ajustar privacidad",
|
||||
"privacy.direct.long": "Sólo mostrar a los usuarios mencionados",
|
||||
"privacy.direct.short": "Directo",
|
||||
|
@ -156,45 +158,54 @@
|
|||
"privacy.unlisted.long": "No mostrar en la historia federada",
|
||||
"privacy.unlisted.short": "Sin federar",
|
||||
"reply_indicator.cancel": "Cancelar",
|
||||
"report.placeholder": "Additional comments",
|
||||
"report.submit": "Submit",
|
||||
"report.target": "Reporting",
|
||||
"report.placeholder": "Comentarios adicionales",
|
||||
"report.submit": "Publicar",
|
||||
"report.target": "Reportando",
|
||||
"search.placeholder": "Buscar",
|
||||
"search_results.total": "{count, number} {count, plural, one {result} other {results}}",
|
||||
"standalone.public_title": "A look inside...",
|
||||
"status.cannot_reblog": "This post cannot be boosted",
|
||||
"search_results.total": "{count, number} {count, plural, one {resultado} other {resultados}}",
|
||||
"standalone.public_title": "Un pequeño vistazo...",
|
||||
"status.cannot_reblog": "Este toot no puede retootearse",
|
||||
"status.delete": "Borrar",
|
||||
"status.embed": "Embed",
|
||||
"status.embed": "Incrustado",
|
||||
"status.favourite": "Favorito",
|
||||
"status.load_more": "Load more",
|
||||
"status.media_hidden": "Media hidden",
|
||||
"status.load_more": "Cargar más",
|
||||
"status.media_hidden": "Contenido multimedia oculto",
|
||||
"status.mention": "Mencionar",
|
||||
"status.mute_conversation": "Mute conversation",
|
||||
"status.mute_conversation": "Silenciar conversación",
|
||||
"status.open": "Expandir estado",
|
||||
"status.pin": "Pin on profile",
|
||||
"status.reblog": "Retoot",
|
||||
"status.pin": "Fijar",
|
||||
"status.reblog": "Retootear",
|
||||
"status.reblogged_by": "Retooteado por {name}",
|
||||
"status.reply": "Responder",
|
||||
"status.replyAll": "Reply to thread",
|
||||
"status.replyAll": "Responder al hilo",
|
||||
"status.report": "Reportar",
|
||||
"status.sensitive_toggle": "Click para ver",
|
||||
"status.sensitive_toggle": "Haz clic para ver",
|
||||
"status.sensitive_warning": "Contenido sensible",
|
||||
"status.share": "Share",
|
||||
"status.share": "Compartir",
|
||||
"status.show_less": "Mostrar menos",
|
||||
"status.show_more": "Mostrar más",
|
||||
"status.unmute_conversation": "Unmute conversation",
|
||||
"status.unpin": "Unpin from profile",
|
||||
"status.unmute_conversation": "Dejar de silenciar conversación",
|
||||
"status.unpin": "Dejar de fijar",
|
||||
"tabs_bar.compose": "Redactar",
|
||||
"tabs_bar.federated_timeline": "Federated",
|
||||
"tabs_bar.federated_timeline": "Federado",
|
||||
"tabs_bar.home": "Inicio",
|
||||
"tabs_bar.local_timeline": "Local",
|
||||
"tabs_bar.notifications": "Notificaciones",
|
||||
"upload_area.title": "Drag & drop to upload",
|
||||
"upload_area.title": "Arrastra y suelta para subir",
|
||||
"upload_button.label": "Subir multimedia",
|
||||
"upload_form.undo": "Deshacer",
|
||||
"upload_progress.label": "Uploading...",
|
||||
"video_player.expand": "Expand video",
|
||||
"video_player.toggle_sound": "Act/Desac. sonido",
|
||||
"video_player.toggle_visible": "Toggle visibility",
|
||||
"video_player.video_error": "Video could not be played"
|
||||
"upload_progress.label": "Subiendo…",
|
||||
"video.close": "Cerrar video",
|
||||
"video.exit_fullscreen": "Salir de pantalla completa",
|
||||
"video.expand": "Expandir vídeo",
|
||||
"video.fullscreen": "Pantalla completa",
|
||||
"video.hide": "Ocultar vídeo",
|
||||
"video.mute": "Silenciar sonido",
|
||||
"video.pause": "Pausar",
|
||||
"video.play": "Reproducir",
|
||||
"video.unmute": "Dejar de silenciar sonido",
|
||||
"video_player.expand": "Expandir vídeo",
|
||||
"video_player.toggle_sound": "Activar/Desactivar sonido",
|
||||
"video_player.toggle_visible": "Cambiar visibilidad",
|
||||
"video_player.video_error": "No se pudo reproducir el vídeo"
|
||||
}
|
||||
|
|
|
@ -33,6 +33,7 @@
|
|||
"column.home": "خانه",
|
||||
"column.mutes": "کاربران بیصداشده",
|
||||
"column.notifications": "اعلانها",
|
||||
"column.pins": "نوشتههای ثابت",
|
||||
"column.public": "نوشتههای همهجا",
|
||||
"column_back_button.label": "بازگشت",
|
||||
"column_header.hide_settings": "نهفتن تنظیمات",
|
||||
|
@ -109,6 +110,7 @@
|
|||
"navigation_bar.info": "اطلاعات تکمیلی",
|
||||
"navigation_bar.logout": "خروج",
|
||||
"navigation_bar.mutes": "کاربران بیصداشده",
|
||||
"navigation_bar.pins": "نوشتههای ثابت",
|
||||
"navigation_bar.preferences": "ترجیحات",
|
||||
"navigation_bar.public_timeline": "نوشتههای همهجا",
|
||||
"notification.favourite": "{name} نوشتهٔ شما را پسندید",
|
||||
|
@ -193,6 +195,15 @@
|
|||
"upload_button.label": "افزودن تصویر",
|
||||
"upload_form.undo": "واگردانی",
|
||||
"upload_progress.label": "بارگذاری...",
|
||||
"video.close": "بستن ویدیو",
|
||||
"video.exit_fullscreen": "خروج از حالت تمام صفحه",
|
||||
"video.expand": "بزرگکردن ویدیو",
|
||||
"video.fullscreen": "تمام صفحه",
|
||||
"video.hide": "نهفتن ویدیو",
|
||||
"video.mute": "قطع صدا",
|
||||
"video.pause": "توقف",
|
||||
"video.play": "پخش",
|
||||
"video.unmute": "پخش صدا",
|
||||
"video_player.expand": "بازکردن ویدیو",
|
||||
"video_player.toggle_sound": "تغییر صداداری",
|
||||
"video_player.toggle_visible": "تغییر پیدایی",
|
||||
|
|
|
@ -33,6 +33,7 @@
|
|||
"column.home": "Koti",
|
||||
"column.mutes": "Muted users",
|
||||
"column.notifications": "Ilmoitukset",
|
||||
"column.pins": "Pinned toot",
|
||||
"column.public": "Yleinen aikajana",
|
||||
"column_back_button.label": "Takaisin",
|
||||
"column_header.hide_settings": "Hide settings",
|
||||
|
@ -109,6 +110,7 @@
|
|||
"navigation_bar.info": "Extended information",
|
||||
"navigation_bar.logout": "Kirjaudu ulos",
|
||||
"navigation_bar.mutes": "Muted users",
|
||||
"navigation_bar.pins": "Pinned toots",
|
||||
"navigation_bar.preferences": "Ominaisuudet",
|
||||
"navigation_bar.public_timeline": "Yleinen aikajana",
|
||||
"notification.favourite": "{name} tykkäsi statuksestasi",
|
||||
|
@ -193,6 +195,15 @@
|
|||
"upload_button.label": "Lisää mediaa",
|
||||
"upload_form.undo": "Peru",
|
||||
"upload_progress.label": "Uploading...",
|
||||
"video.close": "Close video",
|
||||
"video.exit_fullscreen": "Exit full screen",
|
||||
"video.expand": "Expand video",
|
||||
"video.fullscreen": "Full screen",
|
||||
"video.hide": "Hide video",
|
||||
"video.mute": "Mute sound",
|
||||
"video.pause": "Pause",
|
||||
"video.play": "Play",
|
||||
"video.unmute": "Unmute sound",
|
||||
"video_player.expand": "Expand video",
|
||||
"video_player.toggle_sound": "Äänet päälle/pois",
|
||||
"video_player.toggle_visible": "Toggle visibility",
|
||||
|
|
|
@ -33,8 +33,8 @@
|
|||
"column.home": "Accueil",
|
||||
"column.mutes": "Comptes masqués",
|
||||
"column.notifications": "Notifications",
|
||||
"column.public": "Fil public global",
|
||||
"column.pins": "Pouets épinglés",
|
||||
"column.public": "Fil public global",
|
||||
"column_back_button.label": "Retour",
|
||||
"column_header.hide_settings": "Masquer les paramètres",
|
||||
"column_header.moveLeft_settings": "Déplacer la colonne vers la gauche",
|
||||
|
@ -110,9 +110,9 @@
|
|||
"navigation_bar.info": "Plus d’informations",
|
||||
"navigation_bar.logout": "Déconnexion",
|
||||
"navigation_bar.mutes": "Comptes masqués",
|
||||
"navigation_bar.pins": "Pouets épinglés",
|
||||
"navigation_bar.preferences": "Préférences",
|
||||
"navigation_bar.public_timeline": "Fil public global",
|
||||
"navigation_bar.pins": "Pouets épinglés",
|
||||
"notification.favourite": "{name} a ajouté à ses favoris :",
|
||||
"notification.follow": "{name} vous suit.",
|
||||
"notification.mention": "{name} vous a mentionné⋅e :",
|
||||
|
@ -166,7 +166,7 @@
|
|||
"standalone.public_title": "Jeter un coup d’œil…",
|
||||
"status.cannot_reblog": "Cette publication ne peut être boostée",
|
||||
"status.delete": "Effacer",
|
||||
"status.embed": "Embed",
|
||||
"status.embed": "Intégrer",
|
||||
"status.favourite": "Ajouter aux favoris",
|
||||
"status.load_more": "Charger plus",
|
||||
"status.media_hidden": "Média caché",
|
||||
|
@ -195,6 +195,15 @@
|
|||
"upload_button.label": "Joindre un média",
|
||||
"upload_form.undo": "Annuler",
|
||||
"upload_progress.label": "Envoi en cours…",
|
||||
"video.close": "Fermer la vidéo",
|
||||
"video.exit_fullscreen": "Quitter plein écran",
|
||||
"video.expand": "Agrandir la vidéo",
|
||||
"video.fullscreen": "Plein écran",
|
||||
"video.hide": "Masquer la vidéo",
|
||||
"video.mute": "Couper le son",
|
||||
"video.pause": "Pause",
|
||||
"video.play": "Lecture",
|
||||
"video.unmute": "Rétablir le son",
|
||||
"video_player.expand": "Agrandir la vidéo",
|
||||
"video_player.toggle_sound": "Activer/Désactiver le son",
|
||||
"video_player.toggle_visible": "Afficher/Cacher la vidéo",
|
||||
|
|
|
@ -33,6 +33,7 @@
|
|||
"column.home": "בבית",
|
||||
"column.mutes": "השתקות",
|
||||
"column.notifications": "התראות",
|
||||
"column.pins": "Pinned toot",
|
||||
"column.public": "בפרהסיה",
|
||||
"column_back_button.label": "חזרה",
|
||||
"column_header.hide_settings": "Hide settings",
|
||||
|
@ -109,6 +110,7 @@
|
|||
"navigation_bar.info": "מידע נוסף",
|
||||
"navigation_bar.logout": "יציאה",
|
||||
"navigation_bar.mutes": "השתקות",
|
||||
"navigation_bar.pins": "Pinned toots",
|
||||
"navigation_bar.preferences": "העדפות",
|
||||
"navigation_bar.public_timeline": "ציר זמן בין-קהילתי",
|
||||
"notification.favourite": "חצרוצך חובב על ידי {name}",
|
||||
|
@ -193,6 +195,15 @@
|
|||
"upload_button.label": "הוספת מדיה",
|
||||
"upload_form.undo": "ביטול",
|
||||
"upload_progress.label": "עולה...",
|
||||
"video.close": "Close video",
|
||||
"video.exit_fullscreen": "Exit full screen",
|
||||
"video.expand": "Expand video",
|
||||
"video.fullscreen": "Full screen",
|
||||
"video.hide": "Hide video",
|
||||
"video.mute": "Mute sound",
|
||||
"video.pause": "Pause",
|
||||
"video.play": "Play",
|
||||
"video.unmute": "Unmute sound",
|
||||
"video_player.expand": "הרחבת וידאו",
|
||||
"video_player.toggle_sound": "הפעלת\\ביטול שמע",
|
||||
"video_player.toggle_visible": "הפעלת\\ביטול תצוגה",
|
||||
|
|
|
@ -33,6 +33,7 @@
|
|||
"column.home": "Dom",
|
||||
"column.mutes": "Utišani korisnici",
|
||||
"column.notifications": "Notifikacije",
|
||||
"column.pins": "Pinned toot",
|
||||
"column.public": "Federalni timeline",
|
||||
"column_back_button.label": "Natrag",
|
||||
"column_header.hide_settings": "Hide settings",
|
||||
|
@ -61,7 +62,6 @@
|
|||
"confirmations.domain_block.message": "Jesi li zaista, zaista siguran da želiš potpuno blokirati {domain}? In most cases a few targeted blocks or mutes are sufficient and preferable.",
|
||||
"confirmations.mute.confirm": "Utišaj",
|
||||
"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.message": "Are you sure you want to unfollow {name}?",
|
||||
"embed.instructions": "Embed this status on your website by copying the code below.",
|
||||
|
@ -110,6 +110,7 @@
|
|||
"navigation_bar.info": "Više informacija",
|
||||
"navigation_bar.logout": "Odjavi se",
|
||||
"navigation_bar.mutes": "Utišani korisnici",
|
||||
"navigation_bar.pins": "Pinned toots",
|
||||
"navigation_bar.preferences": "Postavke",
|
||||
"navigation_bar.public_timeline": "Federalni timeline",
|
||||
"notification.favourite": "{name} je lajkao tvoj status",
|
||||
|
@ -194,6 +195,15 @@
|
|||
"upload_button.label": "Dodaj media",
|
||||
"upload_form.undo": "Poništi",
|
||||
"upload_progress.label": "Uploadam...",
|
||||
"video.close": "Close video",
|
||||
"video.exit_fullscreen": "Exit full screen",
|
||||
"video.expand": "Expand video",
|
||||
"video.fullscreen": "Full screen",
|
||||
"video.hide": "Hide video",
|
||||
"video.mute": "Mute sound",
|
||||
"video.pause": "Pause",
|
||||
"video.play": "Play",
|
||||
"video.unmute": "Unmute sound",
|
||||
"video_player.expand": "Proširi video",
|
||||
"video_player.toggle_sound": "Toggle zvuk",
|
||||
"video_player.toggle_visible": "Preklopi vidljivost",
|
||||
|
|
|
@ -33,6 +33,7 @@
|
|||
"column.home": "Kezdőlap",
|
||||
"column.mutes": "Muted users",
|
||||
"column.notifications": "Értesítések",
|
||||
"column.pins": "Pinned toot",
|
||||
"column.public": "Nyilvános",
|
||||
"column_back_button.label": "Vissza",
|
||||
"column_header.hide_settings": "Hide settings",
|
||||
|
@ -109,6 +110,7 @@
|
|||
"navigation_bar.info": "Extended information",
|
||||
"navigation_bar.logout": "Kijelentkezés",
|
||||
"navigation_bar.mutes": "Muted users",
|
||||
"navigation_bar.pins": "Pinned toots",
|
||||
"navigation_bar.preferences": "Beállítások",
|
||||
"navigation_bar.public_timeline": "Nyilvános időfolyam",
|
||||
"notification.favourite": "{name} kedvencnek jelölte az állapotod",
|
||||
|
@ -193,6 +195,15 @@
|
|||
"upload_button.label": "Média hozzáadása",
|
||||
"upload_form.undo": "Mégsem",
|
||||
"upload_progress.label": "Uploading...",
|
||||
"video.close": "Close video",
|
||||
"video.exit_fullscreen": "Exit full screen",
|
||||
"video.expand": "Expand video",
|
||||
"video.fullscreen": "Full screen",
|
||||
"video.hide": "Hide video",
|
||||
"video.mute": "Mute sound",
|
||||
"video.pause": "Pause",
|
||||
"video.play": "Play",
|
||||
"video.unmute": "Unmute sound",
|
||||
"video_player.expand": "Expand video",
|
||||
"video_player.toggle_sound": "Hang kapcsolása",
|
||||
"video_player.toggle_visible": "Toggle visibility",
|
||||
|
|
|
@ -33,6 +33,7 @@
|
|||
"column.home": "Beranda",
|
||||
"column.mutes": "Pengguna dibisukan",
|
||||
"column.notifications": "Notifikasi",
|
||||
"column.pins": "Pinned toot",
|
||||
"column.public": "Linimasa gabunggan",
|
||||
"column_back_button.label": "Kembali",
|
||||
"column_header.hide_settings": "Hide settings",
|
||||
|
@ -109,6 +110,7 @@
|
|||
"navigation_bar.info": "Informasi selengkapnya",
|
||||
"navigation_bar.logout": "Keluar",
|
||||
"navigation_bar.mutes": "Pengguna dibisukan",
|
||||
"navigation_bar.pins": "Pinned toots",
|
||||
"navigation_bar.preferences": "Pengaturan",
|
||||
"navigation_bar.public_timeline": "Linimasa gabungan",
|
||||
"notification.favourite": "{name} menyukai status anda",
|
||||
|
@ -193,6 +195,15 @@
|
|||
"upload_button.label": "Tambahkan media",
|
||||
"upload_form.undo": "Undo",
|
||||
"upload_progress.label": "Mengunggah...",
|
||||
"video.close": "Close video",
|
||||
"video.exit_fullscreen": "Exit full screen",
|
||||
"video.expand": "Expand video",
|
||||
"video.fullscreen": "Full screen",
|
||||
"video.hide": "Hide video",
|
||||
"video.mute": "Mute sound",
|
||||
"video.pause": "Pause",
|
||||
"video.play": "Play",
|
||||
"video.unmute": "Unmute sound",
|
||||
"video_player.expand": "Tampilkan video",
|
||||
"video_player.toggle_sound": "Suara",
|
||||
"video_player.toggle_visible": "Tampilan",
|
||||
|
|
|
@ -33,6 +33,7 @@
|
|||
"column.home": "Hemo",
|
||||
"column.mutes": "Celita uzeri",
|
||||
"column.notifications": "Savigi",
|
||||
"column.pins": "Pinned toot",
|
||||
"column.public": "Federata tempolineo",
|
||||
"column_back_button.label": "Retro",
|
||||
"column_header.hide_settings": "Hide settings",
|
||||
|
@ -109,6 +110,7 @@
|
|||
"navigation_bar.info": "Detaloza informi",
|
||||
"navigation_bar.logout": "Ekirar",
|
||||
"navigation_bar.mutes": "Celita uzeri",
|
||||
"navigation_bar.pins": "Pinned toots",
|
||||
"navigation_bar.preferences": "Preferi",
|
||||
"navigation_bar.public_timeline": "Federata tempolineo",
|
||||
"notification.favourite": "{name} favorizis tua mesajo",
|
||||
|
@ -193,6 +195,15 @@
|
|||
"upload_button.label": "Adjuntar kontenajo",
|
||||
"upload_form.undo": "Desfacar",
|
||||
"upload_progress.label": "Kargante...",
|
||||
"video.close": "Close video",
|
||||
"video.exit_fullscreen": "Exit full screen",
|
||||
"video.expand": "Expand video",
|
||||
"video.fullscreen": "Full screen",
|
||||
"video.hide": "Hide video",
|
||||
"video.mute": "Mute sound",
|
||||
"video.pause": "Pause",
|
||||
"video.play": "Play",
|
||||
"video.unmute": "Unmute sound",
|
||||
"video_player.expand": "Extensar video",
|
||||
"video_player.toggle_sound": "Acendar sono",
|
||||
"video_player.toggle_visible": "Chanjar videbleso",
|
||||
|
|
|
@ -33,6 +33,7 @@
|
|||
"column.home": "Home",
|
||||
"column.mutes": "Utenti silenziati",
|
||||
"column.notifications": "Notifiche",
|
||||
"column.pins": "Pinned toot",
|
||||
"column.public": "Timeline federata",
|
||||
"column_back_button.label": "Indietro",
|
||||
"column_header.hide_settings": "Hide settings",
|
||||
|
@ -109,6 +110,7 @@
|
|||
"navigation_bar.info": "Informazioni estese",
|
||||
"navigation_bar.logout": "Logout",
|
||||
"navigation_bar.mutes": "Utenti silenziati",
|
||||
"navigation_bar.pins": "Pinned toots",
|
||||
"navigation_bar.preferences": "Impostazioni",
|
||||
"navigation_bar.public_timeline": "Timeline federata",
|
||||
"notification.favourite": "{name} ha apprezzato il tuo post",
|
||||
|
@ -193,6 +195,15 @@
|
|||
"upload_button.label": "Aggiungi file multimediale",
|
||||
"upload_form.undo": "Annulla",
|
||||
"upload_progress.label": "Sto caricando...",
|
||||
"video.close": "Close video",
|
||||
"video.exit_fullscreen": "Exit full screen",
|
||||
"video.expand": "Expand video",
|
||||
"video.fullscreen": "Full screen",
|
||||
"video.hide": "Hide video",
|
||||
"video.mute": "Mute sound",
|
||||
"video.pause": "Pause",
|
||||
"video.play": "Play",
|
||||
"video.unmute": "Unmute sound",
|
||||
"video_player.expand": "Espandi video",
|
||||
"video_player.toggle_sound": "Attiva suono",
|
||||
"video_player.toggle_visible": "Attiva visibilità",
|
||||
|
|
|
@ -33,8 +33,8 @@
|
|||
"column.home": "ホーム",
|
||||
"column.mutes": "ミュートしたユーザー",
|
||||
"column.notifications": "通知",
|
||||
"column.public": "連合タイムライン",
|
||||
"column.pins": "固定されたトゥート",
|
||||
"column.public": "連合タイムライン",
|
||||
"column_back_button.label": "戻る",
|
||||
"column_header.hide_settings": "設定を隠す",
|
||||
"column_header.moveLeft_settings": "カラムを左に移動する",
|
||||
|
@ -97,8 +97,8 @@
|
|||
"home.column_settings.show_replies": "返信表示",
|
||||
"home.settings": "カラム設定",
|
||||
"lightbox.close": "閉じる",
|
||||
"lightbox.next": "Next",
|
||||
"lightbox.previous": "Previous",
|
||||
"lightbox.next": "次",
|
||||
"lightbox.previous": "前",
|
||||
"loading_indicator.label": "読み込み中...",
|
||||
"media_gallery.toggle_visible": "表示切り替え",
|
||||
"missing_indicator.label": "見つかりません",
|
||||
|
@ -110,9 +110,9 @@
|
|||
"navigation_bar.info": "このインスタンスについて",
|
||||
"navigation_bar.logout": "ログアウト",
|
||||
"navigation_bar.mutes": "ミュートしたユーザー",
|
||||
"navigation_bar.pins": "固定されたトゥート",
|
||||
"navigation_bar.preferences": "ユーザー設定",
|
||||
"navigation_bar.public_timeline": "連合タイムライン",
|
||||
"navigation_bar.pins": "固定されたトゥート",
|
||||
"notification.favourite": "{name}さんがあなたのトゥートをお気に入りに登録しました",
|
||||
"notification.follow": "{name}さんにフォローされました",
|
||||
"notification.mention": "{name}さんがあなたに返信しました",
|
||||
|
@ -195,6 +195,15 @@
|
|||
"upload_button.label": "メディアを追加",
|
||||
"upload_form.undo": "やり直す",
|
||||
"upload_progress.label": "アップロード中...",
|
||||
"video.close": "動画を閉じる",
|
||||
"video.exit_fullscreen": "全画面を終了する",
|
||||
"video.expand": "動画を拡大する",
|
||||
"video.fullscreen": "全画面",
|
||||
"video.hide": "動画を閉じる",
|
||||
"video.mute": "ミュート",
|
||||
"video.pause": "一時停止",
|
||||
"video.play": "再生",
|
||||
"video.unmute": "ミュートを解除する",
|
||||
"video_player.expand": "動画の詳細",
|
||||
"video_player.toggle_sound": "音の切り替え",
|
||||
"video_player.toggle_visible": "表示切り替え",
|
||||
|
|
|
@ -33,8 +33,8 @@
|
|||
"column.home": "홈",
|
||||
"column.mutes": "뮤트 중인 사용자",
|
||||
"column.notifications": "알림",
|
||||
"column.public": "연합 타임라인",
|
||||
"column.pins": "고정된 Toot",
|
||||
"column.public": "연합 타임라인",
|
||||
"column_back_button.label": "돌아가기",
|
||||
"column_header.hide_settings": "Hide settings",
|
||||
"column_header.moveLeft_settings": "Move column to the left",
|
||||
|
@ -110,9 +110,9 @@
|
|||
"navigation_bar.info": "이 인스턴스에 대해서",
|
||||
"navigation_bar.logout": "로그아웃",
|
||||
"navigation_bar.mutes": "뮤트 중인 사용자",
|
||||
"navigation_bar.pins": "고정된 Toot",
|
||||
"navigation_bar.preferences": "사용자 설정",
|
||||
"navigation_bar.public_timeline": "연합 타임라인",
|
||||
"navigation_bar.pins": "고정된 Toot",
|
||||
"notification.favourite": "{name}님이 즐겨찾기 했습니다",
|
||||
"notification.follow": "{name}님이 나를 팔로우 했습니다",
|
||||
"notification.mention": "{name}님이 답글을 보냈습니다",
|
||||
|
@ -195,6 +195,15 @@
|
|||
"upload_button.label": "미디어 추가",
|
||||
"upload_form.undo": "재시도",
|
||||
"upload_progress.label": "업로드 중...",
|
||||
"video.close": "Close video",
|
||||
"video.exit_fullscreen": "Exit full screen",
|
||||
"video.expand": "Expand video",
|
||||
"video.fullscreen": "Full screen",
|
||||
"video.hide": "Hide video",
|
||||
"video.mute": "Mute sound",
|
||||
"video.pause": "Pause",
|
||||
"video.play": "Play",
|
||||
"video.unmute": "Unmute sound",
|
||||
"video_player.expand": "동영상 자세히 보기",
|
||||
"video_player.toggle_sound": "소리 토글하기",
|
||||
"video_player.toggle_visible": "표시 전환",
|
||||
|
|
|
@ -12,7 +12,7 @@
|
|||
"account.mute": "Negeer @{name}",
|
||||
"account.posts": "Toots",
|
||||
"account.report": "Rapporteer @{name}",
|
||||
"account.requested": "Wacht op goedkeuring",
|
||||
"account.requested": "Wacht op goedkeuring. Klik om volgverzoek te annuleren.",
|
||||
"account.share": "Profiel van @{name} delen",
|
||||
"account.unblock": "Deblokkeer @{name}",
|
||||
"account.unblock_domain": "{domain} niet meer negeren",
|
||||
|
@ -33,11 +33,13 @@
|
|||
"column.home": "Start",
|
||||
"column.mutes": "Genegeerde gebruikers",
|
||||
"column.notifications": "Meldingen",
|
||||
"column.pins": "Pinned toot",
|
||||
"column.public": "Globale tijdlijn",
|
||||
"column.pins": "Vastgezette toots",
|
||||
"column_back_button.label": "terug",
|
||||
"column_header.hide_settings": "Instellingen verbergen",
|
||||
"column_header.moveLeft_settings": "Move column to the left",
|
||||
"column_header.moveRight_settings": "Move column to the right",
|
||||
"column_header.moveLeft_settings": "Kolom naar links verplaatsen",
|
||||
"column_header.moveRight_settings": "Kolom naar rechts verplaatsen",
|
||||
"column_header.pin": "Vastmaken",
|
||||
"column_header.show_settings": "Instellingen tonen",
|
||||
"column_header.unpin": "Losmaken",
|
||||
|
@ -63,8 +65,8 @@
|
|||
"confirmations.mute.message": "Weet je het zeker dat je {name} wilt negeren?",
|
||||
"confirmations.unfollow.confirm": "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:",
|
||||
"embed.instructions": "Embed deze toot op jouw website, door de onderstaande code te kopiëren.",
|
||||
"embed.preview": "Zo komt het eruit te zien:",
|
||||
"emoji_button.activity": "Activiteiten",
|
||||
"emoji_button.flags": "Vlaggen",
|
||||
"emoji_button.food": "Eten en drinken",
|
||||
|
@ -85,6 +87,7 @@
|
|||
"follow_request.authorize": "Goedkeuren",
|
||||
"follow_request.reject": "Afkeuren",
|
||||
"getting_started.appsshort": "Apps",
|
||||
"getting_started.donate": "Doneren",
|
||||
"getting_started.faq": "FAQ",
|
||||
"getting_started.heading": "Beginnen",
|
||||
"getting_started.open_source_notice": "Mastodon is open-sourcesoftware. Je kunt bijdragen of problemen melden op GitHub via {github}.",
|
||||
|
@ -109,8 +112,10 @@
|
|||
"navigation_bar.info": "Uitgebreide informatie",
|
||||
"navigation_bar.logout": "Afmelden",
|
||||
"navigation_bar.mutes": "Genegeerde gebruikers",
|
||||
"navigation_bar.pins": "Pinned toots",
|
||||
"navigation_bar.preferences": "Instellingen",
|
||||
"navigation_bar.public_timeline": "Globale tijdlijn",
|
||||
"navigation_bar.pins": "Vastgezette toots",
|
||||
"notification.favourite": "{name} markeerde jouw toot als favoriet",
|
||||
"notification.follow": "{name} volgt jou nu",
|
||||
"notification.mention": "{name} vermeldde jou",
|
||||
|
@ -171,7 +176,7 @@
|
|||
"status.mention": "Vermeld @{name}",
|
||||
"status.mute_conversation": "Negeer conversatie",
|
||||
"status.open": "Toot volledig tonen",
|
||||
"status.pin": "Pin on profile",
|
||||
"status.pin": "Aan profielpagina vastmaken",
|
||||
"status.reblog": "Boost",
|
||||
"status.reblogged_by": "{name} boostte",
|
||||
"status.reply": "Reageren",
|
||||
|
@ -183,7 +188,7 @@
|
|||
"status.show_less": "Minder tonen",
|
||||
"status.show_more": "Meer tonen",
|
||||
"status.unmute_conversation": "Conversatie niet meer negeren",
|
||||
"status.unpin": "Unpin from profile",
|
||||
"status.unpin": "Van profielpagina losmaken",
|
||||
"tabs_bar.compose": "Schrijven",
|
||||
"tabs_bar.federated_timeline": "Globaal",
|
||||
"tabs_bar.home": "Start",
|
||||
|
@ -193,6 +198,15 @@
|
|||
"upload_button.label": "Media toevoegen",
|
||||
"upload_form.undo": "Ongedaan maken",
|
||||
"upload_progress.label": "Uploaden...",
|
||||
"video.close": "Close video",
|
||||
"video.exit_fullscreen": "Exit full screen",
|
||||
"video.expand": "Video groter maken",
|
||||
"video.fullscreen": "Volledig scherm",
|
||||
"video.hide": "Video verbergen",
|
||||
"video.mute": "Geluid uitschakelen",
|
||||
"video.pause": "Pauze",
|
||||
"video.play": "Afspelen",
|
||||
"video.unmute": "Geluid inschakelen",
|
||||
"video_player.expand": "Video groter maken",
|
||||
"video_player.toggle_sound": "Geluid in-/uitschakelen",
|
||||
"video_player.toggle_visible": "Video wel/niet tonen",
|
||||
|
|
|
@ -33,6 +33,7 @@
|
|||
"column.home": "Hjem",
|
||||
"column.mutes": "Dempede brukere",
|
||||
"column.notifications": "Varsler",
|
||||
"column.pins": "Pinned toot",
|
||||
"column.public": "Felles tidslinje",
|
||||
"column_back_button.label": "Tilbake",
|
||||
"column_header.hide_settings": "Hide settings",
|
||||
|
@ -109,6 +110,7 @@
|
|||
"navigation_bar.info": "Utvidet informasjon",
|
||||
"navigation_bar.logout": "Logg ut",
|
||||
"navigation_bar.mutes": "Dempede brukere",
|
||||
"navigation_bar.pins": "Pinned toots",
|
||||
"navigation_bar.preferences": "Preferanser",
|
||||
"navigation_bar.public_timeline": "Felles tidslinje",
|
||||
"notification.favourite": "{name} likte din status",
|
||||
|
@ -193,6 +195,15 @@
|
|||
"upload_button.label": "Legg til media",
|
||||
"upload_form.undo": "Angre",
|
||||
"upload_progress.label": "Laster opp...",
|
||||
"video.close": "Close video",
|
||||
"video.exit_fullscreen": "Exit full screen",
|
||||
"video.expand": "Expand video",
|
||||
"video.fullscreen": "Full screen",
|
||||
"video.hide": "Hide video",
|
||||
"video.mute": "Mute sound",
|
||||
"video.pause": "Pause",
|
||||
"video.play": "Play",
|
||||
"video.unmute": "Unmute sound",
|
||||
"video_player.expand": "Utvid video",
|
||||
"video_player.toggle_sound": "Veksle lyd",
|
||||
"video_player.toggle_visible": "Veksle synlighet",
|
||||
|
|
|
@ -33,8 +33,8 @@
|
|||
"column.home": "Acuèlh",
|
||||
"column.mutes": "Personas en silenci",
|
||||
"column.notifications": "Notificacions",
|
||||
"column.public": "Flux public global",
|
||||
"column.pins": "Tuts penjats",
|
||||
"column.public": "Flux public global",
|
||||
"column_back_button.label": "Tornar",
|
||||
"column_header.hide_settings": "Amagar los paramètres",
|
||||
"column_header.moveLeft_settings": "Desplaçar la colomna a man drecha",
|
||||
|
@ -64,7 +64,7 @@
|
|||
"confirmations.mute.message": "Sètz segur de voler metre en silenci {name} ?",
|
||||
"confirmations.unfollow.confirm": "Quitar de sègre",
|
||||
"confirmations.unfollow.message": "Volètz vertadièrament quitar de sègre {name} ?",
|
||||
"embed.instructions": "Embarcar aqueste estatut per o far veire sus un site Internet en copiar lo còdi çai-jos.",
|
||||
"embed.instructions": "Embarcar aqueste estatut per lo far veire sus un site Internet en copiar lo còdi çai-jos.",
|
||||
"embed.preview": "Semblarà aquò : ",
|
||||
"emoji_button.activity": "Activitats",
|
||||
"emoji_button.flags": "Drapèus",
|
||||
|
@ -110,9 +110,9 @@
|
|||
"navigation_bar.info": "Mai informacions",
|
||||
"navigation_bar.logout": "Desconnexion",
|
||||
"navigation_bar.mutes": "Personas rescondudas",
|
||||
"navigation_bar.pins": "Tuts penjats",
|
||||
"navigation_bar.preferences": "Preferéncias",
|
||||
"navigation_bar.public_timeline": "Flux public global",
|
||||
"navigation_bar.pins": "Tuts penjats",
|
||||
"notification.favourite": "{name} a ajustat a sos favorits :",
|
||||
"notification.follow": "{name} vos sèc",
|
||||
"notification.mention": "{name} vos a mencionat :",
|
||||
|
@ -195,6 +195,15 @@
|
|||
"upload_button.label": "Ajustar un mèdia",
|
||||
"upload_form.undo": "Anullar",
|
||||
"upload_progress.label": "Mandadís…",
|
||||
"video.close": "Tampar la vidèo",
|
||||
"video.exit_fullscreen": "Sortir plen ecran",
|
||||
"video.expand": "Agrandir la vidèo",
|
||||
"video.fullscreen": "Ecran complet",
|
||||
"video.hide": "Amagar la vidèo",
|
||||
"video.mute": "Copar lo son",
|
||||
"video.pause": "Pausa",
|
||||
"video.play": "Lectura",
|
||||
"video.unmute": "Restablir lo son",
|
||||
"video_player.expand": "Mostrar la vidèo",
|
||||
"video_player.toggle_sound": "Activar/Desactivar lo son",
|
||||
"video_player.toggle_visible": "Mostrar/Rescondre la vidèo",
|
||||
|
|
|
@ -195,7 +195,16 @@
|
|||
"upload_button.label": "Dodaj zawartość multimedialną",
|
||||
"upload_form.undo": "Cofnij",
|
||||
"upload_progress.label": "Wysyłanie",
|
||||
"video_player.expand": "Przełącz wideo",
|
||||
"video.close": "Zamknij film",
|
||||
"video.exit_fullscreen": "Opuść tryb pełnoekranowy",
|
||||
"video.expand": "Rozszerz film",
|
||||
"video.fullscreen": "Pełny ekran",
|
||||
"video.hide": "Ukryj film",
|
||||
"video.mute": "Wycisz",
|
||||
"video.pause": "Pauzuj",
|
||||
"video.play": "Odtwórz",
|
||||
"video.unmute": "Cofnij wyciszenie",
|
||||
"video_player.expand": "Rozszerz film",
|
||||
"video_player.toggle_sound": "Przełącz dźwięk",
|
||||
"video_player.toggle_visible": "Przełącz widoczność",
|
||||
"video_player.video_error": "Nie można odtworzyć pliku wideo"
|
||||
|
|
|
@ -6,25 +6,25 @@
|
|||
"account.follow": "Seguir",
|
||||
"account.followers": "Seguidores",
|
||||
"account.follows": "Segue",
|
||||
"account.follows_you": "É seu seguidor",
|
||||
"account.follows_you": "Segue você",
|
||||
"account.media": "Mídia",
|
||||
"account.mention": "Mencionar @{name}",
|
||||
"account.mute": "Silenciar @{name}",
|
||||
"account.posts": "Posts",
|
||||
"account.report": "Denunciar @{name}",
|
||||
"account.requested": "Aguardando aprovação",
|
||||
"account.requested": "Aguardando aprovação. Clique para cancelar a solicitação.",
|
||||
"account.share": "Compartilhar perfil de @{name}",
|
||||
"account.unblock": "Não bloquear @{name}",
|
||||
"account.unblock": "Desbloquear @{name}",
|
||||
"account.unblock_domain": "Desbloquear {domain}",
|
||||
"account.unfollow": "Deixar de seguir",
|
||||
"account.unmute": "Não silenciar @{name}",
|
||||
"account.view_full_profile": "Ver perfil completo",
|
||||
"boost_modal.combo": "Pode clicar {combo} para não voltar a ver",
|
||||
"bundle_column_error.body": "Something went wrong while loading this component.",
|
||||
"boost_modal.combo": "Você pode pressionar {combo} para ignorar este diálogo na próxima vez",
|
||||
"bundle_column_error.body": "Algo de errado aconteceu enquanto este componente era carregado.",
|
||||
"bundle_column_error.retry": "Tente novamente",
|
||||
"bundle_column_error.title": "Network error",
|
||||
"bundle_column_error.title": "Erro de rede",
|
||||
"bundle_modal_error.close": "Fechar",
|
||||
"bundle_modal_error.message": "Something went wrong while loading this component.",
|
||||
"bundle_modal_error.message": "Algo de errado aconteceu enquanto este componente era carregado.",
|
||||
"bundle_modal_error.retry": "Tente novamente",
|
||||
"column.blocks": "Usuários bloqueados",
|
||||
"column.community": "Local",
|
||||
|
@ -33,7 +33,9 @@
|
|||
"column.home": "Página inicial",
|
||||
"column.mutes": "Usuários silenciados",
|
||||
"column.notifications": "Notificações",
|
||||
"column.pins": "Postagens fixadas",
|
||||
"column.public": "Global",
|
||||
"column.pins": "Postagens fixadas",
|
||||
"column_back_button.label": "Voltar",
|
||||
"column_header.hide_settings": "Esconder configurações",
|
||||
"column_header.moveLeft_settings": "Mover coluna para a esquerda",
|
||||
|
@ -43,156 +45,169 @@
|
|||
"column_header.unpin": "Desafixar",
|
||||
"column_subheading.navigation": "Navegação",
|
||||
"column_subheading.settings": "Configurações",
|
||||
"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": "A sua conta não está {locked}. Qualquer pessoa pode te seguir e visualizar postagens direcionadas a apenas seguidores.",
|
||||
"compose_form.lock_disclaimer.lock": "trancado",
|
||||
"compose_form.placeholder": "No que você está pensando?",
|
||||
"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.privacy_disclaimer": "O seu conteúdo privado será compartilhado com os usuários de {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 destinatários indesejados.",
|
||||
"compose_form.publish": "Publicar",
|
||||
"compose_form.publish_loud": "{publish}!",
|
||||
"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 de conteúdo",
|
||||
"compose_form.spoiler_placeholder": "Aviso de conteúdo",
|
||||
"confirmation_modal.cancel": "Cancelar",
|
||||
"confirmations.block.confirm": "Bloquear",
|
||||
"confirmations.block.message": "Você tem certeza de que quer bloquear {name}?",
|
||||
"confirmations.delete.confirm": "Excluir",
|
||||
"confirmations.delete.message": "Você tem certeza de que quer excluir este status?",
|
||||
"confirmations.delete.message": "Você tem certeza de que quer excluir esta postagem?",
|
||||
"confirmations.domain_block.confirm": "Esconder o domínio inteiro",
|
||||
"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": "Silenciar",
|
||||
"confirmations.mute.message": "Você tem certeza de que quer silenciar {name}?",
|
||||
"confirmations.unfollow.confirm": "Deixar de seguir",
|
||||
"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.flags": "Flags",
|
||||
"emoji_button.food": "Food & Drink",
|
||||
"embed.instructions": "Incorpore esta postagem em seu site copiando o código abaixo:",
|
||||
"embed.preview": "Aqui está uma previsão de como ficará:",
|
||||
"emoji_button.activity": "Atividades",
|
||||
"emoji_button.flags": "Bandeiras",
|
||||
"emoji_button.food": "Comidas & Bebidas",
|
||||
"emoji_button.label": "Inserir Emoji",
|
||||
"emoji_button.nature": "Nature",
|
||||
"emoji_button.objects": "Objects",
|
||||
"emoji_button.people": "People",
|
||||
"emoji_button.search": "Search...",
|
||||
"emoji_button.symbols": "Symbols",
|
||||
"emoji_button.travel": "Travel & Places",
|
||||
"empty_column.community": "Ainda não existem conteúdo local para mostrar!",
|
||||
"empty_column.hashtag": "Ainda não existe qualquer conteúdo com essa hashtag",
|
||||
"empty_column.home": "Ainda não segues qualquer utilizador. Visita {public} ou utiliza a pesquisa para procurar outros utilizadores.",
|
||||
"empty_column.home.inactivity": "Your home feed is empty. If you have been inactive for a while, it will be regenerated for you soon.",
|
||||
"emoji_button.nature": "Natureza",
|
||||
"emoji_button.objects": "Objetos",
|
||||
"emoji_button.people": "Pessoas",
|
||||
"emoji_button.search": "Buscar...",
|
||||
"emoji_button.symbols": "Símbolos",
|
||||
"emoji_button.travel": "Viagens & Lugares",
|
||||
"empty_column.community": "A timeline local está vazia. Escreva algo publicamente para começar!",
|
||||
"empty_column.hashtag": "Ainda não há qualquer conteúdo com essa hashtag",
|
||||
"empty_column.home": "Você ainda não segue usuário algo. Visite a timeline {public} ou use o buscador para procurar e conhecer outros usuários.",
|
||||
"empty_column.home.inactivity": "A sua página inicial está vazia. Se você esteve inativo por um tempo, ela irá se regenerar em alguns intantes.",
|
||||
"empty_column.home.public_timeline": "global",
|
||||
"empty_column.notifications": "Não tens notificações. Interage com outros utilizadores para iniciar uma conversa.",
|
||||
"empty_column.public": "Não há nada aqui! Escreve algo publicamente ou segue outros utilizadores para ver aqui os conteúdos públicos.",
|
||||
"empty_column.notifications": "Você ainda não possui notificações. Interaja com outros usuários para começar a conversar!",
|
||||
"empty_column.public": "Não há nada aqui! Escreva algo publicamente ou siga manualmente usuários de outras instâncias.",
|
||||
"follow_request.authorize": "Autorizar",
|
||||
"follow_request.reject": "Rejeitar",
|
||||
"getting_started.appsshort": "Apps",
|
||||
"getting_started.faq": "FAQ",
|
||||
"getting_started.heading": "Primeiros passos",
|
||||
"getting_started.open_source_notice": "Mastodon é software de fonte aberta. Podes contribuir ou repostar problemas no GitHub do projecto: {github}.",
|
||||
"getting_started.userguide": "User Guide",
|
||||
"getting_started.open_source_notice": "Mastodon é um software de código aberto. Você pode contribuir ou reportar problemas na página do GitHub do projeto: {github}.",
|
||||
"getting_started.userguide": "Guia de usuário",
|
||||
"home.column_settings.advanced": "Avançado",
|
||||
"home.column_settings.basic": "Básico",
|
||||
"home.column_settings.filter_regex": "Filtrar com uma expressão regular",
|
||||
"home.column_settings.show_reblogs": "Mostrar as partilhas",
|
||||
"home.column_settings.show_reblogs": "Mostrar compartilhamentos",
|
||||
"home.column_settings.show_replies": "Mostrar as respostas",
|
||||
"home.settings": "Parâmetros da listagem",
|
||||
"home.settings": "Configurações de colunas",
|
||||
"lightbox.close": "Fechar",
|
||||
"lightbox.next": "Next",
|
||||
"lightbox.previous": "Previous",
|
||||
"lightbox.next": "Próximo",
|
||||
"lightbox.previous": "Anterior",
|
||||
"loading_indicator.label": "Carregando...",
|
||||
"media_gallery.toggle_visible": "Esconder/Mostrar",
|
||||
"missing_indicator.label": "Não encontrado",
|
||||
"navigation_bar.blocks": "Utilizadores bloqueados",
|
||||
"navigation_bar.blocks": "Usuários bloqueados",
|
||||
"navigation_bar.community_timeline": "Local",
|
||||
"navigation_bar.edit_profile": "Editar perfil",
|
||||
"navigation_bar.favourites": "Favoritos",
|
||||
"navigation_bar.follow_requests": "Seguidores pendentes",
|
||||
"navigation_bar.info": "Mais informações",
|
||||
"navigation_bar.logout": "Sair",
|
||||
"navigation_bar.mutes": "Utilizadores silenciados",
|
||||
"navigation_bar.mutes": "Usuários silenciados",
|
||||
"navigation_bar.pins": "Postagens fixadas",
|
||||
"navigation_bar.preferences": "Preferências",
|
||||
"navigation_bar.public_timeline": "Global",
|
||||
"notification.favourite": "{name} adicionou o teu post aos favoritos",
|
||||
"notification.follow": "{name} seguiu-te",
|
||||
"notification.mention": "{name} mencionou-te",
|
||||
"notification.reblog": "{name} partilhou o teu post",
|
||||
"navigation_bar.preferences": "Preferências",
|
||||
"navigation_bar.public_timeline": "Global",
|
||||
"navigation_bar.pins": "Postagens fixadas",
|
||||
"notification.favourite": "{name} adicionou a sua postagem aos favoritos",
|
||||
"notification.follow": "{name} te seguiu",
|
||||
"notification.mention": "{name} te mencionou",
|
||||
"notification.reblog": "{name} compartilhou a sua postagem",
|
||||
"notifications.clear": "Limpar notificações",
|
||||
"notifications.clear_confirmation": "Queres mesmo limpar todas as notificações?",
|
||||
"notifications.clear_confirmation": "Você tem certeza de que quer limpar todas as suas notificações permanentemente?",
|
||||
"notifications.column_settings.alert": "Notificações no computador",
|
||||
"notifications.column_settings.favourite": "Favoritos:",
|
||||
"notifications.column_settings.follow": "Novos seguidores:",
|
||||
"notifications.column_settings.mention": "Menções:",
|
||||
"notifications.column_settings.push": "Push notifications",
|
||||
"notifications.column_settings.push_meta": "This device",
|
||||
"notifications.column_settings.reblog": "Partilhas:",
|
||||
"notifications.column_settings.push": "Enviar notificações",
|
||||
"notifications.column_settings.push_meta": "Este aparelho",
|
||||
"notifications.column_settings.reblog": "Compartilhamento:",
|
||||
"notifications.column_settings.show": "Mostrar nas colunas",
|
||||
"notifications.column_settings.sound": "Reproduzir som",
|
||||
"onboarding.done": "Done",
|
||||
"onboarding.next": "Next",
|
||||
"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_four.home": "The home timeline shows posts from people you follow.",
|
||||
"onboarding.page_four.notifications": "The notifications column shows when someone interacts with you.",
|
||||
"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.handle": "You are on {domain}, so your full handle is {handle}",
|
||||
"onboarding.page_one.welcome": "Welcome to Mastodon!",
|
||||
"onboarding.page_six.admin": "Your instance's admin is {admin}.",
|
||||
"onboarding.page_six.almost_done": "Almost done...",
|
||||
"onboarding.done": "Pronto",
|
||||
"onboarding.next": "Próximo",
|
||||
"onboarding.page_five.public_timelines": "A timeline local mostra postagens públicas de todos os usuários no {domain}. A timeline federada mostra todas as postagens de todas as pessoas que pessoas no {domain} seguem. Estas são as timelines públicas, uma ótima maneira de conhecer novas pessoas.",
|
||||
"onboarding.page_four.home": "A página inicial mostra postagens de pessoas que você segue.",
|
||||
"onboarding.page_four.notifications": "A coluna de notificações te mostra quando alguém interage com você.",
|
||||
"onboarding.page_one.federation": "Mastodon é uma rede d servidores independentes se juntando para fazer uma grande rede social. Nós chamamos estes servidores de instâncias.",
|
||||
"onboarding.page_one.handle": "Você está no {domain}, então o seu nome de usuário completo é {handle}",
|
||||
"onboarding.page_one.welcome": "Seja bem-vindo(a) ao Mastodon!",
|
||||
"onboarding.page_six.admin": "O administrador de sua instância é {admin}.",
|
||||
"onboarding.page_six.almost_done": "Quase acabando...",
|
||||
"onboarding.page_six.appetoot": "Bon Appetoot!",
|
||||
"onboarding.page_six.apps_available": "There are {apps} available for iOS, Android and other platforms.",
|
||||
"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.guidelines": "community guidelines",
|
||||
"onboarding.page_six.read_guidelines": "Please read {domain}'s {guidelines}!",
|
||||
"onboarding.page_six.various_app": "mobile apps",
|
||||
"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.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_two.compose": "Write posts from the compose column. You can upload images, change privacy settings, and add content warnings with the icons below.",
|
||||
"onboarding.skip": "Skip",
|
||||
"onboarding.page_six.apps_available": "Há {apps} disponíveis para iOS, Android e outras plataformas.",
|
||||
"onboarding.page_six.github": "Mastodon é um software gratuito e de código aberto. Você pode reportar bugs, prequisitar novas funções ou contribuir para o código no {github}.",
|
||||
"onboarding.page_six.guidelines": "diretrizes da comunidade",
|
||||
"onboarding.page_six.read_guidelines": "Por favor, leia as {guidelines} do {domain}!",
|
||||
"onboarding.page_six.various_app": "aplicativos móveis",
|
||||
"onboarding.page_three.profile": "Edite o seu perfil para mudar o seu o seu avatar, bio e nome de exibição. No menu de configurações, você também encontrará outras preferências.",
|
||||
"onboarding.page_three.search": "Use a barra de buscas para encontrar pessoas e consultar hashtahs, como #illustrations e #introductions. Para procurar por uma pessoa que não estiver nesta instância, use o nome de usuário completo dela.",
|
||||
"onboarding.page_two.compose": "Escreva postagens na coluna de escrita. Você pode hospedar imagens, mudar as configurações de privacidade e adicionar alertas de conteúdo através dos ícones abaixo.",
|
||||
"onboarding.skip": "Pular",
|
||||
"privacy.change": "Ajustar a privacidade da mensagem",
|
||||
"privacy.direct.long": "Apenas para utilizadores mencionados",
|
||||
"privacy.direct.short": "Directo",
|
||||
"privacy.private.long": "Apenas para os seguidores",
|
||||
"privacy.private.short": "Privado",
|
||||
"privacy.direct.long": "Apenas para usuários mencionados",
|
||||
"privacy.direct.short": "Direta",
|
||||
"privacy.private.long": "Apenas para seus seguidores",
|
||||
"privacy.private.short": "Privada",
|
||||
"privacy.public.long": "Publicar em todos os feeds",
|
||||
"privacy.public.short": "Público",
|
||||
"privacy.unlisted.long": "Não publicar nos feeds públicos",
|
||||
"privacy.unlisted.short": "Não listar",
|
||||
"privacy.public.short": "Pública",
|
||||
"privacy.unlisted.long": "Não publicar em feeds públicos",
|
||||
"privacy.unlisted.short": "Não listada",
|
||||
"reply_indicator.cancel": "Cancelar",
|
||||
"report.placeholder": "Comentários adicionais",
|
||||
"report.submit": "Enviar",
|
||||
"report.target": "Denunciar",
|
||||
"search.placeholder": "Pesquisar",
|
||||
"search_results.total": "{count, number} {count, plural, one {resultado} other {resultados}}",
|
||||
"standalone.public_title": "A look inside...",
|
||||
"status.cannot_reblog": "This post cannot be boosted",
|
||||
"standalone.public_title": "Dê uma espiada...",
|
||||
"status.cannot_reblog": "Esta postagem não pode ser compartilhada",
|
||||
"status.delete": "Eliminar",
|
||||
"status.embed": "Embed",
|
||||
"status.embed": "Incorporar",
|
||||
"status.favourite": "Adicionar aos favoritos",
|
||||
"status.load_more": "Carregar mais",
|
||||
"status.media_hidden": "Media escondida",
|
||||
"status.media_hidden": "Mídia escondida",
|
||||
"status.mention": "Mencionar @{name}",
|
||||
"status.mute_conversation": "Mute conversation",
|
||||
"status.mute_conversation": "Silenciar conversa",
|
||||
"status.open": "Expandir",
|
||||
"status.pin": "Pin on profile",
|
||||
"status.reblog": "Partilhar",
|
||||
"status.reblogged_by": "{name} partilhou",
|
||||
"status.pin": "Fixar no perfil",
|
||||
"status.reblog": "Compartilhar",
|
||||
"status.reblogged_by": "{name} compartilhou",
|
||||
"status.reply": "Responder",
|
||||
"status.replyAll": "Reply to thread",
|
||||
"status.report": "Denúnciar @{name}",
|
||||
"status.replyAll": "Responder à sequência",
|
||||
"status.report": "Denunciar @{name}",
|
||||
"status.sensitive_toggle": "Clique para ver",
|
||||
"status.sensitive_warning": "Conteúdo sensível",
|
||||
"status.share": "Share",
|
||||
"status.share": "Compartilhar",
|
||||
"status.show_less": "Mostrar menos",
|
||||
"status.show_more": "Mostrar mais",
|
||||
"status.unmute_conversation": "Unmute conversation",
|
||||
"status.unpin": "Unpin from profile",
|
||||
"status.unmute_conversation": "Desativar silêncio desta conversa",
|
||||
"status.unpin": "Desafixar do perfil",
|
||||
"tabs_bar.compose": "Criar",
|
||||
"tabs_bar.federated_timeline": "Global",
|
||||
"tabs_bar.home": "Home",
|
||||
"tabs_bar.home": "Página inicial",
|
||||
"tabs_bar.local_timeline": "Local",
|
||||
"tabs_bar.notifications": "Notificações",
|
||||
"upload_area.title": "Arraste e solte para enviar",
|
||||
"upload_button.label": "Adicionar media",
|
||||
"upload_button.label": "Adicionar mídia",
|
||||
"upload_form.undo": "Anular",
|
||||
"upload_progress.label": "A gravar...",
|
||||
"upload_progress.label": "Salvando...",
|
||||
"video.close": "Fechar vídeo",
|
||||
"video.exit_fullscreen": "Sair da tela cheia",
|
||||
"video.expand": "Expandir vídeo",
|
||||
"video.fullscreen": "Tela cheia",
|
||||
"video.hide": "Esconder vídeo",
|
||||
"video.mute": "Silenciar vídeo",
|
||||
"video.pause": "Parar",
|
||||
"video.play": "Reproduzir",
|
||||
"video.unmute": "Retirar silêncio",
|
||||
"video_player.expand": "Expandir vídeo",
|
||||
"video_player.toggle_sound": "Ligar/Desligar som",
|
||||
"video_player.toggle_visible": "Ligar/Desligar vídeo",
|
||||
|
|
|
@ -33,6 +33,7 @@
|
|||
"column.home": "Home",
|
||||
"column.mutes": "Utilizadores silenciados",
|
||||
"column.notifications": "Notificações",
|
||||
"column.pins": "Pinned toot",
|
||||
"column.public": "Global",
|
||||
"column_back_button.label": "Voltar",
|
||||
"column_header.hide_settings": "Hide settings",
|
||||
|
@ -109,6 +110,7 @@
|
|||
"navigation_bar.info": "Mais informações",
|
||||
"navigation_bar.logout": "Sair",
|
||||
"navigation_bar.mutes": "Utilizadores silenciados",
|
||||
"navigation_bar.pins": "Pinned toots",
|
||||
"navigation_bar.preferences": "Preferências",
|
||||
"navigation_bar.public_timeline": "Global",
|
||||
"notification.favourite": "{name} adicionou o teu post aos favoritos",
|
||||
|
@ -193,6 +195,15 @@
|
|||
"upload_button.label": "Adicionar media",
|
||||
"upload_form.undo": "Anular",
|
||||
"upload_progress.label": "A gravar...",
|
||||
"video.close": "Close video",
|
||||
"video.exit_fullscreen": "Exit full screen",
|
||||
"video.expand": "Expand video",
|
||||
"video.fullscreen": "Full screen",
|
||||
"video.hide": "Hide video",
|
||||
"video.mute": "Mute sound",
|
||||
"video.pause": "Pause",
|
||||
"video.play": "Play",
|
||||
"video.unmute": "Unmute sound",
|
||||
"video_player.expand": "Expandir vídeo",
|
||||
"video_player.toggle_sound": "Ligar/Desligar som",
|
||||
"video_player.toggle_visible": "Ligar/Desligar vídeo",
|
||||
|
|
|
@ -33,6 +33,7 @@
|
|||
"column.home": "Главная",
|
||||
"column.mutes": "Список глушения",
|
||||
"column.notifications": "Уведомления",
|
||||
"column.pins": "Pinned toot",
|
||||
"column.public": "Глобальная лента",
|
||||
"column_back_button.label": "Назад",
|
||||
"column_header.hide_settings": "Скрыть настройки",
|
||||
|
@ -109,6 +110,7 @@
|
|||
"navigation_bar.info": "Об узле",
|
||||
"navigation_bar.logout": "Выйти",
|
||||
"navigation_bar.mutes": "Список глушения",
|
||||
"navigation_bar.pins": "Pinned toots",
|
||||
"navigation_bar.preferences": "Опции",
|
||||
"navigation_bar.public_timeline": "Глобальная лента",
|
||||
"notification.favourite": "{name} понравился Ваш статус",
|
||||
|
@ -193,6 +195,15 @@
|
|||
"upload_button.label": "Добавить медиаконтент",
|
||||
"upload_form.undo": "Отменить",
|
||||
"upload_progress.label": "Загрузка...",
|
||||
"video.close": "Close video",
|
||||
"video.exit_fullscreen": "Exit full screen",
|
||||
"video.expand": "Expand video",
|
||||
"video.fullscreen": "Full screen",
|
||||
"video.hide": "Hide video",
|
||||
"video.mute": "Mute sound",
|
||||
"video.pause": "Pause",
|
||||
"video.play": "Play",
|
||||
"video.unmute": "Unmute sound",
|
||||
"video_player.expand": "Развернуть видео",
|
||||
"video_player.toggle_sound": "Вкл./выкл. звук",
|
||||
"video_player.toggle_visible": "Показать/скрыть",
|
||||
|
|
|
@ -33,6 +33,7 @@
|
|||
"column.home": "Home",
|
||||
"column.mutes": "Muted users",
|
||||
"column.notifications": "Notifications",
|
||||
"column.pins": "Pinned toot",
|
||||
"column.public": "Federated timeline",
|
||||
"column_back_button.label": "Back",
|
||||
"column_header.hide_settings": "Hide settings",
|
||||
|
@ -109,6 +110,7 @@
|
|||
"navigation_bar.info": "About this instance",
|
||||
"navigation_bar.logout": "Logout",
|
||||
"navigation_bar.mutes": "Muted users",
|
||||
"navigation_bar.pins": "Pinned toots",
|
||||
"navigation_bar.preferences": "Preferences",
|
||||
"navigation_bar.public_timeline": "Federated timeline",
|
||||
"notification.favourite": "{name} favourited your status",
|
||||
|
@ -193,6 +195,15 @@
|
|||
"upload_button.label": "Add media",
|
||||
"upload_form.undo": "Undo",
|
||||
"upload_progress.label": "Uploading...",
|
||||
"video.close": "Close video",
|
||||
"video.exit_fullscreen": "Exit full screen",
|
||||
"video.expand": "Expand video",
|
||||
"video.fullscreen": "Full screen",
|
||||
"video.hide": "Hide video",
|
||||
"video.mute": "Mute sound",
|
||||
"video.pause": "Pause",
|
||||
"video.play": "Play",
|
||||
"video.unmute": "Unmute sound",
|
||||
"video_player.expand": "Expand video",
|
||||
"video_player.toggle_sound": "Toggle sound",
|
||||
"video_player.toggle_visible": "Toggle visibility",
|
||||
|
|
|
@ -33,6 +33,7 @@
|
|||
"column.home": "Anasayfa",
|
||||
"column.mutes": "Susturulmuş kullanıcılar",
|
||||
"column.notifications": "Bildirimler",
|
||||
"column.pins": "Pinned toot",
|
||||
"column.public": "Federe zaman tüneli",
|
||||
"column_back_button.label": "Geri",
|
||||
"column_header.hide_settings": "Hide settings",
|
||||
|
@ -109,6 +110,7 @@
|
|||
"navigation_bar.info": "Genişletilmiş bilgi",
|
||||
"navigation_bar.logout": "Çıkış",
|
||||
"navigation_bar.mutes": "Sessize alınmış kullanıcılar",
|
||||
"navigation_bar.pins": "Pinned toots",
|
||||
"navigation_bar.preferences": "Tercihler",
|
||||
"navigation_bar.public_timeline": "Federe zaman tüneli",
|
||||
"notification.favourite": "{name} senin durumunu favorilere ekledi",
|
||||
|
@ -193,6 +195,15 @@
|
|||
"upload_button.label": "Görsel ekle",
|
||||
"upload_form.undo": "Geri al",
|
||||
"upload_progress.label": "Yükleniyor...",
|
||||
"video.close": "Close video",
|
||||
"video.exit_fullscreen": "Exit full screen",
|
||||
"video.expand": "Expand video",
|
||||
"video.fullscreen": "Full screen",
|
||||
"video.hide": "Hide video",
|
||||
"video.mute": "Mute sound",
|
||||
"video.pause": "Pause",
|
||||
"video.play": "Play",
|
||||
"video.unmute": "Unmute sound",
|
||||
"video_player.expand": "Videoyu genişlet",
|
||||
"video_player.toggle_sound": "Sesi aç/kapa",
|
||||
"video_player.toggle_visible": "Göster/gizle",
|
||||
|
|
|
@ -33,6 +33,7 @@
|
|||
"column.home": "Головна",
|
||||
"column.mutes": "Заглушені користувачі",
|
||||
"column.notifications": "Сповіщення",
|
||||
"column.pins": "Pinned toot",
|
||||
"column.public": "Глобальна стрічка",
|
||||
"column_back_button.label": "Назад",
|
||||
"column_header.hide_settings": "Hide settings",
|
||||
|
@ -109,6 +110,7 @@
|
|||
"navigation_bar.info": "Про інстанцію",
|
||||
"navigation_bar.logout": "Вийти",
|
||||
"navigation_bar.mutes": "Заглушені користувачі",
|
||||
"navigation_bar.pins": "Pinned toots",
|
||||
"navigation_bar.preferences": "Налаштування",
|
||||
"navigation_bar.public_timeline": "Глобальна стрічка",
|
||||
"notification.favourite": "{name} сподобався ваш допис",
|
||||
|
@ -193,6 +195,15 @@
|
|||
"upload_button.label": "Додати медіаконтент",
|
||||
"upload_form.undo": "Відмінити",
|
||||
"upload_progress.label": "Завантаження...",
|
||||
"video.close": "Close video",
|
||||
"video.exit_fullscreen": "Exit full screen",
|
||||
"video.expand": "Expand video",
|
||||
"video.fullscreen": "Full screen",
|
||||
"video.hide": "Hide video",
|
||||
"video.mute": "Mute sound",
|
||||
"video.pause": "Pause",
|
||||
"video.play": "Play",
|
||||
"video.unmute": "Unmute sound",
|
||||
"video_player.expand": "Розгорнути ",
|
||||
"video_player.toggle_sound": "Увімкнути/вимкнути звук",
|
||||
"video_player.toggle_visible": "Показати/приховати",
|
||||
|
|
|
@ -1,13 +1,13 @@
|
|||
{
|
||||
"account.block": "屏蔽 @{name}",
|
||||
"account.block_domain": "Hide everything from {domain}",
|
||||
"account.disclaimer_full": "Information below may reflect the user's profile incompletely.",
|
||||
"account.block_domain": "隐藏一切来自 {domain} 的嘟文",
|
||||
"account.disclaimer_full": "下列资料不一定完整。",
|
||||
"account.edit_profile": "修改个人资料",
|
||||
"account.follow": "关注",
|
||||
"account.followers": "关注者",
|
||||
"account.follows": "正在关注",
|
||||
"account.follows": "正关注",
|
||||
"account.follows_you": "关注你",
|
||||
"account.media": "Media",
|
||||
"account.media": "媒体",
|
||||
"account.mention": "提及 @{name}",
|
||||
"account.mute": "将 @{name} 静音",
|
||||
"account.posts": "嘟文",
|
||||
|
@ -15,40 +15,41 @@
|
|||
"account.requested": "等待审批",
|
||||
"account.share": "分享 @{name}的个人资料",
|
||||
"account.unblock": "解除对 @{name} 的屏蔽",
|
||||
"account.unblock_domain": "解除封锁 {domain}",
|
||||
"account.unblock_domain": "不再隐藏 {domain}",
|
||||
"account.unfollow": "取消关注",
|
||||
"account.unmute": "取消 @{name} 的静音",
|
||||
"account.view_full_profile": "查看完整资料",
|
||||
"boost_modal.combo": "如你想在下次路过时显示,请按{combo},",
|
||||
"bundle_column_error.body": "载入组件出错。",
|
||||
"bundle_column_error.retry": "再次尝试",
|
||||
"bundle_column_error.retry": "重试",
|
||||
"bundle_column_error.title": "网络错误",
|
||||
"bundle_modal_error.close": "关闭",
|
||||
"bundle_modal_error.message": "载入组件出错。",
|
||||
"bundle_modal_error.retry": "再次尝试",
|
||||
"bundle_modal_error.retry": "重试",
|
||||
"column.blocks": "屏蔽用户",
|
||||
"column.community": "本站时间轴",
|
||||
"column.favourites": "赞过的嘟文",
|
||||
"column.favourites": "收藏过的嘟文",
|
||||
"column.follow_requests": "关注请求",
|
||||
"column.home": "主页",
|
||||
"column.mutes": "被静音的用户",
|
||||
"column.notifications": "通知",
|
||||
"column.pins": "Pinned toot",
|
||||
"column.public": "跨站公共时间轴",
|
||||
"column_back_button.label": "返回",
|
||||
"column_header.hide_settings": "Hide settings",
|
||||
"column_header.moveLeft_settings": "Move column to the left",
|
||||
"column_header.moveRight_settings": "Move column to the right",
|
||||
"column_header.pin": "Pin",
|
||||
"column_header.show_settings": "Show settings",
|
||||
"column_header.unpin": "Unpin",
|
||||
"column_header.hide_settings": "隐藏设置",
|
||||
"column_header.moveLeft_settings": "将栏左移",
|
||||
"column_header.moveRight_settings": "将栏右移",
|
||||
"column_header.pin": "置顶",
|
||||
"column_header.show_settings": "显示设置",
|
||||
"column_header.unpin": "撤顶",
|
||||
"column_subheading.navigation": "导航",
|
||||
"column_subheading.settings": "设置",
|
||||
"compose_form.lock_disclaimer": "你的账户没 {locked}. 任何人可以通过关注你来查看只有关注者可见的嘟文.",
|
||||
"compose_form.lock_disclaimer": "你的帐户没 {locked}. 任何人可以通过关注你来查看只有关注者可见的嘟文.",
|
||||
"compose_form.lock_disclaimer.lock": "被保护",
|
||||
"compose_form.placeholder": "在想啥?",
|
||||
"compose_form.privacy_disclaimer": "你的私人嘟文,将被发送至你所提及的 {domains} 用户。你是否信任{domainsCount, plural, one {这个网站} other {这些网站}}?请留意,嘟文隐私设置只适用于各 Mastodon 服务器实例,如果 {domains} {domainsCount, plural, one {不是 Mastodon 服务器实例} other {之中有些不是 Mastodon 服务器实例}},对方将无法收到这篇嘟文的隐私设置,然后可能被转嘟给不能预知的用户阅读。",
|
||||
"compose_form.publish": "嘟嘟",
|
||||
"compose_form.publish_loud": "{publish}!",
|
||||
"compose_form.publish_loud": "{publish}!",
|
||||
"compose_form.sensitive": "将媒体文件标示为“敏感内容”",
|
||||
"compose_form.spoiler": "将部分文本藏于警告消息之后",
|
||||
"compose_form.spoiler_placeholder": "敏感内容的警告消息",
|
||||
|
@ -57,14 +58,14 @@
|
|||
"confirmations.block.message": "想好了,真的要屏蔽 {name}?",
|
||||
"confirmations.delete.confirm": "删除",
|
||||
"confirmations.delete.message": "想好了,真的要删除这条嘟文?",
|
||||
"confirmations.domain_block.confirm": "Hide entire domain",
|
||||
"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.confirm": "隐藏整个网站",
|
||||
"confirmations.domain_block.message": "你真的真的确定要隐藏整个 {domain} ?多数情况下,封锁或静音几个特定目标就好。",
|
||||
"confirmations.mute.confirm": "静音",
|
||||
"confirmations.mute.message": "想好了,真的要静音 {name}?",
|
||||
"confirmations.unfollow.confirm": "取消关注",
|
||||
"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:",
|
||||
"embed.instructions": "要内嵌此嘟文,请将以下代码贴进你的网站。",
|
||||
"embed.preview": "到时大概长这样:",
|
||||
"emoji_button.activity": "活动",
|
||||
"emoji_button.flags": "旗帜",
|
||||
"emoji_button.food": "食物和饮料",
|
||||
|
@ -72,13 +73,13 @@
|
|||
"emoji_button.nature": "自然",
|
||||
"emoji_button.objects": "物体",
|
||||
"emoji_button.people": "人物",
|
||||
"emoji_button.search": "搜索...",
|
||||
"emoji_button.search": "搜索…",
|
||||
"emoji_button.symbols": "符号",
|
||||
"emoji_button.travel": "旅途和地点",
|
||||
"empty_column.community": "本站时间轴暂时未有内容,快贴文来抢头香啊!",
|
||||
"empty_column.community": "本站时间轴暂时未有内容,快嘟几个来抢头香啊!",
|
||||
"empty_column.hashtag": "这个标签暂时未有内容。",
|
||||
"empty_column.home": "你还没有关注任何用户。快看看{public},向其他用户搭讪吧。",
|
||||
"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": "你的主页暂时没有内容。也许你太久没有来了?如果是这样,文章会慢慢出来,请稍后再看。",
|
||||
"empty_column.home.public_timeline": "公共时间轴",
|
||||
"empty_column.notifications": "你没有任何通知纪录,快向其他用户搭讪吧。",
|
||||
"empty_column.public": "跨站公共时间轴暂时没有内容!快写一些公共的嘟文,或者关注另一些服务器实例的用户吧!你和本站、友站的交流,将决定这里出现的内容。",
|
||||
|
@ -96,33 +97,34 @@
|
|||
"home.column_settings.show_replies": "显示回应嘟文",
|
||||
"home.settings": "字段设置",
|
||||
"lightbox.close": "关闭",
|
||||
"lightbox.next": "Next",
|
||||
"lightbox.previous": "Previous",
|
||||
"lightbox.next": "下一步",
|
||||
"lightbox.previous": "上一步",
|
||||
"loading_indicator.label": "加载中……",
|
||||
"media_gallery.toggle_visible": "打开或关上",
|
||||
"missing_indicator.label": "找不到内容",
|
||||
"navigation_bar.blocks": "被屏蔽的用户",
|
||||
"navigation_bar.community_timeline": "本站时间轴",
|
||||
"navigation_bar.edit_profile": "修改个人资料",
|
||||
"navigation_bar.favourites": "赞的内容",
|
||||
"navigation_bar.favourites": "收藏的内容",
|
||||
"navigation_bar.follow_requests": "关注请求",
|
||||
"navigation_bar.info": "关于本站",
|
||||
"navigation_bar.logout": "注销",
|
||||
"navigation_bar.mutes": "被静音的用户",
|
||||
"navigation_bar.pins": "置顶嘟文",
|
||||
"navigation_bar.preferences": "首选项",
|
||||
"navigation_bar.public_timeline": "跨站公共时间轴",
|
||||
"notification.favourite": "{name} 赞了你的嘟文",
|
||||
"notification.favourite": "{name} 收藏了你的嘟文",
|
||||
"notification.follow": "{name} 开始关注你",
|
||||
"notification.mention": "{name} 提及你",
|
||||
"notification.reblog": "{name} 转嘟了你的嘟文",
|
||||
"notifications.clear": "清空通知纪录",
|
||||
"notifications.clear_confirmation": "你确定要清空通知纪录吗?",
|
||||
"notifications.column_settings.alert": "显示桌面通知",
|
||||
"notifications.column_settings.favourite": "你的嘟文被赞:",
|
||||
"notifications.column_settings.favourite": "你的嘟文被收藏:",
|
||||
"notifications.column_settings.follow": "关注你:",
|
||||
"notifications.column_settings.mention": "提及你:",
|
||||
"notifications.column_settings.push": "Push notifications",
|
||||
"notifications.column_settings.push_meta": "This device",
|
||||
"notifications.column_settings.push": "推送通知",
|
||||
"notifications.column_settings.push_meta": "此设备",
|
||||
"notifications.column_settings.reblog": "你的嘟文被转嘟:",
|
||||
"notifications.column_settings.show": "在通知栏显示",
|
||||
"notifications.column_settings.sound": "播放音效",
|
||||
|
@ -132,18 +134,18 @@
|
|||
"onboarding.page_four.home": "你的主时间轴上是你关注的用户的嘟文.",
|
||||
"onboarding.page_four.notifications": "如果你和他人产生了互动,便会出现在通知列上啦~",
|
||||
"onboarding.page_one.federation": "Mastodon是由一系列独立的服务器共同打造的强大的社交网络,我们将这些独立但又相互连接的服务器叫做服务器实例。",
|
||||
"onboarding.page_one.handle": "你在 {domain}, {handle} 就是你的完整账户名称。",
|
||||
"onboarding.page_one.handle": "你在 {domain}, {handle} 就是你的完整帐户名称。",
|
||||
"onboarding.page_one.welcome": "欢迎来到 Mastodon!",
|
||||
"onboarding.page_six.admin": "{admin} 是你所在服务器实例的管理员.",
|
||||
"onboarding.page_six.almost_done": "快完成了...",
|
||||
"onboarding.page_six.almost_done": "差不多了…",
|
||||
"onboarding.page_six.appetoot": "嗷呜~",
|
||||
"onboarding.page_six.apps_available": "也有适用于 iOS, Android 和其它平台的 {apps} 咯~",
|
||||
"onboarding.page_six.github": "Mastodon 是自由的开放源代码软件。欢迎来 {github} 报告问题,提交功能请求,或者贡献代码 :-)",
|
||||
"onboarding.page_six.guidelines": "社区指南",
|
||||
"onboarding.page_six.read_guidelines": "别忘了看看 {domain} 的 {guidelines}!",
|
||||
"onboarding.page_six.read_guidelines": "别忘了看看 {domain} 的 {guidelines}!",
|
||||
"onboarding.page_six.various_app": "移动应用程序",
|
||||
"onboarding.page_three.profile": "修改你的个人资料,比如头像、简介、和昵称等等。在那还可以找到其它首选项。",
|
||||
"onboarding.page_three.search": "用搜索来找人和标签吧,比如 {illustration} 或者 {introductions}。想找其它服务器实例上的人,用完整账户名称(用户名@域名)啦。",
|
||||
"onboarding.page_three.search": "用搜索来找人和标签吧,比如 {illustration} 或者 {introductions}。想找其它服务器实例上的人,用完整帐户名称(用户名@域名)啦。",
|
||||
"onboarding.page_two.compose": "从这里开始嘟!上面的按钮提供了上传图片,修改隐私设置和提示敏感内容等多种功能。.",
|
||||
"onboarding.skip": "好啦好啦我知道啦",
|
||||
"privacy.change": "调整隐私设置",
|
||||
|
@ -161,29 +163,29 @@
|
|||
"report.target": "Reporting",
|
||||
"search.placeholder": "搜索",
|
||||
"search_results.total": "{count, number} {count, plural, one {result} other {results}}",
|
||||
"standalone.public_title": "A look inside...",
|
||||
"standalone.public_title": "大家都在干啥?",
|
||||
"status.cannot_reblog": "没法转嘟这条嘟文啦……",
|
||||
"status.delete": "删除",
|
||||
"status.embed": "Embed",
|
||||
"status.favourite": "赞",
|
||||
"status.embed": "嵌入",
|
||||
"status.favourite": "收藏",
|
||||
"status.load_more": "加载更多",
|
||||
"status.media_hidden": "隐藏媒体内容",
|
||||
"status.mention": "提及 @{name}",
|
||||
"status.mute_conversation": "Mute conversation",
|
||||
"status.mute_conversation": "静音对话",
|
||||
"status.open": "展开嘟文",
|
||||
"status.pin": "Pin on profile",
|
||||
"status.pin": "置顶到资料",
|
||||
"status.reblog": "转嘟",
|
||||
"status.reblogged_by": "{name} 转嘟",
|
||||
"status.reply": "回应",
|
||||
"status.replyAll": "Reply to thread",
|
||||
"status.replyAll": "回应整串",
|
||||
"status.report": "举报 @{name}",
|
||||
"status.sensitive_toggle": "点击显示",
|
||||
"status.sensitive_warning": "敏感内容",
|
||||
"status.share": "Share",
|
||||
"status.show_less": "减少显示",
|
||||
"status.show_more": "显示更多",
|
||||
"status.unmute_conversation": "Unmute conversation",
|
||||
"status.unpin": "Unpin from profile",
|
||||
"status.unmute_conversation": "解禁对话",
|
||||
"status.unpin": "解除置顶",
|
||||
"tabs_bar.compose": "撰写",
|
||||
"tabs_bar.federated_timeline": "跨站",
|
||||
"tabs_bar.home": "主页",
|
||||
|
@ -193,6 +195,15 @@
|
|||
"upload_button.label": "上传媒体文件",
|
||||
"upload_form.undo": "还原",
|
||||
"upload_progress.label": "上传中……",
|
||||
"video.close": "关闭影片",
|
||||
"video.exit_fullscreen": "退出全荧幕",
|
||||
"video.expand": "展开影片",
|
||||
"video.fullscreen": "全荧幕",
|
||||
"video.hide": "隐藏影片",
|
||||
"video.mute": "静音",
|
||||
"video.pause": "暂停",
|
||||
"video.play": "播放",
|
||||
"video.unmute": "解除静音",
|
||||
"video_player.expand": "展开影片",
|
||||
"video_player.toggle_sound": "开关音效",
|
||||
"video_player.toggle_visible": "打开或关上",
|
||||
|
|
|
@ -1,46 +1,47 @@
|
|||
{
|
||||
"account.block": "封鎖 @{name}",
|
||||
"account.block_domain": "Hide everything from {domain}",
|
||||
"account.disclaimer_full": "Information below may reflect the user's profile incompletely.",
|
||||
"account.block_domain": "隱藏來自 {domain} 的一切文章",
|
||||
"account.disclaimer_full": "下列資料不一定完整。",
|
||||
"account.edit_profile": "修改個人資料",
|
||||
"account.follow": "關注",
|
||||
"account.followers": "關注的人",
|
||||
"account.follows": "正在關注",
|
||||
"account.follows": "正關注",
|
||||
"account.follows_you": "關注你",
|
||||
"account.media": "Media",
|
||||
"account.media": "媒體",
|
||||
"account.mention": "提及 @{name}",
|
||||
"account.mute": "將 @{name} 靜音",
|
||||
"account.posts": "文章",
|
||||
"account.report": "舉報 @{name}",
|
||||
"account.requested": "等候審批",
|
||||
"account.share": "Share @{name}'s profile",
|
||||
"account.share": "分享 @{name} 的個人資料",
|
||||
"account.unblock": "解除對 @{name} 的封鎖",
|
||||
"account.unblock_domain": "Unhide {domain}",
|
||||
"account.unblock_domain": "不再隱藏 {domain}",
|
||||
"account.unfollow": "取消關注",
|
||||
"account.unmute": "取消 @{name} 的靜音",
|
||||
"account.view_full_profile": "View full profile",
|
||||
"account.view_full_profile": "查看完整資料",
|
||||
"boost_modal.combo": "如你想在下次路過這顯示,請按{combo},",
|
||||
"bundle_column_error.body": "Something went wrong while loading this component.",
|
||||
"bundle_column_error.retry": "Try again",
|
||||
"bundle_column_error.title": "Network error",
|
||||
"bundle_modal_error.close": "Close",
|
||||
"bundle_modal_error.message": "Something went wrong while loading this component.",
|
||||
"bundle_modal_error.retry": "Try again",
|
||||
"bundle_column_error.body": "加載本組件出錯。",
|
||||
"bundle_column_error.retry": "重試",
|
||||
"bundle_column_error.title": "網絡錯誤",
|
||||
"bundle_modal_error.close": "關閉",
|
||||
"bundle_modal_error.message": "加載本組件出錯。",
|
||||
"bundle_modal_error.retry": "重試",
|
||||
"column.blocks": "封鎖用戶",
|
||||
"column.community": "本站時間軸",
|
||||
"column.favourites": "喜歡的文章",
|
||||
"column.favourites": "最愛的文章",
|
||||
"column.follow_requests": "關注請求",
|
||||
"column.home": "主頁",
|
||||
"column.mutes": "靜音名單",
|
||||
"column.notifications": "通知",
|
||||
"column.pins": "Pinned toot",
|
||||
"column.public": "跨站時間軸",
|
||||
"column_back_button.label": "返回",
|
||||
"column_header.hide_settings": "Hide settings",
|
||||
"column_header.moveLeft_settings": "Move column to the left",
|
||||
"column_header.moveRight_settings": "Move column to the right",
|
||||
"column_header.pin": "Pin",
|
||||
"column_header.show_settings": "Show settings",
|
||||
"column_header.unpin": "Unpin",
|
||||
"column_header.hide_settings": "隱藏設定",
|
||||
"column_header.moveLeft_settings": "將欄左移",
|
||||
"column_header.moveRight_settings": "將欄右移",
|
||||
"column_header.pin": "置頂",
|
||||
"column_header.show_settings": "顯示設定",
|
||||
"column_header.unpin": "撤頂",
|
||||
"column_subheading.navigation": "瀏覽",
|
||||
"column_subheading.settings": "設定",
|
||||
"compose_form.lock_disclaimer": "你的用戶狀態為「{locked}」,任何人都能立即關注你,然後看到「只有關注者能看」的文章。",
|
||||
|
@ -48,7 +49,7 @@
|
|||
"compose_form.placeholder": "你在想甚麼?",
|
||||
"compose_form.privacy_disclaimer": "你的私人文章,將被遞送至 {domains}。你是否信任{domainsCount, plural, one {這個網站} other {這些網站}}?請留意,文章私隱設定只適用於 Mastodon 服務站,如果 {domains} {domainsCount, plural, one {不是 Mastodon 服務站} other {之中有些不是 Mastodon 服務站}},對方將可無視文章的私隱設定,轉推文章給其他用戶閱讀。",
|
||||
"compose_form.publish": "發文",
|
||||
"compose_form.publish_loud": "{publish}!",
|
||||
"compose_form.publish_loud": "{publish}!",
|
||||
"compose_form.sensitive": "將媒體檔案標示為「敏感內容」",
|
||||
"compose_form.spoiler": "將部份文字藏於警告訊息之後",
|
||||
"compose_form.spoiler_placeholder": "敏感警告訊息",
|
||||
|
@ -57,14 +58,14 @@
|
|||
"confirmations.block.message": "你確定要封鎖{name}嗎?",
|
||||
"confirmations.delete.confirm": "刪除",
|
||||
"confirmations.delete.message": "你確定要刪除{name}嗎?",
|
||||
"confirmations.domain_block.confirm": "Hide entire domain",
|
||||
"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.confirm": "隱藏整個網站",
|
||||
"confirmations.domain_block.message": "你真的真的確定要隱藏整個 {domain} ?多數情況下,比較推薦封鎖或靜音幾個特定目標就好。",
|
||||
"confirmations.mute.confirm": "靜音",
|
||||
"confirmations.mute.message": "你確定要將{name}靜音嗎?",
|
||||
"confirmations.unfollow.confirm": "Unfollow",
|
||||
"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:",
|
||||
"confirmations.unfollow.confirm": "取消關注",
|
||||
"confirmations.unfollow.message": "真的不要繼續關注 {name} 了嗎?",
|
||||
"embed.instructions": "要內嵌此文章,請將以下代碼貼進你的網站。",
|
||||
"embed.preview": "看上去會是這樣:",
|
||||
"emoji_button.activity": "活動",
|
||||
"emoji_button.flags": "旗幟",
|
||||
"emoji_button.food": "飲飲食食",
|
||||
|
@ -75,7 +76,7 @@
|
|||
"emoji_button.search": "搜尋…",
|
||||
"emoji_button.symbols": "符號",
|
||||
"emoji_button.travel": "旅遊景物",
|
||||
"empty_column.community": "本站時間軸暫時未有內容,快貼文來搶頭香啊!",
|
||||
"empty_column.community": "本站時間軸暫時未有內容,快文章來搶頭香啊!",
|
||||
"empty_column.hashtag": "這個標籤暫時未有內容。",
|
||||
"empty_column.home": "你還沒有關注任何用戶。快看看{public},向其他用戶搭訕吧。",
|
||||
"empty_column.home.inactivity": "你的主頁暫時沒有內容。也許你太久沒有來?如果是這樣,文章會慢慢出來,請稍後再看。",
|
||||
|
@ -96,34 +97,35 @@
|
|||
"home.column_settings.show_replies": "顯示回應文章",
|
||||
"home.settings": "欄位設定",
|
||||
"lightbox.close": "關閉",
|
||||
"lightbox.next": "Next",
|
||||
"lightbox.previous": "Previous",
|
||||
"lightbox.next": "繼續",
|
||||
"lightbox.previous": "回退",
|
||||
"loading_indicator.label": "載入中...",
|
||||
"media_gallery.toggle_visible": "打開或關上",
|
||||
"missing_indicator.label": "找不到內容",
|
||||
"navigation_bar.blocks": "被你封鎖的用戶",
|
||||
"navigation_bar.community_timeline": "本站時間軸",
|
||||
"navigation_bar.edit_profile": "修改個人資料",
|
||||
"navigation_bar.favourites": "喜歡的內容",
|
||||
"navigation_bar.favourites": "最愛的內容",
|
||||
"navigation_bar.follow_requests": "關注請求",
|
||||
"navigation_bar.info": "關於本服務站",
|
||||
"navigation_bar.logout": "登出",
|
||||
"navigation_bar.mutes": "被你靜音的用戶",
|
||||
"navigation_bar.pins": "置頂文章",
|
||||
"navigation_bar.preferences": "偏好設定",
|
||||
"navigation_bar.public_timeline": "跨站時間軸",
|
||||
"notification.favourite": "{name} 喜歡你的文章",
|
||||
"notification.favourite": "{name} 收藏了你的文章",
|
||||
"notification.follow": "{name} 開始關注你",
|
||||
"notification.mention": "{name} 提及你",
|
||||
"notification.reblog": "{name} 轉推你的文章",
|
||||
"notifications.clear": "清空通知紀錄",
|
||||
"notifications.clear_confirmation": "你確定要清空通知紀錄嗎?",
|
||||
"notifications.column_settings.alert": "顯示桌面通知",
|
||||
"notifications.column_settings.favourite": "喜歡你的文章:",
|
||||
"notifications.column_settings.follow": "關注你:",
|
||||
"notifications.column_settings.mention": "提及你:",
|
||||
"notifications.column_settings.push": "Push notifications",
|
||||
"notifications.column_settings.push_meta": "This device",
|
||||
"notifications.column_settings.reblog": "轉推你的文章:",
|
||||
"notifications.column_settings.favourite": "收藏了你的文章:",
|
||||
"notifications.column_settings.follow": "關注你:",
|
||||
"notifications.column_settings.mention": "提及你:",
|
||||
"notifications.column_settings.push": "推送通知",
|
||||
"notifications.column_settings.push_meta": "這臺設備",
|
||||
"notifications.column_settings.reblog": "轉推你的文章:",
|
||||
"notifications.column_settings.show": "在通知欄顯示",
|
||||
"notifications.column_settings.sound": "播放音效",
|
||||
"onboarding.done": "開始使用",
|
||||
|
@ -161,17 +163,17 @@
|
|||
"report.target": "舉報",
|
||||
"search.placeholder": "搜尋",
|
||||
"search_results.total": "{count, number} 項結果",
|
||||
"standalone.public_title": "A look inside...",
|
||||
"standalone.public_title": "站點一瞥…",
|
||||
"status.cannot_reblog": "這篇文章無法被轉推",
|
||||
"status.delete": "刪除",
|
||||
"status.embed": "Embed",
|
||||
"status.favourite": "喜歡",
|
||||
"status.embed": "鑲嵌",
|
||||
"status.favourite": "收藏",
|
||||
"status.load_more": "載入更多",
|
||||
"status.media_hidden": "隱藏媒體內容",
|
||||
"status.mention": "提及 @{name}",
|
||||
"status.mute_conversation": "Mute conversation",
|
||||
"status.mute_conversation": "靜音對話",
|
||||
"status.open": "展開文章",
|
||||
"status.pin": "Pin on profile",
|
||||
"status.pin": "置頂到資料頁",
|
||||
"status.reblog": "轉推",
|
||||
"status.reblogged_by": "{name} 轉推",
|
||||
"status.reply": "回應",
|
||||
|
@ -182,8 +184,8 @@
|
|||
"status.share": "Share",
|
||||
"status.show_less": "減少顯示",
|
||||
"status.show_more": "顯示更多",
|
||||
"status.unmute_conversation": "Unmute conversation",
|
||||
"status.unpin": "Unpin from profile",
|
||||
"status.unmute_conversation": "解禁對話",
|
||||
"status.unpin": "解除置頂",
|
||||
"tabs_bar.compose": "撰寫",
|
||||
"tabs_bar.federated_timeline": "跨站",
|
||||
"tabs_bar.home": "主頁",
|
||||
|
@ -193,6 +195,15 @@
|
|||
"upload_button.label": "上載媒體檔案",
|
||||
"upload_form.undo": "還原",
|
||||
"upload_progress.label": "上載中……",
|
||||
"video.close": "關閉影片",
|
||||
"video.exit_fullscreen": "退出全熒幕",
|
||||
"video.expand": "展開影片",
|
||||
"video.fullscreen": "全熒幕",
|
||||
"video.hide": "隱藏影片",
|
||||
"video.mute": "靜音",
|
||||
"video.pause": "暫停",
|
||||
"video.play": "播放",
|
||||
"video.unmute": "解除靜音",
|
||||
"video_player.expand": "展開影片",
|
||||
"video_player.toggle_sound": "開關音效",
|
||||
"video_player.toggle_visible": "打開或關上",
|
||||
|
|
|
@ -1,11 +1,11 @@
|
|||
{
|
||||
"account.block": "封鎖 @{name}",
|
||||
"account.block_domain": "隱藏來自 {domain} 的一切",
|
||||
"account.disclaimer_full": "Information below may reflect the user's profile incompletely.",
|
||||
"account.edit_profile": "編輯用戶資訊",
|
||||
"account.block_domain": "隱藏來自 {domain} 的一切貼文",
|
||||
"account.disclaimer_full": "下列資料不一定完整。",
|
||||
"account.edit_profile": "編輯用者資訊",
|
||||
"account.follow": "關注",
|
||||
"account.followers": "專注者",
|
||||
"account.follows": "正在關注",
|
||||
"account.follows": "正關注",
|
||||
"account.follows_you": "關注你",
|
||||
"account.media": "媒體",
|
||||
"account.mention": "提到 @{name}",
|
||||
|
@ -13,19 +13,19 @@
|
|||
"account.posts": "貼文",
|
||||
"account.report": "檢舉 @{name}",
|
||||
"account.requested": "正在等待許可",
|
||||
"account.share": "Share @{name}'s profile",
|
||||
"account.share": "分享 @{name} 的用者資訊",
|
||||
"account.unblock": "取消封鎖 @{name}",
|
||||
"account.unblock_domain": "不再隱藏 {domain}",
|
||||
"account.unfollow": "取消關注",
|
||||
"account.unmute": "不再消音 @{name}",
|
||||
"account.view_full_profile": "View full profile",
|
||||
"account.view_full_profile": "查看完整資訊",
|
||||
"boost_modal.combo": "下次你可以按 {combo} 來跳過",
|
||||
"bundle_column_error.body": "Something went wrong while loading this component.",
|
||||
"bundle_column_error.retry": "Try again",
|
||||
"bundle_column_error.title": "Network error",
|
||||
"bundle_modal_error.close": "Close",
|
||||
"bundle_modal_error.message": "Something went wrong while loading this component.",
|
||||
"bundle_modal_error.retry": "Try again",
|
||||
"bundle_column_error.body": "加載本組件出錯。",
|
||||
"bundle_column_error.retry": "重試",
|
||||
"bundle_column_error.title": "網路錯誤",
|
||||
"bundle_modal_error.close": "關閉",
|
||||
"bundle_modal_error.message": "加載本組件出錯。",
|
||||
"bundle_modal_error.retry": "重試",
|
||||
"column.blocks": "封鎖的使用者",
|
||||
"column.community": "本地時間軸",
|
||||
"column.favourites": "最愛",
|
||||
|
@ -33,21 +33,22 @@
|
|||
"column.home": "家",
|
||||
"column.mutes": "消音的使用者",
|
||||
"column.notifications": "通知",
|
||||
"column.pins": "置頂貼文",
|
||||
"column.public": "聯盟時間軸",
|
||||
"column_back_button.label": "上一頁",
|
||||
"column_header.hide_settings": "Hide settings",
|
||||
"column_header.moveLeft_settings": "Move column to the left",
|
||||
"column_header.moveRight_settings": "Move column to the right",
|
||||
"column_header.pin": "Pin",
|
||||
"column_header.show_settings": "Show settings",
|
||||
"column_header.unpin": "Unpin",
|
||||
"column_header.hide_settings": "隱藏設定",
|
||||
"column_header.moveLeft_settings": "將欄左移",
|
||||
"column_header.moveRight_settings": "將欄右移",
|
||||
"column_header.pin": "置頂",
|
||||
"column_header.show_settings": "顯示設定",
|
||||
"column_header.unpin": "撤頂",
|
||||
"column_subheading.navigation": "瀏覽",
|
||||
"column_subheading.settings": "設定",
|
||||
"compose_form.lock_disclaimer": "你的帳號沒有{locked}。任何人都可以關注你,看到發給關注者的貼文。",
|
||||
"compose_form.lock_disclaimer.lock": "上鎖",
|
||||
"compose_form.placeholder": "在想些什麼?",
|
||||
"compose_form.privacy_disclaimer": "你的貼文會被傳到 {domains} 上被提到的使用者。你信任 {domainsCount, plural, one {這個伺服器} other {這些伺服器}}嗎?貼文的隱私設定只會在 Mastodon 副本上生效。如果 {domains} {domainsCount, plural, one {不是一個 Mastodon 副本} other {都不是 Mastodon 副本}},就不會被標記為非公開貼文,而且可能會被轉推或是讓不預期的人看見。",
|
||||
"compose_form.publish": "推",
|
||||
"compose_form.publish": "貼掉",
|
||||
"compose_form.publish_loud": "{publish}!",
|
||||
"compose_form.sensitive": "將此媒體標為敏感",
|
||||
"compose_form.spoiler": "將訊息隱藏在警告訊息之後",
|
||||
|
@ -58,13 +59,13 @@
|
|||
"confirmations.delete.confirm": "刪除",
|
||||
"confirmations.delete.message": "你確定要刪除這個狀態?",
|
||||
"confirmations.domain_block.confirm": "隱藏整個網域",
|
||||
"confirmations.domain_block.message": "你真的真的確定要封鎖整個 {domain} ?多數情況下,比較推薦封鎖或消音幾個特定目標就好。",
|
||||
"confirmations.domain_block.message": "你真的真的確定要隱藏整個 {domain} ?多數情況下,比較推薦封鎖或消音幾個特定目標就好。",
|
||||
"confirmations.mute.confirm": "消音",
|
||||
"confirmations.mute.message": "你確定要消音 {name} ?",
|
||||
"confirmations.unfollow.confirm": "Unfollow",
|
||||
"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:",
|
||||
"confirmations.unfollow.confirm": "取消關注",
|
||||
"confirmations.unfollow.message": "真的不要繼續關注 {name} 了嗎?",
|
||||
"embed.instructions": "要內嵌此貼文,請將以下代碼貼進你的網站。",
|
||||
"embed.preview": "看上去會變成這樣:",
|
||||
"emoji_button.activity": "活動",
|
||||
"emoji_button.flags": "旗幟",
|
||||
"emoji_button.food": "食物與飲料",
|
||||
|
@ -72,12 +73,12 @@
|
|||
"emoji_button.nature": "自然",
|
||||
"emoji_button.objects": "物件",
|
||||
"emoji_button.people": "人",
|
||||
"emoji_button.search": "搜尋...",
|
||||
"emoji_button.search": "搜尋…",
|
||||
"emoji_button.symbols": "符號",
|
||||
"emoji_button.travel": "旅遊與地點",
|
||||
"empty_column.community": "本地時間軸是空的。公開寫點什麼吧!",
|
||||
"empty_column.hashtag": "這個主題標籤下什麼都沒有。",
|
||||
"empty_column.home": "你還沒關注任何人。造訪{public}或利用搜尋功能找到其他用戶。",
|
||||
"empty_column.home": "你還沒關注任何人。造訪{public}或利用搜尋功能找到其他用者。",
|
||||
"empty_column.home.inactivity": "你家的訊息摘要是空的。如果你很久沒活動了,很快它就會重新產生。",
|
||||
"empty_column.home.public_timeline": "公開時間軸",
|
||||
"empty_column.notifications": "還沒有任何通知。和別的使用者互動來開始對話。",
|
||||
|
@ -96,22 +97,23 @@
|
|||
"home.column_settings.show_replies": "顯示回應",
|
||||
"home.settings": "欄位設定",
|
||||
"lightbox.close": "關閉",
|
||||
"lightbox.next": "Next",
|
||||
"lightbox.previous": "Previous",
|
||||
"lightbox.next": "繼續",
|
||||
"lightbox.previous": "回退",
|
||||
"loading_indicator.label": "讀取中...",
|
||||
"media_gallery.toggle_visible": "切換可見性",
|
||||
"missing_indicator.label": "找不到",
|
||||
"navigation_bar.blocks": "封鎖的使用者",
|
||||
"navigation_bar.community_timeline": "本地時間軸",
|
||||
"navigation_bar.edit_profile": "編輯用戶資訊",
|
||||
"navigation_bar.edit_profile": "編輯用者資訊",
|
||||
"navigation_bar.favourites": "最愛",
|
||||
"navigation_bar.follow_requests": "關注請求",
|
||||
"navigation_bar.info": "關於本站",
|
||||
"navigation_bar.logout": "登出",
|
||||
"navigation_bar.mutes": "消音的使用者",
|
||||
"navigation_bar.pins": "置頂貼文",
|
||||
"navigation_bar.preferences": "偏好設定",
|
||||
"navigation_bar.public_timeline": "聯盟時間軸",
|
||||
"notification.favourite": "{name}喜歡你的狀態",
|
||||
"notification.favourite": "{name}收藏了你的狀態",
|
||||
"notification.follow": "{name}關注了你",
|
||||
"notification.mention": "{name}提到了你",
|
||||
"notification.reblog": "{name}推了你的狀態",
|
||||
|
@ -121,8 +123,8 @@
|
|||
"notifications.column_settings.favourite": "最愛:",
|
||||
"notifications.column_settings.follow": "新的關注者:",
|
||||
"notifications.column_settings.mention": "提到:",
|
||||
"notifications.column_settings.push": "Push notifications",
|
||||
"notifications.column_settings.push_meta": "This device",
|
||||
"notifications.column_settings.push": "推送通知",
|
||||
"notifications.column_settings.push_meta": "這臺設備",
|
||||
"notifications.column_settings.reblog": "轉推:",
|
||||
"notifications.column_settings.show": "顯示在欄位中",
|
||||
"notifications.column_settings.sound": "播放音效",
|
||||
|
@ -135,8 +137,8 @@
|
|||
"onboarding.page_one.handle": "你在 {domain} 上,所以你的帳號全名是 {handle}",
|
||||
"onboarding.page_one.welcome": "歡迎來到 Mastodon !",
|
||||
"onboarding.page_six.admin": "你的副本的管理員是 {admin} 。",
|
||||
"onboarding.page_six.almost_done": "快好了...",
|
||||
"onboarding.page_six.appetoot": "推口大開!",
|
||||
"onboarding.page_six.almost_done": "快好了…",
|
||||
"onboarding.page_six.appetoot": "貼口大開!",
|
||||
"onboarding.page_six.apps_available": "在 iOS 、 Android 和其他平台上有這些 {apps} 可以用。",
|
||||
"onboarding.page_six.github": "Mastodon 是自由的開源軟體。你可以在 {github} 上回報臭蟲、請求新功能或是做出貢獻。",
|
||||
"onboarding.page_six.guidelines": "社群指南",
|
||||
|
@ -161,17 +163,17 @@
|
|||
"report.target": "通報中",
|
||||
"search.placeholder": "搜尋",
|
||||
"search_results.total": "{count, number} 項結果",
|
||||
"standalone.public_title": "A look inside...",
|
||||
"standalone.public_title": "站點一瞥…",
|
||||
"status.cannot_reblog": "此貼文無法轉推",
|
||||
"status.delete": "刪除",
|
||||
"status.embed": "Embed",
|
||||
"status.favourite": "喜愛",
|
||||
"status.favourite": "收藏",
|
||||
"status.load_more": "載入更多",
|
||||
"status.media_hidden": "媒體已隱藏",
|
||||
"status.mention": "提到 @{name}",
|
||||
"status.mute_conversation": "消音對話",
|
||||
"status.open": "展開這個狀態",
|
||||
"status.pin": "Pin on profile",
|
||||
"status.pin": "置頂到個人資訊頁",
|
||||
"status.reblog": "轉推",
|
||||
"status.reblogged_by": "{name} 轉推了",
|
||||
"status.reply": "回應",
|
||||
|
@ -183,7 +185,7 @@
|
|||
"status.show_less": "看少點",
|
||||
"status.show_more": "看更多",
|
||||
"status.unmute_conversation": "不消音對話",
|
||||
"status.unpin": "Unpin from profile",
|
||||
"status.unpin": "解除置頂",
|
||||
"tabs_bar.compose": "編輯",
|
||||
"tabs_bar.federated_timeline": "聯盟",
|
||||
"tabs_bar.home": "家",
|
||||
|
@ -193,6 +195,15 @@
|
|||
"upload_button.label": "增加媒體",
|
||||
"upload_form.undo": "復原",
|
||||
"upload_progress.label": "上傳中...",
|
||||
"video.close": "關閉影片",
|
||||
"video.exit_fullscreen": "退出全熒幕",
|
||||
"video.expand": "展開影片",
|
||||
"video.fullscreen": "全熒幕",
|
||||
"video.hide": "隱藏影片",
|
||||
"video.mute": "消音",
|
||||
"video.pause": "暫停",
|
||||
"video.play": "播放",
|
||||
"video.unmute": "解除消音",
|
||||
"video_player.expand": "展開影片",
|
||||
"video_player.toggle_sound": "切換音效",
|
||||
"video_player.toggle_visible": "切換可見性",
|
||||
|
|
|
@ -0,0 +1,23 @@
|
|||
import { Map as ImmutableMap } from 'immutable';
|
||||
import { HEIGHT_CACHE_SET, HEIGHT_CACHE_CLEAR } from '../actions/height_cache';
|
||||
|
||||
const initialState = ImmutableMap();
|
||||
|
||||
const setHeight = (state, key, id, height) => {
|
||||
return state.update(key, ImmutableMap(), map => map.set(id, height));
|
||||
};
|
||||
|
||||
const clearHeights = () => {
|
||||
return ImmutableMap();
|
||||
};
|
||||
|
||||
export default function statuses(state = initialState, action) {
|
||||
switch(action.type) {
|
||||
case HEIGHT_CACHE_SET:
|
||||
return setHeight(state, action.key, action.id, action.height);
|
||||
case HEIGHT_CACHE_CLEAR:
|
||||
return clearHeights();
|
||||
default:
|
||||
return state;
|
||||
}
|
||||
};
|
|
@ -21,6 +21,7 @@ import compose from './compose';
|
|||
import search from './search';
|
||||
import media_attachments from './media_attachments';
|
||||
import notifications from './notifications';
|
||||
import height_cache from './height_cache';
|
||||
|
||||
const reducers = {
|
||||
timelines,
|
||||
|
@ -45,6 +46,7 @@ const reducers = {
|
|||
search,
|
||||
media_attachments,
|
||||
notifications,
|
||||
height_cache,
|
||||
};
|
||||
|
||||
export default combineReducers(reducers);
|
||||
|
|
|
@ -15,8 +15,6 @@ import {
|
|||
CONTEXT_FETCH_SUCCESS,
|
||||
STATUS_MUTE_SUCCESS,
|
||||
STATUS_UNMUTE_SUCCESS,
|
||||
STATUS_SET_HEIGHT,
|
||||
STATUSES_CLEAR_HEIGHT,
|
||||
} from '../actions/statuses';
|
||||
import {
|
||||
TIMELINE_REFRESH_SUCCESS,
|
||||
|
@ -60,9 +58,14 @@ const normalizeStatus = (state, status) => {
|
|||
}
|
||||
|
||||
const searchContent = [status.spoiler_text, status.content].join(' ').replace(/<br \/>/g, '\n').replace(/<\/p><p>/g, '\n\n');
|
||||
const emojiMap = normalStatus.emojis.reduce((obj, emoji) => {
|
||||
obj[`:${emoji.shortcode}:`] = emoji.url;
|
||||
return obj;
|
||||
}, {});
|
||||
|
||||
normalStatus.search_index = domParser.parseFromString(searchContent, 'text/html').documentElement.textContent;
|
||||
normalStatus.contentHtml = emojify(normalStatus.content);
|
||||
normalStatus.spoilerHtml = emojify(escapeTextContentForBrowser(normalStatus.spoiler_text || ''));
|
||||
normalStatus.contentHtml = emojify(normalStatus.content, emojiMap);
|
||||
normalStatus.spoilerHtml = emojify(escapeTextContentForBrowser(normalStatus.spoiler_text || ''), emojiMap);
|
||||
|
||||
return state.update(status.id, ImmutableMap(), map => map.mergeDeep(fromJS(normalStatus)));
|
||||
};
|
||||
|
@ -95,18 +98,6 @@ const filterStatuses = (state, relationship) => {
|
|||
return state;
|
||||
};
|
||||
|
||||
const setHeight = (state, id, height) => {
|
||||
return state.update(id, ImmutableMap(), map => map.set('height', height));
|
||||
};
|
||||
|
||||
const clearHeights = (state) => {
|
||||
state.forEach(status => {
|
||||
state = state.deleteIn([status.get('id'), 'height']);
|
||||
});
|
||||
|
||||
return state;
|
||||
};
|
||||
|
||||
const initialState = ImmutableMap();
|
||||
|
||||
export default function statuses(state = initialState, action) {
|
||||
|
@ -148,10 +139,6 @@ export default function statuses(state = initialState, action) {
|
|||
return deleteStatus(state, action.id, action.references);
|
||||
case ACCOUNT_BLOCK_SUCCESS:
|
||||
return filterStatuses(state, action.relationship);
|
||||
case STATUS_SET_HEIGHT:
|
||||
return setHeight(state, action.id, action.height);
|
||||
case STATUSES_CLEAR_HEIGHT:
|
||||
return clearHeights(state);
|
||||
default:
|
||||
return state;
|
||||
}
|
||||
|
|
|
@ -25,6 +25,11 @@ function main() {
|
|||
const emojify = require('../mastodon/emoji').default;
|
||||
const { getLocale } = require('../mastodon/locales');
|
||||
const { localeData } = getLocale();
|
||||
const VideoContainer = require('../mastodon/containers/video_container').default;
|
||||
const MediaGalleryContainer = require('../mastodon/containers/media_gallery_container').default;
|
||||
const CardContainer = require('../mastodon/containers/card_container').default;
|
||||
const React = require('react');
|
||||
const ReactDOM = require('react-dom');
|
||||
|
||||
localeData.forEach(IntlRelativeFormat.__addLocaleData);
|
||||
|
||||
|
@ -66,22 +71,21 @@ function main() {
|
|||
window.open(e.target.href, 'mastodon-intent', 'width=400,height=400,resizable=no,menubar=no,status=no,scrollbars=yes');
|
||||
});
|
||||
});
|
||||
|
||||
[].forEach.call(document.querySelectorAll('[data-component="Video"]'), (content) => {
|
||||
const props = JSON.parse(content.getAttribute('data-props'));
|
||||
ReactDOM.render(<VideoContainer locale={locale} {...props} />, content);
|
||||
});
|
||||
|
||||
delegate(document, '.video-player video', 'click', ({ target }) => {
|
||||
if (target.paused) {
|
||||
target.play();
|
||||
} else {
|
||||
target.pause();
|
||||
}
|
||||
[].forEach.call(document.querySelectorAll('[data-component="MediaGallery"]'), (content) => {
|
||||
const props = JSON.parse(content.getAttribute('data-props'));
|
||||
ReactDOM.render(<MediaGalleryContainer locale={locale} {...props} />, content);
|
||||
});
|
||||
|
||||
delegate(document, '.activity-stream .media-spoiler-wrapper .media-spoiler', 'click', function() {
|
||||
this.parentNode.classList.add('media-spoiler-wrapper__visible');
|
||||
[].forEach.call(document.querySelectorAll('[data-component="Card"]'), (content) => {
|
||||
const props = JSON.parse(content.getAttribute('data-props'));
|
||||
ReactDOM.render(<CardContainer locale={locale} {...props} />, content);
|
||||
});
|
||||
|
||||
delegate(document, '.activity-stream .media-spoiler-wrapper .spoiler-button', 'click', function() {
|
||||
this.parentNode.classList.remove('media-spoiler-wrapper__visible');
|
||||
});
|
||||
|
||||
delegate(document, '.webapp-btn', 'click', ({ target, button }) => {
|
||||
|
@ -126,7 +130,7 @@ function main() {
|
|||
delegate(document, '#account_avatar', 'change', ({ target }) => {
|
||||
const avatar = document.querySelector('.card.compact .avatar img');
|
||||
const [file] = target.files || [];
|
||||
const url = URL.createObjectURL(file);
|
||||
const url = file ? URL.createObjectURL(file) : avatar.dataset.originalSrc;
|
||||
|
||||
avatar.src = url;
|
||||
});
|
||||
|
@ -134,7 +138,7 @@ function main() {
|
|||
delegate(document, '#account_header', 'change', ({ target }) => {
|
||||
const header = document.querySelector('.card.compact');
|
||||
const [file] = target.files || [];
|
||||
const url = URL.createObjectURL(file);
|
||||
const url = file ? URL.createObjectURL(file) : header.dataset.originalSrc;
|
||||
|
||||
header.style.backgroundImage = `url(${url})`;
|
||||
});
|
||||
|
|
|
@ -137,7 +137,7 @@
|
|||
padding-bottom: 15px;
|
||||
|
||||
.hero .heading {
|
||||
padding-bottom: 30px;
|
||||
padding-bottom: 20px;
|
||||
font-family: 'mastodon-font-sans-serif', sans-serif;
|
||||
font-size: 16px;
|
||||
font-weight: 400;
|
||||
|
@ -327,7 +327,7 @@
|
|||
|
||||
.about-short {
|
||||
background: darken($ui-base-color, 4%);
|
||||
padding: 50px 0;
|
||||
padding: 50px 0 30px;
|
||||
font-family: 'mastodon-font-sans-serif', sans-serif;
|
||||
font-size: 16px;
|
||||
font-weight: 400;
|
||||
|
@ -640,8 +640,11 @@
|
|||
.header-wrapper {
|
||||
padding-top: 0;
|
||||
|
||||
&.compact {
|
||||
padding-bottom: 0;
|
||||
}
|
||||
|
||||
&.compact .hero .heading {
|
||||
padding-bottom: 20px;
|
||||
text-align: initial;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -97,6 +97,14 @@
|
|||
margin-bottom: 40px;
|
||||
}
|
||||
|
||||
h3 {
|
||||
color: $ui-secondary-color;
|
||||
font-size: 20px;
|
||||
line-height: 28px;
|
||||
font-weight: 400;
|
||||
margin-bottom: 30px;
|
||||
}
|
||||
|
||||
h6 {
|
||||
font-size: 16px;
|
||||
color: $ui-secondary-color;
|
||||
|
|
|
@ -631,6 +631,10 @@
|
|||
opacity: 1;
|
||||
animation: fade 150ms linear;
|
||||
|
||||
.video-player {
|
||||
margin-top: 8px;
|
||||
}
|
||||
|
||||
&.status-direct {
|
||||
background: lighten($ui-base-color, 8%);
|
||||
|
||||
|
@ -867,6 +871,10 @@
|
|||
height: 22px;
|
||||
}
|
||||
}
|
||||
|
||||
.video-player {
|
||||
margin-top: 8px;
|
||||
}
|
||||
}
|
||||
|
||||
.detailed-status__meta {
|
||||
|
@ -1610,9 +1618,8 @@
|
|||
|
||||
.column,
|
||||
.drawer {
|
||||
@supports(display: grid) { // hack to fix Chrome <57
|
||||
contain: strict;
|
||||
}
|
||||
flex: 1 1 100%;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
@include limited-single-column('screen and (max-width: 360px)', $parent: null) {
|
||||
|
@ -1790,9 +1797,7 @@
|
|||
overflow-x: hidden;
|
||||
flex: 1 1 auto;
|
||||
-webkit-overflow-scrolling: touch;
|
||||
@supports(display: grid) { // hack to fix Chrome <57
|
||||
contain: strict;
|
||||
}
|
||||
will-change: transform; // improves perf in mobile Chrome
|
||||
|
||||
&.optionally-scrollable {
|
||||
overflow-y: auto;
|
||||
|
@ -2642,7 +2647,7 @@ button.icon-button.active i.fa-retweet {
|
|||
|
||||
.media-spoiler {
|
||||
background: $base-overlay-background;
|
||||
color: $primary-text-color;
|
||||
color: $ui-primary-color;
|
||||
border: 0;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
|
@ -4206,6 +4211,182 @@ button.icon-button.active i.fa-retweet {
|
|||
z-index: 5;
|
||||
}
|
||||
|
||||
.video-player {
|
||||
overflow: hidden;
|
||||
position: relative;
|
||||
background: $base-shadow-color;
|
||||
max-width: 100%;
|
||||
|
||||
video {
|
||||
height: 100%;
|
||||
width: 100%;
|
||||
z-index: 1;
|
||||
}
|
||||
|
||||
&.fullscreen {
|
||||
width: 100% !important;
|
||||
height: 100% !important;
|
||||
margin: 0;
|
||||
|
||||
video {
|
||||
max-width: 100% !important;
|
||||
max-height: 100% !important;
|
||||
}
|
||||
}
|
||||
|
||||
&.inline {
|
||||
video {
|
||||
object-fit: cover;
|
||||
position: relative;
|
||||
top: 50%;
|
||||
transform: translateY(-50%);
|
||||
}
|
||||
}
|
||||
|
||||
&__controls {
|
||||
position: absolute;
|
||||
z-index: 2;
|
||||
bottom: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
box-sizing: border-box;
|
||||
background: linear-gradient(0deg, rgba($base-shadow-color, 0.8) 0, rgba($base-shadow-color, 0.35) 60%, transparent);
|
||||
padding: 0 10px;
|
||||
opacity: 0;
|
||||
transition: opacity .1s ease;
|
||||
|
||||
&.active {
|
||||
opacity: 1;
|
||||
}
|
||||
}
|
||||
|
||||
&.inactive {
|
||||
video,
|
||||
.video-player__controls {
|
||||
visibility: hidden;
|
||||
}
|
||||
}
|
||||
|
||||
&__spoiler {
|
||||
display: none;
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
z-index: 4;
|
||||
border: 0;
|
||||
background: $base-shadow-color;
|
||||
color: $ui-primary-color;
|
||||
transition: none;
|
||||
pointer-events: none;
|
||||
|
||||
&.active {
|
||||
display: block;
|
||||
pointer-events: auto;
|
||||
|
||||
&:hover,
|
||||
&:active,
|
||||
&:focus {
|
||||
color: lighten($ui-primary-color, 8%);
|
||||
}
|
||||
}
|
||||
|
||||
&__title {
|
||||
display: block;
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
&__subtitle {
|
||||
display: block;
|
||||
font-size: 11px;
|
||||
font-weight: 500;
|
||||
}
|
||||
}
|
||||
|
||||
&__buttons {
|
||||
padding-bottom: 10px;
|
||||
font-size: 16px;
|
||||
|
||||
&.left {
|
||||
float: left;
|
||||
|
||||
button {
|
||||
padding-right: 10px;
|
||||
}
|
||||
}
|
||||
|
||||
&.right {
|
||||
float: right;
|
||||
|
||||
button {
|
||||
padding-left: 10px;
|
||||
}
|
||||
}
|
||||
|
||||
button {
|
||||
background: transparent;
|
||||
padding: 0;
|
||||
border: 0;
|
||||
color: $white;
|
||||
|
||||
&:active,
|
||||
&:hover,
|
||||
&:focus {
|
||||
color: $ui-highlight-color;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
&__seek {
|
||||
cursor: pointer;
|
||||
height: 24px;
|
||||
position: relative;
|
||||
|
||||
&::before {
|
||||
content: "";
|
||||
width: 100%;
|
||||
background: rgba($white, 0.35);
|
||||
display: block;
|
||||
position: absolute;
|
||||
height: 4px;
|
||||
top: 10px;
|
||||
}
|
||||
|
||||
&__progress {
|
||||
display: block;
|
||||
position: absolute;
|
||||
height: 4px;
|
||||
top: 10px;
|
||||
background: $ui-highlight-color;
|
||||
}
|
||||
|
||||
&__handle {
|
||||
position: absolute;
|
||||
z-index: 3;
|
||||
opacity: 0;
|
||||
border-radius: 50%;
|
||||
width: 12px;
|
||||
height: 12px;
|
||||
top: 6px;
|
||||
margin-left: -6px;
|
||||
transition: opacity .1s ease;
|
||||
background: $ui-highlight-color;
|
||||
pointer-events: none;
|
||||
|
||||
&.active {
|
||||
opacity: 1;
|
||||
}
|
||||
}
|
||||
|
||||
&:hover {
|
||||
.video-player__seek__handle {
|
||||
opacity: 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.media-spoiler-video {
|
||||
background-size: cover;
|
||||
background-repeat: no-repeat;
|
||||
|
|
|
@ -349,9 +349,46 @@ code {
|
|||
box-shadow: 0 0 5px rgba($base-shadow-color, 0.2);
|
||||
text-align: center;
|
||||
|
||||
p {
|
||||
margin-bottom: 15px;
|
||||
}
|
||||
|
||||
.oauth-code {
|
||||
color: $ui-secondary-color;
|
||||
outline: 0;
|
||||
box-sizing: border-box;
|
||||
display: block;
|
||||
width: 100%;
|
||||
border: none;
|
||||
padding: 10px;
|
||||
font-family: 'mastodon-font-monospace', monospace;
|
||||
background: $ui-base-color;
|
||||
color: $ui-primary-color;
|
||||
font-size: 14px;
|
||||
margin: 0;
|
||||
|
||||
&::-moz-focus-inner {
|
||||
border: 0;
|
||||
}
|
||||
|
||||
&::-moz-focus-inner,
|
||||
&:focus,
|
||||
&:active {
|
||||
outline: 0 !important;
|
||||
}
|
||||
|
||||
&:focus {
|
||||
background: lighten($ui-base-color, 4%);
|
||||
}
|
||||
}
|
||||
|
||||
strong {
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
@media screen and (max-width: 740px) and (min-width: 441px) {
|
||||
margin-top: 40px;
|
||||
}
|
||||
}
|
||||
|
||||
.form-footer {
|
||||
|
|
|
@ -140,19 +140,6 @@
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
.status__attachments {
|
||||
margin-top: 8px;
|
||||
overflow: hidden;
|
||||
width: 100%;
|
||||
box-sizing: border-box;
|
||||
position: relative;
|
||||
|
||||
.status__attachments__inner {
|
||||
display: flex;
|
||||
height: 214px;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.detailed-status.light {
|
||||
|
@ -233,139 +220,35 @@
|
|||
}
|
||||
}
|
||||
|
||||
.detailed-status__attachments {
|
||||
margin-top: 8px;
|
||||
overflow: hidden;
|
||||
width: 100%;
|
||||
box-sizing: border-box;
|
||||
position: relative;
|
||||
.status-card {
|
||||
border-color: lighten($ui-secondary-color, 4%);
|
||||
color: darken($ui-primary-color, 4%);
|
||||
|
||||
.status__attachments__inner {
|
||||
display: flex;
|
||||
height: 360px;
|
||||
&:hover {
|
||||
background: lighten($ui-secondary-color, 4%);
|
||||
}
|
||||
}
|
||||
|
||||
.video-player {
|
||||
margin-top: 8px;
|
||||
height: 300px;
|
||||
overflow: hidden;
|
||||
position: relative;
|
||||
|
||||
video {
|
||||
position: relative;
|
||||
z-index: 1;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
object-fit: cover;
|
||||
top: 50%;
|
||||
transform: translateY(-50%);
|
||||
}
|
||||
}
|
||||
.status-card__title,
|
||||
.status-card__description {
|
||||
color: $ui-base-color;
|
||||
}
|
||||
|
||||
.media-item,
|
||||
.video-item {
|
||||
box-sizing: border-box;
|
||||
position: relative;
|
||||
left: auto;
|
||||
top: auto;
|
||||
right: auto;
|
||||
bottom: auto;
|
||||
float: left;
|
||||
border: medium none;
|
||||
display: block;
|
||||
flex: 1 1 auto;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
overflow: hidden;
|
||||
margin-right: 2px;
|
||||
|
||||
&:last-child {
|
||||
margin-right: 0;
|
||||
}
|
||||
|
||||
a {
|
||||
display: block;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
background: no-repeat scroll center center / cover;
|
||||
text-decoration: none;
|
||||
cursor: zoom-in;
|
||||
}
|
||||
|
||||
video {
|
||||
position: relative;
|
||||
z-index: 1;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
object-fit: cover;
|
||||
top: 50%;
|
||||
transform: translateY(-50%);
|
||||
}
|
||||
}
|
||||
|
||||
.video-item {
|
||||
a {
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.video-item__play {
|
||||
position: absolute;
|
||||
top: 50%;
|
||||
left: 50%;
|
||||
font-size: 36px;
|
||||
transform: translate(-50%, -50%);
|
||||
padding: 5px;
|
||||
border-radius: 100px;
|
||||
color: rgba($primary-text-color, 0.8);
|
||||
z-index: 1;
|
||||
.status-card__image {
|
||||
background: $ui-secondary-color;
|
||||
}
|
||||
}
|
||||
|
||||
.media-spoiler {
|
||||
background: $ui-primary-color;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
cursor: pointer;
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
flex-direction: column;
|
||||
text-align: center;
|
||||
color: $white;
|
||||
transition: all 100ms linear;
|
||||
z-index: 2;
|
||||
|
||||
&:hover {
|
||||
&:hover,
|
||||
&:active,
|
||||
&:focus {
|
||||
background: darken($ui-primary-color, 5%);
|
||||
}
|
||||
|
||||
span {
|
||||
display: block;
|
||||
|
||||
&:first-child {
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
&:last-child {
|
||||
font-size: 11px;
|
||||
font-weight: 500;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.media-spoiler-wrapper {
|
||||
&.media-spoiler-wrapper__visible {
|
||||
.media-spoiler {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.spoiler-button {
|
||||
display: block;
|
||||
}
|
||||
color: unset;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -11,7 +11,12 @@ class ActivityPub::Activity::Announce < ActivityPub::Activity
|
|||
|
||||
return status unless status.nil?
|
||||
|
||||
status = Status.create!(account: @account, reblog: original_status, uri: @json['id'])
|
||||
status = Status.create!(
|
||||
account: @account,
|
||||
reblog: original_status,
|
||||
uri: @json['id'],
|
||||
created_at: @json['published'] || Time.now.utc
|
||||
)
|
||||
distribute(status)
|
||||
status
|
||||
end
|
||||
|
|
|
@ -4,26 +4,31 @@ class ActivityPub::Activity::Create < ActivityPub::Activity
|
|||
def perform
|
||||
return if delete_arrived_first?(object_uri) || unsupported_object_type?
|
||||
|
||||
status = find_existing_status
|
||||
|
||||
return status unless status.nil?
|
||||
|
||||
ApplicationRecord.transaction do
|
||||
status = Status.create!(status_params)
|
||||
|
||||
process_tags(status)
|
||||
process_attachments(status)
|
||||
RedisLock.acquire(lock_options) do |lock|
|
||||
if lock.acquired?
|
||||
@status = find_existing_status
|
||||
process_status if @status.nil?
|
||||
end
|
||||
end
|
||||
|
||||
resolve_thread(status)
|
||||
distribute(status)
|
||||
forward_for_reply if status.public_visibility? || status.unlisted_visibility?
|
||||
|
||||
status
|
||||
@status
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def process_status
|
||||
ApplicationRecord.transaction do
|
||||
@status = Status.create!(status_params)
|
||||
|
||||
process_tags(@status)
|
||||
process_attachments(@status)
|
||||
end
|
||||
|
||||
resolve_thread(@status)
|
||||
distribute(@status)
|
||||
forward_for_reply if @status.public_visibility? || @status.unlisted_visibility?
|
||||
end
|
||||
|
||||
def find_existing_status
|
||||
status = status_from_uri(object_uri)
|
||||
status ||= Status.find_by(uri: @object['atomUri']) if @object['atomUri'].present?
|
||||
|
@ -56,6 +61,8 @@ class ActivityPub::Activity::Create < ActivityPub::Activity
|
|||
process_hashtag tag, status
|
||||
when 'Mention'
|
||||
process_mention tag, status
|
||||
when 'Emoji'
|
||||
process_emoji tag, status
|
||||
end
|
||||
end
|
||||
end
|
||||
|
@ -74,6 +81,17 @@ class ActivityPub::Activity::Create < ActivityPub::Activity
|
|||
account.mentions.create(status: status)
|
||||
end
|
||||
|
||||
def process_emoji(tag, _status)
|
||||
shortcode = tag['name'].delete(':')
|
||||
emoji = CustomEmoji.find_by(shortcode: shortcode, domain: @account.domain)
|
||||
|
||||
return if !emoji.nil? || skip_download?
|
||||
|
||||
emoji = CustomEmoji.new(domain: @account.domain, shortcode: shortcode)
|
||||
emoji.image_remote_url = tag['href']
|
||||
emoji.save
|
||||
end
|
||||
|
||||
def process_attachments(status)
|
||||
return unless @object['attachment'].is_a?(Array)
|
||||
|
||||
|
@ -182,4 +200,8 @@ class ActivityPub::Activity::Create < ActivityPub::Activity
|
|||
return unless @json['signature'].present? && reply_to_local?
|
||||
ActivityPub::RawDistributionWorker.perform_async(Oj.dump(@json), replied_to_status.account_id)
|
||||
end
|
||||
|
||||
def lock_options
|
||||
{ redis: Redis.current, key: "create:#{@object['id']}" }
|
||||
end
|
||||
end
|
||||
|
|
|
@ -14,6 +14,8 @@ class ActivityPub::Adapter < ActiveModelSerializers::Adapter::Base
|
|||
'atomUri' => 'ostatus:atomUri',
|
||||
'inReplyToAtomUri' => 'ostatus:inReplyToAtomUri',
|
||||
'conversation' => 'ostatus:conversation',
|
||||
'toot' => 'http://joinmastodon.org/ns#',
|
||||
'Emoji' => 'toot:Emoji',
|
||||
},
|
||||
],
|
||||
}.freeze
|
||||
|
@ -28,7 +30,7 @@ class ActivityPub::Adapter < ActiveModelSerializers::Adapter::Base
|
|||
|
||||
def serializable_hash(options = nil)
|
||||
options = serialization_options(options)
|
||||
serialized_hash = CONTEXT.merge(ActiveModelSerializers::Adapter::Attributes.new(serializer, instance_options).serializable_hash(options))
|
||||
self.class.transform_key_casing!(serialized_hash, instance_options)
|
||||
serialized_hash = ActiveModelSerializers::Adapter::Attributes.new(serializer, instance_options).serializable_hash(options)
|
||||
CONTEXT.merge(self.class.transform_key_casing!(serialized_hash, instance_options))
|
||||
end
|
||||
end
|
||||
|
|
|
@ -37,7 +37,7 @@ class ActivityPub::TagManager
|
|||
end
|
||||
|
||||
def activity_uri_for(target)
|
||||
return nil unless %i(note comment activity).include?(target.object_type) && target.local?
|
||||
raise ArgumentError, 'target must be a local activity' unless %i(note comment activity).include?(target.object_type) && target.local?
|
||||
|
||||
activity_account_status_url(target.account, target)
|
||||
end
|
||||
|
|
|
@ -9,7 +9,7 @@ class Formatter
|
|||
|
||||
include ActionView::Helpers::TextHelper
|
||||
|
||||
def format(status)
|
||||
def format(status, options = {})
|
||||
if status.reblog?
|
||||
prepend_reblog = status.reblog.account.acct
|
||||
status = status.proper
|
||||
|
@ -19,7 +19,11 @@ class Formatter
|
|||
|
||||
raw_content = status.text
|
||||
|
||||
return reformat(raw_content) unless status.local?
|
||||
unless status.local?
|
||||
html = reformat(raw_content)
|
||||
html = encode_custom_emojis(html, status.emojis) if options[:custom_emojify]
|
||||
return html
|
||||
end
|
||||
|
||||
linkable_accounts = status.mentions.map(&:account)
|
||||
linkable_accounts << status.account
|
||||
|
@ -27,6 +31,7 @@ class Formatter
|
|||
html = raw_content
|
||||
html = "RT @#{prepend_reblog} #{html}" if prepend_reblog
|
||||
html = encode_and_link_urls(html, linkable_accounts)
|
||||
html = encode_custom_emojis(html, status.emojis) if options[:custom_emojify]
|
||||
html = simple_format(html, {}, sanitize: false)
|
||||
html = html.delete("\n")
|
||||
|
||||
|
@ -39,7 +44,9 @@ class Formatter
|
|||
|
||||
def plaintext(status)
|
||||
return status.text if status.local?
|
||||
strip_tags(status.text)
|
||||
|
||||
text = status.text.gsub(/(<br \/>|<br>|<\/p>)+/) { |match| "#{match}\n" }
|
||||
strip_tags(text)
|
||||
end
|
||||
|
||||
def simplified_format(account)
|
||||
|
@ -76,6 +83,47 @@ class Formatter
|
|||
end
|
||||
end
|
||||
|
||||
def encode_custom_emojis(html, emojis)
|
||||
return html if emojis.empty?
|
||||
|
||||
emoji_map = emojis.map { |e| [e.shortcode, full_asset_url(e.image.url)] }.to_h
|
||||
|
||||
i = -1
|
||||
inside_tag = false
|
||||
inside_shortname = false
|
||||
shortname_start_index = -1
|
||||
|
||||
while i + 1 < html.size
|
||||
i += 1
|
||||
|
||||
if inside_shortname && html[i] == ':'
|
||||
shortcode = html[shortname_start_index + 1..i - 1]
|
||||
emoji = emoji_map[shortcode]
|
||||
|
||||
if emoji
|
||||
replacement = "<img draggable=\"false\" class=\"emojione\" alt=\":#{shortcode}:\" title=\":#{shortcode}:\" src=\"#{emoji}\" />"
|
||||
before_html = shortname_start_index.positive? ? html[0..shortname_start_index - 1] : ''
|
||||
html = before_html + replacement + html[i + 1..-1]
|
||||
i += replacement.size - (shortcode.size + 2) - 1
|
||||
else
|
||||
i -= 1
|
||||
end
|
||||
|
||||
inside_shortname = false
|
||||
elsif inside_tag && html[i] == '>'
|
||||
inside_tag = false
|
||||
elsif html[i] == '<'
|
||||
inside_tag = true
|
||||
inside_shortname = false
|
||||
elsif !inside_tag && html[i] == ':'
|
||||
inside_shortname = true
|
||||
shortname_start_index = i
|
||||
end
|
||||
end
|
||||
|
||||
html
|
||||
end
|
||||
|
||||
def rewrite(text, entities)
|
||||
chars = text.to_s.to_char_a
|
||||
|
||||
|
@ -131,13 +179,13 @@ class Formatter
|
|||
end
|
||||
|
||||
def link_html(url)
|
||||
url = Addressable::URI.parse(url).display_uri.to_s
|
||||
url = Addressable::URI.parse(url).to_s
|
||||
prefix = url.match(/\Ahttps?:\/\/(www\.)?/).to_s
|
||||
text = url[prefix.length, 30]
|
||||
suffix = url[prefix.length + 30..-1]
|
||||
cutoff = url[prefix.length..-1].length > 30
|
||||
|
||||
"<span class=\"invisible\">#{prefix}</span><span class=\"#{cutoff ? 'ellipsis' : ''}\">#{text}</span><span class=\"invisible\">#{suffix}</span>"
|
||||
"<span class=\"invisible\">#{encode(prefix)}</span><span class=\"#{cutoff ? 'ellipsis' : ''}\">#{encode(text)}</span><span class=\"invisible\">#{encode(suffix)}</span>"
|
||||
end
|
||||
|
||||
def hashtag_html(tag)
|
||||
|
|
|
@ -1,26 +1,31 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
class LanguageDetector
|
||||
attr_reader :text, :account
|
||||
include Singleton
|
||||
|
||||
def initialize(text, account = nil)
|
||||
@text = text
|
||||
@account = account
|
||||
def initialize
|
||||
@identifier = CLD3::NNetLanguageIdentifier.new(1, 2048)
|
||||
end
|
||||
|
||||
def to_iso_s
|
||||
detected_language_code || default_locale
|
||||
def detect(text, account)
|
||||
detect_language_code(text) || default_locale(account)
|
||||
end
|
||||
|
||||
def prepared_text
|
||||
simplified_text.strip
|
||||
def language_names
|
||||
@language_names =
|
||||
CLD3::TaskContextParams::LANGUAGE_NAMES.map { |name| iso6391(name.to_s).to_sym }
|
||||
.uniq
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def detected_language_code
|
||||
iso6391(result.language).to_sym if detected_language_reliable?
|
||||
def prepare_text(text)
|
||||
simplify_text(text).strip
|
||||
end
|
||||
|
||||
def detect_language_code(text)
|
||||
result = @identifier.find_language(prepare_text(text))
|
||||
iso6391(result.language.to_s).to_sym if result.reliable?
|
||||
end
|
||||
|
||||
def iso6391(bcp47)
|
||||
|
@ -32,15 +37,7 @@ class LanguageDetector
|
|||
ISO_639.find(iso639).alpha2
|
||||
end
|
||||
|
||||
def result
|
||||
@result ||= @identifier.find_language(prepared_text)
|
||||
end
|
||||
|
||||
def detected_language_reliable?
|
||||
result.reliable?
|
||||
end
|
||||
|
||||
def simplified_text
|
||||
def simplify_text(text)
|
||||
text.dup.tap do |new_text|
|
||||
new_text.gsub!(FetchLinkCardService::URL_PATTERN, '')
|
||||
new_text.gsub!(Account::MENTION_RE, '')
|
||||
|
@ -49,7 +46,7 @@ class LanguageDetector
|
|||
end
|
||||
end
|
||||
|
||||
def default_locale
|
||||
account&.user_locale&.to_sym || nil
|
||||
def default_locale(account)
|
||||
account.user_locale&.to_sym
|
||||
end
|
||||
end
|
||||
|
|
|
@ -42,6 +42,7 @@ class OStatus::Activity::Creation < OStatus::Activity::Base
|
|||
save_mentions(status)
|
||||
save_hashtags(status)
|
||||
save_media(status)
|
||||
save_emojis(status)
|
||||
end
|
||||
|
||||
if thread? && status.thread.nil?
|
||||
|
@ -150,6 +151,25 @@ class OStatus::Activity::Creation < OStatus::Activity::Base
|
|||
end
|
||||
end
|
||||
|
||||
def save_emojis(parent)
|
||||
do_not_download = DomainBlock.find_by(domain: parent.account.domain)&.reject_media?
|
||||
|
||||
return if do_not_download
|
||||
|
||||
@xml.xpath('./xmlns:link[@rel="emoji"]', xmlns: TagManager::XMLNS).each do |link|
|
||||
next unless link['href'] && link['name']
|
||||
|
||||
shortcode = link['name'].delete(':')
|
||||
emoji = CustomEmoji.find_by(shortcode: shortcode, domain: parent.account.domain)
|
||||
|
||||
next unless emoji.nil?
|
||||
|
||||
emoji = CustomEmoji.new(shortcode: shortcode, domain: parent.account.domain)
|
||||
emoji.image_remote_url = link['href']
|
||||
emoji.save
|
||||
end
|
||||
end
|
||||
|
||||
def account_from_href(href)
|
||||
url = Addressable::URI.parse(href).normalize
|
||||
|
||||
|
|
|
@ -368,5 +368,9 @@ class OStatus::AtomSerializer
|
|||
end
|
||||
|
||||
append_element(entry, 'mastodon:scope', status.visibility)
|
||||
|
||||
status.emojis.each do |emoji|
|
||||
append_element(entry, 'link', nil, rel: :emoji, href: full_asset_url(emoji.image.url), name: emoji.shortcode)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
@ -31,6 +31,8 @@ class Request
|
|||
|
||||
def perform
|
||||
http_client.headers(headers).public_send(@verb, @url.to_s, @options)
|
||||
rescue => e
|
||||
raise e.class, "#{e.message} on #{@url}"
|
||||
end
|
||||
|
||||
def headers
|
||||
|
|
|
@ -87,7 +87,7 @@ class TagManager
|
|||
def local_url?(url)
|
||||
uri = Addressable::URI.parse(url).normalize
|
||||
domain = uri.host + (uri.port ? ":#{uri.port}" : '')
|
||||
TagManager.instance.local_domain?(domain)
|
||||
TagManager.instance.web_domain?(domain)
|
||||
end
|
||||
|
||||
def uri_for(target)
|
||||
|
|
|
@ -106,6 +106,7 @@ class Account < ApplicationRecord
|
|||
scope :by_domain_accounts, -> { group(:domain).select(:domain, 'COUNT(*) AS accounts_count').order('accounts_count desc') }
|
||||
scope :matches_username, ->(value) { where(arel_table[:username].matches("#{value}%")) }
|
||||
scope :matches_display_name, ->(value) { where(arel_table[:display_name].matches("#{value}%")) }
|
||||
scope :matches_domain, ->(value) { where(arel_table[:domain].matches("%#{value}%")) }
|
||||
|
||||
delegate :email,
|
||||
:current_sign_in_ip,
|
||||
|
@ -174,6 +175,10 @@ class Account < ApplicationRecord
|
|||
end
|
||||
|
||||
class << self
|
||||
def readonly_attributes
|
||||
super - %w(statuses_count following_count followers_count)
|
||||
end
|
||||
|
||||
def domains
|
||||
reorder(nil).pluck('distinct accounts.domain')
|
||||
end
|
||||
|
|
|
@ -27,9 +27,11 @@ module Remotable
|
|||
|
||||
matches = response.headers['content-disposition']&.match(/filename="([^"]*)"/)
|
||||
filename = matches.nil? ? parsed_url.path.split('/').last : matches[1]
|
||||
basename = SecureRandom.hex(8)
|
||||
extname = File.extname(filename)
|
||||
|
||||
send("#{attachment_name}=", StringIO.new(response.to_s))
|
||||
send("#{attachment_name}_file_name=", filename)
|
||||
send("#{attachment_name}_file_name=", basename + extname)
|
||||
|
||||
self[attribute_name] = url if has_attribute?(attribute_name)
|
||||
rescue HTTP::TimeoutError, HTTP::ConnectionError, OpenSSL::SSL::SSLError, Paperclip::Errors::NotIdentifiedByImageMagickError, Addressable::URI::InvalidURIError => e
|
||||
|
|
|
@ -0,0 +1,38 @@
|
|||
# frozen_string_literal: true
|
||||
# == Schema Information
|
||||
#
|
||||
# Table name: custom_emojis
|
||||
#
|
||||
# id :integer not null, primary key
|
||||
# shortcode :string default(""), not null
|
||||
# domain :string
|
||||
# image_file_name :string
|
||||
# image_content_type :string
|
||||
# image_file_size :integer
|
||||
# image_updated_at :datetime
|
||||
# created_at :datetime not null
|
||||
# updated_at :datetime not null
|
||||
#
|
||||
|
||||
class CustomEmoji < ApplicationRecord
|
||||
SHORTCODE_RE_FRAGMENT = '[a-zA-Z0-9_]{2,}'
|
||||
|
||||
SCAN_RE = /(?<=[^[:alnum:]:]|\n|^)
|
||||
:(#{SHORTCODE_RE_FRAGMENT}):
|
||||
(?=[^[:alnum:]:]|$)/x
|
||||
|
||||
has_attached_file :image
|
||||
|
||||
validates_attachment :image, content_type: { content_type: 'image/png' }, presence: true, size: { in: 0..50.kilobytes }
|
||||
validates :shortcode, uniqueness: { scope: :domain }, format: { with: /\A#{SHORTCODE_RE_FRAGMENT}\z/ }, length: { minimum: 2 }
|
||||
|
||||
include Remotable
|
||||
|
||||
class << self
|
||||
def from_text(text, domain)
|
||||
return [] if text.blank?
|
||||
shortcodes = text.scan(SCAN_RE).map(&:first)
|
||||
where(shortcode: shortcodes, domain: domain)
|
||||
end
|
||||
end
|
||||
end
|
|
@ -0,0 +1,28 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
class InstanceFilter
|
||||
attr_reader :params
|
||||
|
||||
def initialize(params)
|
||||
@params = params
|
||||
end
|
||||
|
||||
def results
|
||||
scope = Account.remote.by_domain_accounts
|
||||
params.each do |key, value|
|
||||
scope.merge!(scope_for(key, value)) if value.present?
|
||||
end
|
||||
scope
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def scope_for(key, value)
|
||||
case key.to_s
|
||||
when 'domain_name'
|
||||
Account.matches_domain(value)
|
||||
else
|
||||
raise "Unknown filter: #{key}"
|
||||
end
|
||||
end
|
||||
end
|
|
@ -59,12 +59,18 @@ class MediaAttachment < ApplicationRecord
|
|||
scope :attached, -> { where.not(status_id: nil) }
|
||||
scope :unattached, -> { where(status_id: nil) }
|
||||
scope :local, -> { where(remote_url: '') }
|
||||
scope :remote, -> { where.not(remote_url: '') }
|
||||
|
||||
default_scope { order(id: :asc) }
|
||||
|
||||
def local?
|
||||
remote_url.blank?
|
||||
end
|
||||
|
||||
def needs_redownload?
|
||||
file.blank? && remote_url.present?
|
||||
end
|
||||
|
||||
def to_param
|
||||
shortcode
|
||||
end
|
||||
|
|
|
@ -0,0 +1,44 @@
|
|||
# frozen_string_literal: true
|
||||
# == Schema Information
|
||||
#
|
||||
# Table name: site_uploads
|
||||
#
|
||||
# id :integer not null, primary key
|
||||
# var :string default(""), not null
|
||||
# file_file_name :string
|
||||
# file_content_type :string
|
||||
# file_file_size :integer
|
||||
# file_updated_at :datetime
|
||||
# meta :json
|
||||
# created_at :datetime not null
|
||||
# updated_at :datetime not null
|
||||
#
|
||||
|
||||
class SiteUpload < ApplicationRecord
|
||||
has_attached_file :file
|
||||
|
||||
validates_attachment_content_type :file, content_type: /\Aimage\/.*\z/
|
||||
validates :var, presence: true, uniqueness: true
|
||||
|
||||
before_save :set_meta
|
||||
after_commit :clear_cache
|
||||
|
||||
def cache_key
|
||||
"site_uploads/#{var}"
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def set_meta
|
||||
tempfile = file.queued_for_write[:original]
|
||||
|
||||
return if tempfile.nil?
|
||||
|
||||
geometry = Paperclip::Geometry.from_file(tempfile)
|
||||
self.meta = { width: geometry.width.to_i, height: geometry.height.to_i }
|
||||
end
|
||||
|
||||
def clear_cache
|
||||
Rails.cache.delete(cache_key)
|
||||
end
|
||||
end
|
|
@ -55,7 +55,7 @@ class Status < ApplicationRecord
|
|||
has_one :notification, as: :activity, dependent: :destroy
|
||||
has_one :stream_entry, as: :activity, inverse_of: :status
|
||||
|
||||
validates :uri, uniqueness: true, unless: :local?
|
||||
validates :uri, uniqueness: true, presence: true, unless: :local?
|
||||
validates :text, presence: true, unless: :reblog?
|
||||
validates_with StatusLengthValidator
|
||||
validates :reblog, uniqueness: { scope: :account }, if: :reblog?
|
||||
|
@ -70,7 +70,6 @@ class Status < ApplicationRecord
|
|||
scope :without_reblogs, -> { where('statuses.reblog_of_id IS NULL') }
|
||||
scope :with_public_visibility, -> { where(visibility: :public) }
|
||||
scope :tagged_with, ->(tag) { joins(:statuses_tags).where(statuses_tags: { tag_id: tag }) }
|
||||
scope :local_only, -> { left_outer_joins(:account).where(accounts: { domain: nil }) }
|
||||
scope :excluding_silenced_accounts, -> { left_outer_joins(:account).where(accounts: { silenced: false }) }
|
||||
scope :including_silenced_accounts, -> { left_outer_joins(:account).where(accounts: { silenced: true }) }
|
||||
scope :not_excluded_by_account, ->(account) { where.not(account_id: account.excluded_from_timeline_account_ids) }
|
||||
|
@ -132,6 +131,10 @@ class Status < ApplicationRecord
|
|||
!sensitive? && media_attachments.any?
|
||||
end
|
||||
|
||||
def emojis
|
||||
CustomEmoji.from_text(text, account.domain)
|
||||
end
|
||||
|
||||
after_create :store_uri, if: :local?
|
||||
|
||||
before_validation :prepare_contents, if: :local?
|
||||
|
@ -221,7 +224,7 @@ class Status < ApplicationRecord
|
|||
private
|
||||
|
||||
def timeline_scope(local_only = false)
|
||||
starting_scope = local_only ? Status.local_only : Status
|
||||
starting_scope = local_only ? Status.local : Status
|
||||
starting_scope
|
||||
.with_public_visibility
|
||||
.without_reblogs
|
||||
|
|