mirror of
https://github.com/glitch-soc/mastodon.git
synced 2025-12-15 00:38:27 +00:00
Compare commits
153 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
7447e7a2ea | ||
|
|
c8be05a4a7 | ||
|
|
41c697fd81 | ||
|
|
c5afe573da | ||
|
|
485310a43c | ||
|
|
9aae9ae40c | ||
|
|
1fb125b630 | ||
|
|
057567d548 | ||
|
|
169c68a739 | ||
|
|
9f182346d7 | ||
|
|
a58c935c3d | ||
|
|
c0c56db0fa | ||
|
|
d9dc0fe84e | ||
|
|
55b56e3f95 | ||
|
|
c4d39b1b3d | ||
|
|
ac54da9394 | ||
|
|
043862f411 | ||
|
|
9e5c1c487e | ||
|
|
5619099564 | ||
|
|
ce80d0b0a9 | ||
|
|
6327f69cab | ||
|
|
5f8155482a | ||
|
|
efcf9448da | ||
|
|
e70b84b1dc | ||
|
|
d7a4e8739a | ||
|
|
9362700137 | ||
|
|
1206627c59 | ||
|
|
edefcfcf42 | ||
|
|
b330d1f000 | ||
|
|
1a5a54eb4b | ||
|
|
447b8bc44e | ||
|
|
093879c177 | ||
|
|
d2c20936f3 | ||
|
|
0220f3a171 | ||
|
|
905a4faa1c | ||
|
|
5355b7d930 | ||
|
|
aec2458d81 | ||
|
|
4fe5e04ea4 | ||
|
|
3f42ad7d1a | ||
|
|
f7c466c8d8 | ||
|
|
245b9cb4ba | ||
|
|
9275f92972 | ||
|
|
4a6d3bac86 | ||
|
|
00cc3066a2 | ||
|
|
a57d30c680 | ||
|
|
467d32fce3 | ||
|
|
8aadb7b0b2 | ||
|
|
79546799af | ||
|
|
90d0018fd5 | ||
|
|
1a12fd14d4 | ||
|
|
282bb55c3c | ||
|
|
0e4479bb3a | ||
|
|
af7e880df5 | ||
|
|
aa7bf1515c | ||
|
|
4f781b17cc | ||
|
|
137100dcf3 | ||
|
|
3a9eb81a80 | ||
|
|
0e39cc6a35 | ||
|
|
faefd8ec8f | ||
|
|
a18fd491b9 | ||
|
|
96715d9af5 | ||
|
|
f24daa399b | ||
|
|
af96e71883 | ||
|
|
5dc73339ae | ||
|
|
ccaf3dbc5a | ||
|
|
1ea662963f | ||
|
|
bd834add56 | ||
|
|
9966bd27c2 | ||
|
|
b0ab632531 | ||
|
|
e1264bbd92 | ||
|
|
38e24a699b | ||
|
|
bf3e56b8ad | ||
|
|
9b698bf448 | ||
|
|
0254ee9795 | ||
|
|
e32edd247f | ||
|
|
dab9b5bd3a | ||
|
|
e17b5b228d | ||
|
|
c4baa9fb6b | ||
|
|
c2a31b8032 | ||
|
|
9e63bf446e | ||
|
|
c44a700252 | ||
|
|
aa90798386 | ||
|
|
0930ce5560 | ||
|
|
7f0a865b05 | ||
|
|
08fce08217 | ||
|
|
3064ef96a1 | ||
|
|
ee69ece7b5 | ||
|
|
d90d23699c | ||
|
|
1f5ff46fd9 | ||
|
|
13528f50c3 | ||
|
|
dd1ae3b109 | ||
|
|
b352a8e5d4 | ||
|
|
fd102059aa | ||
|
|
323671a653 | ||
|
|
b155e6ccf5 | ||
|
|
f16b9a4928 | ||
|
|
24eb45425e | ||
|
|
3442bc0ea3 | ||
|
|
40bdf43297 | ||
|
|
8ead070b94 | ||
|
|
b22b2cbfac | ||
|
|
2f2b84bfbb | ||
|
|
5cdd2c2414 | ||
|
|
3ddd936b03 | ||
|
|
1921c5416b | ||
|
|
fc47c1d00e | ||
|
|
327a6e166f | ||
|
|
6f5268b02d | ||
|
|
4964433190 | ||
|
|
9e3c4fd2d7 | ||
|
|
89e8e110c8 | ||
|
|
9f7ea77d0c | ||
|
|
5f74397ef0 | ||
|
|
960181fd99 | ||
|
|
2a7602cad4 | ||
|
|
47aacb773b | ||
|
|
82d9336114 | ||
|
|
e60286a344 | ||
|
|
53850bce93 | ||
|
|
1236529e39 | ||
|
|
06444bf050 | ||
|
|
b723ee73fc | ||
|
|
c35bda0551 | ||
|
|
f53fb6aa66 | ||
|
|
a85d4473aa | ||
|
|
c9b9225951 | ||
|
|
11898a6461 | ||
|
|
01e5447e35 | ||
|
|
4ada50985a | ||
|
|
a283786463 | ||
|
|
12f72e1740 | ||
|
|
b57eed4584 | ||
|
|
3672a799d4 | ||
|
|
3fd5385e7b | ||
|
|
d439855a6d | ||
|
|
2810013b93 | ||
|
|
0687ab8ae3 | ||
|
|
64dbde0dbf | ||
|
|
ae57b3a8c5 | ||
|
|
0dbbc16c69 | ||
|
|
f690320fb9 | ||
|
|
553170b77a | ||
|
|
8a6096a3de | ||
|
|
d2f6d9b9fb | ||
|
|
dbe9f33fdc | ||
|
|
1be6aa0c7f | ||
|
|
087ca3009b | ||
|
|
db7c7d1af1 | ||
|
|
42fb4faa0f | ||
|
|
9bb398ee91 | ||
|
|
9043b32183 | ||
|
|
e30bbb1cb0 | ||
|
|
8bdf02812c |
@@ -1,6 +1,8 @@
|
||||
engines:
|
||||
duplication:
|
||||
enabled: true
|
||||
exclude_paths:
|
||||
- app/assets/javascripts/components/locales/
|
||||
config:
|
||||
languages:
|
||||
- ruby
|
||||
|
||||
12
.editorconfig
Normal file
12
.editorconfig
Normal file
@@ -0,0 +1,12 @@
|
||||
# EditorConfig is awesome: http://EditorConfig.org
|
||||
|
||||
# top-most EditorConfig file
|
||||
root = true
|
||||
|
||||
# Unix-style newlines with a newline ending every file
|
||||
[*]
|
||||
end_of_line = lf
|
||||
insert_final_newline = true
|
||||
charset = utf-8
|
||||
indent_style = space
|
||||
indent_size = 2
|
||||
@@ -35,6 +35,11 @@ SMTP_PORT=587
|
||||
SMTP_LOGIN=
|
||||
SMTP_PASSWORD=
|
||||
SMTP_FROM_ADDRESS=notifications@example.com
|
||||
#SMTP_DELIVERY_METHOD=smtp # delivery method can also be sendmail
|
||||
#SMTP_AUTH_METHOD=plain
|
||||
#SMTP_OPENSSL_VERIFY_MODE=peer
|
||||
#SMTP_ENABLE_STARTTLS_AUTO=true
|
||||
|
||||
|
||||
# Optional asset host for multi-server setups
|
||||
# CDN_HOST=assets.example.com
|
||||
|
||||
30
.eslintignore
Normal file
30
.eslintignore
Normal file
@@ -0,0 +1,30 @@
|
||||
# See https://help.github.com/articles/ignoring-files for more about ignoring files.
|
||||
#
|
||||
# If you find yourself ignoring temporary files generated by your text editor
|
||||
# or operating system, you probably want to add a global ignore instead:
|
||||
# git config --global core.excludesfile '~/.gitignore_global'
|
||||
|
||||
# Ignore bundler config.
|
||||
/.bundle
|
||||
|
||||
# Ignore the default SQLite database.
|
||||
/db/*.sqlite3
|
||||
/db/*.sqlite3-journal
|
||||
|
||||
# Ignore all logfiles and tempfiles.
|
||||
/log/*
|
||||
!/log/.keep
|
||||
/tmp
|
||||
coverage
|
||||
public/system
|
||||
public/assets
|
||||
.env
|
||||
.env.production
|
||||
node_modules/
|
||||
neo4j/
|
||||
|
||||
# Ignore Vagrant files
|
||||
.vagrant/
|
||||
|
||||
# Ignore Capistrano customizations
|
||||
config/deploy/*
|
||||
8
.gitignore
vendored
8
.gitignore
vendored
@@ -28,3 +28,11 @@ neo4j/
|
||||
|
||||
# Ignore Capistrano customizations
|
||||
config/deploy/*
|
||||
|
||||
|
||||
# Ignore IDE files
|
||||
.vscode/
|
||||
|
||||
# Ignore postgres + redis volume optionally created by docker-compose
|
||||
postgres
|
||||
redis
|
||||
|
||||
@@ -1 +1 @@
|
||||
2.3.1
|
||||
2.4.1
|
||||
|
||||
@@ -16,7 +16,7 @@ addons:
|
||||
postgresql: 9.4
|
||||
|
||||
rvm:
|
||||
- 2.3.1
|
||||
- 2.4.1
|
||||
|
||||
services:
|
||||
- redis-server
|
||||
|
||||
@@ -7,7 +7,7 @@ There are three ways in which you can contribute to this repository:
|
||||
2. By working on the back-end application
|
||||
3. By working on the front-end application
|
||||
|
||||
Choosing what to work on in a large open source project is not easy. The list of GitHub issues may provide some ideas, but not every feature request has been greenlit. Likewise, not every change or feature that resolves a personal itch will be merged into the main repository. Some communication ahead of time may be wise.
|
||||
Choosing what to work on in a large open source project is not easy. The list of GitHub issues may provide some ideas, but not every feature request has been greenlit. Likewise, not every change or feature that resolves a personal itch will be merged into the main repository. Some communication ahead of time may be wise. If your addition creates a new feature or setting, or otherwise changes how things work in some substantial way, please remember to submit a correlating pull request to document your changes in the [documentation](http://github.com/tootsuite/documentation).
|
||||
|
||||
Below are the guidelines for working on pull requests:
|
||||
|
||||
@@ -41,3 +41,4 @@ It is expected that you have a working development environment set up (see back-
|
||||
* If you are introducing new strings, they must be using localization methods
|
||||
|
||||
If the JavaScript or CSS assets won't compile due to a syntax error, it's a good sign that the pull request isn't ready for submission yet.
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
FROM ruby:2.3.1-alpine
|
||||
FROM ruby:2.4.1-alpine
|
||||
|
||||
LABEL maintainer="https://github.com/tootsuite/mastodon" \
|
||||
description="A GNU Social-compatible microblogging server"
|
||||
|
||||
5
Gemfile
5
Gemfile
@@ -1,7 +1,7 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
source 'https://rubygems.org'
|
||||
ruby '2.3.1'
|
||||
ruby '2.4.1'
|
||||
|
||||
gem 'rails', '~> 5.0.2'
|
||||
gem 'sass-rails', '~> 5.0'
|
||||
@@ -32,6 +32,7 @@ gem 'htmlentities'
|
||||
gem 'http'
|
||||
gem 'http_accept_language'
|
||||
gem 'httplog'
|
||||
gem 'kaminari'
|
||||
gem 'link_header'
|
||||
gem 'nokogiri'
|
||||
gem 'oj'
|
||||
@@ -52,7 +53,6 @@ gem 'simple_form'
|
||||
gem 'statsd-instrument'
|
||||
gem 'twitter-text'
|
||||
gem 'tzinfo-data'
|
||||
gem 'will_paginate'
|
||||
|
||||
gem 'react-rails'
|
||||
gem 'browserify-rails'
|
||||
@@ -68,6 +68,7 @@ end
|
||||
|
||||
group :test do
|
||||
gem 'faker'
|
||||
gem 'rails-controller-testing'
|
||||
gem 'rspec-sidekiq'
|
||||
gem 'simplecov', require: false
|
||||
gem 'webmock'
|
||||
|
||||
206
Gemfile.lock
206
Gemfile.lock
@@ -24,7 +24,7 @@ GEM
|
||||
erubis (~> 2.7.0)
|
||||
rails-dom-testing (~> 2.0)
|
||||
rails-html-sanitizer (~> 1.0, >= 1.0.3)
|
||||
active_record_query_trace (1.5.3)
|
||||
active_record_query_trace (1.5.4)
|
||||
activejob (5.0.2)
|
||||
activesupport (= 5.0.2)
|
||||
globalid (>= 0.3.6)
|
||||
@@ -39,7 +39,7 @@ GEM
|
||||
i18n (~> 0.7)
|
||||
minitest (~> 5.1)
|
||||
tzinfo (~> 1.1)
|
||||
addressable (2.5.0)
|
||||
addressable (2.5.1)
|
||||
public_suffix (~> 2.0, >= 2.0.2)
|
||||
airbrussh (1.1.2)
|
||||
sshkit (>= 1.6.1, != 1.7.0)
|
||||
@@ -47,17 +47,17 @@ GEM
|
||||
ast (2.3.0)
|
||||
attr_encrypted (3.0.3)
|
||||
encryptor (~> 3.0.0)
|
||||
autoprefixer-rails (6.5.0.2)
|
||||
autoprefixer-rails (6.7.7.1)
|
||||
execjs
|
||||
av (0.9.0)
|
||||
cocaine (~> 0.5.3)
|
||||
aws-sdk (2.6.28)
|
||||
aws-sdk-resources (= 2.6.28)
|
||||
aws-sdk-core (2.6.28)
|
||||
aws-sdk (2.9.6)
|
||||
aws-sdk-resources (= 2.9.6)
|
||||
aws-sdk-core (2.9.6)
|
||||
aws-sigv4 (~> 1.0)
|
||||
jmespath (~> 1.0)
|
||||
aws-sdk-resources (2.6.28)
|
||||
aws-sdk-core (= 2.6.28)
|
||||
aws-sdk-resources (2.9.6)
|
||||
aws-sdk-core (= 2.9.6)
|
||||
aws-sigv4 (1.0.0)
|
||||
babel-source (5.8.35)
|
||||
babel-transpiler (0.7.0)
|
||||
@@ -78,12 +78,11 @@ GEM
|
||||
railties (>= 4.0.0, < 5.1)
|
||||
sprockets (>= 3.6.0)
|
||||
builder (3.2.3)
|
||||
bullet (5.3.0)
|
||||
bullet (5.5.1)
|
||||
activesupport (>= 3.0.0)
|
||||
uniform_notifier (~> 1.10.0)
|
||||
capistrano (3.7.2)
|
||||
capistrano (3.8.0)
|
||||
airbrussh (>= 1.0.0)
|
||||
capistrano-harrow
|
||||
i18n
|
||||
rake (>= 10.0.0)
|
||||
sshkit (>= 1.9.0)
|
||||
@@ -92,8 +91,7 @@ GEM
|
||||
sshkit (~> 1.2)
|
||||
capistrano-faster-assets (1.0.2)
|
||||
capistrano (>= 3.1)
|
||||
capistrano-harrow (0.5.3)
|
||||
capistrano-rails (1.2.2)
|
||||
capistrano-rails (1.2.3)
|
||||
capistrano (~> 3.1)
|
||||
capistrano-bundler (~> 1.1)
|
||||
capistrano-rbenv (2.1.0)
|
||||
@@ -119,7 +117,7 @@ GEM
|
||||
crack (0.4.3)
|
||||
safe_yaml (~> 1.0.0)
|
||||
debug_inspector (0.0.2)
|
||||
devise (4.2.0)
|
||||
devise (4.2.1)
|
||||
bcrypt (~> 3.0)
|
||||
orm_adapter (~> 0.1)
|
||||
railties (>= 4.1.0, < 5.1)
|
||||
@@ -131,16 +129,16 @@ GEM
|
||||
devise (~> 4.0)
|
||||
railties
|
||||
rotp (~> 2.0)
|
||||
diff-lcs (1.2.5)
|
||||
diff-lcs (1.3)
|
||||
docile (1.1.5)
|
||||
domain_name (0.5.20161129)
|
||||
domain_name (0.5.20170404)
|
||||
unf (>= 0.0.5, < 1.0.0)
|
||||
doorkeeper (4.2.0)
|
||||
doorkeeper (4.2.5)
|
||||
railties (>= 4.2)
|
||||
dotenv (2.1.1)
|
||||
dotenv-rails (2.1.1)
|
||||
dotenv (= 2.1.1)
|
||||
railties (>= 4.0, < 5.1)
|
||||
dotenv (2.2.0)
|
||||
dotenv-rails (2.2.0)
|
||||
dotenv (= 2.2.0)
|
||||
railties (>= 3.2, < 5.1)
|
||||
easy_translate (0.5.0)
|
||||
json
|
||||
thread
|
||||
@@ -148,14 +146,14 @@ GEM
|
||||
encryptor (3.0.0)
|
||||
erubis (2.7.0)
|
||||
execjs (2.7.0)
|
||||
fabrication (2.15.2)
|
||||
faker (1.6.6)
|
||||
fabrication (2.16.1)
|
||||
faker (1.7.3)
|
||||
i18n (~> 0.5)
|
||||
fast_blank (1.0.0)
|
||||
font-awesome-rails (4.6.3.1)
|
||||
font-awesome-rails (4.7.0.1)
|
||||
railties (>= 3.2, < 5.1)
|
||||
fuubar (2.1.1)
|
||||
rspec (~> 3.0)
|
||||
fuubar (2.2.0)
|
||||
rspec-core (~> 3.0)
|
||||
ruby-progressbar (~> 1.4)
|
||||
globalid (0.3.7)
|
||||
activesupport (>= 4.1.0)
|
||||
@@ -163,20 +161,20 @@ GEM
|
||||
addressable (~> 2.4)
|
||||
http (~> 2.0)
|
||||
nokogiri (~> 1.6)
|
||||
hamlit (2.7.2)
|
||||
temple (~> 0.7.6)
|
||||
hamlit (2.8.1)
|
||||
temple (>= 0.8.0)
|
||||
thor
|
||||
tilt
|
||||
hamlit-rails (0.1.0)
|
||||
hamlit-rails (0.2.0)
|
||||
actionpack (>= 4.0.1)
|
||||
activesupport (>= 4.0.1)
|
||||
hamlit (>= 1.2.0)
|
||||
railties (>= 4.0.1)
|
||||
hashdiff (0.3.0)
|
||||
hashdiff (0.3.2)
|
||||
highline (1.7.8)
|
||||
hiredis (0.6.1)
|
||||
htmlentities (4.3.4)
|
||||
http (2.1.0)
|
||||
http (2.2.1)
|
||||
addressable (~> 2.3)
|
||||
http-cookie (~> 1.0)
|
||||
http-form_data (~> 1.0.1)
|
||||
@@ -186,10 +184,10 @@ GEM
|
||||
http-form_data (1.0.1)
|
||||
http_accept_language (2.1.0)
|
||||
http_parser.rb (0.6.0)
|
||||
httplog (0.3.2)
|
||||
httplog (0.99.2)
|
||||
colorize
|
||||
i18n (0.8.1)
|
||||
i18n-tasks (0.9.6)
|
||||
i18n-tasks (0.9.13)
|
||||
activesupport (>= 4.0.2)
|
||||
ast (>= 2.1.0)
|
||||
easy_translate (>= 0.5.0)
|
||||
@@ -197,19 +195,31 @@ GEM
|
||||
highline (>= 1.7.3)
|
||||
i18n
|
||||
parser (>= 2.2.3.0)
|
||||
term-ansicolor (>= 1.3.2)
|
||||
rainbow (~> 2.2)
|
||||
terminal-table (>= 1.5.1)
|
||||
jmespath (1.3.1)
|
||||
jquery-rails (4.1.1)
|
||||
jquery-rails (4.3.1)
|
||||
rails-dom-testing (>= 1, < 3)
|
||||
railties (>= 4.2.0)
|
||||
thor (>= 0.14, < 2.0)
|
||||
json (1.8.3)
|
||||
json (2.0.3)
|
||||
kaminari (1.0.1)
|
||||
activesupport (>= 4.1.0)
|
||||
kaminari-actionview (= 1.0.1)
|
||||
kaminari-activerecord (= 1.0.1)
|
||||
kaminari-core (= 1.0.1)
|
||||
kaminari-actionview (1.0.1)
|
||||
actionview
|
||||
kaminari-core (= 1.0.1)
|
||||
kaminari-activerecord (1.0.1)
|
||||
activerecord
|
||||
kaminari-core (= 1.0.1)
|
||||
kaminari-core (1.0.1)
|
||||
launchy (2.4.3)
|
||||
addressable (~> 2.3)
|
||||
letter_opener (1.4.1)
|
||||
launchy (~> 2.2)
|
||||
letter_opener_web (1.3.0)
|
||||
letter_opener_web (1.3.1)
|
||||
actionmailer (>= 3.2)
|
||||
letter_opener (~> 1.0)
|
||||
railties (>= 3.2)
|
||||
@@ -231,11 +241,11 @@ GEM
|
||||
minitest (5.10.1)
|
||||
net-scp (1.2.1)
|
||||
net-ssh (>= 2.6.5)
|
||||
net-ssh (4.0.1)
|
||||
net-ssh (4.1.0)
|
||||
nio4r (2.0.0)
|
||||
nokogiri (1.7.1)
|
||||
mini_portile2 (~> 2.1.0)
|
||||
oj (2.17.3)
|
||||
oj (2.18.5)
|
||||
orm_adapter (0.5.0)
|
||||
ostatus2 (1.0.2)
|
||||
addressable (~> 2.4)
|
||||
@@ -251,26 +261,26 @@ GEM
|
||||
paperclip-av-transcoder (0.6.4)
|
||||
av (~> 0.9.0)
|
||||
paperclip (>= 2.5.2)
|
||||
parser (2.3.1.2)
|
||||
parser (2.4.0.0)
|
||||
ast (~> 2.2)
|
||||
pg (0.18.4)
|
||||
pghero (1.6.2)
|
||||
pg (0.20.0)
|
||||
pghero (1.6.4)
|
||||
activerecord
|
||||
powerpack (0.1.1)
|
||||
pry (0.10.4)
|
||||
coderay (~> 1.1.0)
|
||||
method_source (~> 0.8.1)
|
||||
slop (~> 3.4)
|
||||
pry-rails (0.3.4)
|
||||
pry (>= 0.9.10)
|
||||
public_suffix (2.0.4)
|
||||
puma (3.6.0)
|
||||
pry-rails (0.3.6)
|
||||
pry (>= 0.10.4)
|
||||
public_suffix (2.0.5)
|
||||
puma (3.8.2)
|
||||
rabl (0.13.1)
|
||||
activesupport (>= 2.3.14)
|
||||
rack (2.0.1)
|
||||
rack-attack (5.0.1)
|
||||
rack
|
||||
rack-cors (0.4.0)
|
||||
rack-cors (0.4.1)
|
||||
rack-protection (1.5.3)
|
||||
rack
|
||||
rack-test (0.6.3)
|
||||
@@ -288,6 +298,10 @@ GEM
|
||||
bundler (>= 1.3.0, < 2.0)
|
||||
railties (= 5.0.2)
|
||||
sprockets-rails (>= 2.0.0)
|
||||
rails-controller-testing (1.0.1)
|
||||
actionpack (~> 5.x)
|
||||
actionview (~> 5.x)
|
||||
activesupport (~> 5.x)
|
||||
rails-dom-testing (2.0.2)
|
||||
activesupport (>= 4.2.0, < 6.0)
|
||||
nokogiri (~> 1.6)
|
||||
@@ -306,42 +320,37 @@ GEM
|
||||
method_source
|
||||
rake (>= 0.8.7)
|
||||
thor (>= 0.18.1, < 2.0)
|
||||
rainbow (2.1.0)
|
||||
rainbow (2.2.1)
|
||||
rake (12.0.0)
|
||||
react-rails (1.10.0)
|
||||
react-rails (1.11.0)
|
||||
babel-transpiler (>= 0.7.0)
|
||||
coffee-script-source (~> 1.8)
|
||||
connection_pool
|
||||
execjs
|
||||
railties (>= 3.2)
|
||||
tilt
|
||||
redis (3.3.2)
|
||||
redis-actionpack (5.0.0)
|
||||
actionpack (>= 4.0.0, < 6)
|
||||
redis-rack (~> 2.0.0.pre)
|
||||
redis-store (~> 1.2.0.pre)
|
||||
redis-activesupport (5.0.1)
|
||||
redis (3.3.3)
|
||||
redis-actionpack (5.0.1)
|
||||
actionpack (>= 4.0, < 6)
|
||||
redis-rack (>= 1, < 3)
|
||||
redis-store (>= 1.1.0, < 1.4.0)
|
||||
redis-activesupport (5.0.2)
|
||||
activesupport (>= 3, < 6)
|
||||
redis-store (~> 1.2.0)
|
||||
redis-rack (2.0.0)
|
||||
rack (~> 2.0)
|
||||
redis-store (~> 1.2.0)
|
||||
redis-rails (5.0.1)
|
||||
redis-actionpack (~> 5.0.0)
|
||||
redis-activesupport (~> 5.0.0)
|
||||
redis-store (~> 1.2.0)
|
||||
redis-store (1.2.0)
|
||||
redis-store (~> 1.3.0)
|
||||
redis-rack (2.0.1)
|
||||
rack (>= 2.0, < 3)
|
||||
redis-store (>= 1.2, < 1.4)
|
||||
redis-rails (5.0.2)
|
||||
redis-actionpack (>= 5.0, < 6)
|
||||
redis-activesupport (>= 5.0, < 6)
|
||||
redis-store (>= 1.2, < 2)
|
||||
redis-store (1.3.0)
|
||||
redis (>= 2.2)
|
||||
responders (2.3.0)
|
||||
railties (>= 4.2.0, < 5.1)
|
||||
rotp (2.1.2)
|
||||
rqrcode (0.10.1)
|
||||
chunky_png (~> 1.0)
|
||||
rspec (3.5.0)
|
||||
rspec-core (~> 3.5.0)
|
||||
rspec-expectations (~> 3.5.0)
|
||||
rspec-mocks (~> 3.5.0)
|
||||
rspec-core (3.5.2)
|
||||
rspec-core (3.5.4)
|
||||
rspec-support (~> 3.5.0)
|
||||
rspec-expectations (3.5.0)
|
||||
diff-lcs (>= 1.2.0, < 2.0)
|
||||
@@ -349,7 +358,7 @@ GEM
|
||||
rspec-mocks (3.5.0)
|
||||
diff-lcs (>= 1.2.0, < 2.0)
|
||||
rspec-support (~> 3.5.0)
|
||||
rspec-rails (3.5.1)
|
||||
rspec-rails (3.5.2)
|
||||
actionpack (>= 3.0)
|
||||
activesupport (>= 3.0)
|
||||
railties (>= 3.0)
|
||||
@@ -357,40 +366,40 @@ GEM
|
||||
rspec-expectations (~> 3.5.0)
|
||||
rspec-mocks (~> 3.5.0)
|
||||
rspec-support (~> 3.5.0)
|
||||
rspec-sidekiq (2.2.0)
|
||||
rspec (~> 3.0, >= 3.0.0)
|
||||
rspec-sidekiq (3.0.0)
|
||||
rspec-core (~> 3.0, >= 3.0.0)
|
||||
sidekiq (>= 2.4.0)
|
||||
rspec-support (3.5.0)
|
||||
rubocop (0.42.0)
|
||||
parser (>= 2.3.1.1, < 3.0)
|
||||
rubocop (0.48.1)
|
||||
parser (>= 2.3.3.1, < 3.0)
|
||||
powerpack (~> 0.1)
|
||||
rainbow (>= 1.99.1, < 3.0)
|
||||
ruby-progressbar (~> 1.7)
|
||||
unicode-display_width (~> 1.0, >= 1.0.1)
|
||||
ruby-oembed (0.10.1)
|
||||
ruby-oembed (0.12.0)
|
||||
ruby-progressbar (1.8.1)
|
||||
safe_yaml (1.0.4)
|
||||
sass (3.4.22)
|
||||
sass (3.4.23)
|
||||
sass-rails (5.0.6)
|
||||
railties (>= 4.0.0, < 6)
|
||||
sass (~> 3.1)
|
||||
sprockets (>= 2.8, < 4.0)
|
||||
sprockets-rails (>= 2.0, < 4.0)
|
||||
tilt (>= 1.1, < 3)
|
||||
sidekiq (4.2.7)
|
||||
sidekiq (4.2.10)
|
||||
concurrent-ruby (~> 1.0)
|
||||
connection_pool (~> 2.2, >= 2.2.0)
|
||||
rack-protection (>= 1.5.0)
|
||||
redis (~> 3.2, >= 3.2.1)
|
||||
sidekiq-unique-jobs (4.0.18)
|
||||
sidekiq (>= 2.6)
|
||||
sidekiq-unique-jobs (5.0.0)
|
||||
sidekiq (>= 4.0)
|
||||
thor
|
||||
simple-navigation (4.0.3)
|
||||
simple-navigation (4.0.5)
|
||||
activesupport (>= 2.3.2)
|
||||
simple_form (3.2.1)
|
||||
simple_form (3.4.0)
|
||||
actionpack (> 4, < 5.1)
|
||||
activemodel (> 4, < 5.1)
|
||||
simplecov (0.12.0)
|
||||
simplecov (0.14.1)
|
||||
docile (~> 1.1.0)
|
||||
json (>= 1.8, < 3)
|
||||
simplecov-html (~> 0.10.0)
|
||||
@@ -403,43 +412,39 @@ GEM
|
||||
actionpack (>= 4.0)
|
||||
activesupport (>= 4.0)
|
||||
sprockets (>= 3.0.0)
|
||||
sshkit (1.11.5)
|
||||
sshkit (1.13.1)
|
||||
net-scp (>= 1.1.2)
|
||||
net-ssh (>= 2.8.0)
|
||||
statsd-instrument (2.1.2)
|
||||
temple (0.7.7)
|
||||
term-ansicolor (1.4.0)
|
||||
tins (~> 1.0)
|
||||
terminal-table (1.7.0)
|
||||
unicode-display_width (~> 1.1)
|
||||
temple (0.8.0)
|
||||
terminal-table (1.7.3)
|
||||
unicode-display_width (~> 1.1.1)
|
||||
thor (0.19.4)
|
||||
thread (0.2.2)
|
||||
thread_safe (0.3.6)
|
||||
tilt (2.0.6)
|
||||
tins (1.12.0)
|
||||
tilt (2.0.7)
|
||||
twitter-text (1.14.5)
|
||||
unf (~> 0.1.0)
|
||||
tzinfo (1.2.2)
|
||||
tzinfo (1.2.3)
|
||||
thread_safe (~> 0.1)
|
||||
tzinfo-data (1.2017.2)
|
||||
tzinfo (>= 1.0.0)
|
||||
uglifier (3.0.1)
|
||||
uglifier (3.2.0)
|
||||
execjs (>= 0.3.0, < 3)
|
||||
unf (0.1.4)
|
||||
unf_ext
|
||||
unf_ext (0.0.7.2)
|
||||
unicode-display_width (1.1.0)
|
||||
unicode-display_width (1.1.3)
|
||||
uniform_notifier (1.10.0)
|
||||
warden (1.2.6)
|
||||
warden (1.2.7)
|
||||
rack (>= 1.0)
|
||||
webmock (2.1.0)
|
||||
webmock (2.3.2)
|
||||
addressable (>= 2.3.6)
|
||||
crack (>= 0.3.2)
|
||||
hashdiff
|
||||
websocket-driver (0.6.5)
|
||||
websocket-extensions (>= 0.1.0)
|
||||
websocket-extensions (0.1.2)
|
||||
will_paginate (3.1.0)
|
||||
|
||||
PLATFORMS
|
||||
ruby
|
||||
@@ -478,6 +483,7 @@ DEPENDENCIES
|
||||
httplog
|
||||
i18n-tasks (~> 0.9.6)
|
||||
jquery-rails
|
||||
kaminari
|
||||
letter_opener
|
||||
letter_opener_web
|
||||
link_header
|
||||
@@ -497,6 +503,7 @@ DEPENDENCIES
|
||||
rack-cors
|
||||
rack-timeout
|
||||
rails (~> 5.0.2)
|
||||
rails-controller-testing
|
||||
rails-settings-cached
|
||||
rails_12factor
|
||||
react-rails
|
||||
@@ -518,10 +525,9 @@ DEPENDENCIES
|
||||
tzinfo-data
|
||||
uglifier (>= 1.3.0)
|
||||
webmock
|
||||
will_paginate
|
||||
|
||||
RUBY VERSION
|
||||
ruby 2.3.1p112
|
||||
ruby 2.4.1p111
|
||||
|
||||
BUNDLED WITH
|
||||
1.14.5
|
||||
1.14.6
|
||||
|
||||
73
README.md
73
README.md
@@ -25,11 +25,11 @@ If you would like, you can [support the development of this project on Patreon][
|
||||
|
||||
## Resources
|
||||
|
||||
- [List of Mastodon instances](docs/Using-Mastodon/List-of-Mastodon-instances.md)
|
||||
- [List of Mastodon instances](https://github.com/tootsuite/documentation/blob/master/Using-Mastodon/List-of-Mastodon-instances.md)
|
||||
- [Use this tool to find Twitter friends on Mastodon](https://mastodon-bridge.herokuapp.com)
|
||||
- [API overview](docs/Using-the-API/API.md)
|
||||
- [Frequently Asked Questions](docs/Using-Mastodon/FAQ.md)
|
||||
- [List of apps](docs/Using-Mastodon/Apps.md)
|
||||
- [API overview](https://github.com/tootsuite/documentation/blob/master/Using-the-API/API.md)
|
||||
- [Frequently Asked Questions](https://github.com/tootsuite/documentation/blob/master/Using-Mastodon/FAQ.md)
|
||||
- [List of apps](https://github.com/tootsuite/documentation/blob/master/Using-Mastodon/Apps.md)
|
||||
|
||||
## Features
|
||||
|
||||
@@ -67,23 +67,52 @@ Consult the example configuration file, `.env.production.sample` for the full li
|
||||
|
||||
[](https://microbadger.com/images/gargron/mastodon "Get your own version badge on microbadger.com") [](https://microbadger.com/images/gargron/mastodon "Get your own image badge on microbadger.com")
|
||||
|
||||
The project now includes a `Dockerfile` and a `docker-compose.yml`. You need to turn `.env.production.sample` into `.env.production` with all the variables set before you can:
|
||||
The project now includes a `Dockerfile` and a `docker-compose.yml` file (which requires at least docker-compose version `1.10.0`).
|
||||
|
||||
Review the settings in `docker-compose.yml`. Note that it is not default to store the postgresql database and redis databases in a persistent storage location,
|
||||
so you may need or want to adjust the settings there.
|
||||
|
||||
Before running the first time, you need to build the images:
|
||||
|
||||
docker-compose build
|
||||
|
||||
And finally
|
||||
Then, you need to fill in the `.env.production` file:
|
||||
|
||||
docker-compose up -d
|
||||
cp .env.production.sample .env.production
|
||||
nano .env.production
|
||||
|
||||
As usual, the first thing you would need to do would be to run migrations:
|
||||
Do NOT change the `REDIS_*` or `DB_*` settings when running with the default docker configurations.
|
||||
|
||||
You will need to fill in, at least: `LOCAL_DOMAIN`, `LOCAL_HTTPS`, `PAPERCLIP_SECRET`, `SECRET_KEY_BASE`, `OTP_SECRET`, and the `SMTP_*` settings. To generate the `PAPERCLIP_SECRET`, `SECRET_KEY_BASE`, and `OTP_SECRET`, you may use:
|
||||
|
||||
docker-compose run --rm web rake secret
|
||||
|
||||
Do this once for each of those keys, and copy the result into the `.env.production` file in the appropriate field.
|
||||
|
||||
Then you should run the `db:migrate` command to create the database, or migrate it from an older release:
|
||||
|
||||
docker-compose run --rm web rails db:migrate
|
||||
|
||||
And since the instance running in the container will be running in production mode, you need to pre-compile assets:
|
||||
Then, you will also need to precompile the assets:
|
||||
|
||||
docker-compose run --rm web rails assets:precompile
|
||||
|
||||
The container has two volumes, for the assets and for user uploads. The default docker-compose.yml maps them to the repository's `public/assets` and `public/system` directories, you may wish to put them somewhere else. Likewise, the PostgreSQL and Redis images have data containers that you may wish to map somewhere where you know how to find them and back them up.
|
||||
before you can launch the docker image with:
|
||||
|
||||
docker-compose up
|
||||
|
||||
If you wish to run this as a daemon process instead of monitoring it on console, use instead:
|
||||
|
||||
docker-compose up -d
|
||||
|
||||
Then you may login to your new Mastodon instance by browsing to http://localhost:3000/
|
||||
|
||||
Following that, make sure that you read the [production guide](docs/Running-Mastodon/Production-guide.md). You are probably going to want to understand how
|
||||
to configure Nginx to make your Mastodon instance available to the rest of the world.
|
||||
|
||||
The container has two volumes, for the assets and for user uploads, and optionally two more, for the postgresql and redis databases.
|
||||
|
||||
The default docker-compose.yml maps them to the repository's `public/assets` and `public/system` directories, you may wish to put them somewhere else. Likewise, the PostgreSQL and Redis images have data containers that you may wish to map somewhere where you know how to find them and back them up.
|
||||
|
||||
**Note**: The `--rm` option for docker-compose will remove the container that is created to run a one-off command after it completes. As data is stored in volumes it is not affected by that container clean-up.
|
||||
|
||||
@@ -103,39 +132,33 @@ Running any of these tasks via docker-compose would look like this:
|
||||
|
||||
This approach makes updating to the latest version a real breeze.
|
||||
|
||||
git pull
|
||||
|
||||
To pull down the updates, re-run
|
||||
|
||||
docker-compose build
|
||||
|
||||
And finally,
|
||||
|
||||
docker-compose up -d
|
||||
|
||||
Which will re-create the updated containers, leaving databases and data as is. Depending on what files have been updated, you might need to re-run migrations and asset compilation.
|
||||
1. `git pull` to download updates from the repository
|
||||
2. `docker-compose build` to compile the Docker image out of the changed source files
|
||||
3. (optional) `docker-compose run --rm web rails db:migrate` to perform database migrations. Does nothing if your database is up to date
|
||||
4. (optional) `docker-compose run --rm web rails assets:precompile` to compile new JS and CSS assets
|
||||
5. `docker-compose up -d` to re-create (restart) containers and pick up the changes
|
||||
|
||||
## Deployment without Docker
|
||||
|
||||
Docker is great for quickly trying out software, but it has its drawbacks too. If you prefer to run Mastodon without using Docker, refer to the [production guide](docs/Running-Mastodon/Production-guide.md) for examples, configuration and instructions.
|
||||
Docker is great for quickly trying out software, but it has its drawbacks too. If you prefer to run Mastodon without using Docker, refer to the [production guide](https://github.com/tootsuite/documentation/blob/master/Running-Mastodon/Production-guide.md) for examples, configuration and instructions.
|
||||
|
||||
## Deployment on Scalingo
|
||||
|
||||
[](https://my.scalingo.com/deploy?source=https://github.com/tootsuite/mastodon#master)
|
||||
|
||||
[You can view a guide for deployment on Scalingo here.](docs/Running-Mastodon/Scalingo-guide.md)
|
||||
[You can view a guide for deployment on Scalingo here.](https://github.com/tootsuite/documentation/blob/master/Running-Mastodon/Scalingo-guide.md)
|
||||
|
||||
## Deployment on Heroku (experimental)
|
||||
|
||||
[](https://heroku.com/deploy)
|
||||
|
||||
Mastodon can theoretically run indefinitely on a free [Heroku](https://heroku.com) app. [You can view a guide for deployment on Heroku here.](docs/Running-Mastodon/Heroku-guide.md)
|
||||
Mastodon can run on [Heroku](https://heroku.com), but it gets expensive and impractical due to how Heroku prices resource usage. [You can view a guide for deployment on Heroku here](https://github.com/tootsuite/documentation/blob/master/Running-Mastodon/Heroku-guide.md), but you have been warned.
|
||||
|
||||
## Development with Vagrant
|
||||
|
||||
A quick way to get a development environment up and running is with Vagrant. You will need recent versions of [Vagrant](https://www.vagrantup.com/) and [VirtualBox](https://www.virtualbox.org/) installed.
|
||||
|
||||
[You can find the guide for setting up a Vagrant development environment here.](docs/Running-Mastodon/Vagrant-guide.md)
|
||||
[You can find the guide for setting up a Vagrant development environment here.](https://github.com/tootsuite/documentation/blob/master/Running-Mastodon/Vagrant-guide.md)
|
||||
|
||||
## Contributing
|
||||
|
||||
|
||||
8
Vagrantfile
vendored
8
Vagrantfile
vendored
@@ -46,12 +46,12 @@ git clone https://github.com/rbenv/ruby-build.git ~/.rbenv/plugins/ruby-build
|
||||
export PATH="$HOME/.rbenv/bin::$PATH"
|
||||
eval "$(rbenv init -)"
|
||||
|
||||
echo "Compiling Ruby 2.3.1: warning, this takes a while!!!"
|
||||
rbenv install 2.3.1
|
||||
rbenv global 2.3.1
|
||||
|
||||
cd /vagrant
|
||||
|
||||
echo "Compiling Ruby $(cat .ruby-version): warning, this takes a while!!!"
|
||||
rbenv install $(cat .ruby-version)
|
||||
rbenv global $(cat .ruby-version)
|
||||
|
||||
# Configure database
|
||||
sudo -u postgres createuser -U postgres vagrant -s
|
||||
sudo -u postgres createdb -U postgres mastodon_development
|
||||
|
||||
12
app.json
12
app.json
@@ -79,6 +79,18 @@
|
||||
"SMTP_FROM_ADDRESS": {
|
||||
"description": "Address to send emails from",
|
||||
"required": false
|
||||
},
|
||||
"SMTP_AUTH_METHOD": {
|
||||
"description": "Authentication method to use with SMTP server. Default is 'plain'.",
|
||||
"required": false
|
||||
},
|
||||
"SMTP_OPENSSL_VERIFY_MODE": {
|
||||
"description": "SMTP server certificate verification mode. Defaults is 'peer'.",
|
||||
"required": false
|
||||
},
|
||||
"SMTP_ENABLE_STARTTLS_AUTO": {
|
||||
"description": "Enable STARTTLS if SMTP server supports it? Default is true.",
|
||||
"required": false
|
||||
}
|
||||
},
|
||||
"buildpacks": [
|
||||
|
||||
Binary file not shown.
|
Before Width: | Height: | Size: 209 KiB After Width: | Height: | Size: 258 KiB |
@@ -1,4 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="1000" height="1000" viewBox="0 0 1000 1000">
|
||||
<path d="M527.194 543.7a28.362 28.362 0 0 0-56.723 0 25.73 25.73 0 0 0 2.67 11.674 26.42 26.42 0 0 0 5.672 8.34 28.2 28.2 0 0 0 40.04 0 31.87 31.87 0 0 0 6.006-8.34 28.8 28.8 0 0 0 2.336-11.674m-48.382-113.413a28.308 28.308 0 1 0 40.04 40.027 37.2 37.2 0 0 0 4.67-5.67 28.092 28.092 0 0 0 3.67-14.343 27.29 27.29 0 0 0-8.34-20.012 28.24 28.24 0 0 0-5.006-4 26.958 26.958 0 0 0-15.015-4.336 27.31 27.31 0 0 0-20.02 8.34m20.02-101.735a28.476 28.476 0 1 0 20.02 8.34 27.31 27.31 0 0 0-20.02-8.34M231.9 573.717a28.18 28.18 0 1 0 8.342 20.012 27.308 27.308 0 0 0-8.342-20.014m-40.04-93.4a28.352 28.352 0 0 0 20.02 48.366 26.958 26.958 0 0 0 15.015-4.336 28.255 28.255 0 0 0 5.005-4 27.29 27.29 0 0 0 8.342-20.013 28.09 28.09 0 0 0-3.67-14.343 37.21 37.21 0 0 0-4.67-5.67 28.2 28.2 0 0 0-40.04 0m40.04-93.4a28.2 28.2 0 0 0-40.04 0 26.425 26.425 0 0 0-5.673 8.34 25.73 25.73 0 0 0-2.67 11.673 28.315 28.315 0 0 0 48.38 20.018 27.29 27.29 0 0 0 8.342-20.012 28.8 28.8 0 0 0-2.336-11.674 31.87 31.87 0 0 0-6.006-8.34m550.55 178.453a28.476 28.476 0 1 0 20.02 8.34 27.31 27.31 0 0 0-20.02-8.34m20.02-85.057a28.2 28.2 0 0 0-40.04 0 37.2 37.2 0 0 0-4.672 5.67 28.092 28.092 0 0 0-3.67 14.343 27.29 27.29 0 0 0 8.342 20.013 28.248 28.248 0 0 0 5.005 4 26.96 26.96 0 0 0 15.015 4.336 28.3 28.3 0 0 0 20.02-48.366m-46.046-85.057a28.8 28.8 0 0 0-2.336 11.673 28.362 28.362 0 0 0 56.723 0 25.73 25.73 0 0 0-2.668-11.674 26.427 26.427 0 0 0-5.672-8.34 28.2 28.2 0 0 0-40.04 0 31.86 31.86 0 0 0-6.007 8.343z" fill="#2b90d9"/>
|
||||
<path d="M853.52 146.764Q707.04 0 499.833 0 292.96 0 146.48 146.764 0 293.2 0 500q0 207.138 146.48 353.57T499.833 1000q207.207 0 353.687-146.43T1000 500q0-206.8-146.48-353.236zM213.547 708.806h-3.337q-43.043 0-73.407-30.02-30.03-30.354-30.03-73.382V395.93v-.666q1.335-41.027 30.03-69.713 30.364-30.35 73.407-30.35t73.073 30.354q29.363 29.02 30.364 70.38V615.41q2.336 55.037 46.713 93.4zM600.6 554.7q-1 41.36-30.364 70.38-30.03 30.353-73.073 30.354t-73.407-30.354q-28.7-28.686-30.03-69.713V345.23q0-43.03 30.03-73.382 30.364-30.02 73.407-30.02h150.15q-44.378 38.36-46.713 93.4zm286.954 50.7q0 43.03-30.03 73.382-30.364 30.02-73.407 30.02h-150.15q44.378-38.36 46.713-93.4v-219.47q1-41.362 30.364-70.38 30.03-30.355 73.073-30.355t73.407 30.354q28.7 28.687 30.03 69.714V605.4z" fill="#2b90d9"/>
|
||||
</svg>
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 1000 1000" height="1000" width="1000"><g fill="#189efc"><path d="M500 0A500 500 0 0 0 0 500a500 500 0 0 0 500 500 500 500 0 0 0 500-500A500 500 0 0 0 500 0zm-2.5 271.1h107.24c-20.56 14.471-27.24 57.064-27.24 78.927v202.145c0 43.726-35.202 78.928-80 78.928s-80-35.202-80-78.928V350.027c0-43.725 35.202-78.927 80-78.927zm-276 48.9c44.798 0 80 35.202 80 78.928v202.144c0 21.863 6.68 64.456 27.24 78.928H221.5c-44.798 0-80-35.202-80-78.928V398.928c0-43.726 35.202-78.928 80-78.928zm550.24 0c44.799 0 80 35.202 80 78.928v202.144c0 43.726-35.201 78.928-80 78.928H664.5c20.56-14.472 27.24-57.065 27.24-78.928V398.928c0-43.726 35.202-78.928 80-78.928z"/><g transform="translate(-2)"><circle cx="223.5" cy="410.5" r="27.5"/><circle cx="223.5" cy="500.5" r="27.5"/><circle cx="223.5" cy="590.5" r="27.5"/></g><g transform="matrix(1 0 0 -1 274 951)"><circle cx="223.5" cy="410.5" r="27.5"/><circle cx="223.5" cy="500.5" r="27.5"/><circle cx="223.5" cy="590.5" r="27.5"/></g><g transform="matrix(-1 0 0 1 995 0)"><circle cx="223.5" cy="410.5" r="27.5"/><circle cx="223.5" cy="500.5" r="27.5"/><circle cx="223.5" cy="590.5" r="27.5"/></g></g></svg>
|
||||
|
Before Width: | Height: | Size: 2.3 KiB After Width: | Height: | Size: 1.2 KiB |
@@ -2,6 +2,8 @@ import api from '../api';
|
||||
|
||||
import { updateTimeline } from './timelines';
|
||||
|
||||
import * as emojione from 'emojione';
|
||||
|
||||
export const COMPOSE_CHANGE = 'COMPOSE_CHANGE';
|
||||
export const COMPOSE_SUBMIT_REQUEST = 'COMPOSE_SUBMIT_REQUEST';
|
||||
export const COMPOSE_SUBMIT_SUCCESS = 'COMPOSE_SUBMIT_SUCCESS';
|
||||
@@ -72,9 +74,8 @@ export function mentionCompose(account, router) {
|
||||
export function submitCompose() {
|
||||
return function (dispatch, getState) {
|
||||
dispatch(submitComposeRequest());
|
||||
|
||||
api(getState).post('/api/v1/statuses', {
|
||||
status: getState().getIn(['compose', 'text'], ''),
|
||||
status: emojione.shortnameToUnicode(getState().getIn(['compose', 'text'], '')),
|
||||
in_reply_to_id: getState().getIn(['compose', 'in_reply_to'], null),
|
||||
media_ids: getState().getIn(['compose', 'media_attachments']).map(item => item.get('id')),
|
||||
sensitive: getState().getIn(['compose', 'sensitive']),
|
||||
|
||||
@@ -50,6 +50,8 @@ export function updateNotifications(notification, intlMessages, intlLocale) {
|
||||
};
|
||||
};
|
||||
|
||||
const excludeTypesFromSettings = state => state.getIn(['settings', 'notifications', 'shows']).filter(enabled => !enabled).keySeq().toJS();
|
||||
|
||||
export function refreshNotifications() {
|
||||
return (dispatch, getState) => {
|
||||
dispatch(refreshNotificationsRequest());
|
||||
@@ -61,6 +63,8 @@ export function refreshNotifications() {
|
||||
params.since_id = ids.first().get('id');
|
||||
}
|
||||
|
||||
params.exclude_types = excludeTypesFromSettings(getState());
|
||||
|
||||
api(getState).get('/api/v1/notifications', { params }).then(response => {
|
||||
const next = getLinks(response).refs.find(link => link.rel === 'next');
|
||||
|
||||
@@ -105,11 +109,11 @@ export function expandNotifications() {
|
||||
|
||||
dispatch(expandNotificationsRequest());
|
||||
|
||||
api(getState).get(url, {
|
||||
params: {
|
||||
limit: 5
|
||||
}
|
||||
}).then(response => {
|
||||
const params = {};
|
||||
|
||||
params.exclude_types = excludeTypesFromSettings(getState());
|
||||
|
||||
api(getState).get(url, params).then(response => {
|
||||
const next = getLinks(response).refs.find(link => link.rel === 'next');
|
||||
|
||||
dispatch(expandNotificationsSuccess(response.data, next ? next.uri : null));
|
||||
|
||||
@@ -7,7 +7,8 @@ export const REPORT_SUBMIT_REQUEST = 'REPORT_SUBMIT_REQUEST';
|
||||
export const REPORT_SUBMIT_SUCCESS = 'REPORT_SUBMIT_SUCCESS';
|
||||
export const REPORT_SUBMIT_FAIL = 'REPORT_SUBMIT_FAIL';
|
||||
|
||||
export const REPORT_STATUS_TOGGLE = 'REPORT_STATUS_TOGGLE';
|
||||
export const REPORT_STATUS_TOGGLE = 'REPORT_STATUS_TOGGLE';
|
||||
export const REPORT_COMMENT_CHANGE = 'REPORT_COMMENT_CHANGE';
|
||||
|
||||
export function initReport(account, status) {
|
||||
return {
|
||||
@@ -62,3 +63,10 @@ export function submitReportFail(error) {
|
||||
error
|
||||
};
|
||||
};
|
||||
|
||||
export function changeReportComment(comment) {
|
||||
return {
|
||||
type: REPORT_COMMENT_CHANGE,
|
||||
comment
|
||||
};
|
||||
};
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import axios from 'axios';
|
||||
import LinkHeader from 'http-link-header';
|
||||
import LinkHeader from './link_header';
|
||||
|
||||
export const getLinks = response => {
|
||||
const value = response.headers.link;
|
||||
|
||||
@@ -65,7 +65,7 @@ const Account = React.createClass({
|
||||
<div className='account'>
|
||||
<div style={{ display: 'flex' }}>
|
||||
<Permalink key={account.get('id')} className='account__display-name' href={account.get('url')} to={`/accounts/${account.get('id')}`}>
|
||||
<div style={{ float: 'left', marginLeft: '12px', marginRight: '10px' }}><Avatar src={account.get('avatar')} size={36} /></div>
|
||||
<div style={{ float: 'left', marginLeft: '12px', marginRight: '10px' }}><Avatar src={account.get('avatar')} staticSrc={account.get('avatar_static')} size={36} /></div>
|
||||
<DisplayName account={account} />
|
||||
</Permalink>
|
||||
|
||||
|
||||
@@ -1,103 +1,18 @@
|
||||
import PureRenderMixin from 'react-addons-pure-render-mixin';
|
||||
|
||||
// From: http://stackoverflow.com/a/18320662
|
||||
const resample = (canvas, width, height, resize_canvas) => {
|
||||
let width_source = canvas.width;
|
||||
let height_source = canvas.height;
|
||||
width = Math.round(width);
|
||||
height = Math.round(height);
|
||||
|
||||
let ratio_w = width_source / width;
|
||||
let ratio_h = height_source / height;
|
||||
let ratio_w_half = Math.ceil(ratio_w / 2);
|
||||
let ratio_h_half = Math.ceil(ratio_h / 2);
|
||||
|
||||
let ctx = canvas.getContext("2d");
|
||||
let img = ctx.getImageData(0, 0, width_source, height_source);
|
||||
let img2 = ctx.createImageData(width, height);
|
||||
let data = img.data;
|
||||
let data2 = img2.data;
|
||||
|
||||
for (let j = 0; j < height; j++) {
|
||||
for (let i = 0; i < width; i++) {
|
||||
let x2 = (i + j * width) * 4;
|
||||
let weight = 0;
|
||||
let weights = 0;
|
||||
let weights_alpha = 0;
|
||||
let gx_r = 0;
|
||||
let gx_g = 0;
|
||||
let gx_b = 0;
|
||||
let gx_a = 0;
|
||||
let center_y = (j + 0.5) * ratio_h;
|
||||
let yy_start = Math.floor(j * ratio_h);
|
||||
let yy_stop = Math.ceil((j + 1) * ratio_h);
|
||||
|
||||
for (let yy = yy_start; yy < yy_stop; yy++) {
|
||||
let dy = Math.abs(center_y - (yy + 0.5)) / ratio_h_half;
|
||||
let center_x = (i + 0.5) * ratio_w;
|
||||
let w0 = dy * dy; //pre-calc part of w
|
||||
let xx_start = Math.floor(i * ratio_w);
|
||||
let xx_stop = Math.ceil((i + 1) * ratio_w);
|
||||
|
||||
for (let xx = xx_start; xx < xx_stop; xx++) {
|
||||
let dx = Math.abs(center_x - (xx + 0.5)) / ratio_w_half;
|
||||
let w = Math.sqrt(w0 + dx * dx);
|
||||
|
||||
if (w >= 1) {
|
||||
// pixel too far
|
||||
continue;
|
||||
}
|
||||
|
||||
// hermite filter
|
||||
weight = 2 * w * w * w - 3 * w * w + 1;
|
||||
let pos_x = 4 * (xx + yy * width_source);
|
||||
|
||||
// alpha
|
||||
gx_a += weight * data[pos_x + 3];
|
||||
weights_alpha += weight;
|
||||
|
||||
// colors
|
||||
if (data[pos_x + 3] < 255)
|
||||
weight = weight * data[pos_x + 3] / 250;
|
||||
|
||||
gx_r += weight * data[pos_x];
|
||||
gx_g += weight * data[pos_x + 1];
|
||||
gx_b += weight * data[pos_x + 2];
|
||||
weights += weight;
|
||||
}
|
||||
}
|
||||
|
||||
data2[x2] = gx_r / weights;
|
||||
data2[x2 + 1] = gx_g / weights;
|
||||
data2[x2 + 2] = gx_b / weights;
|
||||
data2[x2 + 3] = gx_a / weights_alpha;
|
||||
}
|
||||
}
|
||||
|
||||
// clear and resize canvas
|
||||
if (resize_canvas === true) {
|
||||
canvas.width = width;
|
||||
canvas.height = height;
|
||||
} else {
|
||||
ctx.clearRect(0, 0, width_source, height_source);
|
||||
}
|
||||
|
||||
// draw
|
||||
ctx.putImageData(img2, 0, 0);
|
||||
};
|
||||
|
||||
const Avatar = React.createClass({
|
||||
|
||||
propTypes: {
|
||||
src: React.PropTypes.string.isRequired,
|
||||
staticSrc: React.PropTypes.string,
|
||||
size: React.PropTypes.number.isRequired,
|
||||
style: React.PropTypes.object,
|
||||
animated: React.PropTypes.bool
|
||||
animate: React.PropTypes.bool
|
||||
},
|
||||
|
||||
getDefaultProps () {
|
||||
return {
|
||||
animated: true
|
||||
animate: false
|
||||
};
|
||||
},
|
||||
|
||||
@@ -117,38 +32,30 @@ const Avatar = React.createClass({
|
||||
this.setState({ hovering: false });
|
||||
},
|
||||
|
||||
handleLoad () {
|
||||
this.canvas.width = this.image.naturalWidth;
|
||||
this.canvas.height = this.image.naturalHeight;
|
||||
this.canvas.getContext('2d').drawImage(this.image, 0, 0);
|
||||
|
||||
resample(this.canvas, this.props.size * window.devicePixelRatio, this.props.size * window.devicePixelRatio, true);
|
||||
},
|
||||
|
||||
setImageRef (c) {
|
||||
this.image = c;
|
||||
},
|
||||
|
||||
setCanvasRef (c) {
|
||||
this.canvas = c;
|
||||
},
|
||||
|
||||
render () {
|
||||
const { src, size, staticSrc, animate } = this.props;
|
||||
const { hovering } = this.state;
|
||||
|
||||
if (this.props.animated) {
|
||||
return (
|
||||
<div style={{ ...this.props.style, width: `${this.props.size}px`, height: `${this.props.size}px` }}>
|
||||
<img src={this.props.src} width={this.props.size} height={this.props.size} alt='' style={{ borderRadius: '4px' }} />
|
||||
</div>
|
||||
);
|
||||
const style = {
|
||||
...this.props.style,
|
||||
width: `${size}px`,
|
||||
height: `${size}px`,
|
||||
backgroundSize: `${size}px ${size}px`
|
||||
};
|
||||
|
||||
if (hovering || animate) {
|
||||
style.backgroundImage = `url(${src})`;
|
||||
} else {
|
||||
style.backgroundImage = `url(${staticSrc})`;
|
||||
}
|
||||
|
||||
return (
|
||||
<div onMouseEnter={this.handleMouseEnter} onMouseLeave={this.handleMouseLeave} style={{ ...this.props.style, width: `${this.props.size}px`, height: `${this.props.size}px`, position: 'relative' }}>
|
||||
<img ref={this.setImageRef} onLoad={this.handleLoad} src={this.props.src} width={this.props.size} height={this.props.size} alt='' style={{ position: 'absolute', top: '0', left: '0', opacity: hovering ? '1' : '0', borderRadius: '4px' }} />
|
||||
<canvas ref={this.setCanvasRef} style={{ borderRadius: '4px', width: this.props.size, height: this.props.size, opacity: hovering ? '0' : '1' }} />
|
||||
</div>
|
||||
<div
|
||||
className='avatar'
|
||||
onMouseEnter={this.handleMouseEnter}
|
||||
onMouseLeave={this.handleMouseLeave}
|
||||
style={style}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
@@ -3,15 +3,43 @@ import PureRenderMixin from 'react-addons-pure-render-mixin';
|
||||
const ExtendedVideoPlayer = React.createClass({
|
||||
|
||||
propTypes: {
|
||||
src: React.PropTypes.string.isRequired
|
||||
src: React.PropTypes.string.isRequired,
|
||||
time: React.PropTypes.number,
|
||||
controls: React.PropTypes.bool.isRequired,
|
||||
muted: React.PropTypes.bool.isRequired
|
||||
},
|
||||
|
||||
mixins: [PureRenderMixin],
|
||||
|
||||
handleLoadedData () {
|
||||
if (this.props.time) {
|
||||
this.video.currentTime = this.props.time;
|
||||
}
|
||||
},
|
||||
|
||||
componentDidMount () {
|
||||
this.video.addEventListener('loadeddata', this.handleLoadedData);
|
||||
},
|
||||
|
||||
componentWillUnmount () {
|
||||
this.video.removeEventListener('loadeddata', this.handleLoadedData);
|
||||
},
|
||||
|
||||
setRef (c) {
|
||||
this.video = c;
|
||||
},
|
||||
|
||||
render () {
|
||||
return (
|
||||
<div>
|
||||
<video src={this.props.src} autoPlay muted loop />
|
||||
<div className='extended-video-player'>
|
||||
<video
|
||||
ref={this.setRef}
|
||||
src={this.props.src}
|
||||
autoPlay
|
||||
muted={this.props.muted}
|
||||
controls={this.props.controls}
|
||||
loop={!this.props.controls}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
},
|
||||
|
||||
@@ -13,7 +13,8 @@ const IconButton = React.createClass({
|
||||
activeStyle: React.PropTypes.object,
|
||||
disabled: React.PropTypes.bool,
|
||||
inverted: React.PropTypes.bool,
|
||||
animate: React.PropTypes.bool
|
||||
animate: React.PropTypes.bool,
|
||||
overlay: React.PropTypes.bool
|
||||
},
|
||||
|
||||
getDefaultProps () {
|
||||
@@ -21,7 +22,8 @@ const IconButton = React.createClass({
|
||||
size: 18,
|
||||
active: false,
|
||||
disabled: false,
|
||||
animate: false
|
||||
animate: false,
|
||||
overlay: false
|
||||
};
|
||||
},
|
||||
|
||||
@@ -31,7 +33,7 @@ const IconButton = React.createClass({
|
||||
e.preventDefault();
|
||||
|
||||
if (!this.props.disabled) {
|
||||
this.props.onClick();
|
||||
this.props.onClick(e);
|
||||
}
|
||||
},
|
||||
|
||||
@@ -39,7 +41,7 @@ const IconButton = React.createClass({
|
||||
let style = {
|
||||
fontSize: `${this.props.size}px`,
|
||||
width: `${this.props.size * 1.28571429}px`,
|
||||
height: `${this.props.size}px`,
|
||||
height: `${this.props.size * 1.28571429}px`,
|
||||
lineHeight: `${this.props.size}px`,
|
||||
...this.props.style
|
||||
};
|
||||
@@ -48,13 +50,31 @@ const IconButton = React.createClass({
|
||||
style = { ...style, ...this.props.activeStyle };
|
||||
}
|
||||
|
||||
const classes = ['icon-button'];
|
||||
|
||||
if (this.props.active) {
|
||||
classes.push('active');
|
||||
}
|
||||
|
||||
if (this.props.disabled) {
|
||||
classes.push('disabled');
|
||||
}
|
||||
|
||||
if (this.props.inverted) {
|
||||
classes.push('inverted');
|
||||
}
|
||||
|
||||
if (this.props.overlay) {
|
||||
classes.push('overlayed');
|
||||
}
|
||||
|
||||
return (
|
||||
<Motion defaultStyle={{ rotate: this.props.active ? -360 : 0 }} style={{ rotate: this.props.animate ? spring(this.props.active ? -360 : 0, { stiffness: 120, damping: 7 }) : 0 }}>
|
||||
{({ rotate }) =>
|
||||
<button
|
||||
aria-label={this.props.title}
|
||||
title={this.props.title}
|
||||
className={`icon-button ${this.props.active ? 'active' : ''} ${this.props.disabled ? 'disabled' : ''} ${this.props.inverted ? 'inverted' : ''}`}
|
||||
className={classes.join(' ')}
|
||||
onClick={this.handleClick}
|
||||
style={style}>
|
||||
<i style={{ transform: `rotate(${rotate}deg)` }} className={`fa fa-fw fa-${this.props.icon}`} aria-hidden='true' />
|
||||
|
||||
@@ -39,8 +39,8 @@ const spoilerSubSpanStyle = {
|
||||
|
||||
const spoilerButtonStyle = {
|
||||
position: 'absolute',
|
||||
top: '6px',
|
||||
left: '8px',
|
||||
top: '4px',
|
||||
left: '4px',
|
||||
zIndex: '100'
|
||||
};
|
||||
|
||||
@@ -232,8 +232,8 @@ const MediaGallery = React.createClass({
|
||||
|
||||
return (
|
||||
<div style={{ ...outerStyle, height: `${this.props.height}px` }}>
|
||||
<div style={spoilerButtonStyle}>
|
||||
<IconButton title={intl.formatMessage(messages.toggle_visible)} icon={this.state.visible ? 'eye' : 'eye-slash'} onClick={this.handleOpen} />
|
||||
<div style={{ ...spoilerButtonStyle, display: !this.state.visible ? 'none' : 'block' }}>
|
||||
<IconButton title={intl.formatMessage(messages.toggle_visible)} icon={this.state.visible ? 'eye' : 'eye-slash'} overlay onClick={this.handleOpen} />
|
||||
</div>
|
||||
|
||||
{children}
|
||||
|
||||
@@ -25,8 +25,10 @@ const Status = React.createClass({
|
||||
onReblog: React.PropTypes.func,
|
||||
onDelete: React.PropTypes.func,
|
||||
onOpenMedia: React.PropTypes.func,
|
||||
onOpenVideo: React.PropTypes.func,
|
||||
onBlock: React.PropTypes.func,
|
||||
me: React.PropTypes.number,
|
||||
boostModal: React.PropTypes.bool,
|
||||
muted: React.PropTypes.bool
|
||||
},
|
||||
|
||||
@@ -75,7 +77,7 @@ const Status = React.createClass({
|
||||
|
||||
if (status.get('media_attachments').size > 0 && !this.props.muted) {
|
||||
if (status.getIn(['media_attachments', 0, 'type']) === 'video') {
|
||||
media = <VideoPlayer media={status.getIn(['media_attachments', 0])} sensitive={status.get('sensitive')} />;
|
||||
media = <VideoPlayer media={status.getIn(['media_attachments', 0])} sensitive={status.get('sensitive')} onOpenVideo={this.props.onOpenVideo} />;
|
||||
} else {
|
||||
media = <MediaGallery media={status.get('media_attachments')} sensitive={status.get('sensitive')} height={110} onOpenMedia={this.props.onOpenMedia} />;
|
||||
}
|
||||
@@ -90,7 +92,7 @@ const Status = React.createClass({
|
||||
|
||||
<a onClick={this.handleAccountClick.bind(this, status.getIn(['account', 'id']))} href={status.getIn(['account', 'url'])} className='status__display-name' style={{ display: 'block', maxWidth: '100%', paddingRight: '25px' }}>
|
||||
<div className='status__avatar' style={{ position: 'absolute', left: '10px', top: '10px', width: '48px', height: '48px' }}>
|
||||
<Avatar src={status.getIn(['account', 'avatar'])} size={48} />
|
||||
<Avatar src={status.getIn(['account', 'avatar'])} staticSrc={status.getIn(['account', 'avatar_static'])} size={48} />
|
||||
</div>
|
||||
|
||||
<DisplayName account={status.get('account')} />
|
||||
|
||||
@@ -46,8 +46,8 @@ const StatusActionBar = React.createClass({
|
||||
this.props.onFavourite(this.props.status);
|
||||
},
|
||||
|
||||
handleReblogClick () {
|
||||
this.props.onReblog(this.props.status);
|
||||
handleReblogClick (e) {
|
||||
this.props.onReblog(this.props.status, e);
|
||||
},
|
||||
|
||||
handleDeleteClick () {
|
||||
|
||||
@@ -36,6 +36,7 @@ const StatusContent = React.createClass({
|
||||
|
||||
if (mention) {
|
||||
link.addEventListener('click', this.onMentionClick.bind(this, mention), false);
|
||||
link.setAttribute('title', mention.get('acct'));
|
||||
} else if (link.textContent[0] === '#' || (link.previousSibling && link.previousSibling.textContent && link.previousSibling.textContent[link.previousSibling.textContent.length - 1] === '#')) {
|
||||
link.addEventListener('click', this.onHashtagClick.bind(this, link.text), false);
|
||||
} else if (media) {
|
||||
@@ -125,7 +126,7 @@ const StatusContent = React.createClass({
|
||||
<div style={{ display: hidden ? 'none' : 'block', ...directionStyle }} dangerouslySetInnerHTML={content} />
|
||||
</div>
|
||||
);
|
||||
} else {
|
||||
} else if (this.props.onClick) {
|
||||
return (
|
||||
<div
|
||||
className='status__content'
|
||||
@@ -135,6 +136,14 @@ const StatusContent = React.createClass({
|
||||
dangerouslySetInnerHTML={content}
|
||||
/>
|
||||
);
|
||||
} else {
|
||||
return (
|
||||
<div
|
||||
className='status__content'
|
||||
style={{ ...directionStyle }}
|
||||
dangerouslySetInnerHTML={content}
|
||||
/>
|
||||
);
|
||||
}
|
||||
},
|
||||
|
||||
|
||||
@@ -6,7 +6,8 @@ import { isIOS } from '../is_mobile';
|
||||
|
||||
const messages = defineMessages({
|
||||
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' }
|
||||
});
|
||||
|
||||
const videoStyle = {
|
||||
@@ -21,8 +22,8 @@ const videoStyle = {
|
||||
|
||||
const muteStyle = {
|
||||
position: 'absolute',
|
||||
top: '10px',
|
||||
right: '10px',
|
||||
top: '4px',
|
||||
right: '4px',
|
||||
color: 'white',
|
||||
textShadow: "0px 1px 1px black, 1px 0px 1px black",
|
||||
opacity: '0.8',
|
||||
@@ -54,8 +55,17 @@ const spoilerSubSpanStyle = {
|
||||
|
||||
const spoilerButtonStyle = {
|
||||
position: 'absolute',
|
||||
top: '6px',
|
||||
left: '8px',
|
||||
top: '4px',
|
||||
left: '4px',
|
||||
color: 'white',
|
||||
textShadow: "0px 1px 1px black, 1px 0px 1px black",
|
||||
zIndex: '100'
|
||||
};
|
||||
|
||||
const expandButtonStyle = {
|
||||
position: 'absolute',
|
||||
bottom: '4px',
|
||||
right: '4px',
|
||||
color: 'white',
|
||||
textShadow: "0px 1px 1px black, 1px 0px 1px black",
|
||||
zIndex: '100'
|
||||
@@ -68,7 +78,8 @@ const VideoPlayer = React.createClass({
|
||||
height: React.PropTypes.number,
|
||||
sensitive: React.PropTypes.bool,
|
||||
intl: React.PropTypes.object.isRequired,
|
||||
autoplay: React.PropTypes.bool
|
||||
autoplay: React.PropTypes.bool,
|
||||
onOpenVideo: React.PropTypes.func.isRequired
|
||||
},
|
||||
|
||||
getDefaultProps () {
|
||||
@@ -116,6 +127,11 @@ const VideoPlayer = React.createClass({
|
||||
});
|
||||
},
|
||||
|
||||
handleExpand () {
|
||||
this.video.pause();
|
||||
this.props.onOpenVideo(this.props.media, this.video.currentTime);
|
||||
},
|
||||
|
||||
setRef (c) {
|
||||
this.video = c;
|
||||
},
|
||||
@@ -154,8 +170,14 @@ const VideoPlayer = React.createClass({
|
||||
const { media, intl, width, height, sensitive, autoplay } = this.props;
|
||||
|
||||
let spoilerButton = (
|
||||
<div style={spoilerButtonStyle} >
|
||||
<IconButton title={intl.formatMessage(messages.toggle_visible)} icon={this.state.visible ? 'eye' : 'eye-slash'} onClick={this.handleVisibility} />
|
||||
<div style={{...spoilerButtonStyle, display: !this.state.visible ? 'none' : 'block'}} >
|
||||
<IconButton overlay title={intl.formatMessage(messages.toggle_visible)} icon={this.state.visible ? 'eye' : 'eye-slash'} onClick={this.handleVisibility} />
|
||||
</div>
|
||||
);
|
||||
|
||||
let expandButton = (
|
||||
<div style={expandButtonStyle} >
|
||||
<IconButton overlay title={intl.formatMessage(messages.expand_video)} icon='expand' onClick={this.handleExpand} />
|
||||
</div>
|
||||
);
|
||||
|
||||
@@ -164,7 +186,7 @@ const VideoPlayer = React.createClass({
|
||||
if (this.state.hasAudio) {
|
||||
muteButton = (
|
||||
<div style={muteStyle}>
|
||||
<IconButton title={intl.formatMessage(messages.toggle_sound)} icon={this.state.muted ? 'volume-off' : 'volume-up'} onClick={this.handleClick} />
|
||||
<IconButton overlay title={intl.formatMessage(messages.toggle_sound)} icon={this.state.muted ? 'volume-off' : 'volume-up'} onClick={this.handleClick} />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -202,6 +224,7 @@ const VideoPlayer = React.createClass({
|
||||
<div style={{ cursor: 'default', marginTop: '8px', overflow: 'hidden', width: `${width}px`, height: `${height}px`, boxSizing: 'border-box', background: '#000', position: 'relative' }}>
|
||||
{spoilerButton}
|
||||
{muteButton}
|
||||
{expandButton}
|
||||
<video ref={this.setRef} src={media.get('url')} autoPlay={!isIOS()} loop={true} muted={this.state.muted} style={videoStyle} onClick={this.handleVideoClick} />
|
||||
</div>
|
||||
);
|
||||
|
||||
@@ -41,13 +41,20 @@ import Report from '../features/report';
|
||||
import { IntlProvider, addLocaleData } from 'react-intl';
|
||||
import en from 'react-intl/locale-data/en';
|
||||
import de from 'react-intl/locale-data/de';
|
||||
import es from 'react-intl/locale-data/es';
|
||||
import fr from 'react-intl/locale-data/fr';
|
||||
import pt from 'react-intl/locale-data/pt';
|
||||
import hu from 'react-intl/locale-data/hu';
|
||||
import uk from 'react-intl/locale-data/uk';
|
||||
import fi from 'react-intl/locale-data/fi';
|
||||
import eo from 'react-intl/locale-data/eo';
|
||||
import es from 'react-intl/locale-data/es';
|
||||
import fi from 'react-intl/locale-data/fi';
|
||||
import fr from 'react-intl/locale-data/fr';
|
||||
import hu from 'react-intl/locale-data/hu';
|
||||
import ja from 'react-intl/locale-data/ja';
|
||||
import pt from 'react-intl/locale-data/pt';
|
||||
import nl from 'react-intl/locale-data/nl';
|
||||
import no from 'react-intl/locale-data/no';
|
||||
import ru from 'react-intl/locale-data/ru';
|
||||
import uk from 'react-intl/locale-data/uk';
|
||||
import zh from 'react-intl/locale-data/zh';
|
||||
import bg from 'react-intl/locale-data/bg';
|
||||
import { localeData as zh_hk } from '../locales/zh-hk';
|
||||
import getMessagesForLocale from '../locales';
|
||||
import { hydrateStore } from '../actions/store';
|
||||
import createStream from '../stream';
|
||||
@@ -60,7 +67,24 @@ const browserHistory = useRouterHistory(createBrowserHistory)({
|
||||
basename: '/web'
|
||||
});
|
||||
|
||||
addLocaleData([...en, ...de, ...es, ...fr, ...pt, ...hu, ...uk, ...fi, ...eo]);
|
||||
addLocaleData([
|
||||
...en,
|
||||
...de,
|
||||
...eo,
|
||||
...es,
|
||||
...fi,
|
||||
...fr,
|
||||
...hu,
|
||||
...ja,
|
||||
...pt,
|
||||
...nl,
|
||||
...no,
|
||||
...ru,
|
||||
...uk,
|
||||
...zh,
|
||||
...zh_hk,
|
||||
...bg,
|
||||
]);
|
||||
|
||||
const Mastodon = React.createClass({
|
||||
|
||||
|
||||
@@ -26,7 +26,8 @@ const makeMapStateToProps = () => {
|
||||
|
||||
const mapStateToProps = (state, props) => ({
|
||||
status: getStatus(state, props.id),
|
||||
me: state.getIn(['meta', 'me'])
|
||||
me: state.getIn(['meta', 'me']),
|
||||
boostModal: state.getIn(['meta', 'boost_modal'])
|
||||
});
|
||||
|
||||
return mapStateToProps;
|
||||
@@ -38,11 +39,19 @@ const mapDispatchToProps = (dispatch) => ({
|
||||
dispatch(replyCompose(status, router));
|
||||
},
|
||||
|
||||
onReblog (status) {
|
||||
onModalReblog (status) {
|
||||
dispatch(reblog(status));
|
||||
},
|
||||
|
||||
onReblog (status, e) {
|
||||
if (status.get('reblogged')) {
|
||||
dispatch(unreblog(status));
|
||||
} else {
|
||||
dispatch(reblog(status));
|
||||
if (e.shiftKey || !this.boostModal) {
|
||||
this.onModalReblog(status);
|
||||
} else {
|
||||
dispatch(openModal('BOOST', { status, onReblog: this.onModalReblog }));
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
@@ -66,6 +75,10 @@ const mapDispatchToProps = (dispatch) => ({
|
||||
dispatch(openModal('MEDIA', { media, index }));
|
||||
},
|
||||
|
||||
onOpenVideo (media, time) {
|
||||
dispatch(openModal('VIDEO', { media, time }));
|
||||
},
|
||||
|
||||
onBlock (account) {
|
||||
dispatch(blockAccount(account.get('id')));
|
||||
},
|
||||
|
||||
@@ -14,7 +14,7 @@ import ColumnBackButtonSlim from '../../components/column_back_button_slim';
|
||||
import createStream from '../../stream';
|
||||
|
||||
const messages = defineMessages({
|
||||
title: { id: 'column.community', defaultMessage: 'Local' }
|
||||
title: { id: 'column.community', defaultMessage: 'Local timeline' }
|
||||
});
|
||||
|
||||
const mapStateToProps = state => ({
|
||||
|
||||
@@ -4,7 +4,7 @@ import ImmutablePropTypes from 'react-immutable-proptypes';
|
||||
|
||||
const AutosuggestAccount = ({ account }) => (
|
||||
<div style={{ overflow: 'hidden' }} className='autosuggest-account'>
|
||||
<div style={{ float: 'left', marginRight: '5px' }}><Avatar src={account.get('avatar')} size={18} /></div>
|
||||
<div style={{ float: 'left', marginRight: '5px' }}><Avatar src={account.get('avatar')} staticSrc={account.get('avatar_static')} size={18} /></div>
|
||||
<DisplayName account={account} />
|
||||
</div>
|
||||
);
|
||||
|
||||
@@ -19,7 +19,7 @@ import TextIconButton from './text_icon_button';
|
||||
const messages = defineMessages({
|
||||
placeholder: { id: 'compose_form.placeholder', defaultMessage: 'What is on your mind?' },
|
||||
spoiler_placeholder: { id: 'compose_form.spoiler_placeholder', defaultMessage: 'Content warning' },
|
||||
publish: { id: 'compose_form.publish', defaultMessage: 'Publish' }
|
||||
publish: { id: 'compose_form.publish', defaultMessage: 'Toot' }
|
||||
});
|
||||
|
||||
const ComposeForm = React.createClass({
|
||||
@@ -83,11 +83,23 @@ const ComposeForm = React.createClass({
|
||||
this.props.onChangeSpoilerText(e.target.value);
|
||||
},
|
||||
|
||||
componentWillReceiveProps (nextProps) {
|
||||
// If this is the update where we've finished uploading,
|
||||
// save the last caret position so we can restore it below!
|
||||
if (!nextProps.is_uploading && this.props.is_uploading) {
|
||||
this._restoreCaret = this.autosuggestTextarea.textarea.selectionStart;
|
||||
}
|
||||
},
|
||||
|
||||
componentDidUpdate (prevProps) {
|
||||
if (this.props.focusDate !== prevProps.focusDate) {
|
||||
// If replying to zero or one users, places the cursor at the end of the textbox.
|
||||
// If replying to more than one user, selects any usernames past the first;
|
||||
// this provides a convenient shortcut to drop everyone else from the conversation.
|
||||
// This statement does several things:
|
||||
// - If we're beginning a reply, and,
|
||||
// - Replying to zero or one users, places the cursor at the end of the textbox.
|
||||
// - Replying to more than one user, selects any usernames past the first;
|
||||
// this provides a convenient shortcut to drop everyone else from the conversation.
|
||||
// - If we've just finished uploading an image, and have a saved caret position,
|
||||
// restores the cursor to that position after the text changes!
|
||||
if (this.props.focusDate !== prevProps.focusDate || (prevProps.is_uploading && !this.props.is_uploading && typeof this._restoreCaret === 'number')) {
|
||||
let selectionEnd, selectionStart;
|
||||
|
||||
if (this.props.preselectDate !== prevProps.preselectDate) {
|
||||
@@ -118,7 +130,7 @@ const ComposeForm = React.createClass({
|
||||
|
||||
render () {
|
||||
const { intl, needsPrivacyWarning, mentionedDomains, onPaste } = this.props;
|
||||
const disabled = this.props.is_submitting || this.props.is_uploading;
|
||||
const disabled = this.props.is_submitting;
|
||||
|
||||
let publishText = '';
|
||||
let privacyWarning = '';
|
||||
|
||||
@@ -46,8 +46,8 @@ const EmojiPickerDropdown = React.createClass({
|
||||
<img draggable="false" className="emojione" alt="🙂" src="/emoji/1f602.svg" />
|
||||
</DropdownTrigger>
|
||||
|
||||
<DropdownContent className='dropdown__left'>
|
||||
<EmojiPicker emojione={settings} onChange={this.handleChange} />
|
||||
<DropdownContent className='dropdown__left light'>
|
||||
<EmojiPicker emojione={settings} onChange={this.handleChange} search={true} />
|
||||
</DropdownContent>
|
||||
</Dropdown>
|
||||
);
|
||||
|
||||
@@ -17,7 +17,7 @@ const NavigationBar = React.createClass({
|
||||
render () {
|
||||
return (
|
||||
<div className='navigation-bar'>
|
||||
<Permalink href={this.props.account.get('url')} to={`/accounts/${this.props.account.get('id')}`} style={{ textDecoration: 'none' }}><Avatar src={this.props.account.get('avatar')} size={40} /></Permalink>
|
||||
<Permalink href={this.props.account.get('url')} to={`/accounts/${this.props.account.get('id')}`} style={{ textDecoration: 'none' }}><Avatar src={this.props.account.get('avatar')} animate size={40} /></Permalink>
|
||||
|
||||
<div style={{ flex: '1 1 auto', marginLeft: '8px' }}>
|
||||
<strong style={{ fontWeight: '500', display: 'block' }}>{this.props.account.get('acct')}</strong>
|
||||
|
||||
@@ -50,7 +50,7 @@ const ReplyIndicator = React.createClass({
|
||||
<div style={{ float: 'right', lineHeight: '24px' }}><IconButton title={intl.formatMessage(messages.cancel)} icon='times' onClick={this.handleClick} /></div>
|
||||
|
||||
<a href={status.getIn(['account', 'url'])} onClick={this.handleAccountClick} className='reply-indicator__display-name' style={{ display: 'block', maxWidth: '100%', paddingRight: '25px', textDecoration: 'none', overflow: 'hidden', lineHeight: '24px' }}>
|
||||
<div style={{ float: 'left', marginRight: '5px' }}><Avatar size={24} src={status.getIn(['account', 'avatar'])} /></div>
|
||||
<div style={{ float: 'left', marginRight: '5px' }}><Avatar size={24} src={status.getIn(['account', 'avatar'])} staticSrc={status.getIn(['account', 'avatar_static'])} /></div>
|
||||
<DisplayName account={status.get('account')} />
|
||||
</a>
|
||||
</div>
|
||||
|
||||
@@ -12,7 +12,7 @@ import SearchResultsContainer from './containers/search_results_container';
|
||||
|
||||
const messages = defineMessages({
|
||||
start: { id: 'getting_started.heading', defaultMessage: 'Getting started' },
|
||||
public: { id: 'navigation_bar.public_timeline', defaultMessage: 'Whole Known Network' },
|
||||
public: { id: 'navigation_bar.public_timeline', defaultMessage: 'Federated timeline' },
|
||||
community: { id: 'navigation_bar.community_timeline', defaultMessage: 'Local timeline' },
|
||||
preferences: { id: 'navigation_bar.preferences', defaultMessage: 'Preferences' },
|
||||
logout: { id: 'navigation_bar.logout', defaultMessage: 'Logout' }
|
||||
|
||||
@@ -33,7 +33,7 @@ const AccountAuthorize = ({ intl, account, onAuthorize, onReject }) => {
|
||||
<div>
|
||||
<div style={outerStyle}>
|
||||
<Permalink href={account.get('url')} to={`/accounts/${account.get('id')}`} className='detailed-status__display-name' style={{ display: 'block', overflow: 'hidden', marginBottom: '15px' }}>
|
||||
<div style={{ float: 'left', marginRight: '10px' }}><Avatar src={account.get('avatar')} size={48} /></div>
|
||||
<div style={{ float: 'left', marginRight: '10px' }}><Avatar src={account.get('avatar')} staticSrc={account.get('avatar_static')} size={48} /></div>
|
||||
<DisplayName account={account} />
|
||||
</Permalink>
|
||||
|
||||
|
||||
@@ -7,11 +7,11 @@ import ImmutablePropTypes from 'react-immutable-proptypes';
|
||||
|
||||
const messages = defineMessages({
|
||||
heading: { id: 'getting_started.heading', defaultMessage: 'Getting started' },
|
||||
public_timeline: { id: 'navigation_bar.public_timeline', defaultMessage: 'Whole Known Network' },
|
||||
public_timeline: { id: 'navigation_bar.public_timeline', defaultMessage: 'Federated timeline' },
|
||||
community_timeline: { id: 'navigation_bar.community_timeline', defaultMessage: 'Local timeline' },
|
||||
preferences: { id: 'navigation_bar.preferences', defaultMessage: 'Preferences' },
|
||||
follow_requests: { id: 'navigation_bar.follow_requests', defaultMessage: 'Follow requests' },
|
||||
sign_out: { id: 'navigation_bar.logout', defaultMessage: 'Sign out' },
|
||||
sign_out: { id: 'navigation_bar.logout', defaultMessage: 'Logout' },
|
||||
favourites: { id: 'navigation_bar.favourites', defaultMessage: 'Favourites' },
|
||||
blocks: { id: 'navigation_bar.blocks', defaultMessage: 'Blocked users' },
|
||||
info: { id: 'navigation_bar.info', defaultMessage: 'Extended information' }
|
||||
@@ -43,7 +43,7 @@ const GettingStarted = ({ intl, me }) => {
|
||||
|
||||
<div className='scrollable optionally-scrollable' style={{ display: 'flex', flexDirection: 'column' }}>
|
||||
<div className='static-content getting-started'>
|
||||
<p><FormattedMessage id='getting_started.open_source_notice' defaultMessage='Mastodon is open source software. You can contribute or report issues on GitHub at {github}. {apps}.' values={{ github: <a href="https://github.com/tootsuite/mastodon" target="_blank">tootsuite/mastodon</a>, apps: <a href="https://github.com/tootsuite/mastodon/blob/master/docs/Using-Mastodon/Apps.md" target="_blank"><FormattedMessage id='getting_started.apps' defaultMessage='Various apps are available' /></a> }} /></p>
|
||||
<p><FormattedMessage id='getting_started.open_source_notice' defaultMessage='Mastodon is open source software. You can contribute or report issues on GitHub at {github}. {apps}.' values={{ github: <a href="https://github.com/tootsuite/mastodon" target="_blank">tootsuite/mastodon</a>, apps: <a href="https://github.com/tootsuite/documentation/blob/master/Using-Mastodon/Apps.md" target="_blank"><FormattedMessage id='getting_started.apps' defaultMessage='Various apps are available' /></a> }} /></p>
|
||||
</div>
|
||||
</div>
|
||||
</Column>
|
||||
|
||||
@@ -4,16 +4,6 @@ const messages = defineMessages({
|
||||
clear: { id: 'notifications.clear', defaultMessage: 'Clear notifications' }
|
||||
});
|
||||
|
||||
const iconStyle = {
|
||||
fontSize: '16px',
|
||||
padding: '15px',
|
||||
position: 'absolute',
|
||||
right: '48px',
|
||||
top: '0',
|
||||
cursor: 'pointer',
|
||||
zIndex: '2'
|
||||
};
|
||||
|
||||
const ClearColumnButton = React.createClass({
|
||||
|
||||
propTypes: {
|
||||
@@ -25,7 +15,7 @@ const ClearColumnButton = React.createClass({
|
||||
const { intl } = this.props;
|
||||
|
||||
return (
|
||||
<div title={intl.formatMessage(messages.clear)} className='column-icon' tabIndex='0' style={iconStyle} onClick={this.props.onClick}>
|
||||
<div title={intl.formatMessage(messages.clear)} className='column-icon column-icon-clear' tabIndex='0' onClick={this.props.onClick}>
|
||||
<i className='fa fa-eraser' />
|
||||
</div>
|
||||
);
|
||||
|
||||
@@ -27,8 +27,9 @@ const ColumnSettings = React.createClass({
|
||||
|
||||
propTypes: {
|
||||
settings: ImmutablePropTypes.map.isRequired,
|
||||
intl: React.PropTypes.object.isRequired,
|
||||
onChange: React.PropTypes.func.isRequired,
|
||||
onSave: React.PropTypes.func.isRequired
|
||||
onSave: React.PropTypes.func.isRequired,
|
||||
},
|
||||
|
||||
mixins: [PureRenderMixin],
|
||||
|
||||
@@ -21,7 +21,7 @@ const Notification = React.createClass({
|
||||
|
||||
renderFollow (account, link) {
|
||||
return (
|
||||
<div className='notification'>
|
||||
<div className='notification notification-follow'>
|
||||
<div className='notification__message'>
|
||||
<div style={{ position: 'absolute', 'left': '-26px'}}>
|
||||
<i className='fa fa-fw fa-user-plus' />
|
||||
@@ -41,7 +41,7 @@ const Notification = React.createClass({
|
||||
|
||||
renderFavourite (notification, link) {
|
||||
return (
|
||||
<div className='notification'>
|
||||
<div className='notification notification-favourite'>
|
||||
<div className='notification__message'>
|
||||
<div style={{ position: 'absolute', 'left': '-26px'}}>
|
||||
<i className='fa fa-fw fa-star' style={{ color: '#ca8f04' }} />
|
||||
@@ -57,7 +57,7 @@ const Notification = React.createClass({
|
||||
|
||||
renderReblog (notification, link) {
|
||||
return (
|
||||
<div className='notification'>
|
||||
<div className='notification notification-reblog'>
|
||||
<div className='notification__message'>
|
||||
<div style={{ position: 'absolute', 'left': '-26px'}}>
|
||||
<i className='fa fa-fw fa-retweet' />
|
||||
@@ -76,17 +76,17 @@ const Notification = React.createClass({
|
||||
const account = notification.get('account');
|
||||
const displayName = account.get('display_name').length > 0 ? account.get('display_name') : account.get('username');
|
||||
const displayNameHTML = { __html: emojify(escapeTextContentForBrowser(displayName)) };
|
||||
const link = <Permalink className='notification__display-name' style={linkStyle} href={account.get('url')} to={`/accounts/${account.get('id')}`} dangerouslySetInnerHTML={displayNameHTML} />;
|
||||
const link = <Permalink className='notification__display-name' style={linkStyle} href={account.get('url')} title={account.get('acct')} to={`/accounts/${account.get('id')}`} dangerouslySetInnerHTML={displayNameHTML} />;
|
||||
|
||||
switch(notification.get('type')) {
|
||||
case 'follow':
|
||||
return this.renderFollow(account, link);
|
||||
case 'mention':
|
||||
return this.renderMention(notification);
|
||||
case 'favourite':
|
||||
return this.renderFavourite(notification, link);
|
||||
case 'reblog':
|
||||
return this.renderReblog(notification, link);
|
||||
case 'follow':
|
||||
return this.renderFollow(account, link);
|
||||
case 'mention':
|
||||
return this.renderMention(notification);
|
||||
case 'favourite':
|
||||
return this.renderFavourite(notification, link);
|
||||
case 'reblog':
|
||||
return this.renderReblog(notification, link);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -14,7 +14,7 @@ import ColumnBackButtonSlim from '../../components/column_back_button_slim';
|
||||
import createStream from '../../stream';
|
||||
|
||||
const messages = defineMessages({
|
||||
title: { id: 'column.public', defaultMessage: 'Whole Known Network' }
|
||||
title: { id: 'column.public', defaultMessage: 'Federated timeline' }
|
||||
});
|
||||
|
||||
const mapStateToProps = state => ({
|
||||
|
||||
@@ -47,7 +47,7 @@ const Report = React.createClass({
|
||||
propTypes: {
|
||||
isSubmitting: React.PropTypes.bool,
|
||||
account: ImmutablePropTypes.map,
|
||||
statusIds: ImmutablePropTypes.list.isRequired,
|
||||
statusIds: ImmutablePropTypes.orderedSet.isRequired,
|
||||
comment: React.PropTypes.string.isRequired,
|
||||
dispatch: React.PropTypes.func.isRequired,
|
||||
intl: React.PropTypes.object.isRequired
|
||||
@@ -94,7 +94,8 @@ const Report = React.createClass({
|
||||
return (
|
||||
<Column heading={intl.formatMessage(messages.heading)} icon='flag'>
|
||||
<ColumnBackButtonSlim />
|
||||
<div className='report' style={{ display: 'flex', flexDirection: 'column', maxHeight: '100%', boxSizing: 'border-box' }}>
|
||||
|
||||
<div className='report scrollable' style={{ display: 'flex', flexDirection: 'column', maxHeight: '100%', boxSizing: 'border-box' }}>
|
||||
<div className='report__target' style={{ flex: '0 0 auto', padding: '10px' }}>
|
||||
<FormattedMessage id='report.target' defaultMessage='Reporting' />
|
||||
<strong>{account.get('acct')}</strong>
|
||||
@@ -106,7 +107,7 @@ const Report = React.createClass({
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div style={{ flex: '0 0 160px', padding: '10px' }}>
|
||||
<div style={{ flex: '0 0 100px', padding: '10px' }}>
|
||||
<textarea
|
||||
className='report__textarea'
|
||||
placeholder={intl.formatMessage(messages.placeholder)}
|
||||
|
||||
@@ -37,8 +37,8 @@ const ActionBar = React.createClass({
|
||||
this.props.onReply(this.props.status);
|
||||
},
|
||||
|
||||
handleReblogClick () {
|
||||
this.props.onReblog(this.props.status);
|
||||
handleReblogClick (e) {
|
||||
this.props.onReblog(this.props.status, e);
|
||||
},
|
||||
|
||||
handleFavouriteClick () {
|
||||
|
||||
@@ -17,7 +17,8 @@ const DetailedStatus = React.createClass({
|
||||
|
||||
propTypes: {
|
||||
status: ImmutablePropTypes.map.isRequired,
|
||||
onOpenMedia: React.PropTypes.func.isRequired
|
||||
onOpenMedia: React.PropTypes.func.isRequired,
|
||||
onOpenVideo: React.PropTypes.func.isRequired,
|
||||
},
|
||||
|
||||
mixins: [PureRenderMixin],
|
||||
@@ -39,7 +40,7 @@ const DetailedStatus = React.createClass({
|
||||
|
||||
if (status.get('media_attachments').size > 0) {
|
||||
if (status.getIn(['media_attachments', 0, 'type']) === 'video') {
|
||||
media = <VideoPlayer sensitive={status.get('sensitive')} media={status.getIn(['media_attachments', 0])} width={300} height={150} autoplay />;
|
||||
media = <VideoPlayer sensitive={status.get('sensitive')} media={status.getIn(['media_attachments', 0])} width={300} height={150} onOpenVideo={this.props.onOpenVideo} autoplay />;
|
||||
} else {
|
||||
media = <MediaGallery sensitive={status.get('sensitive')} media={status.get('media_attachments')} height={300} onOpenMedia={this.props.onOpenMedia} />;
|
||||
}
|
||||
@@ -54,7 +55,7 @@ const DetailedStatus = React.createClass({
|
||||
return (
|
||||
<div style={{ padding: '14px 10px' }} className='detailed-status'>
|
||||
<a href={status.getIn(['account', 'url'])} onClick={this.handleAccountClick} className='detailed-status__display-name' style={{ display: 'block', overflow: 'hidden', marginBottom: '15px' }}>
|
||||
<div style={{ float: 'left', marginRight: '10px' }}><Avatar src={status.getIn(['account', 'avatar'])} size={48} /></div>
|
||||
<div style={{ float: 'left', marginRight: '10px' }}><Avatar src={status.getIn(['account', 'avatar'])} staticSrc={status.getIn(['account', 'avatar_static'])} size={48} /></div>
|
||||
<DisplayName account={status.get('account')} />
|
||||
</a>
|
||||
|
||||
|
||||
@@ -38,7 +38,8 @@ const makeMapStateToProps = () => {
|
||||
status: getStatus(state, Number(props.params.statusId)),
|
||||
ancestorsIds: state.getIn(['timelines', 'ancestors', Number(props.params.statusId)]),
|
||||
descendantsIds: state.getIn(['timelines', 'descendants', Number(props.params.statusId)]),
|
||||
me: state.getIn(['meta', 'me'])
|
||||
me: state.getIn(['meta', 'me']),
|
||||
boostModal: state.getIn(['meta', 'boost_modal'])
|
||||
});
|
||||
|
||||
return mapStateToProps;
|
||||
@@ -55,7 +56,8 @@ const Status = React.createClass({
|
||||
status: ImmutablePropTypes.map,
|
||||
ancestorsIds: ImmutablePropTypes.list,
|
||||
descendantsIds: ImmutablePropTypes.list,
|
||||
me: React.PropTypes.number
|
||||
me: React.PropTypes.number,
|
||||
boostModal: React.PropTypes.bool
|
||||
},
|
||||
|
||||
mixins: [PureRenderMixin],
|
||||
@@ -82,11 +84,19 @@ const Status = React.createClass({
|
||||
this.props.dispatch(replyCompose(status, this.context.router));
|
||||
},
|
||||
|
||||
handleReblogClick (status) {
|
||||
handleModalReblog (status) {
|
||||
this.props.dispatch(reblog(status));
|
||||
},
|
||||
|
||||
handleReblogClick (status, e) {
|
||||
if (status.get('reblogged')) {
|
||||
this.props.dispatch(unreblog(status));
|
||||
} else {
|
||||
this.props.dispatch(reblog(status));
|
||||
if (e.shiftKey || !this.props.boostModal) {
|
||||
this.handleModalReblog(status);
|
||||
} else {
|
||||
this.props.dispatch(openModal('BOOST', { status, onReblog: this.handleModalReblog }));
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
@@ -102,6 +112,10 @@ const Status = React.createClass({
|
||||
this.props.dispatch(openModal('MEDIA', { media, index }));
|
||||
},
|
||||
|
||||
handleOpenVideo (media, time) {
|
||||
this.props.dispatch(openModal('VIDEO', { media, time }));
|
||||
},
|
||||
|
||||
handleReport (status) {
|
||||
this.props.dispatch(initReport(status.get('account'), status));
|
||||
},
|
||||
@@ -141,7 +155,7 @@ const Status = React.createClass({
|
||||
<div className='scrollable'>
|
||||
{ancestors}
|
||||
|
||||
<DetailedStatus status={status} me={me} onOpenMedia={this.handleOpenMedia} />
|
||||
<DetailedStatus status={status} me={me} onOpenVideo={this.handleOpenVideo} onOpenMedia={this.handleOpenMedia} />
|
||||
<ActionBar status={status} me={me} onReply={this.handleReplyClick} onFavourite={this.handleFavouriteClick} onReblog={this.handleReblogClick} onDelete={this.handleDeleteClick} onMention={this.handleMentionClick} onReport={this.handleReport} />
|
||||
|
||||
{descendants}
|
||||
|
||||
@@ -0,0 +1,77 @@
|
||||
import PureRenderMixin from 'react-addons-pure-render-mixin';
|
||||
import ImmutablePropTypes from 'react-immutable-proptypes';
|
||||
import { defineMessages, injectIntl, FormattedMessage } from 'react-intl';
|
||||
import IconButton from '../../../components/icon_button';
|
||||
import Button from '../../../components/button';
|
||||
import StatusContent from '../../../components/status_content';
|
||||
import Avatar from '../../../components/avatar';
|
||||
import RelativeTimestamp from '../../../components/relative_timestamp';
|
||||
import DisplayName from '../../../components/display_name';
|
||||
|
||||
const messages = defineMessages({
|
||||
reblog: { id: 'status.reblog', defaultMessage: 'Boost' }
|
||||
});
|
||||
|
||||
const BoostModal = React.createClass({
|
||||
contextTypes: {
|
||||
router: React.PropTypes.object
|
||||
},
|
||||
|
||||
propTypes: {
|
||||
status: ImmutablePropTypes.map.isRequired,
|
||||
onReblog: React.PropTypes.func.isRequired,
|
||||
onClose: React.PropTypes.func.isRequired,
|
||||
intl: React.PropTypes.object.isRequired
|
||||
},
|
||||
|
||||
mixins: [PureRenderMixin],
|
||||
|
||||
handleReblog() {
|
||||
this.props.onReblog(this.props.status);
|
||||
this.props.onClose();
|
||||
},
|
||||
|
||||
handleAccountClick (e) {
|
||||
if (e.button === 0) {
|
||||
e.preventDefault();
|
||||
this.props.onClose();
|
||||
this.context.router.push(`/accounts/${this.props.status.getIn(['account', 'id'])}`);
|
||||
}
|
||||
},
|
||||
|
||||
render () {
|
||||
const { status, intl, onClose } = this.props;
|
||||
|
||||
return (
|
||||
<div className='modal-root__modal boost-modal'>
|
||||
<div className='boost-modal__container'>
|
||||
<div className='status light'>
|
||||
<div style={{ fontSize: '15px' }}>
|
||||
<div style={{ float: 'right', fontSize: '14px' }}>
|
||||
<a href={status.get('url')} className='status__relative-time' target='_blank' rel='noopener'><RelativeTimestamp timestamp={status.get('created_at')} /></a>
|
||||
</div>
|
||||
|
||||
<a onClick={this.handleAccountClick} href={status.getIn(['account', 'url'])} className='status__display-name' style={{ display: 'block', maxWidth: '100%', paddingRight: '25px' }}>
|
||||
<div className='status__avatar' style={{ position: 'absolute', left: '10px', top: '10px', width: '48px', height: '48px' }}>
|
||||
<Avatar src={status.getIn(['account', 'avatar'])} staticSrc={status.getIn(['account', 'avatar_static'])} size={48} />
|
||||
</div>
|
||||
|
||||
<DisplayName account={status.get('account')} />
|
||||
</a>
|
||||
</div>
|
||||
|
||||
<StatusContent status={status} />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className='boost-modal__action-bar'>
|
||||
<div><FormattedMessage id='boost_modal.combo' defaultMessage='You can press {combo} to skip this next time' values={{ combo: <span>Shift + <i className='fa fa-retweet' /></span> }} /></div>
|
||||
<Button text={intl.formatMessage(messages.reblog)} onClick={this.handleReblog} />
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
});
|
||||
|
||||
export default injectIntl(BoostModal);
|
||||
@@ -41,8 +41,11 @@ const Column = React.createClass({
|
||||
mixins: [PureRenderMixin],
|
||||
|
||||
handleHeaderClick () {
|
||||
let node = ReactDOM.findDOMNode(this);
|
||||
this._interruptScrollAnimation = scrollTop(node.querySelector('.scrollable'));
|
||||
const scrollable = ReactDOM.findDOMNode(this).querySelector('.scrollable');
|
||||
if (!scrollable) {
|
||||
return;
|
||||
}
|
||||
this._interruptScrollAnimation = scrollTop(scrollable);
|
||||
},
|
||||
|
||||
handleWheel () {
|
||||
|
||||
@@ -111,7 +111,7 @@ const MediaModal = React.createClass({
|
||||
if (attachment.get('type') === 'image') {
|
||||
content = <ImageLoader src={url} imgProps={{ style: { display: 'block' } }} />;
|
||||
} else if (attachment.get('type') === 'gifv') {
|
||||
content = <ExtendedVideoPlayer src={url} />;
|
||||
content = <ExtendedVideoPlayer src={url} muted={true} controls={false} />;
|
||||
}
|
||||
|
||||
return (
|
||||
|
||||
@@ -1,9 +1,13 @@
|
||||
import PureRenderMixin from 'react-addons-pure-render-mixin';
|
||||
import MediaModal from './media_modal';
|
||||
import VideoModal from './video_modal';
|
||||
import BoostModal from './boost_modal';
|
||||
import { TransitionMotion, spring } from 'react-motion';
|
||||
|
||||
const MODAL_COMPONENTS = {
|
||||
'MEDIA': MediaModal
|
||||
'MEDIA': MediaModal,
|
||||
'VIDEO': VideoModal,
|
||||
'BOOST': BoostModal
|
||||
};
|
||||
|
||||
const ModalRoot = React.createClass({
|
||||
|
||||
@@ -0,0 +1,47 @@
|
||||
import LoadingIndicator from '../../../components/loading_indicator';
|
||||
import PureRenderMixin from 'react-addons-pure-render-mixin';
|
||||
import ImmutablePropTypes from 'react-immutable-proptypes';
|
||||
import ExtendedVideoPlayer from '../../../components/extended_video_player';
|
||||
import { defineMessages, injectIntl } from 'react-intl';
|
||||
import IconButton from '../../../components/icon_button';
|
||||
|
||||
const messages = defineMessages({
|
||||
close: { id: 'lightbox.close', defaultMessage: 'Close' }
|
||||
});
|
||||
|
||||
const closeStyle = {
|
||||
position: 'absolute',
|
||||
zIndex: '100',
|
||||
top: '4px',
|
||||
right: '4px'
|
||||
};
|
||||
|
||||
const VideoModal = React.createClass({
|
||||
|
||||
propTypes: {
|
||||
media: ImmutablePropTypes.map.isRequired,
|
||||
time: React.PropTypes.number,
|
||||
onClose: React.PropTypes.func.isRequired,
|
||||
intl: React.PropTypes.object.isRequired
|
||||
},
|
||||
|
||||
mixins: [PureRenderMixin],
|
||||
|
||||
render () {
|
||||
const { media, intl, time, onClose } = this.props;
|
||||
|
||||
const url = media.get('url');
|
||||
|
||||
return (
|
||||
<div className='modal-root__modal media-modal'>
|
||||
<div>
|
||||
<div style={closeStyle}><IconButton title={intl.formatMessage(messages.close)} icon='times' overlay onClick={onClose} /></div>
|
||||
<ExtendedVideoPlayer src={url} muted={false} controls={true} time={time} />
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
});
|
||||
|
||||
export default injectIntl(VideoModal);
|
||||
@@ -47,7 +47,7 @@ const UI = React.createClass({
|
||||
this.dragTargets.push(e.target);
|
||||
}
|
||||
|
||||
if (e.dataTransfer && e.dataTransfer.files.length > 0) {
|
||||
if (e.dataTransfer && e.dataTransfer.items.length > 0) {
|
||||
this.setState({ draggingOver: true });
|
||||
}
|
||||
},
|
||||
|
||||
33
app/assets/javascripts/components/link_header.jsx
Normal file
33
app/assets/javascripts/components/link_header.jsx
Normal file
@@ -0,0 +1,33 @@
|
||||
import Link from 'http-link-header';
|
||||
import querystring from 'querystring';
|
||||
|
||||
Link.parseAttrs = (link, parts) => {
|
||||
let match = null
|
||||
let attr = ''
|
||||
let value = ''
|
||||
let attrs = ''
|
||||
|
||||
let uriAttrs = /<(.*)>;\s*(.*)/gi.exec(parts)
|
||||
|
||||
if(uriAttrs) {
|
||||
attrs = uriAttrs[2]
|
||||
link = Link.parseParams(link, uriAttrs[1])
|
||||
}
|
||||
|
||||
while(match = Link.attrPattern.exec(attrs)) {
|
||||
attr = match[1].toLowerCase()
|
||||
value = match[4] || match[3] || match[2]
|
||||
|
||||
if( /\*$/.test(attr)) {
|
||||
Link.setAttr(link, attr, Link.parseExtendedValue(value))
|
||||
} else if(/%/.test(value)) {
|
||||
Link.setAttr(link, attr, querystring.decode(value))
|
||||
} else {
|
||||
Link.setAttr(link, attr, value)
|
||||
}
|
||||
}
|
||||
|
||||
return link
|
||||
};
|
||||
|
||||
export default Link;
|
||||
68
app/assets/javascripts/components/locales/bg.jsx
Normal file
68
app/assets/javascripts/components/locales/bg.jsx
Normal file
@@ -0,0 +1,68 @@
|
||||
const bg = {
|
||||
"column_back_button.label": "Назад",
|
||||
"lightbox.close": "Затвори",
|
||||
"loading_indicator.label": "Зареждане...",
|
||||
"status.mention": "Споменаване",
|
||||
"status.delete": "Изтриване",
|
||||
"status.reply": "Отговор",
|
||||
"status.reblog": "Споделяне",
|
||||
"status.favourite": "Предпочитани",
|
||||
"status.reblogged_by": "{name} сподели",
|
||||
"status.sensitive_warning": "Деликатно съдържание",
|
||||
"status.sensitive_toggle": "Покажи",
|
||||
"video_player.toggle_sound": "Звук",
|
||||
"account.mention": "Споменаване",
|
||||
"account.edit_profile": "Редактирай профила си",
|
||||
"account.unblock": "Не блокирай",
|
||||
"account.unfollow": "Не следвай",
|
||||
"account.block": "Блокирай",
|
||||
"account.follow": "Последвай",
|
||||
"account.posts": "Публикации",
|
||||
"account.follows": "Следвам",
|
||||
"account.followers": "Последователи",
|
||||
"account.follows_you": "Твой последовател",
|
||||
"account.requested": "В очакване на одобрение",
|
||||
"getting_started.heading": "Първи стъпки",
|
||||
"getting_started.about_addressing": "Можеш да последваш потребител, ако знаеш потребителското му име и домейна, на който се намира, като в полето за търсене ги въведеш по този начин: име@домейн",
|
||||
"getting_started.about_shortcuts": "Ако с търсения потребител се намирате на един и същ домейн, достатъчно е да въведеш само името. Същото важи и за споменаване на хора в публикации.",
|
||||
"getting_started.about_developer": "Можеш да потърсиш разработчика на този проект като: Gargron@mastodon.social",
|
||||
"getting_started.open_source_notice": "Mastodon е софтуер с отворен код. Можеш да помогнеш или да докладваш за проблеми в Github: {github}.",
|
||||
"column.home": "Начало",
|
||||
"column.mentions": "Споменавания",
|
||||
"column.public": "Публичен канал",
|
||||
"column.notifications": "Известия",
|
||||
"tabs_bar.compose": "Съставяне",
|
||||
"tabs_bar.home": "Начало",
|
||||
"tabs_bar.mentions": "Споменавания",
|
||||
"tabs_bar.public": "Публичен канал",
|
||||
"tabs_bar.notifications": "Известия",
|
||||
"compose_form.placeholder": "Какво си мислиш?",
|
||||
"compose_form.publish": "Раздумай",
|
||||
"compose_form.sensitive": "Отбележи съдържанието като деликатно",
|
||||
"compose_form.spoiler": "Скрий текста зад предупреждение",
|
||||
"compose_form.private": "Отбележи като поверително",
|
||||
"compose_form.privacy_disclaimer": "Поверителни публикации ще бъдат изпратени до споменатите потребители на {domains}. Доверяваш ли се на {domainsCount, plural, one {that server} other {those servers}}, че няма да издаде твоята публикация?",
|
||||
"compose_form.unlisted": "Не показвай в публичния канал",
|
||||
"navigation_bar.edit_profile": "Редактирай профил",
|
||||
"navigation_bar.preferences": "Предпочитания",
|
||||
"navigation_bar.public_timeline": "Публичен канал",
|
||||
"navigation_bar.logout": "Излизане",
|
||||
"reply_indicator.cancel": "Отказ",
|
||||
"search.placeholder": "Търсене",
|
||||
"search.account": "Акаунт",
|
||||
"search.hashtag": "Хаштаг",
|
||||
"upload_button.label": "Добави медия",
|
||||
"upload_form.undo": "Отмяна",
|
||||
"notification.follow": "{name} те последва",
|
||||
"notification.favourite": "{name} хареса твоята публикация",
|
||||
"notification.reblog": "{name} сподели твоята публикация",
|
||||
"notification.mention": "{name} те спомена",
|
||||
"notifications.column_settings.alert": "Десктоп известия",
|
||||
"notifications.column_settings.show": "Покажи в колона",
|
||||
"notifications.column_settings.follow": "Нови последователи:",
|
||||
"notifications.column_settings.favourite": "Предпочитани:",
|
||||
"notifications.column_settings.mention": "Споменавания:",
|
||||
"notifications.column_settings.reblog": "Споделяния:",
|
||||
};
|
||||
|
||||
export default bg;
|
||||
@@ -1,4 +1,4 @@
|
||||
const en = {
|
||||
const de = {
|
||||
"column_back_button.label": "Zurück",
|
||||
"lightbox.close": "Schließen",
|
||||
"loading_indicator.label": "Lade…",
|
||||
@@ -74,4 +74,4 @@ const en = {
|
||||
"missing_indicator.label": "Nicht gefunden"
|
||||
};
|
||||
|
||||
export default en;
|
||||
export default de;
|
||||
|
||||
@@ -1,72 +1,131 @@
|
||||
/**
|
||||
* Note for Contributors:
|
||||
* This file (en.jsx) serve as a template for other languages.
|
||||
* To make other contributors' life easier, please REMEMBER:
|
||||
* 1. to add your new string here; and
|
||||
* 2. to remove old strings that are no longer needed; and
|
||||
* 3. to sort the strings by the key.
|
||||
* 4. To rename the `en` const name and export default name to match your locale.
|
||||
* Thanks!
|
||||
*/
|
||||
const en = {
|
||||
"column_back_button.label": "Back",
|
||||
"lightbox.close": "Close",
|
||||
"loading_indicator.label": "Loading...",
|
||||
"status.mention": "Mention @{name}",
|
||||
"status.delete": "Delete",
|
||||
"status.reply": "Reply",
|
||||
"status.reblog": "Boost",
|
||||
"status.favourite": "Favourite",
|
||||
"status.reblogged_by": "{name} boosted",
|
||||
"status.sensitive_warning": "Sensitive content",
|
||||
"status.sensitive_toggle": "Click to view",
|
||||
"status.show_more": "Show more",
|
||||
"status.show_less": "Show less",
|
||||
"status.open": "Expand this status",
|
||||
"status.report": "Report @{name}",
|
||||
"video_player.toggle_sound": "Toggle sound",
|
||||
"account.mention": "Mention @{name}",
|
||||
"account.edit_profile": "Edit profile",
|
||||
"account.unblock": "Unblock @{name}",
|
||||
"account.unfollow": "Unfollow",
|
||||
"account.block": "Block @{name}",
|
||||
"account.disclaimer": "This user is from another instance. This number may be larger.",
|
||||
"account.edit_profile": "Edit profile",
|
||||
"account.follow": "Follow",
|
||||
"account.posts": "Posts",
|
||||
"account.follows": "Follows",
|
||||
"account.followers": "Followers",
|
||||
"account.follows_you": "Follows you",
|
||||
"account.follows": "Follows",
|
||||
"account.mention": "Mention @{name}",
|
||||
"account.mute": "Mute @{name}",
|
||||
"account.posts": "Posts",
|
||||
"account.report": "Report @{name}",
|
||||
"account.requested": "Awaiting approval",
|
||||
"getting_started.heading": "Getting started",
|
||||
"getting_started.about_addressing": "You can follow people if you know their username and the domain they are on by entering an e-mail-esque address into the search form.",
|
||||
"getting_started.about_shortcuts": "If the target user is on the same domain as you, just the username will work. The same rule applies to mentioning people in statuses.",
|
||||
"getting_started.open_source_notice": "Mastodon is open source software. You can contribute or report issues on GitHub at {github}. {apps}.",
|
||||
"column.home": "Home",
|
||||
"account.unblock": "Unblock @{name}",
|
||||
"account.unfollow": "Unfollow",
|
||||
"account.unmute": "Unmute @{name}",
|
||||
"boost_modal.combo": "You can press {combo} to skip this next time",
|
||||
"column_back_button.label": "Back",
|
||||
"column.blocks": "Blocked users",
|
||||
"column.community": "Local timeline",
|
||||
"column.public": "Federated timeline",
|
||||
"column.favourites": "Favourites",
|
||||
"column.follow_requests": "Follow requests",
|
||||
"column.home": "Home",
|
||||
"column.notifications": "Notifications",
|
||||
"tabs_bar.compose": "Compose",
|
||||
"tabs_bar.home": "Home",
|
||||
"tabs_bar.mentions": "Mentions",
|
||||
"tabs_bar.public": "Federated timeline",
|
||||
"tabs_bar.notifications": "Notifications",
|
||||
"column.public": "Federated timeline",
|
||||
"compose_form.placeholder": "What is on your mind?",
|
||||
"compose_form.privacy_disclaimer": "Your private status will be delivered to mentioned users on {domains}. Do you trust {domainsCount, plural, one {that server} other {those servers}}? Post privacy only works on Mastodon instances. If {domains} {domainsCount, plural, one {is not a Mastodon instance} other {are not Mastodon instances}}, there will be no indication that your post is private, and it may be boosted or otherwise made visible to unintended recipients.",
|
||||
"compose_form.publish": "Toot",
|
||||
"compose_form.sensitive": "Mark media as sensitive",
|
||||
"compose_form.spoiler_placeholder": "Content warning",
|
||||
"compose_form.spoiler": "Hide text behind warning",
|
||||
"compose_form.private": "Mark as private",
|
||||
"compose_form.privacy_disclaimer": "Your private status will be delivered to mentioned users on {domains}. Do you trust {domainsCount, plural, one {that server} other {those servers}}? Post privacy only works on Mastodon instances. If {domains} {domainsCount, plural, one {is not a Mastodon instance} other {are not Mastodon instances}}, there will be no indication that your post is private, and it may be boosted or otherwise made visible to unintended recipients.",
|
||||
"compose_form.unlisted": "Do not display on public timelines",
|
||||
"navigation_bar.edit_profile": "Edit profile",
|
||||
"navigation_bar.preferences": "Preferences",
|
||||
"emoji_button.label": "Insert emoji",
|
||||
"empty_column.community": "The local timeline is empty. Write something publicly to get the ball rolling!",
|
||||
"empty_column.hashtag": "There is nothing in this hashtag yet.",
|
||||
"empty_column.home.public_timeline": "the public timeline",
|
||||
"empty_column.home": "You aren't following anyone yet. Visit {public} or use search to get started and meet other users.",
|
||||
"empty_column.notifications": "You don't have any notifications yet. Interact with others to start the conversation.",
|
||||
"empty_column.public": "There is nothing here! Write something publicly, or manually follow users from other instances to fill it up",
|
||||
"follow_request.authorize": "Authorize",
|
||||
"follow_request.reject": "Reject",
|
||||
"getting_started.apps": "Various apps are available",
|
||||
"getting_started.heading": "Getting started",
|
||||
"getting_started.open_source_notice": "Mastodon is open source software. You can contribute or report issues on GitHub at {github}. {apps}.",
|
||||
"home.column_settings.advanced": "Advanced",
|
||||
"home.column_settings.basic": "Basic",
|
||||
"home.column_settings.filter_regex": "Filter out by regular expressions",
|
||||
"home.column_settings.show_reblogs": "Show boosts",
|
||||
"home.column_settings.show_replies": "Show replies",
|
||||
"home.settings": "Column settings",
|
||||
"lightbox.close": "Close",
|
||||
"loading_indicator.label": "Loading...",
|
||||
"media_gallery.toggle_visible": "Toggle visibility",
|
||||
"missing_indicator.label": "Not found",
|
||||
"navigation_bar.blocks": "Blocked users",
|
||||
"navigation_bar.community_timeline": "Local timeline",
|
||||
"navigation_bar.public_timeline": "Federated timeline",
|
||||
"navigation_bar.edit_profile": "Edit profile",
|
||||
"navigation_bar.favourites": "Favourites",
|
||||
"navigation_bar.follow_requests": "Follow requests",
|
||||
"navigation_bar.info": "Extended information",
|
||||
"navigation_bar.logout": "Logout",
|
||||
"reply_indicator.cancel": "Cancel",
|
||||
"search.placeholder": "Search",
|
||||
"search.account": "Account",
|
||||
"search.hashtag": "Hashtag",
|
||||
"upload_button.label": "Add media",
|
||||
"upload_form.undo": "Undo",
|
||||
"notification.follow": "{name} followed you",
|
||||
"navigation_bar.preferences": "Preferences",
|
||||
"navigation_bar.public_timeline": "Federated timeline",
|
||||
"notification.favourite": "{name} favourited your status",
|
||||
"notification.follow": "{name} followed you",
|
||||
"notification.reblog": "{name} boosted your status",
|
||||
"notification.mention": "{name} mentioned you",
|
||||
"notifications.clear_confirmation": "Are you sure you want to clear all your notifications?",
|
||||
"notifications.clear": "Clear notifications",
|
||||
"notifications.column_settings.alert": "Desktop notifications",
|
||||
"notifications.column_settings.show": "Show in column",
|
||||
"notifications.column_settings.follow": "New followers:",
|
||||
"notifications.column_settings.favourite": "Favourites:",
|
||||
"notifications.column_settings.follow": "New followers:",
|
||||
"notifications.column_settings.mention": "Mentions:",
|
||||
"notifications.column_settings.reblog": "Boosts:",
|
||||
"notifications.column_settings.show": "Show in column",
|
||||
"notifications.column_settings.sound": "Play sound",
|
||||
"notifications.settings": "Column settings",
|
||||
"privacy.change": "Adjust status privacy",
|
||||
"privacy.direct.long": "Post to mentioned users only",
|
||||
"privacy.direct.short": "Direct",
|
||||
"privacy.private.long": "Post to followers only",
|
||||
"privacy.private.short": "Private",
|
||||
"privacy.public.long": "Post to public timelines",
|
||||
"privacy.public.short": "Public",
|
||||
"privacy.unlisted.long": "Do not show in public timelines",
|
||||
"privacy.unlisted.short": "Unlisted",
|
||||
"reply_indicator.cancel": "Cancel",
|
||||
"report.heading": "New report",
|
||||
"report.placeholder": "Additional comments",
|
||||
"report.submit": "Submit",
|
||||
"report.target": "Reporting",
|
||||
"search_results.total": "{count} {count, plural, one {result} other {results}}",
|
||||
"search.placeholder": "Search",
|
||||
"search.status_by": "Status by {name}",
|
||||
"status.delete": "Delete",
|
||||
"status.favourite": "Favourite",
|
||||
"status.load_more": "Load more",
|
||||
"status.media_hidden": "Media hidden",
|
||||
"status.mention": "Mention @{name}",
|
||||
"status.open": "Expand this status",
|
||||
"status.reblog": "Boost",
|
||||
"status.reblogged_by": "{name} boosted",
|
||||
"status.reply": "Reply",
|
||||
"status.report": "Report @{name}",
|
||||
"status.sensitive_toggle": "Click to view",
|
||||
"status.sensitive_warning": "Sensitive content",
|
||||
"status.show_less": "Show less",
|
||||
"status.show_more": "Show more",
|
||||
"tabs_bar.compose": "Compose",
|
||||
"tabs_bar.federated_timeline": "Federated",
|
||||
"tabs_bar.home": "Home",
|
||||
"tabs_bar.local_timeline": "Local",
|
||||
"tabs_bar.notifications": "Notifications",
|
||||
"upload_area.title": "Drag & drop to upload",
|
||||
"upload_button.label": "Add media",
|
||||
"upload_form.undo": "Undo",
|
||||
"upload_progress.label": "Uploading...",
|
||||
"video_player.toggle_sound": "Toggle sound",
|
||||
"video_player.toggle_visible": "Toggle visibility",
|
||||
"video_player.expand": "Expand video",
|
||||
};
|
||||
|
||||
export default en;
|
||||
|
||||
@@ -5,28 +5,35 @@ const es = {
|
||||
"status.mention": "Mencionar",
|
||||
"status.delete": "Borrar",
|
||||
"status.reply": "Responder",
|
||||
"status.reblog": "Republicar",
|
||||
"status.reblog": "Retoot",
|
||||
"status.favourite": "Favorito",
|
||||
"status.reblogged_by": "{name} republicado",
|
||||
"status.reblogged_by": "Retooteado por {name}",
|
||||
"status.sensitive_warning": "Contenido sensible",
|
||||
"status.sensitive_toggle": "Click para ver",
|
||||
"status.show_more": "Mostrar más",
|
||||
"status.show_less": "Mostrar menos",
|
||||
"status.open": "Expandir estado",
|
||||
"status.report": "Reportar",
|
||||
"video_player.toggle_sound": "Act/Desac. sonido",
|
||||
"account.mention": "Mención",
|
||||
"account.mention": "Mencionar",
|
||||
"account.edit_profile": "Editar perfil",
|
||||
"account.unblock": "Desbloquear",
|
||||
"account.unfollow": "Dejar de seguir",
|
||||
"account.mute": "Silenciar",
|
||||
"account.block": "Bloquear",
|
||||
"account.follow": "Seguir",
|
||||
"account.block": "Bloquear",
|
||||
"account.posts": "Publicaciones",
|
||||
"account.follows": "Seguir",
|
||||
"account.followers": "Seguidores",
|
||||
"account.follows_you": "Te sigue",
|
||||
"account.requested": "Esperando aprobación",
|
||||
"getting_started.heading": "Primeros pasos",
|
||||
"getting_started.about_addressing": "Puedes seguir a gente si conoces su nombre de usuario y el dominio en el que están registrados, introduciendo algo similar a una dirección de correo electrónico en el formulario en la parte superior de la barra lateral.",
|
||||
"getting_started.about_shortcuts": "Si el usuario que buscas está en el mismo dominio que tú, simplemente funcionará introduciendo el nombre de usuario. La misma regla se aplica para mencionar a usuarios.",
|
||||
"getting_started.about_developer": "Puedes seguir al desarrollador de este proyecto en Gargron@mastodon.social",
|
||||
"getting_started.open_source_notice": "Mastodon es software libre. Puedes contribuir o reportar errores en {github}. {apps}.",
|
||||
"column.home": "Inicio",
|
||||
"column.mentions": "Menciones",
|
||||
"column.public": "Historia pública",
|
||||
"column.community": "Historia local",
|
||||
"column.public": "Historia federada",
|
||||
"column.notifications": "Notificaciones",
|
||||
"tabs_bar.compose": "Redactar",
|
||||
"tabs_bar.home": "Inicio",
|
||||
@@ -34,23 +41,47 @@ const es = {
|
||||
"tabs_bar.public": "Público",
|
||||
"tabs_bar.notifications": "Notificaciones",
|
||||
"compose_form.placeholder": "¿En qué estás pensando?",
|
||||
"compose_form.publish": "Publicar",
|
||||
"compose_form.sensitive": "Marcar el contenido como sensible",
|
||||
"compose_form.unlisted": "Privado",
|
||||
"compose_form.publish": "Tootear",
|
||||
"compose_form.sensitive": "Marcar contenido como sensible",
|
||||
"compose_form.spoiler": "Ocultar texto tras advertencia",
|
||||
"compose_form.spoiler_placeholder": "Advertencia de contenido",
|
||||
"composer_form.private": "Marcar como privado",
|
||||
"composer_form.privacy_disclaimer": "Tu estado se mostrará a los usuarios mencionados en {domains}. Tu estado podrá ser visto en otras instancias, quizás no quieras que tu estado sea visto por otros usuarios.",
|
||||
"compose_form.unlisted": "No mostrar en la historia federada",
|
||||
"navigation_bar.edit_profile": "Editar perfil",
|
||||
"navigation_bar.preferences": "Preferencias",
|
||||
"navigation_bar.public_timeline": "Público",
|
||||
"navigation_bar.community_timeline": "Historia local",
|
||||
"navigation_bar.public_timeline": "Historia federada",
|
||||
"navigation_bar.favourites": "Favoritos",
|
||||
"navigation_bar.blocks": "Usuarios bloqueados",
|
||||
"navigation_bar.info": "Información adicional",
|
||||
"navigation_bar.logout": "Cerrar sesión",
|
||||
"reply_indicator.cancel": "Cancelar",
|
||||
"search.placeholder": "Buscar",
|
||||
"search.account": "Cuenta",
|
||||
"search.hashtag": "Etiqueta",
|
||||
"upload_button.label": "Añadir medio",
|
||||
"upload_button.label": "Subir multimedia",
|
||||
"upload_form.undo": "Deshacer",
|
||||
"notification.follow": "{name} le esta ahora siguiendo",
|
||||
"notification.favourite": "{name} marcó como favorito su estado",
|
||||
"notification.reblog": "{name} volvió a publicar su estado",
|
||||
"notification.mention": "Fue mencionado por {name}"
|
||||
"notification.follow": "{name} te empezó a seguir",
|
||||
"notification.favourite": "{name} marcó tu estado como favorito",
|
||||
"notification.reblog": "{name} ha retooteado tu estado",
|
||||
"notification.mention": "{name} te ha mencionado",
|
||||
"notifications.column_settings.alert": "Notificaciones de escritorio",
|
||||
"notifications.column_settings.show": "Mostrar en columna",
|
||||
"notifications.column_settings.follow": "Nuevos seguidores:",
|
||||
"notifications.column_settings.favourite": "Favoritos:",
|
||||
"notifications.column_settings.mention": "Menciones:",
|
||||
"notifications.column_settings.reblog": "Retoots:",
|
||||
"emoji_button.label": "Insertar emoji",
|
||||
"privacy.public.short": "Público",
|
||||
"privacy.public.long": "Mostrar en la historia federada",
|
||||
"privacy.unlisted.short": "Sin federar",
|
||||
"privacy.unlisted.long": "No mostrar en la historia federada",
|
||||
"privacy.private.short": "Privado",
|
||||
"privacy.private.long": "Sólo mostrar a seguidores",
|
||||
"privacy.direct.short": "Directo",
|
||||
"privacy.direct.long": "Sólo mostrar a los usuarios mencionados",
|
||||
"privacy.change": "Ajustar privacidad"
|
||||
};
|
||||
|
||||
export default es;
|
||||
|
||||
@@ -12,10 +12,12 @@ const fr = {
|
||||
"status.sensitive_toggle": "Cliquer pour dévoiler",
|
||||
"status.show_more": "Déplier",
|
||||
"status.show_less": "Replier",
|
||||
"status.open": "Déplier ce status",
|
||||
"status.open": "Déplier ce statut",
|
||||
"status.report": "Signaler @{name}",
|
||||
"status.load_more": "Charger plus",
|
||||
"status.media_hidden": "Média caché",
|
||||
"video_player.toggle_sound": "Mettre/Couper le son",
|
||||
"video_player.toggle_visible": "Afficher/Cacher la vidéo",
|
||||
"account.mention": "Mentionner",
|
||||
"account.edit_profile": "Modifier le profil",
|
||||
"account.unblock": "Débloquer",
|
||||
@@ -32,7 +34,7 @@ const fr = {
|
||||
"account.report": "Signaler",
|
||||
"account.disclaimer": "Ce compte est situé sur une autre instance. Les nombres peuvent être plus grands.",
|
||||
"getting_started.heading": "Pour commencer",
|
||||
"getting_started.about_addressing": "Vous pouvez suivre les statuts de quelqu’un en entrant dans le champs de recherche leur identifiant et le domaine de leur instance, séparés par un @ à la manière d’une adresse courriel.",
|
||||
"getting_started.about_addressing": "Vous pouvez suivre les statuts de quelqu’un en entrant dans le champ de recherche leur identifiant et le domaine de leur instance, séparés par un @ à la manière d’une adresse courriel.",
|
||||
"getting_started.about_shortcuts": "Si cette personne utilise la même instance que vous, l’identifiant suffit. C’est le même principe pour mentionner quelqu’un dans vos statuts.",
|
||||
"getting_started.about_developer": "Pour suivre le développeur de ce projet, c’est Gargron@mastodon.social",
|
||||
"getting_started.open_source_notice": "Mastodon est un logiciel libre. Vous pouvez contribuer et envoyer vos commentaires et rapports de bogues via {github} sur GitHub.",
|
||||
@@ -42,16 +44,25 @@ const fr = {
|
||||
"column.notifications": "Notifications",
|
||||
"column.blocks": "Utilisateurs bloqués",
|
||||
"column.favourites": "Favoris",
|
||||
"column.follow_requests": "Demandes de suivi",
|
||||
"empty_column.notifications": "Vous n’avez pas encore de notification. Interagissez avec d’autres utilisateurs⋅trices pour débuter la conversation.",
|
||||
"empty_column.public": "Il n'y a rien ici ! Écrivez quelque chose publiquement, ou bien suivez manuellement des utilisateurs d'autres instances pour remplir le fil public.",
|
||||
"empty_column.home": "Vous ne suivez encore personne. Visitez {public} ou bien utilisez la recherche pour vous connecter à d'autres utilisateurs.",
|
||||
"empty_column.home.public_timeline": "le fil public",
|
||||
"empty_column.community": "Le fil public local est vide. Écrivez-donc quelque chose pour le remplir !",
|
||||
"empty_column.hashtag": "Il n'y a encore aucun contenu relatif à ce hashtag",
|
||||
"tabs_bar.compose": "Composer",
|
||||
"tabs_bar.home": "Accueil",
|
||||
"tabs_bar.mentions": "Mentions",
|
||||
"tabs_bar.public": "Fil public global",
|
||||
"tabs_bar.notifications": "Notifications",
|
||||
"tabs_bar.local_timeline": "Fil public local",
|
||||
"tabs_bar.federated_timeline": "Fil public global",
|
||||
"compose_form.placeholder": "Qu’avez-vous en tête ?",
|
||||
"compose_form.publish": "Pouet",
|
||||
"compose_form.sensitive": "Marquer le média comme délicat",
|
||||
"compose_form.spoiler": "Masquer le texte derrière un avertissement",
|
||||
"compose_form.spoiler_placeholder": "Avertissement",
|
||||
"compose_form.private": "Rendre privé",
|
||||
"compose_form.privacy_disclaimer": "Votre statut privé va être transmis aux personnes mentionnées sur {domains}. Avez-vous confiance en {domainsCount, plural, one {ce serveur} other {ces serveurs}} pour ne pas divulguer votre statut ? Les statuts privés ne fonctionnent que sur les instances de Mastodons. Si {domains} {domainsCount, plural, one {n'est pas une instance de Mastodon} other {ne sont pas des instances de Mastodon}}, il n'y aura aucune indication que votre statut est privé, et il pourrait être partagé ou rendu visible d'une autre manière à d'autres personnes imprévues",
|
||||
"compose_form.unlisted": "Ne pas afficher dans les fils publics",
|
||||
@@ -64,23 +75,31 @@ const fr = {
|
||||
"navigation_bar.favourites": "Favoris",
|
||||
"navigation_bar.info": "Plus d'informations",
|
||||
"navigation_bar.logout": "Déconnexion",
|
||||
"navigation_bar.follow_requests": "Demandes de suivi",
|
||||
"reply_indicator.cancel": "Annuler",
|
||||
"search.placeholder": "Chercher",
|
||||
"search.placeholder": "Rechercher",
|
||||
"search.account": "Compte",
|
||||
"search.hashtag": "Mot-clé",
|
||||
"search_results.total": "{count} {count, plural, one {résultat} other {résultats}}",
|
||||
"search.status_by": "Statuts de {name}",
|
||||
"upload_button.label": "Joindre un média",
|
||||
"upload_form.undo": "Annuler",
|
||||
"upload_progress.label": "Envoi en cours…",
|
||||
"upload_area.title": "Glissez et déposez pour envoyer",
|
||||
"notification.follow": "{name} vous suit.",
|
||||
"notification.favourite": "{name} a ajouté à ses favoris :",
|
||||
"notification.reblog": "{name} a partagé votre statut :",
|
||||
"notification.mention": "{name} vous a mentionné⋅e :",
|
||||
"notifications.column_settings.alert": "Notifications locales",
|
||||
"notifications.column_settings.show": "Afficher dans la colonne",
|
||||
"notifications.column_settings.sound": "Émettre un son",
|
||||
"notifications.column_settings.follow": "Nouveaux abonnés :",
|
||||
"notifications.column_settings.favourite": "Favoris :",
|
||||
"notifications.column_settings.mention": "Mentions :",
|
||||
"notifications.column_settings.reblog": "Partages :",
|
||||
"notifications.clear": "Nettoyer",
|
||||
"notifications.clear_confirmation": "Voulez-vous vraiment nettoyer toutes vos notifications ?",
|
||||
"notifications.settings": "Paramètres de la colonne",
|
||||
"privacy.public.short": "Public",
|
||||
"privacy.public.long": "Afficher dans les fils publics",
|
||||
"privacy.unlisted.short": "Non-listé",
|
||||
@@ -88,8 +107,22 @@ const fr = {
|
||||
"privacy.private.short": "Privé",
|
||||
"privacy.private.long": "N’afficher que pour vos abonné⋅e⋅s",
|
||||
"privacy.direct.short": "Direct",
|
||||
"privacy.direct.long": "N’afficher que pour les personnes mentionné⋅e⋅s",
|
||||
"privacy.direct.long": "N’afficher que pour les personnes mentionnées",
|
||||
"privacy.change": "Ajuster la confidentialité du message",
|
||||
"media_gallery.toggle_visible": "Modifier la visibilité",
|
||||
"missing_indicator.label": "Non trouvé",
|
||||
"follow_request.authorize": "Autoriser",
|
||||
"follow_request.reject": "Rejeter",
|
||||
"home.settings": "Paramètres de la colonne",
|
||||
"home.column_settings.basic": "Basique",
|
||||
"home.column_settings.show_reblogs": "Afficher les partages",
|
||||
"home.column_settings.show_replies": "Afficher les réponses",
|
||||
"home.column_settings.advanced": "Avancé",
|
||||
"home.column_settings.filter_regex": "Filtrer avec une expression rationnelle",
|
||||
"report.heading": "Nouveau signalement",
|
||||
"report.placeholder": "Commentaires additionnels",
|
||||
"report.submit": "Envoyer",
|
||||
"report.target": "Signalement"
|
||||
};
|
||||
|
||||
export default fr;
|
||||
|
||||
@@ -3,10 +3,16 @@ import de from './de';
|
||||
import es from './es';
|
||||
import hu from './hu';
|
||||
import fr from './fr';
|
||||
import nl from './nl';
|
||||
import no from './no';
|
||||
import pt from './pt';
|
||||
import uk from './uk';
|
||||
import fi from './fi';
|
||||
import eo from './eo';
|
||||
import ru from './ru';
|
||||
import ja from './ja';
|
||||
import zh_hk from './zh-hk';
|
||||
import bg from './bg';
|
||||
|
||||
const locales = {
|
||||
en,
|
||||
@@ -14,10 +20,16 @@ const locales = {
|
||||
es,
|
||||
hu,
|
||||
fr,
|
||||
nl,
|
||||
no,
|
||||
pt,
|
||||
uk,
|
||||
fi,
|
||||
eo
|
||||
eo,
|
||||
ru,
|
||||
ja,
|
||||
'zh-HK': zh_hk,
|
||||
bg,
|
||||
};
|
||||
|
||||
export default function getMessagesForLocale (locale) {
|
||||
|
||||
119
app/assets/javascripts/components/locales/ja.jsx
Normal file
119
app/assets/javascripts/components/locales/ja.jsx
Normal file
@@ -0,0 +1,119 @@
|
||||
const ja = {
|
||||
"column_back_button.label": "戻る",
|
||||
"lightbox.close": "閉じる",
|
||||
"loading_indicator.label": "読み込み中...",
|
||||
"status.mention": "@{name} さんへの返信",
|
||||
"status.delete": "削除",
|
||||
"status.reply": "返信",
|
||||
"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.edit_profile": "プロフィールを編集",
|
||||
"account.unblock": "@{name} さんのブロックを解除",
|
||||
"account.unfollow": "フォロー解除",
|
||||
"account.block": "@{name} さんをブロック",
|
||||
"account.mute": "ミュート",
|
||||
"account.unmute": "ミュート解除",
|
||||
"account.follow": "フォロー",
|
||||
"account.report": "@{name}を通報する",
|
||||
"account.posts": "投稿",
|
||||
"account.follows": "フォロー",
|
||||
"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}",
|
||||
"column.home": "ホーム",
|
||||
"column.community": "ローカルタイムライン",
|
||||
"column.public": "連合タイムライン",
|
||||
"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.publish": "トゥート",
|
||||
"compose_form.sensitive": "メディアを不適切なコンテンツとしてマークする",
|
||||
"compose_form.spoiler": "テキストを隠す",
|
||||
"compose_form.spoiler_placeholder": "内容注意メッセージ",
|
||||
"compose_form.private": "非公開にする",
|
||||
"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.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": "投稿のプライバシーを変更2",
|
||||
"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}からの投稿",
|
||||
"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_timeline": "連合タイムライン",
|
||||
"empty_column.notifications": "まだ通知がありません。他の人とふれ合って会話を始めましょう。",
|
||||
"empty_column.public": "ここにはまだ何もありません!公開で何かを投稿したり、他のインスタンスのユーザーをフォローしたりしていっぱいにしましょう!",
|
||||
"empty_column.hashtag": "このハッシュタグはまだ使っていません。",
|
||||
"upload_progress.label": "アップロード中…",
|
||||
"emoji_button.label": "絵文字を追加",
|
||||
"home.column_settings.basic": "シンプル",
|
||||
"home.column_settings.advanced": "エキスパート",
|
||||
"home.column_settings.show_reblogs": "ブースト表示",
|
||||
"home.column_settings.show_replies": "返信表示",
|
||||
"home.column_settings.filter_regex": "正規表現でフィルター",
|
||||
"home.settings": "カラム設定",
|
||||
"notification.settings": "カラム設定",
|
||||
"missing_indicator.label": "見つかりません",
|
||||
"boost_modal.combo": "次は{combo}を押せば、これをスキップできます。"
|
||||
};
|
||||
|
||||
export default ja;
|
||||
68
app/assets/javascripts/components/locales/nl.jsx
Normal file
68
app/assets/javascripts/components/locales/nl.jsx
Normal file
@@ -0,0 +1,68 @@
|
||||
const nl = {
|
||||
"column_back_button.label": "terug",
|
||||
"lightbox.close": "Sluiten",
|
||||
"loading_indicator.label": "Laden...",
|
||||
"status.mention": "Vermeld @{name}",
|
||||
"status.delete": "Verwijder",
|
||||
"status.reply": "Reageer",
|
||||
"status.reblog": "Boost",
|
||||
"status.favourite": "Favoriet",
|
||||
"status.reblogged_by": "{name} boostte",
|
||||
"status.sensitive_warning": "Gevoelige inhoud",
|
||||
"status.sensitive_toggle": "Klik om te zien",
|
||||
"video_player.toggle_sound": "Geluid omschakelen",
|
||||
"account.mention": "Vermeld @{name}",
|
||||
"account.edit_profile": "Bewerk profiel",
|
||||
"account.unblock": "Deblokkeer @{name}",
|
||||
"account.unfollow": "Ontvolg",
|
||||
"account.block": "Blokkeer @{name}",
|
||||
"account.follow": "Volg",
|
||||
"account.posts": "Berichten",
|
||||
"account.follows": "Volgt",
|
||||
"account.followers": "Volgers",
|
||||
"account.follows_you": "Volgt jou",
|
||||
"account.requested": "Wacht op goedkeuring",
|
||||
"getting_started.heading": "Beginnen",
|
||||
"getting_started.about_addressing": "Je kunt mensen volgen als je hun gebruikersnaam en het domein van hun server kent, door het e-mailachtige adres in het zoekscherm in te voeren.",
|
||||
"getting_started.about_shortcuts": "Als de gezochte gebruiker op hetzelfde domein zit als jijzelf, is invoeren van de gebruikersnaam genoeg. Dat geldt ook als je mensen in de statussen wilt vermelden.",
|
||||
"getting_started.open_source_notice": "Mastodon is open source software. Je kunt bijdragen of problemen melden op GitHub via {github}. {apps}.",
|
||||
"column.home": "Thuis",
|
||||
"column.community": "Lokale tijdlijn",
|
||||
"column.public": "Federatietijdlijn",
|
||||
"column.notifications": "Meldingen",
|
||||
"tabs_bar.compose": "Schrijven",
|
||||
"tabs_bar.home": "Thuis",
|
||||
"tabs_bar.mentions": "Vermeldingen",
|
||||
"tabs_bar.public": "Federatietijdlijn",
|
||||
"tabs_bar.notifications": "Meldingen",
|
||||
"compose_form.placeholder": "Waar ben je mee bezig?",
|
||||
"compose_form.publish": "Toot",
|
||||
"compose_form.sensitive": "Markeer media als gevoelig",
|
||||
"compose_form.spoiler": "Verberg tekst achter waarschuwing",
|
||||
"compose_form.private": "Mark als priv<69>",
|
||||
"compose_form.privacy_disclaimer": "Je besloten status wordt afgeleverd aan vermelde gebruikers op {domains}. Vertrouw je {domainsCount, plural, one {that server} andere {those servers}}? Priv<69> plaatsen werkt alleen op Mastodon servers. Als {domains} {domainsCount, plural, een {is not a Mastodon instance} andere {are not Mastodon instances}}, dan wordt er geen indicatie gegeven dat he bericht besloten is, waardoor het kan worden geboost of op andere manier zichtbaar worden voor niet bedoelde lezers.",
|
||||
"compose_form.unlisted": "Niet tonen op openbare tijdlijnen",
|
||||
"navigation_bar.edit_profile": "Bewerk profiel",
|
||||
"navigation_bar.preferences": "Voorkeuren",
|
||||
"navigation_bar.community_timeline": "Lokale tijdlijn",
|
||||
"navigation_bar.public_timeline": "Federatietijdlijn",
|
||||
"navigation_bar.logout": "Uitloggen",
|
||||
"reply_indicator.cancel": "Annuleren",
|
||||
"search.placeholder": "Zoeken",
|
||||
"search.account": "Account",
|
||||
"search.hashtag": "Hashtag",
|
||||
"upload_button.label": "Toevoegen media",
|
||||
"upload_form.undo": "Ongedaan maken",
|
||||
"notification.follow": "{name} volgde jou",
|
||||
"notification.favourite": "{name} markeerde je status als favoriet",
|
||||
"notification.reblog": "{name} boostte je status",
|
||||
"notification.mention": "{name} vermeldde jou",
|
||||
"notifications.column_settings.alert": "Desktopmeldingen",
|
||||
"notifications.column_settings.show": "Tonen in kolom",
|
||||
"notifications.column_settings.follow": "Nieuwe volgers:",
|
||||
"notifications.column_settings.favourite": "Favoriten:",
|
||||
"notifications.column_settings.mention": "Vermeldingen:",
|
||||
"notifications.column_settings.reblog": "Boosts:",
|
||||
};
|
||||
|
||||
export default nl;
|
||||
77
app/assets/javascripts/components/locales/no.jsx
Normal file
77
app/assets/javascripts/components/locales/no.jsx
Normal file
@@ -0,0 +1,77 @@
|
||||
const no = {
|
||||
"column_back_button.label": "Tilbake",
|
||||
"lightbox.close": "Lukk",
|
||||
"loading_indicator.label": "Laster...",
|
||||
"status.mention": "Nevn @{name}",
|
||||
"status.delete": "Slett",
|
||||
"status.reply": "Svar",
|
||||
"status.reblog": "Reblogg",
|
||||
"status.favourite": "Lik",
|
||||
"status.reblogged_by": "{name} reblogget",
|
||||
"status.sensitive_warning": "Sensitivt innhold",
|
||||
"status.sensitive_toggle": "Klikk for å vise",
|
||||
"status.show_more": "Vis mer",
|
||||
"status.show_less": "Vis mindre",
|
||||
"status.open": "Utvid denne statusen",
|
||||
"status.report": "Rapporter @{name}",
|
||||
"video_player.toggle_sound": "Veksle lyd",
|
||||
"account.mention": "Nevn @{name}",
|
||||
"account.edit_profile": "Rediger profil",
|
||||
"account.unblock": "Avblokker @{name}",
|
||||
"account.unfollow": "Avfølg",
|
||||
"account.block": "Blokker @{name}",
|
||||
"account.follow": "Følg",
|
||||
"account.posts": "Poster",
|
||||
"account.follows": "Følginger",
|
||||
"account.followers": "Følgere",
|
||||
"account.follows_you": "Folger deg",
|
||||
"account.requested": "Venter på godkjennelse",
|
||||
"getting_started.heading": "Kom i gang",
|
||||
"getting_started.about_addressing": "Du kan følge noen hvis du vet brukernavnet deres og domenet de er på ved å skrive en e-postadresse inn i søkeskjemaet.",
|
||||
"getting_started.about_shortcuts": "Hvis målbrukeren er på samme domene som deg, vil kun brukernavnet også fungere. Den samme regelen gjelder når man nevner noen i statuser.",
|
||||
"getting_started.open_source_notice": "Mastodon er programvare med fri kildekode. Du kan bidra eller rapportere problemer på GitHub på {github}. {apps}.",
|
||||
"column.home": "Hjem",
|
||||
"column.community": "Lokal tidslinje",
|
||||
"column.public": "Forent tidslinje",
|
||||
"column.notifications": "Varslinger",
|
||||
"column.blocks": "Blokkerte brukere",
|
||||
"column.favourites": "Likt",
|
||||
"tabs_bar.compose": "Komponer",
|
||||
"tabs_bar.home": "Hjem",
|
||||
"tabs_bar.mentions": "Nevninger",
|
||||
"tabs_bar.public": "Forent tidslinje",
|
||||
"tabs_bar.notifications": "Varslinger",
|
||||
"compose_form.placeholder": "Hva har du på hjertet?",
|
||||
"compose_form.publish": "Tut",
|
||||
"compose_form.sensitive": "Merk media som følsomt",
|
||||
"compose_form.spoiler": "Skjul tekst bak advarsel",
|
||||
"compose_form.private": "Merk som privat",
|
||||
"compose_form.privacy_disclaimer": "Din private status vil leveres til nevnte brukere på {domains}. Stoler du på {domainsCount, plural, one {den serveren} other {de serverne}}? Synlighet fungerer kun på Mastodon-instanser. Hvis {domains} {domainsCount, plural, one {ike er en Mastodon-instans} other {ikke er Mastodon-instanser}}, vil det ikke indikeres at posten din er privat, og den kan kanskje bli reblogget eller på annen måte bli synlig for uventede mottakere.",
|
||||
"compose_form.unlisted": "Ikke vis på offentlige tidslinjer",
|
||||
"navigation_bar.edit_profile": "Rediger profil",
|
||||
"navigation_bar.preferences": "Preferanser",
|
||||
"navigation_bar.community_timeline": "Lokal tidslinje",
|
||||
"navigation_bar.public_timeline": "Forent tidslinje",
|
||||
"navigation_bar.logout": "Logg ut",
|
||||
"navigation_bar.blocks": "Blokkerte brukere",
|
||||
"navigation_bar.info": "Utvidet informasjon",
|
||||
"navigation_bar.favourites": "Likt",
|
||||
"reply_indicator.cancel": "Avbryt",
|
||||
"search.placeholder": "Søk",
|
||||
"search.account": "Konto",
|
||||
"search.hashtag": "Hashtag",
|
||||
"upload_button.label": "Legg til media",
|
||||
"upload_form.undo": "Angre",
|
||||
"notification.follow": "{name} fulgte deg",
|
||||
"notification.favourite": "{name} likte din status",
|
||||
"notification.reblog": "{name} reblogget din status",
|
||||
"notification.mention": "{name} nevnte deg",
|
||||
"notifications.column_settings.alert": "Skrivebordsvarslinger",
|
||||
"notifications.column_settings.show": "Vis i kolonne",
|
||||
"notifications.column_settings.follow": "Nye følgere:",
|
||||
"notifications.column_settings.favourite": "Likt:",
|
||||
"notifications.column_settings.mention": "Nevninger:",
|
||||
"notifications.column_settings.reblog": "Reblogginger:",
|
||||
};
|
||||
|
||||
export default no;
|
||||
@@ -14,59 +14,115 @@ const pt = {
|
||||
"status.show_less": "Mostrar menos",
|
||||
"status.open": "Expandir",
|
||||
"status.report": "Reportar @{name}",
|
||||
"status.load_more": "Carregar mais",
|
||||
"status.media_hidden": "Media escondida",
|
||||
"video_player.toggle_sound": "Ligar/Desligar som",
|
||||
"video_player.toggle_visible": "Ligar/Desligar vídeo",
|
||||
"account.mention": "Mencionar @{name}",
|
||||
"account.edit_profile": "Editar perfil",
|
||||
"account.unblock": "Não bloquear @{name}",
|
||||
"account.unfollow": "Não seguir",
|
||||
"account.block": "Bloquear @{name}",
|
||||
"account.mute": "Mute",
|
||||
"account.unmute": "Remover Mute",
|
||||
"account.follow": "Seguir",
|
||||
"account.posts": "Posts",
|
||||
"account.follows": "Segue",
|
||||
"account.followers": "Seguidores",
|
||||
"account.follows_you": "É teu seguidor",
|
||||
"account.requested": "A aguardar aprovação",
|
||||
"account.report": "Denunciar",
|
||||
"account.disclaimer": "Essa conta está localizado em outra instância. Os nomes podem ser maiores.",
|
||||
"getting_started.heading": "Primeiros passos",
|
||||
"getting_started.about_addressing": "Podes seguir pessoas se sabes o nome de usuário deles e o domínio em que estão colocando um endereço similar a e-mail no campo no topo da barra lateral.",
|
||||
"getting_started.about_shortcuts": "Se o usuário alvo está no mesmo domínio, só o nome funcionará. A mesma regra se aplica a mencionar pessoas nas postagens.",
|
||||
"getting_started.about_developer": "Pode seguir o developer deste projecto em Gargron@mastodon.social",
|
||||
"getting_started.open_source_notice": "Mastodon é software de fonte aberta. Podes contribuir ou repostar problemas no GitHub do projecto: {github}. {apps}.",
|
||||
"column.home": "Home",
|
||||
"column.community": "Local",
|
||||
"column.public": "Público",
|
||||
"column.public": "Global",
|
||||
"column.notifications": "Notificações",
|
||||
"column.blocks": "Utilizadores Bloqueados",
|
||||
"column.favourites": "Favoritos",
|
||||
"column.follow_requests": "Seguidores Pendentes",
|
||||
"empty_column.notifications": "Não tens notificações. Interage com outros utilizadores para iniciar uma conversa.",
|
||||
"empty_column.public": "Não há nada aqui! Escreve algo publicamente ou segue outros utilizadores para ver aqui os conteúdos públicos.",
|
||||
"empty_column.home": "Ainda não segues qualquer utilizador. Visita {public} ou utiliza a pesquisa para procurar outros utilizadores.",
|
||||
"empty_column.home.public_timeline": "global",
|
||||
"empty_column.community": "Ainda não existem conteúdo local para mostrar!",
|
||||
"empty_column.hashtag": "Não existe qualquer conteúdo com essa hashtag",
|
||||
"tabs_bar.compose": "Criar",
|
||||
"tabs_bar.home": "Home",
|
||||
"tabs_bar.mentions": "Menções",
|
||||
"tabs_bar.public": "Público",
|
||||
"tabs_bar.notifications": "Notificações",
|
||||
"tabs_bar.local_timeline": "Local",
|
||||
"tabs_bar.federated_timeline": "Global",
|
||||
"compose_form.placeholder": "Em que estás a pensar?",
|
||||
"compose_form.publish": "Publicar",
|
||||
"compose_form.sensitive": "Media com conteúdo sensível",
|
||||
"compose_form.sensitive": "Marcar media como conteúdo sensível",
|
||||
"compose_form.spoiler": "Esconder texto com aviso",
|
||||
"compose_form.spoiler_placeholder": "Aviso",
|
||||
"compose_form.private": "Tornar privado",
|
||||
"compose_form.privacy_disclaimer": "O teu conteúdo privado vai ser partilhado com os utilizadores do {domains}. Confias {domainsCount, plural, one {neste servidor} other {nestes servidores}}? A privacidade só funciona em instâncias do Mastodon. Se {domains} {domainsCount, plural, one {não é uma instância} other {não são instâncias}}, não existem indicadores da privacidade da tua partilha, e podem ser partilhados com outros.",
|
||||
"compose_form.unlisted": "Não mostrar na listagem pública",
|
||||
"emoji_button.label": "Inserir Emoji",
|
||||
"navigation_bar.edit_profile": "Editar perfil",
|
||||
"navigation_bar.preferences": "Preferências",
|
||||
"navigation_bar.community_timeline": "Local",
|
||||
"navigation_bar.public_timeline": "Público",
|
||||
"navigation_bar.public_timeline": "Global",
|
||||
"navigation_bar.blocks": "Utilizadores bloqueados",
|
||||
"navigation_bar.favourites": "Favoritos",
|
||||
"navigation_bar.info": "Mais informações",
|
||||
"navigation_bar.logout": "Sair",
|
||||
"navigation_bar.follow_requests": "Seguidores pendentes",
|
||||
"reply_indicator.cancel": "Cancelar",
|
||||
"search.placeholder": "Pesquisar",
|
||||
"search.account": "Conta",
|
||||
"search.hashtag": "Hashtag",
|
||||
"search_results.total": "{count} {count, plural, one {resultado} other {resultados}}",
|
||||
"search.status_by": "Post de {name}",
|
||||
"upload_button.label": "Adicionar media",
|
||||
"upload_form.undo": "Anular",
|
||||
"upload_progress.label": "A gravar…",
|
||||
"upload_area.title": "Arraste e solte para enviar",
|
||||
"notification.follow": "{name} seguiu-te",
|
||||
"notification.favourite": "{name} adicionou o teu post aos favoritos",
|
||||
"notification.reblog": "{name} partilhou o teu post",
|
||||
"notification.mention": "{name} mencionou-te",
|
||||
"notifications.column_settings.alert": "Notificações no computador",
|
||||
"notifications.column_settings.show": "Mostrar nas colunas",
|
||||
"notifications.column_settings.sound": "Reproduzir som",
|
||||
"notifications.column_settings.follow": "Novos seguidores:",
|
||||
"notifications.column_settings.favourite": "Favoritos:",
|
||||
"notifications.column_settings.mention": "Menções:",
|
||||
"notifications.column_settings.reblog": "Partilhas:",
|
||||
"notifications.clear": "Limpar notificações",
|
||||
"notifications.clear_confirmation": "Queres mesmo limpar todas as notificações?",
|
||||
"notifications.settings": "Parâmetros da lista de Notificações",
|
||||
"privacy.public.short": "Público",
|
||||
"privacy.public.long": "Publicar em todos os feeds",
|
||||
"privacy.unlisted.short": "Não listar",
|
||||
"privacy.unlisted.long": "Não publicar nos feeds públicos",
|
||||
"privacy.private.short": "Privado",
|
||||
"privacy.private.long": "Apenas para os seguidores",
|
||||
"privacy.direct.short": "Directo",
|
||||
"privacy.direct.long": "Apenas para utilizadores mencionados",
|
||||
"privacy.change": "Ajustar a privacidade da mensagem",
|
||||
"media_gallery.toggle_visible": "Modificar a visibilidade",
|
||||
"missing_indicator.label": "Não encontrado",
|
||||
"follow_request.authorize": "Autorizar",
|
||||
"follow_request.reject": "Rejeitar",
|
||||
"home.settings": "Parâmetros da coluna Home",
|
||||
"home.column_settings.basic": "Básico",
|
||||
"home.column_settings.show_reblogs": "Mostrar as partilhas",
|
||||
"home.column_settings.show_replies": "Mostrar as respostas",
|
||||
"home.column_settings.advanced": "Avançadas",
|
||||
"home.column_settings.filter_regex": "Filtrar com uma expressão regular",
|
||||
"report.heading": "Nova denuncia",
|
||||
"report.placeholder": "Comentários adicionais",
|
||||
"report.submit": "Enviar",
|
||||
"report.target": "Denunciar"
|
||||
};
|
||||
|
||||
export default pt;
|
||||
|
||||
101
app/assets/javascripts/components/locales/ru.jsx
Normal file
101
app/assets/javascripts/components/locales/ru.jsx
Normal file
@@ -0,0 +1,101 @@
|
||||
const ru = {
|
||||
"column_back_button.label": "Назад",
|
||||
"lightbox.close": "Закрыть",
|
||||
"loading_indicator.label": "Загрузка...",
|
||||
"status.mention": "Упомянуть @{name}",
|
||||
"status.delete": "Удалить",
|
||||
"status.reply": "Ответить",
|
||||
"status.reblog": "Продвинуть",
|
||||
"status.favourite": "Нравится",
|
||||
"status.reblogged_by": "{name} продвинул(а)",
|
||||
"status.sensitive_warning": "Чувствительный контент",
|
||||
"status.sensitive_toggle": "Нажмите для просмотра",
|
||||
"status.show_more": "Развернуть",
|
||||
"status.show_less": "Свернуть",
|
||||
"status.open": "Развернуть статус",
|
||||
"status.report": "Пожаловаться",
|
||||
"status.load_more": "Показать еще",
|
||||
"video_player.toggle_sound": "Вкл./выкл. звук",
|
||||
"account.mention": "Упомянуть",
|
||||
"account.edit_profile": "Изменить профиль",
|
||||
"account.unblock": "Разблокировать",
|
||||
"account.unfollow": "Отписаться",
|
||||
"account.block": "Блокировать",
|
||||
"account.mute": "Заглушить",
|
||||
"account.follow": "Подписаться",
|
||||
"account.posts": "Посты",
|
||||
"account.follows": "Подписки",
|
||||
"account.followers": "Подписаны",
|
||||
"account.follows_you": "Подписан(а) на Вас",
|
||||
"account.requested": "Ожидает подтверждения",
|
||||
"getting_started.heading": "Добро пожаловать",
|
||||
"getting_started.about_addressing": "Вы можете подписаться на человека, зная имя пользователя и домен, на котором он находится, введя e-mail-подобный адрес в форму поиска.",
|
||||
"getting_started.about_shortcuts": "Если пользователь находится на одном с Вами домене, можно использовать только имя. То же правило применимо к упоминанию пользователей в статусах.",
|
||||
"getting_started.open_source_notice": "Mastodon - программа с открытым исходным кодом. Вы можете помочь проекту или сообщить о проблемах на GitHub по адресу {github}. {apps}.",
|
||||
"getting_started.apps": "Доступны различные приложения.",
|
||||
"column.home": "Главная",
|
||||
"column.community": "Локальная лента",
|
||||
"column.public": "Глобальная лента",
|
||||
"column.notifications": "Уведомления",
|
||||
"tabs_bar.compose": "Написать",
|
||||
"tabs_bar.home": "Главная",
|
||||
"tabs_bar.mentions": "Упоминания",
|
||||
"tabs_bar.public": "Глобальная лента",
|
||||
"tabs_bar.notifications": "Уведомления",
|
||||
"compose_form.placeholder": "О чем Вы думаете?",
|
||||
"compose_form.publish": "Трубить",
|
||||
"compose_form.sensitive": "Отметить как чувствительный контент",
|
||||
"compose_form.spoiler": "Скрыть текст за предупреждением",
|
||||
"compose_form.private": "Отметить как приватное",
|
||||
"compose_form.privacy_disclaimer": "Ваш приватный статус будет доставлен упомянутым пользователям на доменах {domains}. Доверяете ли вы {domainsCount, plural, one {этому серверу} other {этим серверам}}? Приватность постов работает только на узлах Mastodon. Если {domains} {domainsCount, plural, one {не является узлом Mastodon} other {не являются узлами Mastodon}}, приватность поста не будет указана, и он может оказаться продвинут или иным образом показан не обозначенным Вами пользователям.",
|
||||
"compose_form.unlisted": "Не отображать в публичных лентах",
|
||||
"navigation_bar.edit_profile": "Изменить профиль",
|
||||
"navigation_bar.preferences": "Опции",
|
||||
"navigation_bar.community_timeline": "Локальная лента",
|
||||
"navigation_bar.public_timeline": "Глобальная лента",
|
||||
"navigation_bar.logout": "Выйти",
|
||||
"navigation_bar.info": "Об узле",
|
||||
"navigation_bar.favourites": "Понравившееся",
|
||||
"navigation_bar.blocks": "Список блокировки",
|
||||
"reply_indicator.cancel": "Отмена",
|
||||
"search.placeholder": "Поиск",
|
||||
"search.account": "Аккаунт",
|
||||
"search.hashtag": "Хэштег",
|
||||
"upload_button.label": "Добавить медиаконтент",
|
||||
"upload_form.undo": "Отменить",
|
||||
"notification.follow": "{name} подписался(-лась) на Вас",
|
||||
"notification.favourite": "{name} понравился Ваш статус",
|
||||
"notification.reblog": "{name} продвинул(а) Ваш статус",
|
||||
"notification.mention": "{name} упомянул(а) Вас",
|
||||
"home.settings": "Настройки колонки",
|
||||
"home.column_settings.basic": "Основные",
|
||||
"home.column_settings.advanced": "Дополнительные",
|
||||
"home.column_settings.filter_regex": "Отфильтровать регулярным выражением",
|
||||
"home.column_settings.show_replies": "Показывать продвижения",
|
||||
"home.column_settings.show_replies": "Показывать ответы",
|
||||
"notifications.clear": "Очистить уведомления",
|
||||
"notifications.settings": "Настройки колонки",
|
||||
"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.notifications": "У Вас еще нет уведомлений. Заведите знакомство с другими пользователями, чтобы начать разговор.",
|
||||
"empty_column.hashtag": "Статусов с таким хэштегом еще не существует.",
|
||||
"empty_column.community": "Локальная лента пуста. Напишите что-нибудь, чтобы разогреть народ!",
|
||||
"empty_column.public": "Здесь ничего нет! Опубликуйте что-нибудь или подпишитесь на пользователей с других узлов, чтобы заполнить ленту.",
|
||||
"empty_column.home": "Пока Вы ни на кого не подписаны. Полистайте {public} или используйте поиск, чтобы освоиться и завести новые знакомства.",
|
||||
"empty_column.home.public_timeline": "публичные ленты",
|
||||
"privacy.public.short": "Публичный",
|
||||
"privacy.public.long": "Показать в публичных лентах",
|
||||
"privacy.unlisted.short": "Скрытый",
|
||||
"privacy.unlisted.long": "Не показывать в лентах",
|
||||
"privacy.private.short": "Приватный",
|
||||
"privacy.private.long": "Показать только подписчикам",
|
||||
"privacy.direct.short": "Направленный",
|
||||
"privacy.direct.long": "Показать только упомянутым",
|
||||
};
|
||||
|
||||
export default ru;
|
||||
113
app/assets/javascripts/components/locales/zh-hk.jsx
Normal file
113
app/assets/javascripts/components/locales/zh-hk.jsx
Normal file
@@ -0,0 +1,113 @@
|
||||
import zh from 'react-intl/locale-data/zh';
|
||||
|
||||
const localeData = zh.reduce(function (acc, localeData) {
|
||||
if (localeData.locale === "zh-Hant-HK") {
|
||||
// rename the locale "zh-Hant-HK" as "zh-HK"
|
||||
// (match the code usually used in Accepted-Language header)
|
||||
acc.push(Object.assign({},
|
||||
localeData,
|
||||
{
|
||||
"locale": "zh-HK",
|
||||
"parentLocale": "zh-Hant-HK",
|
||||
}
|
||||
));
|
||||
}
|
||||
return acc;
|
||||
}, []);
|
||||
|
||||
export { localeData as localeData };
|
||||
|
||||
const zh_hk = {
|
||||
"account.block": "封鎖 @{name}",
|
||||
"account.edit_profile": "修改個人資料",
|
||||
"account.follow": "關注",
|
||||
"account.followers": "關注的人",
|
||||
"account.follows_you": "關注你",
|
||||
"account.follows": "正在關注",
|
||||
"account.mention": "提及 @{name}",
|
||||
"account.posts": "文章",
|
||||
"account.requested": "等候審批",
|
||||
"account.unblock": "解除對 @{name} 的封鎖",
|
||||
"account.unfollow": "取消關注",
|
||||
"column_back_button.label": "先前顯示",
|
||||
"column.community": "本站時間軸",
|
||||
"column.home": "家",
|
||||
"column.notifications": "通知",
|
||||
"column.public": "跨站公共時間軸",
|
||||
"compose_form.placeholder": "你在想甚麼?",
|
||||
"compose_form.privacy_disclaimer": "你的私人文章,將被遞送至你所提及的 {domains} 用戶。你是否信任 {domainsCount, plural, one {這個網站} other {這些網站}}?請留意,文章私隱設定只適用於各 Mastodon 服務站,如果 {domains} {domainsCount, plural, one {不是 Mastodon 服務站} other {之中有些不是 Mastodon 服務站}},對方將無法收到這篇文章的私隱設定,然後可能被轉推給不能預知的用戶閱讀。",
|
||||
"compose_form.private": "標示為「只有關注你的人能看」",
|
||||
"compose_form.publish": "發文",
|
||||
"compose_form.sensitive": "將媒體檔案標示為「敏感內容」",
|
||||
"compose_form.spoiler": "將部份文字藏於警告訊息之後",
|
||||
"compose_form.unlisted": "請勿在公共時間軸顯示",
|
||||
"empty_column.community": "本站時間軸暫時未有內容,快貼文來搶頭香啊!",
|
||||
"empty_column.hashtag": "這個標籤暫時未有內容。",
|
||||
"empty_column.home": "你還沒有關注任何用戶。快看看{public},向其他用戶搭訕吧。",
|
||||
"empty_column.home.public_timeline": "公共時間軸",
|
||||
"empty_column.notifications": "You don't have any notifications yet. Interact with others to start the conversation.",
|
||||
"empty_column.public": "There is nothing here! Write something publicly, or manually follow users from other instances to fill it up.",
|
||||
"getting_started.about_addressing": "只要你知道一位用戶的用戶名稱和域名,你可以用「@用戶名稱@域名」的格式在搜尋欄尋找該用戶。",
|
||||
"getting_started.about_shortcuts": "只要該用戶是在你現在的服務站開立,你可以直接輸入用戶𠱷搜尋。同樣的規則適用於在文章提及別的用戶。",
|
||||
"getting_started.apps": "手機或桌面應用程式",
|
||||
"getting_started.heading": "開始使用",
|
||||
"getting_started.open_source_notice": "Mastodon 是一個開放源碼的軟件。你可以在官方 GitHub ({github}) 貢獻或者回報問題。你亦可透過{apps}閱讀 Mastodon 上的消息。",
|
||||
"home.column_settings.basic": "基本",
|
||||
"home.column_settings.show_reblogs": "顯示被轉推的文章",
|
||||
"home.column_settings.show_replies": "顯示回應文章",
|
||||
"home.column_settings.advanced": "進階",
|
||||
"lightbox.close": "關閉",
|
||||
"loading_indicator.label": "載入中...",
|
||||
"missing_indicator.label": "找不到內容",
|
||||
"navigation_bar.community_timeline": "本站時間軸",
|
||||
"navigation_bar.edit_profile": "修改個人資料",
|
||||
"navigation_bar.logout": "登出",
|
||||
"navigation_bar.preferences": "個人設定",
|
||||
"navigation_bar.public_timeline": "跨站公共時間軸",
|
||||
"notification.favourite": "{name} 喜歡你的文章",
|
||||
"notification.follow": "{name} 開始開始你",
|
||||
"notification.mention": "{name} 提及你",
|
||||
"notification.reblog": "{name} 轉推你的文章",
|
||||
"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": "播放音效",
|
||||
"reply_indicator.cancel": "取消",
|
||||
"report.target": "Reporting",
|
||||
"search.account": "用戶",
|
||||
"search.hashtag": "標籤",
|
||||
"search.placeholder": "搜尋",
|
||||
"search_results.total": "{count} 項結果",
|
||||
"search.status_by": "按用戶名稱搜尋文章",
|
||||
"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.home": "家",
|
||||
"tabs_bar.local_timeline": "本站",
|
||||
"tabs_bar.mentions": "提及",
|
||||
"tabs_bar.notifications": "通知",
|
||||
"tabs_bar.public": "跨站公共時間軸",
|
||||
"tabs_bar.federated_timeline": "跨站",
|
||||
"upload_area.title": "將檔案拖放至此上載",
|
||||
"upload_button.label": "上載媒體檔案",
|
||||
"upload_progress.label": "上載中……",
|
||||
"upload_form.undo": "還原",
|
||||
"video_player.toggle_sound": "開關音效",
|
||||
};
|
||||
|
||||
export default zh_hk;
|
||||
@@ -67,6 +67,7 @@ function clearAll(state) {
|
||||
map.set('is_submitting', false);
|
||||
map.set('in_reply_to', null);
|
||||
map.set('privacy', state.get('default_privacy'));
|
||||
map.set('sensitive', false);
|
||||
map.update('media_attachments', list => list.clear());
|
||||
});
|
||||
};
|
||||
@@ -76,7 +77,8 @@ function appendMedia(state, media) {
|
||||
map.update('media_attachments', list => list.push(media));
|
||||
map.set('is_uploading', false);
|
||||
map.set('resetFileKey', Math.floor((Math.random() * 0x10000)));
|
||||
map.update('text', oldText => `${oldText} ${media.get('text_url')}`.trim());
|
||||
map.set('focusDate', new Date());
|
||||
map.update('text', oldText => `${oldText.trim()} ${media.get('text_url')}`.trim() + ' ');
|
||||
});
|
||||
};
|
||||
|
||||
@@ -156,6 +158,9 @@ export default function compose(state = initialState, action) {
|
||||
if (action.status.get('spoiler_text').length > 0) {
|
||||
map.set('spoiler', true);
|
||||
map.set('spoiler_text', action.status.get('spoiler_text'));
|
||||
} else {
|
||||
map.set('spoiler', false);
|
||||
map.set('spoiler_text', '');
|
||||
}
|
||||
});
|
||||
case COMPOSE_REPLY_CANCEL:
|
||||
|
||||
@@ -4,7 +4,8 @@ import {
|
||||
REPORT_SUBMIT_SUCCESS,
|
||||
REPORT_SUBMIT_FAIL,
|
||||
REPORT_CANCEL,
|
||||
REPORT_STATUS_TOGGLE
|
||||
REPORT_STATUS_TOGGLE,
|
||||
REPORT_COMMENT_CHANGE
|
||||
} from '../actions/reports';
|
||||
import Immutable from 'immutable';
|
||||
|
||||
@@ -39,6 +40,8 @@ export default function reports(state = initialState, action) {
|
||||
|
||||
return set.remove(action.statusId);
|
||||
});
|
||||
case REPORT_COMMENT_CHANGE:
|
||||
return state.setIn(['new', 'comment'], action.comment);
|
||||
case REPORT_SUBMIT_REQUEST:
|
||||
return state.setIn(['new', 'isSubmitting'], true);
|
||||
case REPORT_SUBMIT_FAIL:
|
||||
|
||||
@@ -14,7 +14,7 @@
|
||||
}
|
||||
|
||||
&:after {
|
||||
background: rgba($color8, 0.5);
|
||||
background: linear-gradient(rgba($color8, 0.5), rgba($color8, 0.8));
|
||||
display: block;
|
||||
content: "";
|
||||
position: absolute;
|
||||
@@ -83,7 +83,7 @@
|
||||
.counter {
|
||||
width: 80px;
|
||||
color: $color3;
|
||||
padding: 0 10px;
|
||||
padding: 5px 10px 0px;
|
||||
margin-bottom: 10px;
|
||||
border-right: 1px solid $color3;
|
||||
cursor: default;
|
||||
@@ -148,7 +148,7 @@
|
||||
order: 1;
|
||||
}
|
||||
|
||||
@media screen and (max-width: 360px) {
|
||||
@media screen and (max-width: 480px) {
|
||||
.details {
|
||||
display: block;
|
||||
}
|
||||
@@ -173,7 +173,7 @@
|
||||
text-align: center;
|
||||
overflow: hidden;
|
||||
|
||||
a, .current, .next_page, .previous_page, .gap {
|
||||
a, .current, .page, .gap {
|
||||
font-size: 14px;
|
||||
color: $color5;
|
||||
font-weight: 500;
|
||||
@@ -193,12 +193,12 @@
|
||||
cursor: default;
|
||||
}
|
||||
|
||||
.previous_page, .next_page {
|
||||
.prev, .next {
|
||||
text-transform: uppercase;
|
||||
color: $color2;
|
||||
}
|
||||
|
||||
.previous_page {
|
||||
.prev {
|
||||
float: left;
|
||||
padding-left: 0;
|
||||
|
||||
@@ -208,7 +208,7 @@
|
||||
}
|
||||
}
|
||||
|
||||
.next_page {
|
||||
.next {
|
||||
float: right;
|
||||
padding-right: 0;
|
||||
|
||||
@@ -226,11 +226,11 @@
|
||||
@media screen and (max-width: 360px) {
|
||||
padding: 30px 20px;
|
||||
|
||||
a, .current, .next_page, .previous_page, .gap {
|
||||
a, .current, .next, .prev, .gap {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.next_page, .previous_page {
|
||||
.next, .prev {
|
||||
display: inline-block;
|
||||
}
|
||||
}
|
||||
@@ -387,6 +387,5 @@
|
||||
.account__header__content {
|
||||
font-size: 14px;
|
||||
color: $color1;
|
||||
text-shadow: 0 0 2px $color8;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4,305 +4,13 @@
|
||||
@import 'fonts/montserrat';
|
||||
@import 'font-awesome';
|
||||
|
||||
/* http://meyerweb.com/eric/tools/css/reset/
|
||||
v2.0 | 20110126
|
||||
License: none (public domain)
|
||||
*/
|
||||
|
||||
html, body, div, span, applet, object, iframe,
|
||||
h1, h2, h3, h4, h5, h6, p, blockquote, pre,
|
||||
a, abbr, acronym, address, big, cite, code,
|
||||
del, dfn, em, img, ins, kbd, q, s, samp,
|
||||
small, strike, strong, sub, sup, tt, var,
|
||||
b, u, i, center,
|
||||
dl, dt, dd, ol, ul, li,
|
||||
fieldset, form, label, legend,
|
||||
table, caption, tbody, tfoot, thead, tr, th, td,
|
||||
article, aside, canvas, details, embed,
|
||||
figure, figcaption, footer, header, hgroup,
|
||||
menu, nav, output, ruby, section, summary,
|
||||
time, mark, audio, video {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
border: 0;
|
||||
font-size: 100%;
|
||||
font: inherit;
|
||||
vertical-align: baseline;
|
||||
}
|
||||
|
||||
/* HTML5 display-role reset for older browsers */
|
||||
article, aside, details, figcaption, figure,
|
||||
footer, header, hgroup, menu, nav, section {
|
||||
display: block;
|
||||
}
|
||||
|
||||
body {
|
||||
line-height: 1;
|
||||
}
|
||||
|
||||
ol, ul {
|
||||
list-style: none;
|
||||
}
|
||||
|
||||
blockquote, q {
|
||||
quotes: none;
|
||||
}
|
||||
|
||||
blockquote:before, blockquote:after,
|
||||
q:before, q:after {
|
||||
content: '';
|
||||
content: none;
|
||||
}
|
||||
|
||||
table {
|
||||
border-collapse: collapse;
|
||||
border-spacing: 0;
|
||||
}
|
||||
|
||||
::-webkit-scrollbar {
|
||||
width: 8px;
|
||||
height: 8px;
|
||||
}
|
||||
|
||||
::-webkit-scrollbar-thumb {
|
||||
background: lighten($color1, 4%);
|
||||
border: 0px none $color5;
|
||||
border-radius: 50px;
|
||||
}
|
||||
|
||||
::-webkit-scrollbar-thumb:hover {
|
||||
background: lighten($color1, 6%);
|
||||
}
|
||||
|
||||
::-webkit-scrollbar-thumb:active {
|
||||
background: lighten($color1, 4%);
|
||||
}
|
||||
|
||||
::-webkit-scrollbar-track {
|
||||
border: 0px none $color5;
|
||||
border-radius: 0;
|
||||
background: rgba($color8, 0.1);
|
||||
}
|
||||
|
||||
::-webkit-scrollbar-track:hover {
|
||||
background: $color1;
|
||||
}
|
||||
|
||||
::-webkit-scrollbar-track:active {
|
||||
background: $color1;
|
||||
}
|
||||
|
||||
::-webkit-scrollbar-corner {
|
||||
background: transparent;
|
||||
}
|
||||
|
||||
body {
|
||||
font-family: 'Roboto', sans-serif;
|
||||
background: $color1 image-url('background-photo.jpeg');
|
||||
background-size: cover;
|
||||
background-attachment: fixed;
|
||||
font-size: 13px;
|
||||
line-height: 18px;
|
||||
font-weight: 400;
|
||||
color: $color5;
|
||||
padding-bottom: 140px;
|
||||
text-rendering: optimizelegibility;
|
||||
font-feature-settings: "kern";
|
||||
text-size-adjust: none;
|
||||
|
||||
&.app-body {
|
||||
position: fixed;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
padding: 0;
|
||||
background: $color1;
|
||||
}
|
||||
|
||||
&.embed {
|
||||
background: transparent;
|
||||
margin: 0;
|
||||
|
||||
.container {
|
||||
position: absolute;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
overflow: hidden;
|
||||
}
|
||||
}
|
||||
|
||||
&.admin {
|
||||
background: darken($color1, 4%);
|
||||
position: fixed;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
@media screen and (max-width: 360px) {
|
||||
padding-bottom: 0;
|
||||
}
|
||||
}
|
||||
|
||||
button:focus {
|
||||
outline: none;
|
||||
}
|
||||
|
||||
.app-holder {
|
||||
display: flex;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.container {
|
||||
width: 700px;
|
||||
margin: 0 auto;
|
||||
margin-top: 40px;
|
||||
|
||||
@media screen and (max-width: 700px) {
|
||||
width: 100%;
|
||||
margin: 0;
|
||||
}
|
||||
}
|
||||
|
||||
.logo-container {
|
||||
max-width: 400px;
|
||||
margin: 100px auto;
|
||||
margin-bottom: 0;
|
||||
cursor: default;
|
||||
|
||||
@media screen and (max-width: 360px) {
|
||||
margin: 30px auto;
|
||||
}
|
||||
|
||||
h1 {
|
||||
display: block;
|
||||
text-align: center;
|
||||
color: $color5;
|
||||
font-size: 48px;
|
||||
font-weight: 500;
|
||||
|
||||
img {
|
||||
display: block;
|
||||
margin: 20px auto;
|
||||
width: 180px;
|
||||
height: 180px;
|
||||
}
|
||||
|
||||
a {
|
||||
color: inherit;
|
||||
text-decoration: none;
|
||||
outline: 0;
|
||||
|
||||
img {
|
||||
opacity: 0.8;
|
||||
transition: all 0.8s ease;
|
||||
}
|
||||
|
||||
&:hover {
|
||||
img {
|
||||
opacity: 1;
|
||||
transition-duration: 0.2s;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
small {
|
||||
display: block;
|
||||
font-size: 12px;
|
||||
font-weight: 400;
|
||||
font-family: 'Roboto Mono', monospace;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.no-list {
|
||||
list-style: none;
|
||||
|
||||
li {
|
||||
display: inline-block;
|
||||
margin: 0 5px;
|
||||
}
|
||||
}
|
||||
|
||||
.footer {
|
||||
text-align: center;
|
||||
margin-top: 30px;
|
||||
font-size: 12px;
|
||||
color: darken($color2, 25%);
|
||||
|
||||
.domain {
|
||||
font-weight: 500;
|
||||
|
||||
a {
|
||||
color: inherit;
|
||||
text-decoration: none;
|
||||
}
|
||||
}
|
||||
|
||||
.powered-by {
|
||||
font-weight: 400;
|
||||
|
||||
a {
|
||||
color: inherit;
|
||||
text-decoration: underline;
|
||||
font-weight: 500;
|
||||
|
||||
&:hover {
|
||||
text-decoration: none;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.compact-header {
|
||||
h1 {
|
||||
font-size: 24px;
|
||||
line-height: 28px;
|
||||
color: $color3;
|
||||
overflow: hidden;
|
||||
font-weight: 500;
|
||||
margin-bottom: 20px;
|
||||
|
||||
a {
|
||||
color: inherit;
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
small {
|
||||
font-weight: 400;
|
||||
color: $color2;
|
||||
}
|
||||
|
||||
img {
|
||||
display: inline-block;
|
||||
margin-bottom: -5px;
|
||||
margin-right: 15px;
|
||||
width: 36px;
|
||||
height: 36px;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.landing-strip {
|
||||
background: rgba(darken($color1, 7%), 0.8);
|
||||
color: $color3;
|
||||
font-weight: 400;
|
||||
padding: 14px;
|
||||
border-radius: 4px;
|
||||
margin-bottom: 20px;
|
||||
|
||||
strong, a {
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
a {
|
||||
color: inherit;
|
||||
text-decoration: underline;
|
||||
}
|
||||
}
|
||||
|
||||
@import 'reset';
|
||||
@import 'basics';
|
||||
@import 'containers';
|
||||
@import 'lists';
|
||||
@import 'footer';
|
||||
@import 'compact_header';
|
||||
@import 'landing_strip';
|
||||
@import 'forms';
|
||||
@import 'accounts';
|
||||
@import 'stream_entries';
|
||||
|
||||
58
app/assets/stylesheets/basics.scss
Normal file
58
app/assets/stylesheets/basics.scss
Normal file
@@ -0,0 +1,58 @@
|
||||
body {
|
||||
font-family: 'Roboto', sans-serif;
|
||||
background: $color1 image-url('background-photo.jpeg');
|
||||
background-size: cover;
|
||||
background-attachment: fixed;
|
||||
font-size: 13px;
|
||||
line-height: 18px;
|
||||
font-weight: 400;
|
||||
color: $color5;
|
||||
padding-bottom: 140px;
|
||||
text-rendering: optimizelegibility;
|
||||
font-feature-settings: "kern";
|
||||
text-size-adjust: none;
|
||||
|
||||
&.app-body {
|
||||
position: fixed;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
padding: 0;
|
||||
background: $color1;
|
||||
}
|
||||
|
||||
&.embed {
|
||||
background: transparent;
|
||||
margin: 0;
|
||||
|
||||
.container {
|
||||
position: absolute;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
overflow: hidden;
|
||||
}
|
||||
}
|
||||
|
||||
&.admin {
|
||||
background: darken($color1, 4%);
|
||||
position: fixed;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
@media screen and (max-width: 360px) {
|
||||
padding-bottom: 0;
|
||||
}
|
||||
}
|
||||
|
||||
button:focus {
|
||||
outline: none;
|
||||
}
|
||||
|
||||
.app-holder {
|
||||
display: flex;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
}
|
||||
File diff suppressed because one or more lines are too long
28
app/assets/stylesheets/compact_header.scss
Normal file
28
app/assets/stylesheets/compact_header.scss
Normal file
@@ -0,0 +1,28 @@
|
||||
.compact-header {
|
||||
h1 {
|
||||
font-size: 24px;
|
||||
line-height: 28px;
|
||||
color: $color3;
|
||||
overflow: hidden;
|
||||
font-weight: 500;
|
||||
margin-bottom: 20px;
|
||||
|
||||
a {
|
||||
color: inherit;
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
small {
|
||||
font-weight: 400;
|
||||
color: $color2;
|
||||
}
|
||||
|
||||
img {
|
||||
display: inline-block;
|
||||
margin-bottom: -5px;
|
||||
margin-right: 15px;
|
||||
width: 36px;
|
||||
height: 36px;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,7 +1,8 @@
|
||||
@import 'variables';
|
||||
|
||||
.app-body{
|
||||
-ms-overflow-style: -ms-autohiding-scrollbar;
|
||||
-webkit-overflow-scrolling: touch;
|
||||
-ms-overflow-style: -ms-autohiding-scrollbar;
|
||||
}
|
||||
|
||||
.button {
|
||||
@@ -49,6 +50,22 @@
|
||||
}
|
||||
}
|
||||
|
||||
.column-icon-clear {
|
||||
font-size: 16px;
|
||||
padding: 15px;
|
||||
position: absolute;
|
||||
right: 48px;
|
||||
top: 0;
|
||||
cursor: pointer;
|
||||
z-index: 2;
|
||||
}
|
||||
|
||||
@media screen and (min-width: 1025px) {
|
||||
.column-icon-clear {
|
||||
top: 10px;
|
||||
}
|
||||
}
|
||||
|
||||
.icon-button {
|
||||
display: inline-block;
|
||||
padding: 0;
|
||||
@@ -95,6 +112,18 @@
|
||||
color: $color3;
|
||||
}
|
||||
}
|
||||
|
||||
&.overlayed {
|
||||
box-sizing: content-box;
|
||||
background: rgba($color8, 0.6);
|
||||
color: rgba($color5, 0.7);
|
||||
border-radius: 4px;
|
||||
padding: 2px;
|
||||
|
||||
&:hover {
|
||||
background: rgba($color8, 0.9);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.text-icon-button {
|
||||
@@ -149,6 +178,14 @@
|
||||
}
|
||||
}
|
||||
|
||||
.avatar {
|
||||
border-radius: 4px;
|
||||
background: transparent no-repeat;
|
||||
background-position: 50%;
|
||||
background-clip: padding-box;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.lightbox .icon-button {
|
||||
color: $color1;
|
||||
}
|
||||
@@ -325,6 +362,43 @@ a.status__content__spoiler-link {
|
||||
.status__display-name {
|
||||
color: lighten($color1, 26%);
|
||||
}
|
||||
|
||||
&.light {
|
||||
.status__relative-time {
|
||||
color: $color3;
|
||||
}
|
||||
|
||||
.status__display-name {
|
||||
color: $color1;
|
||||
}
|
||||
|
||||
.display-name {
|
||||
strong {
|
||||
color: $color1;
|
||||
}
|
||||
|
||||
span {
|
||||
color: $color3;
|
||||
}
|
||||
}
|
||||
|
||||
.status__content {
|
||||
color: $color1;
|
||||
|
||||
a {
|
||||
color: $color4;
|
||||
}
|
||||
|
||||
a.status__content__spoiler-link {
|
||||
color: $color5;
|
||||
background: $color3;
|
||||
|
||||
&:hover {
|
||||
background: lighten($color3, 8%);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.status-check-box {
|
||||
@@ -643,6 +717,12 @@ a.status__content__spoiler-link {
|
||||
left: 8px;
|
||||
}
|
||||
|
||||
&.light {
|
||||
&:before {
|
||||
border-color: transparent transparent $color5 transparent;
|
||||
}
|
||||
}
|
||||
|
||||
& > ul {
|
||||
list-style: none;
|
||||
background: $color2;
|
||||
@@ -660,7 +740,7 @@ a.status__content__spoiler-link {
|
||||
}
|
||||
|
||||
& > .emoji-dialog {
|
||||
left: -249px;
|
||||
left: -210px;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -714,15 +794,7 @@ a.status__content__spoiler-link {
|
||||
|
||||
@media screen and (min-width: 360px) {
|
||||
.columns-area {
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
.column:first-child, .drawer:first-child {
|
||||
margin-left: 0;
|
||||
}
|
||||
|
||||
.column:last-child, .drawer:last-child {
|
||||
margin-right: 0;
|
||||
padding: 10px;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -730,9 +802,12 @@ a.status__content__spoiler-link {
|
||||
width: 330px;
|
||||
position: relative;
|
||||
box-sizing: border-box;
|
||||
background: $color1;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
|
||||
> .scrollable {
|
||||
background: $color1;
|
||||
}
|
||||
}
|
||||
|
||||
.ui {
|
||||
@@ -764,6 +839,58 @@ a.status__content__spoiler-link {
|
||||
border-bottom: 2px solid transparent;
|
||||
}
|
||||
|
||||
.column, .drawer {
|
||||
flex: 1 1 100%;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
@media screen and (min-width: 360px) {
|
||||
.tabs-bar {
|
||||
margin: 10px;
|
||||
margin-bottom: 0;
|
||||
}
|
||||
|
||||
.search {
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
}
|
||||
|
||||
@media screen and (max-width: 1024px) {
|
||||
.column, .drawer {
|
||||
width: 100%;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
.columns-area {
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.search__input, .autosuggest-textarea__textarea {
|
||||
font-size: 16px;
|
||||
}
|
||||
}
|
||||
|
||||
@media screen and (min-width: 1025px) {
|
||||
.columns-area {
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
.column, .drawer {
|
||||
flex: 0 0 auto;
|
||||
padding: 10px;
|
||||
padding-left: 5px;
|
||||
padding-right: 5px;
|
||||
|
||||
&:first-child {
|
||||
padding-left: 10px;
|
||||
}
|
||||
|
||||
&:last-child {
|
||||
padding-right: 10px;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@media screen and (min-width: 2560px) {
|
||||
.columns-area {
|
||||
justify-content: center;
|
||||
@@ -823,38 +950,6 @@ a.status__content__spoiler-link {
|
||||
}
|
||||
}
|
||||
|
||||
.column, .drawer {
|
||||
margin: 10px;
|
||||
margin-left: 5px;
|
||||
margin-right: 5px;
|
||||
flex: 0 0 auto;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.column:first-child, .drawer:first-child {
|
||||
margin-left: 10px;
|
||||
}
|
||||
|
||||
.column:last-child, .drawer:last-child {
|
||||
margin-right: 10px;
|
||||
}
|
||||
|
||||
@media screen and (max-width: 1024px) {
|
||||
.column, .drawer {
|
||||
width: 100%;
|
||||
margin: 0;
|
||||
flex: 1 1 100%;
|
||||
}
|
||||
|
||||
.columns-area {
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.search__input, .autosuggest-textarea__textarea {
|
||||
font-size: 16px;
|
||||
}
|
||||
}
|
||||
|
||||
.tabs-bar {
|
||||
display: flex;
|
||||
background: lighten($color1, 8%);
|
||||
@@ -865,17 +960,18 @@ a.status__content__spoiler-link {
|
||||
.tabs-bar__link {
|
||||
display: block;
|
||||
flex: 1 1 auto;
|
||||
padding: 10px 5px;
|
||||
padding: 15px 10px;
|
||||
color: $color5;
|
||||
text-decoration: none;
|
||||
text-align: center;
|
||||
font-size:12px;
|
||||
font-size: 14px;
|
||||
font-weight: 500;
|
||||
border-bottom: 2px solid lighten($color1, 8%);
|
||||
transition: all 200ms linear;
|
||||
|
||||
.fa {
|
||||
font-weight: 400;
|
||||
font-size: 16px;
|
||||
}
|
||||
|
||||
&.active {
|
||||
@@ -889,37 +985,13 @@ a.status__content__spoiler-link {
|
||||
}
|
||||
|
||||
span {
|
||||
margin-left: 5px;
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
|
||||
@media screen and (min-width: 360px) {
|
||||
.columns-area {
|
||||
margin: 10px;
|
||||
}
|
||||
|
||||
.tabs-bar {
|
||||
margin: 10px;
|
||||
margin-bottom: 0;
|
||||
}
|
||||
|
||||
.search {
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
}
|
||||
|
||||
@media screen and (min-width: 1024px) {
|
||||
.columns-area {
|
||||
margin: 0;
|
||||
}
|
||||
}
|
||||
|
||||
@media screen and (min-width: 600px) {
|
||||
.tabs-bar__link {
|
||||
.fa {
|
||||
margin-right: 5px;
|
||||
}
|
||||
|
||||
span {
|
||||
display: inline;
|
||||
}
|
||||
@@ -1199,7 +1271,7 @@ a.status__content__spoiler-link {
|
||||
|
||||
@import 'boost';
|
||||
|
||||
button i.fa-retweet {
|
||||
button.icon-button i.fa-retweet {
|
||||
height: 19px;
|
||||
width: 22px;
|
||||
background-position: 0 0;
|
||||
@@ -1211,7 +1283,7 @@ button i.fa-retweet {
|
||||
}
|
||||
}
|
||||
|
||||
button.active i.fa-retweet {
|
||||
button.icon-button.active i.fa-retweet {
|
||||
transition-duration: 0.9s;
|
||||
background-position: 0 100%;
|
||||
}
|
||||
@@ -1381,12 +1453,15 @@ button.active i.fa-retweet {
|
||||
|
||||
.empty-column-indicator {
|
||||
color: lighten($color1, 20%);
|
||||
background: $color1;
|
||||
text-align: center;
|
||||
padding: 20px;
|
||||
padding-top: 100px;
|
||||
font-size: 15px;
|
||||
font-weight: 400;
|
||||
cursor: default;
|
||||
display: flex;
|
||||
flex: 1 1 auto;
|
||||
align-items: center;
|
||||
|
||||
a {
|
||||
color: $color4;
|
||||
@@ -1412,22 +1487,23 @@ button.active i.fa-retweet {
|
||||
}
|
||||
|
||||
.emoji-dialog {
|
||||
width: 280px;
|
||||
height: 220px;
|
||||
background: $color2;
|
||||
width: 245px;
|
||||
height: 270px;
|
||||
background: $color5;
|
||||
box-sizing: border-box;
|
||||
border-radius: 2px;
|
||||
border-radius: 4px;
|
||||
overflow: hidden;
|
||||
position: relative;
|
||||
box-shadow: 0 0 15px rgba($color8, 0.4);
|
||||
box-shadow: 0 0 8px rgba($color8, 0.2);
|
||||
|
||||
.emojione {
|
||||
margin: 0;
|
||||
width: 100%;
|
||||
height: auto;
|
||||
}
|
||||
|
||||
.emoji-dialog-header {
|
||||
padding: 0 10px;
|
||||
background-color: $color3;
|
||||
|
||||
ul {
|
||||
padding: 0;
|
||||
@@ -1438,18 +1514,29 @@ button.active i.fa-retweet {
|
||||
li {
|
||||
display: inline-block;
|
||||
box-sizing: border-box;
|
||||
height: 42px;
|
||||
padding: 9px 5px;
|
||||
padding: 10px 5px;
|
||||
cursor: pointer;
|
||||
border-bottom: 2px solid transparent;
|
||||
|
||||
.emoji {
|
||||
width: 18px;
|
||||
height: 18px;
|
||||
}
|
||||
|
||||
img, svg {
|
||||
width: 22px;
|
||||
height: 22px;
|
||||
width: 18px;
|
||||
height: 18px;
|
||||
filter: grayscale(100%);
|
||||
}
|
||||
|
||||
&:hover {
|
||||
img, svg {
|
||||
filter: grayscale(0);
|
||||
}
|
||||
}
|
||||
|
||||
&.active {
|
||||
background: lighten($color3, 6%);
|
||||
border-bottom-color: $color4;
|
||||
|
||||
img, svg {
|
||||
filter: grayscale(0);
|
||||
@@ -1473,7 +1560,7 @@ button.active i.fa-retweet {
|
||||
.emoji-category-header {
|
||||
box-sizing: border-box;
|
||||
overflow-y: hidden;
|
||||
padding: 8px 16px 0;
|
||||
padding: 10px 8px 10px 16px;
|
||||
display: table;
|
||||
|
||||
> * {
|
||||
@@ -1483,10 +1570,10 @@ button.active i.fa-retweet {
|
||||
}
|
||||
|
||||
.emoji-category-title {
|
||||
font-size: 14px;
|
||||
font-family: sans-serif;
|
||||
font-weight: normal;
|
||||
color: $color1;
|
||||
font-size: 12px;
|
||||
text-transform: uppercase;
|
||||
font-weight: 500;
|
||||
color: darken($color2, 18%);
|
||||
cursor: default;
|
||||
}
|
||||
|
||||
@@ -1526,7 +1613,7 @@ button.active i.fa-retweet {
|
||||
width: 7px;
|
||||
height: 7px;
|
||||
border-radius: 10px;
|
||||
border: 2px solid $color1;
|
||||
border: 2px solid $color5;
|
||||
top: 2px;
|
||||
left: 2px;
|
||||
}
|
||||
@@ -1534,14 +1621,20 @@ button.active i.fa-retweet {
|
||||
}
|
||||
|
||||
.emoji-search-wrapper {
|
||||
padding: 6px 16px;
|
||||
padding: 10px;
|
||||
border-bottom: 1px solid lighten($color2, 4%);
|
||||
}
|
||||
|
||||
.emoji-search {
|
||||
font-size: 12px;
|
||||
padding: 6px 4px;
|
||||
font-size: 14px;
|
||||
font-weight: 400;
|
||||
padding: 7px 9px;
|
||||
font-family: inherit;
|
||||
display: block;
|
||||
width: 100%;
|
||||
border: 1px solid #ddd;
|
||||
background: rgba($color2, 0.3);
|
||||
color: darken($color2, 18%);
|
||||
border: 1px solid $color2;
|
||||
border-radius: 4px;
|
||||
}
|
||||
|
||||
@@ -1554,11 +1647,21 @@ button.active i.fa-retweet {
|
||||
}
|
||||
|
||||
.emoji-search-wrapper + .emoji-categories-wrapper {
|
||||
top: 83px;
|
||||
top: 93px;
|
||||
}
|
||||
|
||||
.emoji-row .emoji:hover {
|
||||
background: lighten($color2, 3%);
|
||||
.emoji-row .emoji {
|
||||
img, svg {
|
||||
transition: transform 60ms ease-in-out;
|
||||
}
|
||||
|
||||
&:hover {
|
||||
background: lighten($color2, 3%);
|
||||
|
||||
img, svg {
|
||||
transform: translateZ(0) scale(1.2);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.emoji {
|
||||
@@ -1915,3 +2018,41 @@ button.active i.fa-retweet {
|
||||
max-height: 80vh;
|
||||
}
|
||||
}
|
||||
|
||||
.boost-modal {
|
||||
background: lighten($color2, 8%);
|
||||
color: $color1;
|
||||
border-radius: 8px;
|
||||
overflow: hidden;
|
||||
max-width: 90vw;
|
||||
width: 480px;
|
||||
position: relative;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.boost-modal__container {
|
||||
padding: 10px;
|
||||
|
||||
.status {
|
||||
user-select: text;
|
||||
border-bottom: 0;
|
||||
}
|
||||
}
|
||||
|
||||
.boost-modal__action-bar {
|
||||
display: flex;
|
||||
background: $color2;
|
||||
padding: 10px;
|
||||
line-height: 36px;
|
||||
|
||||
& > div {
|
||||
flex: 1 1 auto;
|
||||
text-align: right;
|
||||
color: lighten($color1, 33%);
|
||||
padding-right: 10px;
|
||||
}
|
||||
|
||||
.button {
|
||||
flex: 0 0 auto;
|
||||
}
|
||||
}
|
||||
|
||||
61
app/assets/stylesheets/containers.scss
Normal file
61
app/assets/stylesheets/containers.scss
Normal file
@@ -0,0 +1,61 @@
|
||||
.container {
|
||||
width: 700px;
|
||||
margin: 0 auto;
|
||||
margin-top: 40px;
|
||||
|
||||
@media screen and (max-width: 700px) {
|
||||
width: 100%;
|
||||
margin: 0;
|
||||
}
|
||||
}
|
||||
|
||||
.logo-container {
|
||||
max-width: 400px;
|
||||
margin: 100px auto;
|
||||
margin-bottom: 0;
|
||||
cursor: default;
|
||||
|
||||
@media screen and (max-width: 360px) {
|
||||
margin: 30px auto;
|
||||
}
|
||||
|
||||
h1 {
|
||||
display: block;
|
||||
text-align: center;
|
||||
color: $color5;
|
||||
font-size: 48px;
|
||||
font-weight: 500;
|
||||
|
||||
img {
|
||||
display: block;
|
||||
margin: 20px auto;
|
||||
width: 180px;
|
||||
height: 180px;
|
||||
}
|
||||
|
||||
a {
|
||||
color: inherit;
|
||||
text-decoration: none;
|
||||
outline: 0;
|
||||
|
||||
img {
|
||||
opacity: 0.8;
|
||||
transition: all 0.8s ease;
|
||||
}
|
||||
|
||||
&:hover {
|
||||
img {
|
||||
opacity: 1;
|
||||
transition-duration: 0.2s;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
small {
|
||||
display: block;
|
||||
font-size: 12px;
|
||||
font-weight: 400;
|
||||
font-family: 'Roboto Mono', monospace;
|
||||
}
|
||||
}
|
||||
}
|
||||
29
app/assets/stylesheets/footer.scss
Normal file
29
app/assets/stylesheets/footer.scss
Normal file
@@ -0,0 +1,29 @@
|
||||
.footer {
|
||||
text-align: center;
|
||||
margin-top: 30px;
|
||||
font-size: 12px;
|
||||
color: darken($color2, 25%);
|
||||
|
||||
.domain {
|
||||
font-weight: 500;
|
||||
|
||||
a {
|
||||
color: inherit;
|
||||
text-decoration: none;
|
||||
}
|
||||
}
|
||||
|
||||
.powered-by {
|
||||
font-weight: 400;
|
||||
|
||||
a {
|
||||
color: inherit;
|
||||
text-decoration: underline;
|
||||
font-weight: 500;
|
||||
|
||||
&:hover {
|
||||
text-decoration: none;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -88,7 +88,7 @@ code {
|
||||
}
|
||||
}
|
||||
|
||||
input[type=text], input[type=email], input[type=password], textarea {
|
||||
input[type=text], input[type=number], input[type=email], input[type=password], textarea {
|
||||
background: transparent;
|
||||
box-sizing: border-box;
|
||||
border: 0;
|
||||
|
||||
17
app/assets/stylesheets/landing_strip.scss
Normal file
17
app/assets/stylesheets/landing_strip.scss
Normal file
@@ -0,0 +1,17 @@
|
||||
.landing-strip {
|
||||
background: rgba(darken($color1, 7%), 0.8);
|
||||
color: $color3;
|
||||
font-weight: 400;
|
||||
padding: 14px;
|
||||
border-radius: 4px;
|
||||
margin-bottom: 20px;
|
||||
|
||||
strong, a {
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
a {
|
||||
color: inherit;
|
||||
text-decoration: underline;
|
||||
}
|
||||
}
|
||||
8
app/assets/stylesheets/lists.scss
Normal file
8
app/assets/stylesheets/lists.scss
Normal file
@@ -0,0 +1,8 @@
|
||||
.no-list {
|
||||
list-style: none;
|
||||
|
||||
li {
|
||||
display: inline-block;
|
||||
margin: 0 5px;
|
||||
}
|
||||
}
|
||||
91
app/assets/stylesheets/reset.scss
Normal file
91
app/assets/stylesheets/reset.scss
Normal file
@@ -0,0 +1,91 @@
|
||||
/* http://meyerweb.com/eric/tools/css/reset/
|
||||
v2.0 | 20110126
|
||||
License: none (public domain)
|
||||
*/
|
||||
|
||||
html, body, div, span, applet, object, iframe,
|
||||
h1, h2, h3, h4, h5, h6, p, blockquote, pre,
|
||||
a, abbr, acronym, address, big, cite, code,
|
||||
del, dfn, em, img, ins, kbd, q, s, samp,
|
||||
small, strike, strong, sub, sup, tt, var,
|
||||
b, u, i, center,
|
||||
dl, dt, dd, ol, ul, li,
|
||||
fieldset, form, label, legend,
|
||||
table, caption, tbody, tfoot, thead, tr, th, td,
|
||||
article, aside, canvas, details, embed,
|
||||
figure, figcaption, footer, header, hgroup,
|
||||
menu, nav, output, ruby, section, summary,
|
||||
time, mark, audio, video {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
border: 0;
|
||||
font-size: 100%;
|
||||
font: inherit;
|
||||
vertical-align: baseline;
|
||||
}
|
||||
|
||||
/* HTML5 display-role reset for older browsers */
|
||||
article, aside, details, figcaption, figure,
|
||||
footer, header, hgroup, menu, nav, section {
|
||||
display: block;
|
||||
}
|
||||
|
||||
body {
|
||||
line-height: 1;
|
||||
}
|
||||
|
||||
ol, ul {
|
||||
list-style: none;
|
||||
}
|
||||
|
||||
blockquote, q {
|
||||
quotes: none;
|
||||
}
|
||||
|
||||
blockquote:before, blockquote:after,
|
||||
q:before, q:after {
|
||||
content: '';
|
||||
content: none;
|
||||
}
|
||||
|
||||
table {
|
||||
border-collapse: collapse;
|
||||
border-spacing: 0;
|
||||
}
|
||||
|
||||
::-webkit-scrollbar {
|
||||
width: 8px;
|
||||
height: 8px;
|
||||
}
|
||||
|
||||
::-webkit-scrollbar-thumb {
|
||||
background: lighten($color1, 4%);
|
||||
border: 0px none $color5;
|
||||
border-radius: 50px;
|
||||
}
|
||||
|
||||
::-webkit-scrollbar-thumb:hover {
|
||||
background: lighten($color1, 6%);
|
||||
}
|
||||
|
||||
::-webkit-scrollbar-thumb:active {
|
||||
background: lighten($color1, 4%);
|
||||
}
|
||||
|
||||
::-webkit-scrollbar-track {
|
||||
border: 0px none $color5;
|
||||
border-radius: 0;
|
||||
background: rgba($color8, 0.1);
|
||||
}
|
||||
|
||||
::-webkit-scrollbar-track:hover {
|
||||
background: $color1;
|
||||
}
|
||||
|
||||
::-webkit-scrollbar-track:active {
|
||||
background: $color1;
|
||||
}
|
||||
|
||||
::-webkit-scrollbar-corner {
|
||||
background: transparent;
|
||||
}
|
||||
@@ -218,6 +218,7 @@
|
||||
margin-top: 8px;
|
||||
height: 300px;
|
||||
overflow: hidden;
|
||||
position: relative;
|
||||
|
||||
video {
|
||||
position: relative;
|
||||
|
||||
@@ -35,11 +35,11 @@ class AccountsController < ApplicationController
|
||||
end
|
||||
|
||||
def followers
|
||||
@followers = @account.followers.order('follows.created_at desc').paginate(page: params[:page], per_page: 12)
|
||||
@followers = @account.followers.order('follows.created_at desc').page(params[:page]).per(12)
|
||||
end
|
||||
|
||||
def following
|
||||
@following = @account.following.order('follows.created_at desc').paginate(page: params[:page], per_page: 12)
|
||||
@following = @account.following.order('follows.created_at desc').page(params[:page]).per(12)
|
||||
end
|
||||
|
||||
private
|
||||
@@ -53,7 +53,7 @@ class AccountsController < ApplicationController
|
||||
end
|
||||
|
||||
def webfinger_account_url
|
||||
webfinger_url(resource: "acct:#{@account.acct}@#{Rails.configuration.x.local_domain}")
|
||||
webfinger_url(resource: @account.to_webfinger_s)
|
||||
end
|
||||
|
||||
def check_account_suspension
|
||||
|
||||
@@ -1,51 +1,30 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
class Admin::AccountsController < ApplicationController
|
||||
before_action :require_admin!
|
||||
before_action :set_account, except: :index
|
||||
module Admin
|
||||
class AccountsController < BaseController
|
||||
def index
|
||||
@accounts = filtered_accounts.page(params[:page])
|
||||
end
|
||||
|
||||
layout 'admin'
|
||||
def show
|
||||
@account = Account.find(params[:id])
|
||||
end
|
||||
|
||||
def index
|
||||
@accounts = Account.alphabetic.paginate(page: params[:page], per_page: 40)
|
||||
private
|
||||
|
||||
@accounts = @accounts.local if params[:local].present?
|
||||
@accounts = @accounts.remote if params[:remote].present?
|
||||
@accounts = @accounts.where(domain: params[:by_domain]) if params[:by_domain].present?
|
||||
@accounts = @accounts.silenced if params[:silenced].present?
|
||||
@accounts = @accounts.recent if params[:recent].present?
|
||||
@accounts = @accounts.suspended if params[:suspended].present?
|
||||
end
|
||||
def filtered_accounts
|
||||
AccountFilter.new(filter_params).results
|
||||
end
|
||||
|
||||
def show; end
|
||||
|
||||
def suspend
|
||||
Admin::SuspensionWorker.perform_async(@account.id)
|
||||
redirect_to admin_accounts_path
|
||||
end
|
||||
|
||||
def unsuspend
|
||||
@account.update(suspended: false)
|
||||
redirect_to admin_accounts_path
|
||||
end
|
||||
|
||||
def silence
|
||||
@account.update(silenced: true)
|
||||
redirect_to admin_accounts_path
|
||||
end
|
||||
|
||||
def unsilence
|
||||
@account.update(silenced: false)
|
||||
redirect_to admin_accounts_path
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def set_account
|
||||
@account = Account.find(params[:id])
|
||||
end
|
||||
|
||||
def account_params
|
||||
params.require(:account).permit(:silenced, :suspended)
|
||||
def filter_params
|
||||
params.permit(
|
||||
:local,
|
||||
:remote,
|
||||
:by_domain,
|
||||
:silenced,
|
||||
:recent,
|
||||
:suspended
|
||||
)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
9
app/controllers/admin/base_controller.rb
Normal file
9
app/controllers/admin/base_controller.rb
Normal file
@@ -0,0 +1,9 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
module Admin
|
||||
class BaseController < ApplicationController
|
||||
before_action :require_admin!
|
||||
|
||||
layout 'admin'
|
||||
end
|
||||
end
|
||||
@@ -1,32 +1,30 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
class Admin::DomainBlocksController < ApplicationController
|
||||
before_action :require_admin!
|
||||
module Admin
|
||||
class DomainBlocksController < BaseController
|
||||
def index
|
||||
@blocks = DomainBlock.page(params[:page])
|
||||
end
|
||||
|
||||
layout 'admin'
|
||||
def new
|
||||
@domain_block = DomainBlock.new
|
||||
end
|
||||
|
||||
def index
|
||||
@blocks = DomainBlock.paginate(page: params[:page], per_page: 40)
|
||||
end
|
||||
def create
|
||||
@domain_block = DomainBlock.new(resource_params)
|
||||
|
||||
def new
|
||||
@domain_block = DomainBlock.new
|
||||
end
|
||||
if @domain_block.save
|
||||
DomainBlockWorker.perform_async(@domain_block.id)
|
||||
redirect_to admin_domain_blocks_path, notice: 'Domain block is now being processed'
|
||||
else
|
||||
render action: :new
|
||||
end
|
||||
end
|
||||
|
||||
def create
|
||||
@domain_block = DomainBlock.new(resource_params)
|
||||
private
|
||||
|
||||
if @domain_block.save
|
||||
DomainBlockWorker.perform_async(@domain_block.id)
|
||||
redirect_to admin_domain_blocks_path, notice: 'Domain block is now being processed'
|
||||
else
|
||||
render action: :new
|
||||
def resource_params
|
||||
params.require(:domain_block).permit(:domain, :severity)
|
||||
end
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def resource_params
|
||||
params.require(:domain_block).permit(:domain, :severity)
|
||||
end
|
||||
end
|
||||
|
||||
@@ -1,11 +1,9 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
class Admin::PubsubhubbubController < ApplicationController
|
||||
before_action :require_admin!
|
||||
|
||||
layout 'admin'
|
||||
|
||||
def index
|
||||
@subscriptions = Subscription.order('id desc').includes(:account).paginate(page: params[:page], per_page: 40)
|
||||
module Admin
|
||||
class PubsubhubbubController < BaseController
|
||||
def index
|
||||
@subscriptions = Subscription.order('id desc').includes(:account).page(params[:page])
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
@@ -1,45 +1,44 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
class Admin::ReportsController < ApplicationController
|
||||
before_action :require_admin!
|
||||
before_action :set_report, except: [:index]
|
||||
module Admin
|
||||
class ReportsController < BaseController
|
||||
before_action :set_report, except: [:index]
|
||||
|
||||
layout 'admin'
|
||||
def index
|
||||
@reports = Report.includes(:account, :target_account).order('id desc').page(params[:page])
|
||||
@reports = params[:action_taken].present? ? @reports.resolved : @reports.unresolved
|
||||
end
|
||||
|
||||
def index
|
||||
@reports = Report.includes(:account, :target_account).order('id desc').paginate(page: params[:page], per_page: 40)
|
||||
@reports = params[:action_taken].present? ? @reports.resolved : @reports.unresolved
|
||||
end
|
||||
def show
|
||||
@statuses = Status.where(id: @report.status_ids)
|
||||
end
|
||||
|
||||
def show
|
||||
@statuses = Status.where(id: @report.status_ids)
|
||||
end
|
||||
def resolve
|
||||
@report.update(action_taken: true, action_taken_by_account_id: current_account.id)
|
||||
redirect_to admin_report_path(@report)
|
||||
end
|
||||
|
||||
def resolve
|
||||
@report.update(action_taken: true, action_taken_by_account_id: current_account.id)
|
||||
redirect_to admin_report_path(@report)
|
||||
end
|
||||
def suspend
|
||||
Admin::SuspensionWorker.perform_async(@report.target_account.id)
|
||||
Report.unresolved.where(target_account: @report.target_account).update_all(action_taken: true, action_taken_by_account_id: current_account.id)
|
||||
redirect_to admin_report_path(@report)
|
||||
end
|
||||
|
||||
def suspend
|
||||
Admin::SuspensionWorker.perform_async(@report.target_account.id)
|
||||
Report.unresolved.where(target_account: @report.target_account).update_all(action_taken: true, action_taken_by_account_id: current_account.id)
|
||||
redirect_to admin_report_path(@report)
|
||||
end
|
||||
def silence
|
||||
@report.target_account.update(silenced: true)
|
||||
Report.unresolved.where(target_account: @report.target_account).update_all(action_taken: true, action_taken_by_account_id: current_account.id)
|
||||
redirect_to admin_report_path(@report)
|
||||
end
|
||||
|
||||
def silence
|
||||
@report.target_account.update(silenced: true)
|
||||
Report.unresolved.where(target_account: @report.target_account).update_all(action_taken: true, action_taken_by_account_id: current_account.id)
|
||||
redirect_to admin_report_path(@report)
|
||||
end
|
||||
def remove
|
||||
RemovalWorker.perform_async(params[:status_id])
|
||||
redirect_to admin_report_path(@report)
|
||||
end
|
||||
|
||||
def remove
|
||||
RemovalWorker.perform_async(params[:status_id])
|
||||
redirect_to admin_report_path(@report)
|
||||
end
|
||||
private
|
||||
|
||||
private
|
||||
|
||||
def set_report
|
||||
@report = Report.find(params[:id])
|
||||
def set_report
|
||||
@report = Report.find(params[:id])
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
@@ -1,35 +1,33 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
class Admin::SettingsController < ApplicationController
|
||||
before_action :require_admin!
|
||||
|
||||
layout 'admin'
|
||||
|
||||
def index
|
||||
@settings = Setting.all_as_records
|
||||
end
|
||||
|
||||
def update
|
||||
@setting = Setting.where(var: params[:id]).first_or_initialize(var: params[:id])
|
||||
value = settings_params[:value]
|
||||
|
||||
# Special cases
|
||||
value = value == 'true' if @setting.var == 'open_registrations'
|
||||
|
||||
if @setting.value != value
|
||||
@setting.value = value
|
||||
@setting.save
|
||||
module Admin
|
||||
class SettingsController < BaseController
|
||||
def index
|
||||
@settings = Setting.all_as_records
|
||||
end
|
||||
|
||||
respond_to do |format|
|
||||
format.html { redirect_to admin_settings_path }
|
||||
format.json { respond_with_bip(@setting) }
|
||||
def update
|
||||
@setting = Setting.where(var: params[:id]).first_or_initialize(var: params[:id])
|
||||
value = settings_params[:value]
|
||||
|
||||
# Special cases
|
||||
value = value == 'true' if @setting.var == 'open_registrations'
|
||||
|
||||
if @setting.value != value
|
||||
@setting.value = value
|
||||
@setting.save
|
||||
end
|
||||
|
||||
respond_to do |format|
|
||||
format.html { redirect_to admin_settings_path }
|
||||
format.json { respond_with_bip(@setting) }
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
private
|
||||
private
|
||||
|
||||
def settings_params
|
||||
params.require(:setting).permit(:value)
|
||||
def settings_params
|
||||
params.require(:setting).permit(:value)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
23
app/controllers/admin/silences_controller.rb
Normal file
23
app/controllers/admin/silences_controller.rb
Normal file
@@ -0,0 +1,23 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
module Admin
|
||||
class SilencesController < BaseController
|
||||
before_action :set_account
|
||||
|
||||
def create
|
||||
@account.update(silenced: true)
|
||||
redirect_to admin_accounts_path
|
||||
end
|
||||
|
||||
def destroy
|
||||
@account.update(silenced: false)
|
||||
redirect_to admin_accounts_path
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def set_account
|
||||
@account = Account.find(params[:account_id])
|
||||
end
|
||||
end
|
||||
end
|
||||
23
app/controllers/admin/suspensions_controller.rb
Normal file
23
app/controllers/admin/suspensions_controller.rb
Normal file
@@ -0,0 +1,23 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
module Admin
|
||||
class SuspensionsController < BaseController
|
||||
before_action :set_account
|
||||
|
||||
def create
|
||||
Admin::SuspensionWorker.perform_async(@account.id)
|
||||
redirect_to admin_accounts_path
|
||||
end
|
||||
|
||||
def destroy
|
||||
@account.update(suspended: false)
|
||||
redirect_to admin_accounts_path
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def set_account
|
||||
@account = Account.find(params[:account_id])
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -9,7 +9,7 @@ class Api::V1::NotificationsController < ApiController
|
||||
DEFAULT_NOTIFICATIONS_LIMIT = 15
|
||||
|
||||
def index
|
||||
@notifications = Notification.where(account: current_account).browserable.paginate_by_max_id(limit_param(DEFAULT_NOTIFICATIONS_LIMIT), params[:max_id], params[:since_id])
|
||||
@notifications = Notification.where(account: current_account).browserable(exclude_types).paginate_by_max_id(limit_param(DEFAULT_NOTIFICATIONS_LIMIT), params[:max_id], params[:since_id])
|
||||
@notifications = cache_collection(@notifications, Notification)
|
||||
statuses = @notifications.select { |n| !n.target_status.nil? }.map(&:target_status)
|
||||
|
||||
@@ -32,7 +32,13 @@ class Api::V1::NotificationsController < ApiController
|
||||
|
||||
private
|
||||
|
||||
def exclude_types
|
||||
val = params.permit(exclude_types: [])[:exclude_types] || []
|
||||
val = [val] unless val.is_a?(Enumerable)
|
||||
val
|
||||
end
|
||||
|
||||
def pagination_params(core_params)
|
||||
params.permit(:limit).merge(core_params)
|
||||
params.permit(:limit, exclude_types: []).merge(core_params)
|
||||
end
|
||||
end
|
||||
|
||||
@@ -26,7 +26,7 @@ module Localized
|
||||
end
|
||||
|
||||
def default_locale
|
||||
ENV.fetch('DEFAULT_LOCALE') {
|
||||
ENV.fetch('DEFAULT_LOCALE') {
|
||||
http_accept_language.compatible_language_from(I18n.available_locales) || I18n.default_locale
|
||||
}
|
||||
end
|
||||
|
||||
@@ -25,7 +25,7 @@ class RemoteFollowController < ApplicationController
|
||||
|
||||
session[:remote_follow] = @remote_follow.acct
|
||||
|
||||
redirect_to Addressable::Template.new(redirect_url_link.template).expand(uri: "#{@account.username}@#{Rails.configuration.x.local_domain}").to_s
|
||||
redirect_to Addressable::Template.new(redirect_url_link.template).expand(uri: @account.to_webfinger_s).to_s
|
||||
else
|
||||
render :new
|
||||
end
|
||||
|
||||
23
app/controllers/settings/exports/base_controller.rb
Normal file
23
app/controllers/settings/exports/base_controller.rb
Normal file
@@ -0,0 +1,23 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
module Settings
|
||||
module Exports
|
||||
class BaseController < ApplicationController
|
||||
before_action :authenticate_user!
|
||||
|
||||
def index
|
||||
@export = Export.new(current_account)
|
||||
|
||||
respond_to do |format|
|
||||
format.csv { send_data export_data, filename: export_filename }
|
||||
end
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def export_filename
|
||||
"#{controller_name}.csv"
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -0,0 +1,13 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
module Settings
|
||||
module Exports
|
||||
class BlockedAccountsController < BaseController
|
||||
private
|
||||
|
||||
def export_data
|
||||
@export.to_blocked_accounts_csv
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -0,0 +1,13 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
module Settings
|
||||
module Exports
|
||||
class FollowingAccountsController < BaseController
|
||||
private
|
||||
|
||||
def export_data
|
||||
@export.to_following_accounts_csv
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -0,0 +1,13 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
module Settings
|
||||
module Exports
|
||||
class MutedAccountsController < BaseController
|
||||
private
|
||||
|
||||
def export_data
|
||||
@export.to_muted_accounts_csv
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -1,46 +1,11 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
require 'csv'
|
||||
|
||||
class Settings::ExportsController < ApplicationController
|
||||
layout 'admin'
|
||||
|
||||
before_action :authenticate_user!
|
||||
before_action :set_account
|
||||
|
||||
def show
|
||||
@total_storage = current_account.media_attachments.sum(:file_file_size)
|
||||
@total_follows = current_account.following.count
|
||||
@total_blocks = current_account.blocking.count
|
||||
end
|
||||
|
||||
def download_following_list
|
||||
@accounts = current_account.following
|
||||
|
||||
respond_to do |format|
|
||||
format.csv { render text: accounts_list_to_csv(@accounts) }
|
||||
end
|
||||
end
|
||||
|
||||
def download_blocking_list
|
||||
@accounts = current_account.blocking
|
||||
|
||||
respond_to do |format|
|
||||
format.csv { render text: accounts_list_to_csv(@accounts) }
|
||||
end
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def set_account
|
||||
@account = current_user.account
|
||||
end
|
||||
|
||||
def accounts_list_to_csv(list)
|
||||
CSV.generate do |csv|
|
||||
list.each do |account|
|
||||
csv << [(account.local? ? "#{account.username}@#{Rails.configuration.x.local_domain}" : account.acct)]
|
||||
end
|
||||
end
|
||||
@export = Export.new(current_account)
|
||||
end
|
||||
end
|
||||
|
||||
@@ -23,8 +23,9 @@ class Settings::PreferencesController < ApplicationController
|
||||
}
|
||||
|
||||
current_user.settings['default_privacy'] = user_params[:setting_default_privacy]
|
||||
current_user.settings['boost_modal'] = user_params[:setting_boost_modal] == '1'
|
||||
|
||||
if current_user.update(user_params.except(:notification_emails, :interactions, :setting_default_privacy))
|
||||
if current_user.update(user_params.except(:notification_emails, :interactions, :setting_default_privacy, :setting_boost_modal))
|
||||
redirect_to settings_preferences_path, notice: I18n.t('generic.changes_saved_msg')
|
||||
else
|
||||
render action: :show
|
||||
@@ -34,6 +35,6 @@ class Settings::PreferencesController < ApplicationController
|
||||
private
|
||||
|
||||
def user_params
|
||||
params.require(:user).permit(:locale, :setting_default_privacy, notification_emails: [:follow, :follow_request, :reblog, :favourite, :mention, :digest], interactions: [:must_be_follower, :must_be_following])
|
||||
params.require(:user).permit(:locale, :setting_default_privacy, :setting_boost_modal, notification_emails: [:follow, :follow_request, :reblog, :favourite, :mention, :digest], interactions: [:must_be_follower, :must_be_following])
|
||||
end
|
||||
end
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user