Merge tag 'v2.8.2' into instance_only_statuses
This commit is contained in:
commit
84c8b1e200
51
CHANGELOG.md
51
CHANGELOG.md
@ -3,6 +3,57 @@ Changelog
|
|||||||
|
|
||||||
All notable changes to this project will be documented in this file.
|
All notable changes to this project will be documented in this file.
|
||||||
|
|
||||||
|
## [2.8.2] - 2019-05-05
|
||||||
|
### Added
|
||||||
|
|
||||||
|
- Add `SOURCE_TAG` environment variable ([ushitora-anqou](https://github.com/tootsuite/mastodon/pull/10698))
|
||||||
|
|
||||||
|
### Fixed
|
||||||
|
|
||||||
|
- Fix cropped hero image on frontpage ([BaptisteGelez](https://github.com/tootsuite/mastodon/pull/10702))
|
||||||
|
- Fix blurhash gem not compiling on some operating systems ([Gargron](https://github.com/tootsuite/mastodon/pull/10700))
|
||||||
|
- Fix unexpected CSS animations in some browsers ([ThibG](https://github.com/tootsuite/mastodon/pull/10699))
|
||||||
|
- Fix closing video modal scrolling timelines to top ([ThibG](https://github.com/tootsuite/mastodon/pull/10695))
|
||||||
|
|
||||||
|
## [2.8.1] - 2019-05-04
|
||||||
|
### Added
|
||||||
|
|
||||||
|
- Add link to existing domain block when trying to block an already-blocked domain ([ThibG](https://github.com/tootsuite/mastodon/pull/10663))
|
||||||
|
- Add button to view context to media modal when opened from account gallery in web UI ([Gargron](https://github.com/tootsuite/mastodon/pull/10676))
|
||||||
|
- Add ability to create multiple-choice polls in web UI ([ThibG](https://github.com/tootsuite/mastodon/pull/10603))
|
||||||
|
- Add `GITHUB_REPOSITORY` and `SOURCE_BASE_URL` environment variables ([rosylilly](https://github.com/tootsuite/mastodon/pull/10600))
|
||||||
|
- Add `/interact/` paths to `robots.txt` ([ThibG](https://github.com/tootsuite/mastodon/pull/10666))
|
||||||
|
- Add `blurhash` to the Attachment entity in the REST API ([Gargron](https://github.com/tootsuite/mastodon/pull/10630))
|
||||||
|
|
||||||
|
### Changed
|
||||||
|
|
||||||
|
- Change hidden media to be shown as a blurhash-based colorful gradient instead of a black box in web UI ([Gargron](https://github.com/tootsuite/mastodon/pull/10630))
|
||||||
|
- Change rejected media to be shown as a blurhash-based gradient instead of a list of filenames in web UI ([Gargron](https://github.com/tootsuite/mastodon/pull/10630))
|
||||||
|
- Change e-mail whitelist/blacklist to not be checked when invited ([Gargron](https://github.com/tootsuite/mastodon/pull/10683))
|
||||||
|
- Change cache header of REST API results to no-cache ([ThibG](https://github.com/tootsuite/mastodon/pull/10655))
|
||||||
|
- Change the "mark media as sensitive" button to be more obvious in web UI ([Gargron](https://github.com/tootsuite/mastodon/pull/10673), [Gargron](https://github.com/tootsuite/mastodon/pull/10682))
|
||||||
|
- Change account gallery in web UI to display 3 columns, open media modal ([Gargron](https://github.com/tootsuite/mastodon/pull/10667), [Gargron](https://github.com/tootsuite/mastodon/pull/10674))
|
||||||
|
|
||||||
|
### Fixed
|
||||||
|
|
||||||
|
- Fix LDAP/PAM/SAML/CAS users not being pre-approved ([Gargron](https://github.com/tootsuite/mastodon/pull/10621))
|
||||||
|
- Fix accounts created through tootctl not being always pre-approved ([Gargron](https://github.com/tootsuite/mastodon/pull/10684))
|
||||||
|
- Fix Sidekiq retrying ActivityPub processing jobs that fail validation ([ThibG](https://github.com/tootsuite/mastodon/pull/10614))
|
||||||
|
- Fix toots not being scrolled into view sometimes through keyboard selection ([ThibG](https://github.com/tootsuite/mastodon/pull/10593))
|
||||||
|
- Fix expired invite links being usable to bypass approval mode ([ThibG](https://github.com/tootsuite/mastodon/pull/10657))
|
||||||
|
- Fix not being able to save e-mail preference for new pending accounts ([Gargron](https://github.com/tootsuite/mastodon/pull/10622))
|
||||||
|
- Fix upload progressbar when image resizing is involved ([ThibG](https://github.com/tootsuite/mastodon/pull/10632))
|
||||||
|
- Fix block action not automatically cancelling pending follow request ([ThibG](https://github.com/tootsuite/mastodon/pull/10633))
|
||||||
|
- Fix stoplight logging to stderr separate from Rails logger ([Gargron](https://github.com/tootsuite/mastodon/pull/10624))
|
||||||
|
- Fix sign up button not saying sign up when invite is used ([Gargron](https://github.com/tootsuite/mastodon/pull/10623))
|
||||||
|
- Fix health checks in Docker Compose configuration ([fabianonline](https://github.com/tootsuite/mastodon/pull/10553))
|
||||||
|
- Fix modal items not being scrollable on touch devices ([kedamaDQ](https://github.com/tootsuite/mastodon/pull/10605))
|
||||||
|
- Fix Keybase configuration using wrong domain when a web domain is used ([BenLubar](https://github.com/tootsuite/mastodon/pull/10565))
|
||||||
|
- Fix avatar GIFs not being animated on-hover on public profiles ([hyenagirl64](https://github.com/tootsuite/mastodon/pull/10549))
|
||||||
|
- Fix OpenGraph parser not understanding some valid property meta tags ([da2x](https://github.com/tootsuite/mastodon/pull/10604))
|
||||||
|
- Fix wrong fonts being displayed when Roboto is installed on user's machine ([ThibG](https://github.com/tootsuite/mastodon/pull/10594))
|
||||||
|
- Fix confirmation modals being too narrow for a secondary action button ([ThibG](https://github.com/tootsuite/mastodon/pull/10586))
|
||||||
|
|
||||||
## [2.8.0] - 2019-04-10
|
## [2.8.0] - 2019-04-10
|
||||||
### Added
|
### Added
|
||||||
|
|
||||||
|
17
Gemfile
17
Gemfile
@ -21,6 +21,7 @@ gem 'fog-openstack', '~> 0.3', require: false
|
|||||||
gem 'paperclip', '~> 6.0'
|
gem 'paperclip', '~> 6.0'
|
||||||
gem 'paperclip-av-transcoder', '~> 0.6'
|
gem 'paperclip-av-transcoder', '~> 0.6'
|
||||||
gem 'streamio-ffmpeg', '~> 3.0'
|
gem 'streamio-ffmpeg', '~> 3.0'
|
||||||
|
gem 'blurhash', '~> 0.1'
|
||||||
|
|
||||||
gem 'active_model_serializers', '~> 0.10'
|
gem 'active_model_serializers', '~> 0.10'
|
||||||
gem 'addressable', '~> 2.6'
|
gem 'addressable', '~> 2.6'
|
||||||
@ -29,7 +30,7 @@ gem 'browser'
|
|||||||
gem 'charlock_holmes', '~> 0.7.6'
|
gem 'charlock_holmes', '~> 0.7.6'
|
||||||
gem 'iso-639'
|
gem 'iso-639'
|
||||||
gem 'chewy', '~> 5.0'
|
gem 'chewy', '~> 5.0'
|
||||||
gem 'cld3', '~> 3.2.3'
|
gem 'cld3', '~> 3.2.4'
|
||||||
gem 'devise', '~> 4.6'
|
gem 'devise', '~> 4.6'
|
||||||
gem 'devise-two-factor', '~> 3.0'
|
gem 'devise-two-factor', '~> 3.0'
|
||||||
|
|
||||||
@ -42,7 +43,7 @@ gem 'omniauth-cas', '~> 1.1'
|
|||||||
gem 'omniauth-saml', '~> 1.10'
|
gem 'omniauth-saml', '~> 1.10'
|
||||||
gem 'omniauth', '~> 1.9'
|
gem 'omniauth', '~> 1.9'
|
||||||
|
|
||||||
gem 'doorkeeper', '~> 5.0'
|
gem 'doorkeeper', '~> 5.1'
|
||||||
gem 'fast_blank', '~> 1.0'
|
gem 'fast_blank', '~> 1.0'
|
||||||
gem 'fastimage'
|
gem 'fastimage'
|
||||||
gem 'goldfinger', '~> 2.1'
|
gem 'goldfinger', '~> 2.1'
|
||||||
@ -65,7 +66,7 @@ gem 'ox', '~> 2.10'
|
|||||||
gem 'posix-spawn', git: 'https://github.com/rtomayko/posix-spawn', ref: '58465d2e213991f8afb13b984854a49fcdcc980c'
|
gem 'posix-spawn', git: 'https://github.com/rtomayko/posix-spawn', ref: '58465d2e213991f8afb13b984854a49fcdcc980c'
|
||||||
gem 'pundit', '~> 2.0'
|
gem 'pundit', '~> 2.0'
|
||||||
gem 'premailer-rails'
|
gem 'premailer-rails'
|
||||||
gem 'rack-attack', '~> 5.4'
|
gem 'rack-attack', '~> 6.0'
|
||||||
gem 'rack-cors', '~> 1.0', require: 'rack/cors'
|
gem 'rack-cors', '~> 1.0', require: 'rack/cors'
|
||||||
gem 'rails-i18n', '~> 5.1'
|
gem 'rails-i18n', '~> 5.1'
|
||||||
gem 'rails-settings-cached', '~> 0.6'
|
gem 'rails-settings-cached', '~> 0.6'
|
||||||
@ -107,7 +108,7 @@ group :production, :test do
|
|||||||
end
|
end
|
||||||
|
|
||||||
group :test do
|
group :test do
|
||||||
gem 'capybara', '~> 3.16'
|
gem 'capybara', '~> 3.18'
|
||||||
gem 'climate_control', '~> 0.2'
|
gem 'climate_control', '~> 0.2'
|
||||||
gem 'faker', '~> 1.9'
|
gem 'faker', '~> 1.9'
|
||||||
gem 'microformats', '~> 4.1'
|
gem 'microformats', '~> 4.1'
|
||||||
@ -123,14 +124,14 @@ group :development do
|
|||||||
gem 'annotate', '~> 2.7'
|
gem 'annotate', '~> 2.7'
|
||||||
gem 'better_errors', '~> 2.5'
|
gem 'better_errors', '~> 2.5'
|
||||||
gem 'binding_of_caller', '~> 0.7'
|
gem 'binding_of_caller', '~> 0.7'
|
||||||
gem 'bullet', '~> 5.9'
|
gem 'bullet', '~> 6.0'
|
||||||
gem 'letter_opener', '~> 1.7'
|
gem 'letter_opener', '~> 1.7'
|
||||||
gem 'letter_opener_web', '~> 1.3'
|
gem 'letter_opener_web', '~> 1.3'
|
||||||
gem 'memory_profiler'
|
gem 'memory_profiler'
|
||||||
gem 'rubocop', '~> 0.67', require: false
|
gem 'rubocop', '~> 0.68', require: false
|
||||||
gem 'brakeman', '~> 4.5', require: false
|
gem 'brakeman', '~> 4.5', require: false
|
||||||
gem 'bundler-audit', '~> 0.6', require: false
|
gem 'bundler-audit', '~> 0.6', require: false
|
||||||
gem 'scss_lint', '~> 0.57', require: false
|
gem 'scss_lint', '~> 0.58', require: false
|
||||||
|
|
||||||
gem 'capistrano', '~> 3.11'
|
gem 'capistrano', '~> 3.11'
|
||||||
gem 'capistrano-rails', '~> 1.4'
|
gem 'capistrano-rails', '~> 1.4'
|
||||||
@ -142,7 +143,7 @@ group :development do
|
|||||||
end
|
end
|
||||||
|
|
||||||
group :production do
|
group :production do
|
||||||
gem 'lograge', '~> 0.10'
|
gem 'lograge', '~> 0.11'
|
||||||
gem 'redis-rails', '~> 5.0'
|
gem 'redis-rails', '~> 5.0'
|
||||||
end
|
end
|
||||||
|
|
||||||
|
81
Gemfile.lock
81
Gemfile.lock
@ -66,8 +66,8 @@ GEM
|
|||||||
public_suffix (>= 2.0.2, < 4.0)
|
public_suffix (>= 2.0.2, < 4.0)
|
||||||
airbrussh (1.3.0)
|
airbrussh (1.3.0)
|
||||||
sshkit (>= 1.6.1, != 1.7.0)
|
sshkit (>= 1.6.1, != 1.7.0)
|
||||||
annotate (2.7.4)
|
annotate (2.7.5)
|
||||||
activerecord (>= 3.2, < 6.0)
|
activerecord (>= 3.2, < 7.0)
|
||||||
rake (>= 10.4, < 13.0)
|
rake (>= 10.4, < 13.0)
|
||||||
arel (9.0.0)
|
arel (9.0.0)
|
||||||
ast (2.4.0)
|
ast (2.4.0)
|
||||||
@ -76,16 +76,16 @@ GEM
|
|||||||
av (0.9.0)
|
av (0.9.0)
|
||||||
cocaine (~> 0.5.3)
|
cocaine (~> 0.5.3)
|
||||||
aws-eventstream (1.0.2)
|
aws-eventstream (1.0.2)
|
||||||
aws-partitions (1.147.0)
|
aws-partitions (1.151.0)
|
||||||
aws-sdk-core (3.48.3)
|
aws-sdk-core (3.48.4)
|
||||||
aws-eventstream (~> 1.0, >= 1.0.2)
|
aws-eventstream (~> 1.0, >= 1.0.2)
|
||||||
aws-partitions (~> 1.0)
|
aws-partitions (~> 1.0)
|
||||||
aws-sigv4 (~> 1.1)
|
aws-sigv4 (~> 1.1)
|
||||||
jmespath (~> 1.0)
|
jmespath (~> 1.0)
|
||||||
aws-sdk-kms (1.16.0)
|
aws-sdk-kms (1.17.0)
|
||||||
aws-sdk-core (~> 3, >= 3.48.2)
|
aws-sdk-core (~> 3, >= 3.48.2)
|
||||||
aws-sigv4 (~> 1.1)
|
aws-sigv4 (~> 1.1)
|
||||||
aws-sdk-s3 (1.36.0)
|
aws-sdk-s3 (1.36.1)
|
||||||
aws-sdk-core (~> 3, >= 3.48.2)
|
aws-sdk-core (~> 3, >= 3.48.2)
|
||||||
aws-sdk-kms (~> 1)
|
aws-sdk-kms (~> 1)
|
||||||
aws-sigv4 (~> 1.0)
|
aws-sigv4 (~> 1.0)
|
||||||
@ -99,12 +99,14 @@ GEM
|
|||||||
rack (>= 0.9.0)
|
rack (>= 0.9.0)
|
||||||
binding_of_caller (0.8.0)
|
binding_of_caller (0.8.0)
|
||||||
debug_inspector (>= 0.0.1)
|
debug_inspector (>= 0.0.1)
|
||||||
bootsnap (1.4.3)
|
blurhash (0.1.3)
|
||||||
|
ffi (~> 1.10.0)
|
||||||
|
bootsnap (1.4.4)
|
||||||
msgpack (~> 1.0)
|
msgpack (~> 1.0)
|
||||||
brakeman (4.5.0)
|
brakeman (4.5.0)
|
||||||
browser (2.5.3)
|
browser (2.5.3)
|
||||||
builder (3.2.3)
|
builder (3.2.3)
|
||||||
bullet (5.9.0)
|
bullet (6.0.0)
|
||||||
activesupport (>= 3.0.0)
|
activesupport (>= 3.0.0)
|
||||||
uniform_notifier (~> 1.11)
|
uniform_notifier (~> 1.11)
|
||||||
bundler-audit (0.6.1)
|
bundler-audit (0.6.1)
|
||||||
@ -127,7 +129,7 @@ GEM
|
|||||||
sshkit (~> 1.3)
|
sshkit (~> 1.3)
|
||||||
capistrano-yarn (2.0.2)
|
capistrano-yarn (2.0.2)
|
||||||
capistrano (~> 3.0)
|
capistrano (~> 3.0)
|
||||||
capybara (3.16.1)
|
capybara (3.18.0)
|
||||||
addressable
|
addressable
|
||||||
mini_mime (>= 0.1.3)
|
mini_mime (>= 0.1.3)
|
||||||
nokogiri (~> 1.8)
|
nokogiri (~> 1.8)
|
||||||
@ -143,8 +145,8 @@ GEM
|
|||||||
elasticsearch (>= 2.0.0)
|
elasticsearch (>= 2.0.0)
|
||||||
elasticsearch-dsl
|
elasticsearch-dsl
|
||||||
chunky_png (1.3.10)
|
chunky_png (1.3.10)
|
||||||
cld3 (3.2.3)
|
cld3 (3.2.4)
|
||||||
ffi (>= 1.1.0, < 1.10.0)
|
ffi (>= 1.1.0, < 1.11.0)
|
||||||
climate_control (0.2.0)
|
climate_control (0.2.0)
|
||||||
cocaine (0.5.8)
|
cocaine (0.5.8)
|
||||||
climate_control (>= 0.0.3, < 1.0)
|
climate_control (>= 0.0.3, < 1.0)
|
||||||
@ -184,8 +186,8 @@ GEM
|
|||||||
docile (1.3.0)
|
docile (1.3.0)
|
||||||
domain_name (0.5.20180417)
|
domain_name (0.5.20180417)
|
||||||
unf (>= 0.0.5, < 1.0.0)
|
unf (>= 0.0.5, < 1.0.0)
|
||||||
doorkeeper (5.0.2)
|
doorkeeper (5.1.0)
|
||||||
railties (>= 4.2)
|
railties (>= 5)
|
||||||
dotenv (2.7.2)
|
dotenv (2.7.2)
|
||||||
dotenv-rails (2.7.2)
|
dotenv-rails (2.7.2)
|
||||||
dotenv (= 2.7.2)
|
dotenv (= 2.7.2)
|
||||||
@ -205,14 +207,14 @@ GEM
|
|||||||
et-orbi (1.1.6)
|
et-orbi (1.1.6)
|
||||||
tzinfo
|
tzinfo
|
||||||
excon (0.62.0)
|
excon (0.62.0)
|
||||||
fabrication (2.20.1)
|
fabrication (2.20.2)
|
||||||
faker (1.9.3)
|
faker (1.9.3)
|
||||||
i18n (>= 0.7)
|
i18n (>= 0.7)
|
||||||
faraday (0.15.0)
|
faraday (0.15.0)
|
||||||
multipart-post (>= 1.2, < 3)
|
multipart-post (>= 1.2, < 3)
|
||||||
fast_blank (1.0.0)
|
fast_blank (1.0.0)
|
||||||
fastimage (2.1.5)
|
fastimage (2.1.5)
|
||||||
ffi (1.9.25)
|
ffi (1.10.0)
|
||||||
fog-core (2.1.0)
|
fog-core (2.1.0)
|
||||||
builder
|
builder
|
||||||
excon (~> 0.58)
|
excon (~> 0.58)
|
||||||
@ -318,7 +320,7 @@ GEM
|
|||||||
letter_opener (~> 1.0)
|
letter_opener (~> 1.0)
|
||||||
railties (>= 3.2)
|
railties (>= 3.2)
|
||||||
link_header (0.0.8)
|
link_header (0.0.8)
|
||||||
lograge (0.10.0)
|
lograge (0.11.0)
|
||||||
actionpack (>= 4)
|
actionpack (>= 4)
|
||||||
activesupport (>= 4)
|
activesupport (>= 4)
|
||||||
railties (>= 4)
|
railties (>= 4)
|
||||||
@ -346,7 +348,7 @@ GEM
|
|||||||
mini_mime (1.0.1)
|
mini_mime (1.0.1)
|
||||||
mini_portile2 (2.4.0)
|
mini_portile2 (2.4.0)
|
||||||
minitest (5.11.3)
|
minitest (5.11.3)
|
||||||
msgpack (1.2.9)
|
msgpack (1.2.10)
|
||||||
multi_json (1.13.1)
|
multi_json (1.13.1)
|
||||||
multipart-post (2.0.0)
|
multipart-post (2.0.0)
|
||||||
necromancer (0.4.0)
|
necromancer (0.4.0)
|
||||||
@ -355,7 +357,7 @@ GEM
|
|||||||
net-ssh (>= 2.6.5)
|
net-ssh (>= 2.6.5)
|
||||||
net-ssh (5.0.2)
|
net-ssh (5.0.2)
|
||||||
nio4r (2.3.1)
|
nio4r (2.3.1)
|
||||||
nokogiri (1.10.2)
|
nokogiri (1.10.3)
|
||||||
mini_portile2 (~> 2.4.0)
|
mini_portile2 (~> 2.4.0)
|
||||||
nokogumbo (2.0.0)
|
nokogumbo (2.0.0)
|
||||||
nokogiri (~> 1.8, >= 1.8.4)
|
nokogiri (~> 1.8, >= 1.8.4)
|
||||||
@ -364,7 +366,7 @@ GEM
|
|||||||
concurrent-ruby (~> 1.0, >= 1.0.2)
|
concurrent-ruby (~> 1.0, >= 1.0.2)
|
||||||
sidekiq (>= 3.5)
|
sidekiq (>= 3.5)
|
||||||
statsd-ruby (~> 1.4, >= 1.4.0)
|
statsd-ruby (~> 1.4, >= 1.4.0)
|
||||||
oj (3.7.11)
|
oj (3.7.12)
|
||||||
omniauth (1.9.0)
|
omniauth (1.9.0)
|
||||||
hashie (>= 3.4.6, < 3.7.0)
|
hashie (>= 3.4.6, < 3.7.0)
|
||||||
rack (>= 1.6.2, < 3)
|
rack (>= 1.6.2, < 3)
|
||||||
@ -393,7 +395,7 @@ GEM
|
|||||||
parallel (1.17.0)
|
parallel (1.17.0)
|
||||||
parallel_tests (2.28.0)
|
parallel_tests (2.28.0)
|
||||||
parallel
|
parallel
|
||||||
parser (2.6.2.0)
|
parser (2.6.3.0)
|
||||||
ast (~> 2.4.0)
|
ast (~> 2.4.0)
|
||||||
pastel (0.7.2)
|
pastel (0.7.2)
|
||||||
equatable (~> 0.5.0)
|
equatable (~> 0.5.0)
|
||||||
@ -418,14 +420,13 @@ GEM
|
|||||||
pry (~> 0.10)
|
pry (~> 0.10)
|
||||||
pry-rails (0.3.9)
|
pry-rails (0.3.9)
|
||||||
pry (>= 0.10.4)
|
pry (>= 0.10.4)
|
||||||
psych (3.1.0)
|
|
||||||
public_suffix (3.0.3)
|
public_suffix (3.0.3)
|
||||||
puma (3.12.1)
|
puma (3.12.1)
|
||||||
pundit (2.0.1)
|
pundit (2.0.1)
|
||||||
activesupport (>= 3.0.0)
|
activesupport (>= 3.0.0)
|
||||||
raabro (1.1.6)
|
raabro (1.1.6)
|
||||||
rack (2.0.7)
|
rack (2.0.7)
|
||||||
rack-attack (5.4.2)
|
rack-attack (6.0.0)
|
||||||
rack (>= 1.0, < 3)
|
rack (>= 1.0, < 3)
|
||||||
rack-cors (1.0.3)
|
rack-cors (1.0.3)
|
||||||
rack-protection (2.0.5)
|
rack-protection (2.0.5)
|
||||||
@ -470,8 +471,8 @@ GEM
|
|||||||
rainbow (3.0.0)
|
rainbow (3.0.0)
|
||||||
rake (12.3.2)
|
rake (12.3.2)
|
||||||
rb-fsevent (0.10.3)
|
rb-fsevent (0.10.3)
|
||||||
rb-inotify (0.9.10)
|
rb-inotify (0.10.0)
|
||||||
ffi (>= 0.5.0, < 2)
|
ffi (~> 1.0)
|
||||||
rdf (3.0.9)
|
rdf (3.0.9)
|
||||||
hamster (~> 3.0)
|
hamster (~> 3.0)
|
||||||
link_header (~> 0.0, >= 0.0.8)
|
link_header (~> 0.0, >= 0.0.8)
|
||||||
@ -496,7 +497,7 @@ GEM
|
|||||||
redis-store (>= 1.2, < 2)
|
redis-store (>= 1.2, < 2)
|
||||||
redis-store (1.5.0)
|
redis-store (1.5.0)
|
||||||
redis (>= 2.2, < 5)
|
redis (>= 2.2, < 5)
|
||||||
regexp_parser (1.3.0)
|
regexp_parser (1.4.0)
|
||||||
request_store (1.4.1)
|
request_store (1.4.1)
|
||||||
rack (>= 1.4)
|
rack (>= 1.4)
|
||||||
responders (2.4.1)
|
responders (2.4.1)
|
||||||
@ -526,11 +527,10 @@ GEM
|
|||||||
rspec-core (~> 3.0, >= 3.0.0)
|
rspec-core (~> 3.0, >= 3.0.0)
|
||||||
sidekiq (>= 2.4.0)
|
sidekiq (>= 2.4.0)
|
||||||
rspec-support (3.8.0)
|
rspec-support (3.8.0)
|
||||||
rubocop (0.67.1)
|
rubocop (0.68.1)
|
||||||
jaro_winkler (~> 1.5.1)
|
jaro_winkler (~> 1.5.1)
|
||||||
parallel (~> 1.10)
|
parallel (~> 1.10)
|
||||||
parser (>= 2.5, != 2.5.1.1)
|
parser (>= 2.5, != 2.5.1.1)
|
||||||
psych (>= 3.1.0)
|
|
||||||
rainbow (>= 2.2.2, < 4.0)
|
rainbow (>= 2.2.2, < 4.0)
|
||||||
ruby-progressbar (~> 1.7)
|
ruby-progressbar (~> 1.7)
|
||||||
unicode-display_width (>= 1.4.0, < 1.6)
|
unicode-display_width (>= 1.4.0, < 1.6)
|
||||||
@ -544,15 +544,15 @@ GEM
|
|||||||
crass (~> 1.0.2)
|
crass (~> 1.0.2)
|
||||||
nokogiri (>= 1.8.0)
|
nokogiri (>= 1.8.0)
|
||||||
nokogumbo (~> 2.0)
|
nokogumbo (~> 2.0)
|
||||||
sass (3.6.0)
|
sass (3.7.4)
|
||||||
sass-listen (~> 4.0.0)
|
sass-listen (~> 4.0.0)
|
||||||
sass-listen (4.0.0)
|
sass-listen (4.0.0)
|
||||||
rb-fsevent (~> 0.9, >= 0.9.4)
|
rb-fsevent (~> 0.9, >= 0.9.4)
|
||||||
rb-inotify (~> 0.9, >= 0.9.7)
|
rb-inotify (~> 0.9, >= 0.9.7)
|
||||||
scss_lint (0.57.1)
|
scss_lint (0.58.0)
|
||||||
rake (>= 0.9, < 13)
|
rake (>= 0.9, < 13)
|
||||||
sass (~> 3.5, >= 3.5.5)
|
sass (~> 3.5, >= 3.5.5)
|
||||||
sidekiq (5.2.5)
|
sidekiq (5.2.7)
|
||||||
connection_pool (~> 2.2, >= 2.2.2)
|
connection_pool (~> 2.2, >= 2.2.2)
|
||||||
rack (>= 1.5.0)
|
rack (>= 1.5.0)
|
||||||
rack-protection (>= 1.5.0)
|
rack-protection (>= 1.5.0)
|
||||||
@ -564,7 +564,7 @@ GEM
|
|||||||
rufus-scheduler (~> 3.2)
|
rufus-scheduler (~> 3.2)
|
||||||
sidekiq (>= 3)
|
sidekiq (>= 3)
|
||||||
tilt (>= 1.4.0)
|
tilt (>= 1.4.0)
|
||||||
sidekiq-unique-jobs (6.0.12)
|
sidekiq-unique-jobs (6.0.13)
|
||||||
concurrent-ruby (~> 1.0, >= 1.0.5)
|
concurrent-ruby (~> 1.0, >= 1.0.5)
|
||||||
sidekiq (>= 4.0, < 7.0)
|
sidekiq (>= 4.0, < 7.0)
|
||||||
thor (~> 0)
|
thor (~> 0)
|
||||||
@ -640,7 +640,7 @@ GEM
|
|||||||
activesupport (>= 4.2)
|
activesupport (>= 4.2)
|
||||||
rack-proxy (>= 0.6.1)
|
rack-proxy (>= 0.6.1)
|
||||||
railties (>= 4.2)
|
railties (>= 4.2)
|
||||||
webpush (0.3.7)
|
webpush (0.3.8)
|
||||||
hkdf (~> 0.2)
|
hkdf (~> 0.2)
|
||||||
jwt (~> 2.0)
|
jwt (~> 2.0)
|
||||||
websocket-driver (0.7.0)
|
websocket-driver (0.7.0)
|
||||||
@ -661,26 +661,27 @@ DEPENDENCIES
|
|||||||
aws-sdk-s3 (~> 1.36)
|
aws-sdk-s3 (~> 1.36)
|
||||||
better_errors (~> 2.5)
|
better_errors (~> 2.5)
|
||||||
binding_of_caller (~> 0.7)
|
binding_of_caller (~> 0.7)
|
||||||
|
blurhash (~> 0.1)
|
||||||
bootsnap (~> 1.4)
|
bootsnap (~> 1.4)
|
||||||
brakeman (~> 4.5)
|
brakeman (~> 4.5)
|
||||||
browser
|
browser
|
||||||
bullet (~> 5.9)
|
bullet (~> 6.0)
|
||||||
bundler-audit (~> 0.6)
|
bundler-audit (~> 0.6)
|
||||||
capistrano (~> 3.11)
|
capistrano (~> 3.11)
|
||||||
capistrano-rails (~> 1.4)
|
capistrano-rails (~> 1.4)
|
||||||
capistrano-rbenv (~> 2.1)
|
capistrano-rbenv (~> 2.1)
|
||||||
capistrano-yarn (~> 2.0)
|
capistrano-yarn (~> 2.0)
|
||||||
capybara (~> 3.16)
|
capybara (~> 3.18)
|
||||||
charlock_holmes (~> 0.7.6)
|
charlock_holmes (~> 0.7.6)
|
||||||
chewy (~> 5.0)
|
chewy (~> 5.0)
|
||||||
cld3 (~> 3.2.3)
|
cld3 (~> 3.2.4)
|
||||||
climate_control (~> 0.2)
|
climate_control (~> 0.2)
|
||||||
concurrent-ruby
|
concurrent-ruby
|
||||||
derailed_benchmarks
|
derailed_benchmarks
|
||||||
devise (~> 4.6)
|
devise (~> 4.6)
|
||||||
devise-two-factor (~> 3.0)
|
devise-two-factor (~> 3.0)
|
||||||
devise_pam_authenticatable2 (~> 9.2)
|
devise_pam_authenticatable2 (~> 9.2)
|
||||||
doorkeeper (~> 5.0)
|
doorkeeper (~> 5.1)
|
||||||
dotenv-rails (~> 2.7)
|
dotenv-rails (~> 2.7)
|
||||||
fabrication (~> 2.20)
|
fabrication (~> 2.20)
|
||||||
faker (~> 1.9)
|
faker (~> 1.9)
|
||||||
@ -706,7 +707,7 @@ DEPENDENCIES
|
|||||||
letter_opener (~> 1.7)
|
letter_opener (~> 1.7)
|
||||||
letter_opener_web (~> 1.3)
|
letter_opener_web (~> 1.3)
|
||||||
link_header (~> 0.0)
|
link_header (~> 0.0)
|
||||||
lograge (~> 0.10)
|
lograge (~> 0.11)
|
||||||
makara (~> 0.4)
|
makara (~> 0.4)
|
||||||
mario-redis-lock (~> 1.2)
|
mario-redis-lock (~> 1.2)
|
||||||
memory_profiler
|
memory_profiler
|
||||||
@ -734,7 +735,7 @@ DEPENDENCIES
|
|||||||
pry-rails (~> 0.3)
|
pry-rails (~> 0.3)
|
||||||
puma (~> 3.12)
|
puma (~> 3.12)
|
||||||
pundit (~> 2.0)
|
pundit (~> 2.0)
|
||||||
rack-attack (~> 5.4)
|
rack-attack (~> 6.0)
|
||||||
rack-cors (~> 1.0)
|
rack-cors (~> 1.0)
|
||||||
rails (~> 5.2.3)
|
rails (~> 5.2.3)
|
||||||
rails-controller-testing (~> 1.0)
|
rails-controller-testing (~> 1.0)
|
||||||
@ -747,9 +748,9 @@ DEPENDENCIES
|
|||||||
rqrcode (~> 0.10)
|
rqrcode (~> 0.10)
|
||||||
rspec-rails (~> 3.8)
|
rspec-rails (~> 3.8)
|
||||||
rspec-sidekiq (~> 3.0)
|
rspec-sidekiq (~> 3.0)
|
||||||
rubocop (~> 0.67)
|
rubocop (~> 0.68)
|
||||||
sanitize (~> 5.0)
|
sanitize (~> 5.0)
|
||||||
scss_lint (~> 0.57)
|
scss_lint (~> 0.58)
|
||||||
sidekiq (~> 5.2)
|
sidekiq (~> 5.2)
|
||||||
sidekiq-bulk (~> 0.2.0)
|
sidekiq-bulk (~> 0.2.0)
|
||||||
sidekiq-scheduler (~> 3.0)
|
sidekiq-scheduler (~> 3.0)
|
||||||
|
@ -13,13 +13,25 @@ module Admin
|
|||||||
authorize :domain_block, :create?
|
authorize :domain_block, :create?
|
||||||
|
|
||||||
@domain_block = DomainBlock.new(resource_params)
|
@domain_block = DomainBlock.new(resource_params)
|
||||||
|
existing_domain_block = resource_params[:domain].present? ? DomainBlock.find_by(domain: resource_params[:domain]) : nil
|
||||||
|
|
||||||
if @domain_block.save
|
if existing_domain_block.present? && !@domain_block.stricter_than?(existing_domain_block)
|
||||||
DomainBlockWorker.perform_async(@domain_block.id)
|
@domain_block.save
|
||||||
log_action :create, @domain_block
|
flash[:alert] = I18n.t('admin.domain_blocks.existing_domain_block_html', name: existing_domain_block.domain, unblock_url: admin_domain_block_path(existing_domain_block)).html_safe # rubocop:disable Rails/OutputSafety
|
||||||
redirect_to admin_instances_path(limited: '1'), notice: I18n.t('admin.domain_blocks.created_msg')
|
@domain_block.errors[:domain].clear
|
||||||
else
|
|
||||||
render :new
|
render :new
|
||||||
|
else
|
||||||
|
if existing_domain_block.present?
|
||||||
|
@domain_block = existing_domain_block
|
||||||
|
@domain_block.update(resource_params)
|
||||||
|
end
|
||||||
|
if @domain_block.save
|
||||||
|
DomainBlockWorker.perform_async(@domain_block.id)
|
||||||
|
log_action :create, @domain_block
|
||||||
|
redirect_to admin_instances_path(limited: '1'), notice: I18n.t('admin.domain_blocks.created_msg')
|
||||||
|
else
|
||||||
|
render :new
|
||||||
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
@ -9,6 +9,8 @@ class Api::BaseController < ApplicationController
|
|||||||
skip_before_action :store_current_location
|
skip_before_action :store_current_location
|
||||||
skip_before_action :check_user_permissions
|
skip_before_action :check_user_permissions
|
||||||
|
|
||||||
|
before_action :set_cache_headers
|
||||||
|
|
||||||
protect_from_forgery with: :null_session
|
protect_from_forgery with: :null_session
|
||||||
|
|
||||||
rescue_from ActiveRecord::RecordInvalid, Mastodon::ValidationError do |e|
|
rescue_from ActiveRecord::RecordInvalid, Mastodon::ValidationError do |e|
|
||||||
@ -88,4 +90,8 @@ class Api::BaseController < ApplicationController
|
|||||||
def authorize_if_got_token!(*scopes)
|
def authorize_if_got_token!(*scopes)
|
||||||
doorkeeper_authorize!(*scopes) if doorkeeper_token
|
doorkeeper_authorize!(*scopes) if doorkeeper_token
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def set_cache_headers
|
||||||
|
response.headers['Cache-Control'] = 'no-cache, no-store, max-age=0, must-revalidate'
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
@ -3,6 +3,8 @@
|
|||||||
class Api::V1::CustomEmojisController < Api::BaseController
|
class Api::V1::CustomEmojisController < Api::BaseController
|
||||||
respond_to :json
|
respond_to :json
|
||||||
|
|
||||||
|
skip_before_action :set_cache_headers
|
||||||
|
|
||||||
def index
|
def index
|
||||||
render_cached_json('api:v1:custom_emojis', expires_in: 1.minute) do
|
render_cached_json('api:v1:custom_emojis', expires_in: 1.minute) do
|
||||||
ActiveModelSerializers::SerializableResource.new(CustomEmoji.local.where(disabled: false), each_serializer: REST::CustomEmojiSerializer)
|
ActiveModelSerializers::SerializableResource.new(CustomEmoji.local.where(disabled: false), each_serializer: REST::CustomEmojiSerializer)
|
||||||
|
@ -2,6 +2,7 @@
|
|||||||
|
|
||||||
class Api::V1::Instances::ActivityController < Api::BaseController
|
class Api::V1::Instances::ActivityController < Api::BaseController
|
||||||
before_action :require_enabled_api!
|
before_action :require_enabled_api!
|
||||||
|
skip_before_action :set_cache_headers
|
||||||
|
|
||||||
respond_to :json
|
respond_to :json
|
||||||
|
|
||||||
|
@ -2,6 +2,7 @@
|
|||||||
|
|
||||||
class Api::V1::Instances::PeersController < Api::BaseController
|
class Api::V1::Instances::PeersController < Api::BaseController
|
||||||
before_action :require_enabled_api!
|
before_action :require_enabled_api!
|
||||||
|
skip_before_action :set_cache_headers
|
||||||
|
|
||||||
respond_to :json
|
respond_to :json
|
||||||
|
|
||||||
|
@ -2,6 +2,7 @@
|
|||||||
|
|
||||||
class Api::V1::InstancesController < Api::BaseController
|
class Api::V1::InstancesController < Api::BaseController
|
||||||
respond_to :json
|
respond_to :json
|
||||||
|
skip_before_action :set_cache_headers
|
||||||
|
|
||||||
def show
|
def show
|
||||||
render_cached_json('api:v1:instances', expires_in: 5.minutes) do
|
render_cached_json('api:v1:instances', expires_in: 5.minutes) do
|
||||||
|
@ -91,7 +91,8 @@ class Auth::RegistrationsController < Devise::RegistrationsController
|
|||||||
end
|
end
|
||||||
|
|
||||||
def set_invite
|
def set_invite
|
||||||
@invite = invite_code.present? ? Invite.find_by(code: invite_code) : nil
|
invite = invite_code.present? ? Invite.find_by(code: invite_code) : nil
|
||||||
|
@invite = invite&.valid_for_use? ? invite : nil
|
||||||
end
|
end
|
||||||
|
|
||||||
def determine_layout
|
def determine_layout
|
||||||
|
@ -25,7 +25,7 @@ class Settings::NotificationsController < Settings::BaseController
|
|||||||
|
|
||||||
def user_settings_params
|
def user_settings_params
|
||||||
params.require(:user).permit(
|
params.require(:user).permit(
|
||||||
notification_emails: %i(follow follow_request reblog favourite mention digest report),
|
notification_emails: %i(follow follow_request reblog favourite mention digest report pending_account),
|
||||||
interactions: %i(must_be_follower must_be_following must_be_following_dm)
|
interactions: %i(must_be_follower must_be_following must_be_following_dm)
|
||||||
)
|
)
|
||||||
end
|
end
|
||||||
|
@ -205,8 +205,8 @@ export function uploadCompose(files) {
|
|||||||
return function (dispatch, getState) {
|
return function (dispatch, getState) {
|
||||||
const uploadLimit = 4;
|
const uploadLimit = 4;
|
||||||
const media = getState().getIn(['compose', 'media_attachments']);
|
const media = getState().getIn(['compose', 'media_attachments']);
|
||||||
const total = Array.from(files).reduce((a, v) => a + v.size, 0);
|
|
||||||
const progress = new Array(files.length).fill(0);
|
const progress = new Array(files.length).fill(0);
|
||||||
|
let total = Array.from(files).reduce((a, v) => a + v.size, 0);
|
||||||
|
|
||||||
if (files.length + media.size > uploadLimit) {
|
if (files.length + media.size > uploadLimit) {
|
||||||
dispatch(showAlert(undefined, messages.uploadErrorLimit));
|
dispatch(showAlert(undefined, messages.uploadErrorLimit));
|
||||||
@ -226,6 +226,8 @@ export function uploadCompose(files) {
|
|||||||
resizeImage(f).then(file => {
|
resizeImage(f).then(file => {
|
||||||
const data = new FormData();
|
const data = new FormData();
|
||||||
data.append('file', file);
|
data.append('file', file);
|
||||||
|
// Account for disparity in size of original image and resized data
|
||||||
|
total += file.size - f.size;
|
||||||
|
|
||||||
return api(getState).post('/api/v1/media', data, {
|
return api(getState).post('/api/v1/media', data, {
|
||||||
onUploadProgress: function({ loaded }){
|
onUploadProgress: function({ loaded }){
|
||||||
|
@ -96,7 +96,7 @@ export const expandPublicTimeline = ({ maxId, onlyMedia } = {}, done =
|
|||||||
export const expandCommunityTimeline = ({ maxId, onlyMedia } = {}, done = noOp) => expandTimeline(`community${onlyMedia ? ':media' : ''}`, '/api/v1/timelines/public', { local: true, max_id: maxId, only_media: !!onlyMedia }, done);
|
export const expandCommunityTimeline = ({ maxId, onlyMedia } = {}, done = noOp) => expandTimeline(`community${onlyMedia ? ':media' : ''}`, '/api/v1/timelines/public', { local: true, max_id: maxId, only_media: !!onlyMedia }, done);
|
||||||
export const expandAccountTimeline = (accountId, { maxId, withReplies } = {}) => expandTimeline(`account:${accountId}${withReplies ? ':with_replies' : ''}`, `/api/v1/accounts/${accountId}/statuses`, { exclude_replies: !withReplies, max_id: maxId });
|
export const expandAccountTimeline = (accountId, { maxId, withReplies } = {}) => expandTimeline(`account:${accountId}${withReplies ? ':with_replies' : ''}`, `/api/v1/accounts/${accountId}/statuses`, { exclude_replies: !withReplies, max_id: maxId });
|
||||||
export const expandAccountFeaturedTimeline = accountId => expandTimeline(`account:${accountId}:pinned`, `/api/v1/accounts/${accountId}/statuses`, { pinned: true });
|
export const expandAccountFeaturedTimeline = accountId => expandTimeline(`account:${accountId}:pinned`, `/api/v1/accounts/${accountId}/statuses`, { pinned: true });
|
||||||
export const expandAccountMediaTimeline = (accountId, { maxId } = {}) => expandTimeline(`account:${accountId}:media`, `/api/v1/accounts/${accountId}/statuses`, { max_id: maxId, only_media: true });
|
export const expandAccountMediaTimeline = (accountId, { maxId } = {}) => expandTimeline(`account:${accountId}:media`, `/api/v1/accounts/${accountId}/statuses`, { max_id: maxId, only_media: true, limit: 40 });
|
||||||
export const expandListTimeline = (id, { maxId } = {}, done = noOp) => expandTimeline(`list:${id}`, `/api/v1/timelines/list/${id}`, { max_id: maxId }, done);
|
export const expandListTimeline = (id, { maxId } = {}, done = noOp) => expandTimeline(`list:${id}`, `/api/v1/timelines/list/${id}`, { max_id: maxId }, done);
|
||||||
export const expandHashtagTimeline = (hashtag, { maxId, tags } = {}, done = noOp) => {
|
export const expandHashtagTimeline = (hashtag, { maxId, tags } = {}, done = noOp) => {
|
||||||
return expandTimeline(`hashtag:${hashtag}`, `/api/v1/timelines/tag/${hashtag}`, {
|
return expandTimeline(`hashtag:${hashtag}`, `/api/v1/timelines/tag/${hashtag}`, {
|
||||||
|
@ -7,6 +7,7 @@ import { defineMessages, injectIntl, FormattedMessage } from 'react-intl';
|
|||||||
import { isIOS } from '../is_mobile';
|
import { isIOS } from '../is_mobile';
|
||||||
import classNames from 'classnames';
|
import classNames from 'classnames';
|
||||||
import { autoPlayGif, displayMedia } from '../initial_state';
|
import { autoPlayGif, displayMedia } from '../initial_state';
|
||||||
|
import { decode } from 'blurhash';
|
||||||
|
|
||||||
const messages = defineMessages({
|
const messages = defineMessages({
|
||||||
toggle_visible: { id: 'media_gallery.toggle_visible', defaultMessage: 'Toggle visibility' },
|
toggle_visible: { id: 'media_gallery.toggle_visible', defaultMessage: 'Toggle visibility' },
|
||||||
@ -21,6 +22,7 @@ class Item extends React.PureComponent {
|
|||||||
size: PropTypes.number.isRequired,
|
size: PropTypes.number.isRequired,
|
||||||
onClick: PropTypes.func.isRequired,
|
onClick: PropTypes.func.isRequired,
|
||||||
displayWidth: PropTypes.number,
|
displayWidth: PropTypes.number,
|
||||||
|
visible: PropTypes.bool.isRequired,
|
||||||
};
|
};
|
||||||
|
|
||||||
static defaultProps = {
|
static defaultProps = {
|
||||||
@ -29,6 +31,10 @@ class Item extends React.PureComponent {
|
|||||||
size: 1,
|
size: 1,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
state = {
|
||||||
|
loaded: false,
|
||||||
|
};
|
||||||
|
|
||||||
handleMouseEnter = (e) => {
|
handleMouseEnter = (e) => {
|
||||||
if (this.hoverToPlay()) {
|
if (this.hoverToPlay()) {
|
||||||
e.target.play();
|
e.target.play();
|
||||||
@ -62,8 +68,40 @@ class Item extends React.PureComponent {
|
|||||||
e.stopPropagation();
|
e.stopPropagation();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
componentDidMount () {
|
||||||
|
if (this.props.attachment.get('blurhash')) {
|
||||||
|
this._decode();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
componentDidUpdate (prevProps) {
|
||||||
|
if (prevProps.attachment.get('blurhash') !== this.props.attachment.get('blurhash') && this.props.attachment.get('blurhash')) {
|
||||||
|
this._decode();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
_decode () {
|
||||||
|
const hash = this.props.attachment.get('blurhash');
|
||||||
|
const pixels = decode(hash, 32, 32);
|
||||||
|
|
||||||
|
if (pixels) {
|
||||||
|
const ctx = this.canvas.getContext('2d');
|
||||||
|
const imageData = new ImageData(pixels, 32, 32);
|
||||||
|
|
||||||
|
ctx.putImageData(imageData, 0, 0);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
setCanvasRef = c => {
|
||||||
|
this.canvas = c;
|
||||||
|
}
|
||||||
|
|
||||||
|
handleImageLoad = () => {
|
||||||
|
this.setState({ loaded: true });
|
||||||
|
}
|
||||||
|
|
||||||
render () {
|
render () {
|
||||||
const { attachment, index, size, standalone, displayWidth } = this.props;
|
const { attachment, index, size, standalone, displayWidth, visible } = this.props;
|
||||||
|
|
||||||
let width = 50;
|
let width = 50;
|
||||||
let height = 100;
|
let height = 100;
|
||||||
@ -116,12 +154,20 @@ class Item extends React.PureComponent {
|
|||||||
|
|
||||||
let thumbnail = '';
|
let thumbnail = '';
|
||||||
|
|
||||||
if (attachment.get('type') === 'image') {
|
if (attachment.get('type') === 'unknown') {
|
||||||
|
return (
|
||||||
|
<div className={classNames('media-gallery__item', { standalone })} key={attachment.get('id')} style={{ left: left, top: top, right: right, bottom: bottom, width: `${width}%`, height: `${height}%` }}>
|
||||||
|
<a className='media-gallery__item-thumbnail' href={attachment.get('remote_url')} target='_blank' style={{ cursor: 'pointer' }}>
|
||||||
|
<canvas width={32} height={32} ref={this.setCanvasRef} className='media-gallery__preview' />
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
} else if (attachment.get('type') === 'image') {
|
||||||
const previewUrl = attachment.get('preview_url');
|
const previewUrl = attachment.get('preview_url');
|
||||||
const previewWidth = attachment.getIn(['meta', 'small', 'width']);
|
const previewWidth = attachment.getIn(['meta', 'small', 'width']);
|
||||||
|
|
||||||
const originalUrl = attachment.get('url');
|
const originalUrl = attachment.get('url');
|
||||||
const originalWidth = attachment.getIn(['meta', 'original', 'width']);
|
const originalWidth = attachment.getIn(['meta', 'original', 'width']);
|
||||||
|
|
||||||
const hasSize = typeof originalWidth === 'number' && typeof previewWidth === 'number';
|
const hasSize = typeof originalWidth === 'number' && typeof previewWidth === 'number';
|
||||||
|
|
||||||
@ -147,6 +193,7 @@ class Item extends React.PureComponent {
|
|||||||
alt={attachment.get('description')}
|
alt={attachment.get('description')}
|
||||||
title={attachment.get('description')}
|
title={attachment.get('description')}
|
||||||
style={{ objectPosition: `${x}% ${y}%` }}
|
style={{ objectPosition: `${x}% ${y}%` }}
|
||||||
|
onLoad={this.handleImageLoad}
|
||||||
/>
|
/>
|
||||||
</a>
|
</a>
|
||||||
);
|
);
|
||||||
@ -176,7 +223,8 @@ class Item extends React.PureComponent {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<div className={classNames('media-gallery__item', { standalone })} key={attachment.get('id')} style={{ left: left, top: top, right: right, bottom: bottom, width: `${width}%`, height: `${height}%` }}>
|
<div className={classNames('media-gallery__item', { standalone })} key={attachment.get('id')} style={{ left: left, top: top, right: right, bottom: bottom, width: `${width}%`, height: `${height}%` }}>
|
||||||
{thumbnail}
|
<canvas width={32} height={32} ref={this.setCanvasRef} className={classNames('media-gallery__preview', { 'media-gallery__preview--hidden': visible && this.state.loaded })} />
|
||||||
|
{visible && thumbnail}
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@ -225,6 +273,7 @@ class MediaGallery extends React.PureComponent {
|
|||||||
if (node /*&& this.isStandaloneEligible()*/) {
|
if (node /*&& this.isStandaloneEligible()*/) {
|
||||||
// offsetWidth triggers a layout, so only calculate when we need to
|
// offsetWidth triggers a layout, so only calculate when we need to
|
||||||
if (this.props.cacheWidth) this.props.cacheWidth(node.offsetWidth);
|
if (this.props.cacheWidth) this.props.cacheWidth(node.offsetWidth);
|
||||||
|
|
||||||
this.setState({
|
this.setState({
|
||||||
width: node.offsetWidth,
|
width: node.offsetWidth,
|
||||||
});
|
});
|
||||||
@ -242,7 +291,7 @@ class MediaGallery extends React.PureComponent {
|
|||||||
|
|
||||||
const width = this.state.width || defaultWidth;
|
const width = this.state.width || defaultWidth;
|
||||||
|
|
||||||
let children;
|
let children, spoilerButton;
|
||||||
|
|
||||||
const style = {};
|
const style = {};
|
||||||
|
|
||||||
@ -256,35 +305,28 @@ class MediaGallery extends React.PureComponent {
|
|||||||
style.height = height;
|
style.height = height;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!visible) {
|
const size = media.take(4).size;
|
||||||
let warning;
|
|
||||||
|
|
||||||
if (sensitive) {
|
if (this.isStandaloneEligible()) {
|
||||||
warning = <FormattedMessage id='status.sensitive_warning' defaultMessage='Sensitive content' />;
|
children = <Item standalone onClick={this.handleClick} attachment={media.get(0)} displayWidth={width} visible={visible} />;
|
||||||
} else {
|
} else {
|
||||||
warning = <FormattedMessage id='status.media_hidden' defaultMessage='Media hidden' />;
|
children = media.take(4).map((attachment, i) => <Item key={attachment.get('id')} onClick={this.handleClick} attachment={attachment} index={i} size={size} displayWidth={width} visible={visible} />);
|
||||||
}
|
}
|
||||||
|
|
||||||
children = (
|
if (visible) {
|
||||||
<button type='button' className='media-spoiler' onClick={this.handleOpen} style={style} ref={this.handleRef}>
|
spoilerButton = <IconButton title={intl.formatMessage(messages.toggle_visible)} icon='eye-slash' overlay onClick={this.handleOpen} />;
|
||||||
<span className='media-spoiler__warning'>{warning}</span>
|
} else {
|
||||||
<span className='media-spoiler__trigger'><FormattedMessage id='status.sensitive_toggle' defaultMessage='Click to view' /></span>
|
spoilerButton = (
|
||||||
|
<button type='button' onClick={this.handleOpen} className='spoiler-button__overlay'>
|
||||||
|
<span className='spoiler-button__overlay__label'>{sensitive ? <FormattedMessage id='status.sensitive_warning' defaultMessage='Sensitive content' /> : <FormattedMessage id='status.media_hidden' defaultMessage='Media hidden' />}</span>
|
||||||
</button>
|
</button>
|
||||||
);
|
);
|
||||||
} else {
|
|
||||||
const size = media.take(4).size;
|
|
||||||
|
|
||||||
if (this.isStandaloneEligible()) {
|
|
||||||
children = <Item standalone onClick={this.handleClick} attachment={media.get(0)} displayWidth={width} />;
|
|
||||||
} else {
|
|
||||||
children = media.take(4).map((attachment, i) => <Item key={attachment.get('id')} onClick={this.handleClick} attachment={attachment} index={i} size={size} displayWidth={width} />);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className='media-gallery' style={style} ref={this.handleRef}>
|
<div className='media-gallery' style={style} ref={this.handleRef}>
|
||||||
<div className={classNames('spoiler-button', { 'spoiler-button--visible': visible })}>
|
<div className={classNames('spoiler-button', { 'spoiler-button--minified': visible })}>
|
||||||
<IconButton title={intl.formatMessage(messages.toggle_visible)} icon={visible ? 'eye' : 'eye-slash'} overlay onClick={this.handleOpen} />
|
{spoilerButton}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{children}
|
{children}
|
||||||
|
@ -274,7 +274,7 @@ class Status extends ImmutablePureComponent {
|
|||||||
if (status.get('poll')) {
|
if (status.get('poll')) {
|
||||||
media = <PollContainer pollId={status.get('poll')} />;
|
media = <PollContainer pollId={status.get('poll')} />;
|
||||||
} else if (status.get('media_attachments').size > 0) {
|
} else if (status.get('media_attachments').size > 0) {
|
||||||
if (this.props.muted || status.get('media_attachments').some(item => item.get('type') === 'unknown')) {
|
if (this.props.muted) {
|
||||||
media = (
|
media = (
|
||||||
<AttachmentList
|
<AttachmentList
|
||||||
compact
|
compact
|
||||||
@ -289,6 +289,7 @@ class Status extends ImmutablePureComponent {
|
|||||||
{Component => (
|
{Component => (
|
||||||
<Component
|
<Component
|
||||||
preview={video.get('preview_url')}
|
preview={video.get('preview_url')}
|
||||||
|
blurhash={video.get('blurhash')}
|
||||||
src={video.get('url')}
|
src={video.get('url')}
|
||||||
alt={video.get('description')}
|
alt={video.get('description')}
|
||||||
width={this.props.cachedMediaWidth}
|
width={this.props.cachedMediaWidth}
|
||||||
|
@ -46,22 +46,28 @@ export default class StatusList extends ImmutablePureComponent {
|
|||||||
|
|
||||||
handleMoveUp = (id, featured) => {
|
handleMoveUp = (id, featured) => {
|
||||||
const elementIndex = this.getCurrentStatusIndex(id, featured) - 1;
|
const elementIndex = this.getCurrentStatusIndex(id, featured) - 1;
|
||||||
this._selectChild(elementIndex);
|
this._selectChild(elementIndex, true);
|
||||||
}
|
}
|
||||||
|
|
||||||
handleMoveDown = (id, featured) => {
|
handleMoveDown = (id, featured) => {
|
||||||
const elementIndex = this.getCurrentStatusIndex(id, featured) + 1;
|
const elementIndex = this.getCurrentStatusIndex(id, featured) + 1;
|
||||||
this._selectChild(elementIndex);
|
this._selectChild(elementIndex, false);
|
||||||
}
|
}
|
||||||
|
|
||||||
handleLoadOlder = debounce(() => {
|
handleLoadOlder = debounce(() => {
|
||||||
this.props.onLoadMore(this.props.statusIds.size > 0 ? this.props.statusIds.last() : undefined);
|
this.props.onLoadMore(this.props.statusIds.size > 0 ? this.props.statusIds.last() : undefined);
|
||||||
}, 300, { leading: true })
|
}, 300, { leading: true })
|
||||||
|
|
||||||
_selectChild (index) {
|
_selectChild (index, align_top) {
|
||||||
const element = this.node.node.querySelector(`article:nth-of-type(${index + 1}) .focusable`);
|
const container = this.node.node;
|
||||||
|
const element = container.querySelector(`article:nth-of-type(${index + 1}) .focusable`);
|
||||||
|
|
||||||
if (element) {
|
if (element) {
|
||||||
|
if (align_top && container.scrollTop > element.offsetTop) {
|
||||||
|
element.scrollIntoView(true);
|
||||||
|
} else if (!align_top && container.scrollTop + container.clientHeight < element.offsetTop + element.offsetHeight) {
|
||||||
|
element.scrollIntoView(false);
|
||||||
|
}
|
||||||
element.focus();
|
element.focus();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,62 +1,142 @@
|
|||||||
import React from 'react';
|
import React from 'react';
|
||||||
|
import PropTypes from 'prop-types';
|
||||||
import ImmutablePropTypes from 'react-immutable-proptypes';
|
import ImmutablePropTypes from 'react-immutable-proptypes';
|
||||||
import ImmutablePureComponent from 'react-immutable-pure-component';
|
import ImmutablePureComponent from 'react-immutable-pure-component';
|
||||||
import Permalink from '../../../components/permalink';
|
import { autoPlayGif, displayMedia } from 'mastodon/initial_state';
|
||||||
import { displayMedia } from '../../../initial_state';
|
import classNames from 'classnames';
|
||||||
import Icon from 'mastodon/components/icon';
|
import { decode } from 'blurhash';
|
||||||
|
import { isIOS } from 'mastodon/is_mobile';
|
||||||
|
|
||||||
export default class MediaItem extends ImmutablePureComponent {
|
export default class MediaItem extends ImmutablePureComponent {
|
||||||
|
|
||||||
static propTypes = {
|
static propTypes = {
|
||||||
media: ImmutablePropTypes.map.isRequired,
|
attachment: ImmutablePropTypes.map.isRequired,
|
||||||
|
displayWidth: PropTypes.number.isRequired,
|
||||||
|
onOpenMedia: PropTypes.func.isRequired,
|
||||||
};
|
};
|
||||||
|
|
||||||
state = {
|
state = {
|
||||||
visible: displayMedia !== 'hide_all' && !this.props.media.getIn(['status', 'sensitive']) || displayMedia === 'show_all',
|
visible: displayMedia !== 'hide_all' && !this.props.attachment.getIn(['status', 'sensitive']) || displayMedia === 'show_all',
|
||||||
|
loaded: false,
|
||||||
};
|
};
|
||||||
|
|
||||||
handleClick = () => {
|
componentDidMount () {
|
||||||
if (!this.state.visible) {
|
if (this.props.attachment.get('blurhash')) {
|
||||||
this.setState({ visible: true });
|
this._decode();
|
||||||
return true;
|
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
return false;
|
componentDidUpdate (prevProps) {
|
||||||
|
if (prevProps.attachment.get('blurhash') !== this.props.attachment.get('blurhash') && this.props.attachment.get('blurhash')) {
|
||||||
|
this._decode();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
_decode () {
|
||||||
|
const hash = this.props.attachment.get('blurhash');
|
||||||
|
const pixels = decode(hash, 32, 32);
|
||||||
|
|
||||||
|
if (pixels) {
|
||||||
|
const ctx = this.canvas.getContext('2d');
|
||||||
|
const imageData = new ImageData(pixels, 32, 32);
|
||||||
|
|
||||||
|
ctx.putImageData(imageData, 0, 0);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
setCanvasRef = c => {
|
||||||
|
this.canvas = c;
|
||||||
|
}
|
||||||
|
|
||||||
|
handleImageLoad = () => {
|
||||||
|
this.setState({ loaded: true });
|
||||||
|
}
|
||||||
|
|
||||||
|
handleMouseEnter = e => {
|
||||||
|
if (this.hoverToPlay()) {
|
||||||
|
e.target.play();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
handleMouseLeave = e => {
|
||||||
|
if (this.hoverToPlay()) {
|
||||||
|
e.target.pause();
|
||||||
|
e.target.currentTime = 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
hoverToPlay () {
|
||||||
|
return !autoPlayGif && ['gifv', 'video'].indexOf(this.props.attachment.get('type')) !== -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
handleClick = e => {
|
||||||
|
if (e.button === 0 && !(e.ctrlKey || e.metaKey)) {
|
||||||
|
e.preventDefault();
|
||||||
|
|
||||||
|
if (this.state.visible) {
|
||||||
|
this.props.onOpenMedia(this.props.attachment);
|
||||||
|
} else {
|
||||||
|
this.setState({ visible: true });
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
render () {
|
render () {
|
||||||
const { media } = this.props;
|
const { attachment, displayWidth } = this.props;
|
||||||
const { visible } = this.state;
|
const { visible, loaded } = this.state;
|
||||||
const status = media.get('status');
|
|
||||||
const focusX = media.getIn(['meta', 'focus', 'x']);
|
|
||||||
const focusY = media.getIn(['meta', 'focus', 'y']);
|
|
||||||
const x = ((focusX / 2) + .5) * 100;
|
|
||||||
const y = ((focusY / -2) + .5) * 100;
|
|
||||||
const style = {};
|
|
||||||
|
|
||||||
let label, icon;
|
const width = `${Math.floor((displayWidth - 4) / 3) - 4}px`;
|
||||||
|
const height = width;
|
||||||
|
const status = attachment.get('status');
|
||||||
|
|
||||||
if (media.get('type') === 'gifv') {
|
let thumbnail = '';
|
||||||
label = <span className='media-gallery__gifv__label'>GIF</span>;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (visible) {
|
if (attachment.get('type') === 'unknown') {
|
||||||
style.backgroundImage = `url(${media.get('preview_url')})`;
|
// Skip
|
||||||
style.backgroundPosition = `${x}% ${y}%`;
|
} else if (attachment.get('type') === 'image') {
|
||||||
} else {
|
const focusX = attachment.getIn(['meta', 'focus', 'x']) || 0;
|
||||||
icon = (
|
const focusY = attachment.getIn(['meta', 'focus', 'y']) || 0;
|
||||||
<span className='account-gallery__item__icons'>
|
const x = ((focusX / 2) + .5) * 100;
|
||||||
<Icon id='eye-slash' />
|
const y = ((focusY / -2) + .5) * 100;
|
||||||
</span>
|
|
||||||
|
thumbnail = (
|
||||||
|
<img
|
||||||
|
src={attachment.get('preview_url')}
|
||||||
|
alt={attachment.get('description')}
|
||||||
|
title={attachment.get('description')}
|
||||||
|
style={{ objectPosition: `${x}% ${y}%` }}
|
||||||
|
onLoad={this.handleImageLoad}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
} else if (['gifv', 'video'].indexOf(attachment.get('type')) !== -1) {
|
||||||
|
const autoPlay = !isIOS() && autoPlayGif;
|
||||||
|
|
||||||
|
thumbnail = (
|
||||||
|
<div className={classNames('media-gallery__gifv', { autoplay: autoPlay })}>
|
||||||
|
<video
|
||||||
|
className='media-gallery__item-gifv-thumbnail'
|
||||||
|
aria-label={attachment.get('description')}
|
||||||
|
title={attachment.get('description')}
|
||||||
|
role='application'
|
||||||
|
src={attachment.get('url')}
|
||||||
|
onMouseEnter={this.handleMouseEnter}
|
||||||
|
onMouseLeave={this.handleMouseLeave}
|
||||||
|
autoPlay={autoPlay}
|
||||||
|
loop
|
||||||
|
muted
|
||||||
|
/>
|
||||||
|
|
||||||
|
<span className='media-gallery__gifv__label'>GIF</span>
|
||||||
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className='account-gallery__item'>
|
<div className='account-gallery__item' style={{ width, height }}>
|
||||||
<Permalink to={`/statuses/${status.get('id')}`} href={status.get('url')} style={style} onInterceptClick={this.handleClick}>
|
<a className='media-gallery__item-thumbnail' href={status.get('url')} target='_blank' onClick={this.handleClick}>
|
||||||
{icon}
|
<canvas width={32} height={32} ref={this.setCanvasRef} className={classNames('media-gallery__preview', { 'media-gallery__preview--hidden': visible && loaded })} />
|
||||||
{label}
|
{visible && thumbnail}
|
||||||
</Permalink>
|
</a>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@ -2,24 +2,25 @@ import React from 'react';
|
|||||||
import { connect } from 'react-redux';
|
import { connect } from 'react-redux';
|
||||||
import ImmutablePropTypes from 'react-immutable-proptypes';
|
import ImmutablePropTypes from 'react-immutable-proptypes';
|
||||||
import PropTypes from 'prop-types';
|
import PropTypes from 'prop-types';
|
||||||
import { fetchAccount } from '../../actions/accounts';
|
import { fetchAccount } from 'mastodon/actions/accounts';
|
||||||
import { expandAccountMediaTimeline } from '../../actions/timelines';
|
import { expandAccountMediaTimeline } from '../../actions/timelines';
|
||||||
import LoadingIndicator from '../../components/loading_indicator';
|
import LoadingIndicator from 'mastodon/components/loading_indicator';
|
||||||
import Column from '../ui/components/column';
|
import Column from '../ui/components/column';
|
||||||
import ColumnBackButton from '../../components/column_back_button';
|
import ColumnBackButton from 'mastodon/components/column_back_button';
|
||||||
import ImmutablePureComponent from 'react-immutable-pure-component';
|
import ImmutablePureComponent from 'react-immutable-pure-component';
|
||||||
import { getAccountGallery } from '../../selectors';
|
import { getAccountGallery } from 'mastodon/selectors';
|
||||||
import MediaItem from './components/media_item';
|
import MediaItem from './components/media_item';
|
||||||
import HeaderContainer from '../account_timeline/containers/header_container';
|
import HeaderContainer from '../account_timeline/containers/header_container';
|
||||||
import { ScrollContainer } from 'react-router-scroll-4';
|
import { ScrollContainer } from 'react-router-scroll-4';
|
||||||
import LoadMore from '../../components/load_more';
|
import LoadMore from 'mastodon/components/load_more';
|
||||||
import MissingIndicator from 'mastodon/components/missing_indicator';
|
import MissingIndicator from 'mastodon/components/missing_indicator';
|
||||||
|
import { openModal } from 'mastodon/actions/modal';
|
||||||
|
|
||||||
const mapStateToProps = (state, props) => ({
|
const mapStateToProps = (state, props) => ({
|
||||||
isAccount: !!state.getIn(['accounts', props.params.accountId]),
|
isAccount: !!state.getIn(['accounts', props.params.accountId]),
|
||||||
medias: getAccountGallery(state, props.params.accountId),
|
attachments: getAccountGallery(state, props.params.accountId),
|
||||||
isLoading: state.getIn(['timelines', `account:${props.params.accountId}:media`, 'isLoading']),
|
isLoading: state.getIn(['timelines', `account:${props.params.accountId}:media`, 'isLoading']),
|
||||||
hasMore: state.getIn(['timelines', `account:${props.params.accountId}:media`, 'hasMore']),
|
hasMore: state.getIn(['timelines', `account:${props.params.accountId}:media`, 'hasMore']),
|
||||||
});
|
});
|
||||||
|
|
||||||
class LoadMoreMedia extends ImmutablePureComponent {
|
class LoadMoreMedia extends ImmutablePureComponent {
|
||||||
@ -51,12 +52,16 @@ class AccountGallery extends ImmutablePureComponent {
|
|||||||
static propTypes = {
|
static propTypes = {
|
||||||
params: PropTypes.object.isRequired,
|
params: PropTypes.object.isRequired,
|
||||||
dispatch: PropTypes.func.isRequired,
|
dispatch: PropTypes.func.isRequired,
|
||||||
medias: ImmutablePropTypes.list.isRequired,
|
attachments: ImmutablePropTypes.list.isRequired,
|
||||||
isLoading: PropTypes.bool,
|
isLoading: PropTypes.bool,
|
||||||
hasMore: PropTypes.bool,
|
hasMore: PropTypes.bool,
|
||||||
isAccount: PropTypes.bool,
|
isAccount: PropTypes.bool,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
state = {
|
||||||
|
width: 323,
|
||||||
|
};
|
||||||
|
|
||||||
componentDidMount () {
|
componentDidMount () {
|
||||||
this.props.dispatch(fetchAccount(this.props.params.accountId));
|
this.props.dispatch(fetchAccount(this.props.params.accountId));
|
||||||
this.props.dispatch(expandAccountMediaTimeline(this.props.params.accountId));
|
this.props.dispatch(expandAccountMediaTimeline(this.props.params.accountId));
|
||||||
@ -71,11 +76,11 @@ class AccountGallery extends ImmutablePureComponent {
|
|||||||
|
|
||||||
handleScrollToBottom = () => {
|
handleScrollToBottom = () => {
|
||||||
if (this.props.hasMore) {
|
if (this.props.hasMore) {
|
||||||
this.handleLoadMore(this.props.medias.size > 0 ? this.props.medias.last().getIn(['status', 'id']) : undefined);
|
this.handleLoadMore(this.props.attachments.size > 0 ? this.props.attachments.last().getIn(['status', 'id']) : undefined);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
handleScroll = (e) => {
|
handleScroll = e => {
|
||||||
const { scrollTop, scrollHeight, clientHeight } = e.target;
|
const { scrollTop, scrollHeight, clientHeight } = e.target;
|
||||||
const offset = scrollHeight - scrollTop - clientHeight;
|
const offset = scrollHeight - scrollTop - clientHeight;
|
||||||
|
|
||||||
@ -88,13 +93,31 @@ class AccountGallery extends ImmutablePureComponent {
|
|||||||
this.props.dispatch(expandAccountMediaTimeline(this.props.params.accountId, { maxId }));
|
this.props.dispatch(expandAccountMediaTimeline(this.props.params.accountId, { maxId }));
|
||||||
};
|
};
|
||||||
|
|
||||||
handleLoadOlder = (e) => {
|
handleLoadOlder = e => {
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
this.handleScrollToBottom();
|
this.handleScrollToBottom();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
handleOpenMedia = attachment => {
|
||||||
|
if (attachment.get('type') === 'video') {
|
||||||
|
this.props.dispatch(openModal('VIDEO', { media: attachment, status: attachment.get('status') }));
|
||||||
|
} else {
|
||||||
|
const media = attachment.getIn(['status', 'media_attachments']);
|
||||||
|
const index = media.findIndex(x => x.get('id') === attachment.get('id'));
|
||||||
|
|
||||||
|
this.props.dispatch(openModal('MEDIA', { media, index, status: attachment.get('status') }));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
handleRef = c => {
|
||||||
|
if (c) {
|
||||||
|
this.setState({ width: c.offsetWidth });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
render () {
|
render () {
|
||||||
const { medias, shouldUpdateScroll, isLoading, hasMore, isAccount } = this.props;
|
const { attachments, shouldUpdateScroll, isLoading, hasMore, isAccount } = this.props;
|
||||||
|
const { width } = this.state;
|
||||||
|
|
||||||
if (!isAccount) {
|
if (!isAccount) {
|
||||||
return (
|
return (
|
||||||
@ -104,9 +127,7 @@ class AccountGallery extends ImmutablePureComponent {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
let loadOlder = null;
|
if (!attachments && isLoading) {
|
||||||
|
|
||||||
if (!medias && isLoading) {
|
|
||||||
return (
|
return (
|
||||||
<Column>
|
<Column>
|
||||||
<LoadingIndicator />
|
<LoadingIndicator />
|
||||||
@ -114,7 +135,9 @@ class AccountGallery extends ImmutablePureComponent {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (hasMore && !(isLoading && medias.size === 0)) {
|
let loadOlder = null;
|
||||||
|
|
||||||
|
if (hasMore && !(isLoading && attachments.size === 0)) {
|
||||||
loadOlder = <LoadMore visible={!isLoading} onClick={this.handleLoadOlder} />;
|
loadOlder = <LoadMore visible={!isLoading} onClick={this.handleLoadOlder} />;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -126,23 +149,17 @@ class AccountGallery extends ImmutablePureComponent {
|
|||||||
<div className='scrollable scrollable--flex' onScroll={this.handleScroll}>
|
<div className='scrollable scrollable--flex' onScroll={this.handleScroll}>
|
||||||
<HeaderContainer accountId={this.props.params.accountId} />
|
<HeaderContainer accountId={this.props.params.accountId} />
|
||||||
|
|
||||||
<div role='feed' className='account-gallery__container'>
|
<div role='feed' className='account-gallery__container' ref={this.handleRef}>
|
||||||
{medias.map((media, index) => media === null ? (
|
{attachments.map((attachment, index) => attachment === null ? (
|
||||||
<LoadMoreMedia
|
<LoadMoreMedia key={'more:' + attachments.getIn(index + 1, 'id')} maxId={index > 0 ? attachments.getIn(index - 1, 'id') : null} onLoadMore={this.handleLoadMore} />
|
||||||
key={'more:' + medias.getIn(index + 1, 'id')}
|
|
||||||
maxId={index > 0 ? medias.getIn(index - 1, 'id') : null}
|
|
||||||
onLoadMore={this.handleLoadMore}
|
|
||||||
/>
|
|
||||||
) : (
|
) : (
|
||||||
<MediaItem
|
<MediaItem key={attachment.get('id')} attachment={attachment} displayWidth={width} onOpenMedia={this.handleOpenMedia} />
|
||||||
key={media.get('id')}
|
|
||||||
media={media}
|
|
||||||
/>
|
|
||||||
))}
|
))}
|
||||||
|
|
||||||
{loadOlder}
|
{loadOlder}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{isLoading && medias.size === 0 && (
|
{isLoading && attachments.size === 0 && (
|
||||||
<div className='scrollable__append'>
|
<div className='scrollable__append'>
|
||||||
<LoadingIndicator />
|
<LoadingIndicator />
|
||||||
</div>
|
</div>
|
||||||
|
@ -11,7 +11,6 @@ import { defineMessages, injectIntl } from 'react-intl';
|
|||||||
import SpoilerButtonContainer from '../containers/spoiler_button_container';
|
import SpoilerButtonContainer from '../containers/spoiler_button_container';
|
||||||
import PrivacyDropdownContainer from '../containers/privacy_dropdown_container';
|
import PrivacyDropdownContainer from '../containers/privacy_dropdown_container';
|
||||||
import FederationDropdownContainer from '../containers/federation_dropdown_container';
|
import FederationDropdownContainer from '../containers/federation_dropdown_container';
|
||||||
import SensitiveButtonContainer from '../containers/sensitive_button_container';
|
|
||||||
import EmojiPickerDropdown from '../containers/emoji_picker_dropdown_container';
|
import EmojiPickerDropdown from '../containers/emoji_picker_dropdown_container';
|
||||||
import PollFormContainer from '../containers/poll_form_container';
|
import PollFormContainer from '../containers/poll_form_container';
|
||||||
import UploadFormContainer from '../containers/upload_form_container';
|
import UploadFormContainer from '../containers/upload_form_container';
|
||||||
@ -41,18 +40,17 @@ class ComposeForm extends ImmutablePureComponent {
|
|||||||
static propTypes = {
|
static propTypes = {
|
||||||
intl: PropTypes.object.isRequired,
|
intl: PropTypes.object.isRequired,
|
||||||
text: PropTypes.string.isRequired,
|
text: PropTypes.string.isRequired,
|
||||||
suggestion_token: PropTypes.string,
|
|
||||||
suggestions: ImmutablePropTypes.list,
|
suggestions: ImmutablePropTypes.list,
|
||||||
spoiler: PropTypes.bool,
|
spoiler: PropTypes.bool,
|
||||||
privacy: PropTypes.string,
|
privacy: PropTypes.string,
|
||||||
federation: PropTypes.bool,
|
federation: PropTypes.bool,
|
||||||
spoiler_text: PropTypes.string,
|
spoilerText: PropTypes.string,
|
||||||
focusDate: PropTypes.instanceOf(Date),
|
focusDate: PropTypes.instanceOf(Date),
|
||||||
caretPosition: PropTypes.number,
|
caretPosition: PropTypes.number,
|
||||||
preselectDate: PropTypes.instanceOf(Date),
|
preselectDate: PropTypes.instanceOf(Date),
|
||||||
is_submitting: PropTypes.bool,
|
isSubmitting: PropTypes.bool,
|
||||||
is_changing_upload: PropTypes.bool,
|
isChangingUpload: PropTypes.bool,
|
||||||
is_uploading: PropTypes.bool,
|
isUploading: PropTypes.bool,
|
||||||
onChange: PropTypes.func.isRequired,
|
onChange: PropTypes.func.isRequired,
|
||||||
onSubmit: PropTypes.func.isRequired,
|
onSubmit: PropTypes.func.isRequired,
|
||||||
onClearSuggestions: PropTypes.func.isRequired,
|
onClearSuggestions: PropTypes.func.isRequired,
|
||||||
@ -87,10 +85,10 @@ class ComposeForm extends ImmutablePureComponent {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Submit disabled:
|
// Submit disabled:
|
||||||
const { is_submitting, is_changing_upload, is_uploading, anyMedia } = this.props;
|
const { isSubmitting, isChangingUpload, isUploading, anyMedia } = this.props;
|
||||||
const fulltext = [this.props.spoiler_text, countableText(this.props.text)].join('');
|
const fulltext = [this.props.spoilerText, countableText(this.props.text)].join('');
|
||||||
|
|
||||||
if (is_submitting || is_uploading || is_changing_upload || length(fulltext) > 500 || (fulltext.length !== 0 && fulltext.trim().length === 0 && !anyMedia)) {
|
if (isSubmitting || isUploading || isChangingUpload || length(fulltext) > 500 || (fulltext.length !== 0 && fulltext.trim().length === 0 && !anyMedia)) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -135,7 +133,7 @@ class ComposeForm extends ImmutablePureComponent {
|
|||||||
|
|
||||||
this.autosuggestTextarea.textarea.setSelectionRange(selectionStart, selectionEnd);
|
this.autosuggestTextarea.textarea.setSelectionRange(selectionStart, selectionEnd);
|
||||||
this.autosuggestTextarea.textarea.focus();
|
this.autosuggestTextarea.textarea.focus();
|
||||||
} else if(prevProps.is_submitting && !this.props.is_submitting) {
|
} else if(prevProps.isSubmitting && !this.props.isSubmitting) {
|
||||||
this.autosuggestTextarea.textarea.focus();
|
this.autosuggestTextarea.textarea.focus();
|
||||||
} else if (this.props.spoiler !== prevProps.spoiler) {
|
} else if (this.props.spoiler !== prevProps.spoiler) {
|
||||||
if (this.props.spoiler) {
|
if (this.props.spoiler) {
|
||||||
@ -164,9 +162,9 @@ class ComposeForm extends ImmutablePureComponent {
|
|||||||
|
|
||||||
render () {
|
render () {
|
||||||
const { intl, onPaste, showSearch, anyMedia } = this.props;
|
const { intl, onPaste, showSearch, anyMedia } = this.props;
|
||||||
const disabled = this.props.is_submitting;
|
const disabled = this.props.isSubmitting;
|
||||||
const text = [this.props.spoiler_text, countableText(this.props.text)].join('');
|
const text = [this.props.spoilerText, countableText(this.props.text)].join('');
|
||||||
const disabledButton = disabled || this.props.is_uploading || this.props.is_changing_upload || length(text) > 500 || (text.length !== 0 && text.trim().length === 0 && !anyMedia);
|
const disabledButton = disabled || this.props.isUploading || this.props.isChangingUpload || length(text) > 500 || (text.length !== 0 && text.trim().length === 0 && !anyMedia);
|
||||||
let publishText = '';
|
let publishText = '';
|
||||||
|
|
||||||
if (this.props.privacy === 'private' || this.props.privacy === 'direct') {
|
if (this.props.privacy === 'private' || this.props.privacy === 'direct') {
|
||||||
@ -184,7 +182,7 @@ class ComposeForm extends ImmutablePureComponent {
|
|||||||
<div className={`spoiler-input ${this.props.spoiler ? 'spoiler-input--visible' : ''}`}>
|
<div className={`spoiler-input ${this.props.spoiler ? 'spoiler-input--visible' : ''}`}>
|
||||||
<label>
|
<label>
|
||||||
<span style={{ display: 'none' }}>{intl.formatMessage(messages.spoiler_placeholder)}</span>
|
<span style={{ display: 'none' }}>{intl.formatMessage(messages.spoiler_placeholder)}</span>
|
||||||
<input placeholder={intl.formatMessage(messages.spoiler_placeholder)} value={this.props.spoiler_text} onChange={this.handleChangeSpoilerText} onKeyDown={this.handleKeyDown} tabIndex={this.props.spoiler ? 0 : -1} type='text' className='spoiler-input__input' id='cw-spoiler-input' ref={this.setSpoilerText} />
|
<input placeholder={intl.formatMessage(messages.spoiler_placeholder)} value={this.props.spoilerText} onChange={this.handleChangeSpoilerText} onKeyDown={this.handleKeyDown} tabIndex={this.props.spoiler ? 0 : -1} type='text' className='spoiler-input__input' id='cw-spoiler-input' ref={this.setSpoilerText} />
|
||||||
</label>
|
</label>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@ -217,7 +215,6 @@ class ComposeForm extends ImmutablePureComponent {
|
|||||||
<UploadButtonContainer />
|
<UploadButtonContainer />
|
||||||
<PollButtonContainer />
|
<PollButtonContainer />
|
||||||
<PrivacyDropdownContainer />
|
<PrivacyDropdownContainer />
|
||||||
<SensitiveButtonContainer />
|
|
||||||
<SpoilerButtonContainer />
|
<SpoilerButtonContainer />
|
||||||
<FederationDropdownContainer />
|
<FederationDropdownContainer />
|
||||||
</div>
|
</div>
|
||||||
|
@ -26,6 +26,7 @@ class Option extends React.PureComponent {
|
|||||||
isPollMultiple: PropTypes.bool,
|
isPollMultiple: PropTypes.bool,
|
||||||
onChange: PropTypes.func.isRequired,
|
onChange: PropTypes.func.isRequired,
|
||||||
onRemove: PropTypes.func.isRequired,
|
onRemove: PropTypes.func.isRequired,
|
||||||
|
onToggleMultiple: PropTypes.func.isRequired,
|
||||||
intl: PropTypes.object.isRequired,
|
intl: PropTypes.object.isRequired,
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -37,13 +38,24 @@ class Option extends React.PureComponent {
|
|||||||
this.props.onRemove(this.props.index);
|
this.props.onRemove(this.props.index);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
handleToggleMultiple = e => {
|
||||||
|
this.props.onToggleMultiple();
|
||||||
|
e.preventDefault();
|
||||||
|
e.stopPropagation();
|
||||||
|
};
|
||||||
|
|
||||||
render () {
|
render () {
|
||||||
const { isPollMultiple, title, index, intl } = this.props;
|
const { isPollMultiple, title, index, intl } = this.props;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<li>
|
<li>
|
||||||
<label className='poll__text editable'>
|
<label className='poll__text editable'>
|
||||||
<span className={classNames('poll__input', { checkbox: isPollMultiple })} />
|
<span
|
||||||
|
className={classNames('poll__input', { checkbox: isPollMultiple })}
|
||||||
|
onClick={this.handleToggleMultiple}
|
||||||
|
role='button'
|
||||||
|
tabIndex='0'
|
||||||
|
/>
|
||||||
|
|
||||||
<input
|
<input
|
||||||
type='text'
|
type='text'
|
||||||
@ -86,6 +98,10 @@ class PollForm extends ImmutablePureComponent {
|
|||||||
this.props.onChangeSettings(e.target.value, this.props.isMultiple);
|
this.props.onChangeSettings(e.target.value, this.props.isMultiple);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
handleToggleMultiple = () => {
|
||||||
|
this.props.onChangeSettings(this.props.expiresIn, !this.props.isMultiple);
|
||||||
|
};
|
||||||
|
|
||||||
render () {
|
render () {
|
||||||
const { options, expiresIn, isMultiple, onChangeOption, onRemoveOption, intl } = this.props;
|
const { options, expiresIn, isMultiple, onChangeOption, onRemoveOption, intl } = this.props;
|
||||||
|
|
||||||
@ -96,7 +112,7 @@ class PollForm extends ImmutablePureComponent {
|
|||||||
return (
|
return (
|
||||||
<div className='compose-form__poll-wrapper'>
|
<div className='compose-form__poll-wrapper'>
|
||||||
<ul>
|
<ul>
|
||||||
{options.map((title, i) => <Option title={title} key={i} index={i} onChange={onChangeOption} onRemove={onRemoveOption} isPollMultiple={isMultiple} />)}
|
{options.map((title, i) => <Option title={title} key={i} index={i} onChange={onChangeOption} onRemove={onRemoveOption} isPollMultiple={isMultiple} onToggleMultiple={this.handleToggleMultiple} />)}
|
||||||
</ul>
|
</ul>
|
||||||
|
|
||||||
<div className='poll__footer'>
|
<div className='poll__footer'>
|
||||||
|
@ -73,7 +73,7 @@ class Search extends React.PureComponent {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
handleKeyDown = (e) => {
|
handleKeyUp = (e) => {
|
||||||
if (e.key === 'Enter') {
|
if (e.key === 'Enter') {
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
this.props.onSubmit();
|
this.props.onSubmit();
|
||||||
@ -82,10 +82,6 @@ class Search extends React.PureComponent {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
noop () {
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
handleFocus = () => {
|
handleFocus = () => {
|
||||||
this.setState({ expanded: true });
|
this.setState({ expanded: true });
|
||||||
this.props.onShow();
|
this.props.onShow();
|
||||||
@ -110,7 +106,7 @@ class Search extends React.PureComponent {
|
|||||||
placeholder={intl.formatMessage(messages.placeholder)}
|
placeholder={intl.formatMessage(messages.placeholder)}
|
||||||
value={value}
|
value={value}
|
||||||
onChange={this.handleChange}
|
onChange={this.handleChange}
|
||||||
onKeyUp={this.handleKeyDown}
|
onKeyUp={this.handleKeyUp}
|
||||||
onFocus={this.handleFocus}
|
onFocus={this.handleFocus}
|
||||||
onBlur={this.handleBlur}
|
onBlur={this.handleBlur}
|
||||||
/>
|
/>
|
||||||
|
@ -3,6 +3,7 @@ import ImmutablePropTypes from 'react-immutable-proptypes';
|
|||||||
import UploadProgressContainer from '../containers/upload_progress_container';
|
import UploadProgressContainer from '../containers/upload_progress_container';
|
||||||
import ImmutablePureComponent from 'react-immutable-pure-component';
|
import ImmutablePureComponent from 'react-immutable-pure-component';
|
||||||
import UploadContainer from '../containers/upload_container';
|
import UploadContainer from '../containers/upload_container';
|
||||||
|
import SensitiveButtonContainer from '../containers/sensitive_button_container';
|
||||||
|
|
||||||
export default class UploadForm extends ImmutablePureComponent {
|
export default class UploadForm extends ImmutablePureComponent {
|
||||||
|
|
||||||
@ -22,6 +23,8 @@ export default class UploadForm extends ImmutablePureComponent {
|
|||||||
<UploadContainer id={id} key={id} />
|
<UploadContainer id={id} key={id} />
|
||||||
))}
|
))}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
{!mediaIds.isEmpty() && <SensitiveButtonContainer />}
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@ -1,6 +1,5 @@
|
|||||||
import { connect } from 'react-redux';
|
import { connect } from 'react-redux';
|
||||||
import ComposeForm from '../components/compose_form';
|
import ComposeForm from '../components/compose_form';
|
||||||
import { uploadCompose } from '../../../actions/compose';
|
|
||||||
import {
|
import {
|
||||||
changeCompose,
|
changeCompose,
|
||||||
submitCompose,
|
submitCompose,
|
||||||
@ -9,22 +8,22 @@ import {
|
|||||||
selectComposeSuggestion,
|
selectComposeSuggestion,
|
||||||
changeComposeSpoilerText,
|
changeComposeSpoilerText,
|
||||||
insertEmojiCompose,
|
insertEmojiCompose,
|
||||||
|
uploadCompose,
|
||||||
} from '../../../actions/compose';
|
} from '../../../actions/compose';
|
||||||
|
|
||||||
const mapStateToProps = state => ({
|
const mapStateToProps = state => ({
|
||||||
text: state.getIn(['compose', 'text']),
|
text: state.getIn(['compose', 'text']),
|
||||||
suggestion_token: state.getIn(['compose', 'suggestion_token']),
|
|
||||||
suggestions: state.getIn(['compose', 'suggestions']),
|
suggestions: state.getIn(['compose', 'suggestions']),
|
||||||
spoiler: state.getIn(['compose', 'spoiler']),
|
spoiler: state.getIn(['compose', 'spoiler']),
|
||||||
spoiler_text: state.getIn(['compose', 'spoiler_text']),
|
spoilerText: state.getIn(['compose', 'spoiler_text']),
|
||||||
privacy: state.getIn(['compose', 'privacy']),
|
privacy: state.getIn(['compose', 'privacy']),
|
||||||
federation: state.getIn(['compose', 'federation']),
|
federation: state.getIn(['compose', 'federation']),
|
||||||
focusDate: state.getIn(['compose', 'focusDate']),
|
focusDate: state.getIn(['compose', 'focusDate']),
|
||||||
caretPosition: state.getIn(['compose', 'caretPosition']),
|
caretPosition: state.getIn(['compose', 'caretPosition']),
|
||||||
preselectDate: state.getIn(['compose', 'preselectDate']),
|
preselectDate: state.getIn(['compose', 'preselectDate']),
|
||||||
is_submitting: state.getIn(['compose', 'is_submitting']),
|
is_submitting: state.getIn(['compose', 'is_submitting']),
|
||||||
is_changing_upload: state.getIn(['compose', 'is_changing_upload']),
|
isChangingUpload: state.getIn(['compose', 'is_changing_upload']),
|
||||||
is_uploading: state.getIn(['compose', 'is_uploading']),
|
isUploading: state.getIn(['compose', 'is_uploading']),
|
||||||
showSearch: state.getIn(['search', 'submitted']) && !state.getIn(['search', 'hidden']),
|
showSearch: state.getIn(['search', 'submitted']) && !state.getIn(['search', 'hidden']),
|
||||||
anyMedia: state.getIn(['compose', 'media_attachments']).size > 0,
|
anyMedia: state.getIn(['compose', 'media_attachments']).size > 0,
|
||||||
});
|
});
|
||||||
@ -47,8 +46,8 @@ const mapDispatchToProps = (dispatch) => ({
|
|||||||
dispatch(fetchComposeSuggestions(token));
|
dispatch(fetchComposeSuggestions(token));
|
||||||
},
|
},
|
||||||
|
|
||||||
onSuggestionSelected (position, token, accountId) {
|
onSuggestionSelected (position, token, suggestion) {
|
||||||
dispatch(selectComposeSuggestion(position, token, accountId));
|
dispatch(selectComposeSuggestion(position, token, suggestion));
|
||||||
},
|
},
|
||||||
|
|
||||||
onChangeSpoilerText (checked) {
|
onChangeSpoilerText (checked) {
|
||||||
|
@ -2,11 +2,9 @@ import React from 'react';
|
|||||||
import { connect } from 'react-redux';
|
import { connect } from 'react-redux';
|
||||||
import PropTypes from 'prop-types';
|
import PropTypes from 'prop-types';
|
||||||
import classNames from 'classnames';
|
import classNames from 'classnames';
|
||||||
import IconButton from '../../../components/icon_button';
|
import { changeComposeSensitivity } from 'mastodon/actions/compose';
|
||||||
import { changeComposeSensitivity } from '../../../actions/compose';
|
import { injectIntl, defineMessages, FormattedMessage } from 'react-intl';
|
||||||
import Motion from '../../ui/util/optional_motion';
|
import Icon from 'mastodon/components/icon';
|
||||||
import spring from 'react-motion/lib/spring';
|
|
||||||
import { injectIntl, defineMessages } from 'react-intl';
|
|
||||||
|
|
||||||
const messages = defineMessages({
|
const messages = defineMessages({
|
||||||
marked: { id: 'compose_form.sensitive.marked', defaultMessage: 'Media is marked as sensitive' },
|
marked: { id: 'compose_form.sensitive.marked', defaultMessage: 'Media is marked as sensitive' },
|
||||||
@ -14,7 +12,6 @@ const messages = defineMessages({
|
|||||||
});
|
});
|
||||||
|
|
||||||
const mapStateToProps = state => ({
|
const mapStateToProps = state => ({
|
||||||
visible: state.getIn(['compose', 'media_attachments']).size > 0,
|
|
||||||
active: state.getIn(['compose', 'sensitive']),
|
active: state.getIn(['compose', 'sensitive']),
|
||||||
disabled: state.getIn(['compose', 'spoiler']),
|
disabled: state.getIn(['compose', 'spoiler']),
|
||||||
});
|
});
|
||||||
@ -30,7 +27,6 @@ const mapDispatchToProps = dispatch => ({
|
|||||||
class SensitiveButton extends React.PureComponent {
|
class SensitiveButton extends React.PureComponent {
|
||||||
|
|
||||||
static propTypes = {
|
static propTypes = {
|
||||||
visible: PropTypes.bool,
|
|
||||||
active: PropTypes.bool,
|
active: PropTypes.bool,
|
||||||
disabled: PropTypes.bool,
|
disabled: PropTypes.bool,
|
||||||
onClick: PropTypes.func.isRequired,
|
onClick: PropTypes.func.isRequired,
|
||||||
@ -38,32 +34,14 @@ class SensitiveButton extends React.PureComponent {
|
|||||||
};
|
};
|
||||||
|
|
||||||
render () {
|
render () {
|
||||||
const { visible, active, disabled, onClick, intl } = this.props;
|
const { active, disabled, onClick, intl } = this.props;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Motion defaultStyle={{ scale: 0.87 }} style={{ scale: spring(visible ? 1 : 0.87, { stiffness: 200, damping: 3 }) }}>
|
<div className='compose-form__sensitive-button'>
|
||||||
{({ scale }) => {
|
<button className={classNames('icon-button', { active })} onClick={onClick} disabled={disabled} title={intl.formatMessage(active ? messages.marked : messages.unmarked)}>
|
||||||
const icon = active ? 'eye-slash' : 'eye';
|
<Icon id='eye-slash' /> <FormattedMessage id='compose_form.sensitive.hide' defaultMessage='Mark media as sensitive' />
|
||||||
const className = classNames('compose-form__sensitive-button', {
|
</button>
|
||||||
'compose-form__sensitive-button--visible': visible,
|
</div>
|
||||||
});
|
|
||||||
return (
|
|
||||||
<div className={className} style={{ transform: `scale(${scale})` }}>
|
|
||||||
<IconButton
|
|
||||||
className='compose-form__sensitive-button__icon'
|
|
||||||
title={intl.formatMessage(active ? messages.marked : messages.unmarked)}
|
|
||||||
icon={icon}
|
|
||||||
onClick={onClick}
|
|
||||||
size={18}
|
|
||||||
active={active}
|
|
||||||
disabled={disabled}
|
|
||||||
style={{ lineHeight: null, height: null }}
|
|
||||||
inverted
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
}}
|
|
||||||
</Motion>
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -20,18 +20,24 @@ export default class ConversationsList extends ImmutablePureComponent {
|
|||||||
|
|
||||||
handleMoveUp = id => {
|
handleMoveUp = id => {
|
||||||
const elementIndex = this.getCurrentIndex(id) - 1;
|
const elementIndex = this.getCurrentIndex(id) - 1;
|
||||||
this._selectChild(elementIndex);
|
this._selectChild(elementIndex, true);
|
||||||
}
|
}
|
||||||
|
|
||||||
handleMoveDown = id => {
|
handleMoveDown = id => {
|
||||||
const elementIndex = this.getCurrentIndex(id) + 1;
|
const elementIndex = this.getCurrentIndex(id) + 1;
|
||||||
this._selectChild(elementIndex);
|
this._selectChild(elementIndex, false);
|
||||||
}
|
}
|
||||||
|
|
||||||
_selectChild (index) {
|
_selectChild (index, align_top) {
|
||||||
const element = this.node.node.querySelector(`article:nth-of-type(${index + 1}) .focusable`);
|
const container = this.node.node;
|
||||||
|
const element = container.querySelector(`article:nth-of-type(${index + 1}) .focusable`);
|
||||||
|
|
||||||
if (element) {
|
if (element) {
|
||||||
|
if (align_top && container.scrollTop > element.offsetTop) {
|
||||||
|
element.scrollIntoView(true);
|
||||||
|
} else if (!align_top && container.scrollTop + container.clientHeight < element.offsetTop + element.offsetHeight) {
|
||||||
|
element.scrollIntoView(false);
|
||||||
|
}
|
||||||
element.focus();
|
element.focus();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -7,7 +7,7 @@ import { connect } from 'react-redux';
|
|||||||
import PropTypes from 'prop-types';
|
import PropTypes from 'prop-types';
|
||||||
import ImmutablePropTypes from 'react-immutable-proptypes';
|
import ImmutablePropTypes from 'react-immutable-proptypes';
|
||||||
import ImmutablePureComponent from 'react-immutable-pure-component';
|
import ImmutablePureComponent from 'react-immutable-pure-component';
|
||||||
import { me, invitesEnabled, version, profile_directory } from '../../initial_state';
|
import { me, invitesEnabled, version, profile_directory, repository, source_url } from '../../initial_state';
|
||||||
import { fetchFollowRequests } from '../../actions/accounts';
|
import { fetchFollowRequests } from '../../actions/accounts';
|
||||||
import { List as ImmutableList } from 'immutable';
|
import { List as ImmutableList } from 'immutable';
|
||||||
import { Link } from 'react-router-dom';
|
import { Link } from 'react-router-dom';
|
||||||
@ -172,7 +172,7 @@ class GettingStarted extends ImmutablePureComponent {
|
|||||||
<FormattedMessage
|
<FormattedMessage
|
||||||
id='getting_started.open_source_notice'
|
id='getting_started.open_source_notice'
|
||||||
defaultMessage='Mastodon is open source software. You can contribute or report issues on GitHub at {github}.'
|
defaultMessage='Mastodon is open source software. You can contribute or report issues on GitHub at {github}.'
|
||||||
values={{ github: <span><a href='https://github.com/tootsuite/mastodon' rel='noopener' target='_blank'>tootsuite/mastodon</a> (v{version})</span> }}
|
values={{ github: <span><a href={source_url} rel='noopener' target='_blank'>{repository}</a> (v{version})</span> }}
|
||||||
/>
|
/>
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
|
@ -113,18 +113,24 @@ class Notifications extends React.PureComponent {
|
|||||||
|
|
||||||
handleMoveUp = id => {
|
handleMoveUp = id => {
|
||||||
const elementIndex = this.props.notifications.findIndex(item => item !== null && item.get('id') === id) - 1;
|
const elementIndex = this.props.notifications.findIndex(item => item !== null && item.get('id') === id) - 1;
|
||||||
this._selectChild(elementIndex);
|
this._selectChild(elementIndex, true);
|
||||||
}
|
}
|
||||||
|
|
||||||
handleMoveDown = id => {
|
handleMoveDown = id => {
|
||||||
const elementIndex = this.props.notifications.findIndex(item => item !== null && item.get('id') === id) + 1;
|
const elementIndex = this.props.notifications.findIndex(item => item !== null && item.get('id') === id) + 1;
|
||||||
this._selectChild(elementIndex);
|
this._selectChild(elementIndex, false);
|
||||||
}
|
}
|
||||||
|
|
||||||
_selectChild (index) {
|
_selectChild (index, align_top) {
|
||||||
const element = this.column.node.querySelector(`article:nth-of-type(${index + 1}) .focusable`);
|
const container = this.column.node;
|
||||||
|
const element = container.querySelector(`article:nth-of-type(${index + 1}) .focusable`);
|
||||||
|
|
||||||
if (element) {
|
if (element) {
|
||||||
|
if (align_top && container.scrollTop > element.offsetTop) {
|
||||||
|
element.scrollIntoView(true);
|
||||||
|
} else if (!align_top && container.scrollTop + container.clientHeight < element.offsetTop + element.offsetHeight) {
|
||||||
|
element.scrollIntoView(false);
|
||||||
|
}
|
||||||
element.focus();
|
element.focus();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -35,6 +35,7 @@ export default class StatusCheckBox extends React.PureComponent {
|
|||||||
{Component => (
|
{Component => (
|
||||||
<Component
|
<Component
|
||||||
preview={video.get('preview_url')}
|
preview={video.get('preview_url')}
|
||||||
|
blurhash={video.get('blurhash')}
|
||||||
src={video.get('url')}
|
src={video.get('url')}
|
||||||
alt={video.get('description')}
|
alt={video.get('description')}
|
||||||
width={239}
|
width={239}
|
||||||
|
@ -5,7 +5,6 @@ import Avatar from '../../../components/avatar';
|
|||||||
import DisplayName from '../../../components/display_name';
|
import DisplayName from '../../../components/display_name';
|
||||||
import StatusContent from '../../../components/status_content';
|
import StatusContent from '../../../components/status_content';
|
||||||
import MediaGallery from '../../../components/media_gallery';
|
import MediaGallery from '../../../components/media_gallery';
|
||||||
import AttachmentList from '../../../components/attachment_list';
|
|
||||||
import { Link } from 'react-router-dom';
|
import { Link } from 'react-router-dom';
|
||||||
import { defineMessages, injectIntl, FormattedDate, FormattedNumber } from 'react-intl';
|
import { defineMessages, injectIntl, FormattedDate, FormattedNumber } from 'react-intl';
|
||||||
import Card from './card';
|
import Card from './card';
|
||||||
@ -116,14 +115,13 @@ export default class DetailedStatus extends ImmutablePureComponent {
|
|||||||
if (status.get('poll')) {
|
if (status.get('poll')) {
|
||||||
media = <PollContainer pollId={status.get('poll')} />;
|
media = <PollContainer pollId={status.get('poll')} />;
|
||||||
} else if (status.get('media_attachments').size > 0) {
|
} else if (status.get('media_attachments').size > 0) {
|
||||||
if (status.get('media_attachments').some(item => item.get('type') === 'unknown')) {
|
if (status.getIn(['media_attachments', 0, 'type']) === 'video') {
|
||||||
media = <AttachmentList media={status.get('media_attachments')} />;
|
|
||||||
} else if (status.getIn(['media_attachments', 0, 'type']) === 'video') {
|
|
||||||
const video = status.getIn(['media_attachments', 0]);
|
const video = status.getIn(['media_attachments', 0]);
|
||||||
|
|
||||||
media = (
|
media = (
|
||||||
<Video
|
<Video
|
||||||
preview={video.get('preview_url')}
|
preview={video.get('preview_url')}
|
||||||
|
blurhash={video.get('blurhash')}
|
||||||
src={video.get('url')}
|
src={video.get('url')}
|
||||||
alt={video.get('description')}
|
alt={video.get('description')}
|
||||||
width={300}
|
width={300}
|
||||||
|
@ -316,15 +316,15 @@ class Status extends ImmutablePureComponent {
|
|||||||
const { status, ancestorsIds, descendantsIds } = this.props;
|
const { status, ancestorsIds, descendantsIds } = this.props;
|
||||||
|
|
||||||
if (id === status.get('id')) {
|
if (id === status.get('id')) {
|
||||||
this._selectChild(ancestorsIds.size - 1);
|
this._selectChild(ancestorsIds.size - 1, true);
|
||||||
} else {
|
} else {
|
||||||
let index = ancestorsIds.indexOf(id);
|
let index = ancestorsIds.indexOf(id);
|
||||||
|
|
||||||
if (index === -1) {
|
if (index === -1) {
|
||||||
index = descendantsIds.indexOf(id);
|
index = descendantsIds.indexOf(id);
|
||||||
this._selectChild(ancestorsIds.size + index);
|
this._selectChild(ancestorsIds.size + index, true);
|
||||||
} else {
|
} else {
|
||||||
this._selectChild(index - 1);
|
this._selectChild(index - 1, true);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -333,23 +333,29 @@ class Status extends ImmutablePureComponent {
|
|||||||
const { status, ancestorsIds, descendantsIds } = this.props;
|
const { status, ancestorsIds, descendantsIds } = this.props;
|
||||||
|
|
||||||
if (id === status.get('id')) {
|
if (id === status.get('id')) {
|
||||||
this._selectChild(ancestorsIds.size + 1);
|
this._selectChild(ancestorsIds.size + 1, false);
|
||||||
} else {
|
} else {
|
||||||
let index = ancestorsIds.indexOf(id);
|
let index = ancestorsIds.indexOf(id);
|
||||||
|
|
||||||
if (index === -1) {
|
if (index === -1) {
|
||||||
index = descendantsIds.indexOf(id);
|
index = descendantsIds.indexOf(id);
|
||||||
this._selectChild(ancestorsIds.size + index + 2);
|
this._selectChild(ancestorsIds.size + index + 2, false);
|
||||||
} else {
|
} else {
|
||||||
this._selectChild(index + 1);
|
this._selectChild(index + 1, false);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
_selectChild (index) {
|
_selectChild (index, align_top) {
|
||||||
const element = this.node.querySelectorAll('.focusable')[index];
|
const container = this.node;
|
||||||
|
const element = container.querySelectorAll('.focusable')[index];
|
||||||
|
|
||||||
if (element) {
|
if (element) {
|
||||||
|
if (align_top && container.scrollTop > element.offsetTop) {
|
||||||
|
element.scrollIntoView(true);
|
||||||
|
} else if (!align_top && container.scrollTop + container.clientHeight < element.offsetTop + element.offsetHeight) {
|
||||||
|
element.scrollIntoView(false);
|
||||||
|
}
|
||||||
element.focus();
|
element.focus();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -64,7 +64,7 @@ export default class ActionsModal extends ImmutablePureComponent {
|
|||||||
<div className='modal-root__modal actions-modal'>
|
<div className='modal-root__modal actions-modal'>
|
||||||
{status}
|
{status}
|
||||||
|
|
||||||
<ul>
|
<ul className={classNames({ 'with-status': !!status })}>
|
||||||
{this.props.actions.map(this.renderAction)}
|
{this.props.actions.map(this.renderAction)}
|
||||||
</ul>
|
</ul>
|
||||||
</div>
|
</div>
|
||||||
|
@ -2,11 +2,11 @@ import React from 'react';
|
|||||||
import ReactSwipeableViews from 'react-swipeable-views';
|
import ReactSwipeableViews from 'react-swipeable-views';
|
||||||
import ImmutablePropTypes from 'react-immutable-proptypes';
|
import ImmutablePropTypes from 'react-immutable-proptypes';
|
||||||
import PropTypes from 'prop-types';
|
import PropTypes from 'prop-types';
|
||||||
import Video from '../../video';
|
import Video from 'mastodon/features/video';
|
||||||
import ExtendedVideoPlayer from '../../../components/extended_video_player';
|
import ExtendedVideoPlayer from 'mastodon/components/extended_video_player';
|
||||||
import classNames from 'classnames';
|
import classNames from 'classnames';
|
||||||
import { defineMessages, injectIntl } from 'react-intl';
|
import { defineMessages, injectIntl, FormattedMessage } from 'react-intl';
|
||||||
import IconButton from '../../../components/icon_button';
|
import IconButton from 'mastodon/components/icon_button';
|
||||||
import ImmutablePureComponent from 'react-immutable-pure-component';
|
import ImmutablePureComponent from 'react-immutable-pure-component';
|
||||||
import ImageLoader from './image_loader';
|
import ImageLoader from './image_loader';
|
||||||
import Icon from 'mastodon/components/icon';
|
import Icon from 'mastodon/components/icon';
|
||||||
@ -24,6 +24,7 @@ class MediaModal extends ImmutablePureComponent {
|
|||||||
|
|
||||||
static propTypes = {
|
static propTypes = {
|
||||||
media: ImmutablePropTypes.list.isRequired,
|
media: ImmutablePropTypes.list.isRequired,
|
||||||
|
status: ImmutablePropTypes.map,
|
||||||
index: PropTypes.number.isRequired,
|
index: PropTypes.number.isRequired,
|
||||||
onClose: PropTypes.func.isRequired,
|
onClose: PropTypes.func.isRequired,
|
||||||
intl: PropTypes.object.isRequired,
|
intl: PropTypes.object.isRequired,
|
||||||
@ -72,9 +73,12 @@ class MediaModal extends ImmutablePureComponent {
|
|||||||
|
|
||||||
componentDidMount () {
|
componentDidMount () {
|
||||||
window.addEventListener('keydown', this.handleKeyDown, false);
|
window.addEventListener('keydown', this.handleKeyDown, false);
|
||||||
|
|
||||||
if (this.context.router) {
|
if (this.context.router) {
|
||||||
const history = this.context.router.history;
|
const history = this.context.router.history;
|
||||||
|
|
||||||
history.push(history.location.pathname, previewState);
|
history.push(history.location.pathname, previewState);
|
||||||
|
|
||||||
this.unlistenHistory = history.listen(() => {
|
this.unlistenHistory = history.listen(() => {
|
||||||
this.props.onClose();
|
this.props.onClose();
|
||||||
});
|
});
|
||||||
@ -83,6 +87,7 @@ class MediaModal extends ImmutablePureComponent {
|
|||||||
|
|
||||||
componentWillUnmount () {
|
componentWillUnmount () {
|
||||||
window.removeEventListener('keydown', this.handleKeyDown);
|
window.removeEventListener('keydown', this.handleKeyDown);
|
||||||
|
|
||||||
if (this.context.router) {
|
if (this.context.router) {
|
||||||
this.unlistenHistory();
|
this.unlistenHistory();
|
||||||
|
|
||||||
@ -102,8 +107,15 @@ class MediaModal extends ImmutablePureComponent {
|
|||||||
}));
|
}));
|
||||||
};
|
};
|
||||||
|
|
||||||
|
handleStatusClick = e => {
|
||||||
|
if (e.button === 0 && !(e.ctrlKey || e.metaKey)) {
|
||||||
|
e.preventDefault();
|
||||||
|
this.context.router.history.push(`/statuses/${this.props.status.get('id')}`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
render () {
|
render () {
|
||||||
const { media, intl, onClose } = this.props;
|
const { media, status, intl, onClose } = this.props;
|
||||||
const { navigationHidden } = this.state;
|
const { navigationHidden } = this.state;
|
||||||
|
|
||||||
const index = this.getIndex();
|
const index = this.getIndex();
|
||||||
@ -144,6 +156,7 @@ class MediaModal extends ImmutablePureComponent {
|
|||||||
return (
|
return (
|
||||||
<Video
|
<Video
|
||||||
preview={image.get('preview_url')}
|
preview={image.get('preview_url')}
|
||||||
|
blurhash={image.get('blurhash')}
|
||||||
src={image.get('url')}
|
src={image.get('url')}
|
||||||
width={image.get('width')}
|
width={image.get('width')}
|
||||||
height={image.get('height')}
|
height={image.get('height')}
|
||||||
@ -206,10 +219,19 @@ class MediaModal extends ImmutablePureComponent {
|
|||||||
{content}
|
{content}
|
||||||
</ReactSwipeableViews>
|
</ReactSwipeableViews>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className={navigationClassName}>
|
<div className={navigationClassName}>
|
||||||
<IconButton className='media-modal__close' title={intl.formatMessage(messages.close)} icon='times' onClick={onClose} size={40} />
|
<IconButton className='media-modal__close' title={intl.formatMessage(messages.close)} icon='times' onClick={onClose} size={40} />
|
||||||
|
|
||||||
{leftNav}
|
{leftNav}
|
||||||
{rightNav}
|
{rightNav}
|
||||||
|
|
||||||
|
{status && (
|
||||||
|
<div className={classNames('media-modal__meta', { 'media-modal__meta--shifted': media.size > 1 })}>
|
||||||
|
<a href={status.get('url')} onClick={this.handleStatusClick}><FormattedMessage id='lightbox.view_context' defaultMessage='View context' /></a>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
|
||||||
<ul className='media-modal__pagination'>
|
<ul className='media-modal__pagination'>
|
||||||
{pagination}
|
{pagination}
|
||||||
</ul>
|
</ul>
|
||||||
|
@ -1,28 +1,69 @@
|
|||||||
import React from 'react';
|
import React from 'react';
|
||||||
import ImmutablePropTypes from 'react-immutable-proptypes';
|
import ImmutablePropTypes from 'react-immutable-proptypes';
|
||||||
import PropTypes from 'prop-types';
|
import PropTypes from 'prop-types';
|
||||||
import Video from '../../video';
|
import Video from 'mastodon/features/video';
|
||||||
import ImmutablePureComponent from 'react-immutable-pure-component';
|
import ImmutablePureComponent from 'react-immutable-pure-component';
|
||||||
|
import { FormattedMessage } from 'react-intl';
|
||||||
|
|
||||||
|
export const previewState = 'previewVideoModal';
|
||||||
|
|
||||||
export default class VideoModal extends ImmutablePureComponent {
|
export default class VideoModal extends ImmutablePureComponent {
|
||||||
|
|
||||||
static propTypes = {
|
static propTypes = {
|
||||||
media: ImmutablePropTypes.map.isRequired,
|
media: ImmutablePropTypes.map.isRequired,
|
||||||
|
status: ImmutablePropTypes.map,
|
||||||
time: PropTypes.number,
|
time: PropTypes.number,
|
||||||
onClose: PropTypes.func.isRequired,
|
onClose: PropTypes.func.isRequired,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
static contextTypes = {
|
||||||
|
router: PropTypes.object,
|
||||||
|
};
|
||||||
|
|
||||||
|
componentDidMount () {
|
||||||
|
if (this.context.router) {
|
||||||
|
const history = this.context.router.history;
|
||||||
|
|
||||||
|
history.push(history.location.pathname, previewState);
|
||||||
|
|
||||||
|
this.unlistenHistory = history.listen(() => {
|
||||||
|
this.props.onClose();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
componentWillUnmount () {
|
||||||
|
if (this.context.router) {
|
||||||
|
this.unlistenHistory();
|
||||||
|
|
||||||
|
if (this.context.router.history.location.state === previewState) {
|
||||||
|
this.context.router.history.goBack();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
handleStatusClick = e => {
|
||||||
|
if (e.button === 0 && !(e.ctrlKey || e.metaKey)) {
|
||||||
|
e.preventDefault();
|
||||||
|
this.context.router.history.push(`/statuses/${this.props.status.get('id')}`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
render () {
|
render () {
|
||||||
const { media, time, onClose } = this.props;
|
const { media, status, time, onClose } = this.props;
|
||||||
|
|
||||||
|
const link = status && <a href={status.get('url')} onClick={this.handleStatusClick}><FormattedMessage id='lightbox.view_context' defaultMessage='View context' /></a>;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className='modal-root__modal video-modal'>
|
<div className='modal-root__modal video-modal'>
|
||||||
<div>
|
<div>
|
||||||
<Video
|
<Video
|
||||||
preview={media.get('preview_url')}
|
preview={media.get('preview_url')}
|
||||||
|
blurhash={media.get('blurhash')}
|
||||||
src={media.get('url')}
|
src={media.get('url')}
|
||||||
startTime={time}
|
startTime={time}
|
||||||
onCloseVideo={onClose}
|
onCloseVideo={onClose}
|
||||||
|
link={link}
|
||||||
detailed
|
detailed
|
||||||
alt={media.get('description')}
|
alt={media.get('description')}
|
||||||
/>
|
/>
|
||||||
|
@ -47,7 +47,8 @@ import {
|
|||||||
Lists,
|
Lists,
|
||||||
} from './util/async-components';
|
} from './util/async-components';
|
||||||
import { me } from '../../initial_state';
|
import { me } from '../../initial_state';
|
||||||
import { previewState } from './components/media_modal';
|
import { previewState as previewMediaState } from './components/media_modal';
|
||||||
|
import { previewState as previewVideoState } from './components/video_modal';
|
||||||
|
|
||||||
// Dummy import, to make sure that <Status /> ends up in the application bundle.
|
// Dummy import, to make sure that <Status /> ends up in the application bundle.
|
||||||
// Without this it ends up in ~8 very commonly used bundles.
|
// Without this it ends up in ~8 very commonly used bundles.
|
||||||
@ -121,7 +122,7 @@ class SwitchingColumnsArea extends React.PureComponent {
|
|||||||
}
|
}
|
||||||
|
|
||||||
shouldUpdateScroll (_, { location }) {
|
shouldUpdateScroll (_, { location }) {
|
||||||
return location.state !== previewState;
|
return location.state !== previewMediaState && location.state !== previewVideoState;
|
||||||
}
|
}
|
||||||
|
|
||||||
handleResize = debounce(() => {
|
handleResize = debounce(() => {
|
||||||
@ -367,11 +368,16 @@ class UI extends React.PureComponent {
|
|||||||
handleHotkeyFocusColumn = e => {
|
handleHotkeyFocusColumn = e => {
|
||||||
const index = (e.key * 1) + 1; // First child is drawer, skip that
|
const index = (e.key * 1) + 1; // First child is drawer, skip that
|
||||||
const column = this.node.querySelector(`.column:nth-child(${index})`);
|
const column = this.node.querySelector(`.column:nth-child(${index})`);
|
||||||
|
if (!column) return;
|
||||||
|
const container = column.querySelector('.scrollable');
|
||||||
|
|
||||||
if (column) {
|
if (container) {
|
||||||
const status = column.querySelector('.focusable');
|
const status = container.querySelector('.focusable');
|
||||||
|
|
||||||
if (status) {
|
if (status) {
|
||||||
|
if (container.scrollTop > status.offsetTop) {
|
||||||
|
status.scrollIntoView(true);
|
||||||
|
}
|
||||||
status.focus();
|
status.focus();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -7,6 +7,7 @@ import classNames from 'classnames';
|
|||||||
import { isFullscreen, requestFullscreen, exitFullscreen } from '../ui/util/fullscreen';
|
import { isFullscreen, requestFullscreen, exitFullscreen } from '../ui/util/fullscreen';
|
||||||
import { displayMedia } from '../../initial_state';
|
import { displayMedia } from '../../initial_state';
|
||||||
import Icon from 'mastodon/components/icon';
|
import Icon from 'mastodon/components/icon';
|
||||||
|
import { decode } from 'blurhash';
|
||||||
|
|
||||||
const messages = defineMessages({
|
const messages = defineMessages({
|
||||||
play: { id: 'video.play', defaultMessage: 'Play' },
|
play: { id: 'video.play', defaultMessage: 'Play' },
|
||||||
@ -102,6 +103,8 @@ class Video extends React.PureComponent {
|
|||||||
inline: PropTypes.bool,
|
inline: PropTypes.bool,
|
||||||
cacheWidth: PropTypes.func,
|
cacheWidth: PropTypes.func,
|
||||||
intl: PropTypes.object.isRequired,
|
intl: PropTypes.object.isRequired,
|
||||||
|
blurhash: PropTypes.string,
|
||||||
|
link: PropTypes.node,
|
||||||
};
|
};
|
||||||
|
|
||||||
state = {
|
state = {
|
||||||
@ -139,6 +142,7 @@ class Video extends React.PureComponent {
|
|||||||
|
|
||||||
setVideoRef = c => {
|
setVideoRef = c => {
|
||||||
this.video = c;
|
this.video = c;
|
||||||
|
|
||||||
if (this.video) {
|
if (this.video) {
|
||||||
this.setState({ volume: this.video.volume, muted: this.video.muted });
|
this.setState({ volume: this.video.volume, muted: this.video.muted });
|
||||||
}
|
}
|
||||||
@ -152,6 +156,10 @@ class Video extends React.PureComponent {
|
|||||||
this.volume = c;
|
this.volume = c;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
setCanvasRef = c => {
|
||||||
|
this.canvas = c;
|
||||||
|
}
|
||||||
|
|
||||||
handleClickRoot = e => e.stopPropagation();
|
handleClickRoot = e => e.stopPropagation();
|
||||||
|
|
||||||
handlePlay = () => {
|
handlePlay = () => {
|
||||||
@ -170,7 +178,6 @@ class Video extends React.PureComponent {
|
|||||||
}
|
}
|
||||||
|
|
||||||
handleVolumeMouseDown = e => {
|
handleVolumeMouseDown = e => {
|
||||||
|
|
||||||
document.addEventListener('mousemove', this.handleMouseVolSlide, true);
|
document.addEventListener('mousemove', this.handleMouseVolSlide, true);
|
||||||
document.addEventListener('mouseup', this.handleVolumeMouseUp, true);
|
document.addEventListener('mouseup', this.handleVolumeMouseUp, true);
|
||||||
document.addEventListener('touchmove', this.handleMouseVolSlide, true);
|
document.addEventListener('touchmove', this.handleMouseVolSlide, true);
|
||||||
@ -190,7 +197,6 @@ class Video extends React.PureComponent {
|
|||||||
}
|
}
|
||||||
|
|
||||||
handleMouseVolSlide = throttle(e => {
|
handleMouseVolSlide = throttle(e => {
|
||||||
|
|
||||||
const rect = this.volume.getBoundingClientRect();
|
const rect = this.volume.getBoundingClientRect();
|
||||||
const x = (e.clientX - rect.left) / this.volWidth; //x position within the element.
|
const x = (e.clientX - rect.left) / this.volWidth; //x position within the element.
|
||||||
|
|
||||||
@ -261,6 +267,10 @@ class Video extends React.PureComponent {
|
|||||||
document.addEventListener('webkitfullscreenchange', this.handleFullscreenChange, true);
|
document.addEventListener('webkitfullscreenchange', this.handleFullscreenChange, true);
|
||||||
document.addEventListener('mozfullscreenchange', this.handleFullscreenChange, true);
|
document.addEventListener('mozfullscreenchange', this.handleFullscreenChange, true);
|
||||||
document.addEventListener('MSFullscreenChange', this.handleFullscreenChange, true);
|
document.addEventListener('MSFullscreenChange', this.handleFullscreenChange, true);
|
||||||
|
|
||||||
|
if (this.props.blurhash) {
|
||||||
|
this._decode();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
componentWillUnmount () {
|
componentWillUnmount () {
|
||||||
@ -270,6 +280,24 @@ class Video extends React.PureComponent {
|
|||||||
document.removeEventListener('MSFullscreenChange', this.handleFullscreenChange, true);
|
document.removeEventListener('MSFullscreenChange', this.handleFullscreenChange, true);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
componentDidUpdate (prevProps) {
|
||||||
|
if (prevProps.blurhash !== this.props.blurhash && this.props.blurhash) {
|
||||||
|
this._decode();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
_decode () {
|
||||||
|
const hash = this.props.blurhash;
|
||||||
|
const pixels = decode(hash, 32, 32);
|
||||||
|
|
||||||
|
if (pixels) {
|
||||||
|
const ctx = this.canvas.getContext('2d');
|
||||||
|
const imageData = new ImageData(pixels, 32, 32);
|
||||||
|
|
||||||
|
ctx.putImageData(imageData, 0, 0);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
handleFullscreenChange = () => {
|
handleFullscreenChange = () => {
|
||||||
this.setState({ fullscreen: isFullscreen() });
|
this.setState({ fullscreen: isFullscreen() });
|
||||||
}
|
}
|
||||||
@ -314,6 +342,7 @@ class Video extends React.PureComponent {
|
|||||||
|
|
||||||
handleOpenVideo = () => {
|
handleOpenVideo = () => {
|
||||||
const { src, preview, width, height, alt } = this.props;
|
const { src, preview, width, height, alt } = this.props;
|
||||||
|
|
||||||
const media = fromJS({
|
const media = fromJS({
|
||||||
type: 'video',
|
type: 'video',
|
||||||
url: src,
|
url: src,
|
||||||
@ -333,7 +362,7 @@ class Video extends React.PureComponent {
|
|||||||
}
|
}
|
||||||
|
|
||||||
render () {
|
render () {
|
||||||
const { preview, src, inline, startTime, onOpenVideo, onCloseVideo, intl, alt, detailed, sensitive } = this.props;
|
const { preview, src, inline, startTime, onOpenVideo, onCloseVideo, intl, alt, detailed, sensitive, link } = this.props;
|
||||||
const { containerWidth, currentTime, duration, volume, buffer, dragging, paused, fullscreen, hovered, muted, revealed } = this.state;
|
const { containerWidth, currentTime, duration, volume, buffer, dragging, paused, fullscreen, hovered, muted, revealed } = this.state;
|
||||||
const progress = (currentTime / duration) * 100;
|
const progress = (currentTime / duration) * 100;
|
||||||
|
|
||||||
@ -351,6 +380,7 @@ class Video extends React.PureComponent {
|
|||||||
}
|
}
|
||||||
|
|
||||||
let preload;
|
let preload;
|
||||||
|
|
||||||
if (startTime || fullscreen || dragging) {
|
if (startTime || fullscreen || dragging) {
|
||||||
preload = 'auto';
|
preload = 'auto';
|
||||||
} else if (detailed) {
|
} else if (detailed) {
|
||||||
@ -360,6 +390,7 @@ class Video extends React.PureComponent {
|
|||||||
}
|
}
|
||||||
|
|
||||||
let warning;
|
let warning;
|
||||||
|
|
||||||
if (sensitive) {
|
if (sensitive) {
|
||||||
warning = <FormattedMessage id='status.sensitive_warning' defaultMessage='Sensitive content' />;
|
warning = <FormattedMessage id='status.sensitive_warning' defaultMessage='Sensitive content' />;
|
||||||
} else {
|
} else {
|
||||||
@ -377,7 +408,9 @@ class Video extends React.PureComponent {
|
|||||||
onClick={this.handleClickRoot}
|
onClick={this.handleClickRoot}
|
||||||
tabIndex={0}
|
tabIndex={0}
|
||||||
>
|
>
|
||||||
<video
|
<canvas width={32} height={32} ref={this.setCanvasRef} className={classNames('media-gallery__preview', { 'media-gallery__preview--hidden': revealed })} />
|
||||||
|
|
||||||
|
{revealed && <video
|
||||||
ref={this.setVideoRef}
|
ref={this.setVideoRef}
|
||||||
src={src}
|
src={src}
|
||||||
poster={preview}
|
poster={preview}
|
||||||
@ -397,12 +430,13 @@ class Video extends React.PureComponent {
|
|||||||
onLoadedData={this.handleLoadedData}
|
onLoadedData={this.handleLoadedData}
|
||||||
onProgress={this.handleProgress}
|
onProgress={this.handleProgress}
|
||||||
onVolumeChange={this.handleVolumeChange}
|
onVolumeChange={this.handleVolumeChange}
|
||||||
/>
|
/>}
|
||||||
|
|
||||||
<button type='button' className={classNames('video-player__spoiler', { active: !revealed })} onClick={this.toggleReveal}>
|
<div className={classNames('spoiler-button', { 'spoiler-button--hidden': revealed })}>
|
||||||
<span className='video-player__spoiler__title'>{warning}</span>
|
<button type='button' className='spoiler-button__overlay' onClick={this.toggleReveal}>
|
||||||
<span className='video-player__spoiler__subtitle'><FormattedMessage id='status.sensitive_toggle' defaultMessage='Click to view' /></span>
|
<span className='spoiler-button__overlay__label'>{warning}</span>
|
||||||
</button>
|
</button>
|
||||||
|
</div>
|
||||||
|
|
||||||
<div className={classNames('video-player__controls', { active: paused || hovered })}>
|
<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' onMouseDown={this.handleMouseDown} ref={this.setSeekRef}>
|
||||||
@ -420,6 +454,7 @@ class Video extends React.PureComponent {
|
|||||||
<div className='video-player__buttons left'>
|
<div className='video-player__buttons left'>
|
||||||
<button type='button' aria-label={intl.formatMessage(paused ? messages.play : messages.pause)} onClick={this.togglePlay}><Icon id={paused ? 'play' : 'pause'} fixedWidth /></button>
|
<button type='button' aria-label={intl.formatMessage(paused ? messages.play : messages.pause)} onClick={this.togglePlay}><Icon id={paused ? 'play' : 'pause'} fixedWidth /></button>
|
||||||
<button type='button' aria-label={intl.formatMessage(muted ? messages.unmute : messages.mute)} onClick={this.toggleMute}><Icon id={muted ? 'volume-off' : 'volume-up'} fixedWidth /></button>
|
<button type='button' aria-label={intl.formatMessage(muted ? messages.unmute : messages.mute)} onClick={this.toggleMute}><Icon id={muted ? 'volume-off' : 'volume-up'} fixedWidth /></button>
|
||||||
|
|
||||||
<div className='video-player__volume' onMouseDown={this.handleVolumeMouseDown} ref={this.setVolumeRef}>
|
<div className='video-player__volume' onMouseDown={this.handleVolumeMouseDown} ref={this.setVolumeRef}>
|
||||||
<div className='video-player__volume__current' style={{ width: `${volumeWidth}px` }} />
|
<div className='video-player__volume__current' style={{ width: `${volumeWidth}px` }} />
|
||||||
<span
|
<span
|
||||||
@ -429,17 +464,19 @@ class Video extends React.PureComponent {
|
|||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{(detailed || fullscreen) &&
|
{(detailed || fullscreen) && (
|
||||||
<span>
|
<span>
|
||||||
<span className='video-player__time-current'>{formatTime(currentTime)}</span>
|
<span className='video-player__time-current'>{formatTime(currentTime)}</span>
|
||||||
<span className='video-player__time-sep'>/</span>
|
<span className='video-player__time-sep'>/</span>
|
||||||
<span className='video-player__time-total'>{formatTime(duration)}</span>
|
<span className='video-player__time-total'>{formatTime(duration)}</span>
|
||||||
</span>
|
</span>
|
||||||
}
|
)}
|
||||||
|
|
||||||
|
{link && <span className='video-player__link'>{link}</span>}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className='video-player__buttons right'>
|
<div className='video-player__buttons right'>
|
||||||
{!onCloseVideo && <button type='button' aria-label={intl.formatMessage(messages.hide)} onClick={this.toggleReveal}><Icon id='eye' fixedWidth /></button>}
|
{!onCloseVideo && <button type='button' aria-label={intl.formatMessage(messages.hide)} onClick={this.toggleReveal}><Icon id='eye-slash' fixedWidth /></button>}
|
||||||
{(!fullscreen && onOpenVideo) && <button type='button' aria-label={intl.formatMessage(messages.expand)} onClick={this.handleOpenVideo}><Icon id='expand' fixedWidth /></button>}
|
{(!fullscreen && onOpenVideo) && <button type='button' aria-label={intl.formatMessage(messages.expand)} onClick={this.handleOpenVideo}><Icon id='expand' fixedWidth /></button>}
|
||||||
{onCloseVideo && <button type='button' aria-label={intl.formatMessage(messages.close)} onClick={this.handleCloseVideo}><Icon id='compress' fixedWidth /></button>}
|
{onCloseVideo && <button type='button' aria-label={intl.formatMessage(messages.close)} onClick={this.handleCloseVideo}><Icon id='compress' fixedWidth /></button>}
|
||||||
<button type='button' aria-label={intl.formatMessage(fullscreen ? messages.exit_fullscreen : messages.fullscreen)} onClick={this.toggleFullscreen}><Icon id={fullscreen ? 'compress' : 'arrows-alt'} fixedWidth /></button>
|
<button type='button' aria-label={intl.formatMessage(fullscreen ? messages.exit_fullscreen : messages.fullscreen)} onClick={this.toggleFullscreen}><Icon id={fullscreen ? 'compress' : 'arrows-alt'} fixedWidth /></button>
|
||||||
|
@ -13,6 +13,8 @@ export const deleteModal = getMeta('delete_modal');
|
|||||||
export const me = getMeta('me');
|
export const me = getMeta('me');
|
||||||
export const searchEnabled = getMeta('search_enabled');
|
export const searchEnabled = getMeta('search_enabled');
|
||||||
export const invitesEnabled = getMeta('invites_enabled');
|
export const invitesEnabled = getMeta('invites_enabled');
|
||||||
|
export const repository = getMeta('repository');
|
||||||
|
export const source_url = getMeta('source_url');
|
||||||
export const version = getMeta('version');
|
export const version = getMeta('version');
|
||||||
export const mascot = getMeta('mascot');
|
export const mascot = getMeta('mascot');
|
||||||
export const profile_directory = getMeta('profile_directory');
|
export const profile_directory = getMeta('profile_directory');
|
||||||
|
@ -77,6 +77,7 @@
|
|||||||
"compose_form.poll.remove_option": "Odstranit tuto volbu",
|
"compose_form.poll.remove_option": "Odstranit tuto volbu",
|
||||||
"compose_form.publish": "Tootnout",
|
"compose_form.publish": "Tootnout",
|
||||||
"compose_form.publish_loud": "{publish}!",
|
"compose_form.publish_loud": "{publish}!",
|
||||||
|
"compose_form.sensitive.hide": "Označit média jako citlivá",
|
||||||
"compose_form.sensitive.marked": "Média jsou označena jako citlivá",
|
"compose_form.sensitive.marked": "Média jsou označena jako citlivá",
|
||||||
"compose_form.sensitive.unmarked": "Média nejsou označena jako citlivá",
|
"compose_form.sensitive.unmarked": "Média nejsou označena jako citlivá",
|
||||||
"compose_form.spoiler.marked": "Text je skrytý za varováním",
|
"compose_form.spoiler.marked": "Text je skrytý za varováním",
|
||||||
@ -214,6 +215,7 @@
|
|||||||
"lightbox.close": "Zavřít",
|
"lightbox.close": "Zavřít",
|
||||||
"lightbox.next": "Další",
|
"lightbox.next": "Další",
|
||||||
"lightbox.previous": "Předchozí",
|
"lightbox.previous": "Předchozí",
|
||||||
|
"lightbox.view_context": "Zobrazit kontext",
|
||||||
"lists.account.add": "Přidat do seznamu",
|
"lists.account.add": "Přidat do seznamu",
|
||||||
"lists.account.remove": "Odebrat ze seznamu",
|
"lists.account.remove": "Odebrat ze seznamu",
|
||||||
"lists.delete": "Smazat seznam",
|
"lists.delete": "Smazat seznam",
|
||||||
|
384
app/javascript/mastodon/locales/hi.json
Normal file
384
app/javascript/mastodon/locales/hi.json
Normal file
@ -0,0 +1,384 @@
|
|||||||
|
{
|
||||||
|
"account.add_or_remove_from_list": "Add or Remove from lists",
|
||||||
|
"account.badges.bot": "Bot",
|
||||||
|
"account.block": "Block @{name}",
|
||||||
|
"account.block_domain": "Hide everything from {domain}",
|
||||||
|
"account.blocked": "Blocked",
|
||||||
|
"account.direct": "Direct message @{name}",
|
||||||
|
"account.domain_blocked": "Domain hidden",
|
||||||
|
"account.edit_profile": "Edit profile",
|
||||||
|
"account.endorse": "Feature on profile",
|
||||||
|
"account.follow": "Follow",
|
||||||
|
"account.followers": "Followers",
|
||||||
|
"account.followers.empty": "No one follows this user yet.",
|
||||||
|
"account.follows": "Follows",
|
||||||
|
"account.follows.empty": "This user doesn't follow anyone yet.",
|
||||||
|
"account.follows_you": "Follows you",
|
||||||
|
"account.hide_reblogs": "Hide boosts from @{name}",
|
||||||
|
"account.link_verified_on": "Ownership of this link was checked on {date}",
|
||||||
|
"account.locked_info": "This account privacy status is set to locked. The owner manually reviews who can follow them.",
|
||||||
|
"account.media": "Media",
|
||||||
|
"account.mention": "Mention @{name}",
|
||||||
|
"account.moved_to": "{name} has moved to:",
|
||||||
|
"account.mute": "Mute @{name}",
|
||||||
|
"account.mute_notifications": "Mute notifications from @{name}",
|
||||||
|
"account.muted": "Muted",
|
||||||
|
"account.posts": "Toots",
|
||||||
|
"account.posts_with_replies": "Toots and replies",
|
||||||
|
"account.report": "Report @{name}",
|
||||||
|
"account.requested": "Awaiting approval. Click to cancel follow request",
|
||||||
|
"account.share": "Share @{name}'s profile",
|
||||||
|
"account.show_reblogs": "Show boosts from @{name}",
|
||||||
|
"account.unblock": "Unblock @{name}",
|
||||||
|
"account.unblock_domain": "Unhide {domain}",
|
||||||
|
"account.unendorse": "Don't feature on profile",
|
||||||
|
"account.unfollow": "Unfollow",
|
||||||
|
"account.unmute": "Unmute @{name}",
|
||||||
|
"account.unmute_notifications": "Unmute notifications from @{name}",
|
||||||
|
"alert.unexpected.message": "An unexpected error occurred.",
|
||||||
|
"alert.unexpected.title": "Oops!",
|
||||||
|
"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",
|
||||||
|
"column.blocks": "Blocked users",
|
||||||
|
"column.community": "Local timeline",
|
||||||
|
"column.direct": "Direct messages",
|
||||||
|
"column.domain_blocks": "Hidden domains",
|
||||||
|
"column.favourites": "Favourites",
|
||||||
|
"column.follow_requests": "Follow requests",
|
||||||
|
"column.home": "Home",
|
||||||
|
"column.lists": "Lists",
|
||||||
|
"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",
|
||||||
|
"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.settings": "Settings",
|
||||||
|
"community.column_settings.media_only": "Media Only",
|
||||||
|
"compose_form.direct_message_warning": "This toot will only be sent to all the mentioned users.",
|
||||||
|
"compose_form.direct_message_warning_learn_more": "Learn more",
|
||||||
|
"compose_form.hashtag_warning": "This toot won't be listed under any hashtag as it is unlisted. Only public toots can be searched by hashtag.",
|
||||||
|
"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",
|
||||||
|
"compose_form.placeholder": "What is on your mind?",
|
||||||
|
"compose_form.poll.add_option": "Add a choice",
|
||||||
|
"compose_form.poll.duration": "Poll duration",
|
||||||
|
"compose_form.poll.option_placeholder": "Choice {number}",
|
||||||
|
"compose_form.poll.remove_option": "Remove this choice",
|
||||||
|
"compose_form.publish": "Toot",
|
||||||
|
"compose_form.publish_loud": "{publish}!",
|
||||||
|
"compose_form.sensitive.marked": "Media is marked as sensitive",
|
||||||
|
"compose_form.sensitive.unmarked": "Media is not marked as sensitive",
|
||||||
|
"compose_form.spoiler.marked": "Text is hidden behind warning",
|
||||||
|
"compose_form.spoiler.unmarked": "Text is not hidden",
|
||||||
|
"compose_form.spoiler_placeholder": "Write your warning here",
|
||||||
|
"confirmation_modal.cancel": "Cancel",
|
||||||
|
"confirmations.block.block_and_report": "Block & Report",
|
||||||
|
"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.delete_list.confirm": "Delete",
|
||||||
|
"confirmations.delete_list.message": "Are you sure you want to permanently delete this list?",
|
||||||
|
"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. You will not see content from that domain in any public timelines or your notifications. Your followers from that domain will be removed.",
|
||||||
|
"confirmations.mute.confirm": "Mute",
|
||||||
|
"confirmations.mute.message": "Are you sure you want to mute {name}?",
|
||||||
|
"confirmations.redraft.confirm": "Delete & redraft",
|
||||||
|
"confirmations.redraft.message": "Are you sure you want to delete this status and re-draft it? Favourites and boosts will be lost, and replies to the original post will be orphaned.",
|
||||||
|
"confirmations.reply.confirm": "Reply",
|
||||||
|
"confirmations.reply.message": "Replying now will overwrite the message you are currently composing. Are you sure you want to proceed?",
|
||||||
|
"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.custom": "Custom",
|
||||||
|
"emoji_button.flags": "Flags",
|
||||||
|
"emoji_button.food": "Food & Drink",
|
||||||
|
"emoji_button.label": "Insert emoji",
|
||||||
|
"emoji_button.nature": "Nature",
|
||||||
|
"emoji_button.not_found": "No emojos!! (╯°□°)╯︵ ┻━┻",
|
||||||
|
"emoji_button.objects": "Objects",
|
||||||
|
"emoji_button.people": "People",
|
||||||
|
"emoji_button.recent": "Frequently used",
|
||||||
|
"emoji_button.search": "Search...",
|
||||||
|
"emoji_button.search_results": "Search results",
|
||||||
|
"emoji_button.symbols": "Symbols",
|
||||||
|
"emoji_button.travel": "Travel & Places",
|
||||||
|
"empty_column.account_timeline": "No toots here!",
|
||||||
|
"empty_column.account_unavailable": "Profile unavailable",
|
||||||
|
"empty_column.blocks": "You haven't blocked any users yet.",
|
||||||
|
"empty_column.community": "The local timeline is empty. Write something publicly to get the ball rolling!",
|
||||||
|
"empty_column.direct": "You don't have any direct messages yet. When you send or receive one, it will show up here.",
|
||||||
|
"empty_column.domain_blocks": "There are no hidden domains yet.",
|
||||||
|
"empty_column.favourited_statuses": "You don't have any favourite toots yet. When you favourite one, it will show up here.",
|
||||||
|
"empty_column.favourites": "No one has favourited this toot yet. When someone does, they will show up here.",
|
||||||
|
"empty_column.follow_requests": "You don't have any follow requests yet. When you receive one, it will show up here.",
|
||||||
|
"empty_column.hashtag": "There is nothing in this hashtag yet.",
|
||||||
|
"empty_column.home": "Your home timeline is empty! Visit {public} or use search to get started and meet other users.",
|
||||||
|
"empty_column.home.public_timeline": "the public timeline",
|
||||||
|
"empty_column.list": "There is nothing in this list yet. When members of this list post new statuses, they will appear here.",
|
||||||
|
"empty_column.lists": "You don't have any lists yet. When you create one, it will show up here.",
|
||||||
|
"empty_column.mutes": "You haven't muted any users yet.",
|
||||||
|
"empty_column.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 servers to fill it up",
|
||||||
|
"follow_request.authorize": "Authorize",
|
||||||
|
"follow_request.reject": "Reject",
|
||||||
|
"getting_started.developers": "Developers",
|
||||||
|
"getting_started.directory": "Profile directory",
|
||||||
|
"getting_started.documentation": "Documentation",
|
||||||
|
"getting_started.heading": "Getting started",
|
||||||
|
"getting_started.invite": "Invite people",
|
||||||
|
"getting_started.open_source_notice": "Mastodon is open source software. You can contribute or report issues on GitHub at {github}.",
|
||||||
|
"getting_started.security": "Security",
|
||||||
|
"getting_started.terms": "Terms of service",
|
||||||
|
"hashtag.column_header.tag_mode.all": "and {additional}",
|
||||||
|
"hashtag.column_header.tag_mode.any": "or {additional}",
|
||||||
|
"hashtag.column_header.tag_mode.none": "without {additional}",
|
||||||
|
"hashtag.column_settings.select.no_options_message": "No suggestions found",
|
||||||
|
"hashtag.column_settings.select.placeholder": "Enter hashtags…",
|
||||||
|
"hashtag.column_settings.tag_mode.all": "All of these",
|
||||||
|
"hashtag.column_settings.tag_mode.any": "Any of these",
|
||||||
|
"hashtag.column_settings.tag_mode.none": "None of these",
|
||||||
|
"hashtag.column_settings.tag_toggle": "Include additional tags in this column",
|
||||||
|
"home.column_settings.basic": "Basic",
|
||||||
|
"home.column_settings.show_reblogs": "Show boosts",
|
||||||
|
"home.column_settings.show_replies": "Show replies",
|
||||||
|
"intervals.full.days": "{number, plural, one {# day} other {# days}}",
|
||||||
|
"intervals.full.hours": "{number, plural, one {# hour} other {# hours}}",
|
||||||
|
"intervals.full.minutes": "{number, plural, one {# minute} other {# minutes}}",
|
||||||
|
"introduction.federation.action": "Next",
|
||||||
|
"introduction.federation.federated.headline": "Federated",
|
||||||
|
"introduction.federation.federated.text": "Public posts from other servers of the fediverse will appear in the federated timeline.",
|
||||||
|
"introduction.federation.home.headline": "Home",
|
||||||
|
"introduction.federation.home.text": "Posts from people you follow will appear in your home feed. You can follow anyone on any server!",
|
||||||
|
"introduction.federation.local.headline": "Local",
|
||||||
|
"introduction.federation.local.text": "Public posts from people on the same server as you will appear in the local timeline.",
|
||||||
|
"introduction.interactions.action": "Finish toot-orial!",
|
||||||
|
"introduction.interactions.favourite.headline": "Favourite",
|
||||||
|
"introduction.interactions.favourite.text": "You can save a toot for later, and let the author know that you liked it, by favouriting it.",
|
||||||
|
"introduction.interactions.reblog.headline": "Boost",
|
||||||
|
"introduction.interactions.reblog.text": "You can share other people's toots with your followers by boosting them.",
|
||||||
|
"introduction.interactions.reply.headline": "Reply",
|
||||||
|
"introduction.interactions.reply.text": "You can reply to other people's and your own toots, which will chain them together in a conversation.",
|
||||||
|
"introduction.welcome.action": "Let's go!",
|
||||||
|
"introduction.welcome.headline": "First steps",
|
||||||
|
"introduction.welcome.text": "Welcome to the fediverse! In a few moments, you'll be able to broadcast messages and talk to your friends across a wide variety of servers. But this server, {domain}, is special—it hosts your profile, so remember its name.",
|
||||||
|
"keyboard_shortcuts.back": "to navigate back",
|
||||||
|
"keyboard_shortcuts.blocked": "to open blocked users list",
|
||||||
|
"keyboard_shortcuts.boost": "to boost",
|
||||||
|
"keyboard_shortcuts.column": "to focus a status in one of the columns",
|
||||||
|
"keyboard_shortcuts.compose": "to focus the compose textarea",
|
||||||
|
"keyboard_shortcuts.description": "Description",
|
||||||
|
"keyboard_shortcuts.direct": "to open direct messages column",
|
||||||
|
"keyboard_shortcuts.down": "to move down in the list",
|
||||||
|
"keyboard_shortcuts.enter": "to open status",
|
||||||
|
"keyboard_shortcuts.favourite": "to favourite",
|
||||||
|
"keyboard_shortcuts.favourites": "to open favourites list",
|
||||||
|
"keyboard_shortcuts.federated": "to open federated timeline",
|
||||||
|
"keyboard_shortcuts.heading": "Keyboard Shortcuts",
|
||||||
|
"keyboard_shortcuts.home": "to open home timeline",
|
||||||
|
"keyboard_shortcuts.hotkey": "Hotkey",
|
||||||
|
"keyboard_shortcuts.legend": "to display this legend",
|
||||||
|
"keyboard_shortcuts.local": "to open local timeline",
|
||||||
|
"keyboard_shortcuts.mention": "to mention author",
|
||||||
|
"keyboard_shortcuts.muted": "to open muted users list",
|
||||||
|
"keyboard_shortcuts.my_profile": "to open your profile",
|
||||||
|
"keyboard_shortcuts.notifications": "to open notifications column",
|
||||||
|
"keyboard_shortcuts.pinned": "to open pinned toots list",
|
||||||
|
"keyboard_shortcuts.profile": "to open author's profile",
|
||||||
|
"keyboard_shortcuts.reply": "to reply",
|
||||||
|
"keyboard_shortcuts.requests": "to open follow requests list",
|
||||||
|
"keyboard_shortcuts.search": "to focus search",
|
||||||
|
"keyboard_shortcuts.start": "to open \"get started\" column",
|
||||||
|
"keyboard_shortcuts.toggle_hidden": "to show/hide text behind CW",
|
||||||
|
"keyboard_shortcuts.toot": "to start a brand new toot",
|
||||||
|
"keyboard_shortcuts.unfocus": "to un-focus compose textarea/search",
|
||||||
|
"keyboard_shortcuts.up": "to move up in the list",
|
||||||
|
"lightbox.close": "Close",
|
||||||
|
"lightbox.next": "Next",
|
||||||
|
"lightbox.previous": "Previous",
|
||||||
|
"lists.account.add": "Add to list",
|
||||||
|
"lists.account.remove": "Remove from list",
|
||||||
|
"lists.delete": "Delete list",
|
||||||
|
"lists.edit": "Edit list",
|
||||||
|
"lists.edit.submit": "Change title",
|
||||||
|
"lists.new.create": "Add list",
|
||||||
|
"lists.new.title_placeholder": "New list title",
|
||||||
|
"lists.search": "Search among people you follow",
|
||||||
|
"lists.subheading": "Your lists",
|
||||||
|
"loading_indicator.label": "Loading...",
|
||||||
|
"media_gallery.toggle_visible": "Toggle visibility",
|
||||||
|
"missing_indicator.label": "Not found",
|
||||||
|
"missing_indicator.sublabel": "This resource could not be found",
|
||||||
|
"mute_modal.hide_notifications": "Hide notifications from this user?",
|
||||||
|
"navigation_bar.apps": "Mobile apps",
|
||||||
|
"navigation_bar.blocks": "Blocked users",
|
||||||
|
"navigation_bar.community_timeline": "Local timeline",
|
||||||
|
"navigation_bar.compose": "Compose new toot",
|
||||||
|
"navigation_bar.direct": "Direct messages",
|
||||||
|
"navigation_bar.discover": "Discover",
|
||||||
|
"navigation_bar.domain_blocks": "Hidden domains",
|
||||||
|
"navigation_bar.edit_profile": "Edit profile",
|
||||||
|
"navigation_bar.favourites": "Favourites",
|
||||||
|
"navigation_bar.filters": "Muted words",
|
||||||
|
"navigation_bar.follow_requests": "Follow requests",
|
||||||
|
"navigation_bar.info": "About this server",
|
||||||
|
"navigation_bar.keyboard_shortcuts": "Hotkeys",
|
||||||
|
"navigation_bar.lists": "Lists",
|
||||||
|
"navigation_bar.logout": "Logout",
|
||||||
|
"navigation_bar.mutes": "Muted users",
|
||||||
|
"navigation_bar.personal": "Personal",
|
||||||
|
"navigation_bar.pins": "Pinned toots",
|
||||||
|
"navigation_bar.preferences": "Preferences",
|
||||||
|
"navigation_bar.public_timeline": "Federated timeline",
|
||||||
|
"navigation_bar.security": "Security",
|
||||||
|
"notification.favourite": "{name} favourited your status",
|
||||||
|
"notification.follow": "{name} followed you",
|
||||||
|
"notification.mention": "{name} mentioned you",
|
||||||
|
"notification.poll": "A poll you have voted in has ended",
|
||||||
|
"notification.reblog": "{name} boosted your status",
|
||||||
|
"notifications.clear": "Clear notifications",
|
||||||
|
"notifications.clear_confirmation": "Are you sure you want to permanently clear all your notifications?",
|
||||||
|
"notifications.column_settings.alert": "Desktop notifications",
|
||||||
|
"notifications.column_settings.favourite": "Favourites:",
|
||||||
|
"notifications.column_settings.filter_bar.advanced": "Display all categories",
|
||||||
|
"notifications.column_settings.filter_bar.category": "Quick filter bar",
|
||||||
|
"notifications.column_settings.filter_bar.show": "Show",
|
||||||
|
"notifications.column_settings.follow": "New followers:",
|
||||||
|
"notifications.column_settings.mention": "Mentions:",
|
||||||
|
"notifications.column_settings.poll": "Poll results:",
|
||||||
|
"notifications.column_settings.push": "Push notifications",
|
||||||
|
"notifications.column_settings.reblog": "Boosts:",
|
||||||
|
"notifications.column_settings.show": "Show in column",
|
||||||
|
"notifications.column_settings.sound": "Play sound",
|
||||||
|
"notifications.filter.all": "All",
|
||||||
|
"notifications.filter.boosts": "Boosts",
|
||||||
|
"notifications.filter.favourites": "Favourites",
|
||||||
|
"notifications.filter.follows": "Follows",
|
||||||
|
"notifications.filter.mentions": "Mentions",
|
||||||
|
"notifications.filter.polls": "Poll results",
|
||||||
|
"notifications.group": "{count} notifications",
|
||||||
|
"poll.closed": "Closed",
|
||||||
|
"poll.refresh": "Refresh",
|
||||||
|
"poll.total_votes": "{count, plural, one {# vote} other {# votes}}",
|
||||||
|
"poll.vote": "Vote",
|
||||||
|
"poll_button.add_poll": "Add a poll",
|
||||||
|
"poll_button.remove_poll": "Remove poll",
|
||||||
|
"privacy.change": "Adjust status privacy",
|
||||||
|
"privacy.direct.long": "Post to mentioned users only",
|
||||||
|
"privacy.direct.short": "Direct",
|
||||||
|
"privacy.private.long": "Post to followers only",
|
||||||
|
"privacy.private.short": "Followers-only",
|
||||||
|
"privacy.public.long": "Post to public timelines",
|
||||||
|
"privacy.public.short": "Public",
|
||||||
|
"privacy.unlisted.long": "Do not show in public timelines",
|
||||||
|
"privacy.unlisted.short": "Unlisted",
|
||||||
|
"regeneration_indicator.label": "Loading…",
|
||||||
|
"regeneration_indicator.sublabel": "Your home feed is being prepared!",
|
||||||
|
"relative_time.days": "{number}d",
|
||||||
|
"relative_time.hours": "{number}h",
|
||||||
|
"relative_time.just_now": "now",
|
||||||
|
"relative_time.minutes": "{number}m",
|
||||||
|
"relative_time.seconds": "{number}s",
|
||||||
|
"reply_indicator.cancel": "Cancel",
|
||||||
|
"report.forward": "Forward to {target}",
|
||||||
|
"report.forward_hint": "The account is from another server. Send an anonymized copy of the report there as well?",
|
||||||
|
"report.hint": "The report will be sent to your server moderators. You can provide an explanation of why you are reporting this account below:",
|
||||||
|
"report.placeholder": "Additional comments",
|
||||||
|
"report.submit": "Submit",
|
||||||
|
"report.target": "Report {target}",
|
||||||
|
"search.placeholder": "Search",
|
||||||
|
"search_popout.search_format": "Advanced search format",
|
||||||
|
"search_popout.tips.full_text": "Simple text returns statuses you have written, favourited, boosted, or have been mentioned in, as well as matching usernames, display names, and hashtags.",
|
||||||
|
"search_popout.tips.hashtag": "hashtag",
|
||||||
|
"search_popout.tips.status": "status",
|
||||||
|
"search_popout.tips.text": "Simple text returns matching display names, usernames and hashtags",
|
||||||
|
"search_popout.tips.user": "user",
|
||||||
|
"search_results.accounts": "People",
|
||||||
|
"search_results.hashtags": "Hashtags",
|
||||||
|
"search_results.statuses": "Toots",
|
||||||
|
"search_results.total": "{count, number} {count, plural, one {result} other {results}}",
|
||||||
|
"status.admin_account": "Open moderation interface for @{name}",
|
||||||
|
"status.admin_status": "Open this status in the moderation interface",
|
||||||
|
"status.block": "Block @{name}",
|
||||||
|
"status.cancel_reblog_private": "Unboost",
|
||||||
|
"status.cannot_reblog": "This post cannot be boosted",
|
||||||
|
"status.copy": "Copy link to status",
|
||||||
|
"status.delete": "Delete",
|
||||||
|
"status.detailed_status": "Detailed conversation view",
|
||||||
|
"status.direct": "Direct message @{name}",
|
||||||
|
"status.embed": "Embed",
|
||||||
|
"status.favourite": "Favourite",
|
||||||
|
"status.filtered": "Filtered",
|
||||||
|
"status.load_more": "Load more",
|
||||||
|
"status.media_hidden": "Media hidden",
|
||||||
|
"status.mention": "Mention @{name}",
|
||||||
|
"status.more": "More",
|
||||||
|
"status.mute": "Mute @{name}",
|
||||||
|
"status.mute_conversation": "Mute conversation",
|
||||||
|
"status.open": "Expand this status",
|
||||||
|
"status.pin": "Pin on profile",
|
||||||
|
"status.pinned": "Pinned toot",
|
||||||
|
"status.read_more": "Read more",
|
||||||
|
"status.reblog": "Boost",
|
||||||
|
"status.reblog_private": "Boost to original audience",
|
||||||
|
"status.reblogged_by": "{name} boosted",
|
||||||
|
"status.reblogs.empty": "No one has boosted this toot yet. When someone does, they will show up here.",
|
||||||
|
"status.redraft": "Delete & re-draft",
|
||||||
|
"status.reply": "Reply",
|
||||||
|
"status.replyAll": "Reply to thread",
|
||||||
|
"status.report": "Report @{name}",
|
||||||
|
"status.sensitive_toggle": "Click to view",
|
||||||
|
"status.sensitive_warning": "Sensitive content",
|
||||||
|
"status.share": "Share",
|
||||||
|
"status.show_less": "Show less",
|
||||||
|
"status.show_less_all": "Show less for all",
|
||||||
|
"status.show_more": "Show more",
|
||||||
|
"status.show_more_all": "Show more for all",
|
||||||
|
"status.show_thread": "Show thread",
|
||||||
|
"status.unmute_conversation": "Unmute conversation",
|
||||||
|
"status.unpin": "Unpin from profile",
|
||||||
|
"suggestions.dismiss": "Dismiss suggestion",
|
||||||
|
"suggestions.header": "You might be interested in…",
|
||||||
|
"tabs_bar.federated_timeline": "Federated",
|
||||||
|
"tabs_bar.home": "Home",
|
||||||
|
"tabs_bar.local_timeline": "Local",
|
||||||
|
"tabs_bar.notifications": "Notifications",
|
||||||
|
"tabs_bar.search": "Search",
|
||||||
|
"time_remaining.days": "{number, plural, one {# day} other {# days}} left",
|
||||||
|
"time_remaining.hours": "{number, plural, one {# hour} other {# hours}} left",
|
||||||
|
"time_remaining.minutes": "{number, plural, one {# minute} other {# minutes}} left",
|
||||||
|
"time_remaining.moments": "Moments remaining",
|
||||||
|
"time_remaining.seconds": "{number, plural, one {# second} other {# seconds}} left",
|
||||||
|
"trends.count_by_accounts": "{count} {rawCount, plural, one {person} other {people}} talking",
|
||||||
|
"ui.beforeunload": "Your draft will be lost if you leave Mastodon.",
|
||||||
|
"upload_area.title": "Drag & drop to upload",
|
||||||
|
"upload_button.label": "Add media (JPEG, PNG, GIF, WebM, MP4, MOV)",
|
||||||
|
"upload_error.limit": "File upload limit exceeded.",
|
||||||
|
"upload_error.poll": "File upload not allowed with polls.",
|
||||||
|
"upload_form.description": "Describe for the visually impaired",
|
||||||
|
"upload_form.focus": "Crop",
|
||||||
|
"upload_form.undo": "Delete",
|
||||||
|
"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"
|
||||||
|
}
|
@ -244,11 +244,11 @@
|
|||||||
"navigation_bar.lists": "Ցանկեր",
|
"navigation_bar.lists": "Ցանկեր",
|
||||||
"navigation_bar.logout": "Դուրս գալ",
|
"navigation_bar.logout": "Դուրս գալ",
|
||||||
"navigation_bar.mutes": "Լռեցրած օգտատերեր",
|
"navigation_bar.mutes": "Լռեցրած օգտատերեր",
|
||||||
"navigation_bar.personal": "Personal",
|
"navigation_bar.personal": "Անձնական",
|
||||||
"navigation_bar.pins": "Ամրացված թթեր",
|
"navigation_bar.pins": "Ամրացված թթեր",
|
||||||
"navigation_bar.preferences": "Նախապատվություններ",
|
"navigation_bar.preferences": "Նախապատվություններ",
|
||||||
"navigation_bar.public_timeline": "Դաշնային հոսք",
|
"navigation_bar.public_timeline": "Դաշնային հոսք",
|
||||||
"navigation_bar.security": "Security",
|
"navigation_bar.security": "Անվտանգություն",
|
||||||
"notification.favourite": "{name} հավանեց թութդ",
|
"notification.favourite": "{name} հավանեց թութդ",
|
||||||
"notification.follow": "{name} սկսեց հետեւել քեզ",
|
"notification.follow": "{name} սկսեց հետեւել քեզ",
|
||||||
"notification.mention": "{name} նշեց քեզ",
|
"notification.mention": "{name} նշեց քեզ",
|
||||||
@ -314,7 +314,7 @@
|
|||||||
"search_results.accounts": "People",
|
"search_results.accounts": "People",
|
||||||
"search_results.hashtags": "Hashtags",
|
"search_results.hashtags": "Hashtags",
|
||||||
"search_results.statuses": "Toots",
|
"search_results.statuses": "Toots",
|
||||||
"search_results.total": "{count, number} {count, plural, one {result} other {results}}",
|
"search_results.total": "{count, number} {count, plural, one {արդյունք} other {արդյունք}}",
|
||||||
"status.admin_account": "Open moderation interface for @{name}",
|
"status.admin_account": "Open moderation interface for @{name}",
|
||||||
"status.admin_status": "Open this status in the moderation interface",
|
"status.admin_status": "Open this status in the moderation interface",
|
||||||
"status.block": "Արգելափակել @{name}֊ին",
|
"status.block": "Արգելափակել @{name}֊ին",
|
||||||
|
@ -11,7 +11,7 @@
|
|||||||
"account.follow": "Volgen",
|
"account.follow": "Volgen",
|
||||||
"account.followers": "Volgers",
|
"account.followers": "Volgers",
|
||||||
"account.followers.empty": "Niemand volgt nog deze gebruiker.",
|
"account.followers.empty": "Niemand volgt nog deze gebruiker.",
|
||||||
"account.follows": "Volgt",
|
"account.follows": "Volgend",
|
||||||
"account.follows.empty": "Deze gebruiker volgt nog niemand.",
|
"account.follows.empty": "Deze gebruiker volgt nog niemand.",
|
||||||
"account.follows_you": "Volgt jou",
|
"account.follows_you": "Volgt jou",
|
||||||
"account.hide_reblogs": "Verberg boosts van @{name}",
|
"account.hide_reblogs": "Verberg boosts van @{name}",
|
||||||
|
@ -117,7 +117,7 @@
|
|||||||
"emoji_button.symbols": "Símbolos",
|
"emoji_button.symbols": "Símbolos",
|
||||||
"emoji_button.travel": "Viagens & Lugares",
|
"emoji_button.travel": "Viagens & Lugares",
|
||||||
"empty_column.account_timeline": "Não há toots aqui!",
|
"empty_column.account_timeline": "Não há toots aqui!",
|
||||||
"empty_column.account_unavailable": "Profile unavailable",
|
"empty_column.account_unavailable": "Perfil indisponível",
|
||||||
"empty_column.blocks": "Você ainda não bloqueou nenhum usuário.",
|
"empty_column.blocks": "Você ainda não bloqueou nenhum usuário.",
|
||||||
"empty_column.community": "A timeline local está vazia. Escreva algo publicamente para começar!",
|
"empty_column.community": "A timeline local está vazia. Escreva algo publicamente para começar!",
|
||||||
"empty_column.direct": "Você não tem nenhuma mensagem direta ainda. Quando você enviar ou receber uma, as mensagens aparecerão por aqui.",
|
"empty_column.direct": "Você não tem nenhuma mensagem direta ainda. Quando você enviar ou receber uma, as mensagens aparecerão por aqui.",
|
||||||
|
@ -118,7 +118,6 @@
|
|||||||
"emoji_button.travel": "Путешествия",
|
"emoji_button.travel": "Путешествия",
|
||||||
"empty_column.account_timeline": "Статусов нет!",
|
"empty_column.account_timeline": "Статусов нет!",
|
||||||
"empty_column.account_unavailable": "Профиль недоступен",
|
"empty_column.account_unavailable": "Профиль недоступен",
|
||||||
"empty_column.account_timeline_blocked": "Вы заблокированы",
|
|
||||||
"empty_column.blocks": "Вы ещё никого не заблокировали.",
|
"empty_column.blocks": "Вы ещё никого не заблокировали.",
|
||||||
"empty_column.community": "Локальная лента пуста. Напишите что-нибудь, чтобы разогреть народ!",
|
"empty_column.community": "Локальная лента пуста. Напишите что-нибудь, чтобы разогреть народ!",
|
||||||
"empty_column.direct": "У Вас пока нет личных сообщений. Когда Вы начнёте их отправлять или получать, они появятся здесь.",
|
"empty_column.direct": "У Вас пока нет личных сообщений. Когда Вы начнёте их отправлять или получать, они появятся здесь.",
|
||||||
|
2
app/javascript/mastodon/locales/whitelist_hi.json
Normal file
2
app/javascript/mastodon/locales/whitelist_hi.json
Normal file
@ -0,0 +1,2 @@
|
|||||||
|
[
|
||||||
|
]
|
@ -173,6 +173,21 @@ function main() {
|
|||||||
avatar.src = url;
|
avatar.src = url;
|
||||||
});
|
});
|
||||||
|
|
||||||
|
const getProfileAvatarAnimationHandler = (swapTo) => {
|
||||||
|
//animate avatar gifs on the profile page when moused over
|
||||||
|
return ({ target }) => {
|
||||||
|
const swapSrc = target.getAttribute(swapTo);
|
||||||
|
//only change the img source if autoplay is off and the image src is actually different
|
||||||
|
if(target.getAttribute('data-autoplay') === 'false' && target.src !== swapSrc) {
|
||||||
|
target.src = swapSrc;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
delegate(document, 'img#profile_page_avatar', 'mouseover', getProfileAvatarAnimationHandler('data-original'));
|
||||||
|
|
||||||
|
delegate(document, 'img#profile_page_avatar', 'mouseout', getProfileAvatarAnimationHandler('data-static'));
|
||||||
|
|
||||||
delegate(document, '#account_header', 'change', ({ target }) => {
|
delegate(document, '#account_header', 'change', ({ target }) => {
|
||||||
const header = document.querySelector('.card .card__img img');
|
const header = document.querySelector('.card .card__img img');
|
||||||
const [file] = target.files || [];
|
const [file] = target.files || [];
|
||||||
|
@ -10,7 +10,7 @@
|
|||||||
|
|
||||||
@font-face {
|
@font-face {
|
||||||
font-family: 'mastodon-font-display';
|
font-family: 'mastodon-font-display';
|
||||||
src: local('Montserrat'),
|
src: local('Montserrat Medium'),
|
||||||
url('../fonts/montserrat/Montserrat-Medium.ttf') format('truetype');
|
url('../fonts/montserrat/Montserrat-Medium.ttf') format('truetype');
|
||||||
font-weight: 500;
|
font-weight: 500;
|
||||||
font-style: normal;
|
font-style: normal;
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
@font-face {
|
@font-face {
|
||||||
font-family: 'mastodon-font-sans-serif';
|
font-family: 'mastodon-font-sans-serif';
|
||||||
src: local('Roboto'),
|
src: local('Roboto Italic'),
|
||||||
url('../fonts/roboto/roboto-italic-webfont.woff2') format('woff2'),
|
url('../fonts/roboto/roboto-italic-webfont.woff2') format('woff2'),
|
||||||
url('../fonts/roboto/roboto-italic-webfont.woff') format('woff'),
|
url('../fonts/roboto/roboto-italic-webfont.woff') format('woff'),
|
||||||
url('../fonts/roboto/roboto-italic-webfont.ttf') format('truetype'),
|
url('../fonts/roboto/roboto-italic-webfont.ttf') format('truetype'),
|
||||||
@ -11,7 +11,7 @@
|
|||||||
|
|
||||||
@font-face {
|
@font-face {
|
||||||
font-family: 'mastodon-font-sans-serif';
|
font-family: 'mastodon-font-sans-serif';
|
||||||
src: local('Roboto'),
|
src: local('Roboto Bold'),
|
||||||
url('../fonts/roboto/roboto-bold-webfont.woff2') format('woff2'),
|
url('../fonts/roboto/roboto-bold-webfont.woff2') format('woff2'),
|
||||||
url('../fonts/roboto/roboto-bold-webfont.woff') format('woff'),
|
url('../fonts/roboto/roboto-bold-webfont.woff') format('woff'),
|
||||||
url('../fonts/roboto/roboto-bold-webfont.ttf') format('truetype'),
|
url('../fonts/roboto/roboto-bold-webfont.ttf') format('truetype'),
|
||||||
@ -22,7 +22,7 @@
|
|||||||
|
|
||||||
@font-face {
|
@font-face {
|
||||||
font-family: 'mastodon-font-sans-serif';
|
font-family: 'mastodon-font-sans-serif';
|
||||||
src: local('Roboto'),
|
src: local('Roboto Medium'),
|
||||||
url('../fonts/roboto/roboto-medium-webfont.woff2') format('woff2'),
|
url('../fonts/roboto/roboto-medium-webfont.woff2') format('woff2'),
|
||||||
url('../fonts/roboto/roboto-medium-webfont.woff') format('woff'),
|
url('../fonts/roboto/roboto-medium-webfont.woff') format('woff'),
|
||||||
url('../fonts/roboto/roboto-medium-webfont.ttf') format('truetype'),
|
url('../fonts/roboto/roboto-medium-webfont.ttf') format('truetype'),
|
||||||
|
@ -50,6 +50,7 @@ $content-width: 840px;
|
|||||||
color: $darker-text-color;
|
color: $darker-text-color;
|
||||||
text-decoration: none;
|
text-decoration: none;
|
||||||
transition: all 200ms linear;
|
transition: all 200ms linear;
|
||||||
|
transition-property: color, background-color;
|
||||||
border-radius: 4px 0 0 4px;
|
border-radius: 4px 0 0 4px;
|
||||||
|
|
||||||
i.fa {
|
i.fa {
|
||||||
@ -60,6 +61,7 @@ $content-width: 840px;
|
|||||||
color: $primary-text-color;
|
color: $primary-text-color;
|
||||||
background-color: darken($ui-base-color, 5%);
|
background-color: darken($ui-base-color, 5%);
|
||||||
transition: all 100ms linear;
|
transition: all 100ms linear;
|
||||||
|
transition-property: color, background-color;
|
||||||
}
|
}
|
||||||
|
|
||||||
&.selected {
|
&.selected {
|
||||||
|
@ -264,6 +264,16 @@
|
|||||||
.compose-form {
|
.compose-form {
|
||||||
padding: 10px;
|
padding: 10px;
|
||||||
|
|
||||||
|
&__sensitive-button {
|
||||||
|
padding: 10px;
|
||||||
|
padding-top: 0;
|
||||||
|
|
||||||
|
.icon-button {
|
||||||
|
font-size: 14px;
|
||||||
|
font-weight: 500;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
.compose-form__warning {
|
.compose-form__warning {
|
||||||
color: $inverted-text-color;
|
color: $inverted-text-color;
|
||||||
margin-bottom: 10px;
|
margin-bottom: 10px;
|
||||||
@ -1962,6 +1972,7 @@ a.account__display-name {
|
|||||||
font-weight: 500;
|
font-weight: 500;
|
||||||
border-bottom: 2px solid lighten($ui-base-color, 8%);
|
border-bottom: 2px solid lighten($ui-base-color, 8%);
|
||||||
transition: all 50ms linear;
|
transition: all 50ms linear;
|
||||||
|
transition-property: border-bottom, background, color;
|
||||||
|
|
||||||
.fa {
|
.fa {
|
||||||
font-weight: 400;
|
font-weight: 400;
|
||||||
@ -2127,7 +2138,7 @@ a.account__display-name {
|
|||||||
padding: 0;
|
padding: 0;
|
||||||
border-radius: 30px;
|
border-radius: 30px;
|
||||||
background-color: $ui-base-color;
|
background-color: $ui-base-color;
|
||||||
transition: all 0.2s ease;
|
transition: background-color 0.2s ease;
|
||||||
}
|
}
|
||||||
|
|
||||||
.react-toggle:hover:not(.react-toggle--disabled) .react-toggle-track {
|
.react-toggle:hover:not(.react-toggle--disabled) .react-toggle-track {
|
||||||
@ -2180,7 +2191,6 @@ a.account__display-name {
|
|||||||
}
|
}
|
||||||
|
|
||||||
.react-toggle-thumb {
|
.react-toggle-thumb {
|
||||||
transition: all 0.5s cubic-bezier(0.23, 1, 0.32, 1) 0ms;
|
|
||||||
position: absolute;
|
position: absolute;
|
||||||
top: 1px;
|
top: 1px;
|
||||||
left: 1px;
|
left: 1px;
|
||||||
@ -2191,6 +2201,7 @@ a.account__display-name {
|
|||||||
background-color: darken($simple-background-color, 2%);
|
background-color: darken($simple-background-color, 2%);
|
||||||
box-sizing: border-box;
|
box-sizing: border-box;
|
||||||
transition: all 0.25s ease;
|
transition: all 0.25s ease;
|
||||||
|
transition-property: border-color, left;
|
||||||
}
|
}
|
||||||
|
|
||||||
.react-toggle--checked .react-toggle-thumb {
|
.react-toggle--checked .react-toggle-thumb {
|
||||||
@ -2412,7 +2423,7 @@ a.account__display-name {
|
|||||||
|
|
||||||
& > div {
|
& > div {
|
||||||
background: rgba($base-shadow-color, 0.6);
|
background: rgba($base-shadow-color, 0.6);
|
||||||
border-radius: 4px;
|
border-radius: 8px;
|
||||||
padding: 12px 9px;
|
padding: 12px 9px;
|
||||||
flex: 0 0 auto;
|
flex: 0 0 auto;
|
||||||
display: flex;
|
display: flex;
|
||||||
@ -2423,19 +2434,18 @@ a.account__display-name {
|
|||||||
button,
|
button,
|
||||||
a {
|
a {
|
||||||
display: inline;
|
display: inline;
|
||||||
color: $primary-text-color;
|
color: $secondary-text-color;
|
||||||
background: transparent;
|
background: transparent;
|
||||||
border: 0;
|
border: 0;
|
||||||
padding: 0 5px;
|
padding: 0 8px;
|
||||||
text-decoration: none;
|
text-decoration: none;
|
||||||
opacity: 0.6;
|
|
||||||
font-size: 18px;
|
font-size: 18px;
|
||||||
line-height: 18px;
|
line-height: 18px;
|
||||||
|
|
||||||
&:hover,
|
&:hover,
|
||||||
&:active,
|
&:active,
|
||||||
&:focus {
|
&:focus {
|
||||||
opacity: 1;
|
color: $primary-text-color;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -2932,15 +2942,49 @@ a.status-card.compact:hover {
|
|||||||
}
|
}
|
||||||
|
|
||||||
.spoiler-button {
|
.spoiler-button {
|
||||||
display: none;
|
top: 0;
|
||||||
left: 4px;
|
left: 0;
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
position: absolute;
|
position: absolute;
|
||||||
text-shadow: 0 1px 1px $base-shadow-color, 1px 0 1px $base-shadow-color;
|
|
||||||
top: 4px;
|
|
||||||
z-index: 100;
|
z-index: 100;
|
||||||
|
|
||||||
&.spoiler-button--visible {
|
&--minified {
|
||||||
display: block;
|
display: block;
|
||||||
|
left: 4px;
|
||||||
|
top: 4px;
|
||||||
|
width: auto;
|
||||||
|
height: auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
&--hidden {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
&__overlay {
|
||||||
|
display: block;
|
||||||
|
background: transparent;
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
border: 0;
|
||||||
|
|
||||||
|
&__label {
|
||||||
|
display: inline-block;
|
||||||
|
background: rgba($base-overlay-background, 0.5);
|
||||||
|
border-radius: 8px;
|
||||||
|
padding: 8px 12px;
|
||||||
|
color: $primary-text-color;
|
||||||
|
font-weight: 500;
|
||||||
|
font-size: 14px;
|
||||||
|
}
|
||||||
|
|
||||||
|
&:hover,
|
||||||
|
&:focus,
|
||||||
|
&:active {
|
||||||
|
.spoiler-button__overlay__label {
|
||||||
|
background: rgba($base-overlay-background, 0.8);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -3509,6 +3553,7 @@ a.status-card.compact:hover {
|
|||||||
display: inline-block;
|
display: inline-block;
|
||||||
opacity: 0;
|
opacity: 0;
|
||||||
transition: all 100ms linear;
|
transition: all 100ms linear;
|
||||||
|
transition-property: transform, opacity;
|
||||||
font-size: 18px;
|
font-size: 18px;
|
||||||
width: 18px;
|
width: 18px;
|
||||||
height: 18px;
|
height: 18px;
|
||||||
@ -3728,6 +3773,31 @@ a.status-card.compact:hover {
|
|||||||
pointer-events: none;
|
pointer-events: none;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.media-modal__meta {
|
||||||
|
text-align: center;
|
||||||
|
position: absolute;
|
||||||
|
left: 0;
|
||||||
|
bottom: 20px;
|
||||||
|
width: 100%;
|
||||||
|
pointer-events: none;
|
||||||
|
|
||||||
|
&--shifted {
|
||||||
|
bottom: 62px;
|
||||||
|
}
|
||||||
|
|
||||||
|
a {
|
||||||
|
text-decoration: none;
|
||||||
|
font-weight: 500;
|
||||||
|
color: $ui-secondary-color;
|
||||||
|
|
||||||
|
&:hover,
|
||||||
|
&:focus,
|
||||||
|
&:active {
|
||||||
|
text-decoration: underline;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
.media-modal__page-dot {
|
.media-modal__page-dot {
|
||||||
display: inline-block;
|
display: inline-block;
|
||||||
}
|
}
|
||||||
@ -3961,14 +4031,6 @@ a.status-card.compact:hover {
|
|||||||
font-size: 14px;
|
font-size: 14px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.confirmation-modal {
|
|
||||||
max-width: 85vw;
|
|
||||||
|
|
||||||
@media screen and (min-width: 480px) {
|
|
||||||
max-width: 380px;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.mute-modal {
|
.mute-modal {
|
||||||
line-height: 24px;
|
line-height: 24px;
|
||||||
}
|
}
|
||||||
@ -4093,6 +4155,11 @@ a.status-card.compact:hover {
|
|||||||
ul {
|
ul {
|
||||||
overflow-y: auto;
|
overflow-y: auto;
|
||||||
flex-shrink: 0;
|
flex-shrink: 0;
|
||||||
|
max-height: 80vh;
|
||||||
|
|
||||||
|
&.with-status {
|
||||||
|
max-height: calc(80vh - 75px);
|
||||||
|
}
|
||||||
|
|
||||||
li:empty {
|
li:empty {
|
||||||
margin: 0;
|
margin: 0;
|
||||||
@ -4147,6 +4214,10 @@ a.status-card.compact:hover {
|
|||||||
color: darken($lighter-text-color, 4%);
|
color: darken($lighter-text-color, 4%);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.confirmation-modal__secondary-button {
|
||||||
|
flex-shrink: 1;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.confirmation-modal__container,
|
.confirmation-modal__container,
|
||||||
@ -4199,6 +4270,7 @@ a.status-card.compact:hover {
|
|||||||
pointer-events: none;
|
pointer-events: none;
|
||||||
opacity: 0.9;
|
opacity: 0.9;
|
||||||
transition: opacity 0.1s ease;
|
transition: opacity 0.1s ease;
|
||||||
|
line-height: 18px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.media-gallery__gifv {
|
.media-gallery__gifv {
|
||||||
@ -4312,6 +4384,8 @@ a.status-card.compact:hover {
|
|||||||
text-decoration: none;
|
text-decoration: none;
|
||||||
color: $secondary-text-color;
|
color: $secondary-text-color;
|
||||||
line-height: 0;
|
line-height: 0;
|
||||||
|
position: relative;
|
||||||
|
z-index: 1;
|
||||||
|
|
||||||
&,
|
&,
|
||||||
img {
|
img {
|
||||||
@ -4324,6 +4398,21 @@ a.status-card.compact:hover {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.media-gallery__preview {
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
object-fit: cover;
|
||||||
|
position: absolute;
|
||||||
|
top: 0;
|
||||||
|
left: 0;
|
||||||
|
z-index: 0;
|
||||||
|
background: $base-overlay-background;
|
||||||
|
|
||||||
|
&--hidden {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
.media-gallery__gifv {
|
.media-gallery__gifv {
|
||||||
height: 100%;
|
height: 100%;
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
@ -4619,6 +4708,23 @@ a.status-card.compact:hover {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
&__link {
|
||||||
|
padding: 2px 10px;
|
||||||
|
|
||||||
|
a {
|
||||||
|
text-decoration: none;
|
||||||
|
font-size: 14px;
|
||||||
|
font-weight: 500;
|
||||||
|
color: $white;
|
||||||
|
|
||||||
|
&:hover,
|
||||||
|
&:active,
|
||||||
|
&:focus {
|
||||||
|
text-decoration: underline;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
&__seek {
|
&__seek {
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
height: 24px;
|
height: 24px;
|
||||||
@ -4711,62 +4817,18 @@ a.status-card.compact:hover {
|
|||||||
|
|
||||||
.account-gallery__container {
|
.account-gallery__container {
|
||||||
display: flex;
|
display: flex;
|
||||||
justify-content: center;
|
|
||||||
flex-wrap: wrap;
|
flex-wrap: wrap;
|
||||||
padding: 2px;
|
padding: 4px 2px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.account-gallery__item {
|
.account-gallery__item {
|
||||||
flex-grow: 1;
|
border: none;
|
||||||
width: 50%;
|
box-sizing: border-box;
|
||||||
overflow: hidden;
|
display: block;
|
||||||
position: relative;
|
position: relative;
|
||||||
|
border-radius: 4px;
|
||||||
&::before {
|
overflow: hidden;
|
||||||
content: "";
|
margin: 2px;
|
||||||
display: block;
|
|
||||||
padding-top: 100%;
|
|
||||||
}
|
|
||||||
|
|
||||||
a {
|
|
||||||
display: block;
|
|
||||||
width: calc(100% - 4px);
|
|
||||||
height: calc(100% - 4px);
|
|
||||||
margin: 2px;
|
|
||||||
top: 0;
|
|
||||||
left: 0;
|
|
||||||
background-color: $base-overlay-background;
|
|
||||||
background-size: cover;
|
|
||||||
background-position: center;
|
|
||||||
position: absolute;
|
|
||||||
color: $darker-text-color;
|
|
||||||
text-decoration: none;
|
|
||||||
border-radius: 4px;
|
|
||||||
|
|
||||||
&:hover,
|
|
||||||
&:active,
|
|
||||||
&:focus {
|
|
||||||
outline: 0;
|
|
||||||
color: $secondary-text-color;
|
|
||||||
|
|
||||||
&::before {
|
|
||||||
content: "";
|
|
||||||
display: block;
|
|
||||||
width: 100%;
|
|
||||||
height: 100%;
|
|
||||||
background: rgba($base-overlay-background, 0.3);
|
|
||||||
border-radius: 4px;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
&__icons {
|
|
||||||
position: absolute;
|
|
||||||
top: 50%;
|
|
||||||
left: 50%;
|
|
||||||
transform: translate(-50%, -50%);
|
|
||||||
font-size: 24px;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.notification__filter-bar,
|
.notification__filter-bar,
|
||||||
|
@ -533,6 +533,17 @@ code {
|
|||||||
color: $error-value-color;
|
color: $error-value-color;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
a {
|
||||||
|
display: inline-block;
|
||||||
|
color: $darker-text-color;
|
||||||
|
text-decoration: none;
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
color: $primary-text-color;
|
||||||
|
text-decoration: underline;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
p {
|
p {
|
||||||
margin-bottom: 15px;
|
margin-bottom: 15px;
|
||||||
}
|
}
|
||||||
|
@ -4,7 +4,6 @@
|
|||||||
|
|
||||||
&__img {
|
&__img {
|
||||||
width: 100%;
|
width: 100%;
|
||||||
height: 167px;
|
|
||||||
position: relative;
|
position: relative;
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
border-radius: 4px 4px 0 0;
|
border-radius: 4px 4px 0 0;
|
||||||
|
@ -194,7 +194,7 @@ class ActivityPub::Activity::Create < ActivityPub::Activity
|
|||||||
next if attachment['url'].blank?
|
next if attachment['url'].blank?
|
||||||
|
|
||||||
href = Addressable::URI.parse(attachment['url']).normalize.to_s
|
href = Addressable::URI.parse(attachment['url']).normalize.to_s
|
||||||
media_attachment = MediaAttachment.create(account: @account, remote_url: href, description: attachment['name'].presence, focus: attachment['focalPoint'])
|
media_attachment = MediaAttachment.create(account: @account, remote_url: href, description: attachment['name'].presence, focus: attachment['focalPoint'], blurhash: supported_blurhash?(attachment['blurhash']) ? attachment['blurhash'] : nil)
|
||||||
media_attachments << media_attachment
|
media_attachments << media_attachment
|
||||||
|
|
||||||
next if unsupported_media_type?(attachment['mediaType']) || skip_download?
|
next if unsupported_media_type?(attachment['mediaType']) || skip_download?
|
||||||
@ -369,6 +369,11 @@ class ActivityPub::Activity::Create < ActivityPub::Activity
|
|||||||
mime_type.present? && !(MediaAttachment::IMAGE_MIME_TYPES + MediaAttachment::VIDEO_MIME_TYPES).include?(mime_type)
|
mime_type.present? && !(MediaAttachment::IMAGE_MIME_TYPES + MediaAttachment::VIDEO_MIME_TYPES).include?(mime_type)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def supported_blurhash?(blurhash)
|
||||||
|
components = blurhash.blank? ? nil : Blurhash.components(blurhash)
|
||||||
|
components.present? && components.none? { |comp| comp > 5 }
|
||||||
|
end
|
||||||
|
|
||||||
def skip_download?
|
def skip_download?
|
||||||
return @skip_download if defined?(@skip_download)
|
return @skip_download if defined?(@skip_download)
|
||||||
@skip_download ||= DomainBlock.find_by(domain: @account.domain)&.reject_media?
|
@skip_download ||= DomainBlock.find_by(domain: @account.domain)&.reject_media?
|
||||||
|
@ -19,6 +19,7 @@ class ActivityPub::Adapter < ActiveModelSerializers::Adapter::Base
|
|||||||
conversation: { 'ostatus' => 'http://ostatus.org#', 'inReplyToAtomUri' => 'ostatus:inReplyToAtomUri', 'conversation' => 'ostatus:conversation' },
|
conversation: { 'ostatus' => 'http://ostatus.org#', 'inReplyToAtomUri' => 'ostatus:inReplyToAtomUri', 'conversation' => 'ostatus:conversation' },
|
||||||
focal_point: { 'toot' => 'http://joinmastodon.org/ns#', 'focalPoint' => { '@container' => '@list', '@id' => 'toot:focalPoint' } },
|
focal_point: { 'toot' => 'http://joinmastodon.org/ns#', 'focalPoint' => { '@container' => '@list', '@id' => 'toot:focalPoint' } },
|
||||||
identity_proof: { 'toot' => 'http://joinmastodon.org/ns#', 'IdentityProof' => 'toot:IdentityProof' },
|
identity_proof: { 'toot' => 'http://joinmastodon.org/ns#', 'IdentityProof' => 'toot:IdentityProof' },
|
||||||
|
blurhash: { 'toot' => 'http://joinmastodon.org/ns#', 'blurhash' => 'toot:blurhash' },
|
||||||
}.freeze
|
}.freeze
|
||||||
|
|
||||||
def self.default_key_transform
|
def self.default_key_transform
|
||||||
|
@ -2,7 +2,7 @@
|
|||||||
|
|
||||||
class ProofProvider::Keybase
|
class ProofProvider::Keybase
|
||||||
BASE_URL = ENV.fetch('KEYBASE_BASE_URL', 'https://keybase.io')
|
BASE_URL = ENV.fetch('KEYBASE_BASE_URL', 'https://keybase.io')
|
||||||
DOMAIN = ENV.fetch('KEYBASE_DOMAIN', Rails.configuration.x.local_domain)
|
DOMAIN = ENV.fetch('KEYBASE_DOMAIN', Rails.configuration.x.web_domain)
|
||||||
|
|
||||||
class Error < StandardError; end
|
class Error < StandardError; end
|
||||||
|
|
||||||
|
@ -6,6 +6,7 @@ module LdapAuthenticable
|
|||||||
def ldap_setup(_attributes)
|
def ldap_setup(_attributes)
|
||||||
self.confirmed_at = Time.now.utc
|
self.confirmed_at = Time.now.utc
|
||||||
self.admin = false
|
self.admin = false
|
||||||
|
self.external = true
|
||||||
|
|
||||||
save!
|
save!
|
||||||
end
|
end
|
||||||
|
@ -66,6 +66,7 @@ module Omniauthable
|
|||||||
email: email || "#{TEMP_EMAIL_PREFIX}-#{auth.uid}-#{auth.provider}.com",
|
email: email || "#{TEMP_EMAIL_PREFIX}-#{auth.uid}-#{auth.provider}.com",
|
||||||
password: Devise.friendly_token[0, 20],
|
password: Devise.friendly_token[0, 20],
|
||||||
agreement: true,
|
agreement: true,
|
||||||
|
external: true,
|
||||||
account_attributes: {
|
account_attributes: {
|
||||||
username: ensure_unique_username(auth.uid),
|
username: ensure_unique_username(auth.uid),
|
||||||
display_name: display_name,
|
display_name: display_name,
|
||||||
|
@ -34,6 +34,7 @@ module PamAuthenticable
|
|||||||
self.confirmed_at = Time.now.utc
|
self.confirmed_at = Time.now.utc
|
||||||
self.admin = false
|
self.admin = false
|
||||||
self.account = account
|
self.account = account
|
||||||
|
self.external = true
|
||||||
|
|
||||||
account.destroy! unless save
|
account.destroy! unless save
|
||||||
end
|
end
|
||||||
|
@ -29,4 +29,11 @@ class DomainBlock < ApplicationRecord
|
|||||||
def self.blocked?(domain)
|
def self.blocked?(domain)
|
||||||
where(domain: domain, severity: :suspend).exists?
|
where(domain: domain, severity: :suspend).exists?
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def stricter_than?(other_block)
|
||||||
|
return true if suspend?
|
||||||
|
return false if other_block.suspend? && (silence? || noop?)
|
||||||
|
return false if other_block.silence? && noop?
|
||||||
|
(reject_media || !other_block.reject_media) && (reject_reports || !other_block.reject_reports)
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
@ -18,6 +18,7 @@
|
|||||||
# account_id :bigint(8)
|
# account_id :bigint(8)
|
||||||
# description :text
|
# description :text
|
||||||
# scheduled_status_id :bigint(8)
|
# scheduled_status_id :bigint(8)
|
||||||
|
# blurhash :string
|
||||||
#
|
#
|
||||||
|
|
||||||
class MediaAttachment < ApplicationRecord
|
class MediaAttachment < ApplicationRecord
|
||||||
@ -32,6 +33,11 @@ class MediaAttachment < ApplicationRecord
|
|||||||
VIDEO_MIME_TYPES = ['video/webm', 'video/mp4', 'video/quicktime'].freeze
|
VIDEO_MIME_TYPES = ['video/webm', 'video/mp4', 'video/quicktime'].freeze
|
||||||
VIDEO_CONVERTIBLE_MIME_TYPES = ['video/webm', 'video/quicktime'].freeze
|
VIDEO_CONVERTIBLE_MIME_TYPES = ['video/webm', 'video/quicktime'].freeze
|
||||||
|
|
||||||
|
BLURHASH_OPTIONS = {
|
||||||
|
x_comp: 4,
|
||||||
|
y_comp: 4,
|
||||||
|
}.freeze
|
||||||
|
|
||||||
IMAGE_STYLES = {
|
IMAGE_STYLES = {
|
||||||
original: {
|
original: {
|
||||||
pixels: 1_638_400, # 1280x1280px
|
pixels: 1_638_400, # 1280x1280px
|
||||||
@ -41,6 +47,7 @@ class MediaAttachment < ApplicationRecord
|
|||||||
small: {
|
small: {
|
||||||
pixels: 160_000, # 400x400px
|
pixels: 160_000, # 400x400px
|
||||||
file_geometry_parser: FastGeometryParser,
|
file_geometry_parser: FastGeometryParser,
|
||||||
|
blurhash: BLURHASH_OPTIONS,
|
||||||
},
|
},
|
||||||
}.freeze
|
}.freeze
|
||||||
|
|
||||||
@ -53,6 +60,8 @@ class MediaAttachment < ApplicationRecord
|
|||||||
},
|
},
|
||||||
format: 'png',
|
format: 'png',
|
||||||
time: 0,
|
time: 0,
|
||||||
|
file_geometry_parser: FastGeometryParser,
|
||||||
|
blurhash: BLURHASH_OPTIONS,
|
||||||
},
|
},
|
||||||
}.freeze
|
}.freeze
|
||||||
|
|
||||||
@ -166,11 +175,11 @@ class MediaAttachment < ApplicationRecord
|
|||||||
|
|
||||||
def file_processors(f)
|
def file_processors(f)
|
||||||
if f.file_content_type == 'image/gif'
|
if f.file_content_type == 'image/gif'
|
||||||
[:gif_transcoder]
|
[:gif_transcoder, :blurhash_transcoder]
|
||||||
elsif VIDEO_MIME_TYPES.include? f.file_content_type
|
elsif VIDEO_MIME_TYPES.include? f.file_content_type
|
||||||
[:video_transcoder]
|
[:video_transcoder, :blurhash_transcoder]
|
||||||
else
|
else
|
||||||
[:lazy_thumbnail]
|
[:lazy_thumbnail, :blurhash_transcoder]
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
@ -78,7 +78,7 @@ class User < ApplicationRecord
|
|||||||
accepts_nested_attributes_for :invite_request, reject_if: ->(attributes) { attributes['text'].blank? }
|
accepts_nested_attributes_for :invite_request, reject_if: ->(attributes) { attributes['text'].blank? }
|
||||||
|
|
||||||
validates :locale, inclusion: I18n.available_locales.map(&:to_s), if: :locale?
|
validates :locale, inclusion: I18n.available_locales.map(&:to_s), if: :locale?
|
||||||
validates_with BlacklistedEmailValidator, if: :email_changed?
|
validates_with BlacklistedEmailValidator, on: :create
|
||||||
validates_with EmailMxValidator, if: :validate_email_dns?
|
validates_with EmailMxValidator, if: :validate_email_dns?
|
||||||
validates :agreement, acceptance: { allow_nil: false, accept: [true, 'true', '1'] }, on: :create
|
validates :agreement, acceptance: { allow_nil: false, accept: [true, 'true', '1'] }, on: :create
|
||||||
|
|
||||||
@ -107,13 +107,14 @@ class User < ApplicationRecord
|
|||||||
:expand_spoilers, :default_language, :aggregate_reblogs, :show_application, :default_federation, to: :settings, prefix: :setting, allow_nil: false
|
:expand_spoilers, :default_language, :aggregate_reblogs, :show_application, :default_federation, to: :settings, prefix: :setting, allow_nil: false
|
||||||
|
|
||||||
attr_reader :invite_code
|
attr_reader :invite_code
|
||||||
|
attr_writer :external
|
||||||
|
|
||||||
def confirmed?
|
def confirmed?
|
||||||
confirmed_at.present?
|
confirmed_at.present?
|
||||||
end
|
end
|
||||||
|
|
||||||
def invited?
|
def invited?
|
||||||
invite_id.present?
|
invite_id.present? && invite.valid_for_use?
|
||||||
end
|
end
|
||||||
|
|
||||||
def disable!
|
def disable!
|
||||||
@ -273,13 +274,17 @@ class User < ApplicationRecord
|
|||||||
private
|
private
|
||||||
|
|
||||||
def set_approved
|
def set_approved
|
||||||
self.approved = open_registrations? || invited?
|
self.approved = open_registrations? || invited? || external?
|
||||||
end
|
end
|
||||||
|
|
||||||
def open_registrations?
|
def open_registrations?
|
||||||
Setting.registrations_mode == 'open'
|
Setting.registrations_mode == 'open'
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def external?
|
||||||
|
!!@external
|
||||||
|
end
|
||||||
|
|
||||||
def sanitize_languages
|
def sanitize_languages
|
||||||
return if chosen_languages.nil?
|
return if chosen_languages.nil?
|
||||||
chosen_languages.reject!(&:blank?)
|
chosen_languages.reject!(&:blank?)
|
||||||
|
@ -2,7 +2,7 @@
|
|||||||
|
|
||||||
class ActivityPub::NoteSerializer < ActivityPub::Serializer
|
class ActivityPub::NoteSerializer < ActivityPub::Serializer
|
||||||
context_extensions :atom_uri, :conversation, :sensitive,
|
context_extensions :atom_uri, :conversation, :sensitive,
|
||||||
:hashtag, :emoji, :focal_point
|
:hashtag, :emoji, :focal_point, :blurhash
|
||||||
|
|
||||||
attributes :id, :type, :summary,
|
attributes :id, :type, :summary,
|
||||||
:in_reply_to, :published, :url,
|
:in_reply_to, :published, :url,
|
||||||
@ -153,7 +153,7 @@ class ActivityPub::NoteSerializer < ActivityPub::Serializer
|
|||||||
class MediaAttachmentSerializer < ActivityPub::Serializer
|
class MediaAttachmentSerializer < ActivityPub::Serializer
|
||||||
include RoutingHelper
|
include RoutingHelper
|
||||||
|
|
||||||
attributes :type, :media_type, :url, :name
|
attributes :type, :media_type, :url, :name, :blurhash
|
||||||
attribute :focal_point, if: :focal_point?
|
attribute :focal_point, if: :focal_point?
|
||||||
|
|
||||||
def type
|
def type
|
||||||
|
@ -14,6 +14,8 @@ class InitialStateSerializer < ActiveModel::Serializer
|
|||||||
domain: Rails.configuration.x.local_domain,
|
domain: Rails.configuration.x.local_domain,
|
||||||
admin: object.admin&.id&.to_s,
|
admin: object.admin&.id&.to_s,
|
||||||
search_enabled: Chewy.enabled?,
|
search_enabled: Chewy.enabled?,
|
||||||
|
repository: Mastodon::Version.repository,
|
||||||
|
source_url: Mastodon::Version.source_url,
|
||||||
version: Mastodon::Version.to_s,
|
version: Mastodon::Version.to_s,
|
||||||
invites_enabled: Setting.min_invite_role == 'user',
|
invites_enabled: Setting.min_invite_role == 'user',
|
||||||
mascot: instance_presenter.mascot&.file&.url,
|
mascot: instance_presenter.mascot&.file&.url,
|
||||||
|
@ -5,7 +5,7 @@ class REST::MediaAttachmentSerializer < ActiveModel::Serializer
|
|||||||
|
|
||||||
attributes :id, :type, :url, :preview_url,
|
attributes :id, :type, :url, :preview_url,
|
||||||
:remote_url, :text_url, :meta,
|
:remote_url, :text_url, :meta,
|
||||||
:description
|
:description, :blurhash
|
||||||
|
|
||||||
def id
|
def id
|
||||||
object.id.to_s
|
object.id.to_s
|
||||||
|
@ -6,6 +6,7 @@ class BlockService < BaseService
|
|||||||
|
|
||||||
UnfollowService.new.call(account, target_account) if account.following?(target_account)
|
UnfollowService.new.call(account, target_account) if account.following?(target_account)
|
||||||
UnfollowService.new.call(target_account, account) if target_account.following?(account)
|
UnfollowService.new.call(target_account, account) if target_account.following?(account)
|
||||||
|
RejectFollowService.new.call(account, target_account) if target_account.requested?(account)
|
||||||
|
|
||||||
block = account.block!(target_account)
|
block = account.block!(target_account)
|
||||||
|
|
||||||
|
@ -165,7 +165,7 @@ class FetchLinkCardService < BaseService
|
|||||||
end
|
end
|
||||||
|
|
||||||
def meta_property(page, property)
|
def meta_property(page, property)
|
||||||
page.at_xpath("//meta[@property=\"#{property}\"]")&.attribute('content')&.value || page.at_xpath("//meta[@name=\"#{property}\"]")&.attribute('content')&.value
|
page.at_xpath("//meta[contains(concat(' ', normalize-space(@property), ' '), ' #{property} ')]")&.attribute('content')&.value || page.at_xpath("//meta[@name=\"#{property}\"]")&.attribute('content')&.value
|
||||||
end
|
end
|
||||||
|
|
||||||
def lock_options
|
def lock_options
|
||||||
|
@ -2,7 +2,10 @@
|
|||||||
|
|
||||||
class BlacklistedEmailValidator < ActiveModel::Validator
|
class BlacklistedEmailValidator < ActiveModel::Validator
|
||||||
def validate(user)
|
def validate(user)
|
||||||
|
return if user.invited?
|
||||||
|
|
||||||
@email = user.email
|
@email = user.email
|
||||||
|
|
||||||
user.errors.add(:email, I18n.t('users.invalid_email')) if blocked_email?
|
user.errors.add(:email, I18n.t('users.invalid_email')) if blocked_email?
|
||||||
end
|
end
|
||||||
|
|
||||||
@ -13,7 +16,7 @@ class BlacklistedEmailValidator < ActiveModel::Validator
|
|||||||
end
|
end
|
||||||
|
|
||||||
def on_blacklist?
|
def on_blacklist?
|
||||||
return true if EmailDomainBlock.block?(@email)
|
return true if EmailDomainBlock.block?(@email)
|
||||||
return false if Rails.configuration.x.email_domains_blacklist.blank?
|
return false if Rails.configuration.x.email_domains_blacklist.blank?
|
||||||
|
|
||||||
domains = Rails.configuration.x.email_domains_blacklist.gsub('.', '\.')
|
domains = Rails.configuration.x.email_domains_blacklist.gsub('.', '\.')
|
||||||
|
@ -3,7 +3,7 @@
|
|||||||
= image_tag (current_account&.user&.setting_auto_play_gif ? account.header_original_url : account.header_static_url), class: 'parallax'
|
= image_tag (current_account&.user&.setting_auto_play_gif ? account.header_original_url : account.header_static_url), class: 'parallax'
|
||||||
.public-account-header__bar
|
.public-account-header__bar
|
||||||
= link_to short_account_url(account), class: 'avatar' do
|
= link_to short_account_url(account), class: 'avatar' do
|
||||||
= image_tag (current_account&.user&.setting_auto_play_gif ? account.avatar_original_url : account.avatar_static_url)
|
= image_tag (current_account&.user&.setting_auto_play_gif ? account.avatar_original_url : account.avatar_static_url), id: 'profile_page_avatar', data: {original: full_asset_url(account.avatar_original_url), static: full_asset_url(account.avatar_static_url), autoplay: current_account&.user&.setting_auto_play_gif}
|
||||||
.public-account-header__tabs
|
.public-account-header__tabs
|
||||||
.public-account-header__tabs__name
|
.public-account-header__tabs__name
|
||||||
%h1
|
%h1
|
||||||
|
@ -36,6 +36,6 @@
|
|||||||
= f.input :agreement, as: :boolean, wrapper: :with_label, label: t('auth.checkbox_agreement_html', rules_path: about_more_path, terms_path: terms_path)
|
= f.input :agreement, as: :boolean, wrapper: :with_label, label: t('auth.checkbox_agreement_html', rules_path: about_more_path, terms_path: terms_path)
|
||||||
|
|
||||||
.actions
|
.actions
|
||||||
= f.button :button, sign_up_message, type: :submit
|
= f.button :button, @invite.present? ? t('auth.register') : sign_up_message, type: :submit
|
||||||
|
|
||||||
.form-footer= render 'auth/shared/links'
|
.form-footer= render 'auth/shared/links'
|
||||||
|
@ -28,7 +28,7 @@
|
|||||||
- elsif !status.media_attachments.empty?
|
- elsif !status.media_attachments.empty?
|
||||||
- if status.media_attachments.first.video?
|
- if status.media_attachments.first.video?
|
||||||
- video = status.media_attachments.first
|
- video = status.media_attachments.first
|
||||||
= react_component :video, src: video.file.url(:original), preview: video.file.url(:small), sensitive: !current_account&.user&.show_all_media? && status.sensitive? || current_account&.user&.hide_all_media?, width: 670, height: 380, detailed: true, inline: true, alt: video.description do
|
= react_component :video, src: video.file.url(:original), preview: video.file.url(:small), blurhash: video.blurhash, sensitive: !current_account&.user&.show_all_media? && status.sensitive? || current_account&.user&.hide_all_media?, width: 670, height: 380, detailed: true, inline: true, alt: video.description do
|
||||||
= render partial: 'stream_entries/attachment_list', locals: { attachments: status.media_attachments }
|
= render partial: 'stream_entries/attachment_list', locals: { attachments: status.media_attachments }
|
||||||
- else
|
- else
|
||||||
= react_component :media_gallery, height: 380, sensitive: !current_account&.user&.show_all_media? && status.sensitive? || current_account&.user&.hide_all_media?, standalone: true, 'autoPlayGif': current_account&.user&.setting_auto_play_gif || autoplay, 'reduceMotion': current_account&.user&.setting_reduce_motion, media: status.media_attachments.map { |a| ActiveModelSerializers::SerializableResource.new(a, serializer: REST::MediaAttachmentSerializer).as_json } do
|
= react_component :media_gallery, height: 380, sensitive: !current_account&.user&.show_all_media? && status.sensitive? || current_account&.user&.hide_all_media?, standalone: true, 'autoPlayGif': current_account&.user&.setting_auto_play_gif || autoplay, 'reduceMotion': current_account&.user&.setting_reduce_motion, media: status.media_attachments.map { |a| ActiveModelSerializers::SerializableResource.new(a, serializer: REST::MediaAttachmentSerializer).as_json } do
|
||||||
|
@ -32,7 +32,7 @@
|
|||||||
- elsif !status.media_attachments.empty?
|
- elsif !status.media_attachments.empty?
|
||||||
- if status.media_attachments.first.video?
|
- if status.media_attachments.first.video?
|
||||||
- video = status.media_attachments.first
|
- video = status.media_attachments.first
|
||||||
= react_component :video, src: video.file.url(:original), preview: video.file.url(:small), sensitive: !current_account&.user&.show_all_media? && status.sensitive? || current_account&.user&.hide_all_media?, width: 610, height: 343, inline: true, alt: video.description do
|
= react_component :video, src: video.file.url(:original), preview: video.file.url(:small), blurhash: video.blurhash, sensitive: !current_account&.user&.show_all_media? && status.sensitive? || current_account&.user&.hide_all_media?, width: 610, height: 343, inline: true, alt: video.description do
|
||||||
= render partial: 'stream_entries/attachment_list', locals: { attachments: status.media_attachments }
|
= render partial: 'stream_entries/attachment_list', locals: { attachments: status.media_attachments }
|
||||||
- else
|
- else
|
||||||
= react_component :media_gallery, height: 343, sensitive: !current_account&.user&.show_all_media? && status.sensitive? || current_account&.user&.hide_all_media?, 'autoPlayGif': current_account&.user&.setting_auto_play_gif || autoplay, media: status.media_attachments.map { |a| ActiveModelSerializers::SerializableResource.new(a, serializer: REST::MediaAttachmentSerializer).as_json } do
|
= react_component :media_gallery, height: 343, sensitive: !current_account&.user&.show_all_media? && status.sensitive? || current_account&.user&.hide_all_media?, 'autoPlayGif': current_account&.user&.setting_auto_play_gif || autoplay, media: status.media_attachments.map { |a| ActiveModelSerializers::SerializableResource.new(a, serializer: REST::MediaAttachmentSerializer).as_json } do
|
||||||
|
@ -7,5 +7,7 @@ class ActivityPub::ProcessingWorker
|
|||||||
|
|
||||||
def perform(account_id, body, delivered_to_account_id = nil)
|
def perform(account_id, body, delivered_to_account_id = nil)
|
||||||
ActivityPub::ProcessCollectionService.new.call(body, Account.find(account_id), override_timestamps: true, delivered_to_account_id: delivered_to_account_id, delivery: true)
|
ActivityPub::ProcessCollectionService.new.call(body, Account.find(account_id), override_timestamps: true, delivered_to_account_id: delivered_to_account_id, delivery: true)
|
||||||
|
rescue ActiveRecord::RecordInvalid => e
|
||||||
|
Rails.logger.debug "Error processing incoming ActivityPub object: #{e}"
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
@ -1,4 +1,6 @@
|
|||||||
ActiveSupport::Notifications.subscribe('rack.attack') do |_name, _start, _finish, _request_id, req|
|
ActiveSupport::Notifications.subscribe(/rack_attack/) do |_name, _start, _finish, _request_id, payload|
|
||||||
|
req = payload[:request]
|
||||||
|
|
||||||
next unless [:throttle, :blacklist].include? req.env['rack.attack.match_type']
|
next unless [:throttle, :blacklist].include? req.env['rack.attack.match_type']
|
||||||
Rails.logger.info("Rate limit hit (#{req.env['rack.attack.match_type']}): #{req.ip} #{req.request_method} #{req.fullpath}")
|
Rails.logger.info("Rate limit hit (#{req.env['rack.attack.match_type']}): #{req.ip} #{req.request_method} #{req.fullpath}")
|
||||||
end
|
end
|
||||||
|
@ -1,3 +1,4 @@
|
|||||||
require 'stoplight'
|
require 'stoplight'
|
||||||
|
|
||||||
Stoplight::Light.default_data_store = Stoplight::DataStore::Redis.new(Redis.current)
|
Stoplight::Light.default_data_store = Stoplight::DataStore::Redis.new(Redis.current)
|
||||||
|
Stoplight::Light.default_notifiers = [Stoplight::Notifier::Logger.new(Rails.logger)]
|
||||||
|
@ -2,8 +2,9 @@
|
|||||||
pt-BR:
|
pt-BR:
|
||||||
activerecord:
|
activerecord:
|
||||||
attributes:
|
attributes:
|
||||||
status:
|
poll:
|
||||||
owned_poll: enquete
|
expires_at: Expira em
|
||||||
|
options: Escolhas
|
||||||
errors:
|
errors:
|
||||||
models:
|
models:
|
||||||
account:
|
account:
|
||||||
|
@ -3,7 +3,7 @@ sk:
|
|||||||
activerecord:
|
activerecord:
|
||||||
attributes:
|
attributes:
|
||||||
poll:
|
poll:
|
||||||
expires_at: Uzávierka
|
expires_at: Trvá do
|
||||||
options: Voľby
|
options: Voľby
|
||||||
status:
|
status:
|
||||||
owned_poll: Anketa
|
owned_poll: Anketa
|
||||||
|
@ -68,6 +68,7 @@ ca:
|
|||||||
admin: Administrador
|
admin: Administrador
|
||||||
bot: Bot
|
bot: Bot
|
||||||
moderator: Moderador
|
moderator: Moderador
|
||||||
|
unavailable: Perfil inaccessible
|
||||||
unfollow: Deixa de seguir
|
unfollow: Deixa de seguir
|
||||||
admin:
|
admin:
|
||||||
account_actions:
|
account_actions:
|
||||||
@ -80,6 +81,7 @@ ca:
|
|||||||
destroyed_msg: Nota de moderació destruïda amb èxit!
|
destroyed_msg: Nota de moderació destruïda amb èxit!
|
||||||
accounts:
|
accounts:
|
||||||
approve: Aprova
|
approve: Aprova
|
||||||
|
approve_all: Aprova'ls tots
|
||||||
are_you_sure: N'estàs segur?
|
are_you_sure: N'estàs segur?
|
||||||
avatar: Avatar
|
avatar: Avatar
|
||||||
by_domain: Domini
|
by_domain: Domini
|
||||||
@ -132,6 +134,7 @@ ca:
|
|||||||
moderation_notes: Notes de moderació
|
moderation_notes: Notes de moderació
|
||||||
most_recent_activity: Activitat més recent
|
most_recent_activity: Activitat més recent
|
||||||
most_recent_ip: IP més recent
|
most_recent_ip: IP més recent
|
||||||
|
no_account_selected: No s'han canviat els comptes perque no s'han seleccionat
|
||||||
no_limits_imposed: Sense límits imposats
|
no_limits_imposed: Sense límits imposats
|
||||||
not_subscribed: No subscrit
|
not_subscribed: No subscrit
|
||||||
outbox_url: URL de la bústia de sortida
|
outbox_url: URL de la bústia de sortida
|
||||||
@ -144,6 +147,7 @@ ca:
|
|||||||
push_subscription_expires: La subscripció PuSH expira
|
push_subscription_expires: La subscripció PuSH expira
|
||||||
redownload: Actualitza el perfil
|
redownload: Actualitza el perfil
|
||||||
reject: Rebutja
|
reject: Rebutja
|
||||||
|
reject_all: Rebutja'ls tots
|
||||||
remove_avatar: Eliminar avatar
|
remove_avatar: Eliminar avatar
|
||||||
remove_header: Treu la capçalera
|
remove_header: Treu la capçalera
|
||||||
resend_confirmation:
|
resend_confirmation:
|
||||||
@ -330,6 +334,8 @@ ca:
|
|||||||
expired: Caducat
|
expired: Caducat
|
||||||
title: Filtre
|
title: Filtre
|
||||||
title: Convida
|
title: Convida
|
||||||
|
pending_accounts:
|
||||||
|
title: Comptes pendents (%{count})
|
||||||
relays:
|
relays:
|
||||||
add_new: Afegiu un nou relay
|
add_new: Afegiu un nou relay
|
||||||
delete: Esborra
|
delete: Esborra
|
||||||
@ -854,18 +860,23 @@ ca:
|
|||||||
revoke_success: S'ha revocat la sessió amb èxit
|
revoke_success: S'ha revocat la sessió amb èxit
|
||||||
title: Sessions
|
title: Sessions
|
||||||
settings:
|
settings:
|
||||||
|
account: Compte
|
||||||
|
account_settings: Ajustos del compte
|
||||||
|
appearance: Aparènça
|
||||||
authorized_apps: Aplicacions autoritzades
|
authorized_apps: Aplicacions autoritzades
|
||||||
back: Torna a l'inici
|
back: Torna a Mastodon
|
||||||
delete: Eliminació del compte
|
delete: Eliminació del compte
|
||||||
development: Desenvolupament
|
development: Desenvolupament
|
||||||
edit_profile: Editar perfil
|
edit_profile: Editar perfil
|
||||||
export: Exportar informació
|
export: Exportar dades
|
||||||
featured_tags: Etiquetes destacades
|
featured_tags: Etiquetes destacades
|
||||||
identity_proofs: Proves d'identitat
|
identity_proofs: Proves d'identitat
|
||||||
import: Importar
|
import: Importar
|
||||||
|
import_and_export: Importar i exportar
|
||||||
migrate: Migració del compte
|
migrate: Migració del compte
|
||||||
notifications: Notificacions
|
notifications: Notificacions
|
||||||
preferences: Preferències
|
preferences: Preferències
|
||||||
|
profile: Perfil
|
||||||
relationships: Seguits i seguidors
|
relationships: Seguits i seguidors
|
||||||
two_factor_authentication: Autenticació de dos factors
|
two_factor_authentication: Autenticació de dos factors
|
||||||
statuses:
|
statuses:
|
||||||
@ -1040,7 +1051,7 @@ ca:
|
|||||||
welcome:
|
welcome:
|
||||||
edit_profile_action: Configurar perfil
|
edit_profile_action: Configurar perfil
|
||||||
edit_profile_step: Pots personalitzar el teu perfil penjant un avatar, un encapçalament, canviant el teu nom de visualització i molt més. Si prefereixes revisar els seguidors nous abans de que et puguin seguir, pots blocar el teu compte.
|
edit_profile_step: Pots personalitzar el teu perfil penjant un avatar, un encapçalament, canviant el teu nom de visualització i molt més. Si prefereixes revisar els seguidors nous abans de que et puguin seguir, pots blocar el teu compte.
|
||||||
explanation: Aquests són alguns consells per començar
|
explanation: Aquests són alguns consells per a començar
|
||||||
final_action: Comença a publicar
|
final_action: Comença a publicar
|
||||||
final_step: 'Comença a publicar! Fins i tot sense seguidors, els altres poden veure els teus missatges públics, per exemple, a la línia de temps local i a les etiquetes ("hashtags"). És possible que vulguis presentar-te amb l''etiqueta #introductions.'
|
final_step: 'Comença a publicar! Fins i tot sense seguidors, els altres poden veure els teus missatges públics, per exemple, a la línia de temps local i a les etiquetes ("hashtags"). És possible que vulguis presentar-te amb l''etiqueta #introductions.'
|
||||||
full_handle: El teu nom d'usuari sencer
|
full_handle: El teu nom d'usuari sencer
|
||||||
|
@ -269,6 +269,7 @@ co:
|
|||||||
created_msg: U blucchime di u duminiu hè attivu
|
created_msg: U blucchime di u duminiu hè attivu
|
||||||
destroyed_msg: U blucchime di u duminiu ùn hè più attivu
|
destroyed_msg: U blucchime di u duminiu ùn hè più attivu
|
||||||
domain: Duminiu
|
domain: Duminiu
|
||||||
|
existing_domain_block_html: Avete digià impostu limite più strette nant'à %{name}, duvete <a href="%{unblock_url}">sbluccallu</a> primu.
|
||||||
new:
|
new:
|
||||||
create: Creà un blucchime
|
create: Creà un blucchime
|
||||||
hint: U blucchime di duminiu ùn impedirà micca a creazione di conti indè a database, mà metudi di muderazione specifiche saranu applicati.
|
hint: U blucchime di duminiu ùn impedirà micca a creazione di conti indè a database, mà metudi di muderazione specifiche saranu applicati.
|
||||||
@ -862,6 +863,7 @@ co:
|
|||||||
settings:
|
settings:
|
||||||
account: Contu
|
account: Contu
|
||||||
account_settings: Parametri di u contu
|
account_settings: Parametri di u contu
|
||||||
|
appearance: Apparenza
|
||||||
authorized_apps: Applicazione auturizate
|
authorized_apps: Applicazione auturizate
|
||||||
back: Ritornu nant’à Mastodon
|
back: Ritornu nant’à Mastodon
|
||||||
delete: Suppressione di u contu
|
delete: Suppressione di u contu
|
||||||
|
@ -273,6 +273,7 @@ cs:
|
|||||||
created_msg: Blokace domény se právě vyřizuje
|
created_msg: Blokace domény se právě vyřizuje
|
||||||
destroyed_msg: Blokace domény byla zrušena
|
destroyed_msg: Blokace domény byla zrušena
|
||||||
domain: Doména
|
domain: Doména
|
||||||
|
existing_domain_block_html: Pro účet %{name} jste již nastavil/a přísnější omezení, musíte jej nejdříve <a href="%{unblock_url}">odblokovat</a>.
|
||||||
new:
|
new:
|
||||||
create: Vytvořit blokaci
|
create: Vytvořit blokaci
|
||||||
hint: Blokace domény nezakáže vytváření záznamů účtů v databázi, ale bude na tyto účty zpětně a automaticky aplikovat specifické metody moderování.
|
hint: Blokace domény nezakáže vytváření záznamů účtů v databázi, ale bude na tyto účty zpětně a automaticky aplikovat specifické metody moderování.
|
||||||
|
@ -3,7 +3,7 @@ sk:
|
|||||||
devise:
|
devise:
|
||||||
confirmations:
|
confirmations:
|
||||||
confirmed: Tvoja emailová adresa bola úspešne overená.
|
confirmed: Tvoja emailová adresa bola úspešne overená.
|
||||||
send_instructions: O niekoľko minút obdržíš email s inštrukciami ako potvrdiť svoj účet. Prosím, skontroluj si aj zložku spam, ak sa k tebe toto potvrdenie nedostalo.
|
send_instructions: O niekoľko minút obdržíš email s pokynmi ako potvrdiť svoj účet. Prosím, skontroluj si aj zložku spam, ak sa k tebe toto potvrdenie nedostalo.
|
||||||
send_paranoid_instructions: Ak sa tvoja emailová adresa nachádza v našej databázi, o niekoľko minút obdržíš email s pokynmi ako potvrdiť svoj účet. Prosím, skontroluj aj zložku spam, ak sa k tebe toto potvrdenie nedostalo.
|
send_paranoid_instructions: Ak sa tvoja emailová adresa nachádza v našej databázi, o niekoľko minút obdržíš email s pokynmi ako potvrdiť svoj účet. Prosím, skontroluj aj zložku spam, ak sa k tebe toto potvrdenie nedostalo.
|
||||||
failure:
|
failure:
|
||||||
already_authenticated: Už si prihlásený/á.
|
already_authenticated: Už si prihlásený/á.
|
||||||
@ -11,27 +11,27 @@ sk:
|
|||||||
invalid: Nesprávny %{authentication_keys}, alebo heslo.
|
invalid: Nesprávny %{authentication_keys}, alebo heslo.
|
||||||
last_attempt: Máš posledný pokus pred zamknutím tvojho účtu.
|
last_attempt: Máš posledný pokus pred zamknutím tvojho účtu.
|
||||||
locked: Tvoj účet je zamknutý.
|
locked: Tvoj účet je zamknutý.
|
||||||
not_found_in_database: Nesprávny %{authentication_keys} alebo heslo.
|
not_found_in_database: Nesprávny %{authentication_keys}, alebo heslo.
|
||||||
pending: Tvoj účet je stále prehodnocovaný.
|
pending: Tvoj účet je stále prehodnocovaný.
|
||||||
timeout: Vaša aktívna sezóna vypršala. Pre pokračovanie sa prosím znovu prihláste.
|
timeout: Tvoja aktívna sezóna vypršala. Pre pokračovanie sa prosím prihlás znovu.
|
||||||
unauthenticated: K pokračovaniu sa musíš zaregistrovať alebo prihlásiť.
|
unauthenticated: K pokračovaniu sa musíš zaregistrovať alebo prihlásiť.
|
||||||
unconfirmed: Pred pokračovaním musíš potvrdiť svoj email.
|
unconfirmed: Pred pokračovaním musíš potvrdiť svoj email.
|
||||||
mailer:
|
mailer:
|
||||||
confirmation_instructions:
|
confirmation_instructions:
|
||||||
action: Potvŕď emailovú adresu
|
action: Potvrď emailovú adresu
|
||||||
action_with_app: Potvrď a vráť sa na %{app}
|
action_with_app: Potvrď a vráť sa na %{app}
|
||||||
explanation: S touto emailovou adresou si si vytvoril/a účet na %{host}. Si iba jeden klik od jeho aktivácie. Pokiaľ si to ale nebol/a ty, prosím ignoruj tento email.
|
explanation: S touto emailovou adresou si si vytvoril/a účet na %{host}. Si iba jeden klik od jeho aktivácie. Pokiaľ si to ale nebol/a ty, prosím ignoruj tento email.
|
||||||
extra_html: Prosím pozri sa aj na <a href="%{terms_path}"> pravidlá tohto servera,</a> a <a href="%{policy_path}"> naše užívaťeľské podiemky</a>.
|
extra_html: Prosím, pozri sa aj na <a href="%{terms_path}"> pravidlá tohto servera,</a> a <a href="%{policy_path}"> naše užívaťeľské podiemky</a>.
|
||||||
subject: 'Mastodon: Potvrdzovacie inštrukcie pre %{instance}'
|
subject: 'Mastodon: Potvrdzovacie pokyny pre %{instance}'
|
||||||
title: Potvrď emailovú adresu
|
title: Potvrď emailovú adresu
|
||||||
email_changed:
|
email_changed:
|
||||||
explanation: 'Emailová adresa tvojho účtu bude zmenená na:'
|
explanation: 'Emailová adresa tvojho účtu bude zmenená na:'
|
||||||
extra: Pokiaľ si nezmenil/a svoj email, je pravdepodobné že niekto iný získal prístup k tvojmu účtu. Naliehavo preto prosím zmeň svoje heslo, alebo kontaktuj administrátora tohto serveru pokiaľ si vymknutý/á zo svojho účtu.
|
extra: Ak si nezmenil/a svoj email, je pravdepodobné, že niekto iný získal prístup k tvojmu účtu. Naliehavo preto prosím zmeň svoje heslo, alebo kontaktuj administrátora tohto serveru, pokiaľ si vymknutý/á zo svojho účtu.
|
||||||
subject: 'Mastodon: Emailová adresa bola zmenená'
|
subject: 'Mastodon: Emailová adresa bola zmenená'
|
||||||
title: Nová emailová adresa
|
title: Nová emailová adresa
|
||||||
password_change:
|
password_change:
|
||||||
explanation: Heslo k tvojmu účtu bolo zmenené.
|
explanation: Heslo k tvojmu účtu bolo zmenené.
|
||||||
extra: Ak si heslo nezmenil/a, je pravdepodobné že niekto iný získal prístup k tvojmu účtu. Naliehavo preto prosím zmeň svoje heslo, alebo kontaktuj administrátora tohto serveru pokiaľ si vymknutý/á zo svojho účtu.
|
extra: Ak si heslo nezmenil/a, je pravdepodobné, že niekto iný získal prístup k tvojmu účtu. Naliehavo preto prosím zmeň svoje heslo, alebo kontaktuj administrátora tohto serveru, pokiaľ si vymknutý/á zo svojho účtu.
|
||||||
subject: 'Mastodon: Heslo bolo zmenené'
|
subject: 'Mastodon: Heslo bolo zmenené'
|
||||||
title: Heslo bolo zmenené
|
title: Heslo bolo zmenené
|
||||||
reconfirmation_instructions:
|
reconfirmation_instructions:
|
||||||
@ -42,17 +42,17 @@ sk:
|
|||||||
reset_password_instructions:
|
reset_password_instructions:
|
||||||
action: Zmeň svoje heslo
|
action: Zmeň svoje heslo
|
||||||
explanation: Vyžiadal/a si si nové heslo pre svoj účet.
|
explanation: Vyžiadal/a si si nové heslo pre svoj účet.
|
||||||
extra: Pokiaľ si túto akciu nevyžiadal/a, prosím ignoruj tento email. Tvoje heslo nebude zmenené pokiaľ nepostúpiš na adresu uvedenú vyššie a vytvoríš si nové.
|
extra: Ak si túto akciu nevyžiadal/a, prosím ignoruj tento email. Tvoje heslo nebude zmenené pokiaľ nepostúpiš na adresu uvedenú vyššie a vytvoríš si nové.
|
||||||
subject: 'Mastodon: Inštrukcie pre obnovu hesla'
|
subject: 'Mastodon: Pokyny pre obnovu hesla'
|
||||||
title: Nastav nové heslo
|
title: Nastav nové heslo
|
||||||
unlock_instructions:
|
unlock_instructions:
|
||||||
subject: 'Mastodon: Inštrukcie pre odomknutie účtu'
|
subject: 'Mastodon: Pokyny na odomknutie účtu'
|
||||||
omniauth_callbacks:
|
omniauth_callbacks:
|
||||||
failure: Nebolo možné ťa overiť z dôvodu,%{kind} že "%{reason}".
|
failure: Nebolo možné ťa overiť z %{kind}, lebo "%{reason}".
|
||||||
success: Úspešné overenie z účtu %{kind}.
|
success: Úspešné overenie z účtu %{kind}.
|
||||||
passwords:
|
passwords:
|
||||||
no_token: Túto stránku nemôžete navštíviť pokiaľ neprichádzate z emailu s inštrukciami na obnovu hesla. Pokiaľ prichádzate z tohto emailu, prosím uistite sa že ste použili celú URL z emailu.
|
no_token: Túto stránku nemôžeš navštíviť, ak neprichádzaš z emailu s pokynmi na obnovu hesla. Pokiaľ prichádzaš z tohto emailu, prosím uisti sa že si použil/a celú URL adresu z emailu.
|
||||||
send_instructions: Pokiaľ sa tvoja emailová adresa nachádza v databázi, tak o niekoľko minút obdržíš email s inštrukciami ako nastaviť nové heslo. Ak máš pocit, že si email neobdržal/a, prosím skontroluj aj svoju spam zložku.
|
send_instructions: Ak sa tvoja emailová adresa nachádza v databázi, tak o niekoľko minút obdržíš email s pokynmi ako nastaviť nové heslo. Ak máš pocit, že si email neobdržal/a, prosím skontroluj aj svoju spam zložku.
|
||||||
send_paranoid_instructions: Ak sa tvoja emailová adresa nachádza v databázi, za chvíľu obdržíš odkaz pre obnovu hesla na svoj email. Skontroluj ale prosím aj svoj spam, ak tento email nevidíš.
|
send_paranoid_instructions: Ak sa tvoja emailová adresa nachádza v databázi, za chvíľu obdržíš odkaz pre obnovu hesla na svoj email. Skontroluj ale prosím aj svoj spam, ak tento email nevidíš.
|
||||||
updated: Tvoje heslo bolo úspešne zmenené. Teraz si prihlásený/á.
|
updated: Tvoje heslo bolo úspešne zmenené. Teraz si prihlásený/á.
|
||||||
updated_not_active: Tvoje heslo bolo úspešne zmenené.
|
updated_not_active: Tvoje heslo bolo úspešne zmenené.
|
||||||
@ -66,9 +66,9 @@ sk:
|
|||||||
update_needs_confirmation: Účet bol úspešne pozmenený, ale ešte potrebujeme overiť tvoju novú emailovú adresu. Pre overenie prosím klikni na link v správe ktorú si dostal/a na email. Takisto ale skontroluj aj svoju spam zložku, ak sa ti zdá, že si tento email nedostal/a.
|
update_needs_confirmation: Účet bol úspešne pozmenený, ale ešte potrebujeme overiť tvoju novú emailovú adresu. Pre overenie prosím klikni na link v správe ktorú si dostal/a na email. Takisto ale skontroluj aj svoju spam zložku, ak sa ti zdá, že si tento email nedostal/a.
|
||||||
updated: Tvoj účet bol úspešne aktualizovaný.
|
updated: Tvoj účet bol úspešne aktualizovaný.
|
||||||
sessions:
|
sessions:
|
||||||
already_signed_out: Odhlásil/a si sa úspešné.
|
already_signed_out: Už si sa úspešne odhlásil/a.
|
||||||
signed_in: Prihlásil/a si sa úspešné.
|
signed_in: Prihlásil/a si sa úspešne.
|
||||||
signed_out: Odhlásil/a si sa úspešné.
|
signed_out: Odhlásil/a si sa úspešne.
|
||||||
unlocks:
|
unlocks:
|
||||||
send_instructions: O niekoľko minút obdržíš email s pokynmi, ako nastaviť nové heslo. Prosím, skontroluj ale aj svoju spam zložku, pokiaľ sa ti zdá, že si tento email nedostal/a.
|
send_instructions: O niekoľko minút obdržíš email s pokynmi, ako nastaviť nové heslo. Prosím, skontroluj ale aj svoju spam zložku, pokiaľ sa ti zdá, že si tento email nedostal/a.
|
||||||
send_paranoid_instructions: Ak tvoj účet existuje, o niekoľko minút obdržíš email s pokynmi ako si ho odomknúť. Prosím, skontroluj ale aj svoju spam zložku, pokiaľ sa ti zdá, že si tento email nedostal/a.
|
send_paranoid_instructions: Ak tvoj účet existuje, o niekoľko minút obdržíš email s pokynmi ako si ho odomknúť. Prosím, skontroluj ale aj svoju spam zložku, pokiaľ sa ti zdá, že si tento email nedostal/a.
|
||||||
@ -79,8 +79,8 @@ sk:
|
|||||||
confirmation_period_expired: musí byť potvrdený do %{period}, prosím požiadaj o nový
|
confirmation_period_expired: musí byť potvrdený do %{period}, prosím požiadaj o nový
|
||||||
expired: vypŕšal, prosím, vyžiadaj si nový
|
expired: vypŕšal, prosím, vyžiadaj si nový
|
||||||
not_found: nenájdený
|
not_found: nenájdený
|
||||||
not_locked: nebol uzamknutý
|
not_locked: nebol zamknutý
|
||||||
not_saved:
|
not_saved:
|
||||||
few: "%{resource} nebol uložený kôli %{count} chybám:"
|
few: "%{resource} nebol uložený kvôli %{count} chybám:"
|
||||||
one: "%{resource} nebol uložený kôli chybe:"
|
one: "%{resource} nebol uložený kvôli chybe:"
|
||||||
other: "%{resource} nebol uložený kôli %{count} chybám:"
|
other: "%{resource} nebol uložený kvôli %{count} chybám:"
|
||||||
|
@ -269,6 +269,7 @@ en:
|
|||||||
created_msg: Domain block is now being processed
|
created_msg: Domain block is now being processed
|
||||||
destroyed_msg: Domain block has been undone
|
destroyed_msg: Domain block has been undone
|
||||||
domain: Domain
|
domain: Domain
|
||||||
|
existing_domain_block_html: You have already imposed stricter limits on %{name}, you need to <a href="%{unblock_url}">unblock it</a> first.
|
||||||
new:
|
new:
|
||||||
create: Create block
|
create: Create block
|
||||||
hint: The domain block will not prevent creation of account entries in the database, but will retroactively and automatically apply specific moderation methods on those accounts.
|
hint: The domain block will not prevent creation of account entries in the database, but will retroactively and automatically apply specific moderation methods on those accounts.
|
||||||
|
@ -260,10 +260,10 @@ fr:
|
|||||||
title: Nouveau blocage de domaine
|
title: Nouveau blocage de domaine
|
||||||
reject_media: Fichiers média rejetés
|
reject_media: Fichiers média rejetés
|
||||||
reject_media_hint: Supprime localement les fichiers média stockés et refuse d’en télécharger ultérieurement. Ne concerne pas les suspensions
|
reject_media_hint: Supprime localement les fichiers média stockés et refuse d’en télécharger ultérieurement. Ne concerne pas les suspensions
|
||||||
reject_reports: Rapports de rejet
|
reject_reports: Rejeter les signalements
|
||||||
reject_reports_hint: Ignorez tous les rapports provenant de ce domaine. Sans objet pour les suspensions
|
reject_reports_hint: Ignorez tous les signalements provenant de ce domaine. Ne concerne pas les suspensions
|
||||||
rejecting_media: rejet des fichiers multimédia
|
rejecting_media: rejet des fichiers multimédia
|
||||||
rejecting_reports: rejet de rapports
|
rejecting_reports: rejet des signalements
|
||||||
severity:
|
severity:
|
||||||
silence: silencié
|
silence: silencié
|
||||||
suspend: suspendu
|
suspend: suspendu
|
||||||
|
@ -68,6 +68,7 @@ nl:
|
|||||||
admin: Beheerder
|
admin: Beheerder
|
||||||
bot: Bot
|
bot: Bot
|
||||||
moderator: Moderator
|
moderator: Moderator
|
||||||
|
unavailable: Profiel niet beschikbaar
|
||||||
unfollow: Ontvolgen
|
unfollow: Ontvolgen
|
||||||
admin:
|
admin:
|
||||||
account_actions:
|
account_actions:
|
||||||
@ -80,6 +81,7 @@ nl:
|
|||||||
destroyed_msg: Verwijderen van opmerking voor moderatoren geslaagd!
|
destroyed_msg: Verwijderen van opmerking voor moderatoren geslaagd!
|
||||||
accounts:
|
accounts:
|
||||||
approve: Goedkeuren
|
approve: Goedkeuren
|
||||||
|
approve_all: Alles goedkeuren
|
||||||
are_you_sure: Weet je het zeker?
|
are_you_sure: Weet je het zeker?
|
||||||
avatar: Avatar
|
avatar: Avatar
|
||||||
by_domain: Domein
|
by_domain: Domein
|
||||||
@ -132,6 +134,7 @@ nl:
|
|||||||
moderation_notes: Opmerkingen voor moderatoren
|
moderation_notes: Opmerkingen voor moderatoren
|
||||||
most_recent_activity: Laatst actief
|
most_recent_activity: Laatst actief
|
||||||
most_recent_ip: Laatst gebruikt IP-adres
|
most_recent_ip: Laatst gebruikt IP-adres
|
||||||
|
no_account_selected: Er zijn geen accounts veranderd, omdat er geen een was geselecteerd
|
||||||
no_limits_imposed: Geen limieten ingesteld
|
no_limits_imposed: Geen limieten ingesteld
|
||||||
not_subscribed: Niet geabonneerd
|
not_subscribed: Niet geabonneerd
|
||||||
outbox_url: Outbox-URL
|
outbox_url: Outbox-URL
|
||||||
@ -144,6 +147,7 @@ nl:
|
|||||||
push_subscription_expires: PuSH-abonnement verloopt op
|
push_subscription_expires: PuSH-abonnement verloopt op
|
||||||
redownload: Profiel vernieuwen
|
redownload: Profiel vernieuwen
|
||||||
reject: Afkeuren
|
reject: Afkeuren
|
||||||
|
reject_all: Alles afkeuren
|
||||||
remove_avatar: Avatar verwijderen
|
remove_avatar: Avatar verwijderen
|
||||||
remove_header: Omslagfoto verwijderen
|
remove_header: Omslagfoto verwijderen
|
||||||
resend_confirmation:
|
resend_confirmation:
|
||||||
@ -245,6 +249,7 @@ nl:
|
|||||||
feature_profile_directory: Gebruikersgids
|
feature_profile_directory: Gebruikersgids
|
||||||
feature_registrations: Registraties
|
feature_registrations: Registraties
|
||||||
feature_relay: Federatierelay
|
feature_relay: Federatierelay
|
||||||
|
feature_timeline_preview: Voorvertoning van tijdlijn
|
||||||
features: Functies
|
features: Functies
|
||||||
hidden_service: Federatie met verborgen diensten
|
hidden_service: Federatie met verborgen diensten
|
||||||
open_reports: onopgeloste rapportages
|
open_reports: onopgeloste rapportages
|
||||||
@ -329,6 +334,8 @@ nl:
|
|||||||
expired: Verlopen
|
expired: Verlopen
|
||||||
title: Filter
|
title: Filter
|
||||||
title: Uitnodigingen
|
title: Uitnodigingen
|
||||||
|
pending_accounts:
|
||||||
|
title: Accounts in afwachting (%{count})
|
||||||
relays:
|
relays:
|
||||||
add_new: Nieuwe relayserver toevoegen
|
add_new: Nieuwe relayserver toevoegen
|
||||||
delete: Verwijderen
|
delete: Verwijderen
|
||||||
@ -428,14 +435,14 @@ nl:
|
|||||||
desc_html: Medewerkersbadge op profielpagina tonen
|
desc_html: Medewerkersbadge op profielpagina tonen
|
||||||
title: Medewerkersbadge tonen
|
title: Medewerkersbadge tonen
|
||||||
site_description:
|
site_description:
|
||||||
desc_html: Dit wordt als een alinea op de voorpagina getoond. Beschrijf wat er speciaal is aan deze server en andere zaken die van belang zijn. Je kan HTML gebruiken, zoals <code><a></code> en <code><em></code>.
|
desc_html: Introductie-alinea voor de API. Beschrijf wat er speciaal is aan deze server en andere zaken die van belang zijn. Je kan HTML gebruiken, zoals <code><a></code> en <code><em></code>.
|
||||||
title: Omschrijving Mastodonserver
|
title: Omschrijving Mastodonserver (API)
|
||||||
site_description_extended:
|
site_description_extended:
|
||||||
desc_html: Een goede plek voor je gedragscode, regels, richtlijnen en andere zaken die jouw server uniek maken. Je kan ook hier HTML gebruiken
|
desc_html: Een goede plek voor je gedragscode, regels, richtlijnen en andere zaken die jouw server uniek maken. Je kan ook hier HTML gebruiken
|
||||||
title: Uitgebreide omschrijving Mastodonserver
|
title: Uitgebreide omschrijving Mastodonserver
|
||||||
site_short_description:
|
site_short_description:
|
||||||
desc_html: Dit wordt in de zijbalk getoond als en als metatag in de paginabron. Beschrijf in één alinea wat Mastodon is en wat deze server speciaal maakt. De (langere) omschrijving van de Mastodonserver wordt gebruikt wanneer dit veld wordt leeg gelaten.
|
desc_html: Dit wordt gebruikt op de voorpagina, in de zijbalk op profielpagina's en als metatag in de paginabron. Beschrijf in één alinea wat Mastodon is en wat deze server speciaal maakt.
|
||||||
title: Korte omschrijving Mastodonserver
|
title: Omschrijving Mastodonserver (website)
|
||||||
site_terms:
|
site_terms:
|
||||||
desc_html: Je kan hier jouw eigen privacybeleid, gebruiksvoorwaarden en ander juridisch jargon kwijt. Je kan HTML gebruiken
|
desc_html: Je kan hier jouw eigen privacybeleid, gebruiksvoorwaarden en ander juridisch jargon kwijt. Je kan HTML gebruiken
|
||||||
title: Aangepaste gebruiksvoorwaarden
|
title: Aangepaste gebruiksvoorwaarden
|
||||||
@ -585,6 +592,9 @@ nl:
|
|||||||
content: Het spijt ons, er is aan onze kant iets fout gegaan.
|
content: Het spijt ons, er is aan onze kant iets fout gegaan.
|
||||||
title: Er is iets mis
|
title: Er is iets mis
|
||||||
noscript_html: Schakel JavaScript in om de webapp van Mastodon te kunnen gebruiken. Als alternatief kan je een <a href="%{apps_path}">Mastodon-app</a> zoeken voor jouw platform.
|
noscript_html: Schakel JavaScript in om de webapp van Mastodon te kunnen gebruiken. Als alternatief kan je een <a href="%{apps_path}">Mastodon-app</a> zoeken voor jouw platform.
|
||||||
|
existing_username_validator:
|
||||||
|
not_found: Kon geen lokale gebruiker met die gebruikersnaam vinden
|
||||||
|
not_found_multiple: Kon %{usernames} niet vinden
|
||||||
exports:
|
exports:
|
||||||
archive_takeout:
|
archive_takeout:
|
||||||
date: Datum
|
date: Datum
|
||||||
@ -628,10 +638,13 @@ nl:
|
|||||||
all: Alles
|
all: Alles
|
||||||
changes_saved_msg: Wijzigingen succesvol opgeslagen!
|
changes_saved_msg: Wijzigingen succesvol opgeslagen!
|
||||||
copy: Kopiëren
|
copy: Kopiëren
|
||||||
|
order_by: Sorteer op
|
||||||
save_changes: Wijzigingen opslaan
|
save_changes: Wijzigingen opslaan
|
||||||
validation_errors:
|
validation_errors:
|
||||||
one: Er is iets niet helemaal goed! Bekijk onderstaande fout
|
one: Er is iets niet helemaal goed! Bekijk onderstaande fout
|
||||||
other: Er is iets niet helemaal goed! Bekijk onderstaande %{count} fouten
|
other: Er is iets niet helemaal goed! Bekijk onderstaande %{count} fouten
|
||||||
|
html_validator:
|
||||||
|
invalid_markup: 'bevat ongeldige HTML-opmaak: %{error}'
|
||||||
identity_proofs:
|
identity_proofs:
|
||||||
active: Actief
|
active: Actief
|
||||||
authorize: Ja, autoriseren
|
authorize: Ja, autoriseren
|
||||||
@ -646,6 +659,8 @@ nl:
|
|||||||
i_am_html: Ik ben %{username} op %{service}.
|
i_am_html: Ik ben %{username} op %{service}.
|
||||||
identity: Identiteit
|
identity: Identiteit
|
||||||
inactive: Inactief
|
inactive: Inactief
|
||||||
|
publicize_checkbox: 'En toot dit:'
|
||||||
|
publicize_toot: 'Het is bewezen! Ik ben %{username} op %{service}: %{url}'
|
||||||
status: Verificatiestatus
|
status: Verificatiestatus
|
||||||
view_proof: Bekijk bewijs
|
view_proof: Bekijk bewijs
|
||||||
imports:
|
imports:
|
||||||
@ -768,6 +783,8 @@ nl:
|
|||||||
relationships:
|
relationships:
|
||||||
activity: Accountactiviteit
|
activity: Accountactiviteit
|
||||||
dormant: Sluimerend
|
dormant: Sluimerend
|
||||||
|
last_active: Laatst actief
|
||||||
|
most_recent: Recentelijk gevolgd
|
||||||
moved: Verhuisd
|
moved: Verhuisd
|
||||||
mutual: Wederzijds
|
mutual: Wederzijds
|
||||||
primary: Primair
|
primary: Primair
|
||||||
@ -843,6 +860,9 @@ nl:
|
|||||||
revoke_success: Sessie succesvol ingetrokken
|
revoke_success: Sessie succesvol ingetrokken
|
||||||
title: Sessies
|
title: Sessies
|
||||||
settings:
|
settings:
|
||||||
|
account: Account
|
||||||
|
account_settings: Accountinstellingen
|
||||||
|
appearance: Uiterlijk
|
||||||
authorized_apps: Geautoriseerde apps
|
authorized_apps: Geautoriseerde apps
|
||||||
back: Terug naar Mastodon
|
back: Terug naar Mastodon
|
||||||
delete: Account verwijderen
|
delete: Account verwijderen
|
||||||
@ -852,9 +872,11 @@ nl:
|
|||||||
featured_tags: Uitgelichte hashtags
|
featured_tags: Uitgelichte hashtags
|
||||||
identity_proofs: Identiteitsbewijzen
|
identity_proofs: Identiteitsbewijzen
|
||||||
import: Importeren
|
import: Importeren
|
||||||
|
import_and_export: Importeren en exporteren
|
||||||
migrate: Accountmigratie
|
migrate: Accountmigratie
|
||||||
notifications: Meldingen
|
notifications: Meldingen
|
||||||
preferences: Voorkeuren
|
preferences: Voorkeuren
|
||||||
|
profile: Profiel
|
||||||
relationships: Volgers en gevolgden
|
relationships: Volgers en gevolgden
|
||||||
two_factor_authentication: Tweestapsverificatie
|
two_factor_authentication: Tweestapsverificatie
|
||||||
statuses:
|
statuses:
|
||||||
|
@ -68,6 +68,7 @@ pt-BR:
|
|||||||
admin: Administrador
|
admin: Administrador
|
||||||
bot: Robô
|
bot: Robô
|
||||||
moderator: Moderador
|
moderator: Moderador
|
||||||
|
unavailable: Perfil indisponível
|
||||||
unfollow: Deixar de seguir
|
unfollow: Deixar de seguir
|
||||||
admin:
|
admin:
|
||||||
account_actions:
|
account_actions:
|
||||||
@ -80,6 +81,7 @@ pt-BR:
|
|||||||
destroyed_msg: Nota de moderação excluída com sucesso!
|
destroyed_msg: Nota de moderação excluída com sucesso!
|
||||||
accounts:
|
accounts:
|
||||||
approve: Aprovar
|
approve: Aprovar
|
||||||
|
approve_all: Aprovar tudo
|
||||||
are_you_sure: Você tem certeza?
|
are_you_sure: Você tem certeza?
|
||||||
avatar: Avatar
|
avatar: Avatar
|
||||||
by_domain: Domínio
|
by_domain: Domínio
|
||||||
@ -132,6 +134,7 @@ pt-BR:
|
|||||||
moderation_notes: Notas de moderação
|
moderation_notes: Notas de moderação
|
||||||
most_recent_activity: Atividade mais recente
|
most_recent_activity: Atividade mais recente
|
||||||
most_recent_ip: IP mais recente
|
most_recent_ip: IP mais recente
|
||||||
|
no_account_selected: Nenhuma conta foi modificada, pois nenhuma conta foi selecionada
|
||||||
no_limits_imposed: Nenhum limite imposto
|
no_limits_imposed: Nenhum limite imposto
|
||||||
not_subscribed: Não está inscrito
|
not_subscribed: Não está inscrito
|
||||||
outbox_url: URL da caixa de saída
|
outbox_url: URL da caixa de saída
|
||||||
@ -144,6 +147,7 @@ pt-BR:
|
|||||||
push_subscription_expires: Inscrição PuSH expira
|
push_subscription_expires: Inscrição PuSH expira
|
||||||
redownload: Atualizar perfil
|
redownload: Atualizar perfil
|
||||||
reject: Rejeitar
|
reject: Rejeitar
|
||||||
|
reject_all: Rejeitar tudo
|
||||||
remove_avatar: Remover avatar
|
remove_avatar: Remover avatar
|
||||||
remove_header: Remover cabeçalho
|
remove_header: Remover cabeçalho
|
||||||
resend_confirmation:
|
resend_confirmation:
|
||||||
@ -330,6 +334,8 @@ pt-BR:
|
|||||||
expired: Expirados
|
expired: Expirados
|
||||||
title: Filtro
|
title: Filtro
|
||||||
title: Convites
|
title: Convites
|
||||||
|
pending_accounts:
|
||||||
|
title: Contas pendentes (%{count})
|
||||||
relays:
|
relays:
|
||||||
add_new: Adicionar novo repetidor
|
add_new: Adicionar novo repetidor
|
||||||
delete: Excluir
|
delete: Excluir
|
||||||
@ -854,6 +860,9 @@ pt-BR:
|
|||||||
revoke_success: Sessão revogada com sucesso
|
revoke_success: Sessão revogada com sucesso
|
||||||
title: Sessões
|
title: Sessões
|
||||||
settings:
|
settings:
|
||||||
|
account: Conta
|
||||||
|
account_settings: Configurações da conta
|
||||||
|
appearance: Aparência
|
||||||
authorized_apps: Apps autorizados
|
authorized_apps: Apps autorizados
|
||||||
back: Voltar para o Mastodon
|
back: Voltar para o Mastodon
|
||||||
delete: Exclusão de conta
|
delete: Exclusão de conta
|
||||||
@ -863,9 +872,11 @@ pt-BR:
|
|||||||
featured_tags: Hashtags em destaque
|
featured_tags: Hashtags em destaque
|
||||||
identity_proofs: Provas de identidade
|
identity_proofs: Provas de identidade
|
||||||
import: Importar
|
import: Importar
|
||||||
|
import_and_export: Importar e exportar
|
||||||
migrate: Migração de conta
|
migrate: Migração de conta
|
||||||
notifications: Notificações
|
notifications: Notificações
|
||||||
preferences: Preferências
|
preferences: Preferências
|
||||||
|
profile: Perfil
|
||||||
relationships: Seguindo e seguidores
|
relationships: Seguindo e seguidores
|
||||||
two_factor_authentication: Autenticação em dois passos
|
two_factor_authentication: Autenticação em dois passos
|
||||||
statuses:
|
statuses:
|
||||||
|
@ -41,6 +41,8 @@ nl:
|
|||||||
name: 'Je wilt misschien een van deze gebruiken:'
|
name: 'Je wilt misschien een van deze gebruiken:'
|
||||||
imports:
|
imports:
|
||||||
data: CSV-bestand dat op een andere Mastodonserver werd geëxporteerd
|
data: CSV-bestand dat op een andere Mastodonserver werd geëxporteerd
|
||||||
|
invite_request:
|
||||||
|
text: Dit helpt ons om jouw aanvraag te beoordelen
|
||||||
sessions:
|
sessions:
|
||||||
otp: 'Voer de tweestaps-aanmeldcode vanaf jouw mobiele telefoon in of gebruik een van jouw herstelcodes:'
|
otp: 'Voer de tweestaps-aanmeldcode vanaf jouw mobiele telefoon in of gebruik een van jouw herstelcodes:'
|
||||||
user:
|
user:
|
||||||
@ -118,12 +120,15 @@ nl:
|
|||||||
must_be_follower: Meldingen van mensen die jou niet volgen blokkeren
|
must_be_follower: Meldingen van mensen die jou niet volgen blokkeren
|
||||||
must_be_following: Meldingen van mensen die jij niet volgt blokkeren
|
must_be_following: Meldingen van mensen die jij niet volgt blokkeren
|
||||||
must_be_following_dm: Directe berichten van mensen die jij niet volgt blokkeren
|
must_be_following_dm: Directe berichten van mensen die jij niet volgt blokkeren
|
||||||
|
invite_request:
|
||||||
|
text: Waarom wil jij je aanmelden?
|
||||||
notification_emails:
|
notification_emails:
|
||||||
digest: Periodiek e-mails met een samenvatting versturen
|
digest: Periodiek e-mails met een samenvatting versturen
|
||||||
favourite: Een e-mail versturen wanneer iemand jouw toot aan hun favorieten heeft toegevoegd
|
favourite: Een e-mail versturen wanneer iemand jouw toot aan hun favorieten heeft toegevoegd
|
||||||
follow: Een e-mail versturen wanneer iemand jou volgt
|
follow: Een e-mail versturen wanneer iemand jou volgt
|
||||||
follow_request: Een e-mail versturen wanneer iemand jou wil volgen
|
follow_request: Een e-mail versturen wanneer iemand jou wil volgen
|
||||||
mention: Een e-mail versturen wanneer iemand jou vermeld
|
mention: Een e-mail versturen wanneer iemand jou vermeld
|
||||||
|
pending_account: Een e-mail verzenden wanneer een nieuw account moet worden beoordeeld
|
||||||
reblog: Een e-mail versturen wanneer iemand jouw toot heeft geboost
|
reblog: Een e-mail versturen wanneer iemand jouw toot heeft geboost
|
||||||
report: Verstuur een e-mail wanneer een nieuw rapportage is ingediend
|
report: Verstuur een e-mail wanneer een nieuw rapportage is ingediend
|
||||||
'no': Nee
|
'no': Nee
|
||||||
|
@ -29,8 +29,8 @@ pl:
|
|||||||
setting_aggregate_reblogs: Nie pokazuj nowych podbić dla wpisów, które zostały niedawno podbite (dotyczy tylko nowo otrzymanych podbić)
|
setting_aggregate_reblogs: Nie pokazuj nowych podbić dla wpisów, które zostały niedawno podbite (dotyczy tylko nowo otrzymanych podbić)
|
||||||
setting_default_language: Język Twoich wpisów może być wykrywany automatycznie, ale nie zawsze jest to dokładne
|
setting_default_language: Język Twoich wpisów może być wykrywany automatycznie, ale nie zawsze jest to dokładne
|
||||||
setting_display_media_default: Ukrywaj zawartość oznaczoną jako wrażliwa
|
setting_display_media_default: Ukrywaj zawartość oznaczoną jako wrażliwa
|
||||||
setting_display_media_hide_all: Zawsze ukrywaj zawartość multimedialną
|
setting_display_media_hide_all: Zawsze oznaczaj zawartość multimedialną jako wrażliwą
|
||||||
setting_display_media_show_all: Zawsze pokazuj zawartość multimedialną jako wrażliwą
|
setting_display_media_show_all: Nie ukrywaj zawartości multimedialnej oznaczonej jako wrażliwa
|
||||||
setting_hide_network: Informacje o tym, kto Cię śledzi i kogo śledzisz nie będą widoczne
|
setting_hide_network: Informacje o tym, kto Cię śledzi i kogo śledzisz nie będą widoczne
|
||||||
setting_noindex: Wpływa na widoczność strony profilu i Twoich wpisów
|
setting_noindex: Wpływa na widoczność strony profilu i Twoich wpisów
|
||||||
setting_show_application: W informacjach o wpisie będzie widoczna informacja o aplikacji, z której został wysłany
|
setting_show_application: W informacjach o wpisie będzie widoczna informacja o aplikacji, z której został wysłany
|
||||||
|
@ -41,6 +41,8 @@ pt-BR:
|
|||||||
name: 'Você pode querer usar um destes:'
|
name: 'Você pode querer usar um destes:'
|
||||||
imports:
|
imports:
|
||||||
data: Arquivo CSV exportado de outra instância do Mastodon
|
data: Arquivo CSV exportado de outra instância do Mastodon
|
||||||
|
invite_request:
|
||||||
|
text: Isso vai nos ajudar a revisar sua aplicação
|
||||||
sessions:
|
sessions:
|
||||||
otp: 'Insira o código de autenticação gerado pelo app no seu celular ou use um dos códigos de recuperação:'
|
otp: 'Insira o código de autenticação gerado pelo app no seu celular ou use um dos códigos de recuperação:'
|
||||||
user:
|
user:
|
||||||
@ -119,12 +121,15 @@ pt-BR:
|
|||||||
must_be_follower: Bloquear notificações de não-seguidores
|
must_be_follower: Bloquear notificações de não-seguidores
|
||||||
must_be_following: Bloquear notificações de pessoas que você não segue
|
must_be_following: Bloquear notificações de pessoas que você não segue
|
||||||
must_be_following_dm: Bloquear mensagens diretas de pessoas que você não segue
|
must_be_following_dm: Bloquear mensagens diretas de pessoas que você não segue
|
||||||
|
invite_request:
|
||||||
|
text: Por que você quer se cadastrar?
|
||||||
notification_emails:
|
notification_emails:
|
||||||
digest: Mandar e-mails com relatórios
|
digest: Mandar e-mails com relatórios
|
||||||
favourite: Mandar um e-mail quando alguém favoritar suas postagens
|
favourite: Mandar um e-mail quando alguém favoritar suas postagens
|
||||||
follow: Mandar um e-mail quando alguém te seguir
|
follow: Mandar um e-mail quando alguém te seguir
|
||||||
follow_request: Mandar um e-maill quando alguém solicitar ser seu seguidor
|
follow_request: Mandar um e-maill quando alguém solicitar ser seu seguidor
|
||||||
mention: Mandar um e-mail quando alguém te mencionar
|
mention: Mandar um e-mail quando alguém te mencionar
|
||||||
|
pending_account: Mandar um -mail quando uma nova conta precisar ser revisada
|
||||||
reblog: Mandar um e-mail quando alguém compartilhar suas postagens
|
reblog: Mandar um e-mail quando alguém compartilhar suas postagens
|
||||||
report: Mandar um e-mail quando uma nova denúncia é submetida
|
report: Mandar um e-mail quando uma nova denúncia é submetida
|
||||||
'no': Não
|
'no': Não
|
||||||
|
@ -10,7 +10,7 @@ sk:
|
|||||||
api: API
|
api: API
|
||||||
apps: Aplikácie
|
apps: Aplikácie
|
||||||
apps_platforms: Uživaj Mastodon z iOSu, Androidu a iných platforiem
|
apps_platforms: Uživaj Mastodon z iOSu, Androidu a iných platforiem
|
||||||
browse_directory: Prehľadávaj databázu profilov a filtruj ju podľa záujmov
|
browse_directory: Prehľadávaj databázu profilov, filtruj podľa záujmov
|
||||||
browse_public_posts: Prebádaj naživo prúd verejných príspevkov na Mastodone
|
browse_public_posts: Prebádaj naživo prúd verejných príspevkov na Mastodone
|
||||||
contact: Kontakt
|
contact: Kontakt
|
||||||
contact_missing: Nezadaný
|
contact_missing: Nezadaný
|
||||||
@ -20,9 +20,9 @@ sk:
|
|||||||
extended_description_html: |
|
extended_description_html: |
|
||||||
<h3>Pravidlá</h3>
|
<h3>Pravidlá</h3>
|
||||||
<p>Žiadne zatiaľ uvedené nie sú</p>
|
<p>Žiadne zatiaľ uvedené nie sú</p>
|
||||||
federation_hint_html: S účtom na %{instance} budeš môcť následovať ľúdí na hociakom inom Mastodon serveri, ale aj inde.
|
federation_hint_html: S účtom na %{instance} budeš môcť následovať ľúdí na hociakom Mastodon serveri, ale aj inde.
|
||||||
generic_description: "%{domain} je jeden server v sieti"
|
generic_description: "%{domain} je jeden server v sieti"
|
||||||
get_apps: Vyskúšaj mobilnú aplikáciu
|
get_apps: Vyskúšaj aplikácie
|
||||||
hosted_on: Mastodon hostovaný na %{domain}
|
hosted_on: Mastodon hostovaný na %{domain}
|
||||||
learn_more: Zisti viac
|
learn_more: Zisti viac
|
||||||
privacy_policy: Ustanovenia o súkromí
|
privacy_policy: Ustanovenia o súkromí
|
||||||
@ -32,22 +32,22 @@ sk:
|
|||||||
status_count_after:
|
status_count_after:
|
||||||
few: príspevkov
|
few: príspevkov
|
||||||
one: príspevok
|
one: príspevok
|
||||||
other: príspevkov
|
other: príspevky
|
||||||
status_count_before: Ktorí napísali
|
status_count_before: Ktorí napísali
|
||||||
tagline: Následuj kamarátov, a objavuj nových
|
tagline: Následuj kamarátov, a objavuj nových
|
||||||
terms: Podmienky užívania
|
terms: Podmienky užívania
|
||||||
user_count_after:
|
user_count_after:
|
||||||
few: užívatelia
|
few: užívateľov
|
||||||
one: užívateľ
|
one: užívateľ
|
||||||
other: užívateľov
|
other: užívatelia
|
||||||
user_count_before: Domov pre
|
user_count_before: Domov pre
|
||||||
what_is_mastodon: Čo je Mastodon?
|
what_is_mastodon: Čo je Mastodon?
|
||||||
accounts:
|
accounts:
|
||||||
choices_html: "%{name}vé voľby:"
|
choices_html: "%{name}vé voľby:"
|
||||||
follow: Sleduj
|
follow: Následuj
|
||||||
followers:
|
followers:
|
||||||
few: Sledovatelia
|
few: Sledovateľov
|
||||||
one: Sledujúci
|
one: Sledovateľ
|
||||||
other: Sledovatelia
|
other: Sledovatelia
|
||||||
following: Sledovaní
|
following: Sledovaní
|
||||||
joined: Pridal/a sa v %{date}
|
joined: Pridal/a sa v %{date}
|
||||||
@ -70,8 +70,9 @@ sk:
|
|||||||
reserved_username: Prihlasovacie meno je rezervované
|
reserved_username: Prihlasovacie meno je rezervované
|
||||||
roles:
|
roles:
|
||||||
admin: Administrátor
|
admin: Administrátor
|
||||||
bot: Automat
|
bot: Bot
|
||||||
moderator: Moderátor
|
moderator: Moderátor
|
||||||
|
unavailable: Profil nieje dostupný
|
||||||
unfollow: Prestaň sledovať
|
unfollow: Prestaň sledovať
|
||||||
admin:
|
admin:
|
||||||
account_actions:
|
account_actions:
|
||||||
@ -84,12 +85,13 @@ sk:
|
|||||||
destroyed_msg: Moderátorska poznámka bola úspešne zmazaná!
|
destroyed_msg: Moderátorska poznámka bola úspešne zmazaná!
|
||||||
accounts:
|
accounts:
|
||||||
approve: Schváľ
|
approve: Schváľ
|
||||||
|
approve_all: Schváľ všetky
|
||||||
are_you_sure: Si si istý/á?
|
are_you_sure: Si si istý/á?
|
||||||
avatar: Maskot
|
avatar: Maskot
|
||||||
by_domain: Doména
|
by_domain: Doména
|
||||||
change_email:
|
change_email:
|
||||||
changed_msg: Email k tomuto účtu bol úspešne zmenený!
|
changed_msg: Email pre tento účet bol úspešne zmenený!
|
||||||
current_email: Súčastný email
|
current_email: Súčasný email
|
||||||
label: Zmeň email
|
label: Zmeň email
|
||||||
new_email: Nový email
|
new_email: Nový email
|
||||||
submit: Zmeň email
|
submit: Zmeň email
|
||||||
@ -97,37 +99,37 @@ sk:
|
|||||||
confirm: Potvrď
|
confirm: Potvrď
|
||||||
confirmed: Potvrdený
|
confirmed: Potvrdený
|
||||||
confirming: Potvrdzujúci
|
confirming: Potvrdzujúci
|
||||||
deleted: Zmazané
|
deleted: Vymazané
|
||||||
demote: Degradovať
|
demote: Degraduj
|
||||||
disable: Zablokuj
|
disable: Zablokuj
|
||||||
disable_two_factor_authentication: Zakáž 2FA
|
disable_two_factor_authentication: Zakáž 2FA
|
||||||
disabled: Blokovaný
|
disabled: Blokovaný
|
||||||
display_name: Zobraziť meno
|
display_name: Ukáž meno
|
||||||
domain: Doména
|
domain: Doména
|
||||||
edit: Uprav
|
edit: Uprav
|
||||||
email: Email
|
email: Email
|
||||||
email_status: Stav emailu
|
email_status: Stav emailu
|
||||||
enable: Povoliť
|
enable: Povoľ
|
||||||
enabled: Povolený
|
enabled: Povolený
|
||||||
feed_url: URL časovej osi
|
feed_url: URL adresa časovej osi
|
||||||
followers: Sledujúci
|
followers: Sledujúci
|
||||||
followers_url: URL sledujúcich
|
followers_url: URL adresa sledujúcich
|
||||||
follows: Sledovania
|
follows: Sledovania
|
||||||
header: Hlavička
|
header: Hlavička
|
||||||
inbox_url: URL prijatých správ
|
inbox_url: URL adresa prijatých správ
|
||||||
invited_by: Pozvaný/á užívateľom
|
invited_by: Pozvaný/á užívateľom
|
||||||
ip: IP
|
ip: IP adresa
|
||||||
joined: Pridal/a sa
|
joined: Pridal/a sa
|
||||||
location:
|
location:
|
||||||
all: Všetko
|
all: Všetko
|
||||||
local: Miestne
|
local: Miestne
|
||||||
remote: Federované
|
remote: Federované
|
||||||
title: Lokácia
|
title: Umiestnenie
|
||||||
login_status: Stav prihlásenia
|
login_status: Stav prihlásenia
|
||||||
media_attachments: Prílohy
|
media_attachments: Prílohy
|
||||||
memorialize: Zmeniť na "Navždy budeme spomínať"
|
memorialize: Zmeň na "Navždy budeme spomínať"
|
||||||
moderation:
|
moderation:
|
||||||
active: Aktívny
|
active: Aktívny/a
|
||||||
all: Všetko
|
all: Všetko
|
||||||
pending: Čakajúci
|
pending: Čakajúci
|
||||||
silenced: Umlčané
|
silenced: Umlčané
|
||||||
@ -135,21 +137,22 @@ sk:
|
|||||||
title: Moderácia
|
title: Moderácia
|
||||||
moderation_notes: Moderátorské poznámky
|
moderation_notes: Moderátorské poznámky
|
||||||
most_recent_activity: Posledná aktivita
|
most_recent_activity: Posledná aktivita
|
||||||
most_recent_ip: Posledná IP
|
most_recent_ip: Posledná IP adresa
|
||||||
no_limits_imposed: Nie sú stanovené žiadné obmedzenia
|
no_limits_imposed: Nie sú stanovené žiadné obmedzenia
|
||||||
not_subscribed: Neodoberá
|
not_subscribed: Neodoberá
|
||||||
outbox_url: URL poslaných
|
outbox_url: URL poslaných
|
||||||
pending: Vyžaduje posúdenie
|
pending: Vyžaduje posúdenie
|
||||||
perform_full_suspension: Vylúč
|
perform_full_suspension: Vylúč
|
||||||
profile_url: URL profilu
|
profile_url: URL adresa profilu
|
||||||
promote: Povýš
|
promote: Vyzdvihni
|
||||||
protocol: Protokol
|
protocol: Protokol
|
||||||
public: Verejná os
|
public: Verejná časová os
|
||||||
push_subscription_expires: PuSH odoberanie expiruje
|
push_subscription_expires: PuSH odoberanie expiruje
|
||||||
redownload: Obnov profil
|
redownload: Obnov profil
|
||||||
reject: Odmietni
|
reject: Zamietni
|
||||||
remove_avatar: Odstrániť avatár
|
reject_all: Zamietni všetky
|
||||||
remove_header: Odstráň hlavičku
|
remove_avatar: Vymaž avatar
|
||||||
|
remove_header: Vymaž hlavičku
|
||||||
resend_confirmation:
|
resend_confirmation:
|
||||||
already_confirmed: Tento užívateľ je už potvrdený
|
already_confirmed: Tento užívateľ je už potvrdený
|
||||||
send: Odošli potvrdzovací email znovu
|
send: Odošli potvrdzovací email znovu
|
||||||
@ -164,7 +167,7 @@ sk:
|
|||||||
staff: Člen
|
staff: Člen
|
||||||
user: Užívateľ
|
user: Užívateľ
|
||||||
salmon_url: Salmon adresa
|
salmon_url: Salmon adresa
|
||||||
search: Hľadať
|
search: Hľadaj
|
||||||
shared_inbox_url: URL zdieľanej schránky
|
shared_inbox_url: URL zdieľanej schránky
|
||||||
show:
|
show:
|
||||||
created_reports: Vytvorené hlásenia
|
created_reports: Vytvorené hlásenia
|
||||||
@ -172,15 +175,15 @@ sk:
|
|||||||
silence: Stíš
|
silence: Stíš
|
||||||
silenced: Utíšený/é
|
silenced: Utíšený/é
|
||||||
statuses: Príspevky
|
statuses: Príspevky
|
||||||
subscribe: Odoberať
|
subscribe: Odoberaj
|
||||||
suspended: Zablokovaní
|
suspended: Zablokovaní
|
||||||
title: Účty
|
title: Účty
|
||||||
unconfirmed_email: Nepotvrdený email
|
unconfirmed_email: Nepotvrdený email
|
||||||
undo_silenced: Zrušiť stíšenie
|
undo_silenced: Zruš stíšenie
|
||||||
undo_suspension: Zruš blokovanie
|
undo_suspension: Zruš blokovanie
|
||||||
unsubscribe: Prestaň odoberať
|
unsubscribe: Prestaň odoberať
|
||||||
username: Prezývka
|
username: Prezývka
|
||||||
warn: Varovať
|
warn: Varuj
|
||||||
web: Web
|
web: Web
|
||||||
action_logs:
|
action_logs:
|
||||||
actions:
|
actions:
|
||||||
@ -227,7 +230,7 @@ sk:
|
|||||||
disable: Zakázať
|
disable: Zakázať
|
||||||
disabled_msg: Emoji bolo úspešne zakázané
|
disabled_msg: Emoji bolo úspešne zakázané
|
||||||
emoji: Emotikony
|
emoji: Emotikony
|
||||||
enable: Povoliť
|
enable: Povoľ
|
||||||
enabled_msg: Emoji bolo úspešne povolené
|
enabled_msg: Emoji bolo úspešne povolené
|
||||||
image_hint: PNG do 50KB
|
image_hint: PNG do 50KB
|
||||||
listed: V zozname
|
listed: V zozname
|
||||||
@ -240,7 +243,7 @@ sk:
|
|||||||
unlisted: Nie je na zozname
|
unlisted: Nie je na zozname
|
||||||
update_failed_msg: Nebolo možné aktualizovať toto emoji
|
update_failed_msg: Nebolo možné aktualizovať toto emoji
|
||||||
updated_msg: Emoji bolo úspešne aktualizované!
|
updated_msg: Emoji bolo úspešne aktualizované!
|
||||||
upload: Nahrať
|
upload: Nahraj
|
||||||
dashboard:
|
dashboard:
|
||||||
backlog: odložené aktivity
|
backlog: odložené aktivity
|
||||||
config: Nastavenia
|
config: Nastavenia
|
||||||
@ -249,10 +252,11 @@ sk:
|
|||||||
feature_profile_directory: Katalóg profilov
|
feature_profile_directory: Katalóg profilov
|
||||||
feature_registrations: Registrácie
|
feature_registrations: Registrácie
|
||||||
feature_relay: Federovací mostík
|
feature_relay: Federovací mostík
|
||||||
|
feature_timeline_preview: Náhľad časovej osi
|
||||||
features: Vymoženosti
|
features: Vymoženosti
|
||||||
hidden_service: Federácia so skrytými službami
|
hidden_service: Federácia so skrytými službami
|
||||||
open_reports: otvorené hlásenia
|
open_reports: otvorené hlásenia
|
||||||
recent_users: Nedávny užívatelia
|
recent_users: Nedávni užívatelia
|
||||||
search: Celofrázové vyhľadávanie
|
search: Celofrázové vyhľadávanie
|
||||||
single_user_mode: Jednouživateľské rozhranie
|
single_user_mode: Jednouživateľské rozhranie
|
||||||
software: Softvér
|
software: Softvér
|
||||||
@ -264,10 +268,11 @@ sk:
|
|||||||
week_users_active: aktívni tento týždeň
|
week_users_active: aktívni tento týždeň
|
||||||
week_users_new: užívateľov počas tohto týždňa
|
week_users_new: užívateľov počas tohto týždňa
|
||||||
domain_blocks:
|
domain_blocks:
|
||||||
add_new: Pridaj nové doménové blokovanie
|
add_new: Blokuj novú doménu
|
||||||
created_msg: Doména je v procese blokovania
|
created_msg: Doména je v štádiu blokovania
|
||||||
destroyed_msg: Blokovanie domény bolo zrušené
|
destroyed_msg: Blokovanie domény bolo zrušené
|
||||||
domain: Doména
|
domain: Doména
|
||||||
|
existing_domain_block_html: Pre účet %{name} si už nahodil/a přísnejšie obmedzenie, najskôr ho teda musíš <a href="%{unblock_url}">odblokovať</a>.
|
||||||
new:
|
new:
|
||||||
create: Vytvor blokovanie domény
|
create: Vytvor blokovanie domény
|
||||||
hint: Blokovanie domény stále dovolí vytvárať nové účty v databázi, ale tieto budú spätne automaticky moderované.
|
hint: Blokovanie domény stále dovolí vytvárať nové účty v databázi, ale tieto budú spätne automaticky moderované.
|
||||||
@ -284,7 +289,7 @@ sk:
|
|||||||
rejecting_media: odmietanie médiálnych súborov
|
rejecting_media: odmietanie médiálnych súborov
|
||||||
rejecting_reports: odmietané hlásenia
|
rejecting_reports: odmietané hlásenia
|
||||||
severity:
|
severity:
|
||||||
silence: utíšené
|
silence: stíšený
|
||||||
suspend: vylúčený
|
suspend: vylúčený
|
||||||
show:
|
show:
|
||||||
affected_accounts:
|
affected_accounts:
|
||||||
@ -295,12 +300,12 @@ sk:
|
|||||||
silence: Zruš stíšenie všetkých existujúcich účtov z tejto domény
|
silence: Zruš stíšenie všetkých existujúcich účtov z tejto domény
|
||||||
suspend: Zruš suspendáciu všetkých existujúcich účtov z tejto domény
|
suspend: Zruš suspendáciu všetkých existujúcich účtov z tejto domény
|
||||||
title: Zruš blokovanie domény %{domain}
|
title: Zruš blokovanie domény %{domain}
|
||||||
undo: Vrátiť späť
|
undo: Vráť späť
|
||||||
undo: Odvolaj blokovanie domény
|
undo: Odvolaj blokovanie domény
|
||||||
email_domain_blocks:
|
email_domain_blocks:
|
||||||
add_new: Pridaj nový
|
add_new: Pridaj nový
|
||||||
created_msg: Emailová doména bola úspešne pridaná do zoznamu zakázaných
|
created_msg: Emailová doména bola úspešne pridaná do zoznamu zakázaných
|
||||||
delete: Zmazať
|
delete: Vymaž
|
||||||
destroyed_msg: Emailová doména bola úspešne vymazaná zo zoznamu zakázaných
|
destroyed_msg: Emailová doména bola úspešne vymazaná zo zoznamu zakázaných
|
||||||
domain: Doména
|
domain: Doména
|
||||||
new:
|
new:
|
||||||
@ -328,15 +333,15 @@ sk:
|
|||||||
total_reported: Nahlásenia o nich
|
total_reported: Nahlásenia o nich
|
||||||
total_storage: Mediálne prílohy
|
total_storage: Mediálne prílohy
|
||||||
invites:
|
invites:
|
||||||
deactivate_all: Pozastaviť všetky
|
deactivate_all: Pozastav všetky
|
||||||
filter:
|
filter:
|
||||||
all: Všetky
|
all: Všetky
|
||||||
available: Dostupné
|
available: Dostupné
|
||||||
expired: Vypršalo
|
expired: Vypršalo
|
||||||
title: Filtrovať
|
title: Filtruj
|
||||||
title: Pozvánky
|
title: Pozvánky
|
||||||
relays:
|
relays:
|
||||||
add_new: Pridaj novú priechodnú oporu
|
add_new: Pridaj nový federovací mostík
|
||||||
delete: Vymaž
|
delete: Vymaž
|
||||||
description_html: "<strong>Federovací mostík</strong> je prechodný server ktorý obmieňa veľké množstvá verejných príspevkov medzi tými servermi ktoré na od neho odoberajú, aj doňho prispievajú. <strong>Môže to pomôcť malým a stredným instanciám objavovať federovaný obsah</strong>, čo inak vyžaduje aby miestni užívatelia ručne následovali iných ľudí zo vzdialených instancií."
|
description_html: "<strong>Federovací mostík</strong> je prechodný server ktorý obmieňa veľké množstvá verejných príspevkov medzi tými servermi ktoré na od neho odoberajú, aj doňho prispievajú. <strong>Môže to pomôcť malým a stredným instanciám objavovať federovaný obsah</strong>, čo inak vyžaduje aby miestni užívatelia ručne následovali iných ľudí zo vzdialených instancií."
|
||||||
disable: Pozastav
|
disable: Pozastav
|
||||||
@ -344,7 +349,7 @@ sk:
|
|||||||
enable: Povoľ
|
enable: Povoľ
|
||||||
enable_hint: Ak povolíš, tvoj server bude odoberať všetky verejné príspevky z tohto mostu, a začne posielať verejné príspevky tvojho servera na tento most.
|
enable_hint: Ak povolíš, tvoj server bude odoberať všetky verejné príspevky z tohto mostu, a začne posielať verejné príspevky tvojho servera na tento most.
|
||||||
enabled: Povolené
|
enabled: Povolené
|
||||||
inbox_url: URL mostu
|
inbox_url: URL adresa mostu
|
||||||
pending: Čakám na povolenie od prechodného mostu
|
pending: Čakám na povolenie od prechodného mostu
|
||||||
save_and_enable: Uložiť a povoliť
|
save_and_enable: Uložiť a povoliť
|
||||||
setup: Nastav prepojenie s mostom
|
setup: Nastav prepojenie s mostom
|
||||||
@ -359,7 +364,7 @@ sk:
|
|||||||
report: nahlás
|
report: nahlás
|
||||||
action_taken_by: Zákrok vykonal/a
|
action_taken_by: Zákrok vykonal/a
|
||||||
are_you_sure: Si si istý/á?
|
are_you_sure: Si si istý/á?
|
||||||
assign_to_self: Priraď k sebe
|
assign_to_self: Priraď sebe
|
||||||
assigned: Priradený moderátor
|
assigned: Priradený moderátor
|
||||||
comment:
|
comment:
|
||||||
none: Žiadne
|
none: Žiadne
|
||||||
@ -379,7 +384,7 @@ sk:
|
|||||||
resolved: Vyriešené
|
resolved: Vyriešené
|
||||||
resolved_msg: Hlásenie úspešne vyriešené!
|
resolved_msg: Hlásenie úspešne vyriešené!
|
||||||
status: Stav
|
status: Stav
|
||||||
title: Reporty
|
title: Hlásenia
|
||||||
unassign: Odobrať
|
unassign: Odobrať
|
||||||
unresolved: Nevyriešené
|
unresolved: Nevyriešené
|
||||||
updated_at: Aktualizované
|
updated_at: Aktualizované
|
||||||
@ -391,7 +396,7 @@ sk:
|
|||||||
desc_html: Ak je prezývok viacero, každú oddeľte čiarkou. Možno zadať iba miestne, odomknuté účty. Pokiaľ necháte prázdne, je to pre všetkých miestnych administrátorov.
|
desc_html: Ak je prezývok viacero, každú oddeľte čiarkou. Možno zadať iba miestne, odomknuté účty. Pokiaľ necháte prázdne, je to pre všetkých miestnych administrátorov.
|
||||||
title: Štandardní následovníci nových užívateľov
|
title: Štandardní následovníci nových užívateľov
|
||||||
contact_information:
|
contact_information:
|
||||||
email: Pracovný e-mail
|
email: Pracovný email
|
||||||
username: Kontaktné užívateľské meno
|
username: Kontaktné užívateľské meno
|
||||||
custom_css:
|
custom_css:
|
||||||
desc_html: Uprav vzhľad pomocou CSS, ktoré je načítané na každej stránke
|
desc_html: Uprav vzhľad pomocou CSS, ktoré je načítané na každej stránke
|
||||||
@ -508,11 +513,13 @@ sk:
|
|||||||
invalid_url: Zadaná URL adresa je nesprávna
|
invalid_url: Zadaná URL adresa je nesprávna
|
||||||
regenerate_token: Znovu vygenerovať prístupový token
|
regenerate_token: Znovu vygenerovať prístupový token
|
||||||
token_regenerated: Prístupový token bol úspešne vygenerovaný znova
|
token_regenerated: Prístupový token bol úspešne vygenerovaný znova
|
||||||
warning: Na tieto údaje dávajte ohromný pozor. Nikdy ich s nikým nezďieľajte!
|
warning: Na tieto údaje dávaj ohromný pozor. Nikdy ich s nikým nezďieľaj!
|
||||||
your_token: Váš prístupový token
|
your_token: Tvoj prístupový token
|
||||||
auth:
|
auth:
|
||||||
|
apply_for_account: Vyžiadaj si pozvánku
|
||||||
change_password: Heslo
|
change_password: Heslo
|
||||||
confirm_email: Potvrdiť email
|
checkbox_agreement_html: Súhlasím s <a href="%{rules_path}" target="_blank">pravidlami servera</a>, aj s <a href="%{terms_path}" target="_blank">prevoznými podmienkami</a>
|
||||||
|
confirm_email: Potvrď email
|
||||||
delete_account: Vymaž účet
|
delete_account: Vymaž účet
|
||||||
delete_account_html: Pokiaľ chceš svoj účet odtiaľto vymazať, môžeš tak <a href="%{path}">urobiť tu</a>. Budeš požiadaný/á o potvrdenie tohto kroku.
|
delete_account_html: Pokiaľ chceš svoj účet odtiaľto vymazať, môžeš tak <a href="%{path}">urobiť tu</a>. Budeš požiadaný/á o potvrdenie tohto kroku.
|
||||||
didnt_get_confirmation: Neobdržal/a si kroky na potvrdenie?
|
didnt_get_confirmation: Neobdržal/a si kroky na potvrdenie?
|
||||||
@ -521,16 +528,17 @@ sk:
|
|||||||
login: Prihlás sa
|
login: Prihlás sa
|
||||||
logout: Odhlás sa
|
logout: Odhlás sa
|
||||||
migrate_account: Presúvam sa na iný účet
|
migrate_account: Presúvam sa na iný účet
|
||||||
migrate_account_html: Pokiaľ si želáš presmerovať tento účet na nejaký iný, môžeš si to <a href="%{path}">nastaviť tu</a>.
|
migrate_account_html: Ak si želáš presmerovať tento účet na nejaký iný, môžeš si to <a href="%{path}">nastaviť tu</a>.
|
||||||
or_log_in_with: Alebo prihlásiť z
|
or_log_in_with: Alebo prihlás s
|
||||||
providers:
|
providers:
|
||||||
cas: CAS
|
cas: CAS
|
||||||
saml: SAML
|
saml: SAML
|
||||||
register: Zaregistruj sa
|
register: Zaregistruj sa
|
||||||
resend_confirmation: Poslať potvrdzujúce pokyny znovu
|
resend_confirmation: Zašli potvrdzujúce pokyny znovu
|
||||||
reset_password: Resetovať heslo
|
reset_password: Obnov heslo
|
||||||
security: Zabezpečenie
|
security: Zabezpečenie
|
||||||
set_new_password: Nastaviť nové heslo
|
set_new_password: Nastav nové heslo
|
||||||
|
trouble_logging_in: Problém s prihlásením?
|
||||||
authorize_follow:
|
authorize_follow:
|
||||||
already_following: Tento účet už následuješ
|
already_following: Tento účet už následuješ
|
||||||
error: Naneštastie nastala chyba pri hľadaní vzdialeného účtu
|
error: Naneštastie nastala chyba pri hľadaní vzdialeného účtu
|
||||||
@ -636,10 +644,10 @@ sk:
|
|||||||
other: Niečo ešte stále nieje v poriadku! Prosím skontroluj všetky %{count} nižšie uvedené pochybenia
|
other: Niečo ešte stále nieje v poriadku! Prosím skontroluj všetky %{count} nižšie uvedené pochybenia
|
||||||
imports:
|
imports:
|
||||||
modes:
|
modes:
|
||||||
merge: Spojiť dohromady
|
merge: Spoj dohromady
|
||||||
merge_long: Ponechaj existujúce záznamy a pridaj k nim nové
|
merge_long: Ponechaj existujúce záznamy a pridaj k nim nové
|
||||||
overwrite: Prepíš
|
overwrite: Prepíš
|
||||||
overwrite_long: Nahraď súčasné záznamy s novými
|
overwrite_long: Nahraď súčasné záznamy novými
|
||||||
preface: Môžeš nahrať dáta ktoré si exportoval/a z iného Mastodon serveru, ako sú napríklad zoznamy ľudí ktorých sleduješ, alebo blokuješ.
|
preface: Môžeš nahrať dáta ktoré si exportoval/a z iného Mastodon serveru, ako sú napríklad zoznamy ľudí ktorých sleduješ, alebo blokuješ.
|
||||||
success: Tvoje dáta boli nahraté úspešne, a teraz budú spracované v danom čase
|
success: Tvoje dáta boli nahraté úspešne, a teraz budú spracované v danom čase
|
||||||
types:
|
types:
|
||||||
@ -647,10 +655,10 @@ sk:
|
|||||||
domain_blocking: Zoznam blokovaných domén
|
domain_blocking: Zoznam blokovaných domén
|
||||||
following: Zoznam sledovaných
|
following: Zoznam sledovaných
|
||||||
muting: Zoznam ignorovaných
|
muting: Zoznam ignorovaných
|
||||||
upload: Nahrať
|
upload: Nahraj
|
||||||
in_memoriam_html: V pamäti.
|
in_memoriam_html: V pamäti.
|
||||||
invites:
|
invites:
|
||||||
delete: Deaktivovať
|
delete: Deaktivuj
|
||||||
expired: Neplatné
|
expired: Neplatné
|
||||||
expires_in:
|
expires_in:
|
||||||
'1800': 30 minút
|
'1800': 30 minút
|
||||||
@ -661,13 +669,13 @@ sk:
|
|||||||
'86400': 1 deň
|
'86400': 1 deň
|
||||||
expires_in_prompt: Nikdy
|
expires_in_prompt: Nikdy
|
||||||
generate: Vygeneruj
|
generate: Vygeneruj
|
||||||
invited_by: 'Bol/a si pozvan/á užívateľom:'
|
invited_by: 'Bol/a si pozvaný/á užívateľom:'
|
||||||
max_uses:
|
max_uses:
|
||||||
few: "%{count} použitia"
|
few: "%{count} použitia"
|
||||||
one: jedno použitie
|
one: jedno použitie
|
||||||
other: "%{count} použití"
|
other: "%{count} použití"
|
||||||
max_uses_prompt: Bez limitov
|
max_uses_prompt: Bez obmedzení
|
||||||
prompt: Vygeneruj a zdieľaj linky s ostatnými aby mali umožnený prístup k tomuto serveru
|
prompt: Vygeneruj a zdieľaj linky s ostatnými, aby mali umožnený prístup k tomuto serveru
|
||||||
table:
|
table:
|
||||||
expires_at: Vyprší
|
expires_at: Vyprší
|
||||||
uses: Používa
|
uses: Používa
|
||||||
@ -692,16 +700,16 @@ sk:
|
|||||||
body: Tu nájdete krátky súhrn správ ktoré ste zmeškali od svojej poslednj návštevi od %{since}
|
body: Tu nájdete krátky súhrn správ ktoré ste zmeškali od svojej poslednj návštevi od %{since}
|
||||||
mention: "%{name} ťa spomenul/a v:"
|
mention: "%{name} ťa spomenul/a v:"
|
||||||
new_followers_summary:
|
new_followers_summary:
|
||||||
few: Taktiež, získal/a si %{count} nových následovníkov za tú dobu čo si bol/a preč. Yay!
|
few: Tiež si získal/a %{count} nových následovateľov za tú dobu čo si bol/a preč. Yay!
|
||||||
one: Taktiež, získal/a si jedného nového následovníka zatiaľ čo si bol/a preč. Yay!
|
one: Tiež si získal/a jedného nového následovateľa zatiaľ čo si bol/a preč. Yay!
|
||||||
other: Taktiež, získal/a si %{count} nových následovníkov za tú dobu čo si bol/a preč. Yay!
|
other: Tiež si získal/a %{count} nových následovateľov za tú dobu čo si bol/a preč. Yay!
|
||||||
subject:
|
subject:
|
||||||
few: "%{count} nové notifikácie od tvojej poslednej návštevy \U0001F418"
|
few: "%{count} nové notifikácie od tvojej poslednej návštevy \U0001F418"
|
||||||
one: "1 nová notifikácia od tvojej poslednej návštevy \U0001F418"
|
one: "1 nové oboznámenie od tvojej poslednej návštevy \U0001F418"
|
||||||
other: "%{count} nových notifikácií od tvojej poslednej návštevy \U0001F418"
|
other: "%{count} nových oboznámení od tvojej poslednej návštevy \U0001F418"
|
||||||
title: Zatiaľ čo si bol/a preč…
|
title: Zatiaľ čo si bol/a preč…
|
||||||
favourite:
|
favourite:
|
||||||
body: 'Tvoj príspevok bol uložený medi obľúbené užívateľa %{name}:'
|
body: 'Tvoj príspevok bol uložený medzi obľúbené užívateľa %{name}:'
|
||||||
subject: "%{name} si obľúbil/a tvoj príspevok"
|
subject: "%{name} si obľúbil/a tvoj príspevok"
|
||||||
title: Nové obľúbené
|
title: Nové obľúbené
|
||||||
follow:
|
follow:
|
||||||
@ -712,16 +720,16 @@ sk:
|
|||||||
action: Spravuj žiadosti o sledovanie
|
action: Spravuj žiadosti o sledovanie
|
||||||
body: "%{name} žiada povolenie ťa následovať"
|
body: "%{name} žiada povolenie ťa následovať"
|
||||||
subject: "%{name} ťa žiadá o možnosť sledovania"
|
subject: "%{name} ťa žiadá o možnosť sledovania"
|
||||||
title: Nová žiadosť o sledovanie
|
title: Nová žiadosť o následovanie
|
||||||
mention:
|
mention:
|
||||||
action: Odpovedať
|
action: Odpovedať
|
||||||
body: "%{name} ťa spomenul/a v:"
|
body: "%{name} ťa spomenul/a v:"
|
||||||
subject: Bol/a si spomenutý/á užívateľom %{name}
|
subject: Bol/a si spomenutý/á užívateľom %{name}
|
||||||
title: Novo spomenutý/á
|
title: Novo spomenutý/á
|
||||||
reblog:
|
reblog:
|
||||||
body: 'Tvoj príspevok bol pozdvihnutý užívateľom %{name}:'
|
body: 'Tvoj príspevok bol vyzdvihnutý užívateľom %{name}:'
|
||||||
subject: "%{name} pozdvihli tvoj príspevok"
|
subject: "%{name} vyzdvihli tvoj príspevok"
|
||||||
title: Novo pozdvyhnuté
|
title: Novo vyzdvyhnuté
|
||||||
number:
|
number:
|
||||||
human:
|
human:
|
||||||
decimal_units:
|
decimal_units:
|
||||||
@ -820,17 +828,23 @@ sk:
|
|||||||
revoke_success: Sezóna úspešne zamietnutá
|
revoke_success: Sezóna úspešne zamietnutá
|
||||||
title: Sezóny
|
title: Sezóny
|
||||||
settings:
|
settings:
|
||||||
|
account: Účet
|
||||||
|
account_settings: Nastavenia účtu
|
||||||
|
appearance: Vzhľad
|
||||||
authorized_apps: Povolené aplikácie
|
authorized_apps: Povolené aplikácie
|
||||||
back: Späť na Mastodon
|
back: Späť na Mastodon
|
||||||
delete: Vymazanie účtu
|
delete: Vymazanie účtu
|
||||||
development: Vývoj
|
development: Vývoj
|
||||||
edit_profile: Uprav profil
|
edit_profile: Uprav profil
|
||||||
export: Exportovať dáta
|
export: Exportuj dáta
|
||||||
featured_tags: Popredne zvýraznené haštagy
|
featured_tags: Zvýraznené haštagy
|
||||||
import: Importovať
|
import: Importuj
|
||||||
migrate: Presunutie účtu
|
import_and_export: Import a export
|
||||||
notifications: Oznámenia
|
migrate: Presuň účet
|
||||||
|
notifications: Oboznámenia
|
||||||
preferences: Voľby
|
preferences: Voľby
|
||||||
|
profile: Profil
|
||||||
|
relationships: Následovaní a následovatelia
|
||||||
two_factor_authentication: Dvoj-faktorové overenie
|
two_factor_authentication: Dvoj-faktorové overenie
|
||||||
statuses:
|
statuses:
|
||||||
attached:
|
attached:
|
||||||
@ -846,9 +860,9 @@ sk:
|
|||||||
boosted_from_html: Povýšené od %{acct_link}
|
boosted_from_html: Povýšené od %{acct_link}
|
||||||
content_warning: 'Varovanie o obsahu: %{warning}'
|
content_warning: 'Varovanie o obsahu: %{warning}'
|
||||||
disallowed_hashtags:
|
disallowed_hashtags:
|
||||||
few: 'obsahoval nepovolené hashtagy: %{tags}'
|
few: 'obsahoval nepovolené haštagy: %{tags}'
|
||||||
one: 'obsahoval nepovolený hashtag: %{tags}'
|
one: 'obsahoval nepovolený haštag: %{tags}'
|
||||||
other: 'obsahoval nepovolené hashtagy: %{tags}'
|
other: 'obsahoval nepovolené haštagy: %{tags}'
|
||||||
language_detection: Zisti automaticky
|
language_detection: Zisti automaticky
|
||||||
open_in_web: Otvor v okne na webe
|
open_in_web: Otvor v okne na webe
|
||||||
over_character_limit: limit %{max} znakov bol presiahnutý
|
over_character_limit: limit %{max} znakov bol presiahnutý
|
||||||
@ -856,7 +870,7 @@ sk:
|
|||||||
limit: Už si si pripol ten najvyšší možný počet hlášok
|
limit: Už si si pripol ten najvyšší možný počet hlášok
|
||||||
ownership: Nieje možné pripnúť hlášku od niekoho iného
|
ownership: Nieje možné pripnúť hlášku od niekoho iného
|
||||||
private: Neverejné príspevky nemôžu byť pripnuté
|
private: Neverejné príspevky nemôžu byť pripnuté
|
||||||
reblog: Pozdvihnutie sa nedá pripnúť
|
reblog: Vyzdvihnutie sa nedá pripnúť
|
||||||
poll:
|
poll:
|
||||||
total_votes:
|
total_votes:
|
||||||
few: "%{count} hlas(y)ov"
|
few: "%{count} hlas(y)ov"
|
||||||
@ -874,14 +888,14 @@ sk:
|
|||||||
unlisted: Nezaradené
|
unlisted: Nezaradené
|
||||||
unlisted_long: Všetci môžu vidieť, ale nieje zaradené do verejnej osi
|
unlisted_long: Všetci môžu vidieť, ale nieje zaradené do verejnej osi
|
||||||
stream_entries:
|
stream_entries:
|
||||||
pinned: Pripnutý toot
|
pinned: Pripnutý príspevok
|
||||||
reblogged: vyzdvihnutý
|
reblogged: vyzdvihnutý
|
||||||
sensitive_content: Senzitívny obsah
|
sensitive_content: Senzitívny obsah
|
||||||
terms:
|
terms:
|
||||||
body_html: |
|
body_html: |
|
||||||
<h2>Podmienky súkromia</h2>
|
<h2>Podmienky súkromia</h2>
|
||||||
|
|
||||||
<h3 id="collect">Aké informácie zbierame?</h3>
|
<h3 id="collect">Aké informácie sú zbierané?</h3>
|
||||||
|
|
||||||
<ul>
|
<ul>
|
||||||
<li><em>Základné informácie o účte</em>: Ak sa na tomto serveri zaregistruješ, budeš môcť byť požiadaný/á zadať prezývku, emailovú adresu a heslo. Budeš tiež môcť zadať aj ďalšie profilové údaje, ako napríklad meno a životopis, a nahrať profilovú fotku aj obrázok v záhlaví. Tvoja prezývka, meno, životopis, profilová fotka a obrázok v záhlaví sú vždy zobrazené verejne.</li><li><em>Príspevky, sledovania a iné verejné informácie</em>:
|
<li><em>Základné informácie o účte</em>: Ak sa na tomto serveri zaregistruješ, budeš môcť byť požiadaný/á zadať prezývku, emailovú adresu a heslo. Budeš tiež môcť zadať aj ďalšie profilové údaje, ako napríklad meno a životopis, a nahrať profilovú fotku aj obrázok v záhlaví. Tvoja prezývka, meno, životopis, profilová fotka a obrázok v záhlaví sú vždy zobrazené verejne.</li><li><em>Príspevky, sledovania a iné verejné informácie</em>:
|
||||||
@ -973,7 +987,7 @@ sk:
|
|||||||
invalid_otp_token: Neplatný kód pre dvojfaktorovú autentikáciu
|
invalid_otp_token: Neplatný kód pre dvojfaktorovú autentikáciu
|
||||||
otp_lost_help_html: Pokiaľ si stratil/a prístup k obom, môžeš dať vedieť %{email}
|
otp_lost_help_html: Pokiaľ si stratil/a prístup k obom, môžeš dať vedieť %{email}
|
||||||
seamless_external_login: Si prihlásená/ý cez externú službu, takže nastavenia hesla a emailu ti niesú prístupné.
|
seamless_external_login: Si prihlásená/ý cez externú službu, takže nastavenia hesla a emailu ti niesú prístupné.
|
||||||
signed_in_as: 'Prihlásený ako:'
|
signed_in_as: 'Prihlásená/ý ako:'
|
||||||
verification:
|
verification:
|
||||||
explanation_html: 'Môžeš sa <strong>overiť ako majiteľ odkazov v metadátach tvojho profilu</strong>. Na to musí ale odkazovaná stránka obsahovať odkaz späť na tvoj Mastodon profil. Tento spätný odkaz <strong>musí</strong> mať prívlastok <code>rel="me"</code>. Na texte odkazu nezáleží. Tu je príklad:'
|
explanation_html: 'Môžeš sa <strong>overiť ako majiteľ odkazov v metadátach tvojho profilu</strong>. Na to musí ale odkazovaná stránka obsahovať odkaz späť na tvoj Mastodon profil. Tento spätný odkaz <strong>musí</strong> mať prívlastok <code>rel="me"</code>. Na texte odkazu nezáleží. Tu je príklad:'
|
||||||
verification: Overenie
|
verification: Overenie
|
||||||
|
@ -0,0 +1,5 @@
|
|||||||
|
class AddBlurhashToMediaAttachments < ActiveRecord::Migration[5.2]
|
||||||
|
def change
|
||||||
|
add_column :media_attachments, :blurhash, :string
|
||||||
|
end
|
||||||
|
end
|
@ -10,7 +10,7 @@
|
|||||||
#
|
#
|
||||||
# It's strongly recommended that you check this file into your version control system.
|
# It's strongly recommended that you check this file into your version control system.
|
||||||
|
|
||||||
ActiveRecord::Schema.define(version: 2019_04_09_054914) do
|
ActiveRecord::Schema.define(version: 2019_04_20_025523) do
|
||||||
|
|
||||||
# These are extensions that must be enabled in order to support this database
|
# These are extensions that must be enabled in order to support this database
|
||||||
enable_extension "plpgsql"
|
enable_extension "plpgsql"
|
||||||
@ -362,6 +362,7 @@ ActiveRecord::Schema.define(version: 2019_04_09_054914) do
|
|||||||
t.bigint "account_id"
|
t.bigint "account_id"
|
||||||
t.text "description"
|
t.text "description"
|
||||||
t.bigint "scheduled_status_id"
|
t.bigint "scheduled_status_id"
|
||||||
|
t.string "blurhash"
|
||||||
t.index ["account_id"], name: "index_media_attachments_on_account_id"
|
t.index ["account_id"], name: "index_media_attachments_on_account_id"
|
||||||
t.index ["scheduled_status_id"], name: "index_media_attachments_on_scheduled_status_id"
|
t.index ["scheduled_status_id"], name: "index_media_attachments_on_scheduled_status_id"
|
||||||
t.index ["shortcode"], name: "index_media_attachments_on_shortcode", unique: true
|
t.index ["shortcode"], name: "index_media_attachments_on_shortcode", unique: true
|
||||||
|
@ -43,7 +43,7 @@ services:
|
|||||||
- external_network
|
- external_network
|
||||||
- internal_network
|
- internal_network
|
||||||
healthcheck:
|
healthcheck:
|
||||||
test: ["CMD-SHELL", "wget -q --spider --header 'x-forwarded-proto: https' --proxy off localhost:3000/api/v1/instance || exit 1"]
|
test: ["CMD-SHELL", "wget -q --spider --header 'x-forwarded-proto: https' --proxy=off localhost:3000/api/v1/instance || exit 1"]
|
||||||
ports:
|
ports:
|
||||||
- "127.0.0.1:3000:3000"
|
- "127.0.0.1:3000:3000"
|
||||||
depends_on:
|
depends_on:
|
||||||
@ -63,7 +63,7 @@ services:
|
|||||||
- external_network
|
- external_network
|
||||||
- internal_network
|
- internal_network
|
||||||
healthcheck:
|
healthcheck:
|
||||||
test: ["CMD-SHELL", "wget -q --spider --header 'x-forwarded-proto: https' --proxy off localhost:4000/api/v1/streaming/health || exit 1"]
|
test: ["CMD-SHELL", "wget -q --spider --header 'x-forwarded-proto: https' --proxy=off localhost:4000/api/v1/streaming/health || exit 1"]
|
||||||
ports:
|
ports:
|
||||||
- "127.0.0.1:4000:4000"
|
- "127.0.0.1:4000:4000"
|
||||||
depends_on:
|
depends_on:
|
||||||
|
@ -9,6 +9,7 @@ require_relative 'mastodon/search_cli'
|
|||||||
require_relative 'mastodon/settings_cli'
|
require_relative 'mastodon/settings_cli'
|
||||||
require_relative 'mastodon/statuses_cli'
|
require_relative 'mastodon/statuses_cli'
|
||||||
require_relative 'mastodon/domains_cli'
|
require_relative 'mastodon/domains_cli'
|
||||||
|
require_relative 'mastodon/cache_cli'
|
||||||
require_relative 'mastodon/version'
|
require_relative 'mastodon/version'
|
||||||
|
|
||||||
module Mastodon
|
module Mastodon
|
||||||
@ -41,6 +42,9 @@ module Mastodon
|
|||||||
desc 'domains SUBCOMMAND ...ARGS', 'Manage account domains'
|
desc 'domains SUBCOMMAND ...ARGS', 'Manage account domains'
|
||||||
subcommand 'domains', Mastodon::DomainsCLI
|
subcommand 'domains', Mastodon::DomainsCLI
|
||||||
|
|
||||||
|
desc 'cache SUBCOMMAND ...ARGS', 'Manage cache'
|
||||||
|
subcommand 'cache', Mastodon::CacheCLI
|
||||||
|
|
||||||
option :dry_run, type: :boolean
|
option :dry_run, type: :boolean
|
||||||
desc 'self-destruct', 'Erase the server from the federation'
|
desc 'self-destruct', 'Erase the server from the federation'
|
||||||
long_desc <<~LONG_DESC
|
long_desc <<~LONG_DESC
|
||||||
|
@ -73,7 +73,7 @@ module Mastodon
|
|||||||
def create(username)
|
def create(username)
|
||||||
account = Account.new(username: username)
|
account = Account.new(username: username)
|
||||||
password = SecureRandom.hex
|
password = SecureRandom.hex
|
||||||
user = User.new(email: options[:email], password: password, agreement: true, admin: options[:role] == 'admin', moderator: options[:role] == 'moderator', confirmed_at: options[:confirmed] ? Time.now.utc : nil)
|
user = User.new(email: options[:email], password: password, agreement: true, approved: true, admin: options[:role] == 'admin', moderator: options[:role] == 'moderator', confirmed_at: options[:confirmed] ? Time.now.utc : nil)
|
||||||
|
|
||||||
if options[:reattach]
|
if options[:reattach]
|
||||||
account = Account.find_local(username) || Account.new(username: username)
|
account = Account.find_local(username) || Account.new(username: username)
|
||||||
@ -115,6 +115,7 @@ module Mastodon
|
|||||||
option :enable, type: :boolean
|
option :enable, type: :boolean
|
||||||
option :disable, type: :boolean
|
option :disable, type: :boolean
|
||||||
option :disable_2fa, type: :boolean
|
option :disable_2fa, type: :boolean
|
||||||
|
option :approve, type: :boolean
|
||||||
desc 'modify USERNAME', 'Modify a user'
|
desc 'modify USERNAME', 'Modify a user'
|
||||||
long_desc <<-LONG_DESC
|
long_desc <<-LONG_DESC
|
||||||
Modify a user account.
|
Modify a user account.
|
||||||
@ -128,6 +129,9 @@ module Mastodon
|
|||||||
With the --disable option, lock the user out of their account. The
|
With the --disable option, lock the user out of their account. The
|
||||||
--enable option is the opposite.
|
--enable option is the opposite.
|
||||||
|
|
||||||
|
With the --approve option, the account will be approved, if it was
|
||||||
|
previously not due to not having open registrations.
|
||||||
|
|
||||||
With the --disable-2fa option, the two-factor authentication
|
With the --disable-2fa option, the two-factor authentication
|
||||||
requirement for the user can be removed.
|
requirement for the user can be removed.
|
||||||
LONG_DESC
|
LONG_DESC
|
||||||
@ -147,6 +151,7 @@ module Mastodon
|
|||||||
user.email = options[:email] if options[:email]
|
user.email = options[:email] if options[:email]
|
||||||
user.disabled = false if options[:enable]
|
user.disabled = false if options[:enable]
|
||||||
user.disabled = true if options[:disable]
|
user.disabled = true if options[:disable]
|
||||||
|
user.approved = true if options[:approve]
|
||||||
user.otp_required_for_login = false if options[:disable_2fa]
|
user.otp_required_for_login = false if options[:disable_2fa]
|
||||||
user.confirm if options[:confirm]
|
user.confirm if options[:confirm]
|
||||||
|
|
||||||
|
19
lib/mastodon/cache_cli.rb
Normal file
19
lib/mastodon/cache_cli.rb
Normal file
@ -0,0 +1,19 @@
|
|||||||
|
# frozen_string_literal: true
|
||||||
|
|
||||||
|
require_relative '../../config/boot'
|
||||||
|
require_relative '../../config/environment'
|
||||||
|
require_relative 'cli_helper'
|
||||||
|
|
||||||
|
module Mastodon
|
||||||
|
class CacheCLI < Thor
|
||||||
|
def self.exit_on_failure?
|
||||||
|
true
|
||||||
|
end
|
||||||
|
|
||||||
|
desc 'clear', 'Clear out the cache storage'
|
||||||
|
def clear
|
||||||
|
Rails.cache.clear
|
||||||
|
say('OK', :green)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
@ -13,7 +13,7 @@ module Mastodon
|
|||||||
end
|
end
|
||||||
|
|
||||||
def patch
|
def patch
|
||||||
0
|
2
|
||||||
end
|
end
|
||||||
|
|
||||||
def pre
|
def pre
|
||||||
@ -33,16 +33,16 @@ module Mastodon
|
|||||||
end
|
end
|
||||||
|
|
||||||
def repository
|
def repository
|
||||||
'tootsuite/mastodon'
|
ENV.fetch('GITHUB_REPOSITORY') { 'tootsuite/mastodon' }
|
||||||
end
|
end
|
||||||
|
|
||||||
def source_base_url
|
def source_base_url
|
||||||
"https://github.com/#{repository}"
|
ENV.fetch('SOURCE_BASE_URL') { "https://github.com/#{repository}" }
|
||||||
end
|
end
|
||||||
|
|
||||||
# specify git tag or commit hash here
|
# specify git tag or commit hash here
|
||||||
def source_tag
|
def source_tag
|
||||||
nil
|
ENV.fetch('SOURCE_TAG') { nil }
|
||||||
end
|
end
|
||||||
|
|
||||||
def source_url
|
def source_url
|
||||||
|
16
lib/paperclip/blurhash_transcoder.rb
Normal file
16
lib/paperclip/blurhash_transcoder.rb
Normal file
@ -0,0 +1,16 @@
|
|||||||
|
# frozen_string_literal: true
|
||||||
|
|
||||||
|
module Paperclip
|
||||||
|
class BlurhashTranscoder < Paperclip::Processor
|
||||||
|
def make
|
||||||
|
return @file unless options[:style] == :small
|
||||||
|
|
||||||
|
pixels = convert(':source RGB:-', source: File.expand_path(@file.path)).unpack('C*')
|
||||||
|
geometry = options.fetch(:file_geometry_parser).from_file(@file)
|
||||||
|
|
||||||
|
attachment.instance.blurhash = Blurhash.encode(geometry.width, geometry.height, pixels, options[:blurhash] || {})
|
||||||
|
|
||||||
|
@file
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
@ -78,6 +78,7 @@
|
|||||||
"babel-plugin-react-intl": "^3.0.1",
|
"babel-plugin-react-intl": "^3.0.1",
|
||||||
"babel-plugin-transform-react-remove-prop-types": "^0.4.24",
|
"babel-plugin-transform-react-remove-prop-types": "^0.4.24",
|
||||||
"babel-runtime": "^6.26.0",
|
"babel-runtime": "^6.26.0",
|
||||||
|
"blurhash": "^1.0.0",
|
||||||
"classnames": "^2.2.5",
|
"classnames": "^2.2.5",
|
||||||
"compression-webpack-plugin": "^2.0.0",
|
"compression-webpack-plugin": "^2.0.0",
|
||||||
"cross-env": "^5.1.4",
|
"cross-env": "^5.1.4",
|
||||||
|
@ -2,3 +2,4 @@
|
|||||||
|
|
||||||
User-agent: *
|
User-agent: *
|
||||||
Disallow: /media_proxy/
|
Disallow: /media_proxy/
|
||||||
|
Disallow: /interact/
|
||||||
|
@ -37,7 +37,7 @@ RSpec.describe Admin::DomainBlocksController, type: :controller do
|
|||||||
end
|
end
|
||||||
|
|
||||||
it 'renders new when failed to save' do
|
it 'renders new when failed to save' do
|
||||||
Fabricate(:domain_block, domain: 'example.com')
|
Fabricate(:domain_block, domain: 'example.com', severity: 'suspend')
|
||||||
allow(DomainBlockWorker).to receive(:perform_async).and_return(true)
|
allow(DomainBlockWorker).to receive(:perform_async).and_return(true)
|
||||||
|
|
||||||
post :create, params: { domain_block: { domain: 'example.com', severity: 'silence' } }
|
post :create, params: { domain_block: { domain: 'example.com', severity: 'silence' } }
|
||||||
@ -45,6 +45,17 @@ RSpec.describe Admin::DomainBlocksController, type: :controller do
|
|||||||
expect(DomainBlockWorker).not_to have_received(:perform_async)
|
expect(DomainBlockWorker).not_to have_received(:perform_async)
|
||||||
expect(response).to render_template :new
|
expect(response).to render_template :new
|
||||||
end
|
end
|
||||||
|
|
||||||
|
it 'allows upgrading a block' do
|
||||||
|
Fabricate(:domain_block, domain: 'example.com', severity: 'silence')
|
||||||
|
allow(DomainBlockWorker).to receive(:perform_async).and_return(true)
|
||||||
|
|
||||||
|
post :create, params: { domain_block: { domain: 'example.com', severity: 'silence', reject_media: true, reject_reports: true } }
|
||||||
|
|
||||||
|
expect(DomainBlockWorker).to have_received(:perform_async)
|
||||||
|
expect(flash[:notice]).to eq I18n.t('admin.domain_blocks.created_msg')
|
||||||
|
expect(response).to redirect_to(admin_instances_path(limited: '1'))
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
describe 'DELETE #destroy' do
|
describe 'DELETE #destroy' do
|
||||||
|
@ -107,6 +107,89 @@ RSpec.describe Auth::RegistrationsController, type: :controller do
|
|||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
context 'approval-based registrations without invite' do
|
||||||
|
around do |example|
|
||||||
|
registrations_mode = Setting.registrations_mode
|
||||||
|
example.run
|
||||||
|
Setting.registrations_mode = registrations_mode
|
||||||
|
end
|
||||||
|
|
||||||
|
subject do
|
||||||
|
Setting.registrations_mode = 'approved'
|
||||||
|
request.headers["Accept-Language"] = accept_language
|
||||||
|
post :create, params: { user: { account_attributes: { username: 'test' }, email: 'test@example.com', password: '12345678', password_confirmation: '12345678' } }
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'redirects to login page' do
|
||||||
|
subject
|
||||||
|
expect(response).to redirect_to new_user_session_path
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'creates user' do
|
||||||
|
subject
|
||||||
|
user = User.find_by(email: 'test@example.com')
|
||||||
|
expect(user).to_not be_nil
|
||||||
|
expect(user.locale).to eq(accept_language)
|
||||||
|
expect(user.approved).to eq(false)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
context 'approval-based registrations with expired invite' do
|
||||||
|
around do |example|
|
||||||
|
registrations_mode = Setting.registrations_mode
|
||||||
|
example.run
|
||||||
|
Setting.registrations_mode = registrations_mode
|
||||||
|
end
|
||||||
|
|
||||||
|
subject do
|
||||||
|
Setting.registrations_mode = 'approved'
|
||||||
|
request.headers["Accept-Language"] = accept_language
|
||||||
|
invite = Fabricate(:invite, max_uses: nil, expires_at: 1.hour.ago)
|
||||||
|
post :create, params: { user: { account_attributes: { username: 'test' }, email: 'test@example.com', password: '12345678', password_confirmation: '12345678', 'invite_code': invite.code } }
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'redirects to login page' do
|
||||||
|
subject
|
||||||
|
expect(response).to redirect_to new_user_session_path
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'creates user' do
|
||||||
|
subject
|
||||||
|
user = User.find_by(email: 'test@example.com')
|
||||||
|
expect(user).to_not be_nil
|
||||||
|
expect(user.locale).to eq(accept_language)
|
||||||
|
expect(user.approved).to eq(false)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
context 'approval-based registrations with valid invite' do
|
||||||
|
around do |example|
|
||||||
|
registrations_mode = Setting.registrations_mode
|
||||||
|
example.run
|
||||||
|
Setting.registrations_mode = registrations_mode
|
||||||
|
end
|
||||||
|
|
||||||
|
subject do
|
||||||
|
Setting.registrations_mode = 'approved'
|
||||||
|
request.headers["Accept-Language"] = accept_language
|
||||||
|
invite = Fabricate(:invite, max_uses: nil, expires_at: 1.hour.from_now)
|
||||||
|
post :create, params: { user: { account_attributes: { username: 'test' }, email: 'test@example.com', password: '12345678', password_confirmation: '12345678', 'invite_code': invite.code } }
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'redirects to login page' do
|
||||||
|
subject
|
||||||
|
expect(response).to redirect_to new_user_session_path
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'creates user' do
|
||||||
|
subject
|
||||||
|
user = User.find_by(email: 'test@example.com')
|
||||||
|
expect(user).to_not be_nil
|
||||||
|
expect(user.locale).to eq(accept_language)
|
||||||
|
expect(user.approved).to eq(true)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
it 'does nothing if user already exists' do
|
it 'does nothing if user already exists' do
|
||||||
Fabricate(:user, account: Fabricate(:account, username: 'test'))
|
Fabricate(:user, account: Fabricate(:account, username: 'test'))
|
||||||
subject
|
subject
|
||||||
|
@ -36,4 +36,35 @@ RSpec.describe DomainBlock, type: :model do
|
|||||||
expect(DomainBlock.blocked?('domain')).to eq false
|
expect(DomainBlock.blocked?('domain')).to eq false
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
describe 'stricter_than?' do
|
||||||
|
it 'returns true if the new block has suspend severity while the old has lower severity' do
|
||||||
|
suspend = DomainBlock.new(domain: 'domain', severity: :suspend)
|
||||||
|
silence = DomainBlock.new(domain: 'domain', severity: :silence)
|
||||||
|
noop = DomainBlock.new(domain: 'domain', severity: :noop)
|
||||||
|
expect(suspend.stricter_than?(silence)).to be true
|
||||||
|
expect(suspend.stricter_than?(noop)).to be true
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'returns false if the new block has lower severity than the old one' do
|
||||||
|
suspend = DomainBlock.new(domain: 'domain', severity: :suspend)
|
||||||
|
silence = DomainBlock.new(domain: 'domain', severity: :silence)
|
||||||
|
noop = DomainBlock.new(domain: 'domain', severity: :noop)
|
||||||
|
expect(silence.stricter_than?(suspend)).to be false
|
||||||
|
expect(noop.stricter_than?(suspend)).to be false
|
||||||
|
expect(noop.stricter_than?(silence)).to be false
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'returns false if the new block does is less strict regarding reports' do
|
||||||
|
older = DomainBlock.new(domain: 'domain', severity: :silence, reject_reports: true)
|
||||||
|
newer = DomainBlock.new(domain: 'domain', severity: :silence, reject_reports: false)
|
||||||
|
expect(newer.stricter_than?(older)).to be false
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'returns false if the new block does is less strict regarding media' do
|
||||||
|
older = DomainBlock.new(domain: 'domain', severity: :silence, reject_media: true)
|
||||||
|
newer = DomainBlock.new(domain: 'domain', severity: :silence, reject_media: false)
|
||||||
|
expect(newer.stricter_than?(older)).to be false
|
||||||
|
end
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
@ -8,6 +8,7 @@ RSpec.describe BlacklistedEmailValidator, type: :validator do
|
|||||||
let(:errors) { double(add: nil) }
|
let(:errors) { double(add: nil) }
|
||||||
|
|
||||||
before do
|
before do
|
||||||
|
allow(user).to receive(:invited?) { false }
|
||||||
allow_any_instance_of(described_class).to receive(:blocked_email?) { blocked_email }
|
allow_any_instance_of(described_class).to receive(:blocked_email?) { blocked_email }
|
||||||
described_class.new.validate(user)
|
described_class.new.validate(user)
|
||||||
end
|
end
|
||||||
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue
Block a user