Compare commits
1 commit
master
...
feature-im
Author | SHA1 | Date | |
---|---|---|---|
c370e8026b |
|
@ -1,6 +1,12 @@
|
||||||
engines:
|
engines:
|
||||||
duplication:
|
duplication:
|
||||||
enabled: false
|
enabled: true
|
||||||
|
exclude_paths:
|
||||||
|
- app/assets/javascripts/components/locales/
|
||||||
|
config:
|
||||||
|
languages:
|
||||||
|
- ruby
|
||||||
|
- javascript
|
||||||
rubocop:
|
rubocop:
|
||||||
enabled: true
|
enabled: true
|
||||||
eslint:
|
eslint:
|
||||||
|
|
|
@ -14,7 +14,6 @@ addons:
|
||||||
postgresql: 9.4
|
postgresql: 9.4
|
||||||
|
|
||||||
rvm:
|
rvm:
|
||||||
- 2.3.4
|
|
||||||
- 2.4.1
|
- 2.4.1
|
||||||
|
|
||||||
services:
|
services:
|
||||||
|
@ -38,4 +37,3 @@ before_script:
|
||||||
script:
|
script:
|
||||||
- bundle exec rspec
|
- bundle exec rspec
|
||||||
- npm test
|
- npm test
|
||||||
- i18n-tasks unused
|
|
||||||
|
|
|
@ -12,22 +12,20 @@ WORKDIR /mastodon
|
||||||
|
|
||||||
COPY Gemfile Gemfile.lock package.json yarn.lock /mastodon/
|
COPY Gemfile Gemfile.lock package.json yarn.lock /mastodon/
|
||||||
|
|
||||||
RUN echo "@edge https://nl.alpinelinux.org/alpine/edge/main" >> /etc/apk/repositories \
|
RUN BUILD_DEPS=" \
|
||||||
&& BUILD_DEPS=" \
|
|
||||||
postgresql-dev \
|
postgresql-dev \
|
||||||
libxml2-dev \
|
libxml2-dev \
|
||||||
libxslt-dev \
|
libxslt-dev \
|
||||||
build-base" \
|
build-base" \
|
||||||
&& apk -U upgrade && apk add \
|
&& apk -U upgrade && apk add \
|
||||||
$BUILD_DEPS \
|
$BUILD_DEPS \
|
||||||
nodejs@edge \
|
nodejs \
|
||||||
nodejs-npm@edge \
|
|
||||||
libpq \
|
libpq \
|
||||||
libxml2 \
|
libxml2 \
|
||||||
libxslt \
|
libxslt \
|
||||||
ffmpeg \
|
ffmpeg \
|
||||||
file \
|
file \
|
||||||
imagemagick@edge \
|
imagemagick \
|
||||||
&& npm install -g npm@3 && npm install -g yarn \
|
&& npm install -g npm@3 && npm install -g yarn \
|
||||||
&& bundle install --deployment --without test development \
|
&& bundle install --deployment --without test development \
|
||||||
&& yarn --ignore-optional \
|
&& yarn --ignore-optional \
|
||||||
|
|
9
Gemfile
9
Gemfile
|
@ -1,13 +1,14 @@
|
||||||
# frozen_string_literal: true
|
# frozen_string_literal: true
|
||||||
|
|
||||||
source 'https://rubygems.org'
|
source 'https://rubygems.org'
|
||||||
ruby '>= 2.3.0', '< 2.5.0'
|
ruby '2.4.1'
|
||||||
|
|
||||||
gem 'pkg-config'
|
gem 'pkg-config'
|
||||||
|
|
||||||
gem 'rails', '~> 5.0.2'
|
gem 'rails', '~> 5.0.2'
|
||||||
gem 'sass-rails', '~> 5.0'
|
gem 'sass-rails', '~> 5.0'
|
||||||
gem 'uglifier', '>= 1.3.0'
|
gem 'uglifier', '>= 1.3.0'
|
||||||
|
gem 'coffee-rails', '~> 4.1.0'
|
||||||
gem 'jquery-rails'
|
gem 'jquery-rails'
|
||||||
gem 'puma'
|
gem 'puma'
|
||||||
|
|
||||||
|
@ -21,7 +22,6 @@ gem 'best_in_place', '~> 3.0.1'
|
||||||
gem 'paperclip', '~> 5.1'
|
gem 'paperclip', '~> 5.1'
|
||||||
gem 'paperclip-av-transcoder'
|
gem 'paperclip-av-transcoder'
|
||||||
gem 'aws-sdk', '>= 2.0'
|
gem 'aws-sdk', '>= 2.0'
|
||||||
gem 'fog'
|
|
||||||
|
|
||||||
gem 'addressable'
|
gem 'addressable'
|
||||||
gem 'devise'
|
gem 'devise'
|
||||||
|
@ -38,7 +38,7 @@ gem 'kaminari'
|
||||||
gem 'link_header'
|
gem 'link_header'
|
||||||
gem 'nokogiri'
|
gem 'nokogiri'
|
||||||
gem 'oj'
|
gem 'oj'
|
||||||
gem 'ostatus2', '~> 1.1'
|
gem 'ostatus2'
|
||||||
gem 'ox'
|
gem 'ox'
|
||||||
gem 'rabl'
|
gem 'rabl'
|
||||||
gem 'rack-attack'
|
gem 'rack-attack'
|
||||||
|
@ -57,7 +57,6 @@ gem 'sprockets-rails', :require => 'sprockets/railtie'
|
||||||
gem 'statsd-instrument'
|
gem 'statsd-instrument'
|
||||||
gem 'twitter-text'
|
gem 'twitter-text'
|
||||||
gem 'tzinfo-data'
|
gem 'tzinfo-data'
|
||||||
gem 'whatlanguage'
|
|
||||||
|
|
||||||
gem 'react-rails'
|
gem 'react-rails'
|
||||||
gem 'browserify-rails'
|
gem 'browserify-rails'
|
||||||
|
@ -90,7 +89,7 @@ group :development do
|
||||||
gem 'bullet'
|
gem 'bullet'
|
||||||
gem 'active_record_query_trace'
|
gem 'active_record_query_trace'
|
||||||
|
|
||||||
gem 'capistrano', '3.8.0'
|
gem 'capistrano'
|
||||||
gem 'capistrano-rails'
|
gem 'capistrano-rails'
|
||||||
gem 'capistrano-rbenv'
|
gem 'capistrano-rbenv'
|
||||||
gem 'capistrano-yarn'
|
gem 'capistrano-yarn'
|
||||||
|
|
163
Gemfile.lock
163
Gemfile.lock
|
@ -1,7 +1,6 @@
|
||||||
GEM
|
GEM
|
||||||
remote: https://rubygems.org/
|
remote: https://rubygems.org/
|
||||||
specs:
|
specs:
|
||||||
CFPropertyList (2.3.5)
|
|
||||||
actioncable (5.0.2)
|
actioncable (5.0.2)
|
||||||
actionpack (= 5.0.2)
|
actionpack (= 5.0.2)
|
||||||
nio4r (>= 1.2, < 3.0)
|
nio4r (>= 1.2, < 3.0)
|
||||||
|
@ -42,7 +41,7 @@ GEM
|
||||||
tzinfo (~> 1.1)
|
tzinfo (~> 1.1)
|
||||||
addressable (2.5.1)
|
addressable (2.5.1)
|
||||||
public_suffix (~> 2.0, >= 2.0.2)
|
public_suffix (~> 2.0, >= 2.0.2)
|
||||||
airbrussh (1.2.0)
|
airbrussh (1.1.2)
|
||||||
sshkit (>= 1.6.1, != 1.7.0)
|
sshkit (>= 1.6.1, != 1.7.0)
|
||||||
arel (7.1.4)
|
arel (7.1.4)
|
||||||
ast (2.3.0)
|
ast (2.3.0)
|
||||||
|
@ -112,6 +111,13 @@ GEM
|
||||||
cocaine (0.5.8)
|
cocaine (0.5.8)
|
||||||
climate_control (>= 0.0.3, < 1.0)
|
climate_control (>= 0.0.3, < 1.0)
|
||||||
coderay (1.1.1)
|
coderay (1.1.1)
|
||||||
|
coffee-rails (4.1.1)
|
||||||
|
coffee-script (>= 2.2.0)
|
||||||
|
railties (>= 4.0.0, < 5.1.x)
|
||||||
|
coffee-script (2.4.1)
|
||||||
|
coffee-script-source
|
||||||
|
execjs
|
||||||
|
coffee-script-source (1.12.2)
|
||||||
colorize (0.8.1)
|
colorize (0.8.1)
|
||||||
concurrent-ruby (1.0.5)
|
concurrent-ruby (1.0.5)
|
||||||
connection_pool (2.2.1)
|
connection_pool (2.2.1)
|
||||||
|
@ -146,142 +152,13 @@ GEM
|
||||||
thread_safe
|
thread_safe
|
||||||
encryptor (3.0.0)
|
encryptor (3.0.0)
|
||||||
erubis (2.7.0)
|
erubis (2.7.0)
|
||||||
excon (0.55.0)
|
|
||||||
execjs (2.7.0)
|
execjs (2.7.0)
|
||||||
fabrication (2.16.1)
|
fabrication (2.16.1)
|
||||||
faker (1.7.3)
|
faker (1.7.3)
|
||||||
i18n (~> 0.5)
|
i18n (~> 0.5)
|
||||||
fast_blank (1.0.0)
|
fast_blank (1.0.0)
|
||||||
fission (0.5.0)
|
|
||||||
CFPropertyList (~> 2.2)
|
|
||||||
fog (1.38.0)
|
|
||||||
fog-aliyun (>= 0.1.0)
|
|
||||||
fog-atmos
|
|
||||||
fog-aws (>= 0.6.0)
|
|
||||||
fog-brightbox (~> 0.4)
|
|
||||||
fog-cloudatcost (~> 0.1.0)
|
|
||||||
fog-core (~> 1.32)
|
|
||||||
fog-dynect (~> 0.0.2)
|
|
||||||
fog-ecloud (~> 0.1)
|
|
||||||
fog-google (<= 0.1.0)
|
|
||||||
fog-json
|
|
||||||
fog-local
|
|
||||||
fog-openstack
|
|
||||||
fog-powerdns (>= 0.1.1)
|
|
||||||
fog-profitbricks
|
|
||||||
fog-rackspace
|
|
||||||
fog-radosgw (>= 0.0.2)
|
|
||||||
fog-riakcs
|
|
||||||
fog-sakuracloud (>= 0.0.4)
|
|
||||||
fog-serverlove
|
|
||||||
fog-softlayer
|
|
||||||
fog-storm_on_demand
|
|
||||||
fog-terremark
|
|
||||||
fog-vmfusion
|
|
||||||
fog-voxel
|
|
||||||
fog-vsphere (>= 0.4.0)
|
|
||||||
fog-xenserver
|
|
||||||
fog-xml (~> 0.1.1)
|
|
||||||
ipaddress (~> 0.5)
|
|
||||||
fog-aliyun (0.1.0)
|
|
||||||
fog-core (~> 1.27)
|
|
||||||
fog-json (~> 1.0)
|
|
||||||
ipaddress (~> 0.8)
|
|
||||||
xml-simple (~> 1.1)
|
|
||||||
fog-atmos (0.1.0)
|
|
||||||
fog-core
|
|
||||||
fog-xml
|
|
||||||
fog-aws (1.3.0)
|
|
||||||
fog-core (~> 1.38)
|
|
||||||
fog-json (~> 1.0)
|
|
||||||
fog-xml (~> 0.1)
|
|
||||||
ipaddress (~> 0.8)
|
|
||||||
fog-brightbox (0.11.0)
|
|
||||||
fog-core (~> 1.22)
|
|
||||||
fog-json
|
|
||||||
inflecto (~> 0.0.2)
|
|
||||||
fog-cloudatcost (0.1.2)
|
|
||||||
fog-core (~> 1.36)
|
|
||||||
fog-json (~> 1.0)
|
|
||||||
fog-xml (~> 0.1)
|
|
||||||
ipaddress (~> 0.8)
|
|
||||||
fog-core (1.43.0)
|
|
||||||
builder
|
|
||||||
excon (~> 0.49)
|
|
||||||
formatador (~> 0.2)
|
|
||||||
fog-dynect (0.0.3)
|
|
||||||
fog-core
|
|
||||||
fog-json
|
|
||||||
fog-xml
|
|
||||||
fog-ecloud (0.3.0)
|
|
||||||
fog-core
|
|
||||||
fog-xml
|
|
||||||
fog-google (0.1.0)
|
|
||||||
fog-core
|
|
||||||
fog-json
|
|
||||||
fog-xml
|
|
||||||
fog-json (1.0.2)
|
|
||||||
fog-core (~> 1.0)
|
|
||||||
multi_json (~> 1.10)
|
|
||||||
fog-local (0.3.1)
|
|
||||||
fog-core (~> 1.27)
|
|
||||||
fog-openstack (0.1.20)
|
|
||||||
fog-core (>= 1.40)
|
|
||||||
fog-json (>= 1.0)
|
|
||||||
ipaddress (>= 0.8)
|
|
||||||
fog-powerdns (0.1.1)
|
|
||||||
fog-core (~> 1.27)
|
|
||||||
fog-json (~> 1.0)
|
|
||||||
fog-xml (~> 0.1)
|
|
||||||
fog-profitbricks (3.0.0)
|
|
||||||
fog-core (~> 1.42)
|
|
||||||
fog-json (~> 1.0)
|
|
||||||
fog-rackspace (0.1.4)
|
|
||||||
fog-core (>= 1.35)
|
|
||||||
fog-json (>= 1.0)
|
|
||||||
fog-xml (>= 0.1)
|
|
||||||
ipaddress (>= 0.8)
|
|
||||||
fog-radosgw (0.0.5)
|
|
||||||
fog-core (>= 1.21.0)
|
|
||||||
fog-json
|
|
||||||
fog-xml (>= 0.0.1)
|
|
||||||
fog-riakcs (0.1.0)
|
|
||||||
fog-core
|
|
||||||
fog-json
|
|
||||||
fog-xml
|
|
||||||
fog-sakuracloud (1.7.5)
|
|
||||||
fog-core
|
|
||||||
fog-json
|
|
||||||
fog-serverlove (0.1.2)
|
|
||||||
fog-core
|
|
||||||
fog-json
|
|
||||||
fog-softlayer (1.1.4)
|
|
||||||
fog-core
|
|
||||||
fog-json
|
|
||||||
fog-storm_on_demand (0.1.1)
|
|
||||||
fog-core
|
|
||||||
fog-json
|
|
||||||
fog-terremark (0.1.0)
|
|
||||||
fog-core
|
|
||||||
fog-xml
|
|
||||||
fog-vmfusion (0.1.0)
|
|
||||||
fission
|
|
||||||
fog-core
|
|
||||||
fog-voxel (0.1.0)
|
|
||||||
fog-core
|
|
||||||
fog-xml
|
|
||||||
fog-vsphere (1.9.1)
|
|
||||||
fog-core
|
|
||||||
rbvmomi (~> 1.9)
|
|
||||||
fog-xenserver (0.3.0)
|
|
||||||
fog-core
|
|
||||||
fog-xml
|
|
||||||
fog-xml (0.1.3)
|
|
||||||
fog-core
|
|
||||||
nokogiri (>= 1.5.11, < 2.0.0)
|
|
||||||
font-awesome-rails (4.7.0.1)
|
font-awesome-rails (4.7.0.1)
|
||||||
railties (>= 3.2, < 5.1)
|
railties (>= 3.2, < 5.1)
|
||||||
formatador (0.2.5)
|
|
||||||
fuubar (2.2.0)
|
fuubar (2.2.0)
|
||||||
rspec-core (~> 3.0)
|
rspec-core (~> 3.0)
|
||||||
ruby-progressbar (~> 1.4)
|
ruby-progressbar (~> 1.4)
|
||||||
|
@ -327,8 +204,6 @@ GEM
|
||||||
parser (>= 2.2.3.0)
|
parser (>= 2.2.3.0)
|
||||||
rainbow (~> 2.2)
|
rainbow (~> 2.2)
|
||||||
terminal-table (>= 1.5.1)
|
terminal-table (>= 1.5.1)
|
||||||
inflecto (0.0.2)
|
|
||||||
ipaddress (0.8.3)
|
|
||||||
jmespath (1.3.1)
|
jmespath (1.3.1)
|
||||||
jquery-rails (4.3.1)
|
jquery-rails (4.3.1)
|
||||||
rails-dom-testing (>= 1, < 3)
|
rails-dom-testing (>= 1, < 3)
|
||||||
|
@ -375,7 +250,6 @@ GEM
|
||||||
mimemagic (0.3.2)
|
mimemagic (0.3.2)
|
||||||
mini_portile2 (2.1.0)
|
mini_portile2 (2.1.0)
|
||||||
minitest (5.10.1)
|
minitest (5.10.1)
|
||||||
multi_json (1.12.1)
|
|
||||||
net-scp (1.2.1)
|
net-scp (1.2.1)
|
||||||
net-ssh (>= 2.6.5)
|
net-ssh (>= 2.6.5)
|
||||||
net-ssh (4.1.0)
|
net-ssh (4.1.0)
|
||||||
|
@ -383,13 +257,11 @@ GEM
|
||||||
nokogiri (1.7.1)
|
nokogiri (1.7.1)
|
||||||
mini_portile2 (~> 2.1.0)
|
mini_portile2 (~> 2.1.0)
|
||||||
oj (2.18.5)
|
oj (2.18.5)
|
||||||
openssl (2.0.3)
|
|
||||||
orm_adapter (0.5.0)
|
orm_adapter (0.5.0)
|
||||||
ostatus2 (1.1.0)
|
ostatus2 (1.0.2)
|
||||||
addressable (~> 2.4)
|
addressable (~> 2.4)
|
||||||
http (~> 2.0)
|
http (~> 2.0)
|
||||||
nokogiri (~> 1.6)
|
nokogiri (~> 1.6)
|
||||||
openssl (~> 2.0)
|
|
||||||
ox (2.4.11)
|
ox (2.4.11)
|
||||||
paperclip (5.1.0)
|
paperclip (5.1.0)
|
||||||
activemodel (>= 4.2.0)
|
activemodel (>= 4.2.0)
|
||||||
|
@ -465,11 +337,6 @@ GEM
|
||||||
thor (>= 0.18.1, < 2.0)
|
thor (>= 0.18.1, < 2.0)
|
||||||
rainbow (2.2.1)
|
rainbow (2.2.1)
|
||||||
rake (12.0.0)
|
rake (12.0.0)
|
||||||
rbvmomi (1.11.0)
|
|
||||||
builder (~> 3.0)
|
|
||||||
json (>= 1.8)
|
|
||||||
nokogiri (~> 1.5)
|
|
||||||
trollop (~> 2.1)
|
|
||||||
react-rails (1.11.0)
|
react-rails (1.11.0)
|
||||||
babel-transpiler (>= 0.7.0)
|
babel-transpiler (>= 0.7.0)
|
||||||
connection_pool
|
connection_pool
|
||||||
|
@ -571,7 +438,6 @@ GEM
|
||||||
thread (0.2.2)
|
thread (0.2.2)
|
||||||
thread_safe (0.3.6)
|
thread_safe (0.3.6)
|
||||||
tilt (2.0.7)
|
tilt (2.0.7)
|
||||||
trollop (2.1.2)
|
|
||||||
twitter-text (1.14.5)
|
twitter-text (1.14.5)
|
||||||
unf (~> 0.1.0)
|
unf (~> 0.1.0)
|
||||||
tzinfo (1.2.3)
|
tzinfo (1.2.3)
|
||||||
|
@ -582,7 +448,7 @@ GEM
|
||||||
execjs (>= 0.3.0, < 3)
|
execjs (>= 0.3.0, < 3)
|
||||||
unf (0.1.4)
|
unf (0.1.4)
|
||||||
unf_ext
|
unf_ext
|
||||||
unf_ext (0.0.7.3)
|
unf_ext (0.0.7.2)
|
||||||
unicode-display_width (1.1.3)
|
unicode-display_width (1.1.3)
|
||||||
uniform_notifier (1.10.0)
|
uniform_notifier (1.10.0)
|
||||||
warden (1.2.7)
|
warden (1.2.7)
|
||||||
|
@ -594,8 +460,6 @@ GEM
|
||||||
websocket-driver (0.6.5)
|
websocket-driver (0.6.5)
|
||||||
websocket-extensions (>= 0.1.0)
|
websocket-extensions (>= 0.1.0)
|
||||||
websocket-extensions (0.1.2)
|
websocket-extensions (0.1.2)
|
||||||
whatlanguage (1.0.6)
|
|
||||||
xml-simple (1.1.5)
|
|
||||||
xpath (2.0.0)
|
xpath (2.0.0)
|
||||||
nokogiri (~> 1.3)
|
nokogiri (~> 1.3)
|
||||||
|
|
||||||
|
@ -612,12 +476,13 @@ DEPENDENCIES
|
||||||
binding_of_caller
|
binding_of_caller
|
||||||
browserify-rails
|
browserify-rails
|
||||||
bullet
|
bullet
|
||||||
capistrano (= 3.8.0)
|
capistrano
|
||||||
capistrano-faster-assets (~> 1.0)
|
capistrano-faster-assets (~> 1.0)
|
||||||
capistrano-rails
|
capistrano-rails
|
||||||
capistrano-rbenv
|
capistrano-rbenv
|
||||||
capistrano-yarn
|
capistrano-yarn
|
||||||
capybara
|
capybara
|
||||||
|
coffee-rails (~> 4.1.0)
|
||||||
devise
|
devise
|
||||||
devise-two-factor
|
devise-two-factor
|
||||||
doorkeeper
|
doorkeeper
|
||||||
|
@ -625,7 +490,6 @@ DEPENDENCIES
|
||||||
fabrication
|
fabrication
|
||||||
faker
|
faker
|
||||||
fast_blank
|
fast_blank
|
||||||
fog
|
|
||||||
font-awesome-rails
|
font-awesome-rails
|
||||||
fuubar
|
fuubar
|
||||||
goldfinger
|
goldfinger
|
||||||
|
@ -645,7 +509,7 @@ DEPENDENCIES
|
||||||
microformats2
|
microformats2
|
||||||
nokogiri
|
nokogiri
|
||||||
oj
|
oj
|
||||||
ostatus2 (~> 1.1)
|
ostatus2
|
||||||
ox
|
ox
|
||||||
paperclip (~> 5.1)
|
paperclip (~> 5.1)
|
||||||
paperclip-av-transcoder
|
paperclip-av-transcoder
|
||||||
|
@ -683,7 +547,6 @@ DEPENDENCIES
|
||||||
tzinfo-data
|
tzinfo-data
|
||||||
uglifier (>= 1.3.0)
|
uglifier (>= 1.3.0)
|
||||||
webmock
|
webmock
|
||||||
whatlanguage
|
|
||||||
|
|
||||||
RUBY VERSION
|
RUBY VERSION
|
||||||
ruby 2.4.1p111
|
ruby 2.4.1p111
|
||||||
|
|
|
@ -3,4 +3,3 @@
|
||||||
* * * *
|
* * * *
|
||||||
|
|
||||||
- [ ] I searched or browsed the repo’s other issues to ensure this is not a duplicate.
|
- [ ] I searched or browsed the repo’s other issues to ensure this is not a duplicate.
|
||||||
- [ ] This bug happens on a [tagged release](https://github.com/tootsuite/mastodon/releases) and not on `master` (If you're a user, don't worry about this).
|
|
||||||
|
|
|
@ -48,14 +48,6 @@ If you would like, you can [support the development of this project on Patreon][
|
||||||
- **Deployable via Docker**
|
- **Deployable via Docker**
|
||||||
You don't need to mess with dependencies and configuration if you want to try Mastodon, if you have Docker and Docker Compose the deployment is extremely easy
|
You don't need to mess with dependencies and configuration if you want to try Mastodon, if you have Docker and Docker Compose the deployment is extremely easy
|
||||||
|
|
||||||
## Checking out
|
|
||||||
|
|
||||||
If you want a stable release for production use, you should use tagged releases. To checkout the latest available tagged version:
|
|
||||||
|
|
||||||
git clone https://github.com/tootsuite/mastodon.git
|
|
||||||
cd mastodon
|
|
||||||
git checkout $(git describe --tags `git rev-list --tags --max-count=1`)
|
|
||||||
|
|
||||||
## Configuration
|
## Configuration
|
||||||
|
|
||||||
- `LOCAL_DOMAIN` should be the domain/hostname of your instance. This is **absolutely required** as it is used for generating unique IDs for everything federation-related
|
- `LOCAL_DOMAIN` should be the domain/hostname of your instance. This is **absolutely required** as it is used for generating unique IDs for everything federation-related
|
||||||
|
|
Binary file not shown.
Before Width: | Height: | Size: 24 KiB |
|
@ -1,14 +0,0 @@
|
||||||
import { openModal } from './modal';
|
|
||||||
import { changeSetting, saveSettings } from './settings';
|
|
||||||
|
|
||||||
export function showOnboardingOnce() {
|
|
||||||
return (dispatch, getState) => {
|
|
||||||
const alreadySeen = getState().getIn(['settings', 'onboarded']);
|
|
||||||
|
|
||||||
if (!alreadySeen) {
|
|
||||||
dispatch(openModal('ONBOARDING'));
|
|
||||||
dispatch(changeSetting(['onboarded'], true));
|
|
||||||
dispatch(saveSettings());
|
|
||||||
}
|
|
||||||
};
|
|
||||||
};
|
|
|
@ -98,7 +98,7 @@ const StatusActionBar = React.createClass({
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div style={{ marginTop: '10px', overflow: 'hidden' }}>
|
<div style={{ marginTop: '10px', overflow: 'hidden' }}>
|
||||||
<div style={{ float: 'left', marginRight: '18px'}}><IconButton title={intl.formatMessage(messages.reply)} icon={status.get('in_reply_to_id', null) === null ? 'reply' : 'reply-all'} onClick={this.handleReplyClick} /></div>
|
<div style={{ float: 'left', marginRight: '18px'}}><IconButton title={intl.formatMessage(messages.reply)} icon='reply' onClick={this.handleReplyClick} /></div>
|
||||||
<div style={{ float: 'left', marginRight: '18px'}}><IconButton disabled={status.get('visibility') === 'private' || status.get('visibility') === 'direct'} active={status.get('reblogged')} title={intl.formatMessage(messages.reblog)} icon={reblogIcon} onClick={this.handleReblogClick} /></div>
|
<div style={{ float: 'left', marginRight: '18px'}}><IconButton disabled={status.get('visibility') === 'private' || status.get('visibility') === 'direct'} active={status.get('reblogged')} title={intl.formatMessage(messages.reblog)} icon={reblogIcon} onClick={this.handleReblogClick} /></div>
|
||||||
<div style={{ float: 'left', marginRight: '18px'}}><IconButton animate={true} active={status.get('favourited')} title={intl.formatMessage(messages.favourite)} icon='star' onClick={this.handleFavouriteClick} activeStyle={{ color: '#ca8f04' }} /></div>
|
<div style={{ float: 'left', marginRight: '18px'}}><IconButton animate={true} active={status.get('favourited')} title={intl.formatMessage(messages.favourite)} icon='star' onClick={this.handleFavouriteClick} activeStyle={{ color: '#ca8f04' }} /></div>
|
||||||
|
|
||||||
|
|
|
@ -7,8 +7,7 @@ import { isIOS } from '../is_mobile';
|
||||||
const messages = defineMessages({
|
const messages = defineMessages({
|
||||||
toggle_sound: { id: 'video_player.toggle_sound', defaultMessage: 'Toggle sound' },
|
toggle_sound: { id: 'video_player.toggle_sound', defaultMessage: 'Toggle sound' },
|
||||||
toggle_visible: { id: 'video_player.toggle_visible', defaultMessage: 'Toggle visibility' },
|
toggle_visible: { id: 'video_player.toggle_visible', defaultMessage: 'Toggle visibility' },
|
||||||
expand_video: { id: 'video_player.expand', defaultMessage: 'Expand video' },
|
expand_video: { id: 'video_player.expand', defaultMessage: 'Expand video' }
|
||||||
expand_video: { id: 'video_player.video_error', defaultMessage: 'Video could not be played' }
|
|
||||||
});
|
});
|
||||||
|
|
||||||
const videoStyle = {
|
const videoStyle = {
|
||||||
|
@ -31,7 +30,7 @@ const muteStyle = {
|
||||||
zIndex: '5'
|
zIndex: '5'
|
||||||
};
|
};
|
||||||
|
|
||||||
const coverStyle = {
|
const spoilerStyle = {
|
||||||
marginTop: '8px',
|
marginTop: '8px',
|
||||||
textAlign: 'center',
|
textAlign: 'center',
|
||||||
height: '100%',
|
height: '100%',
|
||||||
|
@ -95,8 +94,7 @@ const VideoPlayer = React.createClass({
|
||||||
visible: !this.props.sensitive,
|
visible: !this.props.sensitive,
|
||||||
preview: true,
|
preview: true,
|
||||||
muted: true,
|
muted: true,
|
||||||
hasAudio: true,
|
hasAudio: true
|
||||||
videoError: false
|
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
|
|
||||||
|
@ -144,17 +142,12 @@ const VideoPlayer = React.createClass({
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
handleVideoError () {
|
|
||||||
this.setState({ videoError: true });
|
|
||||||
},
|
|
||||||
|
|
||||||
componentDidMount () {
|
componentDidMount () {
|
||||||
if (!this.video) {
|
if (!this.video) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
this.video.addEventListener('loadeddata', this.handleLoadedData);
|
this.video.addEventListener('loadeddata', this.handleLoadedData);
|
||||||
this.video.addEventListener('error', this.handleVideoError);
|
|
||||||
},
|
},
|
||||||
|
|
||||||
componentDidUpdate () {
|
componentDidUpdate () {
|
||||||
|
@ -163,7 +156,6 @@ const VideoPlayer = React.createClass({
|
||||||
}
|
}
|
||||||
|
|
||||||
this.video.addEventListener('loadeddata', this.handleLoadedData);
|
this.video.addEventListener('loadeddata', this.handleLoadedData);
|
||||||
this.video.addEventListener('error', this.handleVideoError);
|
|
||||||
},
|
},
|
||||||
|
|
||||||
componentWillUnmount () {
|
componentWillUnmount () {
|
||||||
|
@ -172,7 +164,6 @@ const VideoPlayer = React.createClass({
|
||||||
}
|
}
|
||||||
|
|
||||||
this.video.removeEventListener('loadeddata', this.handleLoadedData);
|
this.video.removeEventListener('loadeddata', this.handleLoadedData);
|
||||||
this.video.removeEventListener('error', this.handleVideoError);
|
|
||||||
},
|
},
|
||||||
|
|
||||||
render () {
|
render () {
|
||||||
|
@ -203,7 +194,7 @@ const VideoPlayer = React.createClass({
|
||||||
if (!this.state.visible) {
|
if (!this.state.visible) {
|
||||||
if (sensitive) {
|
if (sensitive) {
|
||||||
return (
|
return (
|
||||||
<div role='button' tabIndex='0' style={{...coverStyle, width: `${width}px`, height: `${height}px` }} className='media-spoiler' onClick={this.handleVisibility}>
|
<div role='button' tabIndex='0' style={{...spoilerStyle, width: `${width}px`, height: `${height}px` }} className='media-spoiler' onClick={this.handleVisibility}>
|
||||||
{spoilerButton}
|
{spoilerButton}
|
||||||
<span style={spoilerSpanStyle}><FormattedMessage id='status.sensitive_warning' defaultMessage='Sensitive content' /></span>
|
<span style={spoilerSpanStyle}><FormattedMessage id='status.sensitive_warning' defaultMessage='Sensitive content' /></span>
|
||||||
<span style={spoilerSubSpanStyle}><FormattedMessage id='status.sensitive_toggle' defaultMessage='Click to view' /></span>
|
<span style={spoilerSubSpanStyle}><FormattedMessage id='status.sensitive_toggle' defaultMessage='Click to view' /></span>
|
||||||
|
@ -211,7 +202,7 @@ const VideoPlayer = React.createClass({
|
||||||
);
|
);
|
||||||
} else {
|
} else {
|
||||||
return (
|
return (
|
||||||
<div role='button' tabIndex='0' style={{...coverStyle, width: `${width}px`, height: `${height}px` }} className='media-spoiler' onClick={this.handleVisibility}>
|
<div role='button' tabIndex='0' style={{...spoilerStyle, width: `${width}px`, height: `${height}px` }} className='media-spoiler' onClick={this.handleVisibility}>
|
||||||
{spoilerButton}
|
{spoilerButton}
|
||||||
<span style={spoilerSpanStyle}><FormattedMessage id='status.media_hidden' defaultMessage='Media hidden' /></span>
|
<span style={spoilerSpanStyle}><FormattedMessage id='status.media_hidden' defaultMessage='Media hidden' /></span>
|
||||||
<span style={spoilerSubSpanStyle}><FormattedMessage id='status.sensitive_toggle' defaultMessage='Click to view' /></span>
|
<span style={spoilerSubSpanStyle}><FormattedMessage id='status.sensitive_toggle' defaultMessage='Click to view' /></span>
|
||||||
|
@ -229,14 +220,6 @@ const VideoPlayer = React.createClass({
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (this.state.videoError) {
|
|
||||||
return (
|
|
||||||
<div style={{...coverStyle, width: `${width}px`, height: `${height}px` }} className='video-error-cover' >
|
|
||||||
<span style={spoilerSpanStyle}><FormattedMessage id='video_player.video_error' defaultMessage='Video could not be played' /></span>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div style={{ cursor: 'default', marginTop: '8px', overflow: 'hidden', width: `${width}px`, height: `${height}px`, boxSizing: 'border-box', background: '#000', position: 'relative' }}>
|
<div style={{ cursor: 'default', marginTop: '8px', overflow: 'hidden', width: `${width}px`, height: `${height}px`, boxSizing: 'border-box', background: '#000', position: 'relative' }}>
|
||||||
{spoilerButton}
|
{spoilerButton}
|
||||||
|
|
|
@ -8,7 +8,6 @@ import {
|
||||||
connectTimeline,
|
connectTimeline,
|
||||||
disconnectTimeline
|
disconnectTimeline
|
||||||
} from '../actions/timelines';
|
} from '../actions/timelines';
|
||||||
import { showOnboardingOnce } from '../actions/onboarding';
|
|
||||||
import { updateNotifications, refreshNotifications } from '../actions/notifications';
|
import { updateNotifications, refreshNotifications } from '../actions/notifications';
|
||||||
import createBrowserHistory from 'history/lib/createBrowserHistory';
|
import createBrowserHistory from 'history/lib/createBrowserHistory';
|
||||||
import {
|
import {
|
||||||
|
@ -135,8 +134,6 @@ const Mastodon = React.createClass({
|
||||||
if (typeof window.Notification !== 'undefined' && Notification.permission === 'default') {
|
if (typeof window.Notification !== 'undefined' && Notification.permission === 'default') {
|
||||||
Notification.requestPermission();
|
Notification.requestPermission();
|
||||||
}
|
}
|
||||||
|
|
||||||
store.dispatch(showOnboardingOnce());
|
|
||||||
},
|
},
|
||||||
|
|
||||||
componentWillUnmount () {
|
componentWillUnmount () {
|
||||||
|
|
|
@ -77,7 +77,7 @@ const ActionBar = React.createClass({
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className='detailed-status__action-bar'>
|
<div className='detailed-status__action-bar'>
|
||||||
<div style={{ flex: '1 1 auto', textAlign: 'center' }}><IconButton title={intl.formatMessage(messages.reply)} icon={status.get('in_reply_to_id', null) === null ? 'reply' : 'reply-all'} onClick={this.handleReplyClick} /></div>
|
<div style={{ flex: '1 1 auto', textAlign: 'center' }}><IconButton title={intl.formatMessage(messages.reply)} icon='reply' onClick={this.handleReplyClick} /></div>
|
||||||
<div style={{ flex: '1 1 auto', textAlign: 'center' }}><IconButton disabled={status.get('visibility') === 'direct' || status.get('visibility') === 'private'} active={status.get('reblogged')} title={intl.formatMessage(messages.reblog)} icon={reblogIcon} onClick={this.handleReblogClick} /></div>
|
<div style={{ flex: '1 1 auto', textAlign: 'center' }}><IconButton disabled={status.get('visibility') === 'direct' || status.get('visibility') === 'private'} active={status.get('reblogged')} title={intl.formatMessage(messages.reblog)} icon={reblogIcon} onClick={this.handleReblogClick} /></div>
|
||||||
<div style={{ flex: '1 1 auto', textAlign: 'center' }}><IconButton animate={true} active={status.get('favourited')} title={intl.formatMessage(messages.favourite)} icon='star' onClick={this.handleFavouriteClick} activeStyle={{ color: '#ca8f04' }} /></div>
|
<div style={{ flex: '1 1 auto', textAlign: 'center' }}><IconButton animate={true} active={status.get('favourited')} title={intl.formatMessage(messages.favourite)} icon='star' onClick={this.handleFavouriteClick} activeStyle={{ color: '#ca8f04' }} /></div>
|
||||||
<div style={{ flex: '1 1 auto', textAlign: 'center' }}><DropdownMenu size={18} icon='ellipsis-h' items={menu} direction="left" /></div>
|
<div style={{ flex: '1 1 auto', textAlign: 'center' }}><DropdownMenu size={18} icon='ellipsis-h' items={menu} direction="left" /></div>
|
||||||
|
|
|
@ -1,13 +1,11 @@
|
||||||
import PureRenderMixin from 'react-addons-pure-render-mixin';
|
import PureRenderMixin from 'react-addons-pure-render-mixin';
|
||||||
import MediaModal from './media_modal';
|
import MediaModal from './media_modal';
|
||||||
import OnboardingModal from './onboarding_modal';
|
|
||||||
import VideoModal from './video_modal';
|
import VideoModal from './video_modal';
|
||||||
import BoostModal from './boost_modal';
|
import BoostModal from './boost_modal';
|
||||||
import { TransitionMotion, spring } from 'react-motion';
|
import { TransitionMotion, spring } from 'react-motion';
|
||||||
|
|
||||||
const MODAL_COMPONENTS = {
|
const MODAL_COMPONENTS = {
|
||||||
'MEDIA': MediaModal,
|
'MEDIA': MediaModal,
|
||||||
'ONBOARDING': OnboardingModal,
|
|
||||||
'VIDEO': VideoModal,
|
'VIDEO': VideoModal,
|
||||||
'BOOST': BoostModal
|
'BOOST': BoostModal
|
||||||
};
|
};
|
||||||
|
|
|
@ -1,251 +0,0 @@
|
||||||
import { connect } from 'react-redux';
|
|
||||||
import PureRenderMixin from 'react-addons-pure-render-mixin';
|
|
||||||
import ImmutablePropTypes from 'react-immutable-proptypes';
|
|
||||||
import { defineMessages, injectIntl, FormattedMessage } from 'react-intl';
|
|
||||||
import Permalink from '../../../components/permalink';
|
|
||||||
import { TransitionMotion, spring } from 'react-motion';
|
|
||||||
import ComposeForm from '../../compose/components/compose_form';
|
|
||||||
import Search from '../../compose/components/search';
|
|
||||||
import NavigationBar from '../../compose/components/navigation_bar';
|
|
||||||
import ColumnHeader from './column_header';
|
|
||||||
import Immutable from 'immutable';
|
|
||||||
|
|
||||||
const messages = defineMessages({
|
|
||||||
home_title: { id: 'column.home', defaultMessage: 'Home' },
|
|
||||||
notifications_title: { id: 'column.notifications', defaultMessage: 'Notifications' },
|
|
||||||
local_title: { id: 'column.community', defaultMessage: 'Local timeline' },
|
|
||||||
federated_title: { id: 'column.public', defaultMessage: 'Federated timeline' }
|
|
||||||
});
|
|
||||||
|
|
||||||
const PageOne = ({ acct, domain }) => (
|
|
||||||
<div className='onboarding-modal__page onboarding-modal__page-one'>
|
|
||||||
<div style={{ flex: '0 0 auto' }}>
|
|
||||||
<div className='onboarding-modal__page-one__elephant-friend' />
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div>
|
|
||||||
<h1><FormattedMessage id='onboarding.page_one.welcome' defaultMessage='Welcome to Mastodon!' /></h1>
|
|
||||||
<p><FormattedMessage id='onboarding.page_one.federation' defaultMessage='Mastodon is a social network that belongs to everyone.' /></p>
|
|
||||||
<p><FormattedMessage id='onboarding.page_one.handle' defaultMessage='You are on {domain}, one of many independent Mastodon instances. Your full handle is {handle}' values={{ domain, handle: <strong>{acct}@{domain}</strong> }}/></p>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
|
|
||||||
PageOne.propTypes = {
|
|
||||||
acct: React.PropTypes.string.isRequired,
|
|
||||||
domain: React.PropTypes.string.isRequired
|
|
||||||
};
|
|
||||||
|
|
||||||
const PageTwo = () => (
|
|
||||||
<div className='onboarding-modal__page onboarding-modal__page-two'>
|
|
||||||
<div className='figure non-interactive'>
|
|
||||||
<ComposeForm
|
|
||||||
text='Awoo! #introductions'
|
|
||||||
suggestions={Immutable.List()}
|
|
||||||
mentionedDomains={[]}
|
|
||||||
onChange={() => {}}
|
|
||||||
onSubmit={() => {}}
|
|
||||||
onPaste={() => {}}
|
|
||||||
onPickEmoji={() => {}}
|
|
||||||
onChangeSpoilerText={() => {}}
|
|
||||||
onClearSuggestions={() => {}}
|
|
||||||
onFetchSuggestions={() => {}}
|
|
||||||
onSuggestionSelected={() => {}}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<p><FormattedMessage id='onboarding.page_two.compose' defaultMessage='Write posts from the compose column. You can upload images, change privacy settings, and add content warnings with the icons below.' /></p>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
|
|
||||||
const PageThree = ({ me, domain }) => (
|
|
||||||
<div className='onboarding-modal__page onboarding-modal__page-three'>
|
|
||||||
<div className='figure non-interactive'>
|
|
||||||
<Search
|
|
||||||
value=''
|
|
||||||
onChange={() => {}}
|
|
||||||
onSubmit={() => {}}
|
|
||||||
onClear={() => {}}
|
|
||||||
onShow={() => {}}
|
|
||||||
/>
|
|
||||||
|
|
||||||
<div className='pseudo-drawer'>
|
|
||||||
<NavigationBar account={me} />
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<p><FormattedMessage id='onboarding.page_three.search' defaultMessage='Use the search bar to find people and look at hashtags, such as {illustration} and {introductions}. To look for a person who is not on this instance, use their full handle.' values={{ illustration: <Permalink to='/timelines/tag/illustration' href='/tags/illustration'>#illustration</Permalink>, introductions: <Permalink to='/timelines/tag/introductions' href='/tags/introductions'>#introductions</Permalink> }}/></p>
|
|
||||||
<p><FormattedMessage id='onboarding.page_three.profile' defaultMessage='Edit your profile to change your avatar, bio, and display name. There, you will also find other preferences.' /></p>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
|
|
||||||
PageThree.propTypes = {
|
|
||||||
me: ImmutablePropTypes.map.isRequired,
|
|
||||||
domain: React.PropTypes.string.isRequired
|
|
||||||
};
|
|
||||||
|
|
||||||
const PageFour = ({ domain, intl }) => (
|
|
||||||
<div className='onboarding-modal__page onboarding-modal__page-four'>
|
|
||||||
<div className='onboarding-modal__page-four__columns'>
|
|
||||||
<div className='row'>
|
|
||||||
<div>
|
|
||||||
<div className='figure non-interactive'><ColumnHeader icon='home' type={intl.formatMessage(messages.home_title)} /></div>
|
|
||||||
<p><FormattedMessage id='onboarding.page_four.home' defaultMessage='Home timeline shows posts from people you follow'/></p>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div>
|
|
||||||
<div className='figure non-interactive'><ColumnHeader icon='bell' type={intl.formatMessage(messages.notifications_title)} /></div>
|
|
||||||
<p><FormattedMessage id='onboarding.page_four.notifications' defaultMessage='Notifications show when someone interacts with you' /></p>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div className='row'>
|
|
||||||
<div>
|
|
||||||
<div className='figure non-interactive' style={{ marginBottom: 0 }}><ColumnHeader icon='globe' type={intl.formatMessage(messages.federated_title)} /></div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div>
|
|
||||||
<div className='figure non-interactive' style={{ marginBottom: 0 }}><ColumnHeader icon='users' type={intl.formatMessage(messages.local_title)} /></div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<p><FormattedMessage id='onboarding.page_five.public_timelines' defaultMessage='Federated timeline lists public posts from everyone who people on {domain} follow. Local timeline is the same, but limited to people on {domain}.' values={{ domain }} /></p>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
|
|
||||||
PageFour.propTypes = {
|
|
||||||
domain: React.PropTypes.string.isRequired,
|
|
||||||
intl: React.PropTypes.object.isRequired
|
|
||||||
};
|
|
||||||
|
|
||||||
const PageSix = ({ admin }) => {
|
|
||||||
let adminSection = '';
|
|
||||||
|
|
||||||
if (admin) {
|
|
||||||
adminSection = (
|
|
||||||
<p>
|
|
||||||
<FormattedMessage id='onboarding.page_six.admin' defaultMessage="Your instance's admin is {admin}." values={{ admin: <Permalink href={admin.get('url')} to={`/accounts/${admin.get('id')}`}>@{admin.get('acct')}</Permalink> }} />
|
|
||||||
<br />
|
|
||||||
<FormattedMessage id='onboarding.page_six.read_guidelines' defaultMessage='Please, do not forget to read the {guidelines}!' values={{ guidelines: <a href='/about/more' target='_blank'><FormattedMessage id='onboarding.page_six.guidelines' defaultMessage='community guidelines' /></a> }}/>
|
|
||||||
</p>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
return (
|
|
||||||
<div className='onboarding-modal__page onboarding-modal__page-six'>
|
|
||||||
<h1><FormattedMessage id='onboarding.page_six.almost_done' defaultMessage='Almost done...' /></h1>
|
|
||||||
{adminSection}
|
|
||||||
<p><FormattedMessage id='onboarding.page_six.github' defaultMessage='Mastodon is free open-source software. You can report bugs, request features, or contribute to the code on {github}.' values={{ github: <a href='https://github.com/tootsuite/mastodon' target='_blank' rel='noopener'>GitHub</a> }} /></p>
|
|
||||||
<p><FormattedMessage id='onboarding.page_six.apps_available' defaultMessage='There are {apps} available for iOS, Android and other platforms. And now... Bon Appetoot!' values={{ apps: <a href='https://github.com/tootsuite/documentation/blob/master/Using-Mastodon/Apps.md' target='_blank' rel='noopener'><FormattedMessage id='onboarding.page_six.various_app' defaultMessage='various mobile apps' /></a> }} /></p>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
PageSix.propTypes = {
|
|
||||||
admin: ImmutablePropTypes.map
|
|
||||||
};
|
|
||||||
|
|
||||||
const mapStateToProps = state => ({
|
|
||||||
me: state.getIn(['accounts', state.getIn(['meta', 'me'])]),
|
|
||||||
admin: state.getIn(['accounts', state.getIn(['meta', 'admin'])]),
|
|
||||||
domain: state.getIn(['meta', 'domain'])
|
|
||||||
});
|
|
||||||
|
|
||||||
const OnboardingModal = React.createClass({
|
|
||||||
|
|
||||||
propTypes: {
|
|
||||||
onClose: React.PropTypes.func.isRequired,
|
|
||||||
intl: React.PropTypes.object.isRequired,
|
|
||||||
me: ImmutablePropTypes.map.isRequired,
|
|
||||||
domain: React.PropTypes.string.isRequired,
|
|
||||||
admin: ImmutablePropTypes.map
|
|
||||||
},
|
|
||||||
|
|
||||||
getInitialState () {
|
|
||||||
return {
|
|
||||||
currentIndex: 0
|
|
||||||
};
|
|
||||||
},
|
|
||||||
|
|
||||||
mixins: [PureRenderMixin],
|
|
||||||
|
|
||||||
handleSkip (e) {
|
|
||||||
e.preventDefault();
|
|
||||||
this.props.onClose();
|
|
||||||
},
|
|
||||||
|
|
||||||
handleDot (i, e) {
|
|
||||||
e.preventDefault();
|
|
||||||
this.setState({ currentIndex: i });
|
|
||||||
},
|
|
||||||
|
|
||||||
handleNext (maxNum, e) {
|
|
||||||
e.preventDefault();
|
|
||||||
|
|
||||||
if (this.state.currentIndex < maxNum - 1) {
|
|
||||||
this.setState({ currentIndex: this.state.currentIndex + 1 });
|
|
||||||
} else {
|
|
||||||
this.props.onClose();
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
render () {
|
|
||||||
const { me, admin, domain, intl } = this.props;
|
|
||||||
|
|
||||||
const pages = [
|
|
||||||
<PageOne acct={me.get('acct')} domain={domain} />,
|
|
||||||
<PageTwo />,
|
|
||||||
<PageThree me={me} domain={domain} />,
|
|
||||||
<PageFour domain={domain} intl={intl} />,
|
|
||||||
<PageSix admin={admin} />
|
|
||||||
];
|
|
||||||
|
|
||||||
const { currentIndex } = this.state;
|
|
||||||
const hasMore = currentIndex < pages.length - 1;
|
|
||||||
|
|
||||||
let nextOrDoneBtn;
|
|
||||||
|
|
||||||
if(hasMore) {
|
|
||||||
nextOrDoneBtn = <a href='#' onClick={this.handleNext.bind(null, pages.length)} className='onboarding-modal__nav onboarding-modal__next'><FormattedMessage id='onboarding.next' defaultMessage='Next' /></a>;
|
|
||||||
} else {
|
|
||||||
nextOrDoneBtn = <a href='#' onClick={this.handleNext.bind(null, pages.length)} className='onboarding-modal__nav onboarding-modal__done'><FormattedMessage id='onboarding.next' defaultMessage='Done' /></a>;
|
|
||||||
}
|
|
||||||
|
|
||||||
const styles = pages.map((page, i) => ({
|
|
||||||
key: i,
|
|
||||||
style: { opacity: spring(i === currentIndex ? 1 : 0) }
|
|
||||||
}));
|
|
||||||
|
|
||||||
return (
|
|
||||||
<div className='modal-root__modal onboarding-modal'>
|
|
||||||
<TransitionMotion styles={styles}>
|
|
||||||
{interpolatedStyles =>
|
|
||||||
<div className='onboarding-modal__pager'>
|
|
||||||
{pages.map((page, i) =>
|
|
||||||
<div key={i} style={{ opacity: interpolatedStyles[i].style.opacity, pointerEvents: i === currentIndex ? 'auto' : 'none' }}>{page}</div>
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
}
|
|
||||||
</TransitionMotion>
|
|
||||||
|
|
||||||
<div className='onboarding-modal__paginator'>
|
|
||||||
<div>
|
|
||||||
<a href='#' className='onboarding-modal__skip' onClick={this.handleSkip}><FormattedMessage id='onboarding.skip' defaultMessage='Skip' /></a>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div className='onboarding-modal__dots'>
|
|
||||||
{pages.map((_, i) => <div key={i} onClick={this.handleDot.bind(null, i)} className={`onboarding-modal__dot ${i === currentIndex ? 'active' : ''}`} />)}
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div>
|
|
||||||
{nextOrDoneBtn}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
});
|
|
||||||
|
|
||||||
export default connect(mapStateToProps)(injectIntl(OnboardingModal));
|
|
|
@ -24,10 +24,8 @@ const makeGetStatusIds = () => createSelector([
|
||||||
|
|
||||||
if (columnSettings.getIn(['regex', 'body'], '').trim().length > 0) {
|
if (columnSettings.getIn(['regex', 'body'], '').trim().length > 0) {
|
||||||
try {
|
try {
|
||||||
if (showStatus) {
|
|
||||||
const regex = new RegExp(columnSettings.getIn(['regex', 'body']).trim(), 'i');
|
const regex = new RegExp(columnSettings.getIn(['regex', 'body']).trim(), 'i');
|
||||||
showStatus = !regex.test(statusForId.get('reblog') ? statuses.getIn([statusForId.get('reblog'), 'unescaped_content']) : statusForId.get('unescaped_content'));
|
showStatus = showStatus && !regex.test(statusForId.get('reblog') ? statuses.getIn([statusForId.get('reblog'), 'content']) : statusForId.get('content'));
|
||||||
}
|
|
||||||
} catch(e) {
|
} catch(e) {
|
||||||
// Bad regex, don't affect filters
|
// Bad regex, don't affect filters
|
||||||
}
|
}
|
||||||
|
|
|
@ -74,7 +74,6 @@ const en = {
|
||||||
"navigation_bar.public_timeline": "Federated timeline",
|
"navigation_bar.public_timeline": "Federated timeline",
|
||||||
"notification.favourite": "{name} favourited your status",
|
"notification.favourite": "{name} favourited your status",
|
||||||
"notification.follow": "{name} followed you",
|
"notification.follow": "{name} followed you",
|
||||||
"notification.mention": "{name} mentioned you",
|
|
||||||
"notification.reblog": "{name} boosted your status",
|
"notification.reblog": "{name} boosted your status",
|
||||||
"notifications.clear_confirmation": "Are you sure you want to clear all your notifications?",
|
"notifications.clear_confirmation": "Are you sure you want to clear all your notifications?",
|
||||||
"notifications.clear": "Clear notifications",
|
"notifications.clear": "Clear notifications",
|
||||||
|
@ -129,7 +128,6 @@ const en = {
|
||||||
"video_player.toggle_sound": "Toggle sound",
|
"video_player.toggle_sound": "Toggle sound",
|
||||||
"video_player.toggle_visible": "Toggle visibility",
|
"video_player.toggle_visible": "Toggle visibility",
|
||||||
"video_player.expand": "Expand video",
|
"video_player.expand": "Expand video",
|
||||||
"video_player.video_error": "Video could not be played",
|
|
||||||
};
|
};
|
||||||
|
|
||||||
export default en;
|
export default en;
|
||||||
|
|
|
@ -75,7 +75,6 @@ const fr = {
|
||||||
"navigation_bar.favourites": "Favoris",
|
"navigation_bar.favourites": "Favoris",
|
||||||
"navigation_bar.info": "Plus d'informations",
|
"navigation_bar.info": "Plus d'informations",
|
||||||
"navigation_bar.logout": "Déconnexion",
|
"navigation_bar.logout": "Déconnexion",
|
||||||
"navigation_bar.mutes": "Utilisateurs muets",
|
|
||||||
"navigation_bar.follow_requests": "Demandes de suivi",
|
"navigation_bar.follow_requests": "Demandes de suivi",
|
||||||
"reply_indicator.cancel": "Annuler",
|
"reply_indicator.cancel": "Annuler",
|
||||||
"search.placeholder": "Rechercher",
|
"search.placeholder": "Rechercher",
|
||||||
|
|
|
@ -1,125 +1,121 @@
|
||||||
const ja = {
|
const ja = {
|
||||||
"account.block": "@{name} さんをブロック",
|
"column_back_button.label": "戻る",
|
||||||
"account.disclaimer": "このユーザーは他のインスタンスに所属しているため、数字が正確で無い場合があります。",
|
"lightbox.close": "閉じる",
|
||||||
"account.edit_profile": "プロフィールを編集",
|
"loading_indicator.label": "読み込み中...",
|
||||||
"account.follow": "フォロー",
|
"status.mention": "@{name} さんへの返信",
|
||||||
"account.followers": "フォロワー",
|
"status.delete": "削除",
|
||||||
"account.follows": "フォロー",
|
"status.reply": "返信",
|
||||||
"account.follows_you": "フォローされています",
|
"status.reblog": "ブースト",
|
||||||
|
"status.favourite": "お気に入り",
|
||||||
|
"status.reblogged_by": "{name} さんにブーストされました",
|
||||||
|
"status.sensitive_warning": "不適切なコンテンツ",
|
||||||
|
"status.sensitive_toggle": "クリックして表示",
|
||||||
|
"status.show_more": "もっと見る",
|
||||||
|
"status.load_more": "もっと見る",
|
||||||
|
"status.show_less": "隠す",
|
||||||
|
"status.open": "Expand this status",
|
||||||
|
"status.report": "@{name} さんを通報",
|
||||||
|
"status.media_hidden": "非表示のメデイア",
|
||||||
|
"video_player.toggle_sound": "音の切り替え",
|
||||||
"account.mention": "@{name} さんに返信",
|
"account.mention": "@{name} さんに返信",
|
||||||
"account.mute": "ミュート",
|
"account.edit_profile": "プロフィールを編集",
|
||||||
"account.posts": "投稿",
|
|
||||||
"account.report": "@{name}を通報する",
|
|
||||||
"account.requested": "承認待ち",
|
|
||||||
"account.unblock": "@{name} さんのブロックを解除",
|
"account.unblock": "@{name} さんのブロックを解除",
|
||||||
"account.unfollow": "フォロー解除",
|
"account.unfollow": "フォロー解除",
|
||||||
|
"account.block": "@{name} さんをブロック",
|
||||||
|
"account.mute": "ミュート",
|
||||||
"account.unmute": "ミュート解除",
|
"account.unmute": "ミュート解除",
|
||||||
"boost_modal.combo": "次からは{combo}を押せば、これをスキップできます。",
|
"account.follow": "フォロー",
|
||||||
"column.blocks": "ブロックしたユーザー",
|
"account.report": "@{name}を通報する",
|
||||||
"column.community": "ローカルタイムライン",
|
"account.posts": "投稿",
|
||||||
"column.favourites": "お気に入り",
|
"account.follows": "フォロー",
|
||||||
"column.follow_requests": "フォローリクエスト",
|
"account.followers": "フォロワー",
|
||||||
|
"account.follows_you": "フォローされています",
|
||||||
|
"account.requested": "承認待ち",
|
||||||
|
"follow_request.authorize": "許可",
|
||||||
|
"follow_request.reject": "拒否",
|
||||||
|
"getting_started.heading": "スタート",
|
||||||
|
"getting_started.about_addressing": "ドメインとユーザー名を知っているなら検索フォームに入力すればフォローできます。",
|
||||||
|
"getting_started.about_shortcuts": "対象のアカウントがあなたと同じドメインのユーザーならばユーザー名のみで検索できます。これは返信のときも一緒です。",
|
||||||
|
"getting_started.open_source_notice": "Mastodon はオープンソースソフトウェアです。誰でも GitHub({github})から開発に参加したり、問題を報告したりできます。 {apps}",
|
||||||
|
"getting_started.apps": "さまざまなアプリで利用できます。",
|
||||||
"column.home": "ホーム",
|
"column.home": "ホーム",
|
||||||
"column.mutes": "ミュートしたユーザー",
|
"column.community": "ローカルタイムライン",
|
||||||
"column.notifications": "通知",
|
|
||||||
"column.public": "連合タイムライン",
|
"column.public": "連合タイムライン",
|
||||||
"column_back_button.label": "戻る",
|
"column.notifications": "通知",
|
||||||
|
"column.favourites": "お気に入り",
|
||||||
|
"tabs_bar.compose": "投稿",
|
||||||
|
"tabs_bar.home": "ホーム",
|
||||||
|
"tabs_bar.mentions": "返信",
|
||||||
|
"tabs_bar.local_timeline": "ローカル",
|
||||||
|
"tabs_bar.federated_timeline": "連合",
|
||||||
|
"tabs_bar.notifications": "通知",
|
||||||
"compose_form.placeholder": "今なにしてる?",
|
"compose_form.placeholder": "今なにしてる?",
|
||||||
"compose_form.privacy_disclaimer": "あなたの非公開トゥートは返信先のユーザー(at {domains})に公開されます。{domainsCount, plural, one {that server} other {those servers}}を信頼しますか?投稿のプライバシー保護はMastodonサーバー内でのみ有効です。 もし{domains} {domainsCount, plural, one {is not a Mastodon instance} other {are not Mastodon instances}}ならばあなたの投稿のプライバシーは保護されず、ブーストされたり予期しないユーザーに見られる可能性があります。",
|
|
||||||
"compose_form.publish": "トゥート",
|
"compose_form.publish": "トゥート",
|
||||||
"compose_form.sensitive": "メディアを不適切なコンテンツとしてマークする",
|
"compose_form.sensitive": "メディアを不適切なコンテンツとしてマークする",
|
||||||
"compose_form.spoiler": "テキストを隠す",
|
"compose_form.spoiler": "テキストを隠す",
|
||||||
"compose_form.spoiler_placeholder": "閲覧注意",
|
"compose_form.spoiler_placeholder": "内容注意メッセージ",
|
||||||
"emoji_button.label": "絵文字を追加",
|
"compose_form.private": "非公開にする",
|
||||||
"empty_column.community": "ローカルタイムラインはまだ使われていません。何か書いてみましょう!",
|
"compose_form.privacy_disclaimer": "あなたの非公開トゥートは返信先のユーザー(at {domains})に公開されます。{domainsCount, plural, one {that server} other {those servers}}を信頼しますか?投稿のプライバシー保護はMastodonサーバー内でのみ有効です。 もし{domains} {domainsCount, plural, one {is not a Mastodon instance} other {are not Mastodon instances}}ならばあなたの投稿のプライバシーは保護されず、ブーストされたり予期しないユーザーに見られる可能性があります。",
|
||||||
"empty_column.hashtag": "このハッシュタグはまだ使われていません。",
|
"compose_form.unlisted": "公開タイムラインに表示しない",
|
||||||
|
"privacy.public.short": "公開",
|
||||||
|
"privacy.public.long": "公開TLに投稿する",
|
||||||
|
"privacy.unlisted.short": "未収載",
|
||||||
|
"privacy.unlisted.long": "公開TLで表示しない",
|
||||||
|
"privacy.private.short": "非公開",
|
||||||
|
"privacy.private.long": "フォロワーだけに公開",
|
||||||
|
"privacy.direct.short": "ダイレクト",
|
||||||
|
"privacy.direct.long": "含んだユーザーだけに公開",
|
||||||
|
"privacy.change": "投稿のプライバシーを変更",
|
||||||
|
"report.heading": "新規通報",
|
||||||
|
"report.placeholder": "コメント",
|
||||||
|
"report.target": "問題のユーザー",
|
||||||
|
"report.submit": "通報する",
|
||||||
|
"navigation_bar.edit_profile": "プロフィールを編集",
|
||||||
|
"navigation_bar.preferences": "ユーザー設定",
|
||||||
|
"navigation_bar.community_timeline": "ローカルタイムライン",
|
||||||
|
"navigation_bar.public_timeline": "連合タイムライン",
|
||||||
|
"navigation_bar.logout": "ログアウト",
|
||||||
|
"navigation_bar.favourites": "お気に入り",
|
||||||
|
"navigation_bar.blocks": "ブロックしたユーザー",
|
||||||
|
"navigation_bar.info": "サーバー情報",
|
||||||
|
"reply_indicator.cancel": "キャンセル",
|
||||||
|
"search.placeholder": "検索",
|
||||||
|
"search.account": "アカウント",
|
||||||
|
"search.hashtag": "ハッシュタグ",
|
||||||
|
"search.status_by": "{uuuname}からの投稿",
|
||||||
|
"search_results.total": "{count} 件",
|
||||||
|
"upload_area.title": "ファイルをこちらにドラッグしてください",
|
||||||
|
"upload_button.label": "メディアを追加",
|
||||||
|
"upload_form.undo": "やり直す",
|
||||||
|
"notification.follow": "{name} さんにフォローされました",
|
||||||
|
"notification.favourite": "{name} さんがあなたのトゥートをお気に入りに登録しました",
|
||||||
|
"notification.reblog": "{name} さんがあなたのトゥートをブーストしました",
|
||||||
|
"notification.mention": "{name} さんがあなたに返信しました",
|
||||||
|
"notifications.clear": "通知を片付ける",
|
||||||
|
"notifications.clear_confirmation": "通知を全部片付けます。大丈夫ですか?",
|
||||||
|
"notifications.column_settings.alert": "デスクトップ通知",
|
||||||
|
"notifications.column_settings.show": "カラムに表示",
|
||||||
|
"notifications.column_settings.follow": "新しいフォロワー",
|
||||||
|
"notifications.column_settings.favourite": "お気に入り",
|
||||||
|
"notifications.column_settings.mention": "返信",
|
||||||
|
"notifications.column_settings.reblog": "ブースト",
|
||||||
|
"notifications.column_settings.sound": "通知音を再生",
|
||||||
"empty_column.home": "まだ誰もフォローしていません。{public}を見に行くか、検索を使って他のユーザーを見つけましょう。",
|
"empty_column.home": "まだ誰もフォローしていません。{public}を見に行くか、検索を使って他のユーザーを見つけましょう。",
|
||||||
"empty_column.home.public_timeline": "連合タイムライン",
|
"empty_column.home.public_timeline": "連合タイムライン",
|
||||||
"empty_column.notifications": "まだ通知がありません。他の人とふれ合って会話を始めましょう。",
|
"empty_column.notifications": "まだ通知がありません。他の人とふれ合って会話を始めましょう。",
|
||||||
"empty_column.public": "ここにはまだ何もありません!公開で何かを投稿したり、他のインスタンスのユーザーをフォローしたりしていっぱいにしましょう!",
|
"empty_column.public": "ここにはまだ何もありません!公開で何かを投稿したり、他のインスタンスのユーザーをフォローしたりしていっぱいにしましょう!",
|
||||||
"follow_request.authorize": "許可",
|
"empty_column.hashtag": "このハッシュタグはまだ使っていません。",
|
||||||
"follow_request.reject": "拒否",
|
"upload_progress.label": "アップロード中…",
|
||||||
"getting_started.apps": "さまざまなアプリで利用できます。",
|
"emoji_button.label": "絵文字を追加",
|
||||||
"getting_started.heading": "スタート",
|
|
||||||
"getting_started.open_source_notice": "Mastodon はオープンソースソフトウェアです。誰でも GitHub({github})から開発に参加したり、問題を報告したりできます。 {apps}",
|
|
||||||
"home.column_settings.advanced": "上級者向け",
|
|
||||||
"home.column_settings.basic": "シンプル",
|
"home.column_settings.basic": "シンプル",
|
||||||
"home.column_settings.filter_regex": "正規表現でフィルター",
|
"home.column_settings.advanced": "エキスパート",
|
||||||
"home.column_settings.show_reblogs": "ブースト表示",
|
"home.column_settings.show_reblogs": "ブースト表示",
|
||||||
"home.column_settings.show_replies": "返信表示",
|
"home.column_settings.show_replies": "返信表示",
|
||||||
|
"home.column_settings.filter_regex": "正規表現でフィルター",
|
||||||
"home.settings": "カラム設定",
|
"home.settings": "カラム設定",
|
||||||
"lightbox.close": "閉じる",
|
|
||||||
"loading_indicator.label": "読み込み中...",
|
|
||||||
"media_gallery.toggle_visible": "表示切り替え",
|
|
||||||
"missing_indicator.label": "見つかりません",
|
|
||||||
"navigation_bar.blocks": "ブロックしたユーザー",
|
|
||||||
"navigation_bar.community_timeline": "ローカルタイムライン",
|
|
||||||
"navigation_bar.edit_profile": "プロフィールを編集",
|
|
||||||
"navigation_bar.favourites": "お気に入り",
|
|
||||||
"navigation_bar.follow_requests": "フォローリクエスト",
|
|
||||||
"navigation_bar.info": "サーバー情報",
|
|
||||||
"navigation_bar.logout": "ログアウト",
|
|
||||||
"navigation_bar.mutes": "ミュートしたユーザー",
|
|
||||||
"navigation_bar.preferences": "ユーザー設定",
|
|
||||||
"navigation_bar.public_timeline": "連合タイムライン",
|
|
||||||
"notification.favourite": "{name} さんがあなたのトゥートをお気に入りに登録しました",
|
|
||||||
"notification.follow": "{name} さんにフォローされました",
|
|
||||||
"notification.mention": "{name} さんがあなたに返信しました",
|
|
||||||
"notification.reblog": "{name} さんがあなたのトゥートをブーストしました",
|
|
||||||
"notifications.clear": "通知を消去",
|
|
||||||
"notifications.clear_confirmation": "本当に通知を消去しますか?",
|
|
||||||
"notifications.column_settings.alert": "デスクトップ通知",
|
|
||||||
"notifications.column_settings.favourite": "お気に入り",
|
|
||||||
"notifications.column_settings.follow": "新しいフォロワー",
|
|
||||||
"notifications.column_settings.mention": "返信",
|
|
||||||
"notifications.column_settings.reblog": "ブースト",
|
|
||||||
"notifications.column_settings.show": "カラムに表示",
|
|
||||||
"notifications.column_settings.sound": "通知音を再生",
|
|
||||||
"notifications.settings": "カラム設定",
|
"notifications.settings": "カラム設定",
|
||||||
"privacy.change": "投稿のプライバシーを変更",
|
"missing_indicator.label": "見つかりません",
|
||||||
"privacy.direct.long": "メンションしたユーザーだけに公開",
|
"boost_modal.combo": "次は{combo}を押せば、これをスキップできます。"
|
||||||
"privacy.direct.short": "ダイレクト",
|
|
||||||
"privacy.private.long": "フォロワーだけに公開",
|
|
||||||
"privacy.private.short": "非公開",
|
|
||||||
"privacy.public.long": "公開TLに投稿する",
|
|
||||||
"privacy.public.short": "公開",
|
|
||||||
"privacy.unlisted.long": "公開TLで表示しない",
|
|
||||||
"privacy.unlisted.short": "未収載",
|
|
||||||
"reply_indicator.cancel": "キャンセル",
|
|
||||||
"report.heading": "新規通報",
|
|
||||||
"report.placeholder": "コメント",
|
|
||||||
"report.submit": "通報する",
|
|
||||||
"report.target": "問題のユーザー",
|
|
||||||
"search.placeholder": "検索",
|
|
||||||
"search.status_by": "{name}からの投稿",
|
|
||||||
"search_results.total": "{count} {count, plural, one {result} other {results}} 件",
|
|
||||||
"status.delete": "削除",
|
|
||||||
"status.favourite": "お気に入り",
|
|
||||||
"status.load_more": "もっと見る",
|
|
||||||
"status.media_hidden": "非表示のメデイア",
|
|
||||||
"status.mention": "@{name} さんへの返信",
|
|
||||||
"status.open": "詳細を表示",
|
|
||||||
"status.reblog": "ブースト",
|
|
||||||
"status.reblogged_by": "{name} さんにブーストされました",
|
|
||||||
"status.reply": "返信",
|
|
||||||
"status.report": "@{name} さんを通報",
|
|
||||||
"status.sensitive_toggle": "クリックして表示",
|
|
||||||
"status.sensitive_warning": "不適切なコンテンツ",
|
|
||||||
"status.show_less": "隠す",
|
|
||||||
"status.show_more": "もっと見る",
|
|
||||||
"tabs_bar.compose": "投稿",
|
|
||||||
"tabs_bar.federated_timeline": "連合",
|
|
||||||
"tabs_bar.home": "ホーム",
|
|
||||||
"tabs_bar.local_timeline": "ローカル",
|
|
||||||
"tabs_bar.notifications": "通知",
|
|
||||||
"upload_area.title": "ドラッグ&ドロップでアップロード",
|
|
||||||
"upload_button.label": "メディアを追加",
|
|
||||||
"upload_form.undo": "やり直す",
|
|
||||||
"upload_progress.label": "アップロード中…",
|
|
||||||
"video_player.expand": "動画の詳細",
|
|
||||||
"video_player.toggle_sound": "音の切り替え",
|
|
||||||
"video_player.toggle_visible": "表示切り替え",
|
|
||||||
"video_player.video_error": "動画の再生に失敗しました",
|
|
||||||
};
|
};
|
||||||
|
|
||||||
export default ja;
|
export default ja;
|
||||||
|
|
|
@ -3,8 +3,6 @@ import { STORE_HYDRATE } from '../actions/store';
|
||||||
import Immutable from 'immutable';
|
import Immutable from 'immutable';
|
||||||
|
|
||||||
const initialState = Immutable.Map({
|
const initialState = Immutable.Map({
|
||||||
onboarded: false,
|
|
||||||
|
|
||||||
home: Immutable.Map({
|
home: Immutable.Map({
|
||||||
shows: Immutable.Map({
|
shows: Immutable.Map({
|
||||||
reblog: true,
|
reblog: true,
|
||||||
|
|
|
@ -48,9 +48,6 @@ const normalizeStatus = (state, status) => {
|
||||||
normalStatus.reblog = status.reblog.id;
|
normalStatus.reblog = status.reblog.id;
|
||||||
}
|
}
|
||||||
|
|
||||||
const linebreakComplemented = status.content.replace(/<br \/>/g, '\n').replace(/<\/p><p>/g, '\n\n');
|
|
||||||
normalStatus.unescaped_content = new DOMParser().parseFromString(linebreakComplemented, 'text/html').documentElement.textContent;
|
|
||||||
|
|
||||||
return state.update(status.id, Immutable.Map(), map => map.mergeDeep(Immutable.fromJS(normalStatus)));
|
return state.update(status.id, Immutable.Map(), map => map.mergeDeep(Immutable.fromJS(normalStatus)));
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
@ -889,11 +889,6 @@ a.status__content__spoiler-link {
|
||||||
padding-right: 10px;
|
padding-right: 10px;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.column {
|
|
||||||
flex-grow: 1;
|
|
||||||
max-width: 600px; // This is just a guess at a sane max value
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@media screen and (min-width: 2560px) {
|
@media screen and (min-width: 2560px) {
|
||||||
|
@ -907,11 +902,6 @@ a.status__content__spoiler-link {
|
||||||
height: 90vh;
|
height: 90vh;
|
||||||
margin-top: 5vh;
|
margin-top: 5vh;
|
||||||
}
|
}
|
||||||
|
|
||||||
.column {
|
|
||||||
flex-grow: 1;
|
|
||||||
max-width: 600px; // This is just a guess at a sane max value
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.drawer__pager {
|
.drawer__pager {
|
||||||
|
@ -942,12 +932,6 @@ a.status__content__spoiler-link {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.pseudo-drawer {
|
|
||||||
background: lighten($color1, 13%);
|
|
||||||
font-size: 13px;
|
|
||||||
text-align: left;
|
|
||||||
}
|
|
||||||
|
|
||||||
.drawer__header {
|
.drawer__header {
|
||||||
flex: 0 0 auto;
|
flex: 0 0 auto;
|
||||||
font-size: 16px;
|
font-size: 16px;
|
||||||
|
@ -1219,10 +1203,6 @@ a.status__content__spoiler-link {
|
||||||
&:focus {
|
&:focus {
|
||||||
outline: 0;
|
outline: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
@media screen and (max-width: 600px) {
|
|
||||||
font-size: 16px;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.spoiler-input__input {
|
.spoiler-input__input {
|
||||||
|
@ -1287,10 +1267,6 @@ a.status__content__spoiler-link {
|
||||||
color: $color5;
|
color: $color5;
|
||||||
border-bottom-color: $color4;
|
border-bottom-color: $color4;
|
||||||
}
|
}
|
||||||
|
|
||||||
@media screen and (max-width: 600px) {
|
|
||||||
font-size: 16px;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@import 'boost';
|
@import 'boost';
|
||||||
|
@ -1407,7 +1383,7 @@ button.icon-button.active i.fa-retweet {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.media-spoiler, .video-error-cover {
|
.media-spoiler {
|
||||||
background: $color8;
|
background: $color8;
|
||||||
color: $color5;
|
color: $color5;
|
||||||
}
|
}
|
||||||
|
@ -1930,10 +1906,6 @@ button.icon-button.active i.fa-retweet {
|
||||||
&:focus {
|
&:focus {
|
||||||
background: lighten($color1, 4%);
|
background: lighten($color1, 4%);
|
||||||
}
|
}
|
||||||
|
|
||||||
@media screen and (max-width: 600px) {
|
|
||||||
font-size: 16px;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.search__icon {
|
.search__icon {
|
||||||
|
@ -2034,7 +2006,6 @@ button.icon-button.active i.fa-retweet {
|
||||||
.modal-root__modal {
|
.modal-root__modal {
|
||||||
pointer-events: auto;
|
pointer-events: auto;
|
||||||
display: flex;
|
display: flex;
|
||||||
z-index: 9999;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.media-modal {
|
.media-modal {
|
||||||
|
@ -2048,237 +2019,6 @@ button.icon-button.active i.fa-retweet {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.onboarding-modal {
|
|
||||||
background: $color2;
|
|
||||||
color: $color1;
|
|
||||||
border-radius: 8px;
|
|
||||||
overflow: hidden;
|
|
||||||
display: flex;
|
|
||||||
flex-direction: column;
|
|
||||||
}
|
|
||||||
|
|
||||||
.onboarding-modal__pager {
|
|
||||||
height: 80vh;
|
|
||||||
width: 80vw;
|
|
||||||
max-width: 500px;
|
|
||||||
max-height: 350px;
|
|
||||||
position: relative;
|
|
||||||
|
|
||||||
& > div {
|
|
||||||
position: absolute;
|
|
||||||
top: 0;
|
|
||||||
left: 0;
|
|
||||||
width: 100%;
|
|
||||||
height: 100%;
|
|
||||||
box-sizing: border-box;
|
|
||||||
padding: 25px;
|
|
||||||
display: none;
|
|
||||||
flex-direction: column;
|
|
||||||
align-items: center;
|
|
||||||
justify-content: center;
|
|
||||||
display: flex;
|
|
||||||
opacity: 0;
|
|
||||||
user-select: text;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@media screen and (max-width: 550px) {
|
|
||||||
.onboarding-modal {
|
|
||||||
width: 100%;
|
|
||||||
height: 100%;
|
|
||||||
border-radius: 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
.onboarding-modal__pager {
|
|
||||||
width: 100%;
|
|
||||||
height: auto;
|
|
||||||
max-width: none;
|
|
||||||
max-height: none;
|
|
||||||
flex: 1 1 auto;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.onboarding-modal__paginator {
|
|
||||||
flex: 0 0 auto;
|
|
||||||
background: darken($color2, 8%);
|
|
||||||
display: flex;
|
|
||||||
padding: 25px;
|
|
||||||
|
|
||||||
& > div {
|
|
||||||
min-width: 33px;
|
|
||||||
}
|
|
||||||
|
|
||||||
a {
|
|
||||||
color: darken($color2, 34%);
|
|
||||||
text-decoration: none;
|
|
||||||
font-size: 14px;
|
|
||||||
font-weight: 500;
|
|
||||||
|
|
||||||
&:hover, &:focus, &:active {
|
|
||||||
color: darken($color2, 38%);
|
|
||||||
}
|
|
||||||
|
|
||||||
&.onboarding-modal__done, &.onboarding-modal__next {
|
|
||||||
color: $color4;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.onboarding-modal__dots {
|
|
||||||
flex: 1 1 auto;
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
justify-content: center;
|
|
||||||
}
|
|
||||||
|
|
||||||
.onboarding-modal__dot {
|
|
||||||
width: 14px;
|
|
||||||
height: 14px;
|
|
||||||
border-radius: 14px;
|
|
||||||
background: darken($color2, 16%);
|
|
||||||
margin: 0 3px;
|
|
||||||
cursor: pointer;
|
|
||||||
|
|
||||||
&:hover {
|
|
||||||
background: darken($color2, 18%);
|
|
||||||
}
|
|
||||||
|
|
||||||
&.active {
|
|
||||||
cursor: default;
|
|
||||||
background: darken($color2, 24%);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.onboarding-modal__page {
|
|
||||||
cursor: default;
|
|
||||||
line-height: 21px;
|
|
||||||
|
|
||||||
h1 {
|
|
||||||
font-size: 18px;
|
|
||||||
font-weight: 500;
|
|
||||||
color: $color1;
|
|
||||||
margin-bottom: 20px;
|
|
||||||
}
|
|
||||||
|
|
||||||
a {
|
|
||||||
color: $color4;
|
|
||||||
|
|
||||||
&:hover, &:focus, &:active {
|
|
||||||
color: lighten($color4, 4%);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
p {
|
|
||||||
font-size: 16px;
|
|
||||||
color: lighten($color1, 8%);
|
|
||||||
margin-top: 10px;
|
|
||||||
margin-bottom: 10px;
|
|
||||||
|
|
||||||
&:last-child {
|
|
||||||
margin-bottom: 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
strong {
|
|
||||||
font-weight: 500;
|
|
||||||
background: $color1;
|
|
||||||
color: $color2;
|
|
||||||
border-radius: 4px;
|
|
||||||
font-size: 14px;
|
|
||||||
padding: 3px 6px;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.onboarding-modal__page-one {
|
|
||||||
display: flex;
|
|
||||||
}
|
|
||||||
|
|
||||||
.onboarding-modal__page-one__elephant-friend {
|
|
||||||
background: image-url('elephant-friend.png') no-repeat 0 0;
|
|
||||||
width: 147px;
|
|
||||||
height: 160px;
|
|
||||||
margin-right: 10px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.onboarding-modal__page-two,
|
|
||||||
.onboarding-modal__page-three,
|
|
||||||
.onboarding-modal__page-four,
|
|
||||||
.onboarding-modal__page-five {
|
|
||||||
p {
|
|
||||||
text-align: left;
|
|
||||||
}
|
|
||||||
|
|
||||||
.figure {
|
|
||||||
background: darken($color1, 8%);
|
|
||||||
color: $color2;
|
|
||||||
margin-bottom: 20px;
|
|
||||||
border-radius: 4px;
|
|
||||||
padding: 10px;
|
|
||||||
text-align: center;
|
|
||||||
font-size: 14px;
|
|
||||||
box-shadow: 1px 2px 6px rgba($color8, 0.3);
|
|
||||||
|
|
||||||
.onboarding-modal__image {
|
|
||||||
border-radius: 4px;
|
|
||||||
margin-bottom: 10px;
|
|
||||||
}
|
|
||||||
|
|
||||||
&.non-interactive {
|
|
||||||
pointer-events: none;
|
|
||||||
text-align: left;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.onboarding-modal__page-four__columns {
|
|
||||||
.row {
|
|
||||||
display: flex;
|
|
||||||
margin-bottom: 20px;
|
|
||||||
|
|
||||||
& > div {
|
|
||||||
flex: 1 1 0;
|
|
||||||
margin: 0 10px;
|
|
||||||
|
|
||||||
&:first-child {
|
|
||||||
margin-left: 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
&:last-child {
|
|
||||||
margin-right: 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
p {
|
|
||||||
text-align: center;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
&:last-child {
|
|
||||||
margin-bottom: 0;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.column-header {
|
|
||||||
color: $color5;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.onboarding-modal__image {
|
|
||||||
border-radius: 8px;
|
|
||||||
width: 70vw;
|
|
||||||
max-width: 450px;
|
|
||||||
max-height: auto;
|
|
||||||
display: block;
|
|
||||||
margin: auto;
|
|
||||||
margin-bottom: 20px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.onboard-sliders {
|
|
||||||
display: inline-block;
|
|
||||||
max-width: 30px;
|
|
||||||
max-height: auto;
|
|
||||||
margin-left: 10px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.boost-modal {
|
.boost-modal {
|
||||||
background: lighten($color2, 8%);
|
background: lighten($color2, 8%);
|
||||||
color: $color1;
|
color: $color1;
|
||||||
|
|
|
@ -8,5 +8,10 @@
|
||||||
}
|
}
|
||||||
|
|
||||||
.recovery-codes {
|
.recovery-codes {
|
||||||
list-style: none;
|
column-count: 2;
|
||||||
|
height: 100px;
|
||||||
|
li {
|
||||||
|
list-style: decimal;
|
||||||
|
margin-left: 20px;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
16
app/assets/stylesheets/variables.scss
Executable file → Normal file
16
app/assets/stylesheets/variables.scss
Executable file → Normal file
|
@ -1,8 +1,8 @@
|
||||||
$color1: #282c37 !default; // darkest
|
$color1: #282c37; // darkest
|
||||||
$color2: #d9e1e8 !default; // lightest
|
$color2: #d9e1e8; // lightest
|
||||||
$color3: #9baec8 !default; // lighter
|
$color3: #9baec8; // lighter
|
||||||
$color4: #2b90d9 !default; // vibrant
|
$color4: #2b90d9; // vibrant
|
||||||
$color5: #ffffff !default; // white
|
$color5: #ffffff; // white
|
||||||
$color6: #df405a !default; // error red
|
$color6: #df405a; // error red
|
||||||
$color7: #79bd9a !default; // succ green
|
$color7: #79bd9a; // succ green
|
||||||
$color8: #000000 !default; // black
|
$color8: #000000; // black
|
||||||
|
|
|
@ -15,7 +15,7 @@ module Admin
|
||||||
|
|
||||||
if @domain_block.save
|
if @domain_block.save
|
||||||
DomainBlockWorker.perform_async(@domain_block.id)
|
DomainBlockWorker.perform_async(@domain_block.id)
|
||||||
redirect_to admin_domain_blocks_path, notice: I18n.t('admin.domain_blocks.created_msg')
|
redirect_to admin_domain_blocks_path, notice: I18n.t('admin.domain_block.created_msg')
|
||||||
else
|
else
|
||||||
render action: :new
|
render action: :new
|
||||||
end
|
end
|
||||||
|
@ -28,7 +28,7 @@ module Admin
|
||||||
def destroy
|
def destroy
|
||||||
@domain_block = DomainBlock.find(params[:id])
|
@domain_block = DomainBlock.find(params[:id])
|
||||||
UnblockDomainService.new.call(@domain_block, resource_params[:retroactive])
|
UnblockDomainService.new.call(@domain_block, resource_params[:retroactive])
|
||||||
redirect_to admin_domain_blocks_path, notice: I18n.t('admin.domain_blocks.destroyed_msg')
|
redirect_to admin_domain_blocks_path, notice: I18n.t('admin.domain_block.destroyed_msg')
|
||||||
end
|
end
|
||||||
|
|
||||||
private
|
private
|
||||||
|
|
|
@ -14,7 +14,7 @@ class Api::OEmbedController < ApiController
|
||||||
def stream_entry_from_url(url)
|
def stream_entry_from_url(url)
|
||||||
params = Rails.application.routes.recognize_path(url)
|
params = Rails.application.routes.recognize_path(url)
|
||||||
|
|
||||||
raise ActiveRecord::RecordNotFound unless params[:controller] == 'stream_entries' && params[:action] == 'show'
|
raise ActiveRecord::NotFound unless params[:controller] == 'stream_entries' && params[:action] == 'show'
|
||||||
|
|
||||||
StreamEntry.find(params[:id])
|
StreamEntry.find(params[:id])
|
||||||
end
|
end
|
||||||
|
|
|
@ -7,7 +7,6 @@ class HomeController < ApplicationController
|
||||||
@body_classes = 'app-body'
|
@body_classes = 'app-body'
|
||||||
@token = find_or_create_access_token.token
|
@token = find_or_create_access_token.token
|
||||||
@web_settings = Web::Setting.find_by(user: current_user)&.data || {}
|
@web_settings = Web::Setting.find_by(user: current_user)&.data || {}
|
||||||
@admin = Account.find_local(Setting.site_contact_username)
|
|
||||||
@streaming_api_base_url = Rails.configuration.x.streaming_api_base_url
|
@streaming_api_base_url = Rails.configuration.x.streaming_api_base_url
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
|
@ -4,8 +4,4 @@ module ApplicationHelper
|
||||||
def active_nav_class(path)
|
def active_nav_class(path)
|
||||||
current_page?(path) ? 'active' : ''
|
current_page?(path) ? 'active' : ''
|
||||||
end
|
end
|
||||||
|
|
||||||
def show_landing_strip?
|
|
||||||
!user_signed_in? && !single_user_mode?
|
|
||||||
end
|
|
||||||
end
|
end
|
||||||
|
|
|
@ -1,19 +0,0 @@
|
||||||
# frozen_string_literal: true
|
|
||||||
|
|
||||||
module StyleHelper
|
|
||||||
def stylesheet_for_layout
|
|
||||||
if asset_exist? 'custom.css'
|
|
||||||
'custom'
|
|
||||||
else
|
|
||||||
'application'
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
def asset_exist?(path)
|
|
||||||
if Rails.configuration.assets.compile
|
|
||||||
Rails.application.precompiled_assets.include? path
|
|
||||||
else
|
|
||||||
Rails.application.assets_manifest.assets[path].present?
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
|
@ -3,8 +3,6 @@
|
||||||
class AtomSerializer
|
class AtomSerializer
|
||||||
include RoutingHelper
|
include RoutingHelper
|
||||||
|
|
||||||
INVALID_XML_CHARS = /[^\u0009\u000a\u000d\u0020-\uD7FF\uE000-\uFFFD\u10000-\u10FFFF]/
|
|
||||||
|
|
||||||
class << self
|
class << self
|
||||||
def render(element)
|
def render(element)
|
||||||
document = Ox::Document.new(version: '1.0')
|
document = Ox::Document.new(version: '1.0')
|
||||||
|
@ -41,7 +39,7 @@ class AtomSerializer
|
||||||
add_namespaces(feed)
|
add_namespaces(feed)
|
||||||
|
|
||||||
append_element(feed, 'id', account_url(account, format: 'atom'))
|
append_element(feed, 'id', account_url(account, format: 'atom'))
|
||||||
append_element(feed, 'title', account.display_name.presence || account.username)
|
append_element(feed, 'title', account.display_name)
|
||||||
append_element(feed, 'subtitle', account.note)
|
append_element(feed, 'subtitle', account.note)
|
||||||
append_element(feed, 'updated', account.updated_at.iso8601)
|
append_element(feed, 'updated', account.updated_at.iso8601)
|
||||||
append_element(feed, 'logo', full_asset_url(account.avatar.url(:original)))
|
append_element(feed, 'logo', full_asset_url(account.avatar.url(:original)))
|
||||||
|
@ -313,15 +311,11 @@ class AtomSerializer
|
||||||
|
|
||||||
def append_element(parent, name, content = nil, attributes = {})
|
def append_element(parent, name, content = nil, attributes = {})
|
||||||
element = Ox::Element.new(name)
|
element = Ox::Element.new(name)
|
||||||
attributes.each { |k, v| element[k] = sanitize_str(v) }
|
attributes.each { |k, v| element[k] = v.to_s }
|
||||||
element << sanitize_str(content) unless content.nil?
|
element << content.to_s unless content.nil?
|
||||||
parent << element
|
parent << element
|
||||||
end
|
end
|
||||||
|
|
||||||
def sanitize_str(raw_str)
|
|
||||||
raw_str.to_s.gsub(INVALID_XML_CHARS, '')
|
|
||||||
end
|
|
||||||
|
|
||||||
def add_namespaces(parent)
|
def add_namespaces(parent)
|
||||||
parent['xmlns'] = TagManager::XMLNS
|
parent['xmlns'] = TagManager::XMLNS
|
||||||
parent['xmlns:thr'] = TagManager::THR_XMLNS
|
parent['xmlns:thr'] = TagManager::THR_XMLNS
|
||||||
|
@ -333,8 +327,8 @@ class AtomSerializer
|
||||||
end
|
end
|
||||||
|
|
||||||
def serialize_status_attributes(entry, status)
|
def serialize_status_attributes(entry, status)
|
||||||
append_element(entry, 'summary', status.spoiler_text, 'xml:lang': status.language) if status.spoiler_text?
|
append_element(entry, 'summary', status.spoiler_text) if status.spoiler_text?
|
||||||
append_element(entry, 'content', Formatter.instance.format(status.proper).to_str, type: 'html', 'xml:lang': status.language)
|
append_element(entry, 'content', Formatter.instance.format(status.proper).to_str, type: 'html')
|
||||||
|
|
||||||
status.mentions.each do |mentioned|
|
status.mentions.each do |mentioned|
|
||||||
append_element(entry, 'link', nil, rel: :mentioned, 'ostatus:object-type': TagManager::TYPES[:person], href: TagManager.instance.uri_for(mentioned.account))
|
append_element(entry, 'link', nil, rel: :mentioned, 'ostatus:object-type': TagManager::TYPES[:person], href: TagManager.instance.uri_for(mentioned.account))
|
||||||
|
|
|
@ -59,12 +59,7 @@ class NotificationMailer < ApplicationMailer
|
||||||
return if @notifications.empty?
|
return if @notifications.empty?
|
||||||
|
|
||||||
I18n.with_locale(@me.user.locale || I18n.default_locale) do
|
I18n.with_locale(@me.user.locale || I18n.default_locale) do
|
||||||
mail to: @me.user.email,
|
mail to: @me.user.email, subject: I18n.t('notification_mailer.digest.subject', count: @notifications.size)
|
||||||
subject: I18n.t(
|
|
||||||
:subject,
|
|
||||||
scope: [:notification_mailer, :digest],
|
|
||||||
count: @notifications.size
|
|
||||||
)
|
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
0
app/models/concerns/.keep
Normal file
0
app/models/concerns/.keep
Normal file
|
@ -7,9 +7,6 @@ class DomainBlock < ApplicationRecord
|
||||||
|
|
||||||
validates :domain, presence: true, uniqueness: true
|
validates :domain, presence: true, uniqueness: true
|
||||||
|
|
||||||
has_many :accounts, foreign_key: :domain, primary_key: :domain
|
|
||||||
delegate :count, to: :accounts, prefix: true
|
|
||||||
|
|
||||||
def self.blocked?(domain)
|
def self.blocked?(domain)
|
||||||
where(domain: domain, severity: :suspend).exists?
|
where(domain: domain, severity: :suspend).exists?
|
||||||
end
|
end
|
||||||
|
|
|
@ -1,15 +1,13 @@
|
||||||
# frozen_string_literal: true
|
# frozen_string_literal: true
|
||||||
|
|
||||||
class Import < ApplicationRecord
|
class Import < ApplicationRecord
|
||||||
FILE_TYPES = ['text/plain', 'text/csv'].freeze
|
|
||||||
|
|
||||||
self.inheritance_column = false
|
self.inheritance_column = false
|
||||||
|
|
||||||
belongs_to :account, required: true
|
|
||||||
|
|
||||||
enum type: [:following, :blocking, :muting]
|
enum type: [:following, :blocking, :muting]
|
||||||
|
|
||||||
validates :type, presence: true
|
belongs_to :account
|
||||||
|
|
||||||
|
FILE_TYPES = ['text/plain', 'text/csv'].freeze
|
||||||
|
|
||||||
has_attached_file :data, url: '/system/:hash.:extension', hash_secret: ENV['PAPERCLIP_SECRET']
|
has_attached_file :data, url: '/system/:hash.:extension', hash_secret: ENV['PAPERCLIP_SECRET']
|
||||||
validates_attachment_content_type :data, content_type: FILE_TYPES
|
validates_attachment_content_type :data, content_type: FILE_TYPES
|
||||||
|
|
|
@ -110,10 +110,6 @@ class Status < ApplicationRecord
|
||||||
results
|
results
|
||||||
end
|
end
|
||||||
|
|
||||||
def non_sensitive_with_media?
|
|
||||||
!sensitive? && media_attachments.any?
|
|
||||||
end
|
|
||||||
|
|
||||||
class << self
|
class << self
|
||||||
def as_home_timeline(account)
|
def as_home_timeline(account)
|
||||||
where(account: [account] + account.following)
|
where(account: [account] + account.following)
|
||||||
|
|
|
@ -16,7 +16,7 @@ class FollowRemoteAccountService < BaseService
|
||||||
return Account.find_local(username) if TagManager.instance.local_domain?(domain)
|
return Account.find_local(username) if TagManager.instance.local_domain?(domain)
|
||||||
|
|
||||||
account = Account.find_remote(username, domain)
|
account = Account.find_remote(username, domain)
|
||||||
return account unless account_needs_webfinger_update?(account)
|
return account unless account&.last_webfingered_at.nil? || 1.day.from_now(account.last_webfingered_at) < Time.now.utc
|
||||||
|
|
||||||
Rails.logger.debug "Looking up webfinger for #{uri}"
|
Rails.logger.debug "Looking up webfinger for #{uri}"
|
||||||
|
|
||||||
|
@ -62,10 +62,6 @@ class FollowRemoteAccountService < BaseService
|
||||||
|
|
||||||
private
|
private
|
||||||
|
|
||||||
def account_needs_webfinger_update?(account)
|
|
||||||
account&.last_webfingered_at.nil? || account.last_webfingered_at <= 1.day.ago
|
|
||||||
end
|
|
||||||
|
|
||||||
def get_feed(url)
|
def get_feed(url)
|
||||||
response = http_client.get(Addressable::URI.parse(url))
|
response = http_client.get(Addressable::URI.parse(url))
|
||||||
[response.to_s, Nokogiri::XML(response)]
|
[response.to_s, Nokogiri::XML(response)]
|
||||||
|
|
|
@ -6,7 +6,7 @@ class NotifyService < BaseService
|
||||||
@activity = activity
|
@activity = activity
|
||||||
@notification = Notification.new(account: @recipient, activity: @activity)
|
@notification = Notification.new(account: @recipient, activity: @activity)
|
||||||
|
|
||||||
return if recipient.user.nil? || blocked?
|
return if blocked? || recipient.user.nil?
|
||||||
|
|
||||||
create_notification
|
create_notification
|
||||||
send_email if email_enabled?
|
send_email if email_enabled?
|
||||||
|
|
|
@ -19,7 +19,6 @@ class PostStatusService < BaseService
|
||||||
sensitive: options[:sensitive],
|
sensitive: options[:sensitive],
|
||||||
spoiler_text: options[:spoiler_text] || '',
|
spoiler_text: options[:spoiler_text] || '',
|
||||||
visibility: options[:visibility],
|
visibility: options[:visibility],
|
||||||
language: detect_language(text),
|
|
||||||
application: options[:application])
|
application: options[:application])
|
||||||
|
|
||||||
attach_media(status, media)
|
attach_media(status, media)
|
||||||
|
@ -52,10 +51,6 @@ class PostStatusService < BaseService
|
||||||
media.update(status_id: status.id)
|
media.update(status_id: status.id)
|
||||||
end
|
end
|
||||||
|
|
||||||
def detect_language(text)
|
|
||||||
WhatLanguage.new(:all).language_iso(text) || 'en'
|
|
||||||
end
|
|
||||||
|
|
||||||
def process_mentions_service
|
def process_mentions_service
|
||||||
@process_mentions_service ||= ProcessMentionsService.new
|
@process_mentions_service ||= ProcessMentionsService.new
|
||||||
end
|
end
|
||||||
|
|
|
@ -119,7 +119,6 @@ class ProcessFeedService < BaseService
|
||||||
spoiler_text: content_warning(entry),
|
spoiler_text: content_warning(entry),
|
||||||
created_at: published(entry),
|
created_at: published(entry),
|
||||||
reply: thread?(entry),
|
reply: thread?(entry),
|
||||||
language: content_language(entry),
|
|
||||||
visibility: visibility_scope(entry)
|
visibility: visibility_scope(entry)
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -162,7 +161,13 @@ class ProcessFeedService < BaseService
|
||||||
xml.xpath('./xmlns:link[@rel="mentioned"]', xmlns: TagManager::XMLNS).each do |link|
|
xml.xpath('./xmlns:link[@rel="mentioned"]', xmlns: TagManager::XMLNS).each do |link|
|
||||||
next if [TagManager::TYPES[:group], TagManager::TYPES[:collection]].include? link['ostatus:object-type']
|
next if [TagManager::TYPES[:group], TagManager::TYPES[:collection]].include? link['ostatus:object-type']
|
||||||
|
|
||||||
mentioned_account = account_from_href(link['href'])
|
url = Addressable::URI.parse(link['href'])
|
||||||
|
|
||||||
|
mentioned_account = if TagManager.instance.web_domain?(url.host)
|
||||||
|
Account.find_local(url.path.gsub('/users/', ''))
|
||||||
|
else
|
||||||
|
Account.find_by(url: link['href']) || FetchRemoteAccountService.new.call(link['href'])
|
||||||
|
end
|
||||||
|
|
||||||
next if mentioned_account.nil? || processed_account_ids.include?(mentioned_account.id)
|
next if mentioned_account.nil? || processed_account_ids.include?(mentioned_account.id)
|
||||||
|
|
||||||
|
@ -173,16 +178,6 @@ class ProcessFeedService < BaseService
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
def account_from_href(href)
|
|
||||||
url = Addressable::URI.parse(href)
|
|
||||||
|
|
||||||
if TagManager.instance.web_domain?(url.host)
|
|
||||||
Account.find_local(url.path.gsub('/users/', ''))
|
|
||||||
else
|
|
||||||
Account.find_by(uri: href) || Account.find_by(url: href) || FetchRemoteAccountService.new.call(href)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
def hashtags_from_xml(parent, xml)
|
def hashtags_from_xml(parent, xml)
|
||||||
tags = xml.xpath('./xmlns:category', xmlns: TagManager::XMLNS).map { |category| category['term'] }.select(&:present?)
|
tags = xml.xpath('./xmlns:category', xmlns: TagManager::XMLNS).map { |category| category['term'] }.select(&:present?)
|
||||||
ProcessHashtagsService.new.call(parent, tags)
|
ProcessHashtagsService.new.call(parent, tags)
|
||||||
|
@ -239,10 +234,6 @@ class ProcessFeedService < BaseService
|
||||||
xml.at_xpath('./xmlns:content', xmlns: TagManager::XMLNS).content
|
xml.at_xpath('./xmlns:content', xmlns: TagManager::XMLNS).content
|
||||||
end
|
end
|
||||||
|
|
||||||
def content_language(xml = @xml)
|
|
||||||
xml.at_xpath('./xmlns:content', xmlns: TagManager::XMLNS)['xml:lang']&.presence || 'en'
|
|
||||||
end
|
|
||||||
|
|
||||||
def content_warning(xml = @xml)
|
def content_warning(xml = @xml)
|
||||||
xml.at_xpath('./xmlns:summary', xmlns: TagManager::XMLNS)&.content || ''
|
xml.at_xpath('./xmlns:summary', xmlns: TagManager::XMLNS)&.content || ''
|
||||||
end
|
end
|
||||||
|
|
|
@ -6,7 +6,6 @@ class UnfollowService < BaseService
|
||||||
# @param [Account] target_account Which to unfollow
|
# @param [Account] target_account Which to unfollow
|
||||||
def call(source_account, target_account)
|
def call(source_account, target_account)
|
||||||
follow = source_account.unfollow!(target_account)
|
follow = source_account.unfollow!(target_account)
|
||||||
return unless follow
|
|
||||||
NotificationWorker.perform_async(build_xml(follow), source_account.id, target_account.id) unless target_account.local?
|
NotificationWorker.perform_async(build_xml(follow), source_account.id, target_account.id) unless target_account.local?
|
||||||
UnmergeWorker.perform_async(target_account.id, source_account.id)
|
UnmergeWorker.perform_async(target_account.id, source_account.id)
|
||||||
end
|
end
|
||||||
|
|
|
@ -1,34 +1,34 @@
|
||||||
.card.h-card.p-author{ style: "background-image: url(#{account.header.url( :original)})" }
|
.card.h-card.p-author{ style: "background-image: url(#{@account.header.url( :original)})" }
|
||||||
- if user_signed_in? && current_account.id != account.id && !current_account.requested?(account)
|
- if user_signed_in? && current_account.id != @account.id && !current_account.requested?(@account)
|
||||||
.controls
|
.controls
|
||||||
- if current_account.following?(account)
|
- if current_account.following?(@account)
|
||||||
= link_to t('accounts.unfollow'), unfollow_account_path(account), data: { method: :post }, class: 'button'
|
= link_to t('accounts.unfollow'), unfollow_account_path(@account), data: { method: :post }, class: 'button'
|
||||||
- else
|
- else
|
||||||
= link_to t('accounts.follow'), follow_account_path(account), data: { method: :post }, class: 'button'
|
= link_to t('accounts.follow'), follow_account_path(@account), data: { method: :post }, class: 'button'
|
||||||
- elsif !user_signed_in?
|
- elsif !user_signed_in?
|
||||||
.controls
|
.controls
|
||||||
.remote-follow
|
.remote-follow
|
||||||
= link_to t('accounts.remote_follow'), account_remote_follow_path(account), class: 'button'
|
= link_to t('accounts.remote_follow'), account_remote_follow_path(@account), class: 'button'
|
||||||
.avatar= image_tag account.avatar.url(:original), class: 'u-photo'
|
.avatar= image_tag @account.avatar.url(:original), class: 'u-photo'
|
||||||
%h1.name
|
%h1.name
|
||||||
%span.p-name.emojify= display_name(account)
|
%span.p-name.emojify= display_name(@account)
|
||||||
%small
|
%small
|
||||||
%span= "@#{account.username}"
|
%span= "@#{@account.username}"
|
||||||
= fa_icon('lock') if account.locked?
|
= fa_icon('lock') if @account.locked?
|
||||||
.details
|
.details
|
||||||
.bio
|
.bio
|
||||||
.account__header__content.p-note.emojify= Formatter.instance.simplified_format(account)
|
.account__header__content.p-note.emojify= Formatter.instance.simplified_format(@account)
|
||||||
|
|
||||||
.details-counters
|
.details-counters
|
||||||
.counter{ class: active_nav_class(short_account_url(account)) }
|
.counter{ class: active_nav_class(short_account_url(@account)) }
|
||||||
= link_to short_account_url(account), class: 'u-url u-uid' do
|
= link_to short_account_url(@account), class: 'u-url u-uid' do
|
||||||
%span.counter-label= t('accounts.posts')
|
%span.counter-label= t('accounts.posts')
|
||||||
%span.counter-number= number_with_delimiter account.statuses_count
|
%span.counter-number= number_with_delimiter @account.statuses_count
|
||||||
.counter{ class: active_nav_class(following_account_url(account)) }
|
.counter{ class: active_nav_class(following_account_url(@account)) }
|
||||||
= link_to following_account_url(account) do
|
= link_to following_account_url(@account) do
|
||||||
%span.counter-label= t('accounts.following')
|
%span.counter-label= t('accounts.following')
|
||||||
%span.counter-number= number_with_delimiter account.following_count
|
%span.counter-number= number_with_delimiter @account.following_count
|
||||||
.counter{ class: active_nav_class(followers_account_url(account)) }
|
.counter{ class: active_nav_class(followers_account_url(@account)) }
|
||||||
= link_to followers_account_url(account) do
|
= link_to followers_account_url(@account) do
|
||||||
%span.counter-label= t('accounts.followers')
|
%span.counter-label= t('accounts.followers')
|
||||||
%span.counter-number= number_with_delimiter account.followers_count
|
%span.counter-number= number_with_delimiter @account.followers_count
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
- content_for :page_title do
|
- content_for :page_title do
|
||||||
= t('accounts.people_who_follow', name: display_name(@account))
|
= t('accounts.people_who_follow', name: display_name(@account))
|
||||||
|
|
||||||
= render 'header', account: @account
|
= render partial: 'header'
|
||||||
|
|
||||||
.accounts-grid
|
.accounts-grid
|
||||||
- if @followers.empty?
|
- if @followers.empty?
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
- content_for :page_title do
|
- content_for :page_title do
|
||||||
= t('accounts.people_followed_by', name: display_name(@account))
|
= t('accounts.people_followed_by', name: display_name(@account))
|
||||||
|
|
||||||
= render 'header', account: @account
|
= render partial: 'header'
|
||||||
|
|
||||||
.accounts-grid
|
.accounts-grid
|
||||||
- if @following.empty?
|
- if @following.empty?
|
||||||
|
|
|
@ -14,13 +14,13 @@
|
||||||
%meta{ property: 'og:image:height', content: '120' }/
|
%meta{ property: 'og:image:height', content: '120' }/
|
||||||
%meta{ property: 'twitter:card', content: 'summary' }/
|
%meta{ property: 'twitter:card', content: 'summary' }/
|
||||||
|
|
||||||
- if show_landing_strip?
|
- if !user_signed_in? && !single_user_mode?
|
||||||
= render partial: 'shared/landing_strip', locals: { account: @account }
|
= render partial: 'shared/landing_strip', locals: { account: @account }
|
||||||
|
|
||||||
.h-feed
|
.h-feed
|
||||||
%data.p-name{ value: "#{@account.username} on #{Rails.configuration.x.local_domain}" }/
|
%data.p-name{ value: "#{@account.username} on #{Rails.configuration.x.local_domain}" }/
|
||||||
|
|
||||||
= render 'header', account: @account
|
= render partial: 'header'
|
||||||
|
|
||||||
- if @statuses.empty?
|
- if @statuses.empty?
|
||||||
.accounts-grid
|
.accounts-grid
|
||||||
|
|
|
@ -61,8 +61,7 @@
|
||||||
= surround '(', ')' do
|
= surround '(', ')' do
|
||||||
= number_to_human_size @account.media_attachments.sum('file_file_size')
|
= number_to_human_size @account.media_attachments.sum('file_file_size')
|
||||||
|
|
||||||
- if @account.local?
|
%div{ style: 'float: right' }
|
||||||
%div{ style: 'float: right' }
|
|
||||||
= link_to t('admin.accounts.reset_password'), admin_account_reset_path(@account.id), method: :create, class: 'button'
|
= link_to t('admin.accounts.reset_password'), admin_account_reset_path(@account.id), method: :create, class: 'button'
|
||||||
|
|
||||||
%div{ style: 'float: left' }
|
%div{ style: 'float: left' }
|
||||||
|
|
|
@ -1,24 +1,24 @@
|
||||||
- content_for :page_title do
|
- content_for :page_title do
|
||||||
= t('admin.domain_blocks.title')
|
= t('admin.domain_block.title')
|
||||||
|
|
||||||
%table.table
|
%table.table
|
||||||
%thead
|
%thead
|
||||||
%tr
|
%tr
|
||||||
%th= t('admin.domain_blocks.domain')
|
%th= t('admin.domain_block.domain')
|
||||||
%th= t('admin.domain_blocks.severity')
|
%th= t('admin.domain_block.severity')
|
||||||
%th= t('admin.domain_blocks.reject_media')
|
%th= t('admin.domain_block.reject_media')
|
||||||
%th
|
%th
|
||||||
%tbody
|
%tbody
|
||||||
- @blocks.each do |block|
|
- @blocks.each do |block|
|
||||||
%tr
|
%tr
|
||||||
%td
|
%td
|
||||||
%samp= block.domain
|
%samp= block.domain
|
||||||
%td= t("admin.domain_blocks.severities.#{block.severity}")
|
%td= t("admin.domain_block.severities.#{block.severity}")
|
||||||
%td
|
%td
|
||||||
- if block.reject_media? || block.suspend?
|
- if block.reject_media? || block.suspend?
|
||||||
%i.fa.fa-check
|
%i.fa.fa-check
|
||||||
%td
|
%td
|
||||||
= table_link_to 'undo', t('admin.domain_blocks.undo'), admin_domain_block_path(block)
|
= table_link_to 'undo', t('admin.domain_block.undo'), admin_domain_block_path(block)
|
||||||
|
|
||||||
= paginate @blocks
|
= paginate @blocks
|
||||||
= link_to t('admin.domain_blocks.add_new'), new_admin_domain_block_path, class: 'button'
|
= link_to t('admin.domain_block.add_new'), new_admin_domain_block_path, class: 'button'
|
||||||
|
|
|
@ -1,17 +1,17 @@
|
||||||
- content_for :page_title do
|
- content_for :page_title do
|
||||||
= t('.title')
|
= t('admin.domain_block.new.title')
|
||||||
|
|
||||||
= simple_form_for @domain_block, url: admin_domain_blocks_path do |f|
|
= simple_form_for @domain_block, url: admin_domain_blocks_path do |f|
|
||||||
= render 'shared/error_messages', object: @domain_block
|
= render 'shared/error_messages', object: @domain_block
|
||||||
|
|
||||||
%p.hint= t('.hint')
|
%p.hint= t('admin.domain_block.new.hint')
|
||||||
|
|
||||||
= f.input :domain, placeholder: t('admin.domain_blocks.domain')
|
= f.input :domain, placeholder: t('admin.domain_block.domain')
|
||||||
= f.input :severity, collection: DomainBlock.severities.keys, wrapper: :with_label, include_blank: false, label_method: lambda { |type| t(".severity.#{type}") }
|
= f.input :severity, collection: DomainBlock.severities.keys, wrapper: :with_label, include_blank: false, label_method: lambda { |type| I18n.t("admin.domain_block.new.severity.#{type}") }
|
||||||
|
|
||||||
%p.hint= t('.severity.desc_html')
|
%p.hint= t('admin.domain_block.new.severity.desc_html')
|
||||||
|
|
||||||
= f.input :reject_media, as: :boolean, wrapper: :with_label, label: I18n.t('admin.domain_blocks.reject_media'), hint: I18n.t('admin.domain_blocks.reject_media_hint')
|
= f.input :reject_media, as: :boolean, wrapper: :with_label, label: I18n.t('admin.domain_block.reject_media'), hint: I18n.t('admin.domain_block.reject_media_hint')
|
||||||
|
|
||||||
.actions
|
.actions
|
||||||
= f.button :button, t('.create'), type: :submit
|
= f.button :button, t('admin.domain_block.new.create'), type: :submit
|
||||||
|
|
|
@ -1,15 +1,9 @@
|
||||||
- content_for :page_title do
|
- content_for :page_title do
|
||||||
= t('admin.domain_blocks.show.title', domain: @domain_block.domain)
|
= t('admin.domain_block.show.title', domain: @domain_block.domain)
|
||||||
|
|
||||||
= simple_form_for @domain_block, url: admin_domain_block_path(@domain_block), method: :delete do |f|
|
= simple_form_for @domain_block, url: admin_domain_block_path(@domain_block), method: :delete do |f|
|
||||||
|
|
||||||
= f.input :retroactive,
|
= f.input :retroactive, as: :boolean, wrapper: :with_label, label: I18n.t("admin.domain_block.show.retroactive.#{@domain_block.severity}"), hint: I18n.t('admin.domain_block.show.affected_accounts', count: Account.where(domain: @domain_block.domain).count)
|
||||||
as: :boolean,
|
|
||||||
wrapper: :with_label,
|
|
||||||
label: t(".retroactive.#{@domain_block.severity}"),
|
|
||||||
hint: t(:affected_accounts,
|
|
||||||
scope: [:admin, :domain_blocks, :show],
|
|
||||||
count: @domain_block.accounts_count)
|
|
||||||
|
|
||||||
.actions
|
.actions
|
||||||
= f.button :button, t('.undo'), type: :submit
|
= f.button :button, t('admin.domain_block.show.undo'), type: :submit
|
||||||
|
|
|
@ -12,7 +12,7 @@
|
||||||
%p
|
%p
|
||||||
%strong= t('admin.reports.comment.label')
|
%strong= t('admin.reports.comment.label')
|
||||||
\:
|
\:
|
||||||
= @report.comment.presence || t('admin.reports.comment.none')
|
= @report.comment.presence || t('reports.comment.none')
|
||||||
|
|
||||||
- unless @report.statuses.empty?
|
- unless @report.statuses.empty?
|
||||||
%hr/
|
%hr/
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
- content_for :header_tags do
|
- content_for :header_tags do
|
||||||
%script#initial-state{:type => 'application/json'}!= json_escape(render(file: 'home/initial_state', formats: :json))
|
%script#initial-state{:type => 'application/json'}!= json_escape(render(file: 'home/initial_state', formats: :json))
|
||||||
|
|
||||||
= javascript_include_tag 'application', integrity: true, crossorigin: 'anonymous'
|
= javascript_include_tag 'application', integrity: true
|
||||||
|
|
||||||
= react_component 'Mastodon', default_props, class: 'app-holder', prerender: false
|
= react_component 'Mastodon', default_props, class: 'app-holder', prerender: false
|
||||||
|
|
|
@ -5,9 +5,7 @@ node(:meta) do
|
||||||
streaming_api_base_url: @streaming_api_base_url,
|
streaming_api_base_url: @streaming_api_base_url,
|
||||||
access_token: @token,
|
access_token: @token,
|
||||||
locale: I18n.locale,
|
locale: I18n.locale,
|
||||||
domain: Rails.configuration.x.local_domain,
|
|
||||||
me: current_account.id,
|
me: current_account.id,
|
||||||
admin: @admin.try(:id),
|
|
||||||
boost_modal: current_account.user.setting_boost_modal,
|
boost_modal: current_account.user.setting_boost_modal,
|
||||||
}
|
}
|
||||||
end
|
end
|
||||||
|
@ -20,10 +18,9 @@ node(:compose) do
|
||||||
end
|
end
|
||||||
|
|
||||||
node(:accounts) do
|
node(:accounts) do
|
||||||
store = {}
|
{
|
||||||
store[current_account.id] = partial('api/v1/accounts/show', object: current_account)
|
current_account.id => partial('api/v1/accounts/show', object: current_account),
|
||||||
store[@admin.id] = partial('api/v1/accounts/show', object: @admin) unless @admin.nil?
|
}
|
||||||
store
|
|
||||||
end
|
end
|
||||||
|
|
||||||
node(:settings) { @web_settings }
|
node(:settings) { @web_settings }
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
- content_for :header_tags do
|
- content_for :header_tags do
|
||||||
= javascript_include_tag 'application_public', integrity: true, crossorigin: 'anonymous'
|
= javascript_include_tag 'application_public', integrity: true
|
||||||
|
|
||||||
- content_for :content do
|
- content_for :content do
|
||||||
.admin-wrapper
|
.admin-wrapper
|
||||||
|
|
2
app/views/layouts/application.html.haml
Executable file → Normal file
2
app/views/layouts/application.html.haml
Executable file → Normal file
|
@ -17,7 +17,7 @@
|
||||||
= ' - '
|
= ' - '
|
||||||
= site_title
|
= site_title
|
||||||
|
|
||||||
= stylesheet_link_tag stylesheet_for_layout, media: 'all'
|
= stylesheet_link_tag 'application', media: 'all'
|
||||||
= csrf_meta_tags
|
= csrf_meta_tags
|
||||||
|
|
||||||
= yield :header_tags
|
= yield :header_tags
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
- content_for :header_tags do
|
- content_for :header_tags do
|
||||||
= javascript_include_tag 'application_public', integrity: true, crossorigin: 'anonymous'
|
= javascript_include_tag 'application_public', integrity: true
|
||||||
|
|
||||||
- content_for :content do
|
- content_for :content do
|
||||||
.container
|
.container
|
||||||
|
|
|
@ -3,6 +3,6 @@
|
||||||
%head
|
%head
|
||||||
%meta{:charset => 'utf-8'}/
|
%meta{:charset => 'utf-8'}/
|
||||||
= stylesheet_link_tag 'application', media: 'all'
|
= stylesheet_link_tag 'application', media: 'all'
|
||||||
= javascript_include_tag 'application_public', integrity: true, crossorigin: 'anonymous'
|
= javascript_include_tag 'application_public', integrity: true
|
||||||
%body.embed
|
%body.embed
|
||||||
= yield
|
= yield
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
- content_for :header_tags do
|
- content_for :header_tags do
|
||||||
= javascript_include_tag 'application_public', integrity: true, crossorigin: 'anonymous'
|
= javascript_include_tag 'application_public', integrity: true
|
||||||
|
|
||||||
- content_for :content do
|
- content_for :content do
|
||||||
.container= yield
|
.container= yield
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
%p.hint= t('two_factor_auth.recovery_instructions')
|
%p.hint= t('two_factor_auth.recovery_instructions')
|
||||||
|
|
||||||
|
%h3= t('two_factor_auth.recovery_codes')
|
||||||
%ol.recovery-codes
|
%ol.recovery-codes
|
||||||
- recovery_codes.each do |code|
|
- @codes.each do |code|
|
||||||
%li
|
%li
|
||||||
%samp= code
|
%samp= code
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
- content_for :page_title do
|
- content_for :page_title do
|
||||||
= t('settings.two_factor_auth')
|
= t('settings.two_factor_auth')
|
||||||
|
|
||||||
= render partial: 'recovery_codes', object: @codes
|
= render 'recovery_codes'
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
- content_for :page_title do
|
- content_for :page_title do
|
||||||
= t('settings.two_factor_auth')
|
= t('settings.two_factor_auth')
|
||||||
|
|
||||||
= render partial: 'recovery_codes', object: @codes
|
= render 'recovery_codes'
|
||||||
|
|
|
@ -10,8 +10,6 @@
|
||||||
= link_to t('two_factor_auth.setup'), new_settings_two_factor_auth_path, class: 'block-button'
|
= link_to t('two_factor_auth.setup'), new_settings_two_factor_auth_path, class: 'block-button'
|
||||||
|
|
||||||
- if current_user.otp_required_for_login
|
- if current_user.otp_required_for_login
|
||||||
%p
|
|
||||||
|
|
||||||
.simple_form
|
.simple_form
|
||||||
%p.hint= t('two_factor_auth.lost_recovery_codes')
|
%p.hint= t('two_factor_auth.lost_recovery_codes')
|
||||||
= link_to t('two_factor_auth.generate_recovery_codes'), recovery_codes_settings_two_factor_auth_path, data: { method: 'POST' }, class: 'block-button'
|
= link_to t('two_factor_auth.generate_recovery_codes'), recovery_codes_settings_two_factor_auth_path, data: { method: 'POST' }, class: 'block-button'
|
||||||
|
|
|
@ -1,4 +0,0 @@
|
||||||
- if activity.is_a?(Status) && activity.spoiler_text?
|
|
||||||
%meta{ property: 'og:description', content: activity.spoiler_text }/
|
|
||||||
- else
|
|
||||||
%meta{ property: 'og:description', content: activity.content }/
|
|
|
@ -1,6 +0,0 @@
|
||||||
- if activity.is_a?(Status) && activity.non_sensitive_with_media?
|
|
||||||
%meta{ property: 'og:image', content: full_asset_url(activity.media_attachments.first.file.url(:small)) }/
|
|
||||||
- else
|
|
||||||
%meta{ property: 'og:image', content: full_asset_url(account.avatar.url(:original)) }/
|
|
||||||
%meta{ property: 'og:image:width', content: '120' }/
|
|
||||||
%meta{ property: 'og:image:height', content: '120' }/
|
|
|
@ -6,12 +6,21 @@
|
||||||
%meta{ property: 'og:type', content: 'article' }/
|
%meta{ property: 'og:type', content: 'article' }/
|
||||||
%meta{ property: 'og:title', content: "#{@account.username} on #{Rails.configuration.x.local_domain}" }/
|
%meta{ property: 'og:title', content: "#{@account.username} on #{Rails.configuration.x.local_domain}" }/
|
||||||
|
|
||||||
= render 'stream_entries/og_description', activity: @stream_entry.activity
|
- if @stream_entry.activity.is_a?(Status) && !@stream_entry.activity.spoiler_text.blank?
|
||||||
= render 'stream_entries/og_image', activity: @stream_entry.activity, account: @account
|
%meta{ property: 'og:description', content: @stream_entry.activity.spoiler_text }/
|
||||||
|
- else
|
||||||
|
%meta{ property: 'og:description', content: @stream_entry.activity.content }/
|
||||||
|
|
||||||
|
- if @stream_entry.activity.is_a?(Status) && !@stream_entry.activity.sensitive? && @stream_entry.activity.media_attachments.size > 0
|
||||||
|
%meta{ property: 'og:image', content: full_asset_url(@stream_entry.activity.media_attachments.first.file.url(:small)) }/
|
||||||
|
- else
|
||||||
|
%meta{ property: 'og:image', content: full_asset_url(@account.avatar.url(:original)) }/
|
||||||
|
%meta{ property: 'og:image:width', content: '120' }/
|
||||||
|
%meta{ property: 'og:image:height', content: '120' }/
|
||||||
|
|
||||||
%meta{ property: 'twitter:card', content: 'summary' }/
|
%meta{ property: 'twitter:card', content: 'summary' }/
|
||||||
|
|
||||||
- if show_landing_strip?
|
- if !user_signed_in? && !single_user_mode?
|
||||||
= render partial: 'shared/landing_strip', locals: { account: @stream_entry.account }
|
= render partial: 'shared/landing_strip', locals: { account: @stream_entry.account }
|
||||||
|
|
||||||
.activity-stream.activity-stream-headless.h-entry
|
.activity-stream.activity-stream-headless.h-entry
|
||||||
|
|
17
bin/rspec
17
bin/rspec
|
@ -1,17 +0,0 @@
|
||||||
#!/usr/bin/env ruby
|
|
||||||
# frozen_string_literal: true
|
|
||||||
#
|
|
||||||
# This file was generated by Bundler.
|
|
||||||
#
|
|
||||||
# The application 'rspec' is installed as part of a gem, and
|
|
||||||
# this file is here to facilitate running it.
|
|
||||||
#
|
|
||||||
|
|
||||||
require "pathname"
|
|
||||||
ENV["BUNDLE_GEMFILE"] ||= File.expand_path("../../Gemfile",
|
|
||||||
Pathname.new(__FILE__).realpath)
|
|
||||||
|
|
||||||
require "rubygems"
|
|
||||||
require "bundler/setup"
|
|
||||||
|
|
||||||
load Gem.bin_path("rspec-core", "rspec")
|
|
|
@ -1,11 +1,8 @@
|
||||||
# frozen_string_literal: true
|
lock '3.7.2'
|
||||||
|
|
||||||
lock '3.8.0'
|
|
||||||
|
|
||||||
set :repo_url, ENV.fetch('REPO', 'https://github.com/tootsuite/mastodon.git')
|
|
||||||
set :branch, ENV.fetch('BRANCH', 'master')
|
|
||||||
|
|
||||||
set :application, 'mastodon'
|
set :application, 'mastodon'
|
||||||
|
set :repo_url, 'https://github.com/tootsuite/mastodon.git'
|
||||||
|
set :branch, 'master'
|
||||||
set :rbenv_type, :user
|
set :rbenv_type, :user
|
||||||
set :rbenv_ruby, File.read('.ruby-version').strip
|
set :rbenv_ruby, File.read('.ruby-version').strip
|
||||||
set :migration_role, :app
|
set :migration_role, :app
|
||||||
|
|
|
@ -104,7 +104,6 @@ Rails.application.configure do
|
||||||
:authentication => ENV['SMTP_AUTH_METHOD'] || :plain,
|
:authentication => ENV['SMTP_AUTH_METHOD'] || :plain,
|
||||||
:openssl_verify_mode => ENV['SMTP_OPENSSL_VERIFY_MODE'],
|
:openssl_verify_mode => ENV['SMTP_OPENSSL_VERIFY_MODE'],
|
||||||
:enable_starttls_auto => ENV['SMTP_ENABLE_STARTTLS_AUTO'] || true,
|
:enable_starttls_auto => ENV['SMTP_ENABLE_STARTTLS_AUTO'] || true,
|
||||||
:ca_file => "/etc/ssl/certs/ca-certificates.crt"
|
|
||||||
}
|
}
|
||||||
|
|
||||||
config.action_mailer.delivery_method = ENV.fetch('SMTP_DELIVERY_METHOD', 'smtp').to_sym
|
config.action_mailer.delivery_method = ENV.fetch('SMTP_DELIVERY_METHOD', 'smtp').to_sym
|
||||||
|
|
|
@ -31,22 +31,8 @@ search:
|
||||||
- app/assets/fonts
|
- app/assets/fonts
|
||||||
- app/assets/videos
|
- app/assets/videos
|
||||||
|
|
||||||
ignore_missing:
|
|
||||||
- 'activemodel.errors.*'
|
|
||||||
- 'activerecord.attributes.*'
|
|
||||||
- 'activerecord.errors.*'
|
|
||||||
- '{devise,pagination,doorkeeper}.*'
|
|
||||||
- '{datetime,time}.*'
|
|
||||||
- 'simple_form.{yes,no}'
|
|
||||||
- 'simple_form.{placeholders,hints,labels}.*'
|
|
||||||
- 'simple_form.{error_notification,required}.:'
|
|
||||||
- 'errors.messages.*'
|
|
||||||
- 'activerecord.errors.models.doorkeeper/*'
|
|
||||||
|
|
||||||
ignore_unused:
|
ignore_unused:
|
||||||
- 'activemodel.errors.*'
|
|
||||||
- 'activerecord.attributes.*'
|
- 'activerecord.attributes.*'
|
||||||
- 'activerecord.errors.*'
|
|
||||||
- '{devise,pagination,doorkeeper}.*'
|
- '{devise,pagination,doorkeeper}.*'
|
||||||
- '{datetime,time}.*'
|
- '{datetime,time}.*'
|
||||||
- 'simple_form.{yes,no}'
|
- 'simple_form.{yes,no}'
|
||||||
|
|
3
config/initializers/assets.rb
Executable file → Normal file
3
config/initializers/assets.rb
Executable file → Normal file
|
@ -8,6 +8,5 @@ Rails.application.config.assets.version = '1.0'
|
||||||
|
|
||||||
# Precompile additional assets.
|
# Precompile additional assets.
|
||||||
# application.js, application.css, and all non-JS/CSS in app/assets folder are already added.
|
# application.js, application.css, and all non-JS/CSS in app/assets folder are already added.
|
||||||
Rails.application.config.assets.precompile += %w(application_public.js custom.css)
|
Rails.application.config.assets.precompile += %w(application_public.js)
|
||||||
|
|
||||||
Rails.application.config.assets.initialize_on_precompile = true
|
Rails.application.config.assets.initialize_on_precompile = true
|
||||||
|
|
|
@ -9,7 +9,7 @@ Rails.application.configure do
|
||||||
config.x.local_domain = host
|
config.x.local_domain = host
|
||||||
config.x.web_domain = web_host
|
config.x.web_domain = web_host
|
||||||
config.x.use_https = https
|
config.x.use_https = https
|
||||||
config.x.use_s3 = ENV['S3_ENABLED'] == 'true' || ENV['GCS_ENABLED'] == 'true'
|
config.x.use_s3 = ENV['S3_ENABLED'] == 'true'
|
||||||
|
|
||||||
config.action_mailer.default_url_options = { host: web_host, protocol: https ? 'https://' : 'http://', trailing_slash: false }
|
config.action_mailer.default_url_options = { host: web_host, protocol: https ? 'https://' : 'http://', trailing_slash: false }
|
||||||
config.x.streaming_api_base_url = 'http://localhost:4000'
|
config.x.streaming_api_base_url = 'http://localhost:4000'
|
||||||
|
|
|
@ -15,7 +15,7 @@ if ENV['S3_ENABLED'] == 'true'
|
||||||
Paperclip::Attachment.default_options[:url] = ':s3_domain_url'
|
Paperclip::Attachment.default_options[:url] = ':s3_domain_url'
|
||||||
Paperclip::Attachment.default_options[:s3_host_name] = ENV.fetch('S3_HOSTNAME') { "s3-#{ENV.fetch('S3_REGION')}.amazonaws.com" }
|
Paperclip::Attachment.default_options[:s3_host_name] = ENV.fetch('S3_HOSTNAME') { "s3-#{ENV.fetch('S3_REGION')}.amazonaws.com" }
|
||||||
Paperclip::Attachment.default_options[:path] = '/:class/:attachment/:id_partition/:style/:filename'
|
Paperclip::Attachment.default_options[:path] = '/:class/:attachment/:id_partition/:style/:filename'
|
||||||
Paperclip::Attachment.default_options[:s3_headers] = { 'Cache-Control' => 'max-age=315576000' }
|
Paperclip::Attachment.default_options[:s3_headers] = { 'Cache-Control' => 'max-age=315576000', 'Expires' => 10.years.from_now.httpdate }
|
||||||
Paperclip::Attachment.default_options[:s3_permissions] = 'public-read'
|
Paperclip::Attachment.default_options[:s3_permissions] = 'public-read'
|
||||||
Paperclip::Attachment.default_options[:s3_region] = ENV.fetch('S3_REGION') { 'us-east-1' }
|
Paperclip::Attachment.default_options[:s3_region] = ENV.fetch('S3_REGION') { 'us-east-1' }
|
||||||
|
|
||||||
|
@ -42,23 +42,3 @@ else
|
||||||
Paperclip::Attachment.default_options[:path] = (ENV['PAPERCLIP_ROOT_PATH'] || ':rails_root/public/system') + '/:class/:attachment/:id_partition/:style/:filename'
|
Paperclip::Attachment.default_options[:path] = (ENV['PAPERCLIP_ROOT_PATH'] || ':rails_root/public/system') + '/:class/:attachment/:id_partition/:style/:filename'
|
||||||
Paperclip::Attachment.default_options[:url] = (ENV['PAPERCLIP_ROOT_URL'] || '/system') + '/:class/:attachment/:id_partition/:style/:filename'
|
Paperclip::Attachment.default_options[:url] = (ENV['PAPERCLIP_ROOT_URL'] || '/system') + '/:class/:attachment/:id_partition/:style/:filename'
|
||||||
end
|
end
|
||||||
|
|
||||||
if ENV['GCS_ENABLED'] == 'true'
|
|
||||||
Paperclip::Attachment.default_options.update({
|
|
||||||
:path => "images/:class/:id/:attachment/:style/img_:fingerprint",
|
|
||||||
:storage => :fog,
|
|
||||||
:fog_credentials => {
|
|
||||||
:provider => 'Google',
|
|
||||||
:google_storage_access_key_id => ENV.fetch('GCS_ACCESS_KEY_ID'),
|
|
||||||
:google_storage_secret_access_key => ENV.fetch('GCS_SECRET_ACCESS_KEY'),
|
|
||||||
:path_style => !ENV['GCS_ALIAS_HOST'].blank?
|
|
||||||
},
|
|
||||||
:fog_directory => ENV.fetch('GCS_BUCKET'),
|
|
||||||
:fog_public => true,
|
|
||||||
:fog_host => 'https://' + ENV.fetch('GCS_BUCKET'),
|
|
||||||
})
|
|
||||||
|
|
||||||
unless ENV['GCS_ALIAS_HOST'].blank?
|
|
||||||
Paperclip::Attachment.default_options[:url] = ENV['GCS_ALIAS_HOST']
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
|
@ -160,6 +160,8 @@ bg:
|
||||||
disable: Деактивирай
|
disable: Деактивирай
|
||||||
enable: Активирай
|
enable: Активирай
|
||||||
instructions_html: "<strong>Сканирай този QR код с Google Authenticator или подобно приложение от своя телефон</strong>. Oтсега нататък, това приложение ще генерира код, който ще трябва да въвеждаш при всяко влизане."
|
instructions_html: "<strong>Сканирай този QR код с Google Authenticator или подобно приложение от своя телефон</strong>. Oтсега нататък, това приложение ще генерира код, който ще трябва да въвеждаш при всяко влизане."
|
||||||
|
plaintext_secret_html: 'Тайна в обикновен текст: <samp>%{secret}</samp>'
|
||||||
|
warning: Ако не можеш да настроиш приложението за удостверяване сега, избери "Деактивирай". В противен случай, няма да можеш да влезеш в акаунта си.
|
||||||
users:
|
users:
|
||||||
invalid_email: E-mail адресът е невалиден
|
invalid_email: E-mail адресът е невалиден
|
||||||
invalid_otp_token: Невалиден код
|
invalid_otp_token: Невалиден код
|
||||||
|
|
|
@ -71,7 +71,6 @@ en:
|
||||||
profile_url: Profile URL
|
profile_url: Profile URL
|
||||||
public: Public
|
public: Public
|
||||||
push_subscription_expires: PuSH subscription expires
|
push_subscription_expires: PuSH subscription expires
|
||||||
reset_password: Reset password
|
|
||||||
salmon_url: Salmon URL
|
salmon_url: Salmon URL
|
||||||
silence: Silence
|
silence: Silence
|
||||||
statuses: Statuses
|
statuses: Statuses
|
||||||
|
@ -80,7 +79,7 @@ en:
|
||||||
undo_suspension: Undo suspension
|
undo_suspension: Undo suspension
|
||||||
username: Username
|
username: Username
|
||||||
web: Web
|
web: Web
|
||||||
domain_blocks:
|
domain_block:
|
||||||
add_new: Add new
|
add_new: Add new
|
||||||
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
|
||||||
|
@ -107,7 +106,6 @@ en:
|
||||||
silence: Unsilence all existing accounts from this domain
|
silence: Unsilence all existing accounts from this domain
|
||||||
suspend: Unsuspend all existing accounts from this domain
|
suspend: Unsuspend all existing accounts from this domain
|
||||||
title: Undo domain block for %{domain}
|
title: Undo domain block for %{domain}
|
||||||
undo: Undo
|
|
||||||
title: Domain Blocks
|
title: Domain Blocks
|
||||||
undo: Undo
|
undo: Undo
|
||||||
pubsubhubbub:
|
pubsubhubbub:
|
||||||
|
@ -260,6 +258,24 @@ en:
|
||||||
missing_resource: Could not find the required redirect URL for your account
|
missing_resource: Could not find the required redirect URL for your account
|
||||||
proceed: Proceed to follow
|
proceed: Proceed to follow
|
||||||
prompt: 'You are going to follow:'
|
prompt: 'You are going to follow:'
|
||||||
|
reports:
|
||||||
|
comment:
|
||||||
|
label: Comment
|
||||||
|
none: None
|
||||||
|
delete: Delete
|
||||||
|
id: ID
|
||||||
|
mark_as_resolved: Mark as resolved
|
||||||
|
report: 'Report #%{id}'
|
||||||
|
reported_account: Reported account
|
||||||
|
reported_by: Reported by
|
||||||
|
reports: Reports
|
||||||
|
resolved: Resolved
|
||||||
|
silence_account: Silence account
|
||||||
|
status: Status
|
||||||
|
suspend_account: Suspend account
|
||||||
|
target: Target
|
||||||
|
unresolved: Unresolved
|
||||||
|
view: View
|
||||||
settings:
|
settings:
|
||||||
authorized_apps: Authorized apps
|
authorized_apps: Authorized apps
|
||||||
back: Back to Mastodon
|
back: Back to Mastodon
|
||||||
|
@ -294,9 +310,11 @@ en:
|
||||||
instructions_html: "<strong>Scan this QR code into Google Authenticator or a similiar TOTP app on your phone</strong>. From now on, that app will generate tokens that you will have to enter when logging in."
|
instructions_html: "<strong>Scan this QR code into Google Authenticator or a similiar TOTP app on your phone</strong>. From now on, that app will generate tokens that you will have to enter when logging in."
|
||||||
lost_recovery_codes: Recovery codes allow you to regain access to your account if you lose your phone. If you've lost your recovery codes, you can regenerate them here. Your old recovery codes will be invalidated.
|
lost_recovery_codes: Recovery codes allow you to regain access to your account if you lose your phone. If you've lost your recovery codes, you can regenerate them here. Your old recovery codes will be invalidated.
|
||||||
manual_instructions: 'If you can''t scan the QR code and need to enter it manually, here is the plain-text secret:'
|
manual_instructions: 'If you can''t scan the QR code and need to enter it manually, here is the plain-text secret:'
|
||||||
|
recovery_codes: Recovery Codes
|
||||||
recovery_codes_regenerated: Recovery codes successfully regenerated
|
recovery_codes_regenerated: Recovery codes successfully regenerated
|
||||||
recovery_instructions: If you ever lose access to your phone, you can use one of the recovery codes below to regain access to your account. Keep the recovery codes safe, for example by printing them and storing them with other important documents.
|
recovery_instructions: If you ever lose access to your phone, you can use one of the recovery codes below to regain access to your account. Keep the recovery codes safe, for example by printing them and storing them with other important documents.
|
||||||
setup: Set up
|
setup: Set up
|
||||||
|
warning: If you cannot configure an authenticator app right now, you should click "disable" or you won't be able to login.
|
||||||
wrong_code: The entered code was invalid! Are server time and device time correct?
|
wrong_code: The entered code was invalid! Are server time and device time correct?
|
||||||
users:
|
users:
|
||||||
invalid_email: The e-mail address is invalid
|
invalid_email: The e-mail address is invalid
|
||||||
|
|
|
@ -145,7 +145,7 @@ eo:
|
||||||
unlisted: Publika, sed ne aperos en publikaj tempolinioj
|
unlisted: Publika, sed ne aperos en publikaj tempolinioj
|
||||||
stream_entries:
|
stream_entries:
|
||||||
click_to_show: Alklaki por montri
|
click_to_show: Alklaki por montri
|
||||||
reblogged: diskonigis
|
reblogged: diskonigita
|
||||||
sensitive_content: Tikla enhavo
|
sensitive_content: Tikla enhavo
|
||||||
time:
|
time:
|
||||||
formats:
|
formats:
|
||||||
|
@ -155,6 +155,8 @@ eo:
|
||||||
disable: Malebligi
|
disable: Malebligi
|
||||||
enable: Ebligi
|
enable: Ebligi
|
||||||
instructions_html: "<strong>Skanu tiun QR-kodon per Google Authenticator aŭ per simila aplikaĵo de via poŝtelefono</strong>. De tiam, la aplikaĵo kreos nombrojn, kiujn vi devos entajpi."
|
instructions_html: "<strong>Skanu tiun QR-kodon per Google Authenticator aŭ per simila aplikaĵo de via poŝtelefono</strong>. De tiam, la aplikaĵo kreos nombrojn, kiujn vi devos entajpi."
|
||||||
|
plaintext_secret_html: 'Rekte legebla sekreta kodo: <samp>%{secret}</samp>'
|
||||||
|
warning: Se vi ne povas agordi aŭtentigan aplikaĵon nun, elektu "malebligi" aŭ vi ne plu povos ensaluti.
|
||||||
users:
|
users:
|
||||||
invalid_email: La retpoŝt-adreso ne estas valida
|
invalid_email: La retpoŝt-adreso ne estas valida
|
||||||
invalid_otp_token: La dufaktora aŭtentigila kodo ne estas valida
|
invalid_otp_token: La dufaktora aŭtentigila kodo ne estas valida
|
||||||
|
|
|
@ -160,6 +160,8 @@ es:
|
||||||
disable: Deshabilitar
|
disable: Deshabilitar
|
||||||
enable: Habilitar
|
enable: Habilitar
|
||||||
instructions_html: "<strong>Escanea este código QR desde Google Authenticator o una aplicación similar en su teléfono</strong>. Desde ahora, esta aplicación va a generar tokens que tienes que ingresar cuando quieras iniciar sesión."
|
instructions_html: "<strong>Escanea este código QR desde Google Authenticator o una aplicación similar en su teléfono</strong>. Desde ahora, esta aplicación va a generar tokens que tienes que ingresar cuando quieras iniciar sesión."
|
||||||
|
plaintext_secret_html: 'Código en texto plano: <samp>%{secret}</samp>'
|
||||||
|
warning: Sí no puedes configurar una aplicación de autenticación ahora, deberás deshabilitar la autenticación de dos factores o no podrás iniciar sesión.
|
||||||
users:
|
users:
|
||||||
invalid_email: La dirección de correo es incorrecta
|
invalid_email: La dirección de correo es incorrecta
|
||||||
invalid_otp_token: Código de dos factores incorrecto
|
invalid_otp_token: Código de dos factores incorrecto
|
||||||
|
|
|
@ -155,6 +155,8 @@ fi:
|
||||||
disable: Poista käytöstä
|
disable: Poista käytöstä
|
||||||
enable: Ota käyttöön
|
enable: Ota käyttöön
|
||||||
instructions_html: "<strong>Skannaa tämä QR-koodi Google Authenticator- tai vastaavaan sovellukseen puhelimellasi</strong>. Tästä hetkestä lähtien ohjelma luo koodin, mikä sinun tarvitsee syöttää sisäänkirjautuessa."
|
instructions_html: "<strong>Skannaa tämä QR-koodi Google Authenticator- tai vastaavaan sovellukseen puhelimellasi</strong>. Tästä hetkestä lähtien ohjelma luo koodin, mikä sinun tarvitsee syöttää sisäänkirjautuessa."
|
||||||
|
plaintext_secret_html: 'Plain-text secret: <samp>%{secret}</samp>'
|
||||||
|
warning: Jos et juuri nyt voi konfiguroida authenticator-applikaatiota juuri nyt, sinun pitäisi klikata "Poista käytöstä" tai et voi kirjautua sisään.
|
||||||
users:
|
users:
|
||||||
invalid_email: Virheellinen sähköposti
|
invalid_email: Virheellinen sähköposti
|
||||||
invalid_otp_token: Virheellinen kaksivaihetunnistuskoodi
|
invalid_otp_token: Virheellinen kaksivaihetunnistuskoodi
|
||||||
|
|
|
@ -79,7 +79,7 @@ fr:
|
||||||
undo_suspension: Annuler la suspension
|
undo_suspension: Annuler la suspension
|
||||||
username: Nom d'utilisateur
|
username: Nom d'utilisateur
|
||||||
web: Web
|
web: Web
|
||||||
domain_blocks:
|
domain_block:
|
||||||
add_new: Ajouter
|
add_new: Ajouter
|
||||||
domain: Domaine
|
domain: Domaine
|
||||||
new:
|
new:
|
||||||
|
@ -241,6 +241,24 @@ fr:
|
||||||
missing_resource: L'URL de redirection n'a pas pu être trouvée
|
missing_resource: L'URL de redirection n'a pas pu être trouvée
|
||||||
proceed: Continuez pour suivre
|
proceed: Continuez pour suivre
|
||||||
prompt: 'Vous allez suivre :'
|
prompt: 'Vous allez suivre :'
|
||||||
|
reports:
|
||||||
|
comment:
|
||||||
|
label: Commentaire
|
||||||
|
none: Aucun
|
||||||
|
delete: Supprimer
|
||||||
|
id: ID
|
||||||
|
mark_as_resolved: Marqué comme résolu
|
||||||
|
report: 'Signalement #%{id}'
|
||||||
|
reported_account: Compte signalé
|
||||||
|
reported_by: Signalé par
|
||||||
|
reports: Signalements
|
||||||
|
resolved: Résolus
|
||||||
|
silence_account: Rendre le compte muet
|
||||||
|
status: Statut
|
||||||
|
suspend_account: Suspendre le compte
|
||||||
|
target: Cible
|
||||||
|
unresolved: Non résolus
|
||||||
|
view: Voir
|
||||||
settings:
|
settings:
|
||||||
authorized_apps: Applications autorisées
|
authorized_apps: Applications autorisées
|
||||||
back: Retour vers Mastodon
|
back: Retour vers Mastodon
|
||||||
|
@ -270,6 +288,8 @@ fr:
|
||||||
disable: Désactiver
|
disable: Désactiver
|
||||||
enable: Activer
|
enable: Activer
|
||||||
instructions_html: "<strong>Scannez ce QR code grâce à Google Authenticator, Authy ou une application similaire sur votre téléphone</strong>. Désormais, cette application générera des jetons que vous devrez saisir à chaque connexion."
|
instructions_html: "<strong>Scannez ce QR code grâce à Google Authenticator, Authy ou une application similaire sur votre téléphone</strong>. Désormais, cette application générera des jetons que vous devrez saisir à chaque connexion."
|
||||||
|
plaintext_secret_html: 'Code secret en clair : <samp>%{secret}</samp>'
|
||||||
|
warning: Si vous ne pouvez pas configurer une application d'authentification maintenant, vous devriez cliquer sur "Désactiver" pour ne pas bloquer l'accès à votre compte.
|
||||||
users:
|
users:
|
||||||
invalid_email: L'adresse courriel est invalide
|
invalid_email: L'adresse courriel est invalide
|
||||||
invalid_otp_token: Le code d'authentification à deux facteurs est invalide
|
invalid_otp_token: Le code d'authentification à deux facteurs est invalide
|
||||||
|
|
|
@ -156,6 +156,8 @@ hr:
|
||||||
disable: Onemogući
|
disable: Onemogući
|
||||||
enable: Omogući
|
enable: Omogući
|
||||||
instructions_html: "<strong>Skeniraj ovaj QR kod into Google Authenticator or a similiar app on your phone</strong>. Od sada, ta aplikacija će generirati tokene koje ćeš unijeti pri prijavljivanju."
|
instructions_html: "<strong>Skeniraj ovaj QR kod into Google Authenticator or a similiar app on your phone</strong>. Od sada, ta aplikacija će generirati tokene koje ćeš unijeti pri prijavljivanju."
|
||||||
|
plaintext_secret_html: 'Plain-text secret: <samp>%{secret}</samp>'
|
||||||
|
warning: Ako trenuno ne možeš konfigurirati authenticator app, trebaš kliknuti "onemogući" ili se nećeš moći prijaviti.
|
||||||
users:
|
users:
|
||||||
invalid_email: E-mail adresa nije valjana
|
invalid_email: E-mail adresa nije valjana
|
||||||
invalid_otp_token: Nevaljani dvo-faktorski kod
|
invalid_otp_token: Nevaljani dvo-faktorski kod
|
||||||
|
|
|
@ -165,6 +165,7 @@ it:
|
||||||
instructions_html: "<strong>Scannerizza questo QR code con Google Authenticator o un'app TOTP simile sul tuo telefono</strong>. Da ora in poi, quell'applicazione genererà codici da inserire necessariamente per eseguire l'accesso."
|
instructions_html: "<strong>Scannerizza questo QR code con Google Authenticator o un'app TOTP simile sul tuo telefono</strong>. Da ora in poi, quell'applicazione genererà codici da inserire necessariamente per eseguire l'accesso."
|
||||||
manual_instructions: 'Se non puoi scannerizzare il QR code e hai bisogno di inserirlo manualmente, questo è il codice segreto in chiaro:'
|
manual_instructions: 'Se non puoi scannerizzare il QR code e hai bisogno di inserirlo manualmente, questo è il codice segreto in chiaro:'
|
||||||
setup: Configura
|
setup: Configura
|
||||||
|
warning: Se non puoi convalidare immediatamente la tua app di autenticazione, dovresti selezionare "disabilita" o non sarai più in grado di eseguire l'accesso.
|
||||||
wrong_code: Il codice inserito non è corretto! Assicurati che l'orario del server e l'orario del telefono siano corretti.
|
wrong_code: Il codice inserito non è corretto! Assicurati che l'orario del server e l'orario del telefono siano corretti.
|
||||||
users:
|
users:
|
||||||
invalid_email: L'indirizzo e-mail inserito non è valido
|
invalid_email: L'indirizzo e-mail inserito non è valido
|
||||||
|
|
|
@ -71,7 +71,6 @@ ja:
|
||||||
profile_url: プロフィールURL
|
profile_url: プロフィールURL
|
||||||
public: パブリック
|
public: パブリック
|
||||||
push_subscription_expires: PuSH購読期限切れ
|
push_subscription_expires: PuSH購読期限切れ
|
||||||
reset_password: パスワード再設定
|
|
||||||
salmon_url: Salmon URL
|
salmon_url: Salmon URL
|
||||||
silence: サイレンス
|
silence: サイレンス
|
||||||
statuses: トゥート数
|
statuses: トゥート数
|
||||||
|
@ -80,10 +79,8 @@ ja:
|
||||||
undo_suspension: 停止から戻す
|
undo_suspension: 停止から戻す
|
||||||
username: ユーザー名
|
username: ユーザー名
|
||||||
web: Web
|
web: Web
|
||||||
domain_blocks:
|
domain_block:
|
||||||
add_new: 新規追加
|
add_new: 新規追加
|
||||||
created_msg: ドメインブロック処理を完了しました
|
|
||||||
destroyed_msg: ドメインブロックを外しました
|
|
||||||
domain: ドメイン
|
domain: ドメイン
|
||||||
new:
|
new:
|
||||||
create: ブロックを作成
|
create: ブロックを作成
|
||||||
|
@ -93,21 +90,8 @@ ja:
|
||||||
silence: サイレンス
|
silence: サイレンス
|
||||||
suspend: 停止
|
suspend: 停止
|
||||||
title: 新規ドメインブロック
|
title: 新規ドメインブロック
|
||||||
reject_media: メディアファイルを拒否
|
|
||||||
reject_media_hint: ローカルに保村されたメディアファイルを削除し、今後のダウンロードを拒否します。停止とは無関係です。
|
|
||||||
severities:
|
|
||||||
silence: サイレンス
|
|
||||||
suspend: 停止
|
|
||||||
severity: 深刻度
|
severity: 深刻度
|
||||||
show:
|
|
||||||
affected_accounts: "データベース中の%{count}個のアカウントに影響します"
|
|
||||||
retroactive:
|
|
||||||
silence: このドメインからの存在するすべてのアカウントのサイレンスを戻す
|
|
||||||
suspend: このドメインからの存在するすべてのアカウントの停止を戻す
|
|
||||||
title: "%{domain}のドメインブロックを戻す"
|
|
||||||
undo: 元に戻す
|
|
||||||
title: ドメインブロック
|
title: ドメインブロック
|
||||||
undo: 元に戻す
|
|
||||||
pubsubhubbub:
|
pubsubhubbub:
|
||||||
callback_url: コールバックURL
|
callback_url: コールバックURL
|
||||||
confirmed: 確認済み
|
confirmed: 確認済み
|
||||||
|
@ -122,7 +106,7 @@ ja:
|
||||||
delete: 削除
|
delete: 削除
|
||||||
id: ID
|
id: ID
|
||||||
mark_as_resolved: 解決済みとしてマーク
|
mark_as_resolved: 解決済みとしてマーク
|
||||||
report: レポート#%{id}
|
report: 'レポート#%{id}'
|
||||||
reported_account: 報告対象アカウント
|
reported_account: 報告対象アカウント
|
||||||
reported_by: 報告者
|
reported_by: 報告者
|
||||||
resolved: 解決済み
|
resolved: 解決済み
|
||||||
|
@ -258,6 +242,24 @@ ja:
|
||||||
missing_resource: リダイレクト先が見つかりませんでした
|
missing_resource: リダイレクト先が見つかりませんでした
|
||||||
proceed: フォローする
|
proceed: フォローする
|
||||||
prompt: 'フォローしようとしています:'
|
prompt: 'フォローしようとしています:'
|
||||||
|
reports:
|
||||||
|
comment:
|
||||||
|
label: コメント
|
||||||
|
none: なし
|
||||||
|
delete: 削除
|
||||||
|
id: ID
|
||||||
|
mark_as_resolved: 解決する
|
||||||
|
report: '通報 #%{id}'
|
||||||
|
reported_account: 通報されているユーザー
|
||||||
|
reported_by: 通報者
|
||||||
|
reports: 通報
|
||||||
|
resolved: 解決済み
|
||||||
|
silence_account: ユーザーをサイレンスする
|
||||||
|
status: 現状
|
||||||
|
suspend_account: ユーザーを停止する
|
||||||
|
target: 通報されているユーザー
|
||||||
|
unresolved: 未決
|
||||||
|
view: 見る
|
||||||
settings:
|
settings:
|
||||||
authorized_apps: 認証済みアプリ
|
authorized_apps: 認証済みアプリ
|
||||||
back: 戻る
|
back: 戻る
|
||||||
|
@ -288,13 +290,10 @@ ja:
|
||||||
disable: 無効
|
disable: 無効
|
||||||
enable: 有効
|
enable: 有効
|
||||||
enabled_success: 二段階認証が有効になりました
|
enabled_success: 二段階認証が有効になりました
|
||||||
generate_recovery_codes: 復元コードを生成
|
|
||||||
instructions_html: "<strong>Google Authenticatorか、もしくはほかのTOTPアプリでこのQRコードをスキャンしてください。</strong>これ以降、ログインするときはそのアプリで生成されるコードが必要になります。"
|
instructions_html: "<strong>Google Authenticatorか、もしくはほかのTOTPアプリでこのQRコードをスキャンしてください。</strong>これ以降、ログインするときはそのアプリで生成されるコードが必要になります。"
|
||||||
lost_recovery_codes: リカバリコードを使用すると携帯電話を紛失した場合でもアカウントにアクセスできるようになります。 リカバリーコードを紛失した場合もここで再生成することができますが、古いリカバリコードは無効になります。
|
|
||||||
manual_instructions: 'QRコードがスキャンできず、手動での登録を希望の場合はこのシークレットコードを利用してください。:'
|
manual_instructions: 'QRコードがスキャンできず、手動での登録を希望の場合はこのシークレットコードを利用してください。:'
|
||||||
recovery_codes_regenerated: リカバリーコードが再生成されました。
|
|
||||||
recovery_instructions: 携帯電話を紛失した場合、以下の内どれかのリカバリコードを使用してアカウントへアクセスすることができます。 リカバリコードは印刷して安全に保管してください。
|
|
||||||
setup: 初期設定
|
setup: 初期設定
|
||||||
|
warning: 現在認証アプリを設定できない場合、無効に設定して、有効にしないでください。
|
||||||
wrong_code: コードが間違っています。サーバー上の時間とデバイス上の時間が一致していることを確認してください。
|
wrong_code: コードが間違っています。サーバー上の時間とデバイス上の時間が一致していることを確認してください。
|
||||||
users:
|
users:
|
||||||
invalid_email: メールアドレスが無効です
|
invalid_email: メールアドレスが無効です
|
||||||
|
|
|
@ -156,6 +156,8 @@ nl:
|
||||||
disable: Uitschakelen
|
disable: Uitschakelen
|
||||||
enable: Inschakelen
|
enable: Inschakelen
|
||||||
instructions_html: "<strong>Scan deze QR-code in Google Authenticator of een soortgelijke app op je mobiele telefoon</strong>. Van nu af aan creëert deze app tokens die je bij aanmelden moet invoeren."
|
instructions_html: "<strong>Scan deze QR-code in Google Authenticator of een soortgelijke app op je mobiele telefoon</strong>. Van nu af aan creëert deze app tokens die je bij aanmelden moet invoeren."
|
||||||
|
plaintext_secret_html: 'Gewone-tekst geheim: <samp>%{secret}</samp>'
|
||||||
|
warning: Als je nu geen authenticator-app kunt installeren, moet je "Uitschakelen" kiezen of je kunt niet meer aanmelden.
|
||||||
users:
|
users:
|
||||||
invalid_email: Het e-mailadres is ongeldig
|
invalid_email: Het e-mailadres is ongeldig
|
||||||
invalid_otp_token: Ongeldige twee-factorcode
|
invalid_otp_token: Ongeldige twee-factorcode
|
||||||
|
|
|
@ -155,6 +155,8 @@
|
||||||
disable: Skru av
|
disable: Skru av
|
||||||
enable: Skru på
|
enable: Skru på
|
||||||
instructions_html: "<strong>Scan denne QR-koden i Google Authenticator eller en lignende app på telefonen din</strong>. Fra nå av vil denne applikasjonen generere koder for deg som skal brukes under innlogging"
|
instructions_html: "<strong>Scan denne QR-koden i Google Authenticator eller en lignende app på telefonen din</strong>. Fra nå av vil denne applikasjonen generere koder for deg som skal brukes under innlogging"
|
||||||
|
plaintext_secret_html: 'Plain-text secret: <samp>%{secret}</samp>'
|
||||||
|
warning: Hvis du ikke kan konfigurere en autentiseringsapp nå bør du trykke "Skru av"; ellers vil du ikke kunne logge inn.
|
||||||
users:
|
users:
|
||||||
invalid_email: E-postaddressen er ugyldig
|
invalid_email: E-postaddressen er ugyldig
|
||||||
invalid_otp_token: Ugyldig tofaktorkode
|
invalid_otp_token: Ugyldig tofaktorkode
|
||||||
|
|
|
@ -155,6 +155,10 @@ pl:
|
||||||
disable: Wyłącz
|
disable: Wyłącz
|
||||||
enable: Włącz
|
enable: Włącz
|
||||||
instructions_html: "<strong>Zeskanuj ten kod QR na swoim urządzeniu za pomocą Google Authenticator, FreeOTP lub podobnej aplikacji</strong>. Od teraz będzie ona generowała kody wymagane przy logowaniu."
|
instructions_html: "<strong>Zeskanuj ten kod QR na swoim urządzeniu za pomocą Google Authenticator, FreeOTP lub podobnej aplikacji</strong>. Od teraz będzie ona generowała kody wymagane przy logowaniu."
|
||||||
|
plaintext_secret_html: 'Sekret: <samp>%{secret}</samp>'
|
||||||
|
warning: Jeśli nie jesteś w stanie skonfigurować aplikacji uwierzytelniania dwustopniowego w tej chwili, wyłącz uwierzytelnianie dwustopniowe. W przeciwnym wypadku nie będziesz się w stanie zalogować!
|
||||||
users:
|
users:
|
||||||
invalid_email: Adres e-mail jest niepoprawny
|
invalid_email: Adres e-mail jest niepoprawny
|
||||||
invalid_otp_token: Kod uwierzytelniający jest niepoprawny
|
invalid_otp_token: Kod uwierzytelniający jest niepoprawny
|
||||||
|
will_paginate:
|
||||||
|
page_gap: "…"
|
||||||
|
|
|
@ -1,174 +1,29 @@
|
||||||
---
|
---
|
||||||
pt:
|
pt:
|
||||||
about:
|
about:
|
||||||
about_mastodon: Mastodon é um servidor de rede social <em>grátis, e open-source</em>. Uma alternativa <em>descentralizada</em> ás plataformas comerciais, que evita o risco de uma única empresa monopolizar a sua comunicação. Escolha um servidor que você confie — qualquer um que escolher, você poderá interagir com todo o resto. Qualquer um pode ter uma instância Mastodon e assim participar na <em>rede social federada</em> sem problemas.
|
about_mastodon: Mastodon é um servidor de rede social <em>grátis, e open-source</em>. Uma alternativa <em>descentralizada</em> ás plataformas comerciais, que evita o risco de uma única empresa monopolizar a sua comunicação. Qualquer um pode ter uma instância Mastodon e assim participar na <em>rede social federada</em> sem problemas.
|
||||||
about_this: Sobre essa instância
|
get_started: Como começar
|
||||||
apps: Aplicações
|
|
||||||
business_email: 'Email comercial:'
|
|
||||||
closed_registrations: Registros estão fechadas para essa instância.
|
|
||||||
contact: Contato
|
|
||||||
description_headline: O que é %{domain}?
|
|
||||||
domain_count_after: outras instâncias
|
|
||||||
domain_count_before: Conectado a
|
|
||||||
features:
|
|
||||||
api: Aberto para API de aplicações e serviços
|
|
||||||
blocks: Bloqueos e ferramentas para mudar
|
|
||||||
characters: 500 caracteres por post
|
|
||||||
chronology: Timeline são cronologicas
|
|
||||||
ethics: 'Design ético: sem propaganda, sem tracking'
|
|
||||||
gifv: GIFV e vídeos curtos
|
|
||||||
privacy: Granular, privacidade setada por post
|
|
||||||
public: Timelines públicas
|
|
||||||
features_headline: O que torna Mastodon diferente
|
|
||||||
get_started: Comece aqui
|
|
||||||
links: Links
|
|
||||||
source_code: Source code
|
source_code: Source code
|
||||||
other_instances: Outras instâncias
|
|
||||||
terms: Termos
|
terms: Termos
|
||||||
user_count_after: usuários
|
|
||||||
user_count_before: Lugar de
|
|
||||||
accounts:
|
accounts:
|
||||||
follow: Seguir
|
follow: Seguir
|
||||||
followers: Seguidores
|
followers: Seguidores
|
||||||
following: Seguindo
|
following: Following
|
||||||
nothing_here: Não há nada aqui!
|
nothing_here: Não há nada aqui!
|
||||||
people_followed_by: Pessoas seguidas por %{name}
|
people_followed_by: Pessoas seguidas por %{name}
|
||||||
people_who_follow: Pessoas que seguem %{name}
|
people_who_follow: Pessoas que seguem %{name}
|
||||||
posts: Posts
|
posts: Posts
|
||||||
remote_follow: Acesso remoto
|
|
||||||
unfollow: Unfollow
|
unfollow: Unfollow
|
||||||
admin:
|
|
||||||
accounts:
|
|
||||||
are_you_sure: Você tem certeza?
|
|
||||||
display_name: Nome mostrado
|
|
||||||
domain: Domain
|
|
||||||
edit: Editar
|
|
||||||
email: E-mail
|
|
||||||
feed_url: URL do Feed
|
|
||||||
followers: Seguidores
|
|
||||||
follows: Seguindo
|
|
||||||
location:
|
|
||||||
all: Todos
|
|
||||||
local: Local
|
|
||||||
remote: Remoto
|
|
||||||
title: Local
|
|
||||||
media_attachments: Mídia anexadas
|
|
||||||
moderation:
|
|
||||||
all: Todos
|
|
||||||
silenced: Silenciado
|
|
||||||
suspended: Supenso
|
|
||||||
title: Moderação
|
|
||||||
most_recent_activity: Atividade mais recente
|
|
||||||
most_recent_ip: IP mais recente
|
|
||||||
not_subscribed: Não inscrito
|
|
||||||
order:
|
|
||||||
alphabetic: Alfabética
|
|
||||||
most_recent: Mais recente
|
|
||||||
title: Ordem
|
|
||||||
perform_full_suspension: Fazer suspensão completa
|
|
||||||
profile_url: URL do perfil
|
|
||||||
public: Público
|
|
||||||
push_subscription_expires: PuSH subscription expires
|
|
||||||
salmon_url: Salmon URL
|
|
||||||
silence: Silêncio
|
|
||||||
statuses: Status
|
|
||||||
title: Contas
|
|
||||||
undo_silenced: Desfazer silenciar
|
|
||||||
undo_suspension: Desfazer supensão
|
|
||||||
username: Usuário
|
|
||||||
web: Web
|
|
||||||
domain_blocks:
|
|
||||||
add_new: Adicionar nova
|
|
||||||
created_msg: Bloqueio do domínio está sendo processado
|
|
||||||
destroyed_msg: Bloqueio de domínio está sendo desfeito
|
|
||||||
domain: Domínio
|
|
||||||
new:
|
|
||||||
create: Criar bloqueio
|
|
||||||
hint: O bloqueio de dominio não vai previnir a criação de entradas no banco de dados, mas irá, retroativamente e automaticamente aplicar métodos de moderação específica nessas contas.
|
|
||||||
severity:
|
|
||||||
desc_html: "<strong>Silenciar</strong> irá fazer com que os posts dessas contas sejam invisíveis para todos que não a seguem. <strong>Supender</strong> irá remover todos o conteúdo das contas, mídia e dados do perfil."
|
|
||||||
silence: Silenciar
|
|
||||||
suspend: Suspender
|
|
||||||
title: Novo bloqueio de domínio
|
|
||||||
reject_media: Rejeitar arquivos de mídia
|
|
||||||
reject_media_hint: Remove localmente arquivos armazenados e rejeita fazer o download de novos no futuro. Irrelevante em suspensões.
|
|
||||||
severities:
|
|
||||||
silence: Silenciar
|
|
||||||
suspend: Suspender
|
|
||||||
severity: Severidade
|
|
||||||
show:
|
|
||||||
affected_accounts:
|
|
||||||
one: Uma conta no banco de dados afetada
|
|
||||||
other: "%{count} contas no banco de dados afetada"
|
|
||||||
retroactive:
|
|
||||||
silence: Desilenciar todas as contas existentes nesse domínio
|
|
||||||
suspend: Desuspender todas as contas existentes nesse domínio
|
|
||||||
title: Desfazer bloqueio de domínio para %{domain}
|
|
||||||
title: Bloqueio de domínio
|
|
||||||
undo: Desfazer
|
|
||||||
pubsubhubbub:
|
|
||||||
callback_url: URL de Callback
|
|
||||||
confirmed: Confirmado
|
|
||||||
expires_in: Expira em
|
|
||||||
last_delivery: Última entrega
|
|
||||||
title: PubSubHubbub
|
|
||||||
topic: Tópico
|
|
||||||
reports:
|
|
||||||
comment:
|
|
||||||
label: Commentário
|
|
||||||
none: None
|
|
||||||
delete: Deletar
|
|
||||||
id: ID
|
|
||||||
mark_as_resolved: Marque como resolvido
|
|
||||||
report: 'Report #%{id}'
|
|
||||||
reported_account: Conta reportada
|
|
||||||
reported_by: Reportado por
|
|
||||||
resolved: Resolvido
|
|
||||||
silence_account: Conta silenciada
|
|
||||||
status: Status
|
|
||||||
suspend_account: Conta suspensa
|
|
||||||
target: Target
|
|
||||||
title: Reports
|
|
||||||
unresolved: Unresolved
|
|
||||||
view: View
|
|
||||||
settings:
|
|
||||||
click_to_edit: Clique para editar
|
|
||||||
contact_information:
|
|
||||||
email: Entre um endereço de email público
|
|
||||||
label: Informação de contato
|
|
||||||
username: Entre com usuário
|
|
||||||
registrations:
|
|
||||||
closed_message:
|
|
||||||
desc_html: Mostrar na página inicial quando registros estão fecados<br/>Você pode usar tags HTML
|
|
||||||
title: Mensagem de registro fechados
|
|
||||||
open:
|
|
||||||
disabled: Desabilitado
|
|
||||||
enabled: Habilitado
|
|
||||||
title: Aberto para registro
|
|
||||||
setting: Preferências
|
|
||||||
site_description:
|
|
||||||
desc_html: Mostrar como parágrafo e usado como meta tag.<br/>Vôce pode usar tags HTML, em particular <code><a></code> e <code><em></code>.
|
|
||||||
title: Descrição do site
|
|
||||||
site_description_extended:
|
|
||||||
desc_html: Mostrar na página de informação extendiada <br/>Você pode usar tags HTML
|
|
||||||
title: Descrição extendida do site
|
|
||||||
site_title: Título do site
|
|
||||||
title: Preferências do site
|
|
||||||
title: Administração
|
|
||||||
application_mailer:
|
application_mailer:
|
||||||
settings: 'Mudar preferências de email: %{link}'
|
|
||||||
signature: notificações Mastodon de %{instance}
|
signature: notificações Mastodon de %{instance}
|
||||||
view: 'View:'
|
|
||||||
applications:
|
|
||||||
invalid_url: URL dada é inválida
|
|
||||||
auth:
|
auth:
|
||||||
change_password: Mudar senha
|
change_password: Mudar password
|
||||||
didnt_get_confirmation: Não recebeu instruções de confirmação?
|
didnt_get_confirmation: Não recebeu instruções de confirmação?
|
||||||
forgot_password: Esqueceu a senha?
|
forgot_password: Esqueceu a password?
|
||||||
login: Entrar
|
login: Entrar
|
||||||
register: Registar
|
register: Registar
|
||||||
resend_confirmation: Reenviar instruções de confirmação
|
resend_confirmation: Reenviar instruções de confirmação
|
||||||
reset_password: Resetar senha
|
reset_password: Reset password
|
||||||
set_new_password: Editar password
|
set_new_password: Editar password
|
||||||
generic:
|
generic:
|
||||||
changes_saved_msg: Mudanças guardadas!
|
changes_saved_msg: Mudanças guardadas!
|
||||||
|
|
|
@ -158,7 +158,9 @@ ru:
|
||||||
enable: Включить
|
enable: Включить
|
||||||
instructions_html: "<strong>Отсканируйте этот QR-код с помощью Google Authenticator или другого подобного приложения на Вашем телефоне</strong>. С этого момента приложение будет генерировать токены, которые будет необходимо ввести для входа."
|
instructions_html: "<strong>Отсканируйте этот QR-код с помощью Google Authenticator или другого подобного приложения на Вашем телефоне</strong>. С этого момента приложение будет генерировать токены, которые будет необходимо ввести для входа."
|
||||||
manual_instructions: 'Если Вы не можете отсканировать QR-код и хотите ввести его вручную, секрет представлен здесь открытым текстом:'
|
manual_instructions: 'Если Вы не можете отсканировать QR-код и хотите ввести его вручную, секрет представлен здесь открытым текстом:'
|
||||||
|
plaintext_secret_html: 'Секрет открытым текстом: <samp>%{secret}</samp>'
|
||||||
setup: Настроить
|
setup: Настроить
|
||||||
|
warning: Если сейчас у Вас не получается настроить аутентификатор, нажмите "отключить", иначе Вы не сможете войти!
|
||||||
users:
|
users:
|
||||||
invalid_email: Введенный e-mail неверен
|
invalid_email: Введенный e-mail неверен
|
||||||
invalid_otp_token: Введен неверный код
|
invalid_otp_token: Введен неверный код
|
||||||
|
|
|
@ -10,13 +10,11 @@ ja:
|
||||||
note: プロフィールは160文字まで設定することができます。
|
note: プロフィールは160文字まで設定することができます。
|
||||||
imports:
|
imports:
|
||||||
data: 他の Mastodon サーバーからエクスポートしたCSVファイルを選択して下さい
|
data: 他の Mastodon サーバーからエクスポートしたCSVファイルを選択して下さい
|
||||||
sessions:
|
|
||||||
otp: 携帯電話に表示された2段階認証コードを入力するか、生成したリカバリーコードを使用してください。
|
|
||||||
labels:
|
labels:
|
||||||
defaults:
|
defaults:
|
||||||
avatar: アイコン
|
avatar: アイコン
|
||||||
confirm_new_password: 新しいパスワード(確認用)
|
confirm_new_password: 新しいパスワード(確認用)
|
||||||
confirm_password: パスワード(確認用)
|
confirm_password: 新しいパスワード
|
||||||
current_password: 現在のパスワード
|
current_password: 現在のパスワード
|
||||||
data: データ
|
data: データ
|
||||||
display_name: 表示名
|
display_name: 表示名
|
||||||
|
@ -24,13 +22,12 @@ ja:
|
||||||
header: ヘッダー
|
header: ヘッダー
|
||||||
locale: 言語
|
locale: 言語
|
||||||
locked: 非公開アカウントにする
|
locked: 非公開アカウントにする
|
||||||
new_password: 新しいパスワード
|
new_password: パスワード
|
||||||
note: プロフィール
|
note: プロフィール
|
||||||
otp_attempt: 二段階認証コード
|
otp_attempt: 二段階認証コード
|
||||||
password: パスワード
|
password: パスワード
|
||||||
setting_boost_modal: ブーストする前に確認ダイアログを表示する
|
setting_boost_modal: ブーストする前に確認ダイアログを表示する
|
||||||
setting_default_privacy: 投稿の公開範囲
|
setting_default_privacy: 投稿の公開範囲
|
||||||
severity: 重大性
|
|
||||||
type: インポートする項目
|
type: インポートする項目
|
||||||
username: ユーザー名
|
username: ユーザー名
|
||||||
interactions:
|
interactions:
|
||||||
|
|
|
@ -4,17 +4,17 @@ pt:
|
||||||
labels:
|
labels:
|
||||||
defaults:
|
defaults:
|
||||||
avatar: Avatar
|
avatar: Avatar
|
||||||
confirm_new_password: Confirme nova senha
|
confirm_new_password: Confirme nova password
|
||||||
confirm_password: Confirme a senha
|
confirm_password: Confirme a password
|
||||||
current_password: Senha atual
|
current_password: Password atual
|
||||||
display_name: Nome
|
display_name: Nome
|
||||||
email: Endereço de email
|
email: Endereço de email
|
||||||
header: Header
|
header: Header
|
||||||
locale: Linguagem
|
locale: Linguagem
|
||||||
new_password: Nova senha
|
new_password: Nova password
|
||||||
note: Biografia
|
note: Biografia
|
||||||
password: Senha
|
password: Password
|
||||||
username: Usuário
|
username: Username
|
||||||
interactions:
|
interactions:
|
||||||
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ê
|
must_be_following: Bloquear notificações de pessoas que você
|
||||||
|
|
|
@ -145,6 +145,8 @@ zh-CN:
|
||||||
disable: 禁用
|
disable: 禁用
|
||||||
enable: 启用
|
enable: 启用
|
||||||
instructions_html: "<strong>使用 Google Authenticator 或类似 APP 扫描二维码</strong>。现在起,APP 将会生成登陆时必须的两步验证码。"
|
instructions_html: "<strong>使用 Google Authenticator 或类似 APP 扫描二维码</strong>。现在起,APP 将会生成登陆时必须的两步验证码。"
|
||||||
|
plaintext_secret_html: 密钥: <samp>%{secret}</samp>
|
||||||
|
warning: 如果你现在没有 Google Authenticator 或类似授权 APP,你应该先「禁用」本功能,否则你将不能正常登陆。
|
||||||
users:
|
users:
|
||||||
invalid_email: 无效的邮箱
|
invalid_email: 无效的邮箱
|
||||||
invalid_otp_token: 无效的两步验证码
|
invalid_otp_token: 无效的两步验证码
|
||||||
|
|
|
@ -79,7 +79,7 @@ zh-HK:
|
||||||
undo_suspension: 解除停權
|
undo_suspension: 解除停權
|
||||||
username: 用戶名稱
|
username: 用戶名稱
|
||||||
web: 用戶頁面
|
web: 用戶頁面
|
||||||
domain_blocks:
|
domain_block:
|
||||||
add_new: 新增
|
add_new: 新增
|
||||||
domain: 域名阻隔
|
domain: 域名阻隔
|
||||||
new:
|
new:
|
||||||
|
@ -246,6 +246,24 @@ zh-HK:
|
||||||
missing_resource: 無法找到你用戶的轉接網址
|
missing_resource: 無法找到你用戶的轉接網址
|
||||||
proceed: 下一步
|
proceed: 下一步
|
||||||
prompt: 你希望關注︰
|
prompt: 你希望關注︰
|
||||||
|
reports:
|
||||||
|
comment:
|
||||||
|
label: 詳細解釋
|
||||||
|
none: 沒有
|
||||||
|
delete: 刪除
|
||||||
|
id: ID
|
||||||
|
mark_as_resolved: 標示為「已處理」
|
||||||
|
report: '舉報 #%{id}'
|
||||||
|
reported_account: 舉報 account
|
||||||
|
reported_by: 舉報者
|
||||||
|
reports: 舉報
|
||||||
|
resolved: 已處埋
|
||||||
|
silence_account: 將用戶靜音
|
||||||
|
status: 狀態
|
||||||
|
suspend_account: 將用戶停權
|
||||||
|
target: 對像
|
||||||
|
unresolved: 未處埋
|
||||||
|
view: 檢視
|
||||||
settings:
|
settings:
|
||||||
authorized_apps: 授權應用程式
|
authorized_apps: 授權應用程式
|
||||||
back: 回到 Mastodon
|
back: 回到 Mastodon
|
||||||
|
@ -279,7 +297,10 @@ zh-HK:
|
||||||
instructions_html: "<strong>請用你手機的認證器應用程式(如 Google Authenticator、Authy),掃描這裏的QR 圖形碼</strong>。在雙重認證啟用後,你登入時將須要使用此應用程式產生的認證碼。"
|
instructions_html: "<strong>請用你手機的認證器應用程式(如 Google Authenticator、Authy),掃描這裏的QR 圖形碼</strong>。在雙重認證啟用後,你登入時將須要使用此應用程式產生的認證碼。"
|
||||||
manual_instructions: 如果你無法掃描 QR 圖形碼,請手動輸入這個文字密碼︰
|
manual_instructions: 如果你無法掃描 QR 圖形碼,請手動輸入這個文字密碼︰
|
||||||
setup: 設定
|
setup: 設定
|
||||||
|
warning: 如果你現在無法正確設定你的應用程式,請即「停用」雙重認證,否則日後可能無法登入本站。
|
||||||
wrong_code: 你輸入的認證碼並不正確!可能伺服器時間和你手機不一致,請檢查你手機的時鐘,或與本站管理員聯絡。
|
wrong_code: 你輸入的認證碼並不正確!可能伺服器時間和你手機不一致,請檢查你手機的時鐘,或與本站管理員聯絡。
|
||||||
users:
|
users:
|
||||||
invalid_email: 電郵地址格式不正確
|
invalid_email: 電郵地址格式不正確
|
||||||
invalid_otp_token: 雙重認證確認碼不正確
|
invalid_otp_token: 雙重認證確認碼不正確
|
||||||
|
will_paginate:
|
||||||
|
page_gap: "…"
|
||||||
|
|
|
@ -79,7 +79,7 @@ zh-TW:
|
||||||
undo_suspension: 取消停權
|
undo_suspension: 取消停權
|
||||||
username: 使用者名稱
|
username: 使用者名稱
|
||||||
web: Web
|
web: Web
|
||||||
domain_blocks:
|
domain_block:
|
||||||
add_new: 新增
|
add_new: 新增
|
||||||
domain: 網域
|
domain: 網域
|
||||||
new:
|
new:
|
||||||
|
@ -240,6 +240,24 @@ zh-TW:
|
||||||
missing_resource: 無法找到資源
|
missing_resource: 無法找到資源
|
||||||
proceed: 下一步
|
proceed: 下一步
|
||||||
prompt: '您希望關注︰'
|
prompt: '您希望關注︰'
|
||||||
|
reports:
|
||||||
|
comment:
|
||||||
|
label: 留言
|
||||||
|
none: 無
|
||||||
|
delete: 刪除
|
||||||
|
id: ID
|
||||||
|
mark_as_resolved: 標記為已解決
|
||||||
|
report: '檢舉 #%{id}'
|
||||||
|
reported_account: 被檢舉帳號
|
||||||
|
reported_by: 檢舉人
|
||||||
|
reports: 檢舉
|
||||||
|
resolved: 已解決
|
||||||
|
silence_account: 靜音帳號
|
||||||
|
status: 狀態
|
||||||
|
suspend_account: 停權帳號
|
||||||
|
target: 目標
|
||||||
|
unresolved: 未解決
|
||||||
|
view: 檢視
|
||||||
settings:
|
settings:
|
||||||
authorized_apps: 已授權應用程式
|
authorized_apps: 已授權應用程式
|
||||||
back: 回到 Mastodon
|
back: 回到 Mastodon
|
||||||
|
@ -273,7 +291,10 @@ zh-TW:
|
||||||
instructions_html: <strong>請用您手機的認證器應用程式(如 Google Authenticator、Authy),掃描這裡的 QR 圖形碼</strong>。在雙因子認證啟用後,您登入時將須要使用此應用程式產生的認證碼。
|
instructions_html: <strong>請用您手機的認證器應用程式(如 Google Authenticator、Authy),掃描這裡的 QR 圖形碼</strong>。在雙因子認證啟用後,您登入時將須要使用此應用程式產生的認證碼。
|
||||||
manual_instructions: 如果您無法掃描 QR 圖形碼,請手動輸入︰
|
manual_instructions: 如果您無法掃描 QR 圖形碼,請手動輸入︰
|
||||||
setup: 設定
|
setup: 設定
|
||||||
|
warning: 如果您現在無法正確設定您的應用程式,請立刻「停用」雙因子認證,否則日後可能無法登入本站。
|
||||||
wrong_code: 您輸入的認證碼並不正確!可能伺服器時間和您手機不一致,請檢查您手機的時間,或與本站管理員聯絡。
|
wrong_code: 您輸入的認證碼並不正確!可能伺服器時間和您手機不一致,請檢查您手機的時間,或與本站管理員聯絡。
|
||||||
users:
|
users:
|
||||||
invalid_email: 信箱地址格式不正確
|
invalid_email: 信箱地址格式不正確
|
||||||
invalid_otp_token: 雙因子認證碼不正確
|
invalid_otp_token: 雙因子認證碼不正確
|
||||||
|
will_paginate:
|
||||||
|
page_gap: "…"
|
||||||
|
|
|
@ -18,9 +18,9 @@ SimpleNavigation::Configuration.run do |navigation|
|
||||||
admin.item :reports, safe_join([fa_icon('flag fw'), t('admin.reports.title')]), admin_reports_url, highlights_on: %r{/admin/reports}
|
admin.item :reports, safe_join([fa_icon('flag fw'), t('admin.reports.title')]), admin_reports_url, highlights_on: %r{/admin/reports}
|
||||||
admin.item :accounts, safe_join([fa_icon('users fw'), t('admin.accounts.title')]), admin_accounts_url, highlights_on: %r{/admin/accounts}
|
admin.item :accounts, safe_join([fa_icon('users fw'), t('admin.accounts.title')]), admin_accounts_url, highlights_on: %r{/admin/accounts}
|
||||||
admin.item :pubsubhubbubs, safe_join([fa_icon('paper-plane-o fw'), t('admin.pubsubhubbub.title')]), admin_pubsubhubbub_index_url
|
admin.item :pubsubhubbubs, safe_join([fa_icon('paper-plane-o fw'), t('admin.pubsubhubbub.title')]), admin_pubsubhubbub_index_url
|
||||||
admin.item :domain_blocks, safe_join([fa_icon('lock fw'), t('admin.domain_blocks.title')]), admin_domain_blocks_url, highlights_on: %r{/admin/domain_blocks}
|
admin.item :domain_blocks, safe_join([fa_icon('lock fw'), t('admin.domain_block.title')]), admin_domain_blocks_url, highlights_on: %r{/admin/domain_blocks}
|
||||||
admin.item :sidekiq, safe_join([fa_icon('diamond fw'), 'Sidekiq']), sidekiq_url, link_html: { target: 'sidekiq' }
|
admin.item :sidekiq, safe_join([fa_icon('diamond fw'), 'Sidekiq']), sidekiq_url
|
||||||
admin.item :pghero, safe_join([fa_icon('database fw'), 'PgHero']), pghero_url, link_html: { target: 'pghero' }
|
admin.item :pghero, safe_join([fa_icon('database fw'), 'PgHero']), pghero_url
|
||||||
admin.item :settings, safe_join([fa_icon('cogs fw'), t('admin.settings.title')]), admin_settings_url
|
admin.item :settings, safe_join([fa_icon('cogs fw'), t('admin.settings.title')]), admin_settings_url
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
|
@ -1,13 +1,17 @@
|
||||||
threads_count = ENV.fetch('MAX_THREADS') { 5 }.to_i
|
threads_count = ENV.fetch('MAX_THREADS') { 5 }.to_i
|
||||||
threads threads_count, threads_count
|
threads threads_count, threads_count
|
||||||
|
|
||||||
bind ENV.fetch('PORT') { 'unix:/home/mastodon/live/web.sock' }
|
port ENV.fetch('PORT') { 3000 }
|
||||||
environment ENV.fetch('RAILS_ENV') { 'development' }
|
environment ENV.fetch('RAILS_ENV') { 'development' }
|
||||||
workers ENV.fetch('WEB_CONCURRENCY') { 2 }
|
workers ENV.fetch('WEB_CONCURRENCY') { 2 }
|
||||||
|
|
||||||
preload_app!
|
preload_app!
|
||||||
|
|
||||||
on_worker_boot do
|
on_worker_boot do
|
||||||
|
if ENV['HEROKU'] # Spawn the workers from Puma, to only use one dyno
|
||||||
|
@sidekiq_pid ||= spawn('bundle exec sidekiq -q default -q push -q pull -q mailers ')
|
||||||
|
end
|
||||||
|
|
||||||
ActiveRecord::Base.establish_connection if defined?(ActiveRecord)
|
ActiveRecord::Base.establish_connection if defined?(ActiveRecord)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
|
@ -1,5 +0,0 @@
|
||||||
class AddLanguageToStatuses < ActiveRecord::Migration[5.0]
|
|
||||||
def change
|
|
||||||
add_column :statuses, :language, :string, null: false, default: 'en'
|
|
||||||
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: 20170414132105) do
|
ActiveRecord::Schema.define(version: 20170414080609) 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"
|
||||||
|
@ -40,6 +40,7 @@ ActiveRecord::Schema.define(version: 20170414132105) do
|
||||||
t.datetime "header_updated_at"
|
t.datetime "header_updated_at"
|
||||||
t.string "avatar_remote_url"
|
t.string "avatar_remote_url"
|
||||||
t.datetime "subscription_expires_at"
|
t.datetime "subscription_expires_at"
|
||||||
|
t.datetime "last_webfingered_at"
|
||||||
t.boolean "silenced", default: false, null: false
|
t.boolean "silenced", default: false, null: false
|
||||||
t.boolean "suspended", default: false, null: false
|
t.boolean "suspended", default: false, null: false
|
||||||
t.boolean "locked", default: false, null: false
|
t.boolean "locked", default: false, null: false
|
||||||
|
@ -47,7 +48,6 @@ ActiveRecord::Schema.define(version: 20170414132105) do
|
||||||
t.integer "statuses_count", default: 0, null: false
|
t.integer "statuses_count", default: 0, null: false
|
||||||
t.integer "followers_count", default: 0, null: false
|
t.integer "followers_count", default: 0, null: false
|
||||||
t.integer "following_count", default: 0, null: false
|
t.integer "following_count", default: 0, null: false
|
||||||
t.datetime "last_webfingered_at"
|
|
||||||
t.index "(((setweight(to_tsvector('simple'::regconfig, (display_name)::text), 'A'::\"char\") || setweight(to_tsvector('simple'::regconfig, (username)::text), 'B'::\"char\")) || setweight(to_tsvector('simple'::regconfig, (COALESCE(domain, ''::character varying))::text), 'C'::\"char\")))", name: "search_index", using: :gin
|
t.index "(((setweight(to_tsvector('simple'::regconfig, (display_name)::text), 'A'::\"char\") || setweight(to_tsvector('simple'::regconfig, (username)::text), 'B'::\"char\")) || setweight(to_tsvector('simple'::regconfig, (COALESCE(domain, ''::character varying))::text), 'C'::\"char\")))", name: "search_index", using: :gin
|
||||||
t.index "lower((username)::text), lower((domain)::text)", name: "index_accounts_on_username_and_domain_lower", using: :btree
|
t.index "lower((username)::text), lower((domain)::text)", name: "index_accounts_on_username_and_domain_lower", using: :btree
|
||||||
t.index ["url"], name: "index_accounts_on_url", using: :btree
|
t.index ["url"], name: "index_accounts_on_url", using: :btree
|
||||||
|
@ -244,7 +244,6 @@ ActiveRecord::Schema.define(version: 20170414132105) do
|
||||||
t.boolean "reply", default: false
|
t.boolean "reply", default: false
|
||||||
t.integer "favourites_count", default: 0, null: false
|
t.integer "favourites_count", default: 0, null: false
|
||||||
t.integer "reblogs_count", default: 0, null: false
|
t.integer "reblogs_count", default: 0, null: false
|
||||||
t.string "language", default: "en", null: false
|
|
||||||
t.index ["account_id"], name: "index_statuses_on_account_id", using: :btree
|
t.index ["account_id"], name: "index_statuses_on_account_id", using: :btree
|
||||||
t.index ["in_reply_to_id"], name: "index_statuses_on_in_reply_to_id", using: :btree
|
t.index ["in_reply_to_id"], name: "index_statuses_on_in_reply_to_id", using: :btree
|
||||||
t.index ["reblog_of_id"], name: "index_statuses_on_reblog_of_id", using: :btree
|
t.index ["reblog_of_id"], name: "index_statuses_on_reblog_of_id", using: :btree
|
||||||
|
|
|
@ -1,16 +1,6 @@
|
||||||
# frozen_string_literal: true
|
# frozen_string_literal: true
|
||||||
|
|
||||||
namespace :mastodon do
|
namespace :mastodon do
|
||||||
desc 'Execute daily tasks'
|
|
||||||
task :daily do
|
|
||||||
Rake::Task['mastodon:feeds:clear'].invoke
|
|
||||||
Rake::Task['mastodon:media:clear'].invoke
|
|
||||||
Rake::Task['mastodon:users:clear'].invoke
|
|
||||||
|
|
||||||
Rake::Task['mastodon:push:refresh'].invoke
|
|
||||||
end
|
|
||||||
|
|
||||||
desc 'Turn a user into an admin, identified by the USERNAME environment variable'
|
|
||||||
task make_admin: :environment do
|
task make_admin: :environment do
|
||||||
include RoutingHelper
|
include RoutingHelper
|
||||||
|
|
||||||
|
@ -23,13 +13,12 @@ namespace :mastodon do
|
||||||
desc 'Manually confirms a user with associated user email address stored in USER_EMAIL environment variable.'
|
desc 'Manually confirms a user with associated user email address stored in USER_EMAIL environment variable.'
|
||||||
task confirm_email: :environment do
|
task confirm_email: :environment do
|
||||||
email = ENV.fetch('USER_EMAIL')
|
email = ENV.fetch('USER_EMAIL')
|
||||||
user = User.find_by(email: email)
|
user = User.where(email: email).first
|
||||||
|
|
||||||
if user
|
if user
|
||||||
user.update(confirmed_at: Time.now.utc)
|
user.update(confirmed_at: Time.now.utc)
|
||||||
puts "#{email} confirmed"
|
puts "User #{email} confirmed."
|
||||||
else
|
else
|
||||||
abort "#{email} not found"
|
abort "User #{email} not found."
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
@ -43,13 +32,6 @@ namespace :mastodon do
|
||||||
task remove_silenced: :environment do
|
task remove_silenced: :environment do
|
||||||
MediaAttachment.where(account: Account.silenced).find_each(&:destroy)
|
MediaAttachment.where(account: Account.silenced).find_each(&:destroy)
|
||||||
end
|
end
|
||||||
|
|
||||||
desc 'Remove cached remote media attachments that are older than a week'
|
|
||||||
task remove_remote: :environment do
|
|
||||||
MediaAttachment.where.not(remote_url: '').where('created_at < ?', 1.week.ago).find_each do |media|
|
|
||||||
media.file.destroy
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
end
|
||||||
|
|
||||||
namespace :push do
|
namespace :push do
|
||||||
|
@ -78,7 +60,7 @@ namespace :mastodon do
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
desc 'Clears all timelines'
|
desc 'Clears all timelines so that they would be regenerated on next hit'
|
||||||
task clear_all: :environment do
|
task clear_all: :environment do
|
||||||
Redis.current.keys('feed:*').each { |key| Redis.current.del(key) }
|
Redis.current.keys('feed:*').each { |key| Redis.current.del(key) }
|
||||||
end
|
end
|
||||||
|
@ -94,14 +76,9 @@ namespace :mastodon do
|
||||||
end
|
end
|
||||||
|
|
||||||
namespace :users do
|
namespace :users do
|
||||||
desc 'Clear out unconfirmed users'
|
desc 'clear unconfirmed users'
|
||||||
task clear: :environment do
|
task clear: :environment do
|
||||||
# Users that never confirmed e-mail never signed in, means they
|
User.where('confirmed_at is NULL AND confirmation_sent_at <= ?', 2.days.ago).find_each(&:destroy)
|
||||||
# only have a user record and an avatar record, with no files uploaded
|
|
||||||
User.where('confirmed_at is NULL AND confirmation_sent_at <= ?', 2.days.ago).find_in_batches do |batch|
|
|
||||||
Account.where(id: batch.map(&:account_id)).delete_all
|
|
||||||
User.where(id: batch.map(&:id)).delete_all
|
|
||||||
end
|
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
@ -144,13 +121,8 @@ namespace :mastodon do
|
||||||
Rails.logger.debug 'Generating static avatars/headers for GIF ones...'
|
Rails.logger.debug 'Generating static avatars/headers for GIF ones...'
|
||||||
|
|
||||||
Account.unscoped.where(avatar_content_type: 'image/gif').or(Account.unscoped.where(header_content_type: 'image/gif')).find_each do |account|
|
Account.unscoped.where(avatar_content_type: 'image/gif').or(Account.unscoped.where(header_content_type: 'image/gif')).find_each do |account|
|
||||||
begin
|
|
||||||
account.avatar.reprocess!
|
account.avatar.reprocess!
|
||||||
account.header.reprocess!
|
account.header.reprocess!
|
||||||
rescue StandardError => e
|
|
||||||
Rails.logger.error "Error while generating static avatars/headers for account #{account.id}: #{e}"
|
|
||||||
next
|
|
||||||
end
|
|
||||||
end
|
end
|
||||||
|
|
||||||
Rails.logger.debug 'Done!'
|
Rails.logger.debug 'Done!'
|
||||||
|
|
|
@ -1,6 +1,5 @@
|
||||||
{
|
{
|
||||||
"name": "mastodon",
|
"name": "mastodon",
|
||||||
"license" : "AGPL-3.0",
|
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"start": "babel-node ./streaming/index.js --presets es2015,stage-2",
|
"start": "babel-node ./streaming/index.js --presets es2015,stage-2",
|
||||||
"storybook": "start-storybook -p 9001 -c storybook",
|
"storybook": "start-storybook -p 9001 -c storybook",
|
||||||
|
|
|
@ -16,33 +16,4 @@ describe ApplicationHelper do
|
||||||
expect(result).to eq ""
|
expect(result).to eq ""
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
describe 'show_landing_strip?', without_verify_partial_doubles: true do
|
|
||||||
describe 'when signed in' do
|
|
||||||
before do
|
|
||||||
allow(helper).to receive(:user_signed_in?).and_return(true)
|
|
||||||
end
|
|
||||||
it 'does not show landing strip' do
|
|
||||||
expect(helper.show_landing_strip?).to eq false
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
describe 'when signed out' do
|
|
||||||
before do
|
|
||||||
allow(helper).to receive(:user_signed_in?).and_return(false)
|
|
||||||
end
|
|
||||||
|
|
||||||
it 'does not show landing strip on single user instance' do
|
|
||||||
allow(helper).to receive(:single_user_mode?).and_return(true)
|
|
||||||
|
|
||||||
expect(helper.show_landing_strip?).to eq false
|
|
||||||
end
|
|
||||||
|
|
||||||
it 'shows landing strip on multi user instance' do
|
|
||||||
allow(helper).to receive(:single_user_mode?).and_return(false)
|
|
||||||
|
|
||||||
expect(helper.show_landing_strip?).to eq true
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
end
|
||||||
|
|
|
@ -13,12 +13,6 @@ RSpec.configure do |config|
|
||||||
|
|
||||||
config.mock_with :rspec do |mocks|
|
config.mock_with :rspec do |mocks|
|
||||||
mocks.verify_partial_doubles = true
|
mocks.verify_partial_doubles = true
|
||||||
|
|
||||||
config.around(:example, :without_verify_partial_doubles) do |example|
|
|
||||||
mocks.verify_partial_doubles = false
|
|
||||||
example.call
|
|
||||||
mocks.verify_partial_doubles = true
|
|
||||||
end
|
|
||||||
end
|
end
|
||||||
|
|
||||||
config.before :each do
|
config.before :each do
|
||||||
|
|
|
@ -1,23 +1,67 @@
|
||||||
require 'rails_helper'
|
require 'rails_helper'
|
||||||
|
$LOAD_PATH << '../lib'
|
||||||
|
require 'tag_manager'
|
||||||
|
|
||||||
describe 'accounts/show.html.haml' do
|
describe 'stream_entries/show.html.haml' do
|
||||||
before do
|
before do
|
||||||
allow(view).to receive(:show_landing_strip?).and_return(true)
|
double(api_oembed_url: '')
|
||||||
|
double(account_stream_entry_url: '')
|
||||||
|
|
||||||
|
def view.single_user_mode?
|
||||||
|
false
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
it 'has an h-feed with correct number of h-entry objects in it' do
|
it 'has valid author h-card and basic data for a detailed_status' do
|
||||||
alice = Fabricate(:account, username: 'alice', display_name: 'Alice')
|
alice = Fabricate(:account, username: 'alice', display_name: 'Alice')
|
||||||
|
bob = Fabricate(:account, username: 'bob', display_name: 'Bob')
|
||||||
status = Fabricate(:status, account: alice, text: 'Hello World')
|
status = Fabricate(:status, account: alice, text: 'Hello World')
|
||||||
status2 = Fabricate(:status, account: alice, text: 'Hello World Again')
|
Fabricate(:status, account: bob, thread: status, text: 'Hello Alice')
|
||||||
status3 = Fabricate(:status, account: alice, text: 'Are You Still There World?')
|
|
||||||
|
|
||||||
assign(:account, alice)
|
assign(:status, status)
|
||||||
assign(:statuses, alice.statuses)
|
|
||||||
assign(:stream_entry, status.stream_entry)
|
assign(:stream_entry, status.stream_entry)
|
||||||
|
assign(:account, alice)
|
||||||
assign(:type, status.stream_entry.activity_type.downcase)
|
assign(:type, status.stream_entry.activity_type.downcase)
|
||||||
|
|
||||||
render
|
render(template: 'stream_entries/show.html.haml')
|
||||||
|
|
||||||
expect(Nokogiri::HTML(rendered).search('.h-feed .h-entry').size).to eq 3
|
mf2 = Microformats2.parse(rendered)
|
||||||
|
|
||||||
|
expect(mf2.entry.name.to_s).to eq status.text
|
||||||
|
expect(mf2.entry.url.to_s).not_to be_empty
|
||||||
|
|
||||||
|
expect(mf2.entry.author.format.name.to_s).to eq alice.display_name
|
||||||
|
expect(mf2.entry.author.format.url.to_s).not_to be_empty
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'has valid h-cites for p-in-reply-to and p-comment' do
|
||||||
|
alice = Fabricate(:account, username: 'alice', display_name: 'Alice')
|
||||||
|
bob = Fabricate(:account, username: 'bob', display_name: 'Bob')
|
||||||
|
carl = Fabricate(:account, username: 'carl', display_name: 'Carl')
|
||||||
|
status = Fabricate(:status, account: alice, text: 'Hello World')
|
||||||
|
reply = Fabricate(:status, account: bob, thread: status, text: 'Hello Alice')
|
||||||
|
Fabricate(:status, account: carl, thread: reply, text: 'Hello Bob')
|
||||||
|
|
||||||
|
assign(:status, reply)
|
||||||
|
assign(:stream_entry, reply.stream_entry)
|
||||||
|
assign(:account, alice)
|
||||||
|
assign(:type, reply.stream_entry.activity_type.downcase)
|
||||||
|
assign(:ancestors, reply.stream_entry.activity.ancestors(bob))
|
||||||
|
assign(:descendants, reply.stream_entry.activity.descendants(bob))
|
||||||
|
|
||||||
|
render(template: 'stream_entries/show.html.haml')
|
||||||
|
|
||||||
|
mf2 = Microformats2.parse(rendered)
|
||||||
|
|
||||||
|
expect(mf2.entry.name.to_s).to eq reply.text
|
||||||
|
expect(mf2.entry.url.to_s).not_to be_empty
|
||||||
|
|
||||||
|
expect(mf2.entry.comment.format.url.to_s).not_to be_empty
|
||||||
|
expect(mf2.entry.comment.format.author.format.name.to_s).to eq carl.display_name
|
||||||
|
expect(mf2.entry.comment.format.author.format.url.to_s).not_to be_empty
|
||||||
|
|
||||||
|
expect(mf2.entry.in_reply_to.format.url.to_s).not_to be_empty
|
||||||
|
expect(mf2.entry.in_reply_to.format.author.format.name.to_s).to eq alice.display_name
|
||||||
|
expect(mf2.entry.in_reply_to.format.author.format.url.to_s).not_to be_empty
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
|
@ -1,64 +1,23 @@
|
||||||
require 'rails_helper'
|
require 'rails_helper'
|
||||||
$LOAD_PATH << '../lib'
|
|
||||||
require 'tag_manager'
|
|
||||||
|
|
||||||
describe 'stream_entries/show.html.haml' do
|
describe 'accounts/show.html.haml' do
|
||||||
before do
|
before do
|
||||||
double(:api_oembed_url => '')
|
def view.single_user_mode?
|
||||||
double(:account_stream_entry_url => '')
|
false
|
||||||
allow(view).to receive(:show_landing_strip?).and_return(true)
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
it 'has valid author h-card and basic data for a detailed_status' do
|
it 'has an h-feed with correct number of h-entry objects in it' do
|
||||||
alice = Fabricate(:account, username: 'alice', display_name: 'Alice')
|
alice = Fabricate(:account, username: 'alice', display_name: 'Alice')
|
||||||
bob = Fabricate(:account, username: 'bob', display_name: 'Bob')
|
Fabricate(:status, account: alice, text: 'Hello World')
|
||||||
status = Fabricate(:status, account: alice, text: 'Hello World')
|
Fabricate(:status, account: alice, text: 'Hello World Again')
|
||||||
reply = Fabricate(:status, account: bob, thread: status, text: 'Hello Alice')
|
Fabricate(:status, account: alice, text: 'Are You Still There World?')
|
||||||
|
|
||||||
assign(:status, status)
|
|
||||||
assign(:stream_entry, status.stream_entry)
|
|
||||||
assign(:account, alice)
|
assign(:account, alice)
|
||||||
assign(:type, status.stream_entry.activity_type.downcase)
|
assign(:statuses, alice.statuses)
|
||||||
|
|
||||||
render
|
render(template: 'accounts/show.html.haml')
|
||||||
|
|
||||||
mf2 = Microformats2.parse(rendered)
|
expect(Nokogiri::HTML(rendered).search('.h-feed .h-entry').size).to eq 3
|
||||||
|
|
||||||
expect(mf2.entry.name.to_s).to eq status.text
|
|
||||||
expect(mf2.entry.url.to_s).not_to be_empty
|
|
||||||
|
|
||||||
expect(mf2.entry.author.format.name.to_s).to eq alice.display_name
|
|
||||||
expect(mf2.entry.author.format.url.to_s).not_to be_empty
|
|
||||||
end
|
|
||||||
|
|
||||||
it 'has valid h-cites for p-in-reply-to and p-comment' do
|
|
||||||
alice = Fabricate(:account, username: 'alice', display_name: 'Alice')
|
|
||||||
bob = Fabricate(:account, username: 'bob', display_name: 'Bob')
|
|
||||||
carl = Fabricate(:account, username: 'carl', display_name: 'Carl')
|
|
||||||
status = Fabricate(:status, account: alice, text: 'Hello World')
|
|
||||||
reply = Fabricate(:status, account: bob, thread: status, text: 'Hello Alice')
|
|
||||||
comment = Fabricate(:status, account: carl, thread: reply, text: 'Hello Bob')
|
|
||||||
|
|
||||||
assign(:status, reply)
|
|
||||||
assign(:stream_entry, reply.stream_entry)
|
|
||||||
assign(:account, alice)
|
|
||||||
assign(:type, reply.stream_entry.activity_type.downcase)
|
|
||||||
assign(:ancestors, reply.stream_entry.activity.ancestors(bob) )
|
|
||||||
assign(:descendants, reply.stream_entry.activity.descendants(bob))
|
|
||||||
|
|
||||||
render
|
|
||||||
|
|
||||||
mf2 = Microformats2.parse(rendered)
|
|
||||||
|
|
||||||
expect(mf2.entry.name.to_s).to eq reply.text
|
|
||||||
expect(mf2.entry.url.to_s).not_to be_empty
|
|
||||||
|
|
||||||
expect(mf2.entry.comment.format.url.to_s).not_to be_empty
|
|
||||||
expect(mf2.entry.comment.format.author.format.name.to_s).to eq carl.display_name
|
|
||||||
expect(mf2.entry.comment.format.author.format.url.to_s).not_to be_empty
|
|
||||||
|
|
||||||
expect(mf2.entry.in_reply_to.format.url.to_s).not_to be_empty
|
|
||||||
expect(mf2.entry.in_reply_to.format.author.format.name.to_s).to eq alice.display_name
|
|
||||||
expect(mf2.entry.in_reply_to.format.author.format.url.to_s).not_to be_empty
|
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
Reference in a new issue