Compare commits

..

144 Commits

Author SHA1 Message Date
Eugen Rochko
6cc432bbc4 Bump version to 2.3.2 2018-03-22 14:13:46 +01:00
Eugen Rochko
dafae9818d Bump version to 2.3.2rc5 2018-03-22 11:31:52 +01:00
Eugen Rochko
9fe1619db9 Do not re-query mentions from serializers (#6858)
Fix performance regression from #6836
2018-03-22 11:31:17 +01:00
Eugen Rochko
da70aca28e Restore username validation to disallow dots, for now (#6863)
Usernames with dots in them do not work with routes, because the dot usually separates the desired page format (e.g. json). I don't want to mess with changing route constraints for this patch release.
2018-03-22 11:30:22 +01:00
ThibG
6f531d140b Fix MENTION_RE to not match nil usernames (#6862) 2018-03-22 10:45:48 +01:00
Eugen Rochko
f66a786029 Hide floating action button on thread views (#6859) 2018-03-22 09:33:14 +01:00
Patrick Figel
d97903a358 Update sanitize and loofah (#6855)
Fixes CVE-2018-8048 and CVE-2018-3740, two medium-severity XSS
vulnerabilities present in these gems when built against
libxml2 >= 2.9.2.
2018-03-21 17:43:28 +01:00
Eugen Rochko
93897134ca Permit dots in usernames with conditions (#6844)
* Permit dots in usernames with conditions

- Dot cannot be the start or end of username
- a.lice and al.ice are considered the same during sign-up

* Fix regex mixin flags
2018-03-21 10:26:53 +01:00
Akihiko Odaki
a6b59cd1a3 Remove debug option from Babel preset env (#6852) 2018-03-21 10:26:15 +01:00
Eugen Rochko
f64af6473f Bump version to 2.3.2rc4 2018-03-20 23:49:24 +01:00
Eugen Rochko
ac49c7932d Add LDAP_TLS_NO_VERIFY option, don't require LDAP_ENABLED outside .env (#6845)
Fix #6816, fix #6790
2018-03-20 19:41:51 +01:00
Akihiko Odaki
61dcb686a8 Fix i18n fallback configuration conflicts with environment configurations (#6843) 2018-03-20 16:36:20 +01:00
Eugen Rochko
9381a7d9d5 Use username/domain to match existing accounts in ActivityPub (#6842)
See also: #6837, #6667
2018-03-20 14:57:46 +01:00
ThibG
a5c6c748e0 Cancel outdated pending compose suggestions (#6838) 2018-03-20 12:40:12 +01:00
Rey Tucker
36b5703796 request: in the event of failure, try other IPs (#6761) (#6813)
* request: in the event of failure, try other IPs (#6761)

In the case where a name has multiple A/AAAA records, we should
try subsequent records instead of immediately failing when we have a
failure on the first IP address.

This significantly improves delivery success when there are network
connectivity problems affecting only IPv4 or IPv6.

* fix method call style

* request_spec: adjust test case to use Addrinfo

* request: Request/open: move private addr check to within begin/rescue

* request_spec: add case to test failover, fix exception check

* Double Addrinfo.foreach so that it correctly yields instances
2018-03-20 09:06:08 +01:00
ThibG
ff6b8a6443 Serialize mentions in the order they are added (#6836)
Up until now, the order seemed to be in the *opposite* order,
which caused the WebUI to populate mentions in reversed order
when replying to toots local to one's instance.
2018-03-19 20:19:35 +01:00
ThibG
6b76a6212d Display content warning in mail notification emails (#6832) 2018-03-19 20:12:20 +01:00
Alexander
33ee347c99 rename pam email environment variable to something more understandable and default to LOCAL_DOMAIN (better fallback) (#6833) 2018-03-19 20:09:26 +01:00
Alexander
0306e3e9be bugfixes and gem update (#6831)
* update to new version of devise_pam_authenticatable2

* fix behaviour if suffix is nil, fix environment loading, fix user email creation

* code cleanup/fix linter warning
2018-03-19 20:08:56 +01:00
ThibG
357f9298bd Fix e-mail changed notification (fixes #6778) (#6835)
In Devise::Mailer#email_changed, the new email might be in the email attr.
See: https://github.com/plataformatec/devise/blob/master/app/views/devise/mailer/email_changed.html.erb
2018-03-19 20:07:47 +01:00
Renato "Lond" Cerqueira
f7c46fc113 Weblate translations 20180319 (#6827)
* Translated using Weblate (Portuguese (Brazil))

Currently translated at 100.0% (280 of 280 strings)

Translation: Mastodon/React
Translate-URL: https://weblate.joinmastodon.org/projects/mastodon/frontend/pt_BR/

* Translated using Weblate (Spanish)

Currently translated at 99.6% (579 of 581 strings)

Translation: Mastodon/Backend
Translate-URL: https://weblate.joinmastodon.org/projects/mastodon/backend/es/

* Translated using Weblate (Spanish)

Currently translated at 100.0% (58 of 58 strings)

Translation: Mastodon/Preferences
Translate-URL: https://weblate.joinmastodon.org/projects/mastodon/simple_form/es/

* Translated using Weblate (Japanese)

Currently translated at 100.0% (280 of 280 strings)

Translation: Mastodon/React
Translate-URL: https://weblate.joinmastodon.org/projects/mastodon/frontend/ja/

* Translated using Weblate (French)

Currently translated at 100.0% (280 of 280 strings)

Translation: Mastodon/React
Translate-URL: https://weblate.joinmastodon.org/projects/mastodon/frontend/fr/

* Translated using Weblate (Spanish)

Currently translated at 100.0% (58 of 58 strings)

Translation: Mastodon/Preferences
Translate-URL: https://weblate.joinmastodon.org/projects/mastodon/simple_form/es/

* Translated using Weblate (Indonesian)

Currently translated at 100.0% (280 of 280 strings)

Translation: Mastodon/React
Translate-URL: https://weblate.joinmastodon.org/projects/mastodon/frontend/id/

* Translated using Weblate (Spanish)

Currently translated at 100.0% (280 of 280 strings)

Translation: Mastodon/React
Translate-URL: https://weblate.joinmastodon.org/projects/mastodon/frontend/es/

* Translated using Weblate (Indonesian)

Currently translated at 94.6% (71 of 75 strings)

Translation: Mastodon/Doorkeeper
Translate-URL: https://weblate.joinmastodon.org/projects/mastodon/doorkeeper/id/

* Translated using Weblate (Indonesian)

Currently translated at 100.0% (280 of 280 strings)

Translation: Mastodon/React
Translate-URL: https://weblate.joinmastodon.org/projects/mastodon/frontend/id/

* Translated using Weblate (Arabic)

Currently translated at 100.0% (62 of 62 strings)

Translation: Mastodon/Devise
Translate-URL: https://weblate.joinmastodon.org/projects/mastodon/devise/ar/

* Translated using Weblate (Indonesian)

Currently translated at 100.0% (280 of 280 strings)

Translation: Mastodon/React
Translate-URL: https://weblate.joinmastodon.org/projects/mastodon/frontend/id/

* Translated using Weblate (Dutch)

Currently translated at 100.0% (280 of 280 strings)

Translation: Mastodon/React
Translate-URL: https://weblate.joinmastodon.org/projects/mastodon/frontend/nl/

* Translated using Weblate (Arabic)

Currently translated at 75.5% (439 of 581 strings)

Translation: Mastodon/Backend
Translate-URL: https://weblate.joinmastodon.org/projects/mastodon/backend/ar/

* Translated using Weblate (Arabic)

Currently translated at 76.2% (443 of 581 strings)

Translation: Mastodon/Backend
Translate-URL: https://weblate.joinmastodon.org/projects/mastodon/backend/ar/

* Translated using Weblate (Russian)

Currently translated at 95.8% (557 of 581 strings)

Translation: Mastodon/Backend
Translate-URL: https://weblate.joinmastodon.org/projects/mastodon/backend/ru/

* Translated using Weblate (Finnish)

Currently translated at 100.0% (280 of 280 strings)

Translation: Mastodon/React
Translate-URL: https://weblate.joinmastodon.org/projects/mastodon/frontend/fi/

* Translated using Weblate (Slovak)

Currently translated at 100.0% (280 of 280 strings)

Translation: Mastodon/React
Translate-URL: https://weblate.joinmastodon.org/projects/mastodon/frontend/sk/

* Translated using Weblate (Slovak)

Currently translated at 91.7% (533 of 581 strings)

Translation: Mastodon/Backend
Translate-URL: https://weblate.joinmastodon.org/projects/mastodon/backend/sk/

* Translated using Weblate (Dutch)

Currently translated at 100.0% (280 of 280 strings)

Translation: Mastodon/React
Translate-URL: https://weblate.joinmastodon.org/projects/mastodon/frontend/nl/

* Translated using Weblate (Spanish)

Currently translated at 99.8% (580 of 581 strings)

Translation: Mastodon/Backend
Translate-URL: https://weblate.joinmastodon.org/projects/mastodon/backend/es/

* Translated using Weblate (Finnish)

Currently translated at 100.0% (280 of 280 strings)

Translation: Mastodon/React
Translate-URL: https://weblate.joinmastodon.org/projects/mastodon/frontend/fi/

* Translated using Weblate (Slovak)

Currently translated at 100.0% (280 of 280 strings)

Translation: Mastodon/React
Translate-URL: https://weblate.joinmastodon.org/projects/mastodon/frontend/sk/

* Translated using Weblate (Spanish)

Currently translated at 100.0% (280 of 280 strings)

Translation: Mastodon/React
Translate-URL: https://weblate.joinmastodon.org/projects/mastodon/frontend/es/

* Translated using Weblate (Swedish)

Currently translated at 100.0% (280 of 280 strings)

Translation: Mastodon/React
Translate-URL: https://weblate.joinmastodon.org/projects/mastodon/frontend/sv/

* Translated using Weblate (Finnish)

Currently translated at 93.1% (54 of 58 strings)

Translation: Mastodon/Preferences
Translate-URL: https://weblate.joinmastodon.org/projects/mastodon/simple_form/fi/

* Translated using Weblate (Arabic)

Currently translated at 76.7% (446 of 581 strings)

Translation: Mastodon/Backend
Translate-URL: https://weblate.joinmastodon.org/projects/mastodon/backend/ar/

* Translated using Weblate (Slovak)

Currently translated at 93.2% (542 of 581 strings)

Translation: Mastodon/Backend
Translate-URL: https://weblate.joinmastodon.org/projects/mastodon/backend/sk/

* Translated using Weblate (Arabic)

Currently translated at 100.0% (280 of 280 strings)

Translation: Mastodon/React
Translate-URL: https://weblate.joinmastodon.org/projects/mastodon/frontend/ar/

* Normalize translations
Ran i18n-tasks normalize && yarn manage:translations
2018-03-19 15:12:06 +01:00
Eugen Rochko
74c39fada0 Bump version to 2.3.2rc3 2018-03-19 12:20:57 +01:00
Eugen Rochko
f02411da40 Ignore media validation when attaching to status during processing (#6822)
Fix #6821
2018-03-19 01:51:19 +01:00
Eugen Rochko
a568e3ca8e Revert #6479, hide sensitive text/images from OpenGraph previews (#6818)
Display summary of attachments in description, and mark up content
warning if present, e.g.:

    Attached: 3 images · Content warning: Dota 2

When text is not supposed to be hidden, it looks more like:

    Attached: 3 images

    Here is the text of the toot

With #6817, multilinguagility should be assured...
2018-03-18 20:33:07 +01:00
Eugen Rochko
3b440bd5af Fix elephant graphic being draggable and selectable (#6819) 2018-03-18 20:32:44 +01:00
Eugen Rochko
39f27b6cf3 If DEFAULT_LOCALE is set, enforce it instead of HTTP request locale (#6817)
Fix #6784
2018-03-18 16:57:04 +01:00
Akihiko Odaki
721234230c Synchronize HTML page cache with sessions (#6815) 2018-03-18 15:14:38 +01:00
nightpool
566ace2d64 Add entropy to download filenames (#6811)
pretty quick fix, and with the 1 week expiration i don't think we need to be too worried about the existing files

closes #6798
2018-03-17 17:39:28 +01:00
Eugen Rochko
092f1df9d0 Bump version to 2.3.2rc2 2018-03-17 15:28:52 +01:00
Eugen Rochko
844616e950 Re-add git and nodejs-npm to Dockerfile (#6810)
Fix #6809

I don't know why, either
2018-03-17 15:28:09 +01:00
Eugen Rochko
40871caa4b Revert "Upgrade Paperclip to version 6.0.0" (#6807)
* Revert "Bump version to 2.3.2rc1"

This reverts commit cdf8b92fea.

* Revert "Downgrade Dockerfile to Ruby 2.4.3 on Alpine 3.6 (#6806)"

This reverts commit 0074cad44f.

* Revert "Handle Mastodon::HostValidationError when pulling remoteable assets (#6782)"

This reverts commit 4a0a19fe54.

* Revert "Correct the reference to user's password in mastodon:add_user task (#6800)"

This reverts commit 338bff8b93.

* Revert "Upgrade Paperclip to version 6.0.0 (#6754)"

This reverts commit b88fcd53f7.
2018-03-17 14:20:35 +01:00
Eugen Rochko
cdf8b92fea Bump version to 2.3.2rc1 2018-03-17 14:07:00 +01:00
Eugen Rochko
0074cad44f Downgrade Dockerfile to Ruby 2.4.3 on Alpine 3.6 (#6806)
Fix 6734
2018-03-17 14:06:25 +01:00
Daniel Hunsaker
4a0a19fe54 Handle Mastodon::HostValidationError when pulling remoteable assets (#6782)
This will prevent, for example, `rake mastodon:redownload_avatars` from crashing when an instance is no longer responding to connection attempts, instead silently continuing as expected.
2018-03-17 13:27:50 +01:00
Akihiko Odaki
338bff8b93 Correct the reference to user's password in mastodon:add_user task (#6800) 2018-03-17 13:27:19 +01:00
Yamagishi Kazutoshi
b88fcd53f7 Upgrade Paperclip to version 6.0.0 (#6754) 2018-03-17 12:37:58 +01:00
trwnh
ca7e6a6d2e Properly center .nothing-here (#6787) (#6788)
Apply "margin: 0 auto;" at line 443 to fix issue #6787
2018-03-17 12:35:35 +01:00
Akihiko Odaki
f0cd957c7a Cache HTML page with Service Worker (#6802)
This is the first step to make Mastodon work offline. It is also required
by Chromium to trigger Web Manifest automated install prompt.
2018-03-17 12:35:13 +01:00
Daniel Hunsaker
64fc8d2b07 [Nanobox] Stream backups to the warehouse (#6799)
The `curl` docs are terrible. Use `-X POST -T` instead of `--data-binary`, to avoid loading entire backups into memory _before_ transferring to the warehouse, and just stream the data across as it comes in.
2018-03-17 08:39:14 +01:00
Marcin Mikołajczak
fd385e256d i18n: Update Polish translation (#6780)
Signed-off-by: Marcin Mikołajczak <me@m4sk.in>
2018-03-14 02:17:48 +09:00
Renato "Lond" Cerqueira
03119c857b Weblate translations (2018-03-13) (#6777)
* Translated using Weblate (Portuguese (Brazil))

Currently translated at 100.0% (280 of 280 strings)

Translation: Mastodon/React
Translate-URL: https://weblate.joinmastodon.org/projects/mastodon/frontend/pt_BR/

* Translated using Weblate (Spanish)

Currently translated at 99.6% (579 of 581 strings)

Translation: Mastodon/Backend
Translate-URL: https://weblate.joinmastodon.org/projects/mastodon/backend/es/

* Translated using Weblate (Spanish)

Currently translated at 100.0% (58 of 58 strings)

Translation: Mastodon/Preferences
Translate-URL: https://weblate.joinmastodon.org/projects/mastodon/simple_form/es/

* Translated using Weblate (Japanese)

Currently translated at 100.0% (280 of 280 strings)

Translation: Mastodon/React
Translate-URL: https://weblate.joinmastodon.org/projects/mastodon/frontend/ja/

* Translated using Weblate (French)

Currently translated at 100.0% (280 of 280 strings)

Translation: Mastodon/React
Translate-URL: https://weblate.joinmastodon.org/projects/mastodon/frontend/fr/

* Translated using Weblate (Spanish)

Currently translated at 100.0% (58 of 58 strings)

Translation: Mastodon/Preferences
Translate-URL: https://weblate.joinmastodon.org/projects/mastodon/simple_form/es/

* Translated using Weblate (Indonesian)

Currently translated at 100.0% (280 of 280 strings)

Translation: Mastodon/React
Translate-URL: https://weblate.joinmastodon.org/projects/mastodon/frontend/id/

* Translated using Weblate (Spanish)

Currently translated at 100.0% (280 of 280 strings)

Translation: Mastodon/React
Translate-URL: https://weblate.joinmastodon.org/projects/mastodon/frontend/es/

* Translated using Weblate (Indonesian)

Currently translated at 94.6% (71 of 75 strings)

Translation: Mastodon/Doorkeeper
Translate-URL: https://weblate.joinmastodon.org/projects/mastodon/doorkeeper/id/

* Translated using Weblate (Indonesian)

Currently translated at 100.0% (280 of 280 strings)

Translation: Mastodon/React
Translate-URL: https://weblate.joinmastodon.org/projects/mastodon/frontend/id/

* Translated using Weblate (Arabic)

Currently translated at 100.0% (62 of 62 strings)

Translation: Mastodon/Devise
Translate-URL: https://weblate.joinmastodon.org/projects/mastodon/devise/ar/

* Translated using Weblate (Indonesian)

Currently translated at 100.0% (280 of 280 strings)

Translation: Mastodon/React
Translate-URL: https://weblate.joinmastodon.org/projects/mastodon/frontend/id/

* Translated using Weblate (Dutch)

Currently translated at 100.0% (280 of 280 strings)

Translation: Mastodon/React
Translate-URL: https://weblate.joinmastodon.org/projects/mastodon/frontend/nl/

* Translated using Weblate (Arabic)

Currently translated at 75.5% (439 of 581 strings)

Translation: Mastodon/Backend
Translate-URL: https://weblate.joinmastodon.org/projects/mastodon/backend/ar/

* Translated using Weblate (Arabic)

Currently translated at 76.2% (443 of 581 strings)

Translation: Mastodon/Backend
Translate-URL: https://weblate.joinmastodon.org/projects/mastodon/backend/ar/

* Translated using Weblate (Russian)

Currently translated at 95.8% (557 of 581 strings)

Translation: Mastodon/Backend
Translate-URL: https://weblate.joinmastodon.org/projects/mastodon/backend/ru/

* Normalize translations
Ran i18n-tasks normalize && yarn manage:translations
2018-03-13 16:16:51 +01:00
Eugen Rochko
2ef1ce1182 Bump version to 2.3.1 2018-03-13 15:50:15 +01:00
Eugen Rochko
eb2425b53b Hide loading bar on status interactions (#6774) 2018-03-13 14:30:01 +01:00
Eugen Rochko
79d3a8553f Bump version to 2.3.1rc3 2018-03-13 09:21:21 +01:00
Eugen Rochko
7709556673 Merge branch 'kagucho-spoiler' 2018-03-13 09:18:27 +01:00
Eugen Rochko
f0ae6b4cc5 Merge branch 'spoiler' of git://github.com/kagucho/mastodon into kagucho-spoiler 2018-03-13 09:18:11 +01:00
Eugen Rochko
9e3a6d6784 Log BackupWorker backtrace, delete Backup if retries exhausted (#6769) 2018-03-13 08:15:24 +01:00
Eugen Rochko
8bf3e750ab Fix #6757: Adjust RTL styles for landing page (#6768) 2018-03-13 08:14:08 +01:00
Akihiko Odaki
18241ccbe1 Change the title of sensitive button by state (#6771)
Icon showing the state may be confusing. (does the slahed eye icon mean
the state that it is sensitive, or to mark it as sensitive?) Moreover, it
may not help for blind people.

The title will give the precise representation of the current state.
2018-03-13 08:10:12 +01:00
Akihiko Odaki
0dccb398bd Change the title of spoiler button by state
The title will give the precise representation of the current state. It
would be helpful for blind people.
2018-03-13 15:58:55 +09:00
Eugen Rochko
386365090c Fix #6762: Do not overwrite some status attributes in reducer (#6767) 2018-03-13 07:16:43 +01:00
Daniel Hunsaker
d9500c8a3b [Nanobox] Fix DB backup task (#6766)
Not sure how I missed that it had been using the wrong evar this entire time...
2018-03-13 06:07:02 +01:00
Akihiko Odaki
f7c1668bf6 Do not run lint in Travis CI (#6763)
Lint is done by codeclimate
2018-03-13 06:06:14 +01:00
Ushitora Anqou
051b649628 Detailed SMTP setup (#6759)
* add detailed SMTP settings setup in mastodon:setup

* add localhost SMTP settings setup in mastodon:setup

* SMTP settings setup should exit after successful delivery of test mail
2018-03-12 21:41:26 +01:00
艮 鮟鱇
f5f165a5eb set SAFETY_ASSURED=1 of db:setup in mastodon:setup (#6758) 2018-03-12 16:21:48 +01:00
Eugen Rochko
f89ff65ec7 Bump version to 2.3.1rc2 2018-03-12 12:48:49 +01:00
Akihiko Odaki
48b940d5c6 Insert space before shortcode if necessary (#6751) 2018-03-12 12:47:51 +01:00
Yamagishi Kazutoshi
6ae70a92c9 Hide pinned toots on with replies (#6753) 2018-03-12 12:47:18 +01:00
Yuto Tokunaga
fa5c867e0e Avoid using JS to set height in MediaModal (#6750)
avoid using JS to set height of ReactSwipeableViews component
reduce max-height of <img/> to 80% to avoid the screen covered by image
2018-03-12 03:52:05 +01:00
nightpool
641abe2db7 Fix Procfile on OS X (#6748) 2018-03-12 03:50:40 +01:00
Eugen Rochko
4f7f6b3922 Fix follow relationships not loading after notifications fetch (#6746) 2018-03-12 03:20:56 +01:00
Yamagishi Kazutoshi
8b14726f5b Weblate translations (2018-03-11) (#6742)
* Translated using Weblate (Japanese)

Currently translated at 100.0% (276 of 276 strings)

Translation: Mastodon/React
Translate-URL: https://weblate.joinmastodon.org/projects/mastodon/frontend/ja/

* Translated using Weblate (Finnish)

Currently translated at 100.0% (276 of 276 strings)

Translation: Mastodon/React
Translate-URL: https://weblate.joinmastodon.org/projects/mastodon/frontend/fi/

* Translated using Weblate (Finnish)

Currently translated at 25.1% (146 of 581 strings)

Translation: Mastodon/Backend
Translate-URL: https://weblate.joinmastodon.org/projects/mastodon/backend/fi/

* Translated using Weblate (Slovak)

Currently translated at 91.2% (530 of 581 strings)

Translation: Mastodon/Backend
Translate-URL: https://weblate.joinmastodon.org/projects/mastodon/backend/sk/

* Translated using Weblate (Arabic)

Currently translated at 67.6% (393 of 581 strings)

Translation: Mastodon/Backend
Translate-URL: https://weblate.joinmastodon.org/projects/mastodon/backend/ar/

* Translated using Weblate (Swedish)

Currently translated at 100.0% (58 of 58 strings)

Translation: Mastodon/Preferences
Translate-URL: https://weblate.joinmastodon.org/projects/mastodon/simple_form/sv/

* Translated using Weblate (Swedish)

Currently translated at 100.0% (581 of 581 strings)

Translation: Mastodon/Backend
Translate-URL: https://weblate.joinmastodon.org/projects/mastodon/backend/sv/

* Translated using Weblate (Finnish)

Currently translated at 100.0% (276 of 276 strings)

Translation: Mastodon/React
Translate-URL: https://weblate.joinmastodon.org/projects/mastodon/frontend/fi/

* Translated using Weblate (Finnish)

Currently translated at 92.0% (69 of 75 strings)

Translation: Mastodon/Doorkeeper
Translate-URL: https://weblate.joinmastodon.org/projects/mastodon/doorkeeper/fi/

* Translated using Weblate (Finnish)

Currently translated at 60.3% (35 of 58 strings)

Translation: Mastodon/Preferences
Translate-URL: https://weblate.joinmastodon.org/projects/mastodon/simple_form/fi/

* Translated using Weblate (Swedish)

Currently translated at 100.0% (75 of 75 strings)

Translation: Mastodon/Doorkeeper
Translate-URL: https://weblate.joinmastodon.org/projects/mastodon/doorkeeper/sv/

* Translated using Weblate (Swedish)

Currently translated at 100.0% (276 of 276 strings)

Translation: Mastodon/React
Translate-URL: https://weblate.joinmastodon.org/projects/mastodon/frontend/sv/

* Translated using Weblate (Arabic)

Currently translated at 68.3% (397 of 581 strings)

Translation: Mastodon/Backend
Translate-URL: https://weblate.joinmastodon.org/projects/mastodon/backend/ar/

* Translated using Weblate (Arabic)

Currently translated at 99.2% (274 of 276 strings)

Translation: Mastodon/React
Translate-URL: https://weblate.joinmastodon.org/projects/mastodon/frontend/ar/

* Translated using Weblate (Slovak)

Currently translated at 100.0% (276 of 276 strings)

Translation: Mastodon/React
Translate-URL: https://weblate.joinmastodon.org/projects/mastodon/frontend/sk/

* Translated using Weblate (Arabic)

Currently translated at 73.8% (429 of 581 strings)

Translation: Mastodon/Backend
Translate-URL: https://weblate.joinmastodon.org/projects/mastodon/backend/ar/

* Translated using Weblate (Slovak)

Currently translated at 91.2% (530 of 581 strings)

Translation: Mastodon/Backend
Translate-URL: https://weblate.joinmastodon.org/projects/mastodon/backend/sk/

* bundle exec i18n-tasks normalize && yarn manage:translations
2018-03-11 16:21:26 +01:00
Marcin Mikołajczak
9090b63831 i18n: Update Polish translation and “yarn manage:translations” (#6743)
Signed-off-by: Marcin Mikołajczak <me@m4sk.in>
2018-03-11 16:21:16 +01:00
Eugen Rochko
ab27dccba5 Bump version to 2.3.1rc1 2018-03-11 15:13:13 +01:00
Eugen Rochko
56eb5c3f34 Fix focal point cropping in MediaGallery, fix focal point modal (#6740)
* Use object-position with object-fit instead of JS top/left

* Fix focal point modal
2018-03-11 15:12:33 +01:00
TrashMacNugget
56333cca88 Add license info to README (#6583)
* Add license info to README

* Reference AUTHORS file
2018-03-11 15:12:23 +01:00
Eugen Rochko
1aaec701bb Fix #6715: Make catalan words with the L geminate letter work in hashtags (#6741) 2018-03-11 14:55:49 +01:00
Konrad Pozniak
cd252b794e add new avatar placeholder missing.png (#6728) 2018-03-11 14:55:38 +01:00
Eugen Rochko
b6003afcdb Add show more/less toggle for entire threads in web UI (#6733)
Fix #1258
2018-03-11 09:52:59 +01:00
Marcin Mikołajczak
f5ee2d469b i18n: Update Polish translation (#6731)
Signed-off-by: Marcin Mikołajczak <me@m4sk.in>
2018-03-11 01:56:30 +09:00
abcang
37b043d447 Improve performance of account_media_status_ids (#6729) 2018-03-10 17:44:26 +01:00
Akihiko Odaki
36579bac88 Use Alpine Linux yarn package in Docker (#6725)
Yarn was manually installed to meet the Yarn version requirement of
webpacker. Today, Alpine Linux 3.7 provides Yarn new enough.
2018-03-10 11:49:04 +01:00
Eugen Rochko
4476a45444 Fix #6717: Do not double html-encode page titles (#6720) 2018-03-10 11:43:20 +01:00
Akihiko Odaki
58a4633707 Remove su-exec from Docker image (#6722)
It is no longer necessary since commit
be9bab171d.
2018-03-10 11:42:42 +01:00
Akihiko Odaki
494969d394 Remove git from Docker image (#6724) 2018-03-10 11:42:28 +01:00
Espen Rønnevik
c1a41181c5 docs: Add AUTHORS file (#6685)
* Add AUTHORS file with information taken from the git log. Authors are listed in order of appearance.

* Remove AUTHORS file with raw log information

* Add AUTHORS.md listing contributors with GitHub usernames and profile links

* Update AUTHORS.md
2018-03-09 13:11:43 +01:00
Eugen Rochko
6e309f5e45 Bump version to 2.3.0 2018-03-09 12:59:58 +01:00
Eugen Rochko
e5f18ace2a When inside Docker, output saved configuration during mastodon:setup (#6711) 2018-03-09 11:52:18 +01:00
Eugen Rochko
11697d6894 Fix thumbnail not filling entire space sometimes (#6709) 2018-03-09 11:33:05 +01:00
Eugen Rochko
675b8fea53 Adjust suggested ES host in .env sample for docker-compose config (#6710) 2018-03-09 11:32:55 +01:00
Yamagishi Kazutoshi
4c16ddf588 Change avatar size on form hints (#6707) 2018-03-09 10:57:33 +01:00
Renato "Lond" Cerqueira
5ba4c36f95 Weblate translations (2018-03-09) (#6708)
* Translated using Weblate (Galician)

Currently translated at 100.0% (580 of 580 strings)

Translation: Mastodon/Backend
Translate-URL: https://weblate.joinmastodon.org/projects/mastodon/backend/gl/

* Translated using Weblate (Dutch)

Currently translated at 100.0% (580 of 580 strings)

Translation: Mastodon/Backend
Translate-URL: https://weblate.joinmastodon.org/projects/mastodon/backend/nl/

* Translated using Weblate (Japanese)

Currently translated at 99.8% (579 of 580 strings)

Translation: Mastodon/Backend
Translate-URL: https://weblate.joinmastodon.org/projects/mastodon/backend/ja/

* Translated using Weblate (Dutch)

Currently translated at 100.0% (581 of 581 strings)

Translation: Mastodon/Backend
Translate-URL: https://weblate.joinmastodon.org/projects/mastodon/backend/nl/

* Translated using Weblate (Dutch)

Currently translated at 100.0% (276 of 276 strings)

Translation: Mastodon/React
Translate-URL: https://weblate.joinmastodon.org/projects/mastodon/frontend/nl/

* Translated using Weblate (Portuguese (Brazil))

Currently translated at 100.0% (276 of 276 strings)

Translation: Mastodon/React
Translate-URL: https://weblate.joinmastodon.org/projects/mastodon/frontend/pt_BR/

* Translated using Weblate (French)

Currently translated at 99.8% (580 of 581 strings)

Translation: Mastodon/Backend
Translate-URL: https://weblate.joinmastodon.org/projects/mastodon/backend/fr/

* Translated using Weblate (German)

Currently translated at 99.3% (577 of 581 strings)

Translation: Mastodon/Backend
Translate-URL: https://weblate.joinmastodon.org/projects/mastodon/backend/de/

* Translated using Weblate (German)

Currently translated at 99.3% (577 of 581 strings)

Translation: Mastodon/Backend
Translate-URL: https://weblate.joinmastodon.org/projects/mastodon/backend/de/

* Translated using Weblate (Portuguese (Brazil))

Currently translated at 100.0% (62 of 62 strings)

Translation: Mastodon/Devise
Translate-URL: https://weblate.joinmastodon.org/projects/mastodon/devise/pt_BR/

* Translated using Weblate (Portuguese (Brazil))

Currently translated at 99.8% (580 of 581 strings)

Translation: Mastodon/Backend
Translate-URL: https://weblate.joinmastodon.org/projects/mastodon/backend/pt_BR/

* Translated using Weblate (Japanese)

Currently translated at 99.8% (580 of 581 strings)

Translation: Mastodon/Backend
Translate-URL: https://weblate.joinmastodon.org/projects/mastodon/backend/ja/

* Translated using Weblate (German)

Currently translated at 100.0% (276 of 276 strings)

Translation: Mastodon/React
Translate-URL: https://weblate.joinmastodon.org/projects/mastodon/frontend/de/

* Translated using Weblate (Catalan)

Currently translated at 100.0% (581 of 581 strings)

Translation: Mastodon/Backend
Translate-URL: https://weblate.joinmastodon.org/projects/mastodon/backend/ca/

* Translated using Weblate (French)

Currently translated at 100.0% (276 of 276 strings)

Translation: Mastodon/React
Translate-URL: https://weblate.joinmastodon.org/projects/mastodon/frontend/fr/

* Translated using Weblate (French)

Currently translated at 99.8% (580 of 581 strings)

Translation: Mastodon/Backend
Translate-URL: https://weblate.joinmastodon.org/projects/mastodon/backend/fr/

* Translated using Weblate (Slovak)

Currently translated at 100.0% (276 of 276 strings)

Translation: Mastodon/React
Translate-URL: https://weblate.joinmastodon.org/projects/mastodon/frontend/sk/

* Translated using Weblate (Polish)

Currently translated at 99.8% (580 of 581 strings)

Translation: Mastodon/Backend
Translate-URL: https://weblate.joinmastodon.org/projects/mastodon/backend/pl/

* Translated using Weblate (Esperanto)

Currently translated at 100.0% (581 of 581 strings)

Translation: Mastodon/Backend
Translate-URL: https://weblate.joinmastodon.org/projects/mastodon/backend/eo/

* Translated using Weblate (Esperanto)

Currently translated at 100.0% (581 of 581 strings)

Translation: Mastodon/Backend
Translate-URL: https://weblate.joinmastodon.org/projects/mastodon/backend/eo/

* Translated using Weblate (Esperanto)

Currently translated at 100.0% (276 of 276 strings)

Translation: Mastodon/React
Translate-URL: https://weblate.joinmastodon.org/projects/mastodon/frontend/eo/

* Translated using Weblate (Slovak)

Currently translated at 90.3% (525 of 581 strings)

Translation: Mastodon/Backend
Translate-URL: https://weblate.joinmastodon.org/projects/mastodon/backend/sk/

* Translated using Weblate (Japanese)

Currently translated at 99.8% (580 of 581 strings)

Translation: Mastodon/Backend
Translate-URL: https://weblate.joinmastodon.org/projects/mastodon/backend/ja/

* Translated using Weblate (Galician)

Currently translated at 100.0% (581 of 581 strings)

Translation: Mastodon/Backend
Translate-URL: https://weblate.joinmastodon.org/projects/mastodon/backend/gl/

* Normalize translations
Ran i18n-tasks normalize && yarn manage:translations
2018-03-09 18:38:13 +09:00
Jeong Arm
ccd53e983c Missing Korean translations (#6703) 2018-03-09 14:21:47 +09:00
Eugen Rochko
ff44b2e92d Add missing meta description to profiles, some other SEO stuff (#6706)
- Add missing meta description to profiles
- Add canonical rel link to landing page
- Remove linebreaks from title tags
- Add username to profile title
- Add toots/following/followers to profile description tags
- Add next/prev rel links to profiles
- Do not index follower/following variants of profiles
2018-03-09 00:35:07 +01:00
Yamagishi Kazutoshi
188aa3ea50 Add polyfill for Object.values (#6697) 2018-03-08 13:07:25 +01:00
Eugen Rochko
bd077ad7d9 Bump version to 2.3.0rc3 2018-03-08 11:19:02 +01:00
Eugen Rochko
a29d409e20 If login redirects to omniauth, redirect logout to root_path (#6694)
Fix #6670
2018-03-08 11:18:26 +01:00
Eugen Rochko
5acd5315f2 Improve styling of closed registrations message, rename button (#6695)
* Improve styling of closed registrations message, rename button

"Sign up on another server"

Fix #6683

* Adjust styling of closed registrations message
2018-03-08 11:10:37 +01:00
Eugen Rochko
b79ab15859 When enabled, always display media in gallery. Also: click to reveal (#6692)
Fix #6677
2018-03-08 08:57:21 +01:00
Eugen Rochko
77406d3a09 Display AttachmentList in notifications (#6693) 2018-03-08 08:22:04 +01:00
Eugen Rochko
510c9049c7 For now, put a "." into no-text statuses with media for backcompat (#6691) 2018-03-08 08:20:49 +01:00
Yamagishi Kazutoshi
ed902581d3 Update Yarn to version 1.5.1 (#6689) 2018-03-08 07:09:10 +01:00
Eugen Rochko
64db9ed5f6 After blocking domain with reject_media, invalidate cache (#6679)
Media attachments are part of the association cache of statuses,
since they are presumed to be immutable. Unless this cache is
cleared manually, the statuses will continue to look like they
have media embedded.
2018-03-08 06:59:42 +01:00
Yamagishi Kazutoshi
1085ef3836 Weblate translations (2018-03-08) (#6690)
* Translated using Weblate (French)

Currently translated at 99.8% (578 of 579 strings)

Translation: Mastodon/Backend
Translate-URL: https://weblate.joinmastodon.org/projects/mastodon/backend/fr/

* Translated using Weblate (French)

Currently translated at 99.8% (578 of 579 strings)

Translation: Mastodon/Backend
Translate-URL: https://weblate.joinmastodon.org/projects/mastodon/backend/fr/

* Translated using Weblate (French)

Currently translated at 99.8% (578 of 579 strings)

Translation: Mastodon/Backend
Translate-URL: https://weblate.joinmastodon.org/projects/mastodon/backend/fr/

* Translated using Weblate (French)

Currently translated at 100.0% (275 of 275 strings)

Translation: Mastodon/React
Translate-URL: https://weblate.joinmastodon.org/projects/mastodon/frontend/fr/

* Translated using Weblate (Slovak)

Currently translated at 85.3% (494 of 579 strings)

Translation: Mastodon/Backend
Translate-URL: https://weblate.joinmastodon.org/projects/mastodon/backend/sk/

* Translated using Weblate (Slovak)

Currently translated at 100.0% (275 of 275 strings)

Translation: Mastodon/React
Translate-URL: https://weblate.joinmastodon.org/projects/mastodon/frontend/sk/

* Translated using Weblate (Japanese)

Currently translated at 100.0% (275 of 275 strings)

Translation: Mastodon/React
Translate-URL: https://weblate.joinmastodon.org/projects/mastodon/frontend/ja/

* Translated using Weblate (Portuguese (Brazil))

Currently translated at 100.0% (275 of 275 strings)

Translation: Mastodon/React
Translate-URL: https://weblate.joinmastodon.org/projects/mastodon/frontend/pt_BR/

* Translated using Weblate (Portuguese (Brazil))

Currently translated at 99.8% (578 of 579 strings)

Translation: Mastodon/Backend
Translate-URL: https://weblate.joinmastodon.org/projects/mastodon/backend/pt_BR/

* Translated using Weblate (Japanese)

Currently translated at 100.0% (275 of 275 strings)

Translation: Mastodon/React
Translate-URL: https://weblate.joinmastodon.org/projects/mastodon/frontend/ja/

* Translated using Weblate (Arabic)

Currently translated at 99.2% (273 of 275 strings)

Translation: Mastodon/React
Translate-URL: https://weblate.joinmastodon.org/projects/mastodon/frontend/ar/

* Translated using Weblate (Japanese)

Currently translated at 100.0% (275 of 275 strings)

Translation: Mastodon/React
Translate-URL: https://weblate.joinmastodon.org/projects/mastodon/frontend/ja/

* Translated using Weblate (Arabic)

Currently translated at 66.8% (387 of 579 strings)

Translation: Mastodon/Backend
Translate-URL: https://weblate.joinmastodon.org/projects/mastodon/backend/ar/

* Translated using Weblate (Arabic)

Currently translated at 93.1% (54 of 58 strings)

Translation: Mastodon/Preferences
Translate-URL: https://weblate.joinmastodon.org/projects/mastodon/simple_form/ar/

* Translated using Weblate (French)

Currently translated at 99.8% (578 of 579 strings)

Translation: Mastodon/Backend
Translate-URL: https://weblate.joinmastodon.org/projects/mastodon/backend/fr/

* Translated using Weblate (Slovak)

Currently translated at 87.7% (508 of 579 strings)

Translation: Mastodon/Backend
Translate-URL: https://weblate.joinmastodon.org/projects/mastodon/backend/sk/

* Translated using Weblate (Japanese)

Currently translated at 100.0% (275 of 275 strings)

Translation: Mastodon/React
Translate-URL: https://weblate.joinmastodon.org/projects/mastodon/frontend/ja/

* bundle exec i18n-tasks normalize && yarn manage:translations

* Remove ar.simple_form.hints.defaults.{display_name,note}
2018-03-08 06:59:34 +01:00
Eugen Rochko
86a9de6753 Display AttachmentList in timelines in compact style when media missing (#6680) 2018-03-08 04:54:26 +01:00
Yamagishi Kazutoshi
83c982b458 Run tests with npm-run-all (#6688) 2018-03-08 04:43:57 +01:00
Renato "Lond" Cerqueira
9aba44ea79 Rescue when there's no extension in the remotable (#6358)
* Rescue when there's no extension in the remotable
Sometimes the remotable is pointing to a directory with no file
extension. Maybe it should not be expecting to identify based on
extensions to begin with, but since it's the case, it should be ready
for it.

* Fix codeclimate issue

* Check if filename is nil instead of rescueing exception
Suggestion made in the PR

* Avoid concatenation issue if filename is nil
If filename is nil, extname was undefined

* Invert condition
Address PR comments
2018-03-08 02:25:10 +01:00
MitarashiDango
6dcf96271e fix validation error (media only status) (#6684)
* fix validation error (media only status)

* Incorporating review suggestions

* Reflect similar fix to OStatus side

* Fix not to include media in transaction

* Restore the limit of the number of media

* Fix not to return nil
2018-03-08 01:22:47 +01:00
Eugen Rochko
4ca60c665e Bump version to 2.3.0rc2 2018-03-07 12:06:23 +01:00
Eugen Rochko
b170627ceb Fix cover behaviour of thumbnails that are wider than taller (#6678) 2018-03-07 12:02:05 +01:00
Eugen Rochko
a1b065700a Fix focal point modals broken by #5956 (#6676) 2018-03-07 12:01:53 +01:00
Eugen Rochko
8de048fcdb In wide layout, columnize Mastodon features on landing page (#6674) 2018-03-07 08:59:27 +01:00
Eugen Rochko
cfa9b6e13a Remove text requirement when media attached from statuses (#6672) 2018-03-07 08:28:52 +01:00
vpzomtrrfrt
e26d5ca923 Don't escape statuses while truncating (#6671) 2018-03-07 07:12:01 +01:00
Effy Elden
dd9d00d293 Add additional first_name and last_name SAML attribute statement options, and modify Omniauthable concern to use full_name or first_name + last_name if not available (#6669) 2018-03-07 06:19:10 +01:00
Eugen Rochko
89a52d6280 Fix wrong target URIs in ActivityPub Add/Remove (#6668) 2018-03-07 05:58:24 +01:00
Eugen Rochko
e6520c0270 Fix #6657 - Use target instead of origin in Remove activity (#6664) 2018-03-07 03:54:46 +01:00
Akihiko Odaki
913a38111f Remove pointer events on the entire UI when a dropdown menu is open (#6648)
* Remove pointer events on the entire UI when a dropdown menu is open

This prevents operations to change the location of the menu such as
scrolling.

* Fix mistake from merge
2018-03-07 02:26:43 +01:00
Eugen Rochko
4847149b6e Always install LDAP, CAS and SAML gems, because they don't require deps (#6663)
Fix #6534

PAM requires a system dependency so...
2018-03-07 02:25:17 +01:00
Eugen Rochko
d7573fe584 Separate chown command in Dockerfile. Use tootsuite/mastodon image (#6662)
Fix #6605
2018-03-07 01:57:31 +01:00
Sylvhem
cb74c0cfe4 Add headings to the security settings page (#6661)
* Changes the headings' rank of the security settings section

This commit changes the existing headings' rank of the security settings section from level 6 to level 4.

* Renames the auth.change_password string into auth.security

The "Security" preferences' section used to be called "Change password". When it was renamed, the string name wasn't changed.
This commits changes auth.change_password to auth.security.

* Adds a heading to the password change form

There was previously no heading for the part of the "Security" page that contain the password change form.
This commit adds a rank 4 heading to this section and reintroduces an "auth.change_password" string to be used inside it.

* Removes useless HR elements

The various sections of the "Security" settings page were previously separated by HR elements.
Now that there is proper headings, they're not required anymore.

* Updates CSS

This commit updates CSS in such a way that the same style is applied to all the H4 elements of the settings.

* Correct a mistake

A character went missing on one of the previous commits, broking the CSS.
This new commit fixes it.
2018-03-07 01:39:40 +01:00
Daniel Hunsaker
b725924f0a [Nanobox] Tuning Update (#6660)
Various preformance and stability enhancements for instances deployed via Nanobox.
2018-03-06 21:59:35 +01:00
Akihiko Odaki
81cefc1913 Do not use npm (#6656)
Both of yarn and npm are used in Mastodon, but the combined usage requires
a redundant dependency and may lead to data inconsistency.

Considering that yarn has autoclean feature which npm does not have,
this change replaces all npm usage with yarn.

This change requires documentation update. Most notably, the following
command must be executed before assets precompilation if any system
dependency of node-sass has changed:

yarn install --force --pure-lockfile
2018-03-06 21:36:46 +01:00
Akihiko Odaki
a07cfee644 Extract columns area from UI component (#6650)
UI component used to toggle isComposing state by directly manipulating the
DOM element to avoid the expensive rendering.

However, it is hacky, and is not effective for other states. Instead,
this change makes the rendering cheaper by extracting the huge columns
area.
2018-03-06 07:45:31 +01:00
Akihiko Odaki
13cf92df27 Use React.PureComponent instead of React.Component (#6653) 2018-03-06 07:28:26 +01:00
Akihiko Odaki
61e6275781 Use withRouter for TabsBar (#6652)
TabsBar refers to router, which is a private context property of
react-router. withRouter is a recommended alternative. It also allows to
track location changes even if React.PureComponent is used.
2018-03-06 07:28:05 +01:00
Eugen Rochko
78d772af86 Fix #3807: Increase avatars to 400x400 max (#6651)
But do not upscale when they are smaller
2018-03-06 06:29:01 +01:00
Yamagishi Kazutoshi
e9e475a29d Upgrade chewy to version 5.0.0 (#6649) 2018-03-06 06:14:26 +01:00
Eugen Rochko
20d1be18af Fix accounts' display name/bio not being set from initial state (#6644) 2018-03-06 00:08:35 +01:00
Akihiko Odaki
b0664a5e6c Replace onScrollToBottom with onLoadMore (#6615)
onScrollToBottom was a function to run instead of onScrollToTop and
onScroll when scrolling to the bottom. The behavior to prevent
onScrollToTop was inconvenient because the viewport can be at the bottom
and at the top at the same time if the viewport is larger than the
container.

onScrollToBottom was also called when the button to load more is clicked
on contray to the name suggests, which led notifications and
status_list_container components to mark the scrolled location is not at
the top mistakenly.

onLoadMore is a replacement for onScrollToBottom. It will be called
independently from onScrollToTop and onScroll.
2018-03-05 19:31:40 +01:00
Akihiko Odaki
a38dbd9c8a Redirect from Web tag timeline to public tag timeline if not signed in (#6633)
This is also implemented in Pawoo:
ceafdbd1bb
2018-03-05 19:29:36 +01:00
Akihiko Odaki
f6a8d835d3 Place dropdown menu top if it is closer to the bottom of the viewport (#6641) 2018-03-05 19:28:56 +01:00
ThibG
4746feaa1c Add “Domain hidden” badge (#6636) 2018-03-05 16:45:36 +01:00
haosbvnker
3d4e788ea9 Fix permissions for volumes (#6637)
When volumes are declared, but the corresponding directories don't exist, permissions for those directories will be root:root instead of mastodon:mastodon..
This changes makes sure the permissions of the volume directories are as expected.
2018-03-05 16:45:09 +01:00
Renato "Lond" Cerqueira
bd40574476 Weblate translations (05-03-2018) (#6640)
* Translated using Weblate (French)

Currently translated at 100.0% (272 of 272 strings)

Translation: Mastodon/React
Translate-URL: https://weblate.joinmastodon.org/projects/mastodon/frontend/fr/

* Translated using Weblate (Japanese)

Currently translated at 98.9% (572 of 578 strings)

Translation: Mastodon/Backend
Translate-URL: https://weblate.joinmastodon.org/projects/mastodon/backend/ja/

* Translated using Weblate (Catalan)

Currently translated at 98.4% (569 of 578 strings)

Translation: Mastodon/Backend
Translate-URL: https://weblate.joinmastodon.org/projects/mastodon/backend/ca/

* Translated using Weblate (Catalan)

Currently translated at 100.0% (578 of 578 strings)

Translation: Mastodon/Backend
Translate-URL: https://weblate.joinmastodon.org/projects/mastodon/backend/ca/

* Translated using Weblate (Catalan)

Currently translated at 100.0% (579 of 579 strings)

Translation: Mastodon/Backend
Translate-URL: https://weblate.joinmastodon.org/projects/mastodon/backend/ca/

* Translated using Weblate (Portuguese (Brazil))

Currently translated at 99.8% (577 of 578 strings)

Translation: Mastodon/Backend
Translate-URL: https://weblate.joinmastodon.org/projects/mastodon/backend/pt_BR/

* Translated using Weblate (Portuguese (Brazil))

Currently translated at 100.0% (58 of 58 strings)

Translation: Mastodon/Preferences
Translate-URL: https://weblate.joinmastodon.org/projects/mastodon/simple_form/pt_BR/

* Translated using Weblate (French)

Currently translated at 99.8% (577 of 578 strings)

Translation: Mastodon/Backend
Translate-URL: https://weblate.joinmastodon.org/projects/mastodon/backend/fr/

* Translated using Weblate (French)

Currently translated at 100.0% (272 of 272 strings)

Translation: Mastodon/React
Translate-URL: https://weblate.joinmastodon.org/projects/mastodon/frontend/fr/

* Translated using Weblate (Esperanto)

Currently translated at 99.6% (576 of 578 strings)

Translation: Mastodon/Backend
Translate-URL: https://weblate.joinmastodon.org/projects/mastodon/backend/eo/

* Translated using Weblate (Esperanto)

Currently translated at 100.0% (58 of 58 strings)

Translation: Mastodon/Preferences
Translate-URL: https://weblate.joinmastodon.org/projects/mastodon/simple_form/eo/

* Translated using Weblate (Esperanto)

Currently translated at 100.0% (272 of 272 strings)

Translation: Mastodon/React
Translate-URL: https://weblate.joinmastodon.org/projects/mastodon/frontend/eo/

* Translated using Weblate (Arabic)

Currently translated at 100.0% (62 of 62 strings)

Translation: Mastodon/Devise
Translate-URL: https://weblate.joinmastodon.org/projects/mastodon/devise/ar/

* Translated using Weblate (Slovak)

Currently translated at 100.0% (272 of 272 strings)

Translation: Mastodon/React
Translate-URL: https://weblate.joinmastodon.org/projects/mastodon/frontend/sk/

* Translated using Weblate (Slovak)

Currently translated at 74.5% (431 of 578 strings)

Translation: Mastodon/Backend
Translate-URL: https://weblate.joinmastodon.org/projects/mastodon/backend/sk/

* Translated using Weblate (Dutch)

Currently translated at 100.0% (272 of 272 strings)

Translation: Mastodon/React
Translate-URL: https://weblate.joinmastodon.org/projects/mastodon/frontend/nl/

* Translated using Weblate (Japanese)

Currently translated at 100.0% (272 of 272 strings)

Translation: Mastodon/React
Translate-URL: https://weblate.joinmastodon.org/projects/mastodon/frontend/ja/

* Translated using Weblate (Dutch)

Currently translated at 100.0% (578 of 578 strings)

Translation: Mastodon/Backend
Translate-URL: https://weblate.joinmastodon.org/projects/mastodon/backend/nl/

* Translated using Weblate (Dutch)

Currently translated at 100.0% (579 of 579 strings)

Translation: Mastodon/Backend
Translate-URL: https://weblate.joinmastodon.org/projects/mastodon/backend/nl/

* Translated using Weblate (Arabic)

Currently translated at 99.6% (271 of 272 strings)

Translation: Mastodon/React
Translate-URL: https://weblate.joinmastodon.org/projects/mastodon/frontend/ar/

* Translated using Weblate (Japanese)

Currently translated at 98.9% (573 of 579 strings)

Translation: Mastodon/Backend
Translate-URL: https://weblate.joinmastodon.org/projects/mastodon/backend/ja/

* Translated using Weblate (Japanese)

Currently translated at 99.8% (578 of 579 strings)

Translation: Mastodon/Backend
Translate-URL: https://weblate.joinmastodon.org/projects/mastodon/backend/ja/

* Translated using Weblate (Arabic)

Currently translated at 66.6% (386 of 579 strings)

Translation: Mastodon/Backend
Translate-URL: https://weblate.joinmastodon.org/projects/mastodon/backend/ar/

* Translated using Weblate (Galician)

Currently translated at 100.0% (579 of 579 strings)

Translation: Mastodon/Backend
Translate-URL: https://weblate.joinmastodon.org/projects/mastodon/backend/gl/

* Translated using Weblate (Catalan)

Currently translated at 100.0% (272 of 272 strings)

Translation: Mastodon/React
Translate-URL: https://weblate.joinmastodon.org/projects/mastodon/frontend/ca/

* Translated using Weblate (Slovak)

Currently translated at 100.0% (58 of 58 strings)

Translation: Mastodon/Preferences
Translate-URL: https://weblate.joinmastodon.org/projects/mastodon/simple_form/sk/

* Translated using Weblate (Slovak)

Currently translated at 100.0% (75 of 75 strings)

Translation: Mastodon/Doorkeeper
Translate-URL: https://weblate.joinmastodon.org/projects/mastodon/doorkeeper/sk/

* Translated using Weblate (Slovak)

Currently translated at 100.0% (272 of 272 strings)

Translation: Mastodon/React
Translate-URL: https://weblate.joinmastodon.org/projects/mastodon/frontend/sk/

* Translated using Weblate (Slovak)

Currently translated at 100.0% (62 of 62 strings)

Translation: Mastodon/Devise
Translate-URL: https://weblate.joinmastodon.org/projects/mastodon/devise/sk/

* Translated using Weblate (Slovak)

Currently translated at 75.4% (437 of 579 strings)

Translation: Mastodon/Backend
Translate-URL: https://weblate.joinmastodon.org/projects/mastodon/backend/sk/

* Translated using Weblate (Polish)

Currently translated at 99.8% (578 of 579 strings)

Translation: Mastodon/Backend
Translate-URL: https://weblate.joinmastodon.org/projects/mastodon/backend/pl/

* Translated using Weblate (Slovak)

Currently translated at 83.5% (484 of 579 strings)

Translation: Mastodon/Backend
Translate-URL: https://weblate.joinmastodon.org/projects/mastodon/backend/sk/

* Translated using Weblate (Slovak)

Currently translated at 100.0% (272 of 272 strings)

Translation: Mastodon/React
Translate-URL: https://weblate.joinmastodon.org/projects/mastodon/frontend/sk/

* Normalize translations
Ran i18n-tasks normalize && yarn manage:translations
2018-03-06 00:27:50 +09:00
Renato "Lond" Cerqueira
1674e2f34c Normalize translations (#6638)
Ran i18n-tasks normalize && yarn manage:translations, so that the
translation changes appear on weblate
2018-03-05 23:12:17 +09:00
Marcin Mikołajczak
3b2e783c1f i18n: Update Polish translation (#6632)
Signed-off-by: Marcin Mikołajczak <me@m4sk.in>
2018-03-05 16:05:52 +09:00
ThibG
46a9a23749 Make more apparent that an account is blocked or muted (fixes #6544) (#6627)
* Add button to unblock blocked accounts from their profile

* Add “Blocked” badge in place of “Follows you” when the user is blocked

* Add “Muted” badge (below “follows you” badge)
2018-03-05 05:09:35 +01:00
Yuto Tokunaga
4e929b2d17 [RFC] Improved media modal (#5956)
* Improved media modal

ImageLoader: Impliment pinch zoom by CSS `transform: scale(X)`
ImageLoader: Impliment panning by CSS `overflow: scroll`
ImageLoader: Larger image
MediaModal: Larger close button
MediaModal: Close the modal by swiping vertically
MediaModal: Show/hide close button and right/left navigation on tapping image
MediaModal: Change the `pointer-event` CSS prpp to get more blank space to close the modal
ImageLoader: Zoom/reset zoom on double tap
MediaModal: disable vertical swiping while horizontally swiped
ImageLoader: prevent propagating touchmove event to MediaModal
MediaModal: Adjust size and potision of buttons
ImageLoader: Adjust scroll potision on pinch zoom

* Remove "swipe to close" and "double tap to zoom" features

* remove unused prop and functions

removed `onScroll` prop and `handleScroll` func in ImageLoader

* separate zoom functionary to ZoomableImage component

adjust styling of ImageLoader
add styling for ZoomableImage

* adjust size and potision of close button of media modal

* Fix for gif video

add `onClick` prop to ExtendedVideoPlayer
specify `onClick` prop to video tag for switching nav of `MediaModal`
add `.video-modal` class to scss to separate styling for `VideoModal`

* fix styling for centering

specify height of `ZoomableImage` by pixel
clean styling for `ImageLoader`

* fix lint errors

* small fix

* fixed designated parts
2018-03-04 20:32:24 +01:00
Akihiko Odaki
ef44c62d17 Do not default site_title with site_hostname in InstanceHelper (#6624)
site_title is "Mastodon" by default configuration, and there is no need to
default site_title with site_hostname in InstanceHelper.
2018-03-04 20:29:49 +01:00
abcang
219aac7800 Show media on report UI (#6619) 2018-03-04 20:29:12 +01:00
Akihiko Odaki
c110fa62ac Provide default OTP_SECRET value for development environment (#6617) 2018-03-04 20:28:24 +01:00
Akihiko Odaki
7a6eaad445 Do not require images in about.js and share.js (#6622)
They are already required by common.js.
2018-03-04 20:27:40 +01:00
Akihiko Odaki
460e380d38 Implement tag auto-completion by history (#6621)
This is a functionality similar to one implemented in Pawoo:
21a3c70f80
2018-03-04 20:27:25 +01:00
Akihiko Odaki
778b37790b Do not fetch environment variables to determine default locale (#6618)
The default locale is now set by config.
2018-03-04 10:00:46 +01:00
Eugen Rochko
b66ec3bf95 Fix #6611: Typo in change password template (#6616) 2018-03-04 10:00:24 +01:00
Akihiko Odaki
51d760960c Set the default locale in config (#6580)
Previously the default locale was set by Localized concern for controllers,
but it was not enforced for mailers.

config is enforced throughout the application and an appropriate place to
set the default locale.
2018-03-04 09:21:35 +01:00
Eugen Rochko
9110db41c5 Federate pinned statuses over ActivityPub (#6610)
* Federate pinned statuses over ActivityPub

* Display pinned toots in web UI

Fix #6117

* Fix migration

* Fix tests

* Update outbox_serializer.rb

* Update remove_serializer.rb

* Update add_serializer.rb

* Update fetch_featured_collection_service.rb
2018-03-04 09:19:11 +01:00
Akihiko Odaki
45feb439bd Finalize location on scrollable notifications when unmounting (#6614)
The top of the scrollable notifications will be invisible after unmounting.
The Redux state should be updated accordingly in such a case so that the
unread notification counter will be updated later.
2018-03-04 08:55:15 +01:00
Eugen Rochko
44829d8216 Fix missing focalPoint in ActivityPub JSON (#6609) 2018-03-04 07:21:41 +01:00
Aboobacker MK
49092945ab Fix 500 while searching after deleting a post (#6604)
Fixes #6602
2018-03-03 19:45:06 +01:00
Jeong Arm
c82a2358bd Translate Korean (#6608) 2018-03-04 01:53:55 +09:00
Yamagishi Kazutoshi
ecf06d7e82 Change "or" translatable (#6597) 2018-03-02 19:03:21 +01:00
Alexander
42fe05dea1 fix logic for pam_controlled_service (#6599) 2018-03-02 19:02:50 +01:00
284 changed files with 4155 additions and 1300 deletions

View File

@@ -4,6 +4,7 @@
[
"env",
{
"exclude": ["transform-async-to-generator", "transform-regenerator"],
"loose": true,
"modules": false,
"targets": {

View File

@@ -13,11 +13,29 @@ DB_PORT=5432
DATABASE_URL=postgresql://$DATA_DB_USER:$DATA_DB_PASS@$DATA_DB_HOST/gonano
# Optional ElasticSearch configuration
# ES_ENABLED=true
# ES_HOST=localhost
# ES_PORT=9200
# Optimizations
LD_PRELOAD=/data/lib/libjemalloc.so
# ImageMagick optimizations
MAGICK_TEMPORARY_PATH=/app/tmp
MAGICK_MEMORY_LIMIT=128MiB
MAGICK_MAP_LIMIT=64MiB
MAGICK_TIME_LIMIT=15
MAGICK_AREA_LIMIT=16MP
MAGICK_WIDTH_LIMIT=8KP
MAGICK_HEIGHT_LIMIT=8KP
# Federation
# Note: Changing LOCAL_DOMAIN or LOCAL_HTTPS at a later time will cause unwanted side effects.
# Note: Changing LOCAL_DOMAIN at a later time will cause unwanted side effects, including breaking all existing federation.
# LOCAL_DOMAIN should *NOT* contain the protocol part of the domain e.g https://example.com.
LOCAL_DOMAIN=${APP_NAME}.nanoapp.io
LOCAL_HTTPS=false
# Changing LOCAL_HTTPS in production is no longer supported. (Mastodon will always serve https:// links)
# Use this only if you need to run mastodon on a different domain than the one used for federation.
# You can read more about this option on https://github.com/tootsuite/documentation/blob/master/Running-Mastodon/Serving_a_different_domain.md
@@ -31,7 +49,6 @@ LOCAL_HTTPS=false
# Application secrets
# Generate each with the `rake secret` task (`nanobox run bundle exec rake secret`)
PAPERCLIP_SECRET=$PAPERCLIP_SECRET
SECRET_KEY_BASE=$SECRET_KEY_BASE
OTP_SECRET=$OTP_SECRET
@@ -131,9 +148,79 @@ SMTP_FROM_ADDRESS=notifications@${APP_NAME}.nanoapp.io
# Cluster number setting for streaming API server.
# If you comment out following line, cluster number will be `numOfCpuCores - 1`.
STREAMING_CLUSTER_NUM=1
# STREAMING_CLUSTER_NUM=1
# Docker mastodon user
# If you use Docker, you may want to assign UID/GID manually.
# UID=1000
# GID=1000
# LDAP authentication (optional)
# LDAP_ENABLED=true
# LDAP_HOST=localhost
# LDAP_PORT=389
# LDAP_METHOD=simple_tls
# LDAP_BASE=
# LDAP_BIND_DN=
# LDAP_PASSWORD=
# LDAP_UID=cn
# PAM authentication (optional)
# PAM authentication uses for the email generation the "email" pam variable
# and optional as fallback PAM_DEFAULT_SUFFIX
# The pam environment variable "email" is provided by:
# https://github.com/devkral/pam_email_extractor
# PAM_ENABLED=true
# Fallback Suffix for email address generation (nil by default)
# PAM_DEFAULT_SUFFIX=pam
# Name of the pam service (pam "auth" section is evaluated)
# PAM_DEFAULT_SERVICE=rpam
# Name of the pam service used for checking if an user can register (pam "account" section is evaluated) (nil (disabled) by default)
# PAM_CONTROLLED_SERVICE=rpam
# Global OAuth settings (optional) :
# If you have only one strategy, you may want to enable this
# OAUTH_REDIRECT_AT_SIGN_IN=true
# Optional CAS authentication (cf. omniauth-cas) :
# CAS_ENABLED=true
# CAS_URL=https://sso.myserver.com/
# CAS_HOST=sso.myserver.com/
# CAS_PORT=443
# CAS_SSL=true
# CAS_VALIDATE_URL=
# CAS_CALLBACK_URL=
# CAS_LOGOUT_URL=
# CAS_LOGIN_URL=
# CAS_UID_FIELD='user'
# CAS_CA_PATH=
# CAS_DISABLE_SSL_VERIFICATION=false
# CAS_UID_KEY='user'
# CAS_NAME_KEY='name'
# CAS_EMAIL_KEY='email'
# CAS_NICKNAME_KEY='nickname'
# CAS_FIRST_NAME_KEY='firstname'
# CAS_LAST_NAME_KEY='lastname'
# CAS_LOCATION_KEY='location'
# CAS_IMAGE_KEY='image'
# CAS_PHONE_KEY='phone'
# Optional SAML authentication (cf. omniauth-saml)
# SAML_ENABLED=true
# SAML_ACS_URL=
# SAML_ISSUER=http://localhost:3000/auth/auth/saml/callback
# SAML_IDP_SSO_TARGET_URL=https://idp.testshib.org/idp/profile/SAML2/Redirect/SSO
# SAML_IDP_CERT=
# SAML_IDP_CERT_FINGERPRINT=
# SAML_NAME_IDENTIFIER_FORMAT=
# SAML_CERT=
# SAML_PRIVATE_KEY=
# SAML_SECURITY_WANT_ASSERTION_SIGNED=true
# SAML_SECURITY_WANT_ASSERTION_ENCRYPTED=true
# SAML_SECURITY_ASSUME_EMAIL_IS_VERIFIED=true
# SAML_ATTRIBUTES_STATEMENTS_UID="urn:oid:0.9.2342.19200300.100.1.1"
# SAML_ATTRIBUTES_STATEMENTS_EMAIL="urn:oid:1.3.6.1.4.1.5923.1.1.1.6"
# SAML_ATTRIBUTES_STATEMENTS_FULL_NAME="urn:oid:2.5.4.42"
# SAML_UID_ATTRIBUTE="urn:oid:0.9.2342.19200300.100.1.1"
# SAML_ATTRIBUTES_STATEMENTS_VERIFIED=
# SAML_ATTRIBUTES_STATEMENTS_VERIFIED_EMAIL=

View File

@@ -11,7 +11,7 @@ DB_PASS=
DB_PORT=5432
# Optional ElasticSearch configuration
# ES_ENABLED=true
# ES_HOST=localhost
# ES_HOST=es
# ES_PORT=9200
# Federation
@@ -155,11 +155,11 @@ STREAMING_CLUSTER_NUM=1
# The pam environment variable "email" is provided by:
# https://github.com/devkral/pam_email_extractor
# PAM_ENABLED=true
# Fallback Suffix for email address generation (nil by default)
# PAM_DEFAULT_SUFFIX=pam
# Fallback email domain for email address generation (LOCAL_DOMAIN by default)
# PAM_EMAIL_DOMAIN=example.com
# Name of the pam service (pam "auth" section is evaluated)
# PAM_DEFAULT_SERVICE=rpam
# Name of the pam service used for checking if an user can register (pam "account" section is evaluated)
# Name of the pam service used for checking if an user can register (pam "account" section is evaluated) (nil (disabled) by default)
# PAM_CONTROLLED_SERVICE=rpam
# Global OAuth settings (optional) :
@@ -204,7 +204,9 @@ STREAMING_CLUSTER_NUM=1
# SAML_SECURITY_ASSUME_EMAIL_IS_VERIFIED=true
# SAML_ATTRIBUTES_STATEMENTS_UID="urn:oid:0.9.2342.19200300.100.1.1"
# SAML_ATTRIBUTES_STATEMENTS_EMAIL="urn:oid:1.3.6.1.4.1.5923.1.1.1.6"
# SAML_ATTRIBUTES_STATEMENTS_FULL_NAME="urn:oid:2.5.4.42"
# SAML_ATTRIBUTES_STATEMENTS_FULL_NAME="urn:oid:2.16.840.1.113730.3.1.241"
# SAML_ATTRIBUTES_STATEMENTS_FIRST_NAME="urn:oid:2.5.4.42"
# SAML_ATTRIBUTES_STATEMENTS_LAST_NAME="urn:oid:2.5.4.4"
# SAML_UID_ATTRIBUTE="urn:oid:0.9.2342.19200300.100.1.1"
# SAML_ATTRIBUTES_STATEMENTS_VERIFIED=
# SAML_ATTRIBUTES_STATEMENTS_VERIFIED_EMAIL=

View File

@@ -1,4 +1,3 @@
# Federation
LOCAL_DOMAIN=cb6e6126.ngrok.io
LOCAL_HTTPS=true
OTP_SECRET=100c7faeef00caa29242f6b04156742bf76065771fd4117990c4282b8748ff3d99f8fdae97c982ab5bd2e6756a159121377cce4421f4a8ecd2d67bd7749a3fb4

View File

@@ -55,5 +55,5 @@ before_script:
script:
- travis_retry bundle exec parallel_test spec/ --group-by filesize --type rspec
- yarn test
- yarn run test:jest
- bundle exec i18n-tasks check-normalized && bundle exec i18n-tasks unused

450
AUTHORS.md Normal file
View File

@@ -0,0 +1,450 @@
Mastodon is available on [GitHub](https://github.com/tootsuite/mastodon)
and provided thanks to the work of the following contributors:
* [Gargron](https://github.com/Gargron)
* [ykzts](https://github.com/ykzts)
* [mjankowski](https://github.com/mjankowski)
* [akihikodaki](https://github.com/akihikodaki)
* [unarist](https://github.com/unarist)
* [yiskah](https://github.com/yiskah)
* [m4sk1n](https://github.com/m4sk1n)
* [nolanlawson](https://github.com/nolanlawson)
* [sorin-davidoi](https://github.com/sorin-davidoi)
* [abcang](https://github.com/abcang)
* [ThibG](https://github.com/ThibG)
* [lynlynlynx](https://github.com/lynlynlynx)
* [alpaca-tc](https://github.com/alpaca-tc)
* [nclm](https://github.com/nclm)
* [ineffyble](https://github.com/ineffyble)
* [jeroenpraat](https://github.com/jeroenpraat)
* [blackle](https://github.com/blackle)
* [Quent-in](https://github.com/Quent-in)
* [JantsoP](https://github.com/JantsoP)
* [nullkal](https://github.com/nullkal)
* [yookoala](https://github.com/yookoala)
* [ysksn](https://github.com/ysksn)
* [ashfurrow](https://github.com/ashfurrow)
* [eramdam](https://github.com/eramdam)
* [mayaeh](https://github.com/mayaeh)
* [zunda](https://github.com/zunda)
* [ticky](https://github.com/ticky)
* [masarakki](https://github.com/masarakki)
* [Wonderfall](https://github.com/Wonderfall)
* [matteoaquila](https://github.com/matteoaquila)
* [rkarabut](https://github.com/rkarabut)
* [stephenburgess8](https://github.com/stephenburgess8)
* [Kjwon15](https://github.com/Kjwon15)
* [Artoria2e5](https://github.com/Artoria2e5)
* [yukimochi](https://github.com/yukimochi)
* [marrus-sh](https://github.com/marrus-sh)
* [krainboltgreene](https://github.com/krainboltgreene)
* [renatolond](https://github.com/renatolond)
* [BoFFire](https://github.com/BoFFire)
* [clworld](https://github.com/clworld)
* [danhunsaker](https://github.com/danhunsaker)
* [patf](https://github.com/patf)
* [Quenty31](https://github.com/Quenty31)
* [MitarashiDango](https://github.com/MitarashiDango)
* [Aldarone](https://github.com/Aldarone)
* [JeanGauthier](https://github.com/JeanGauthier)
* [kschaper](https://github.com/kschaper)
* [takayamaki](https://github.com/takayamaki)
* [adbelle](https://github.com/adbelle)
* [evanminto](https://github.com/evanminto)
* [mabkenar](https://github.com/mabkenar)
* [MightyPork](https://github.com/MightyPork)
* [beatrix-bitrot](https://github.com/beatrix-bitrot)
* [yhirano55](https://github.com/yhirano55)
* [camponez](https://github.com/camponez)
* [aschmitz](https://github.com/aschmitz)
* [fpiesche](https://github.com/fpiesche)
* [gandaro](https://github.com/gandaro)
* [johnsudaar](https://github.com/johnsudaar)
* [trebmuh](https://github.com/trebmuh)
* [Sylvhem](https://github.com/Sylvhem)
* [lindwurm](https://github.com/lindwurm)
* [voidsatisfaction](https://github.com/voidsatisfaction)
* [neetshin](https://github.com/neetshin)
* [valentin2105](https://github.com/valentin2105)
* [hikari-no-yume](https://github.com/hikari-no-yume)
* [Angristan](https://github.com/Angristan)
* [seefood](https://github.com/seefood)
* [jackjennings](https://github.com/jackjennings)
* [hcmiya](https://github.com/hcmiya)
* [nightpool](https://github.com/nightpool)
* [salvadorpla](https://github.com/salvadorpla)
* [expenses](https://github.com/expenses)
* [walf443](https://github.com/walf443)
* [JoelQ](https://github.com/JoelQ)
* [mistydemeo](https://github.com/mistydemeo)
* [dunn](https://github.com/dunn)
* [xqus](https://github.com/xqus)
* [pfm-eyesightjp](https://github.com/pfm-eyesightjp)
* [fakenine](https://github.com/fakenine)
* [tsuwatch](https://github.com/tsuwatch)
* [victorhck](https://github.com/victorhck)
* [puckipedia](https://github.com/puckipedia)
* [contraexemplo](https://github.com/contraexemplo)
* [kazu9su](https://github.com/kazu9su)
* [Komic](https://github.com/Komic)
* [diomed](https://github.com/diomed)
* [rainyday](https://github.com/rainyday)
* [kadiix](https://github.com/kadiix)
* [kodacs](https://github.com/kodacs)
* [ProgVal](https://github.com/ProgVal)
* [sterdev](https://github.com/sterdev)
* [TheKinrar](https://github.com/TheKinrar)
* [AA4ch1](https://github.com/AA4ch1)
* [alexgleason](https://github.com/alexgleason)
* [cpytel](https://github.com/cpytel)
* [northerner](https://github.com/northerner)
* [hnrysmth](https://github.com/hnrysmth)
* [hugogameiro](https://github.com/hugogameiro)
* [JohnD28](https://github.com/JohnD28)
* [znz](https://github.com/znz)
* [Naouak](https://github.com/Naouak)
* [rtucker](https://github.com/rtucker)
* [reneklacan](https://github.com/reneklacan)
* [KScl](https://github.com/KScl)
* [SerCom-KC](https://github.com/SerCom-KC)
* [tcitworld](https://github.com/tcitworld)
* [geta6](https://github.com/geta6)
* [goofy-bz](https://github.com/goofy-bz)
* [happycoloredbanana](https://github.com/happycoloredbanana)
* [leopku](https://github.com/leopku)
* [SansPseudoFix](https://github.com/SansPseudoFix)
* [tomfhowe](https://github.com/tomfhowe)
* [noraworld](https://github.com/noraworld)
* [fvh-P](https://github.com/fvh-P)
* [178inaba](https://github.com/178inaba)
* [devkral](https://github.com/devkral)
* [alyssais](https://github.com/alyssais)
* [kodnaplakal](https://github.com/kodnaplakal)
* [stalker314314](https://github.com/stalker314314)
* [huertanix](https://github.com/huertanix)
* [genesixx](https://github.com/genesixx)
* [fhemberger](https://github.com/fhemberger)
* [halkeye](https://github.com/halkeye)
* [treby](https://github.com/treby)
* [d6rkaiz](https://github.com/d6rkaiz)
* [jpdevries](https://github.com/jpdevries)
* [rndm-stranger](https://github.com/rndm-stranger)
* [saper](https://github.com/saper)
* [nevillepark](https://github.com/nevillepark)
* [ornithocoder](https://github.com/ornithocoder)
* [pierreozoux](https://github.com/pierreozoux)
* [ramlmn](https://github.com/ramlmn)
* [harukasan](https://github.com/harukasan)
* [stamak](https://github.com/stamak)
* [Eychics](https://github.com/Eychics)
* [thor-the-norseman](https://github.com/thor-the-norseman)
* [0x70b1a5](https://github.com/0x70b1a5)
* [gled-rs](https://github.com/gled-rs)
* [R0ckweb](https://github.com/R0ckweb)
* [esetomo](https://github.com/esetomo)
* [foxiehkins](https://github.com/foxiehkins)
* [sdukhovni](https://github.com/sdukhovni)
* [unsmell](https://github.com/unsmell)
* [chriswmartin](https://github.com/chriswmartin)
* [vahnj](https://github.com/vahnj)
* [ikuradon](https://github.com/ikuradon)
* [AndreLewin](https://github.com/AndreLewin)
* [redtachyons](https://github.com/redtachyons)
* [thurloat](https://github.com/thurloat)
* [aaribaud](https://github.com/aaribaud)
* [estuans](https://github.com/estuans)
* [dissolve](https://github.com/dissolve)
* [PurpleBooth](https://github.com/PurpleBooth)
* [bradurani](https://github.com/bradurani)
* [wavebeem](https://github.com/wavebeem)
* [bruwalfas](https://github.com/bruwalfas)
* [foxsan48](https://github.com/foxsan48)
* [wchristian](https://github.com/wchristian)
* [muffinista](https://github.com/muffinista)
* [cdutson](https://github.com/cdutson)
* [farlistener](https://github.com/farlistener)
* [DavidLibeau](https://github.com/DavidLibeau)
* [SirCmpwn](https://github.com/SirCmpwn)
* [MasterGroosha](https://github.com/MasterGroosha)
* [Fjoerfoks](https://github.com/Fjoerfoks)
* [fmauNeko](https://github.com/fmauNeko)
* [gloaec](https://github.com/gloaec)
* [greysteil](https://github.com/greysteil)
* [unstabler](https://github.com/unstabler)
* [potato4d](https://github.com/potato4d)
* [h-izumi](https://github.com/h-izumi)
* [ErikXXon](https://github.com/ErikXXon)
* [ian-kelling](https://github.com/ian-kelling)
* [foozmeat](https://github.com/foozmeat)
* [jasonrhodes](https://github.com/jasonrhodes)
* [asm](https://github.com/asm)
* [jviide](https://github.com/jviide)
* [crakaC](https://github.com/crakaC)
* [tkbky](https://github.com/tkbky)
* [Kazhnuz](https://github.com/Kazhnuz)
* [alimony](https://github.com/alimony)
* [mig5](https://github.com/mig5)
* [ndarville](https://github.com/ndarville)
* [Abzol](https://github.com/Abzol)
* [xPaw](https://github.com/xPaw)
* [raymestalez](https://github.com/raymestalez)
* [sim6](https://github.com/sim6)
* [ekiru](https://github.com/ekiru)
* [Technowix](https://github.com/Technowix)
* [ThomasLeister](https://github.com/ThomasLeister)
* [mcat-ee](https://github.com/mcat-ee)
* [tototoshi](https://github.com/tototoshi)
* [VirtuBox](https://github.com/VirtuBox)
* [kaniini](https://github.com/kaniini)
* [vayan](https://github.com/vayan)
* [yannicka](https://github.com/yannicka)
* [ikasoumen](https://github.com/ikasoumen)
* [zacanger](https://github.com/zacanger)
* [amazedkoumei](https://github.com/amazedkoumei)
* [anon5r](https://github.com/anon5r)
* [codl](https://github.com/codl)
* [barzamin](https://github.com/barzamin)
* [fhalna](https://github.com/fhalna)
* [haoyayoi](https://github.com/haoyayoi)
* [ik11235](https://github.com/ik11235)
* [kawax](https://github.com/kawax)
* [007lva](https://github.com/007lva)
* [matsurai25](https://github.com/matsurai25)
* [mecab](https://github.com/mecab)
* [nicobz25](https://github.com/nicobz25)
* [oliverkeeble](https://github.com/oliverkeeble)
* [pinfort](https://github.com/pinfort)
* [rbaumert](https://github.com/rbaumert)
* [usagi-f](https://github.com/usagi-f)
* [vidarlee](https://github.com/vidarlee)
* [vjackson725](https://github.com/vjackson725)
* [wxcafe](https://github.com/wxcafe)
* [rinsuki](https://github.com/rinsuki)
* [cygnan](https://github.com/cygnan)
* [Awea](https://github.com/Awea)
* [halcy](https://github.com/halcy)
* [bounshi](https://github.com/bounshi)
* [8398a7](https://github.com/8398a7)
* [857b](https://github.com/857b)
* [unascribed](https://github.com/unascribed)
* [Aguay-val](https://github.com/Aguay-val)
* [knu](https://github.com/knu)
* [alxrcs](https://github.com/alxrcs)
* [console-cowboy](https://github.com/console-cowboy)
* [pointlessone](https://github.com/pointlessone)
* [a2](https://github.com/a2)
* [0xa](https://github.com/0xa)
* [virtualpain](https://github.com/virtualpain)
* [sapphirus](https://github.com/sapphirus)
* [amandavisconti](https://github.com/amandavisconti)
* [ameliavoncat](https://github.com/ameliavoncat)
* [ilpianista](https://github.com/ilpianista)
* [andydrop](https://github.com/andydrop)
* [schas002](https://github.com/schas002)
* [jumbosushi](https://github.com/jumbosushi)
* [ayumin](https://github.com/ayumin)
* [BaptisteGelez](https://github.com/BaptisteGelez)
* [bzg](https://github.com/bzg)
* [benediktg](https://github.com/benediktg)
* [blakebarnett](https://github.com/blakebarnett)
* [bradj](https://github.com/bradj)
* [brycied00d](https://github.com/brycied00d)
* [carlosjs23](https://github.com/carlosjs23)
* [cgxxx](https://github.com/cgxxx)
* [chrisheninger](https://github.com/chrisheninger)
* [chris-martin](https://github.com/chris-martin)
* [DoubleMalt](https://github.com/DoubleMalt)
* [Moosh-be](https://github.com/Moosh-be)
* [Motoma](https://github.com/Motoma)
* [chriswk](https://github.com/chriswk)
* [csu](https://github.com/csu)
* [kklleemm](https://github.com/kklleemm)
* [monsterpit-daggertooth](https://github.com/monsterpit-daggertooth)
* [watilde](https://github.com/watilde)
* [daprice](https://github.com/daprice)
* [dar5hak](https://github.com/dar5hak)
* [kant](https://github.com/kant)
* [singingwolfboy](https://github.com/singingwolfboy)
* [davidcelis](https://github.com/davidcelis)
* [yipdw](https://github.com/yipdw)
* [debanshuk](https://github.com/debanshuk)
* [dblandin](https://github.com/dblandin)
* [aranaur](https://github.com/aranaur)
* [d3vgru](https://github.com/d3vgru)
* [Elizafox](https://github.com/Elizafox)
* [ericblade](https://github.com/ericblade)
* [mikoim](https://github.com/mikoim)
* [siuying](https://github.com/siuying)
* [hattori6789](https://github.com/hattori6789)
* [algernon](https://github.com/algernon)
* [Fastbyte01](https://github.com/Fastbyte01)
* [myfreeweb](https://github.com/myfreeweb)
* [gfaivre](https://github.com/gfaivre)
* [Fiaxhs](https://github.com/Fiaxhs)
* [reedcourty](https://github.com/reedcourty)
* [anneau](https://github.com/anneau)
* [HellPie](https://github.com/HellPie)
* [Habu-Kagumba](https://github.com/Habu-Kagumba)
* [hinaloe](https://github.com/hinaloe)
* [suzukaze](https://github.com/suzukaze)
* [Hiromi-Kai](https://github.com/Hiromi-Kai)
* [musashino205](https://github.com/musashino205)
* [iwaim](https://github.com/iwaim)
* [valrus](https://github.com/valrus)
* [IMcD23](https://github.com/IMcD23)
* [yi0713](https://github.com/yi0713)
* [immae](https://github.com/immae)
* [iblech](https://github.com/iblech)
* [jack-michaud](https://github.com/jack-michaud)
* [Floppy](https://github.com/Floppy)
* [loomchild](https://github.com/loomchild)
* [docjkl](https://github.com/docjkl)
* [TrollDecker](https://github.com/TrollDecker)
* [jmontane](https://github.com/jmontane)
* [jonathanklee](https://github.com/jonathanklee)
* [jguerder](https://github.com/jguerder)
* [Jehops](https://github.com/Jehops)
* [joshuap](https://github.com/joshuap)
* [Tiwy57](https://github.com/Tiwy57)
* [xuv](https://github.com/xuv)
* [Jnsll](https://github.com/Jnsll)
* [j0k3r](https://github.com/j0k3r)
* [KEINOS](https://github.com/KEINOS)
* [futoase](https://github.com/futoase)
* [abjectio](https://github.com/abjectio)
* [mkody](https://github.com/mkody)
* [connyduck](https://github.com/connyduck)
* [k0ta0uchi](https://github.com/k0ta0uchi)
* [KrzysiekJ](https://github.com/KrzysiekJ)
* [leowzukw](https://github.com/leowzukw)
* [lmorchard](https://github.com/lmorchard)
* [cacheflow](https://github.com/cacheflow)
* [ldidry](https://github.com/ldidry)
* [jemus42](https://github.com/jemus42)
* [lfuelling](https://github.com/lfuelling)
* [Grabacr07](https://github.com/Grabacr07)
* [mistermantas](https://github.com/mistermantas)
* [wirehack7](https://github.com/wirehack7)
* [marvinkopf](https://github.com/marvinkopf)
* [otsune](https://github.com/otsune)
* [m-blc](https://github.com/m-blc)
* [matt-auckland](https://github.com/matt-auckland)
* [mattjmattj](https://github.com/mattjmattj)
* [mtparet](https://github.com/mtparet)
* [maximeborges](https://github.com/maximeborges)
* [minacle](https://github.com/minacle)
* [michaeljdeeb](https://github.com/michaeljdeeb)
* [Themimitoof](https://github.com/Themimitoof)
* [cyweo](https://github.com/cyweo)
* [M1dgard](https://github.com/M1dgard)
* [mike-burns](https://github.com/mike-burns)
* [verymilan](https://github.com/verymilan)
* [milmazz](https://github.com/milmazz)
* [Mnkai](https://github.com/Mnkai)
* [mitchhentges](https://github.com/mitchhentges)
* [moritzheiber](https://github.com/moritzheiber)
* [mouse-reeve](https://github.com/mouse-reeve)
* [lae](https://github.com/lae)
* [Nanamachi](https://github.com/Nanamachi)
* [ngerakines](https://github.com/ngerakines)
* [vonneudeck](https://github.com/vonneudeck)
* [Ninetailed](https://github.com/Ninetailed)
* [k24](https://github.com/k24)
* [noiob](https://github.com/noiob)
* [kwaio](https://github.com/kwaio)
* [norayr](https://github.com/norayr)
* [joyeusenoelle](https://github.com/joyeusenoelle)
* [OlivierNicole](https://github.com/OlivierNicole)
* [Otakan951](https://github.com/Otakan951)
* [fahy](https://github.com/fahy)
* [Pangoraw](https://github.com/Pangoraw)
* [pwoolcoc](https://github.com/pwoolcoc)
* [peterkeen](https://github.com/peterkeen)
* [petzah](https://github.com/petzah)
* [ignisf](https://github.com/ignisf)
* [rfwatson](https://github.com/rfwatson)
* [rfreebern](https://github.com/rfreebern)
* [sylph01](https://github.com/sylph01)
* [staticsafe](https://github.com/staticsafe)
* [snwh](https://github.com/snwh)
* [skoji](https://github.com/skoji)
* [ScienJus](https://github.com/ScienJus)
* [larkinscott](https://github.com/larkinscott)
* [imolein](https://github.com/imolein)
* [blinry](https://github.com/blinry)
* [Noiwex](https://github.com/Noiwex)
* [yuki764](https://github.com/yuki764)
* [shnjp](https://github.com/shnjp)
* [ernix](https://github.com/ernix)
* [rosylilly](https://github.com/rosylilly)
* [shouko](https://github.com/shouko)
* [sossii](https://github.com/sossii)
* [StefOfficiel](https://github.com/StefOfficiel)
* [svetlik](https://github.com/svetlik)
* [dereckson](https://github.com/dereckson)
* [theboss](https://github.com/theboss)
* [takp](https://github.com/takp)
* [tkusano](https://github.com/tkusano)
* [TheInventrix](https://github.com/TheInventrix)
* [shug0](https://github.com/shug0)
* [Fortyseven](https://github.com/Fortyseven)
* [tobypinder](https://github.com/tobypinder)
* [tomosm](https://github.com/tomosm)
* [TomoyaShibata](https://github.com/TomoyaShibata)
* [TrashMacNugget](https://github.com/TrashMacNugget)
* [treyssatvincent](https://github.com/treyssatvincent)
* [optikfluffel](https://github.com/optikfluffel)
* [vmincev](https://github.com/vmincev)
* [waldyrious](https://github.com/waldyrious)
* [tahnok](https://github.com/tahnok)
* [YDrogen](https://github.com/YDrogen)
* [YOSHIOKAEiichiro](https://github.com/YOSHIOKAEiichiro)
* [S-YOU](https://github.com/S-YOU)
* [YaQ00](https://github.com/YaQ00)
* [yanakend](https://github.com/yanakend)
* [orzFly](https://github.com/orzFly)
* [chansuke](https://github.com/chansuke)
* [yuntan](https://github.com/yuntan)
* [LogicalDash](https://github.com/LogicalDash)
* [ZiiX](https://github.com/ZiiX)
* [benklop](https://github.com/benklop)
* [caasi](https://github.com/caasi)
* [caesarologia](https://github.com/caesarologia)
* [chrolis](https://github.com/chrolis)
* [cormojs](https://github.com/cormojs)
* [cpsdqs](https://github.com/cpsdqs)
* [d0p1s4m4](https://github.com/d0p1s4m4)
* [evilny0](https://github.com/evilny0)
* [febrezo](https://github.com/febrezo)
* [fsubal](https://github.com/fsubal)
* [dikky1218](https://github.com/dikky1218)
* [gentarok](https://github.com/gentarok)
* [hakoai](https://github.com/hakoai)
* [chaosbunker](https://github.com/chaosbunker)
* [isati](https://github.com/isati)
* [jkap](https://github.com/jkap)
* [jirayudech](https://github.com/jirayudech)
* [jukper](https://github.com/jukper)
* [karlyeurl](https://github.com/karlyeurl)
* [kedamaDQ](https://github.com/kedamaDQ)
* [kuro5hin](https://github.com/kuro5hin)
* [maxypy](https://github.com/maxypy)
* [marcus-herrmann](https://github.com/marcus-herrmann)
* [mshrtkch](https://github.com/mshrtkch)
* [muan](https://github.com/muan)
* [rch850](https://github.com/rch850)
* [roikale](https://github.com/roikale)
* [rysiekpl](https://github.com/rysiekpl)
* [saturday06](https://github.com/saturday06)
* [scriptjunkie](https://github.com/scriptjunkie)
* [seekr](https://github.com/seekr)
* [syui](https://github.com/syui)
* [tackeyy](https://github.com/tackeyy)
* [tmyt](https://github.com/tmyt)
* [utam0k](https://github.com/utam0k)
* [vpzomtrrfrt](https://github.com/vpzomtrrfrt)
* [walfie](https://github.com/walfie)
* [y-temp4](https://github.com/y-temp4)
* [ymmtmdk](https://github.com/ymmtmdk)
This document is provided for informational purposes only. Since it is only updated once per release, the version you are looking at may be currently out of date. To see the full list of contributors, consider looking at the [git history](https://github.com/tootsuite/mastodon/graphs/contributors) instead.

View File

@@ -1,7 +1,7 @@
FROM ruby:2.5.0-alpine3.7
FROM ruby:2.4.3-alpine3.6
LABEL maintainer="https://github.com/tootsuite/mastodon" \
description="A GNU Social-compatible microblogging server"
description="Your self-hosted, globally interconnected microblogging community"
ARG UID=991
ARG GID=991
@@ -40,7 +40,6 @@ RUN apk -U upgrade \
nodejs \
nodejs-npm \
protobuf \
su-exec \
tini \
tzdata \
&& update-ca-certificates \
@@ -70,9 +69,13 @@ RUN bundle config build.nokogiri --with-iconv-lib=/usr/local/lib --with-iconv-in
&& yarn --pure-lockfile \
&& yarn cache clean
RUN addgroup -g ${GID} mastodon && adduser -h /mastodon -s /bin/sh -D -G mastodon -u ${UID} mastodon
RUN addgroup -g ${GID} mastodon && adduser -h /mastodon -s /bin/sh -D -G mastodon -u ${UID} mastodon \
&& mkdir -p /mastodon/public/system /mastodon/public/assets /mastodon/public/packs \
&& chown -R mastodon:mastodon /mastodon/public
COPY --chown=mastodon:mastodon . /mastodon
COPY . /mastodon
RUN chown -R mastodon:mastodon /mastodon
VOLUME /mastodon/public/system /mastodon/public/assets /mastodon/public/packs

14
Gemfile
View File

@@ -27,15 +27,17 @@ gem 'bootsnap'
gem 'browser'
gem 'charlock_holmes', '~> 0.7.5'
gem 'iso-639'
gem 'chewy', '~> 0.10', git: 'https://github.com/toptal/chewy.git'
gem 'chewy', '~> 5.0'
gem 'cld3', '~> 3.2.0'
gem 'devise', '~> 4.4'
gem 'devise-two-factor', '~> 3.0'
gem 'devise_pam_authenticatable2', '~> 8.0', install_if: -> { ENV['PAM_ENABLED'] == 'true' }
gem 'net-ldap', '~> 0.10', install_if: -> { ENV['LDAP_ENABLED'] == 'true' }
gem 'omniauth-cas', '~> 1.1', install_if: -> { ENV['CAS_ENABLED'] == 'true' }
gem 'omniauth-saml', '~> 1.10', install_if: -> { ENV['SAML_ENABLED'] == 'true' }
group :pam_authentication, optional: true do
gem 'devise_pam_authenticatable2', '~> 9.0'
end
gem 'net-ldap', '~> 0.10'
gem 'omniauth-cas', '~> 1.1'
gem 'omniauth-saml', '~> 1.10'
gem 'omniauth', '~> 1.2'
gem 'doorkeeper', '~> 4.2'
@@ -69,7 +71,7 @@ gem 'mario-redis-lock', '~> 1.2', require: 'redis_lock'
gem 'rqrcode', '~> 0.10'
gem 'ruby-oembed', '~> 0.12', require: 'oembed'
gem 'ruby-progressbar', '~> 1.4'
gem 'sanitize', '~> 4.4'
gem 'sanitize', '~> 4.6.4'
gem 'sidekiq', '~> 5.0'
gem 'sidekiq-scheduler', '~> 2.1'
gem 'sidekiq-unique-jobs', '~> 5.0'

View File

@@ -1,12 +1,3 @@
GIT
remote: https://github.com/toptal/chewy.git
revision: a7d21eb4b0bd7415533ef134bb6d31b2df309701
specs:
chewy (0.10.1)
activesupport (>= 4.0)
elasticsearch (>= 2.0.0)
elasticsearch-dsl
GEM
remote: https://rubygems.org/
specs:
@@ -118,6 +109,10 @@ GEM
case_transform (0.2)
activesupport
charlock_holmes (0.7.5)
chewy (5.0.0)
activesupport (>= 4.0)
elasticsearch (>= 2.0.0)
elasticsearch-dsl
chunky_png (1.3.8)
cld3 (3.2.2)
ffi (>= 1.1.0, < 1.10.0)
@@ -146,7 +141,7 @@ GEM
devise (~> 4.0)
railties (< 5.2)
rotp (~> 2.0)
devise_pam_authenticatable2 (8.0.1)
devise_pam_authenticatable2 (9.0.0)
devise (>= 4.0.0)
rpam2 (~> 3.0)
diff-lcs (1.3)
@@ -293,7 +288,7 @@ GEM
activesupport (>= 4, < 5.2)
railties (>= 4, < 5.2)
request_store (~> 1.0)
loofah (2.1.1)
loofah (2.2.1)
crass (~> 1.0.2)
nokogiri (>= 1.5.9)
mail (2.7.0)
@@ -321,9 +316,9 @@ GEM
net-ssh (>= 2.6.5)
net-ssh (4.2.0)
nio4r (2.1.0)
nokogiri (1.8.1)
nokogiri (1.8.2)
mini_portile2 (~> 2.3.0)
nokogumbo (1.4.13)
nokogumbo (1.5.0)
nokogiri
nsa (0.2.4)
activesupport (>= 4.2, < 6)
@@ -501,10 +496,10 @@ GEM
rufus-scheduler (3.4.2)
et-orbi (~> 1.0)
safe_yaml (1.0.4)
sanitize (4.5.0)
sanitize (4.6.4)
crass (~> 1.0.2)
nokogiri (>= 1.4.4)
nokogumbo (~> 1.4.1)
nokogumbo (~> 1.4)
sass (3.5.3)
sass-listen (~> 4.0.0)
sass-listen (4.0.0)
@@ -631,12 +626,12 @@ DEPENDENCIES
capistrano-yarn (~> 2.0)
capybara (~> 2.15)
charlock_holmes (~> 0.7.5)
chewy (~> 0.10)!
chewy (~> 5.0)
cld3 (~> 3.2.0)
climate_control (~> 0.2)
devise (~> 4.4)
devise-two-factor (~> 3.0)
devise_pam_authenticatable2 (~> 8.0)
devise_pam_authenticatable2 (~> 9.0)
doorkeeper (~> 4.2)
dotenv-rails (~> 2.2)
fabrication (~> 2.18)
@@ -704,7 +699,7 @@ DEPENDENCIES
rubocop
ruby-oembed (~> 0.12)
ruby-progressbar (~> 1.4)
sanitize (~> 4.4)
sanitize (~> 4.6.4)
scss_lint (~> 0.55)
sidekiq (~> 5.0)
sidekiq-bulk (~> 0.1.1)

View File

@@ -1,4 +1,4 @@
web: PORT=3000 bundle exec puma -C config/puma.rb
sidekiq: PORT=3000 bundle exec sidekiq
stream: PORT=4000 yarn run start
web: env PORT=3000 bundle exec puma -C config/puma.rb
sidekiq: env PORT=3000 bundle exec sidekiq
stream: env PORT=4000 yarn run start
webpack: ./bin/webpack-dev-server --listen-host 0.0.0.0

View File

@@ -79,6 +79,16 @@ You can open issues for bugs you've found or features you think are missing. You
**IRC channel**: #mastodon on irc.freenode.net
## License
Copyright (C) 2016-2018 Eugen Rochko & other Mastodon contributors (see AUTHORS.md)
This program is free software: you can redistribute it and/or modify it under the terms of the GNU Affero General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version.
This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License for more details.
You should have received a copy of the GNU Affero General Public License along with this program. If not, see <https://www.gnu.org/licenses/>.
---
## Extra credits

View File

@@ -0,0 +1,57 @@
# frozen_string_literal: true
class ActivityPub::CollectionsController < Api::BaseController
include SignatureVerification
before_action :set_account
before_action :set_size
before_action :set_statuses
def show
render json: collection_presenter,
serializer: ActivityPub::CollectionSerializer,
adapter: ActivityPub::Adapter,
content_type: 'application/activity+json',
skip_activities: true
end
private
def set_account
@account = Account.find_local!(params[:account_username])
end
def set_statuses
@statuses = scope_for_collection.paginate_by_max_id(20, params[:max_id], params[:since_id])
@statuses = cache_collection(@statuses, Status)
end
def set_size
case params[:id]
when 'featured'
@account.pinned_statuses.count
else
raise ActiveRecord::NotFound
end
end
def scope_for_collection
case params[:id]
when 'featured'
@account.statuses.permitted_for(@account, signed_request_account).tap do |scope|
scope.merge!(@account.pinned_statuses)
end
else
raise ActiveRecord::NotFound
end
end
def collection_presenter
ActivityPub::CollectionPresenter.new(
id: account_collection_url(@account, params[:id]),
type: :ordered,
size: @size,
items: @statuses
)
end
end

View File

@@ -9,7 +9,7 @@ class ActivityPub::OutboxesController < Api::BaseController
@statuses = @account.statuses.permitted_for(@account, signed_request_account).paginate_by_max_id(20, params[:max_id], params[:since_id])
@statuses = cache_collection(@statuses, Status)
render json: outbox_presenter, serializer: ActivityPub::CollectionSerializer, adapter: ActivityPub::Adapter, content_type: 'application/activity+json'
render json: outbox_presenter, serializer: ActivityPub::OutboxSerializer, adapter: ActivityPub::Adapter, content_type: 'application/activity+json'
end
private

View File

@@ -51,7 +51,13 @@ class Api::V1::Accounts::StatusesController < Api::BaseController
end
def account_media_status_ids
@account.media_attachments.attached.reorder(nil).select(:status_id).distinct
# `SELECT DISTINCT id, updated_at` is too slow, so pluck ids at first, and then select id, updated_at with ids.
# Also, Avoid getting slow by not narrowing down by `statuses.account_id`.
# When narrowing down by `statuses.account_id`, `index_statuses_20180106` will be used
# and the table will be joined by `Merge Semi Join`, so the query will be slow.
Status.joins(:media_attachments).merge(@account.media_attachments).permitted_for(@account, current_account)
.paginate_by_max_id(limit_param(DEFAULT_STATUSES_LIMIT), params[:max_id], params[:since_id])
.reorder(id: :desc).distinct(:id).pluck(:id)
end
def pinned_scope

View File

@@ -11,12 +11,18 @@ class Api::V1::Statuses::PinsController < Api::BaseController
def create
StatusPin.create!(account: current_account, status: @status)
distribute_add_activity!
render json: @status, serializer: REST::StatusSerializer
end
def destroy
pin = StatusPin.find_by(account: current_account, status: @status)
pin&.destroy!
if pin
pin.destroy!
distribute_remove_activity!
end
render json: @status, serializer: REST::StatusSerializer
end
@@ -25,4 +31,24 @@ class Api::V1::Statuses::PinsController < Api::BaseController
def set_status
@status = Status.find(params[:status_id])
end
def distribute_add_activity!
json = ActiveModelSerializers::SerializableResource.new(
@status,
serializer: ActivityPub::AddSerializer,
adapter: ActivityPub::Adapter
).as_json
ActivityPub::RawDistributionWorker.perform_async(Oj.dump(json), current_account)
end
def distribute_remove_activity!
json = ActiveModelSerializers::SerializableResource.new(
@status,
serializer: ActivityPub::RemoveSerializer,
adapter: ActivityPub::Adapter
).as_json
ActivityPub::RawDistributionWorker.perform_async(Oj.dump(json), current_account)
end
end

View File

@@ -12,10 +12,9 @@ class Auth::SessionsController < Devise::SessionsController
def new
Devise.omniauth_configs.each do |provider, config|
if config.strategy.redirect_at_sign_in
return redirect_to(omniauth_authorize_path(resource_name, provider))
end
return redirect_to(omniauth_authorize_path(resource_name, provider)) if config.strategy.redirect_at_sign_in
end
super
end
@@ -59,6 +58,14 @@ class Auth::SessionsController < Devise::SessionsController
end
end
def after_sign_out_path_for(_resource_or_scope)
Devise.omniauth_configs.each_value do |config|
return root_path if config.strategy.redirect_at_sign_in
end
super
end
def two_factor_enabled?
find_user.try(:otp_required_for_login?)
end

View File

@@ -17,11 +17,11 @@ module Localized
end
def default_locale
request_locale || env_locale || I18n.default_locale
end
def env_locale
ENV['DEFAULT_LOCALE']
if ENV['DEFAULT_LOCALE'].present?
I18n.default_locale
else
request_locale || I18n.default_locale
end
end
def request_locale
@@ -29,12 +29,10 @@ module Localized
end
def preferred_locale
http_accept_language.preferred_language_from([env_locale]) ||
http_accept_language.preferred_language_from(I18n.available_locales)
http_accept_language.preferred_language_from(I18n.available_locales)
end
def compatible_locale
http_accept_language.compatible_language_from([env_locale]) ||
http_accept_language.compatible_language_from(I18n.available_locales)
http_accept_language.compatible_language_from(I18n.available_locales)
end
end

View File

@@ -34,7 +34,8 @@ class HomeController < ApplicationController
end
end
redirect_to(default_redirect_path)
matches = request.path.match(%r{\A/web/timelines/tag/(?<tag>.+)\z})
redirect_to(matches ? tag_path(CGI.unescape(matches[:tag])) : default_redirect_path)
end
def set_initial_state_json

View File

@@ -2,7 +2,7 @@
module InstanceHelper
def site_title
Setting.site_title.presence || site_hostname
Setting.site_title
end
def site_hostname

View File

@@ -8,6 +8,56 @@ module StreamEntriesHelper
account.display_name.presence || account.username
end
def account_description(account)
prepend_str = [
[
number_to_human(account.statuses_count, strip_insignificant_zeros: true),
t('accounts.posts'),
].join(' '),
[
number_to_human(account.following_count, strip_insignificant_zeros: true),
t('accounts.following'),
].join(' '),
[
number_to_human(account.followers_count, strip_insignificant_zeros: true),
t('accounts.followers'),
].join(' '),
].join(', ')
[prepend_str, account.note].join(' · ')
end
def media_summary(status)
attachments = { image: 0, video: 0 }
status.media_attachments.each do |media|
if media.video?
attachments[:video] += 1
else
attachments[:image] += 1
end
end
text = attachments.to_a.reject { |_, value| value.zero? }.map { |key, value| t("statuses.attached.#{key}", count: value) }.join(' · ')
return if text.blank?
t('statuses.attached.description', attached: text)
end
def status_text_summary(status)
return if status.spoiler_text.blank?
t('statuses.content_warning', warning: status.spoiler_text)
end
def status_description(status)
components = [[media_summary(status), status_text_summary(status)].reject(&:blank?).join(' · ')]
components << status.text if status.spoiler_text.blank?
components.reject(&:blank?).join("\n\n")
end
def stream_link_target
embedded_view? ? '_blank' : nil
end

View File

@@ -1,6 +1,8 @@
import api from '../api';
import { CancelToken } from 'axios';
import { throttle } from 'lodash';
import { search as emojiSearch } from '../features/emoji/emoji_mart_search_light';
import { tagHistory } from '../settings';
import { useEmoji } from './emojis';
import {
@@ -10,6 +12,8 @@ import {
refreshPublicTimeline,
} from './timelines';
let cancelFetchComposeSuggestionsAccounts;
export const COMPOSE_CHANGE = 'COMPOSE_CHANGE';
export const COMPOSE_SUBMIT_REQUEST = 'COMPOSE_SUBMIT_REQUEST';
export const COMPOSE_SUBMIT_SUCCESS = 'COMPOSE_SUBMIT_SUCCESS';
@@ -27,6 +31,9 @@ export const COMPOSE_UPLOAD_UNDO = 'COMPOSE_UPLOAD_UNDO';
export const COMPOSE_SUGGESTIONS_CLEAR = 'COMPOSE_SUGGESTIONS_CLEAR';
export const COMPOSE_SUGGESTIONS_READY = 'COMPOSE_SUGGESTIONS_READY';
export const COMPOSE_SUGGESTION_SELECT = 'COMPOSE_SUGGESTION_SELECT';
export const COMPOSE_SUGGESTION_TAGS_UPDATE = 'COMPOSE_SUGGESTION_TAGS_UPDATE';
export const COMPOSE_TAG_HISTORY_UPDATE = 'COMPOSE_TAG_HISTORY_UPDATE';
export const COMPOSE_MOUNT = 'COMPOSE_MOUNT';
export const COMPOSE_UNMOUNT = 'COMPOSE_UNMOUNT';
@@ -92,8 +99,9 @@ export function mentionCompose(account, router) {
export function submitCompose() {
return function (dispatch, getState) {
const status = getState().getIn(['compose', 'text'], '');
const media = getState().getIn(['compose', 'media_attachments']);
if (!status || !status.length) {
if ((!status || !status.length) && media.size === 0) {
return;
}
@@ -102,7 +110,7 @@ export function submitCompose() {
api(getState).post('/api/v1/statuses', {
status,
in_reply_to_id: getState().getIn(['compose', 'in_reply_to'], null),
media_ids: getState().getIn(['compose', 'media_attachments']).map(item => item.get('id')),
media_ids: media.map(item => item.get('id')),
sensitive: getState().getIn(['compose', 'sensitive']),
spoiler_text: getState().getIn(['compose', 'spoiler_text'], ''),
visibility: getState().getIn(['compose', 'privacy']),
@@ -111,6 +119,7 @@ export function submitCompose() {
'Idempotency-Key': getState().getIn(['compose', 'idempotencyKey']),
},
}).then(function (response) {
dispatch(insertIntoTagHistory(response.data.tags));
dispatch(submitComposeSuccess({ ...response.data }));
// To make the app more responsive, immediately get the status into the columns
@@ -251,13 +260,22 @@ export function undoUploadCompose(media_id) {
};
export function clearComposeSuggestions() {
if (cancelFetchComposeSuggestionsAccounts) {
cancelFetchComposeSuggestionsAccounts();
}
return {
type: COMPOSE_SUGGESTIONS_CLEAR,
};
};
const fetchComposeSuggestionsAccounts = throttle((dispatch, getState, token) => {
if (cancelFetchComposeSuggestionsAccounts) {
cancelFetchComposeSuggestionsAccounts();
}
api(getState).get('/api/v1/accounts/search', {
cancelToken: new CancelToken(cancel => {
cancelFetchComposeSuggestionsAccounts = cancel;
}),
params: {
q: token.slice(1),
resolve: false,
@@ -273,12 +291,22 @@ const fetchComposeSuggestionsEmojis = (dispatch, getState, token) => {
dispatch(readyComposeSuggestionsEmojis(token, results));
};
const fetchComposeSuggestionsTags = (dispatch, getState, token) => {
dispatch(updateSuggestionTags(token));
};
export function fetchComposeSuggestions(token) {
return (dispatch, getState) => {
if (token[0] === ':') {
switch (token[0]) {
case ':':
fetchComposeSuggestionsEmojis(dispatch, getState, token);
} else {
break;
case '#':
fetchComposeSuggestionsTags(dispatch, getState, token);
break;
default:
fetchComposeSuggestionsAccounts(dispatch, getState, token);
break;
}
};
};
@@ -308,6 +336,9 @@ export function selectComposeSuggestion(position, token, suggestion) {
startPosition = position - 1;
dispatch(useEmoji(suggestion));
} else if (suggestion[0] === '#') {
completion = suggestion;
startPosition = position - 1;
} else {
completion = getState().getIn(['accounts', suggestion, 'acct']);
startPosition = position;
@@ -322,6 +353,48 @@ export function selectComposeSuggestion(position, token, suggestion) {
};
};
export function updateSuggestionTags(token) {
return {
type: COMPOSE_SUGGESTION_TAGS_UPDATE,
token,
};
}
export function updateTagHistory(tags) {
return {
type: COMPOSE_TAG_HISTORY_UPDATE,
tags,
};
}
export function hydrateCompose() {
return (dispatch, getState) => {
const me = getState().getIn(['meta', 'me']);
const history = tagHistory.get(me);
if (history !== null) {
dispatch(updateTagHistory(history));
}
};
}
function insertIntoTagHistory(tags) {
return (dispatch, getState) => {
const state = getState();
const oldHistory = state.getIn(['compose', 'tagHistory']);
const me = state.getIn(['meta', 'me']);
const names = tags.map(({ name }) => name);
const intersectedOldHistory = oldHistory.filter(name => !names.includes(name));
names.push(...intersectedOldHistory.toJS());
const newHistory = names.slice(0, 1000);
tagHistory.set(me, newHistory);
dispatch(updateTagHistory(newHistory));
};
}
export function mountCompose() {
return {
type: COMPOSE_MOUNT,

View File

@@ -0,0 +1,10 @@
export const DROPDOWN_MENU_OPEN = 'DROPDOWN_MENU_OPEN';
export const DROPDOWN_MENU_CLOSE = 'DROPDOWN_MENU_CLOSE';
export function openDropdownMenu(id, placement) {
return { type: DROPDOWN_MENU_OPEN, id, placement };
}
export function closeDropdownMenu(id) {
return { type: DROPDOWN_MENU_CLOSE, id };
}

View File

@@ -62,6 +62,7 @@ export function reblogRequest(status) {
return {
type: REBLOG_REQUEST,
status: status,
skipLoading: true,
};
};
@@ -70,6 +71,7 @@ export function reblogSuccess(status, response) {
type: REBLOG_SUCCESS,
status: status,
response: response,
skipLoading: true,
};
};
@@ -78,6 +80,7 @@ export function reblogFail(status, error) {
type: REBLOG_FAIL,
status: status,
error: error,
skipLoading: true,
};
};
@@ -85,6 +88,7 @@ export function unreblogRequest(status) {
return {
type: UNREBLOG_REQUEST,
status: status,
skipLoading: true,
};
};
@@ -93,6 +97,7 @@ export function unreblogSuccess(status, response) {
type: UNREBLOG_SUCCESS,
status: status,
response: response,
skipLoading: true,
};
};
@@ -101,6 +106,7 @@ export function unreblogFail(status, error) {
type: UNREBLOG_FAIL,
status: status,
error: error,
skipLoading: true,
};
};
@@ -132,6 +138,7 @@ export function favouriteRequest(status) {
return {
type: FAVOURITE_REQUEST,
status: status,
skipLoading: true,
};
};
@@ -140,6 +147,7 @@ export function favouriteSuccess(status, response) {
type: FAVOURITE_SUCCESS,
status: status,
response: response,
skipLoading: true,
};
};
@@ -148,6 +156,7 @@ export function favouriteFail(status, error) {
type: FAVOURITE_FAIL,
status: status,
error: error,
skipLoading: true,
};
};
@@ -155,6 +164,7 @@ export function unfavouriteRequest(status) {
return {
type: UNFAVOURITE_REQUEST,
status: status,
skipLoading: true,
};
};
@@ -163,6 +173,7 @@ export function unfavouriteSuccess(status, response) {
type: UNFAVOURITE_SUCCESS,
status: status,
response: response,
skipLoading: true,
};
};
@@ -171,6 +182,7 @@ export function unfavouriteFail(status, error) {
type: UNFAVOURITE_FAIL,
status: status,
error: error,
skipLoading: true,
};
};
@@ -258,6 +270,7 @@ export function pinRequest(status) {
return {
type: PIN_REQUEST,
status,
skipLoading: true,
};
};
@@ -266,6 +279,7 @@ export function pinSuccess(status, response) {
type: PIN_SUCCESS,
status,
response,
skipLoading: true,
};
};
@@ -274,6 +288,7 @@ export function pinFail(status, error) {
type: PIN_FAIL,
status,
error,
skipLoading: true,
};
};
@@ -293,6 +308,7 @@ export function unpinRequest(status) {
return {
type: UNPIN_REQUEST,
status,
skipLoading: true,
};
};
@@ -301,6 +317,7 @@ export function unpinSuccess(status, response) {
type: UNPIN_SUCCESS,
status,
response,
skipLoading: true,
};
};
@@ -309,5 +326,6 @@ export function unpinFail(status, error) {
type: UNPIN_FAIL,
status,
error,
skipLoading: true,
};
};

View File

@@ -24,7 +24,7 @@ defineMessages({
const fetchRelatedRelationships = (dispatch, notifications) => {
const accountIds = notifications.filter(item => item.type === 'follow').map(item => item.account.id);
if (accountIds > 0) {
if (accountIds.length > 0) {
dispatch(fetchRelationships(accountIds));
}
};

View File

@@ -23,6 +23,9 @@ export const STATUS_UNMUTE_REQUEST = 'STATUS_UNMUTE_REQUEST';
export const STATUS_UNMUTE_SUCCESS = 'STATUS_UNMUTE_SUCCESS';
export const STATUS_UNMUTE_FAIL = 'STATUS_UNMUTE_FAIL';
export const STATUS_REVEAL = 'STATUS_REVEAL';
export const STATUS_HIDE = 'STATUS_HIDE';
export function fetchStatusRequest(id, skipLoading) {
return {
type: STATUS_FETCH_REQUEST,
@@ -215,3 +218,25 @@ export function unmuteStatusFail(id, error) {
error,
};
};
export function hideStatus(ids) {
if (!Array.isArray(ids)) {
ids = [ids];
}
return {
type: STATUS_HIDE,
ids,
};
};
export function revealStatus(ids) {
if (!Array.isArray(ids)) {
ids = [ids];
}
return {
type: STATUS_REVEAL,
ids,
};
};

View File

@@ -1,4 +1,5 @@
import { Iterable, fromJS } from 'immutable';
import { hydrateCompose } from './compose';
export const STORE_HYDRATE = 'STORE_HYDRATE';
export const STORE_HYDRATE_LAZY = 'STORE_HYDRATE_LAZY';
@@ -8,10 +9,14 @@ const convertState = rawState =>
Iterable.isIndexed(v) ? v.toList() : v.toMap());
export function hydrateStore(rawState) {
const state = convertState(rawState);
return dispatch => {
const state = convertState(rawState);
return {
type: STORE_HYDRATE,
state,
dispatch({
type: STORE_HYDRATE,
state,
});
dispatch(hydrateCompose());
};
};

View File

@@ -117,13 +117,14 @@ export function refreshTimeline(timelineId, path, params = {}) {
};
};
export const refreshHomeTimeline = () => refreshTimeline('home', '/api/v1/timelines/home');
export const refreshPublicTimeline = () => refreshTimeline('public', '/api/v1/timelines/public');
export const refreshCommunityTimeline = () => refreshTimeline('community', '/api/v1/timelines/public', { local: true });
export const refreshAccountTimeline = (accountId, withReplies) => refreshTimeline(`account:${accountId}${withReplies ? ':with_replies' : ''}`, `/api/v1/accounts/${accountId}/statuses`, { exclude_replies: !withReplies });
export const refreshAccountMediaTimeline = accountId => refreshTimeline(`account:${accountId}:media`, `/api/v1/accounts/${accountId}/statuses`, { only_media: true });
export const refreshHashtagTimeline = hashtag => refreshTimeline(`hashtag:${hashtag}`, `/api/v1/timelines/tag/${hashtag}`);
export const refreshListTimeline = id => refreshTimeline(`list:${id}`, `/api/v1/timelines/list/${id}`);
export const refreshHomeTimeline = () => refreshTimeline('home', '/api/v1/timelines/home');
export const refreshPublicTimeline = () => refreshTimeline('public', '/api/v1/timelines/public');
export const refreshCommunityTimeline = () => refreshTimeline('community', '/api/v1/timelines/public', { local: true });
export const refreshAccountTimeline = (accountId, withReplies) => refreshTimeline(`account:${accountId}${withReplies ? ':with_replies' : ''}`, `/api/v1/accounts/${accountId}/statuses`, { exclude_replies: !withReplies });
export const refreshAccountFeaturedTimeline = accountId => refreshTimeline(`account:${accountId}:pinned`, `/api/v1/accounts/${accountId}/statuses`, { pinned: true });
export const refreshAccountMediaTimeline = accountId => refreshTimeline(`account:${accountId}:media`, `/api/v1/accounts/${accountId}/statuses`, { only_media: true });
export const refreshHashtagTimeline = hashtag => refreshTimeline(`hashtag:${hashtag}`, `/api/v1/timelines/tag/${hashtag}`);
export const refreshListTimeline = id => refreshTimeline(`list:${id}`, `/api/v1/timelines/list/${id}`);
export function refreshTimelineFail(timeline, error, skipLoading) {
return {

View File

@@ -3,6 +3,7 @@ import 'intl/locale-data/jsonp/en';
import 'es6-symbol/implement';
import includes from 'array-includes';
import assign from 'object-assign';
import values from 'object.values';
import isNaN from 'is-nan';
if (!Array.prototype.includes) {
@@ -13,6 +14,10 @@ if (!Object.assign) {
Object.assign = assign;
}
if (!Object.values) {
values.shim();
}
if (!Number.isNaN) {
Number.isNaN = isNaN;
}

View File

@@ -1,5 +1,6 @@
import React from 'react';
import ImmutablePropTypes from 'react-immutable-proptypes';
import PropTypes from 'prop-types';
import ImmutablePureComponent from 'react-immutable-pure-component';
const filename = url => url.split('/').pop().split('#')[0].split('?')[0];
@@ -8,10 +9,29 @@ export default class AttachmentList extends ImmutablePureComponent {
static propTypes = {
media: ImmutablePropTypes.list.isRequired,
compact: PropTypes.bool,
};
render () {
const { media } = this.props;
const { media, compact } = this.props;
if (compact) {
return (
<div className='attachment-list compact'>
<ul className='attachment-list__list'>
{media.map(attachment => {
const displayUrl = attachment.get('remote_url') || attachment.get('url');
return (
<li key={attachment.get('id')}>
<a href={displayUrl} target='_blank' rel='noopener'><i className='fa fa-link' /> {filename(displayUrl)}</a>
</li>
);
})}
</ul>
</div>
);
}
return (
<div className='attachment-list'>
@@ -20,11 +40,15 @@ export default class AttachmentList extends ImmutablePureComponent {
</div>
<ul className='attachment-list__list'>
{media.map(attachment => (
<li key={attachment.get('id')}>
<a href={attachment.get('remote_url')} target='_blank' rel='noopener'>{filename(attachment.get('remote_url'))}</a>
</li>
))}
{media.map(attachment => {
const displayUrl = attachment.get('remote_url') || attachment.get('url');
return (
<li key={attachment.get('id')}>
<a href={displayUrl} target='_blank' rel='noopener'>{filename(displayUrl)}</a>
</li>
);
})}
</ul>
</div>
);

View File

@@ -20,7 +20,7 @@ const textAtCursorMatchesToken = (str, caretPosition) => {
word = str.slice(left, right + caretPosition);
}
if (!word || word.trim().length < 3 || ['@', ':'].indexOf(word[0]) === -1) {
if (!word || word.trim().length < 3 || ['@', ':', '#'].indexOf(word[0]) === -1) {
return [null, null];
}
@@ -170,6 +170,9 @@ export default class AutosuggestTextarea extends ImmutablePureComponent {
if (typeof suggestion === 'object') {
inner = <AutosuggestEmoji emoji={suggestion} />;
key = suggestion.id;
} else if (suggestion[0] === '#') {
inner = suggestion;
key = suggestion;
} else {
inner = <AutosuggestAccountContainer id={suggestion} />;
key = suggestion;

View File

@@ -19,10 +19,11 @@ export default class ColumnHeader extends React.PureComponent {
static propTypes = {
intl: PropTypes.object.isRequired,
title: PropTypes.node.isRequired,
icon: PropTypes.string.isRequired,
title: PropTypes.node,
icon: PropTypes.string,
active: PropTypes.bool,
multiColumn: PropTypes.bool,
extraButton: PropTypes.node,
showBackButton: PropTypes.bool,
children: PropTypes.node,
pinned: PropTypes.bool,
@@ -63,7 +64,7 @@ export default class ColumnHeader extends React.PureComponent {
}
render () {
const { title, icon, active, children, pinned, onPin, multiColumn, showBackButton, intl: { formatMessage } } = this.props;
const { title, icon, active, children, pinned, onPin, multiColumn, extraButton, showBackButton, intl: { formatMessage } } = this.props;
const { collapsed, animating } = this.state;
const wrapperClassName = classNames('column-header__wrapper', {
@@ -125,19 +126,26 @@ export default class ColumnHeader extends React.PureComponent {
}
if (children || multiColumn) {
collapseButton = <button className={collapsibleButtonClassName} aria-label={formatMessage(collapsed ? messages.show : messages.hide)} aria-pressed={collapsed ? 'false' : 'true'} onClick={this.handleToggleClick}><i className='fa fa-sliders' /></button>;
collapseButton = <button className={collapsibleButtonClassName} title={formatMessage(collapsed ? messages.show : messages.hide)} aria-label={formatMessage(collapsed ? messages.show : messages.hide)} aria-pressed={collapsed ? 'false' : 'true'} onClick={this.handleToggleClick}><i className='fa fa-sliders' /></button>;
}
const hasTitle = icon && title;
return (
<div className={wrapperClassName}>
<h1 className={buttonClassName}>
<button onClick={this.handleTitleClick}>
<i className={`fa fa-fw fa-${icon} column-header__icon`} />
{title}
</button>
{hasTitle && (
<button onClick={this.handleTitleClick}>
<i className={`fa fa-fw fa-${icon} column-header__icon`} />
{title}
</button>
)}
{!hasTitle && backButton}
<div className='column-header__buttons'>
{backButton}
{hasTitle && backButton}
{extraButton}
{collapseButton}
</div>
</h1>

View File

@@ -8,6 +8,7 @@ import spring from 'react-motion/lib/spring';
import detectPassiveEvents from 'detect-passive-events';
const listenerOptions = detectPassiveEvents.hasSupport ? { passive: true } : false;
let id = 0;
class DropdownMenu extends React.PureComponent {
@@ -29,6 +30,10 @@ class DropdownMenu extends React.PureComponent {
placement: 'bottom',
};
state = {
mounted: false,
};
handleDocumentClick = e => {
if (this.node && !this.node.contains(e.target)) {
this.props.onClose();
@@ -38,6 +43,7 @@ class DropdownMenu extends React.PureComponent {
componentDidMount () {
document.addEventListener('click', this.handleDocumentClick, false);
document.addEventListener('touchend', this.handleDocumentClick, listenerOptions);
this.setState({ mounted: true });
}
componentWillUnmount () {
@@ -82,11 +88,15 @@ class DropdownMenu extends React.PureComponent {
render () {
const { items, style, placement, arrowOffsetLeft, arrowOffsetTop } = this.props;
const { mounted } = this.state;
return (
<Motion defaultStyle={{ opacity: 0, scaleX: 0.85, scaleY: 0.75 }} style={{ opacity: spring(1, { damping: 35, stiffness: 400 }), scaleX: spring(1, { damping: 35, stiffness: 400 }), scaleY: spring(1, { damping: 35, stiffness: 400 }) }}>
{({ opacity, scaleX, scaleY }) => (
<div className='dropdown-menu' style={{ ...style, opacity: opacity, transform: `scale(${scaleX}, ${scaleY})` }} ref={this.setRef}>
// It should not be transformed when mounting because the resulting
// size will be used to determine the coordinate of the menu by
// react-overlays
<div className='dropdown-menu' style={{ ...style, opacity: opacity, transform: mounted ? `scale(${scaleX}, ${scaleY})` : null }} ref={this.setRef}>
<div className={`dropdown-menu__arrow ${placement}`} style={{ left: arrowOffsetLeft, top: arrowOffsetTop }} />
<ul>
@@ -115,8 +125,10 @@ export default class Dropdown extends React.PureComponent {
status: ImmutablePropTypes.map,
isUserTouching: PropTypes.func,
isModalOpen: PropTypes.bool.isRequired,
onModalOpen: PropTypes.func,
onModalClose: PropTypes.func,
onOpen: PropTypes.func.isRequired,
onClose: PropTypes.func.isRequired,
dropdownPlacement: PropTypes.string,
openDropdownId: PropTypes.number,
};
static defaultProps = {
@@ -124,37 +136,28 @@ export default class Dropdown extends React.PureComponent {
};
state = {
expanded: false,
id: id++,
};
handleClick = () => {
if (!this.state.expanded && this.props.isUserTouching() && this.props.onModalOpen) {
const { status, items } = this.props;
handleClick = ({ target }) => {
if (this.state.id === this.props.openDropdownId) {
this.handleClose();
} else {
const { top } = target.getBoundingClientRect();
const placement = top * 2 < innerHeight ? 'bottom' : 'top';
this.props.onModalOpen({
status,
actions: items,
onClick: this.handleItemClick,
});
return;
this.props.onOpen(this.state.id, this.handleItemClick, placement);
}
this.setState({ expanded: !this.state.expanded });
}
handleClose = () => {
if (this.props.onModalClose) {
this.props.onModalClose();
}
this.setState({ expanded: false });
this.props.onClose(this.state.id);
}
handleKeyDown = e => {
switch(e.key) {
case 'Enter':
this.handleClick();
this.handleClick(e);
break;
case 'Escape':
this.handleClose();
@@ -186,22 +189,22 @@ export default class Dropdown extends React.PureComponent {
}
render () {
const { icon, items, size, title, disabled } = this.props;
const { expanded } = this.state;
const { icon, items, size, title, disabled, dropdownPlacement, openDropdownId } = this.props;
const open = this.state.id === openDropdownId;
return (
<div onKeyDown={this.handleKeyDown}>
<IconButton
icon={icon}
title={title}
active={expanded}
active={open}
disabled={disabled}
size={size}
ref={this.setTargetRef}
onClick={this.handleClick}
/>
<Overlay show={expanded} placement='bottom' target={this.findTarget}>
<Overlay show={open} placement={dropdownPlacement} target={this.findTarget}>
<DropdownMenu items={items} onClose={this.handleClose} />
</Overlay>
</div>

View File

@@ -11,6 +11,7 @@ export default class ExtendedVideoPlayer extends React.PureComponent {
time: PropTypes.number,
controls: PropTypes.bool.isRequired,
muted: PropTypes.bool.isRequired,
onClick: PropTypes.func,
};
handleLoadedData = () => {
@@ -31,6 +32,12 @@ export default class ExtendedVideoPlayer extends React.PureComponent {
this.video = c;
}
handleClick = e => {
e.stopPropagation();
const handler = this.props.onClick;
if (handler) handler();
}
render () {
const { src, muted, controls, alt } = this.props;
@@ -46,6 +53,7 @@ export default class ExtendedVideoPlayer extends React.PureComponent {
muted={muted}
controls={controls}
loop={!controls}
onClick={this.handleClick}
/>
</div>
);

View File

@@ -12,26 +12,6 @@ const messages = defineMessages({
toggle_visible: { id: 'media_gallery.toggle_visible', defaultMessage: 'Toggle visibility' },
});
const shiftToPoint = (containerToImageRatio, containerSize, imageSize, focusSize, toMinus) => {
const containerCenter = Math.floor(containerSize / 2);
const focusFactor = (focusSize + 1) / 2;
const scaledImage = Math.floor(imageSize / containerToImageRatio);
let focus = Math.floor(focusFactor * scaledImage);
if (toMinus) focus = scaledImage - focus;
let focusOffset = focus - containerCenter;
const remainder = scaledImage - focus;
const containerRemainder = containerSize - containerCenter;
if (remainder < containerRemainder) focusOffset -= containerRemainder - remainder;
if (focusOffset < 0) focusOffset = 0;
return (focusOffset * -100 / containerSize) + '%';
};
class Item extends React.PureComponent {
static contextTypes = {
@@ -44,8 +24,6 @@ class Item extends React.PureComponent {
index: PropTypes.number.isRequired,
size: PropTypes.number.isRequired,
onClick: PropTypes.func.isRequired,
containerWidth: PropTypes.number,
containerHeight: PropTypes.number,
};
static defaultProps = {
@@ -84,7 +62,7 @@ class Item extends React.PureComponent {
}
render () {
const { attachment, index, size, standalone, containerWidth, containerHeight } = this.props;
const { attachment, index, size, standalone } = this.props;
let width = 50;
let height = 100;
@@ -143,35 +121,16 @@ class Item extends React.PureComponent {
const originalUrl = attachment.get('url');
const originalWidth = attachment.getIn(['meta', 'original', 'width']);
const originalHeight = attachment.getIn(['meta', 'original', 'height']);
const hasSize = typeof originalWidth === 'number' && typeof previewWidth === 'number';
const srcSet = hasSize ? `${originalUrl} ${originalWidth}w, ${previewUrl} ${previewWidth}w` : null;
const sizes = hasSize ? `(min-width: 1025px) ${320 * (width / 100)}px, ${width}vw` : null;
const focusX = attachment.getIn(['meta', 'focus', 'x']);
const focusY = attachment.getIn(['meta', 'focus', 'y']);
const imageStyle = {};
if (originalWidth && originalHeight && containerWidth && containerHeight && focusX && focusY) {
const widthRatio = originalWidth / (containerWidth * (width / 100));
const heightRatio = originalHeight / (containerHeight * (height / 100));
let hShift = 0;
let vShift = 0;
if (widthRatio > heightRatio) {
hShift = shiftToPoint(heightRatio, (containerWidth * (width / 100)), originalWidth, focusX);
} else if(widthRatio < heightRatio) {
vShift = shiftToPoint(widthRatio, (containerHeight * (height / 100)), originalHeight, focusY, true);
}
imageStyle.top = vShift;
imageStyle.left = hShift;
} else {
imageStyle.height = '100%';
}
const focusX = attachment.getIn(['meta', 'focus', 'x']) || 0;
const focusY = attachment.getIn(['meta', 'focus', 'y']) || 0;
const x = ((focusX / 2) + .5) * 100;
const y = ((focusY / -2) + .5) * 100;
thumbnail = (
<a
@@ -186,7 +145,7 @@ class Item extends React.PureComponent {
sizes={sizes}
alt={attachment.get('description')}
title={attachment.get('description')}
style={imageStyle}
style={{ objectPosition: `${x}% ${y}%` }}
/>
</a>
);
@@ -310,7 +269,7 @@ export default class MediaGallery extends React.PureComponent {
if (this.isStandaloneEligible()) {
children = <Item standalone onClick={this.handleClick} attachment={media.get(0)} />;
} else {
children = media.take(4).map((attachment, i) => <Item key={attachment.get('id')} onClick={this.handleClick} attachment={attachment} index={i} size={size} containerWidth={width} containerHeight={style.height} />);
children = media.take(4).map((attachment, i) => <Item key={attachment.get('id')} onClick={this.handleClick} attachment={attachment} index={i} size={size} />);
}
}

View File

@@ -12,9 +12,15 @@ export default class Permalink extends React.PureComponent {
href: PropTypes.string.isRequired,
to: PropTypes.string.isRequired,
children: PropTypes.node,
onInterceptClick: PropTypes.func,
};
handleClick = (e) => {
handleClick = e => {
if (this.props.onInterceptClick && this.props.onInterceptClick()) {
e.preventDefault();
return;
}
if (this.context.router && e.button === 0 && !(e.ctrlKey || e.metaKey)) {
e.preventDefault();
this.context.router.history.push(this.props.to);
@@ -22,7 +28,7 @@ export default class Permalink extends React.PureComponent {
}
render () {
const { href, children, className, ...other } = this.props;
const { href, children, className, onInterceptClick, ...other } = this.props;
return (
<a target='_blank' href={href} onClick={this.handleClick} {...other} className={`permalink${className ? ' ' + className : ''}`}>

View File

@@ -17,7 +17,7 @@ export default class ScrollableList extends PureComponent {
static propTypes = {
scrollKey: PropTypes.string.isRequired,
onScrollToBottom: PropTypes.func,
onLoadMore: PropTypes.func.isRequired,
onScrollToTop: PropTypes.func,
onScroll: PropTypes.func,
trackScroll: PropTypes.bool,
@@ -45,9 +45,11 @@ export default class ScrollableList extends PureComponent {
const offset = scrollHeight - scrollTop - clientHeight;
this._oldScrollPosition = scrollHeight - scrollTop;
if (400 > offset && this.props.onScrollToBottom && !this.props.isLoading) {
this.props.onScrollToBottom();
} else if (scrollTop < 100 && this.props.onScrollToTop) {
if (400 > offset && this.props.onLoadMore && !this.props.isLoading) {
this.props.onLoadMore();
}
if (scrollTop < 100 && this.props.onScrollToTop) {
this.props.onScrollToTop();
} else if (this.props.onScroll) {
this.props.onScroll();
@@ -138,7 +140,7 @@ export default class ScrollableList extends PureComponent {
handleLoadMore = (e) => {
e.preventDefault();
this.props.onScrollToBottom();
this.props.onLoadMore();
}
_recentlyMoved () {

View File

@@ -7,6 +7,7 @@ import RelativeTimestamp from './relative_timestamp';
import DisplayName from './display_name';
import StatusContent from './status_content';
import StatusActionBar from './status_action_bar';
import AttachmentList from './attachment_list';
import { FormattedMessage } from 'react-intl';
import ImmutablePureComponent from 'react-immutable-pure-component';
import { MediaGallery, Video } from '../features/ui/util/async-components';
@@ -36,16 +37,13 @@ export default class Status extends ImmutablePureComponent {
onBlock: PropTypes.func,
onEmbed: PropTypes.func,
onHeightChange: PropTypes.func,
onToggleHidden: PropTypes.func,
muted: PropTypes.bool,
hidden: PropTypes.bool,
onMoveUp: PropTypes.func,
onMoveDown: PropTypes.func,
};
state = {
isExpanded: false,
}
// Avoid checking props that are functions (and whose equality will always
// evaluate to false. See react-immutable-pure-component for usage.
updateOnProps = [
@@ -55,8 +53,6 @@ export default class Status extends ImmutablePureComponent {
'hidden',
]
updateOnStates = ['isExpanded']
handleClick = () => {
if (!this.context.router) {
return;
@@ -75,7 +71,7 @@ export default class Status extends ImmutablePureComponent {
}
handleExpandedToggle = () => {
this.setState({ isExpanded: !this.state.isExpanded });
this.props.onToggleHidden(this._properStatus());
};
renderLoadingMediaGallery () {
@@ -138,8 +134,7 @@ export default class Status extends ImmutablePureComponent {
let media = null;
let statusAvatar, prepend;
const { hidden } = this.props;
const { isExpanded } = this.state;
const { hidden, featured } = this.props;
let { status, account, ...other } = this.props;
@@ -156,7 +151,14 @@ export default class Status extends ImmutablePureComponent {
);
}
if (status.get('reblog', null) !== null && typeof status.get('reblog') === 'object') {
if (featured) {
prepend = (
<div className='status__prepend'>
<div className='status__prepend-icon-wrapper'><i className='fa fa-fw fa-thumb-tack status__prepend-icon' /></div>
<FormattedMessage id='status.pinned' defaultMessage='Pinned toot' />
</div>
);
} else if (status.get('reblog', null) !== null && typeof status.get('reblog') === 'object') {
const display_name_html = { __html: status.getIn(['account', 'display_name_html']) };
prepend = (
@@ -170,9 +172,14 @@ export default class Status extends ImmutablePureComponent {
status = status.get('reblog');
}
if (status.get('media_attachments').size > 0 && !this.props.muted) {
if (status.get('media_attachments').some(item => item.get('type') === 'unknown')) {
if (status.get('media_attachments').size > 0) {
if (this.props.muted || status.get('media_attachments').some(item => item.get('type') === 'unknown')) {
media = (
<AttachmentList
compact
media={status.get('media_attachments')}
/>
);
} else if (status.getIn(['media_attachments', 0, 'type']) === 'video') {
const video = status.getIn(['media_attachments', 0]);
@@ -235,7 +242,7 @@ export default class Status extends ImmutablePureComponent {
</a>
</div>
<StatusContent status={status} onClick={this.handleClick} expanded={isExpanded} onExpandedToggle={this.handleExpandedToggle} />
<StatusContent status={status} onClick={this.handleClick} expanded={!status.get('hidden')} onExpandedToggle={this.handleExpandedToggle} />
{media}

View File

@@ -24,7 +24,12 @@ export default class StatusContent extends React.PureComponent {
};
_updateStatusLinks () {
const node = this.node;
const node = this.node;
if (!node) {
return;
}
const links = node.querySelectorAll('a');
for (var i = 0; i < links.length; ++i) {
@@ -115,6 +120,10 @@ export default class StatusContent extends React.PureComponent {
render () {
const { status } = this.props;
if (status.get('content').length === 0) {
return null;
}
const hidden = this.props.onExpandedToggle ? !this.props.expanded : this.state.hidden;
const content = { __html: status.get('contentHtml') };
@@ -145,7 +154,7 @@ export default class StatusContent extends React.PureComponent {
}
return (
<div className={classNames} ref={this.setRef} tabIndex='0' onMouseDown={this.handleMouseDown} onMouseUp={this.handleMouseUp}>
<div className={classNames} ref={this.setRef} tabIndex='0' style={directionStyle} onMouseDown={this.handleMouseDown} onMouseUp={this.handleMouseUp}>
<p style={{ marginBottom: hidden && status.get('mentions').isEmpty() ? '0px' : null }}>
<span dangerouslySetInnerHTML={spoilerContent} />
{' '}

View File

@@ -11,7 +11,8 @@ export default class StatusList extends ImmutablePureComponent {
static propTypes = {
scrollKey: PropTypes.string.isRequired,
statusIds: ImmutablePropTypes.list.isRequired,
onScrollToBottom: PropTypes.func,
featuredStatusIds: ImmutablePropTypes.list,
onLoadMore: PropTypes.func,
onScrollToTop: PropTypes.func,
onScroll: PropTypes.func,
trackScroll: PropTypes.bool,
@@ -50,7 +51,7 @@ export default class StatusList extends ImmutablePureComponent {
}
render () {
const { statusIds, ...other } = this.props;
const { statusIds, featuredStatusIds, ...other } = this.props;
const { isLoading, isPartial } = other;
if (isPartial) {
@@ -68,8 +69,8 @@ export default class StatusList extends ImmutablePureComponent {
);
}
const scrollableContent = (isLoading || statusIds.size > 0) ? (
statusIds.map((statusId) => (
let scrollableContent = (isLoading || statusIds.size > 0) ? (
statusIds.map(statusId => (
<StatusContainer
key={statusId}
id={statusId}
@@ -79,6 +80,18 @@ export default class StatusList extends ImmutablePureComponent {
))
) : null;
if (scrollableContent && featuredStatusIds) {
scrollableContent = featuredStatusIds.map(statusId => (
<StatusContainer
key={`f-${statusId}`}
id={statusId}
featured
onMoveUp={this.handleMoveUp}
onMoveDown={this.handleMoveDown}
/>
)).concat(scrollableContent);
}
return (
<ScrollableList {...other} ref={this.setRef}>
{scrollableContent}

View File

@@ -1,3 +1,4 @@
import { openDropdownMenu, closeDropdownMenu } from '../actions/dropdown_menu';
import { openModal, closeModal } from '../actions/modal';
import { connect } from 'react-redux';
import DropdownMenu from '../components/dropdown_menu';
@@ -5,12 +6,22 @@ import { isUserTouching } from '../is_mobile';
const mapStateToProps = state => ({
isModalOpen: state.get('modal').modalType === 'ACTIONS',
dropdownPlacement: state.getIn(['dropdown_menu', 'placement']),
openDropdownId: state.getIn(['dropdown_menu', 'openId']),
});
const mapDispatchToProps = dispatch => ({
isUserTouching,
onModalOpen: props => dispatch(openModal('ACTIONS', props)),
onModalClose: () => dispatch(closeModal()),
const mapDispatchToProps = (dispatch, { status, items }) => ({
onOpen(id, onItemClick, dropdownPlacement) {
dispatch(isUserTouching() ? openModal('ACTIONS', {
status,
actions: items,
onClick: onItemClick,
}) : openDropdownMenu(id, dropdownPlacement));
},
onClose(id) {
dispatch(closeModal());
dispatch(closeDropdownMenu(id));
},
});
export default connect(mapStateToProps, mapDispatchToProps)(DropdownMenu);

View File

@@ -15,7 +15,13 @@ import {
unpin,
} from '../actions/interactions';
import { blockAccount } from '../actions/accounts';
import { muteStatus, unmuteStatus, deleteStatus } from '../actions/statuses';
import {
muteStatus,
unmuteStatus,
deleteStatus,
hideStatus,
revealStatus,
} from '../actions/statuses';
import { initMuteModal } from '../actions/mutes';
import { initReport } from '../actions/reports';
import { openModal } from '../actions/modal';
@@ -128,6 +134,14 @@ const mapDispatchToProps = (dispatch, { intl }) => ({
}
},
onToggleHidden (status) {
if (status.get('hidden')) {
dispatch(revealStatus(status.get('id')));
} else {
dispatch(hideStatus(status.get('id')));
}
},
});
export default injectIntl(connect(makeMapStateToProps, mapDispatchToProps)(Status));

View File

@@ -13,6 +13,7 @@ const messages = defineMessages({
unfollow: { id: 'account.unfollow', defaultMessage: 'Unfollow' },
follow: { id: 'account.follow', defaultMessage: 'Follow' },
requested: { id: 'account.requested', defaultMessage: 'Awaiting approval. Click to cancel follow request' },
unblock: { id: 'account.unblock', defaultMessage: 'Unblock @{name}' },
});
class Avatar extends ImmutablePureComponent {
@@ -69,6 +70,7 @@ export default class Header extends ImmutablePureComponent {
static propTypes = {
account: ImmutablePropTypes.map,
onFollow: PropTypes.func.isRequired,
onBlock: PropTypes.func.isRequired,
intl: PropTypes.object.isRequired,
};
@@ -80,11 +82,20 @@ export default class Header extends ImmutablePureComponent {
}
let info = '';
let mutingInfo = '';
let actionBtn = '';
let lockedIcon = '';
if (me !== account.get('id') && account.getIn(['relationship', 'followed_by'])) {
info = <span className='account--follows-info'><FormattedMessage id='account.follows_you' defaultMessage='Follows you' /></span>;
} else if (me !== account.get('id') && account.getIn(['relationship', 'blocking'])) {
info = <span className='account--follows-info'><FormattedMessage id='account.blocked' defaultMessage='Blocked' /></span>;
}
if (me !== account.get('id') && account.getIn(['relationship', 'muting'])) {
mutingInfo = <span className='account--muting-info'><FormattedMessage id='account.muted' defaultMessage='Muted' /></span>;
} else if (me !== account.get('id') && account.getIn(['relationship', 'domain_blocking'])) {
mutingInfo = <span className='account--muting-info'><FormattedMessage id='account.domain_blocked' defaultMessage='Domain hidden' /></span>;
}
if (me !== account.get('id')) {
@@ -100,6 +111,12 @@ export default class Header extends ImmutablePureComponent {
<IconButton size={26} icon={account.getIn(['relationship', 'following']) ? 'user-times' : 'user-plus'} active={account.getIn(['relationship', 'following'])} title={intl.formatMessage(account.getIn(['relationship', 'following']) ? messages.unfollow : messages.follow)} onClick={this.props.onFollow} />
</div>
);
} else if (account.getIn(['relationship', 'blocking'])) {
actionBtn = (
<div className='account--action-button'>
<IconButton size={26} icon='unlock-alt' title={intl.formatMessage(messages.unblock, { name: account.get('username') })} onClick={this.props.onBlock} />
</div>
);
}
}
@@ -124,6 +141,7 @@ export default class Header extends ImmutablePureComponent {
<div className='account__header__content' dangerouslySetInnerHTML={content} />
{info}
{mutingInfo}
{actionBtn}
</div>
</div>

View File

@@ -2,6 +2,7 @@ import React from 'react';
import ImmutablePropTypes from 'react-immutable-proptypes';
import ImmutablePureComponent from 'react-immutable-pure-component';
import Permalink from '../../../components/permalink';
import { displaySensitiveMedia } from '../../../initial_state';
export default class MediaItem extends ImmutablePureComponent {
@@ -9,8 +10,22 @@ export default class MediaItem extends ImmutablePureComponent {
media: ImmutablePropTypes.map.isRequired,
};
state = {
visible: !this.props.media.getIn(['status', 'sensitive']) || displaySensitiveMedia,
};
handleClick = () => {
if (!this.state.visible) {
this.setState({ visible: true });
return true;
}
return false;
}
render () {
const { media } = this.props;
const { visible } = this.state;
const status = media.get('status');
const focusX = media.getIn(['meta', 'focus', 'x']);
const focusY = media.getIn(['meta', 'focus', 'y']);
@@ -18,21 +33,28 @@ export default class MediaItem extends ImmutablePureComponent {
const y = ((focusY / -2) + .5) * 100;
const style = {};
let content;
let label, icon;
if (media.get('type') === 'gifv') {
content = <span className='media-gallery__gifv__label'>GIF</span>;
label = <span className='media-gallery__gifv__label'>GIF</span>;
}
if (!status.get('sensitive')) {
if (visible) {
style.backgroundImage = `url(${media.get('preview_url')})`;
style.backgroundPosition = `${x}% ${y}%`;
} else {
icon = (
<span className='account-gallery__item__icons'>
<i className='fa fa-eye-slash' />
</span>
);
}
return (
<div className='account-gallery__item'>
<Permalink to={`/statuses/${status.get('id')}`} href={status.get('url')} style={style}>
{content}
<Permalink to={`/statuses/${status.get('id')}`} href={status.get('url')} style={style} onInterceptClick={this.handleClick}>
{icon}
{label}
</Permalink>
</div>
);

View File

@@ -21,6 +21,7 @@ export default class Header extends ImmutablePureComponent {
onMute: PropTypes.func.isRequired,
onBlockDomain: PropTypes.func.isRequired,
onUnblockDomain: PropTypes.func.isRequired,
hideTabs: PropTypes.bool,
};
static contextTypes = {
@@ -68,7 +69,7 @@ export default class Header extends ImmutablePureComponent {
}
render () {
const { account } = this.props;
const { account, hideTabs } = this.props;
if (account === null) {
return <MissingIndicator />;
@@ -81,6 +82,7 @@ export default class Header extends ImmutablePureComponent {
<InnerHeader
account={account}
onFollow={this.handleFollow}
onBlock={this.handleBlock}
/>
<ActionBar
@@ -94,11 +96,13 @@ export default class Header extends ImmutablePureComponent {
onUnblockDomain={this.handleUnblockDomain}
/>
<div className='account__section-headline'>
<NavLink exact to={`/accounts/${account.get('id')}`}><FormattedMessage id='account.posts' defaultMessage='Toots' /></NavLink>
<NavLink exact to={`/accounts/${account.get('id')}/with_replies`}><FormattedMessage id='account.posts_with_replies' defaultMessage='Toots with replies' /></NavLink>
<NavLink exact to={`/accounts/${account.get('id')}/media`}><FormattedMessage id='account.media' defaultMessage='Media' /></NavLink>
</div>
{!hideTabs && (
<div className='account__section-headline'>
<NavLink exact to={`/accounts/${account.get('id')}`}><FormattedMessage id='account.posts' defaultMessage='Toots' /></NavLink>
<NavLink exact to={`/accounts/${account.get('id')}/with_replies`}><FormattedMessage id='account.posts_with_replies' defaultMessage='Toots with replies' /></NavLink>
<NavLink exact to={`/accounts/${account.get('id')}/media`}><FormattedMessage id='account.media' defaultMessage='Media' /></NavLink>
</div>
)}
</div>
);
}

View File

@@ -3,7 +3,7 @@ import { connect } from 'react-redux';
import ImmutablePropTypes from 'react-immutable-proptypes';
import PropTypes from 'prop-types';
import { fetchAccount } from '../../actions/accounts';
import { refreshAccountTimeline, expandAccountTimeline } from '../../actions/timelines';
import { refreshAccountTimeline, refreshAccountFeaturedTimeline, expandAccountTimeline } from '../../actions/timelines';
import StatusList from '../../components/status_list';
import LoadingIndicator from '../../components/loading_indicator';
import Column from '../ui/components/column';
@@ -17,6 +17,7 @@ const mapStateToProps = (state, { params: { accountId }, withReplies = false })
return {
statusIds: state.getIn(['timelines', `account:${path}`, 'items'], ImmutableList()),
featuredStatusIds: withReplies ? ImmutableList() : state.getIn(['timelines', `account:${accountId}:pinned`, 'items'], ImmutableList()),
isLoading: state.getIn(['timelines', `account:${path}`, 'isLoading']),
hasMore: !!state.getIn(['timelines', `account:${path}`, 'next']),
};
@@ -29,31 +30,40 @@ export default class AccountTimeline extends ImmutablePureComponent {
params: PropTypes.object.isRequired,
dispatch: PropTypes.func.isRequired,
statusIds: ImmutablePropTypes.list,
featuredStatusIds: ImmutablePropTypes.list,
isLoading: PropTypes.bool,
hasMore: PropTypes.bool,
withReplies: PropTypes.bool,
};
componentWillMount () {
this.props.dispatch(fetchAccount(this.props.params.accountId));
this.props.dispatch(refreshAccountTimeline(this.props.params.accountId, this.props.withReplies));
const { params: { accountId }, withReplies } = this.props;
this.props.dispatch(fetchAccount(accountId));
if (!withReplies) {
this.props.dispatch(refreshAccountFeaturedTimeline(accountId));
}
this.props.dispatch(refreshAccountTimeline(accountId, withReplies));
}
componentWillReceiveProps (nextProps) {
if ((nextProps.params.accountId !== this.props.params.accountId && nextProps.params.accountId) || nextProps.withReplies !== this.props.withReplies) {
this.props.dispatch(fetchAccount(nextProps.params.accountId));
if (!nextProps.withReplies) {
this.props.dispatch(refreshAccountFeaturedTimeline(nextProps.params.accountId));
}
this.props.dispatch(refreshAccountTimeline(nextProps.params.accountId, nextProps.params.withReplies));
}
}
handleScrollToBottom = () => {
handleLoadMore = () => {
if (!this.props.isLoading && this.props.hasMore) {
this.props.dispatch(expandAccountTimeline(this.props.params.accountId, this.props.withReplies));
}
}
render () {
const { statusIds, isLoading, hasMore } = this.props;
const { statusIds, featuredStatusIds, isLoading, hasMore } = this.props;
if (!statusIds && isLoading) {
return (
@@ -71,9 +81,10 @@ export default class AccountTimeline extends ImmutablePureComponent {
prepend={<HeaderContainer accountId={this.props.params.accountId} />}
scrollKey='account_timeline'
statusIds={statusIds}
featuredStatusIds={featuredStatusIds}
isLoading={isLoading}
hasMore={hasMore}
onScrollToBottom={this.handleScrollToBottom}
onLoadMore={this.handleLoadMore}
/>
</Column>
);

View File

@@ -50,6 +50,7 @@ export default class ComposeForm extends ImmutablePureComponent {
onPaste: PropTypes.func.isRequired,
onPickEmoji: PropTypes.func.isRequired,
showSearch: PropTypes.bool,
anyMedia: PropTypes.bool,
};
static defaultProps = {
@@ -142,10 +143,10 @@ export default class ComposeForm extends ImmutablePureComponent {
}
render () {
const { intl, onPaste, showSearch } = this.props;
const { intl, onPaste, showSearch, anyMedia } = this.props;
const disabled = this.props.is_submitting;
const text = [this.props.spoiler_text, countableText(this.props.text)].join('');
const disabledButton = disabled || this.props.is_uploading || length(text) > 500 || (text.length !== 0 && text.trim().length === 0 && !anyMedia);
let publishText = '';
if (this.props.privacy === 'private' || this.props.privacy === 'direct') {
@@ -203,7 +204,7 @@ export default class ComposeForm extends ImmutablePureComponent {
</div>
<div className='compose-form__publish'>
<div className='compose-form__publish-button-wrapper'><Button text={publishText} onClick={this.handleSubmit} disabled={disabled || this.props.is_uploading || length(text) > 500 || (text.length !== 0 && text.trim().length === 0)} block /></div>
<div className='compose-form__publish-button-wrapper'><Button text={publishText} onClick={this.handleSubmit} disabled={disabledButton} block /></div>
</div>
</div>
);

View File

@@ -23,6 +23,7 @@ const mapStateToProps = state => ({
is_submitting: state.getIn(['compose', 'is_submitting']),
is_uploading: state.getIn(['compose', 'is_uploading']),
showSearch: state.getIn(['search', 'submitted']) && !state.getIn(['search', 'hidden']),
anyMedia: state.getIn(['compose', 'media_attachments']).size > 0,
});
const mapDispatchToProps = (dispatch) => ({

View File

@@ -9,7 +9,8 @@ import spring from 'react-motion/lib/spring';
import { injectIntl, defineMessages } from 'react-intl';
const messages = defineMessages({
title: { id: 'compose_form.sensitive', defaultMessage: 'Mark media as sensitive' },
marked: { id: 'compose_form.sensitive.marked', defaultMessage: 'Media is marked as sensitive' },
unmarked: { id: 'compose_form.sensitive.unmarked', defaultMessage: 'Media is not marked as sensitive' },
});
const mapStateToProps = state => ({
@@ -50,7 +51,7 @@ class SensitiveButton extends React.PureComponent {
<div className={className} style={{ transform: `scale(${scale})` }}>
<IconButton
className='compose-form__sensitive-button__icon'
title={intl.formatMessage(messages.title)}
title={intl.formatMessage(active ? messages.marked : messages.unmarked)}
icon={icon}
onClick={onClick}
size={18}

View File

@@ -4,12 +4,13 @@ import { changeComposeSpoilerness } from '../../../actions/compose';
import { injectIntl, defineMessages } from 'react-intl';
const messages = defineMessages({
title: { id: 'compose_form.spoiler', defaultMessage: 'Hide text behind warning' },
marked: { id: 'compose_form.spoiler.marked', defaultMessage: 'Text is hidden behind warning' },
unmarked: { id: 'compose_form.spoiler.unmarked', defaultMessage: 'Text is not hidden' },
});
const mapStateToProps = (state, { intl }) => ({
label: 'CW',
title: intl.formatMessage(messages.title),
title: intl.formatMessage(state.getIn(['compose', 'spoiler']) ? messages.marked : messages.unmarked),
active: state.getIn(['compose', 'spoiler']),
ariaControls: 'cw-spoiler-input',
});

View File

@@ -5,7 +5,7 @@ import PropTypes from 'prop-types';
import { FormattedMessage } from 'react-intl';
import { me } from '../../../initial_state';
const APPROX_HASHTAG_RE = /(?:^|[^\/\)\w])#(\w*[a-zA-Z]\w*)/i;
const APPROX_HASHTAG_RE = /(?:^|[^\/\)\w])#(\w*[a-zA-Z·]\w*)/i;
const mapStateToProps = state => ({
needsLockWarning: state.getIn(['compose', 'privacy']) === 'private' && !state.getIn(['accounts', me, 'locked']),

View File

@@ -97,7 +97,7 @@ export default class Compose extends React.PureComponent {
<ComposeFormContainer />
{multiColumn && (
<div className='drawer__inner__mastodon'>
<img alt='' src={elephantUIPlane} />
<img alt='' draggable='false' src={elephantUIPlane} />
</div>
)}
</div>

View File

@@ -62,7 +62,7 @@ export default class Favourites extends ImmutablePureComponent {
this.column = c;
}
handleScrollToBottom = debounce(() => {
handleLoadMore = debounce(() => {
this.props.dispatch(expandFavouritedStatuses());
}, 300, { leading: true })
@@ -89,7 +89,7 @@ export default class Favourites extends ImmutablePureComponent {
scrollKey={`favourited_statuses-${columnId}`}
hasMore={hasMore}
isLoading={isLoading}
onScrollToBottom={this.handleScrollToBottom}
onLoadMore={this.handleLoadMore}
/>
</Column>
);

View File

@@ -80,7 +80,7 @@ export default class Followers extends ImmutablePureComponent {
<ScrollContainer scrollKey='followers'>
<div className='scrollable' onScroll={this.handleScroll}>
<div className='followers'>
<HeaderContainer accountId={this.props.params.accountId} />
<HeaderContainer accountId={this.props.params.accountId} hideTabs />
{accountIds.map(id => <AccountContainer key={id} id={id} withNote={false} />)}
{loadMore}
</div>

View File

@@ -80,7 +80,7 @@ export default class Following extends ImmutablePureComponent {
<ScrollContainer scrollKey='following'>
<div className='scrollable' onScroll={this.handleScroll}>
<div className='following'>
<HeaderContainer accountId={this.props.params.accountId} />
<HeaderContainer accountId={this.props.params.accountId} hideTabs />
{accountIds.map(id => <AccountContainer key={id} id={id} withNote={false} />)}
{loadMore}
</div>

View File

@@ -2,7 +2,7 @@ import React from 'react';
import PropTypes from 'prop-types';
import { FormattedMessage } from 'react-intl';
export default class ClearColumnButton extends React.Component {
export default class ClearColumnButton extends React.PureComponent {
static propTypes = {
onClick: PropTypes.func.isRequired,

View File

@@ -50,8 +50,14 @@ export default class Notifications extends React.PureComponent {
trackScroll: true,
};
handleScrollToBottom = debounce(() => {
componentWillUnmount () {
this.handleLoadMore.cancel();
this.handleScrollToTop.cancel();
this.handleScroll.cancel();
this.props.dispatch(scrollTopNotifications(false));
}
handleLoadMore = debounce(() => {
this.props.dispatch(expandNotifications());
}, 300, { leading: true });
@@ -136,7 +142,7 @@ export default class Notifications extends React.PureComponent {
isLoading={isLoading}
hasMore={hasMore}
emptyMessage={emptyMessage}
onScrollToBottom={this.handleScrollToBottom}
onLoadMore={this.handleLoadMore}
onScrollToTop={this.handleScrollToTop}
onScroll={this.handleScroll}
shouldUpdateScroll={shouldUpdateScroll}

View File

@@ -2,6 +2,10 @@ import React from 'react';
import PropTypes from 'prop-types';
import ImmutablePropTypes from 'react-immutable-proptypes';
import Toggle from 'react-toggle';
import noop from 'lodash/noop';
import StatusContent from '../../../components/status_content';
import { MediaGallery, Video } from '../../ui/util/async-components';
import Bundle from '../../ui/components/bundle';
export default class StatusCheckBox extends React.PureComponent {
@@ -14,18 +18,48 @@ export default class StatusCheckBox extends React.PureComponent {
render () {
const { status, checked, onToggle, disabled } = this.props;
const content = { __html: status.get('contentHtml') };
let media = null;
if (status.get('reblog')) {
return null;
}
if (status.get('media_attachments').size > 0) {
if (status.get('media_attachments').some(item => item.get('type') === 'unknown')) {
} else if (status.getIn(['media_attachments', 0, 'type']) === 'video') {
const video = status.getIn(['media_attachments', 0]);
media = (
<Bundle fetchComponent={Video} loading={this.renderLoadingVideoPlayer} >
{Component => (
<Component
preview={video.get('preview_url')}
src={video.get('url')}
width={239}
height={110}
inline
sensitive={status.get('sensitive')}
onOpenVideo={noop}
/>
)}
</Bundle>
);
} else {
media = (
<Bundle fetchComponent={MediaGallery} loading={this.renderLoadingMediaGallery} >
{Component => <Component media={status.get('media_attachments')} sensitive={status.get('sensitive')} height={110} onOpenMedia={noop} />}
</Bundle>
);
}
}
return (
<div className='status-check-box'>
<div
className='status__content'
dangerouslySetInnerHTML={content}
/>
<div className='status-check-box__status'>
<StatusContent status={status} />
{media}
</div>
<div className='status-check-box-toggle'>
<Toggle checked={checked} onChange={onToggle} disabled={disabled} />

View File

@@ -22,6 +22,7 @@ export default class DetailedStatus extends ImmutablePureComponent {
status: ImmutablePropTypes.map.isRequired,
onOpenMedia: PropTypes.func.isRequired,
onOpenVideo: PropTypes.func.isRequired,
onToggleHidden: PropTypes.func.isRequired,
};
handleAccountClick = (e) => {
@@ -37,6 +38,10 @@ export default class DetailedStatus extends ImmutablePureComponent {
this.props.onOpenVideo(this.props.status.getIn(['media_attachments', 0]), startTime);
}
handleExpandedToggle = () => {
this.props.onToggleHidden(this.props.status);
}
render () {
const status = this.props.status.get('reblog') ? this.props.status.get('reblog') : this.props.status;
@@ -105,7 +110,7 @@ export default class DetailedStatus extends ImmutablePureComponent {
<DisplayName account={status.get('account')} />
</a>
<StatusContent status={status} />
<StatusContent status={status} expanded={!status.get('hidden')} onExpandedToggle={this.handleExpandedToggle} />
{media}

View File

@@ -21,12 +21,19 @@ import {
mentionCompose,
} from '../../actions/compose';
import { blockAccount } from '../../actions/accounts';
import { muteStatus, unmuteStatus, deleteStatus } from '../../actions/statuses';
import {
muteStatus,
unmuteStatus,
deleteStatus,
hideStatus,
revealStatus,
} from '../../actions/statuses';
import { initMuteModal } from '../../actions/mutes';
import { initReport } from '../../actions/reports';
import { makeGetStatus } from '../../selectors';
import { ScrollContainer } from 'react-router-scroll-4';
import ColumnBackButton from '../../components/column_back_button';
import ColumnHeader from '../../components/column_header';
import StatusContainer from '../../containers/status_container';
import { openModal } from '../../actions/modal';
import { defineMessages, injectIntl, FormattedMessage } from 'react-intl';
@@ -39,6 +46,8 @@ const messages = defineMessages({
deleteConfirm: { id: 'confirmations.delete.confirm', defaultMessage: 'Delete' },
deleteMessage: { id: 'confirmations.delete.message', defaultMessage: 'Are you sure you want to delete this status?' },
blockConfirm: { id: 'confirmations.block.confirm', defaultMessage: 'Block' },
revealAll: { id: 'status.show_more_all', defaultMessage: 'Show more for all' },
hideAll: { id: 'status.show_less_all', defaultMessage: 'Show less for all' },
});
const makeMapStateToProps = () => {
@@ -163,6 +172,25 @@ export default class Status extends ImmutablePureComponent {
}
}
handleToggleHidden = (status) => {
if (status.get('hidden')) {
this.props.dispatch(revealStatus(status.get('id')));
} else {
this.props.dispatch(hideStatus(status.get('id')));
}
}
handleToggleAll = () => {
const { status, ancestorsIds, descendantsIds } = this.props;
const statusIds = [status.get('id')].concat(ancestorsIds.toJS(), descendantsIds.toJS());
if (status.get('hidden')) {
this.props.dispatch(revealStatus(statusIds));
} else {
this.props.dispatch(hideStatus(statusIds));
}
}
handleBlockClick = (account) => {
const { dispatch, intl } = this.props;
@@ -293,7 +321,7 @@ export default class Status extends ImmutablePureComponent {
render () {
let ancestors, descendants;
const { status, ancestorsIds, descendantsIds } = this.props;
const { status, ancestorsIds, descendantsIds, intl } = this.props;
const { fullscreen } = this.state;
if (status === null) {
@@ -325,7 +353,12 @@ export default class Status extends ImmutablePureComponent {
return (
<Column>
<ColumnBackButton />
<ColumnHeader
showBackButton
extraButton={(
<button className='column-header__button' title={intl.formatMessage(status.get('hidden') ? messages.revealAll : messages.hideAll)} aria-label={intl.formatMessage(status.get('hidden') ? messages.revealAll : messages.hideAll)} onClick={this.handleToggleAll} aria-pressed={status.get('hidden') ? 'false' : 'true'}><i className={`fa fa-${status.get('hidden') ? 'eye-slash' : 'eye'}`} /></button>
)}
/>
<ScrollContainer scrollKey='thread'>
<div className={classNames('scrollable', 'detailed-status__wrapper', { fullscreen })} ref={this.setRef}>
@@ -337,6 +370,7 @@ export default class Status extends ImmutablePureComponent {
status={status}
onOpenVideo={this.handleOpenVideo}
onOpenMedia={this.handleOpenMedia}
onToggleHidden={this.handleToggleHidden}
/>
<ActionBar

View File

@@ -4,7 +4,7 @@ import PropTypes from 'prop-types';
const emptyComponent = () => null;
const noop = () => { };
class Bundle extends React.Component {
class Bundle extends React.PureComponent {
static propTypes = {
fetchComponent: PropTypes.func.isRequired,

View File

@@ -13,7 +13,7 @@ const messages = defineMessages({
retry: { id: 'bundle_column_error.retry', defaultMessage: 'Try again' },
});
class BundleColumnError extends React.Component {
class BundleColumnError extends React.PureComponent {
static propTypes = {
onRetry: PropTypes.func.isRequired,

View File

@@ -10,7 +10,7 @@ const messages = defineMessages({
close: { id: 'bundle_modal_error.close', defaultMessage: 'Close' },
});
class BundleModalError extends React.Component {
class BundleModalError extends React.PureComponent {
static propTypes = {
onRetry: PropTypes.func.isRequired,

View File

@@ -28,6 +28,8 @@ const componentMap = {
'LIST': ListTimeline,
};
const shouldHideFAB = path => path.match(/^\/statuses\//);
@component => injectIntl(component, { withRef: true })
export default class ColumnsArea extends ImmutablePureComponent {
@@ -153,7 +155,7 @@ export default class ColumnsArea extends ImmutablePureComponent {
this.pendingIndex = null;
if (singleColumn) {
const floatingActionButton = this.context.router.history.location.pathname === '/statuses/new' ? null : <Link key='floating-action-button' to='/statuses/new' className='floating-action-button'><i className='fa fa-pencil' /></Link>;
const floatingActionButton = shouldHideFAB(this.context.router.history.location.pathname) ? null : <Link key='floating-action-button' to='/statuses/new' className='floating-action-button'><i className='fa fa-pencil' /></Link>;
return columnIndex !== -1 ? [
<ReactSwipeableViews key='content' index={columnIndex} onChangeIndex={this.handleSwipe} onTransitionEnd={this.handleAnimationEnd} animateTransitions={shouldAnimate} springConfig={{ duration: '400ms', delay: '0s', easeFunction: 'ease' }} style={{ height: '100%' }}>

View File

@@ -103,8 +103,8 @@ export default class FocalPointModal extends ImmutablePureComponent {
const height = media.getIn(['meta', 'original', 'height']) || null;
return (
<div className='modal-root__modal media-modal'>
<div className={classNames('media-modal__content focal-point', { dragging })} ref={this.setRef}>
<div className='modal-root__modal video-modal focal-point-modal'>
<div className={classNames('focal-point', { dragging })} ref={this.setRef}>
<ImageLoader
previewSrc={media.get('preview_url')}
src={media.get('url')}

View File

@@ -1,6 +1,7 @@
import React from 'react';
import PropTypes from 'prop-types';
import classNames from 'classnames';
import ZoomableImage from './zoomable_image';
export default class ImageLoader extends React.PureComponent {
@@ -10,6 +11,7 @@ export default class ImageLoader extends React.PureComponent {
previewSrc: PropTypes.string,
width: PropTypes.number,
height: PropTypes.number,
onClick: PropTypes.func,
}
static defaultProps = {
@@ -24,6 +26,7 @@ export default class ImageLoader extends React.PureComponent {
}
removers = [];
canvas = null;
get canvasContext() {
if (!this.canvas) {
@@ -43,6 +46,10 @@ export default class ImageLoader extends React.PureComponent {
}
}
componentWillUnmount () {
this.removeEventListeners();
}
loadImage (props) {
this.removeEventListeners();
this.setState({ loading: true, error: false });
@@ -118,7 +125,7 @@ export default class ImageLoader extends React.PureComponent {
}
render () {
const { alt, src, width, height } = this.props;
const { alt, src, width, height, onClick } = this.props;
const { loading } = this.state;
const className = classNames('image-loader', {
@@ -128,22 +135,19 @@ export default class ImageLoader extends React.PureComponent {
return (
<div className={className}>
<canvas
className='image-loader__preview-canvas'
width={width}
height={height}
ref={this.setCanvasRef}
style={{ opacity: loading ? 1 : 0 }}
/>
{!loading && (
<img
alt={alt}
className='image-loader__img'
src={src}
{loading ? (
<canvas
className='image-loader__preview-canvas'
ref={this.setCanvasRef}
width={width}
height={height}
/>
) : (
<ZoomableImage
alt={alt}
src={src}
onClick={onClick}
/>
)}
</div>
);

View File

@@ -3,6 +3,7 @@ import ReactSwipeableViews from 'react-swipeable-views';
import ImmutablePropTypes from 'react-immutable-proptypes';
import PropTypes from 'prop-types';
import ExtendedVideoPlayer from '../../../components/extended_video_player';
import classNames from 'classnames';
import { defineMessages, injectIntl } from 'react-intl';
import IconButton from '../../../components/icon_button';
import ImmutablePureComponent from 'react-immutable-pure-component';
@@ -26,6 +27,7 @@ export default class MediaModal extends ImmutablePureComponent {
state = {
index: null,
navigationHidden: false,
};
handleSwipe = (index) => {
@@ -68,14 +70,21 @@ export default class MediaModal extends ImmutablePureComponent {
return this.state.index !== null ? this.state.index : this.props.index;
}
toggleNavigation = () => {
this.setState(prevState => ({
navigationHidden: !prevState.navigationHidden,
}));
};
render () {
const { media, intl, onClose } = this.props;
const { navigationHidden } = this.state;
const index = this.getIndex();
let pagination = [];
const leftNav = media.size > 1 && <button tabIndex='0' className='modal-container__nav modal-container__nav--left' onClick={this.handlePrevClick} aria-label={intl.formatMessage(messages.previous)}><i className='fa fa-fw fa-chevron-left' /></button>;
const rightNav = media.size > 1 && <button tabIndex='0' className='modal-container__nav modal-container__nav--right' onClick={this.handleNextClick} aria-label={intl.formatMessage(messages.next)}><i className='fa fa-fw fa-chevron-right' /></button>;
const leftNav = media.size > 1 && <button tabIndex='0' className='media-modal__nav media-modal__nav--left' onClick={this.handlePrevClick} aria-label={intl.formatMessage(messages.previous)}><i className='fa fa-fw fa-chevron-left' /></button>;
const rightNav = media.size > 1 && <button tabIndex='0' className='media-modal__nav media-modal__nav--right' onClick={this.handleNextClick} aria-label={intl.formatMessage(messages.next)}><i className='fa fa-fw fa-chevron-right' /></button>;
if (media.size > 1) {
pagination = media.map((item, i) => {
@@ -92,33 +101,77 @@ export default class MediaModal extends ImmutablePureComponent {
const height = image.getIn(['meta', 'original', 'height']) || null;
if (image.get('type') === 'image') {
return <ImageLoader previewSrc={image.get('preview_url')} src={image.get('url')} width={width} height={height} alt={image.get('description')} key={image.get('url')} />;
return (
<ImageLoader
previewSrc={image.get('preview_url')}
src={image.get('url')}
width={width}
height={height}
alt={image.get('description')}
key={image.get('url')}
onClick={this.toggleNavigation}
/>
);
} else if (image.get('type') === 'gifv') {
return <ExtendedVideoPlayer src={image.get('url')} muted controls={false} width={width} height={height} key={image.get('preview_url')} alt={image.get('description')} />;
return (
<ExtendedVideoPlayer
src={image.get('url')}
muted
controls={false}
width={width}
height={height}
key={image.get('preview_url')}
alt={image.get('description')}
onClick={this.toggleNavigation}
/>
);
}
return null;
}).toArray();
// you can't use 100vh, because the viewport height is taller
// than the visible part of the document in some mobile
// browsers when it's address bar is visible.
// https://developers.google.com/web/updates/2016/12/url-bar-resizing
const swipeableViewsStyle = {
width: '100%',
height: '100%',
};
const containerStyle = {
alignItems: 'center', // center vertically
};
const navigationClassName = classNames('media-modal__navigation', {
'media-modal__navigation--hidden': navigationHidden,
});
return (
<div className='modal-root__modal media-modal'>
{leftNav}
<div className='media-modal__content'>
<IconButton className='media-modal__close' title={intl.formatMessage(messages.close)} icon='times' onClick={onClose} size={16} />
<ReactSwipeableViews containerStyle={containerStyle} onChangeIndex={this.handleSwipe} index={index}>
<div
className='media-modal__closer'
role='presentation'
onClick={onClose}
>
<ReactSwipeableViews
style={swipeableViewsStyle}
containerStyle={containerStyle}
onChangeIndex={this.handleSwipe}
onSwitching={this.handleSwitching}
index={index}
>
{content}
</ReactSwipeableViews>
</div>
<ul className='media-modal__pagination'>
{pagination}
</ul>
{rightNav}
<div className={navigationClassName}>
<IconButton className='media-modal__close' title={intl.formatMessage(messages.close)} icon='times' onClick={onClose} size={40} />
{leftNav}
{rightNav}
<ul className='media-modal__pagination'>
{pagination}
</ul>
</div>
</div>
);
}

View File

@@ -1,6 +1,6 @@
import React from 'react';
import PropTypes from 'prop-types';
import { NavLink } from 'react-router-dom';
import { NavLink, withRouter } from 'react-router-dom';
import { FormattedMessage, injectIntl } from 'react-intl';
import { debounce } from 'lodash';
import { isUserTouching } from '../../../is_mobile';
@@ -24,14 +24,12 @@ export function getLink (index) {
}
@injectIntl
export default class TabsBar extends React.Component {
static contextTypes = {
router: PropTypes.object.isRequired,
}
@withRouter
export default class TabsBar extends React.PureComponent {
static propTypes = {
intl: PropTypes.object.isRequired,
history: PropTypes.object.isRequired,
}
setRef = ref => {
@@ -59,7 +57,7 @@ export default class TabsBar extends React.Component {
const listener = debounce(() => {
nextTab.removeEventListener('transitionend', listener);
this.context.router.history.push(to);
this.props.history.push(to);
}, 50);
nextTab.addEventListener('transitionend', listener);

View File

@@ -16,7 +16,7 @@ export default class VideoModal extends ImmutablePureComponent {
const { media, time, onClose } = this.props;
return (
<div className='modal-root__modal media-modal'>
<div className='modal-root__modal video-modal'>
<div>
<Video
preview={media.get('preview_url')}

View File

@@ -0,0 +1,151 @@
import React from 'react';
import PropTypes from 'prop-types';
const MIN_SCALE = 1;
const MAX_SCALE = 4;
const getMidpoint = (p1, p2) => ({
x: (p1.clientX + p2.clientX) / 2,
y: (p1.clientY + p2.clientY) / 2,
});
const getDistance = (p1, p2) =>
Math.sqrt(Math.pow(p1.clientX - p2.clientX, 2) + Math.pow(p1.clientY - p2.clientY, 2));
const clamp = (min, max, value) => Math.min(max, Math.max(min, value));
export default class ZoomableImage extends React.PureComponent {
static propTypes = {
alt: PropTypes.string,
src: PropTypes.string.isRequired,
width: PropTypes.number,
height: PropTypes.number,
onClick: PropTypes.func,
}
static defaultProps = {
alt: '',
width: null,
height: null,
};
state = {
scale: MIN_SCALE,
}
removers = [];
container = null;
image = null;
lastTouchEndTime = 0;
lastDistance = 0;
componentDidMount () {
let handler = this.handleTouchStart;
this.container.addEventListener('touchstart', handler);
this.removers.push(() => this.container.removeEventListener('touchstart', handler));
handler = this.handleTouchMove;
// on Chrome 56+, touch event listeners will default to passive
// https://www.chromestatus.com/features/5093566007214080
this.container.addEventListener('touchmove', handler, { passive: false });
this.removers.push(() => this.container.removeEventListener('touchend', handler));
}
componentWillUnmount () {
this.removeEventListeners();
}
removeEventListeners () {
this.removers.forEach(listeners => listeners());
this.removers = [];
}
handleTouchStart = e => {
if (e.touches.length !== 2) return;
this.lastDistance = getDistance(...e.touches);
}
handleTouchMove = e => {
const { scrollTop, scrollHeight, clientHeight } = this.container;
if (e.touches.length === 1 && scrollTop !== scrollHeight - clientHeight) {
// prevent propagating event to MediaModal
e.stopPropagation();
return;
}
if (e.touches.length !== 2) return;
e.preventDefault();
e.stopPropagation();
const distance = getDistance(...e.touches);
const midpoint = getMidpoint(...e.touches);
const scale = clamp(MIN_SCALE, MAX_SCALE, this.state.scale * distance / this.lastDistance);
this.zoom(scale, midpoint);
this.lastMidpoint = midpoint;
this.lastDistance = distance;
}
zoom(nextScale, midpoint) {
const { scale } = this.state;
const { scrollLeft, scrollTop } = this.container;
// math memo:
// x = (scrollLeft + midpoint.x) / scrollWidth
// x' = (nextScrollLeft + midpoint.x) / nextScrollWidth
// scrollWidth = clientWidth * scale
// scrollWidth' = clientWidth * nextScale
// Solve x = x' for nextScrollLeft
const nextScrollLeft = (scrollLeft + midpoint.x) * nextScale / scale - midpoint.x;
const nextScrollTop = (scrollTop + midpoint.y) * nextScale / scale - midpoint.y;
this.setState({ scale: nextScale }, () => {
this.container.scrollLeft = nextScrollLeft;
this.container.scrollTop = nextScrollTop;
});
}
handleClick = e => {
// don't propagate event to MediaModal
e.stopPropagation();
const handler = this.props.onClick;
if (handler) handler();
}
setContainerRef = c => {
this.container = c;
}
setImageRef = c => {
this.image = c;
}
render () {
const { alt, src } = this.props;
const { scale } = this.state;
const overflow = scale === 1 ? 'hidden' : 'scroll';
return (
<div
className='zoomable-image'
ref={this.setContainerRef}
style={{ overflow }}
>
<img
role='presentation'
ref={this.setImageRef}
alt={alt}
src={src}
style={{
transform: `scale(${scale})`,
transformOrigin: '0 0',
}}
onClick={this.handleClick}
/>
</div>
);
}
}

View File

@@ -56,10 +56,7 @@ const makeMapStateToProps = () => {
const mapDispatchToProps = (dispatch, { timelineId, loadMore }) => ({
onScrollToBottom: debounce(() => {
dispatch(scrollTopTimeline(timelineId, false));
loadMore();
}, 300, { leading: true }),
onLoadMore: debounce(loadMore, 300, { leading: true }),
onScrollToTop: debounce(() => {
dispatch(scrollTopTimeline(timelineId, true));

View File

@@ -1,3 +1,4 @@
import classNames from 'classnames';
import React from 'react';
import NotificationsContainer from './containers/notifications_container';
import PropTypes from 'prop-types';
@@ -55,6 +56,7 @@ const messages = defineMessages({
const mapStateToProps = state => ({
isComposing: state.getIn(['compose', 'is_composing']),
hasComposingText: state.getIn(['compose', 'text']) !== '',
dropdownMenuIsOpen: state.getIn(['dropdown_menu', 'openId']) !== null,
});
const keyMap = {
@@ -84,10 +86,93 @@ const keyMap = {
goToMuted: 'g m',
};
class SwitchingColumnsArea extends React.PureComponent {
static propTypes = {
children: PropTypes.node,
location: PropTypes.object,
onLayoutChange: PropTypes.func.isRequired,
};
state = {
mobile: isMobile(window.innerWidth),
};
componentWillMount () {
window.addEventListener('resize', this.handleResize, { passive: true });
}
componentDidUpdate (prevProps) {
if (![this.props.location.pathname, '/'].includes(prevProps.location.pathname)) {
this.node.handleChildrenContentChange();
}
}
componentWillUnmount () {
window.removeEventListener('resize', this.handleResize);
}
handleResize = debounce(() => {
// The cached heights are no longer accurate, invalidate
this.props.onLayoutChange();
this.setState({ mobile: isMobile(window.innerWidth) });
}, 500, {
trailing: true,
});
setRef = c => {
this.node = c.getWrappedInstance().getWrappedInstance();
}
render () {
const { children } = this.props;
const { mobile } = this.state;
return (
<ColumnsAreaContainer ref={this.setRef} singleColumn={mobile}>
<WrappedSwitch>
<Redirect from='/' to='/getting-started' exact />
<WrappedRoute path='/getting-started' component={GettingStarted} content={children} />
<WrappedRoute path='/keyboard-shortcuts' component={KeyboardShortcuts} content={children} />
<WrappedRoute path='/timelines/home' component={HomeTimeline} content={children} />
<WrappedRoute path='/timelines/public' exact component={PublicTimeline} content={children} />
<WrappedRoute path='/timelines/public/local' component={CommunityTimeline} content={children} />
<WrappedRoute path='/timelines/tag/:id' component={HashtagTimeline} content={children} />
<WrappedRoute path='/timelines/list/:id' component={ListTimeline} content={children} />
<WrappedRoute path='/notifications' component={Notifications} content={children} />
<WrappedRoute path='/favourites' component={FavouritedStatuses} content={children} />
<WrappedRoute path='/pinned' component={PinnedStatuses} content={children} />
<WrappedRoute path='/statuses/new' component={Compose} content={children} />
<WrappedRoute path='/statuses/:statusId' exact component={Status} content={children} />
<WrappedRoute path='/statuses/:statusId/reblogs' component={Reblogs} content={children} />
<WrappedRoute path='/statuses/:statusId/favourites' component={Favourites} content={children} />
<WrappedRoute path='/accounts/:accountId' exact component={AccountTimeline} content={children} />
<WrappedRoute path='/accounts/:accountId/with_replies' component={AccountTimeline} content={children} componentParams={{ withReplies: true }} />
<WrappedRoute path='/accounts/:accountId/followers' component={Followers} content={children} />
<WrappedRoute path='/accounts/:accountId/following' component={Following} content={children} />
<WrappedRoute path='/accounts/:accountId/media' component={AccountGallery} content={children} />
<WrappedRoute path='/follow_requests' component={FollowRequests} content={children} />
<WrappedRoute path='/blocks' component={Blocks} content={children} />
<WrappedRoute path='/mutes' component={Mutes} content={children} />
<WrappedRoute path='/lists' component={Lists} content={children} />
<WrappedRoute component={GenericNotFound} content={children} />
</WrappedSwitch>
</ColumnsAreaContainer>
);
}
}
@connect(mapStateToProps)
@injectIntl
@withRouter
export default class UI extends React.Component {
export default class UI extends React.PureComponent {
static contextTypes = {
router: PropTypes.object.isRequired,
@@ -100,10 +185,10 @@ export default class UI extends React.Component {
hasComposingText: PropTypes.bool,
location: PropTypes.object,
intl: PropTypes.object.isRequired,
dropdownMenuIsOpen: PropTypes.bool,
};
state = {
width: window.innerWidth,
draggingOver: false,
};
@@ -118,14 +203,10 @@ export default class UI extends React.Component {
}
}
handleResize = debounce(() => {
handleLayoutChange = () => {
// The cached heights are no longer accurate, invalidate
this.props.dispatch(clearHeight());
this.setState({ width: window.innerWidth });
}, 500, {
trailing: true,
});
}
handleDragEnter = (e) => {
e.preventDefault();
@@ -193,7 +274,6 @@ export default class UI extends React.Component {
componentWillMount () {
window.addEventListener('beforeunload', this.handleBeforeUnload, false);
window.addEventListener('resize', this.handleResize, { passive: true });
document.addEventListener('dragenter', this.handleDragEnter, false);
document.addEventListener('dragover', this.handleDragOver, false);
document.addEventListener('drop', this.handleDrop, false);
@@ -214,28 +294,8 @@ export default class UI extends React.Component {
};
}
shouldComponentUpdate (nextProps) {
if (nextProps.isComposing !== this.props.isComposing) {
// Avoid expensive update just to toggle a class
this.node.classList.toggle('is-composing', nextProps.isComposing);
return false;
}
// Why isn't this working?!?
// return super.shouldComponentUpdate(nextProps, nextState);
return true;
}
componentDidUpdate (prevProps) {
if (![this.props.location.pathname, '/'].includes(prevProps.location.pathname)) {
this.columnsAreaNode.handleChildrenContentChange();
}
}
componentWillUnmount () {
window.removeEventListener('beforeunload', this.handleBeforeUnload);
window.removeEventListener('resize', this.handleResize);
document.removeEventListener('dragenter', this.handleDragEnter);
document.removeEventListener('dragover', this.handleDragOver);
document.removeEventListener('drop', this.handleDrop);
@@ -247,10 +307,6 @@ export default class UI extends React.Component {
this.node = c;
}
setColumnsAreaRef = c => {
this.columnsAreaNode = c.getWrappedInstance().getWrappedInstance();
}
handleHotkeyNew = e => {
e.preventDefault();
@@ -350,8 +406,8 @@ export default class UI extends React.Component {
}
render () {
const { width, draggingOver } = this.state;
const { children } = this.props;
const { draggingOver } = this.state;
const { children, isComposing, location, dropdownMenuIsOpen } = this.props;
const handlers = {
help: this.handleHotkeyToggleHelp,
@@ -374,43 +430,12 @@ export default class UI extends React.Component {
return (
<HotKeys keyMap={keyMap} handlers={handlers} ref={this.setHotkeysRef}>
<div className='ui' ref={this.setRef}>
<div className={classNames('ui', { 'is-composing': isComposing })} ref={this.setRef} style={{ pointerEvents: dropdownMenuIsOpen ? 'none' : null }}>
<TabsBar />
<ColumnsAreaContainer ref={this.setColumnsAreaRef} singleColumn={isMobile(width)}>
<WrappedSwitch>
<Redirect from='/' to='/getting-started' exact />
<WrappedRoute path='/getting-started' component={GettingStarted} content={children} />
<WrappedRoute path='/keyboard-shortcuts' component={KeyboardShortcuts} content={children} />
<WrappedRoute path='/timelines/home' component={HomeTimeline} content={children} />
<WrappedRoute path='/timelines/public' exact component={PublicTimeline} content={children} />
<WrappedRoute path='/timelines/public/local' component={CommunityTimeline} content={children} />
<WrappedRoute path='/timelines/tag/:id' component={HashtagTimeline} content={children} />
<WrappedRoute path='/timelines/list/:id' component={ListTimeline} content={children} />
<WrappedRoute path='/notifications' component={Notifications} content={children} />
<WrappedRoute path='/favourites' component={FavouritedStatuses} content={children} />
<WrappedRoute path='/pinned' component={PinnedStatuses} content={children} />
<WrappedRoute path='/statuses/new' component={Compose} content={children} />
<WrappedRoute path='/statuses/:statusId' exact component={Status} content={children} />
<WrappedRoute path='/statuses/:statusId/reblogs' component={Reblogs} content={children} />
<WrappedRoute path='/statuses/:statusId/favourites' component={Favourites} content={children} />
<WrappedRoute path='/accounts/:accountId' exact component={AccountTimeline} content={children} />
<WrappedRoute path='/accounts/:accountId/with_replies' component={AccountTimeline} content={children} componentParams={{ withReplies: true }} />
<WrappedRoute path='/accounts/:accountId/followers' component={Followers} content={children} />
<WrappedRoute path='/accounts/:accountId/following' component={Following} content={children} />
<WrappedRoute path='/accounts/:accountId/media' component={AccountGallery} content={children} />
<WrappedRoute path='/follow_requests' component={FollowRequests} content={children} />
<WrappedRoute path='/blocks' component={Blocks} content={children} />
<WrappedRoute path='/mutes' component={Mutes} content={children} />
<WrappedRoute path='/lists' component={Lists} content={children} />
<WrappedRoute component={GenericNotFound} content={children} />
</WrappedSwitch>
</ColumnsAreaContainer>
<SwitchingColumnsArea location={location} onLayoutChange={this.handleLayoutChange}>
{children}
</SwitchingColumnsArea>
<NotificationsContainer />
<LoadingBarContainer className='loading-bar' />

View File

@@ -14,6 +14,7 @@ function loadPolyfills() {
const needsBasePolyfills = !(
window.Intl &&
Object.assign &&
Object.values &&
Number.isNaN &&
window.Symbol &&
Array.prototype.includes

View File

@@ -1,7 +1,9 @@
{
"account.block": "حظر @{name}",
"account.block_domain": "إخفاء كل شيئ قادم من إسم النطاق {domain}",
"account.blocked": "محظور",
"account.disclaimer_full": "قد لا تعكس المعلومات أدناه الملف الشخصي الكامل للمستخدم.",
"account.domain_blocked": "النطاق مخفي",
"account.edit_profile": "تعديل الملف الشخصي",
"account.follow": "تابِع",
"account.followers": "المتابعون",
@@ -13,8 +15,9 @@
"account.moved_to": "{name} إنتقل إلى :",
"account.mute": "أكتم @{name}",
"account.mute_notifications": "كتم إخطارات @{name}",
"account.muted": "مكتوم",
"account.posts": "التبويقات",
"account.posts_with_replies": "Toots with replies",
"account.posts_with_replies": "تبويقات تحتوي على رُدود",
"account.report": "أبلغ عن @{name}",
"account.requested": "في انتظار الموافقة",
"account.share": "مشاركة @{name}'s profile",
@@ -57,8 +60,10 @@
"compose_form.placeholder": "فيمَ تفكّر؟",
"compose_form.publish": "بوّق",
"compose_form.publish_loud": "{publish}!",
"compose_form.sensitive": "ضع علامة على الوسيط باعتباره حسّاس",
"compose_form.spoiler": "أخفِ النص واعرض تحذيرا",
"compose_form.sensitive.marked": "لقد تم تحديد هذه الصورة كحساسة",
"compose_form.sensitive.unmarked": "Media is not marked as sensitive",
"compose_form.spoiler.marked": "إنّ النص مخفي وراء تحذير",
"compose_form.spoiler.unmarked": "النص غير مخفي",
"compose_form.spoiler_placeholder": "تنبيه عن المحتوى",
"confirmation_modal.cancel": "إلغاء",
"confirmations.block.confirm": "حجب",
@@ -208,21 +213,22 @@
"relative_time.minutes": "{number}m",
"relative_time.seconds": "{number}s",
"reply_indicator.cancel": "إلغاء",
"report.forward": "Forward to {target}",
"report.forward_hint": "The account is from another server. Send an anonymized copy of the report there as well?",
"report.forward": "التحويل إلى {target}",
"report.forward_hint": "هذا الحساب ينتمي إلى خادوم آخَر. هل تودّ إرسال نسخة مجهولة مِن التقرير إلى هنالك أيضًا ؟",
"report.hint": "The report will be sent to your instance moderators. You can provide an explanation of why you are reporting this account below:",
"report.placeholder": "تعليقات إضافية",
"report.submit": "إرسال",
"report.target": "إبلاغ",
"search.placeholder": "ابحث",
"search_popout.search_format": "نمط البحث المتقدم",
"search_popout.tips.full_text": "Simple text returns statuses you have written, favourited, boosted, or have been mentioned in, as well as matching usernames, display names, and hashtags.",
"search_popout.tips.hashtag": "وسم",
"search_popout.tips.status": "حالة",
"search_popout.tips.text": "جملة قصيرة تُمكّنُك من عرض أسماء و حسابات و كلمات رمزية",
"search_popout.tips.user": "مستخدِم",
"search_results.accounts": "People",
"search_results.hashtags": "Hashtags",
"search_results.statuses": "Toots",
"search_results.accounts": "أشخاص",
"search_results.hashtags": "الوُسوم",
"search_results.statuses": "التبويقات",
"search_results.total": "{count, number} {count, plural, one {result} و {results}}",
"standalone.public_title": "نظرة على ...",
"status.block": "Block @{name}",
@@ -238,6 +244,7 @@
"status.mute_conversation": "كتم المحادثة",
"status.open": "وسع هذه المشاركة",
"status.pin": "تدبيس على الملف الشخصي",
"status.pinned": "تبويق مثبَّت",
"status.reblog": "رَقِّي",
"status.reblogged_by": "{name} رقى",
"status.reply": "ردّ",
@@ -247,10 +254,11 @@
"status.sensitive_warning": "محتوى حساس",
"status.share": "مشاركة",
"status.show_less": "إعرض أقلّ",
"status.show_less_all": "طي الكل",
"status.show_more": "أظهر المزيد",
"status.show_more_all": "توسيع الكل",
"status.unmute_conversation": "فك الكتم عن المحادثة",
"status.unpin": "فك التدبيس من الملف الشخصي",
"tabs_bar.compose": "تحرير",
"tabs_bar.federated_timeline": "الموحَّد",
"tabs_bar.home": "الرئيسية",
"tabs_bar.local_timeline": "المحلي",
@@ -259,7 +267,7 @@
"upload_area.title": "إسحب ثم أفلت للرفع",
"upload_button.label": "إضافة وسائط",
"upload_form.description": "وصف للمعاقين بصريا",
"upload_form.focus": "Crop",
"upload_form.focus": "قص",
"upload_form.undo": "إلغاء",
"upload_progress.label": "يرفع...",
"video.close": "إغلاق الفيديو",

View File

@@ -1,7 +1,9 @@
{
"account.block": "Блокирай",
"account.block_domain": "Hide everything from {domain}",
"account.blocked": "Blocked",
"account.disclaimer_full": "Information below may reflect the user's profile incompletely.",
"account.domain_blocked": "Domain hidden",
"account.edit_profile": "Редактирай профила си",
"account.follow": "Последвай",
"account.followers": "Последователи",
@@ -13,6 +15,7 @@
"account.moved_to": "{name} has moved to:",
"account.mute": "Mute @{name}",
"account.mute_notifications": "Mute notifications from @{name}",
"account.muted": "Muted",
"account.posts": "Публикации",
"account.posts_with_replies": "Toots with replies",
"account.report": "Report @{name}",
@@ -57,8 +60,10 @@
"compose_form.placeholder": "Какво си мислиш?",
"compose_form.publish": "Раздумай",
"compose_form.publish_loud": "{publish}!",
"compose_form.sensitive": "Отбележи съдържанието като деликатно",
"compose_form.spoiler": "Скрий текста зад предупреждение",
"compose_form.sensitive.marked": "Media is marked as sensitive",
"compose_form.sensitive.unmarked": "Media is not marked as sensitive",
"compose_form.spoiler.marked": "Text is hidden behind warning",
"compose_form.spoiler.unmarked": "Text is not hidden",
"compose_form.spoiler_placeholder": "Content warning",
"confirmation_modal.cancel": "Cancel",
"confirmations.block.confirm": "Block",
@@ -216,6 +221,7 @@
"report.target": "Reporting",
"search.placeholder": "Търсене",
"search_popout.search_format": "Advanced search format",
"search_popout.tips.full_text": "Simple text returns statuses you have written, favourited, boosted, or have been mentioned in, as well as matching usernames, display names, and hashtags.",
"search_popout.tips.hashtag": "hashtag",
"search_popout.tips.status": "status",
"search_popout.tips.text": "Simple text returns matching display names, usernames and hashtags",
@@ -238,6 +244,7 @@
"status.mute_conversation": "Mute conversation",
"status.open": "Expand this status",
"status.pin": "Pin on profile",
"status.pinned": "Pinned toot",
"status.reblog": "Споделяне",
"status.reblogged_by": "{name} сподели",
"status.reply": "Отговор",
@@ -247,10 +254,11 @@
"status.sensitive_warning": "Деликатно съдържание",
"status.share": "Share",
"status.show_less": "Show less",
"status.show_less_all": "Show less for all",
"status.show_more": "Show more",
"status.show_more_all": "Show more for all",
"status.unmute_conversation": "Unmute conversation",
"status.unpin": "Unpin from profile",
"tabs_bar.compose": "Съставяне",
"tabs_bar.federated_timeline": "Federated",
"tabs_bar.home": "Начало",
"tabs_bar.local_timeline": "Local",

View File

@@ -1,7 +1,9 @@
{
"account.block": "Bloca @{name}",
"account.block_domain": "Amaga-ho tot de {domain}",
"account.blocked": "Blocked",
"account.disclaimer_full": "La informació següent pot reflectir incompleta el perfil de l'usuari.",
"account.domain_blocked": "Domain hidden",
"account.edit_profile": "Edita el perfil",
"account.follow": "Segueix",
"account.followers": "Seguidors",
@@ -13,8 +15,9 @@
"account.moved_to": "{name} s'ha mogut a:",
"account.mute": "Silencia @{name}",
"account.mute_notifications": "Notificacions desactivades de @{name}",
"account.muted": "Muted",
"account.posts": "Toots",
"account.posts_with_replies": "Toots with replies",
"account.posts_with_replies": "Toots amb respostes",
"account.report": "Informe @{name}",
"account.requested": "Esperant aprovació. Clic per a cancel·lar la petició de seguiment",
"account.share": "Comparteix el perfil de @{name}",
@@ -57,8 +60,10 @@
"compose_form.placeholder": "En què estàs pensant?",
"compose_form.publish": "Toot",
"compose_form.publish_loud": "{publish}!",
"compose_form.sensitive": "Marca el contingut multimèdia com a sensible",
"compose_form.spoiler": "Amaga el text darrera darrere un avís",
"compose_form.sensitive.marked": "Media is marked as sensitive",
"compose_form.sensitive.unmarked": "Media is not marked as sensitive",
"compose_form.spoiler.marked": "Text is hidden behind warning",
"compose_form.spoiler.unmarked": "Text is not hidden",
"compose_form.spoiler_placeholder": "Escriu l'avís aquí",
"confirmation_modal.cancel": "Cancel·la",
"confirmations.block.confirm": "Bloca",
@@ -208,20 +213,21 @@
"relative_time.minutes": "fa {number} minuts",
"relative_time.seconds": "fa {number} segons",
"reply_indicator.cancel": "Cancel·lar",
"report.forward": "Forward to {target}",
"report.forward_hint": "The account is from another server. Send an anonymized copy of the report there as well?",
"report.hint": "The report will be sent to your instance moderators. You can provide an explanation of why you are reporting this account below:",
"report.forward": "Reenvia a {target}",
"report.forward_hint": "Aquest compte és d'un altre servidor. Enviar-hi també una copia anònima del informe?",
"report.hint": "El informe s'enviarà als moderadors de la teva instància. Pots explicar perquè vols informar d'aquest compte aquí:",
"report.placeholder": "Comentaris addicionals",
"report.submit": "Enviar",
"report.target": "Informes",
"search.placeholder": "Cercar",
"search_popout.search_format": "Format de cerca avançada",
"search_popout.tips.full_text": "Simple text returns statuses you have written, favourited, boosted, or have been mentioned in, as well as matching usernames, display names, and hashtags.",
"search_popout.tips.hashtag": "etiqueta",
"search_popout.tips.status": "status",
"search_popout.tips.text": "El text simple retorna coincidències amb els noms de visualització, els noms d'usuari i els hashtags",
"search_popout.tips.user": "usuari",
"search_results.accounts": "People",
"search_results.hashtags": "Hashtags",
"search_results.accounts": "Gent",
"search_results.hashtags": "Etiquetes",
"search_results.statuses": "Toots",
"search_results.total": "{count, number} {count, plural, un {result} altres {results}}",
"standalone.public_title": "Una mirada a l'interior ...",
@@ -238,6 +244,7 @@
"status.mute_conversation": "Silenciar conversació",
"status.open": "Ampliar aquest estat",
"status.pin": "Fixat en el perfil",
"status.pinned": "Pinned toot",
"status.reblog": "Impuls",
"status.reblogged_by": "{name} ha retootejat",
"status.reply": "Respondre",
@@ -247,10 +254,11 @@
"status.sensitive_warning": "Contingut sensible",
"status.share": "Compartir",
"status.show_less": "Mostra menys",
"status.show_less_all": "Show less for all",
"status.show_more": "Mostra més",
"status.show_more_all": "Show more for all",
"status.unmute_conversation": "Activar conversació",
"status.unpin": "Deslliga del perfil",
"tabs_bar.compose": "Compondre",
"tabs_bar.federated_timeline": "Federada",
"tabs_bar.home": "Inici",
"tabs_bar.local_timeline": "Local",
@@ -259,7 +267,7 @@
"upload_area.title": "Arrossega i deixa anar per carregar",
"upload_button.label": "Afegir multimèdia",
"upload_form.description": "Descriure els problemes visuals",
"upload_form.focus": "Crop",
"upload_form.focus": "Retallar",
"upload_form.undo": "Desfer",
"upload_progress.label": "Pujant...",
"video.close": "Tancar el vídeo",

View File

@@ -1,7 +1,9 @@
{
"account.block": "@{name} blocken",
"account.block_domain": "Alles von {domain} verstecken",
"account.blocked": "Blockiert",
"account.disclaimer_full": "Das Profil wird möglicherweise unvollständig wiedergegeben.",
"account.domain_blocked": "Domain versteckt",
"account.edit_profile": "Profil bearbeiten",
"account.follow": "Folgen",
"account.followers": "Folgende",
@@ -13,8 +15,9 @@
"account.moved_to": "{name} ist umgezogen auf:",
"account.mute": "@{name} stummschalten",
"account.mute_notifications": "Benachrichtigungen von @{name} verbergen",
"account.muted": "Stummgeschaltet",
"account.posts": "Beiträge",
"account.posts_with_replies": "Toots with replies",
"account.posts_with_replies": "Beiträge mit Antworten",
"account.report": "@{name} melden",
"account.requested": "Warte auf Erlaubnis. Klicke zum Abbrechen",
"account.share": "Profil von @{name} teilen",
@@ -37,7 +40,7 @@
"column.favourites": "Favoriten",
"column.follow_requests": "Folgeanfragen",
"column.home": "Startseite",
"column.lists": "Lists",
"column.lists": "Listen",
"column.mutes": "Stummgeschaltete Profile",
"column.notifications": "Mitteilungen",
"column.pins": "Angeheftete Beiträge",
@@ -51,14 +54,16 @@
"column_header.unpin": "Lösen",
"column_subheading.navigation": "Navigation",
"column_subheading.settings": "Einstellungen",
"compose_form.hashtag_warning": "This toot won't be listed under any hashtag as it is unlisted. Only public toots can be searched by hashtag.",
"compose_form.hashtag_warning": "Dieser Beitrag wird nicht unter einen dieser Hashtags sichtbar sein, solange er ungelistet ist. Bei einer Suche kann er nicht gefunden werden.",
"compose_form.lock_disclaimer": "Dein Profil ist nicht {locked}. Wer dir folgen will, kann das jederzeit tun und dann auch deine privaten Beiträge sehen.",
"compose_form.lock_disclaimer.lock": "gesperrt",
"compose_form.placeholder": "Worüber möchtest du schreiben?",
"compose_form.publish": "Tröt",
"compose_form.publish_loud": "{publish}!",
"compose_form.sensitive": "Medien als heikel markieren",
"compose_form.spoiler": "Text hinter Warnung verbergen",
"compose_form.sensitive.marked": "Media is marked as sensitive",
"compose_form.sensitive.unmarked": "Media is not marked as sensitive",
"compose_form.spoiler.marked": "Text is hidden behind warning",
"compose_form.spoiler.unmarked": "Text is not hidden",
"compose_form.spoiler_placeholder": "Inhaltswarnung",
"confirmation_modal.cancel": "Abbrechen",
"confirmations.block.confirm": "Blockieren",
@@ -66,7 +71,7 @@
"confirmations.delete.confirm": "Löschen",
"confirmations.delete.message": "Bist du dir sicher, dass du diesen Beitrag löschen möchtest?",
"confirmations.delete_list.confirm": "Delete",
"confirmations.delete_list.message": "Are you sure you want to permanently delete this list?",
"confirmations.delete_list.message": "Bist du dir sicher, dass du diese Liste permanent löschen möchtest?",
"confirmations.domain_block.confirm": "Die ganze Domain verbergen",
"confirmations.domain_block.message": "Bist du dir wirklich sicher, dass du die ganze Domain {domain} verbergen willst? In den meisten Fällen reichen ein paar gezielte Blocks aus.",
"confirmations.mute.confirm": "Stummschalten",
@@ -112,35 +117,35 @@
"keyboard_shortcuts.back": "zurück navigieren",
"keyboard_shortcuts.boost": "boosten",
"keyboard_shortcuts.column": "einen Status in einer der Spalten fokussieren",
"keyboard_shortcuts.compose": "to focus the compose textarea",
"keyboard_shortcuts.description": "Description",
"keyboard_shortcuts.compose": "um das Textfeld zu fokussieren",
"keyboard_shortcuts.description": "Beschreibung",
"keyboard_shortcuts.down": "sich in der Liste hinunter bewegen",
"keyboard_shortcuts.enter": "to open status",
"keyboard_shortcuts.favourite": "favorisieren",
"keyboard_shortcuts.heading": "Keyboard Shortcuts",
"keyboard_shortcuts.enter": "um den Status zu öffnen",
"keyboard_shortcuts.favourite": "um zu favorisieren",
"keyboard_shortcuts.heading": "Tastenkombinationen",
"keyboard_shortcuts.hotkey": "Hotkey",
"keyboard_shortcuts.legend": "diese Übersicht anzeigen",
"keyboard_shortcuts.mention": "Autor_in erwähnen",
"keyboard_shortcuts.reply": "antworten",
"keyboard_shortcuts.search": "die Suche fokussieren",
"keyboard_shortcuts.toot": "einen neuen Toot beginnen",
"keyboard_shortcuts.unfocus": "to un-focus compose textarea/search",
"keyboard_shortcuts.legend": "um diese Übersicht anzuzeigen",
"keyboard_shortcuts.mention": "um Autor_in zu erwähnen",
"keyboard_shortcuts.reply": "um zu antworten",
"keyboard_shortcuts.search": "um die Suche zu fokussieren",
"keyboard_shortcuts.toot": "um einen neuen Toot zu beginnen",
"keyboard_shortcuts.unfocus": "um das Textfeld/die Suche nicht mehr zu fokussieren",
"keyboard_shortcuts.up": "sich in der Liste hinauf bewegen",
"lightbox.close": "Schließen",
"lightbox.next": "Weiter",
"lightbox.previous": "Zurück",
"lists.account.add": "Add to list",
"lists.account.remove": "Remove from list",
"lists.account.add": "Zur Liste hinzufügen",
"lists.account.remove": "Von der Liste entfernen",
"lists.delete": "Delete list",
"lists.edit": "Edit list",
"lists.new.create": "Add list",
"lists.new.title_placeholder": "New list title",
"lists.search": "Search among people you follow",
"lists.subheading": "Your lists",
"lists.edit": "Liste bearbeiten",
"lists.new.create": "Liste hinzufügen",
"lists.new.title_placeholder": "Neuer Titel der Liste",
"lists.search": "Suche nach Leuten denen du folgst",
"lists.subheading": "Deine Listen",
"loading_indicator.label": "Wird geladen …",
"media_gallery.toggle_visible": "Sichtbarkeit umschalten",
"missing_indicator.label": "Nicht gefunden",
"missing_indicator.sublabel": "This resource could not be found",
"missing_indicator.sublabel": "Die Ressource konnte nicht gefunden werden",
"mute_modal.hide_notifications": "Benachrichtigungen von diesem Account verbergen?",
"navigation_bar.blocks": "Blockierte Profile",
"navigation_bar.community_timeline": "Lokale Zeitleiste",
@@ -149,7 +154,7 @@
"navigation_bar.follow_requests": "Folgeanfragen",
"navigation_bar.info": "Über diese Instanz",
"navigation_bar.keyboard_shortcuts": "Tastenkombinationen",
"navigation_bar.lists": "Lists",
"navigation_bar.lists": "Listen",
"navigation_bar.logout": "Abmelden",
"navigation_bar.mutes": "Stummgeschaltete Profile",
"navigation_bar.pins": "Angeheftete Beiträge",
@@ -172,7 +177,7 @@
"notifications.column_settings.sound": "Ton abspielen",
"onboarding.done": "Fertig",
"onboarding.next": "Weiter",
"onboarding.page_five.public_timelines": "Die lokale Zeitleiste zeigt alle Beiträge von Leuten, die auch auf {domain} sind. Das gesamte bekannte Netz zeigt Beiträge von allen, denen von Leuten auf {domain} gefolgt wird. Zusammen sind sie die öffentlichen Zeitleisten. In ihnen kannst du viel Neues entdecken!",
"onboarding.page_five.public_timelines": "Die lokale Zeitleiste zeigt alle Beiträge von Leuten, die auch auf {domain} sind. Das gesamte bekannte Netz zeigt Beiträge von allen, denen von Leuten auf {domain} gefolgt wird. Zusammen sind sie die öffentlichen Zeitleisten, ein guter Weg, um neue Leute zu finden.",
"onboarding.page_four.home": "Die Startseite zeigt dir Beiträge von Leuten, denen du folgst.",
"onboarding.page_four.notifications": "Wenn jemand mit dir interagiert, bekommst du eine Mitteilung.",
"onboarding.page_one.federation": "Mastodon ist ein soziales Netzwerk, das aus unabhängigen Servern besteht. Diese Server nennen wir auch Instanzen.",
@@ -216,6 +221,7 @@
"report.target": "{target} melden",
"search.placeholder": "Suche",
"search_popout.search_format": "Advanced search format",
"search_popout.tips.full_text": "Simple text returns statuses you have written, favourited, boosted, or have been mentioned in, as well as matching usernames, display names, and hashtags.",
"search_popout.tips.hashtag": "hashtag",
"search_popout.tips.status": "status",
"search_popout.tips.text": "Simple text returns matching display names, usernames and hashtags",
@@ -238,6 +244,7 @@
"status.mute_conversation": "Thread stummschalten",
"status.open": "Diesen Beitrag öffnen",
"status.pin": "Im Profil anheften",
"status.pinned": "Pinned toot",
"status.reblog": "Teilen",
"status.reblogged_by": "{name} teilte",
"status.reply": "Antworten",
@@ -247,10 +254,11 @@
"status.sensitive_warning": "Heikle Inhalte",
"status.share": "Teilen",
"status.show_less": "Weniger anzeigen",
"status.show_less_all": "Show less for all",
"status.show_more": "Mehr anzeigen",
"status.show_more_all": "Show more for all",
"status.unmute_conversation": "Stummschaltung von Thread aufheben",
"status.unpin": "Vom Profil lösen",
"tabs_bar.compose": "Schreiben",
"tabs_bar.federated_timeline": "Föderation",
"tabs_bar.home": "Startseite",
"tabs_bar.local_timeline": "Lokal",

View File

@@ -274,6 +274,10 @@
},
{
"descriptors": [
{
"defaultMessage": "Pinned toot",
"id": "status.pinned"
},
{
"defaultMessage": "{name} boosted",
"id": "status.reblogged_by"
@@ -469,9 +473,25 @@
"defaultMessage": "Awaiting approval. Click to cancel follow request",
"id": "account.requested"
},
{
"defaultMessage": "Unblock @{name}",
"id": "account.unblock"
},
{
"defaultMessage": "Follows you",
"id": "account.follows_you"
},
{
"defaultMessage": "Blocked",
"id": "account.blocked"
},
{
"defaultMessage": "Muted",
"id": "account.muted"
},
{
"defaultMessage": "Domain hidden",
"id": "account.domain_blocked"
}
],
"path": "app/javascript/mastodon/features/account/components/header.json"
@@ -683,6 +703,14 @@
"defaultMessage": "Search",
"id": "search.placeholder"
},
{
"defaultMessage": "Simple text returns statuses you have written, favourited, boosted, or have been mentioned in, as well as matching usernames, display names, and hashtags.",
"id": "search_popout.tips.full_text"
},
{
"defaultMessage": "Simple text returns matching display names, usernames and hashtags",
"id": "search_popout.tips.text"
},
{
"defaultMessage": "Advanced search format",
"id": "search_popout.search_format"
@@ -698,10 +726,6 @@
{
"defaultMessage": "status",
"id": "search_popout.tips.status"
},
{
"defaultMessage": "Simple text returns matching display names, usernames and hashtags",
"id": "search_popout.tips.text"
}
],
"path": "app/javascript/mastodon/features/compose/components/search.json"
@@ -744,8 +768,12 @@
{
"descriptors": [
{
"defaultMessage": "Mark media as sensitive",
"id": "compose_form.sensitive"
"defaultMessage": "Media is marked as sensitive",
"id": "compose_form.sensitive.marked"
},
{
"defaultMessage": "Media is not marked as sensitive",
"id": "compose_form.sensitive.unmarked"
}
],
"path": "app/javascript/mastodon/features/compose/containers/sensitive_button_container.json"
@@ -753,8 +781,12 @@
{
"descriptors": [
{
"defaultMessage": "Hide text behind warning",
"id": "compose_form.spoiler"
"defaultMessage": "Text is hidden behind warning",
"id": "compose_form.spoiler.marked"
},
{
"defaultMessage": "Text is not hidden",
"id": "compose_form.spoiler.unmarked"
}
],
"path": "app/javascript/mastodon/features/compose/containers/spoiler_button_container.json"
@@ -1351,6 +1383,14 @@
"defaultMessage": "Block",
"id": "confirmations.block.confirm"
},
{
"defaultMessage": "Show more for all",
"id": "status.show_more_all"
},
{
"defaultMessage": "Show less for all",
"id": "status.show_less_all"
},
{
"defaultMessage": "Are you sure you want to block {name}?",
"id": "confirmations.block.message"
@@ -1576,6 +1616,10 @@
},
{
"descriptors": [
{
"defaultMessage": "Close",
"id": "lightbox.close"
},
{
"defaultMessage": "Additional comments",
"id": "report.placeholder"
@@ -1605,10 +1649,6 @@
},
{
"descriptors": [
{
"defaultMessage": "Compose",
"id": "tabs_bar.compose"
},
{
"defaultMessage": "Home",
"id": "tabs_bar.home"

View File

@@ -1,7 +1,9 @@
{
"account.block": "Block @{name}",
"account.block_domain": "Hide everything from {domain}",
"account.blocked": "Blocked",
"account.disclaimer_full": "Information below may reflect the user's profile incompletely.",
"account.domain_blocked": "Domain hidden",
"account.edit_profile": "Edit profile",
"account.follow": "Follow",
"account.followers": "Followers",
@@ -13,6 +15,7 @@
"account.moved_to": "{name} has moved to:",
"account.mute": "Mute @{name}",
"account.mute_notifications": "Mute notifications from @{name}",
"account.muted": "Muted",
"account.posts": "Toots",
"account.posts_with_replies": "Toots with replies",
"account.report": "Report @{name}",
@@ -57,8 +60,10 @@
"compose_form.placeholder": "What is on your mind?",
"compose_form.publish": "Toot",
"compose_form.publish_loud": "{publish}!",
"compose_form.sensitive": "Mark media as sensitive",
"compose_form.spoiler": "Hide text behind warning",
"compose_form.sensitive.marked": "Media is marked as sensitive",
"compose_form.sensitive.unmarked": "Media is not marked as sensitive",
"compose_form.spoiler.marked": "Text is hidden behind warning",
"compose_form.spoiler.unmarked": "Text is not hidden",
"compose_form.spoiler_placeholder": "Write your warning here",
"confirmation_modal.cancel": "Cancel",
"confirmations.block.confirm": "Block",
@@ -216,6 +221,7 @@
"report.target": "Reporting {target}",
"search.placeholder": "Search",
"search_popout.search_format": "Advanced search format",
"search_popout.tips.full_text": "Simple text returns statuses you have written, favourited, boosted, or have been mentioned in, as well as matching usernames, display names, and hashtags.",
"search_popout.tips.hashtag": "hashtag",
"search_popout.tips.status": "status",
"search_popout.tips.text": "Simple text returns matching display names, usernames and hashtags",
@@ -238,6 +244,7 @@
"status.mute_conversation": "Mute conversation",
"status.open": "Expand this status",
"status.pin": "Pin on profile",
"status.pinned": "Pinned toot",
"status.reblog": "Boost",
"status.reblogged_by": "{name} boosted",
"status.reply": "Reply",
@@ -247,10 +254,11 @@
"status.sensitive_warning": "Sensitive content",
"status.share": "Share",
"status.show_less": "Show less",
"status.show_less_all": "Show less for all",
"status.show_more": "Show more",
"status.show_more_all": "Show more for all",
"status.unmute_conversation": "Unmute conversation",
"status.unpin": "Unpin from profile",
"tabs_bar.compose": "Compose",
"tabs_bar.federated_timeline": "Federated",
"tabs_bar.home": "Home",
"tabs_bar.local_timeline": "Local",

View File

@@ -1,7 +1,9 @@
{
"account.block": "Bloki @{name}",
"account.block_domain": "Kaŝi ĉion de {domain}",
"account.blocked": "Blokita",
"account.disclaimer_full": "Subaj informoj povas reflekti la profilon de la uzanto nekomplete.",
"account.domain_blocked": "Domajno kaŝita",
"account.edit_profile": "Redakti profilon",
"account.follow": "Sekvi",
"account.followers": "Sekvantoj",
@@ -13,8 +15,9 @@
"account.moved_to": "{name} moviĝis al:",
"account.mute": "Silentigi @{name}",
"account.mute_notifications": "Silentigi sciigojn el @{name}",
"account.posts": "Hupoj",
"account.posts_with_replies": "Toots with replies",
"account.muted": "Silentigita",
"account.posts": "Mesaĝoj",
"account.posts_with_replies": "Mesaĝoj kun respondoj",
"account.report": "Signali @{name}",
"account.requested": "Atendo de aprobo. Alklaku por nuligi peton de sekvado",
"account.share": "Diskonigi la profilon de @{name}",
@@ -50,15 +53,17 @@
"column_header.show_settings": "Montri agordojn",
"column_header.unpin": "Depingli",
"column_subheading.navigation": "Navigado",
"column_subheading.settings": "Agordoj",
"column_subheading.settings": "Agordado",
"compose_form.hashtag_warning": "Ĉi tiu mesaĝo ne estos listigita per ajna kradvorto. Nur publikaj mesaĝoj estas serĉeblaj per kradvortoj.",
"compose_form.lock_disclaimer": "Via konta ne estas {locked}. Iu ajn povas sekvi vin por vidi viajn mesaĝojn nur por sekvantoj.",
"compose_form.lock_disclaimer.lock": "ŝlosita",
"compose_form.placeholder": "Pri kio vi pensas?",
"compose_form.publish": "Hup",
"compose_form.publish_loud": "{publish}!",
"compose_form.sensitive": "Marki aŭdovidaĵon tikla",
"compose_form.spoiler": "Kaŝi tekston malantaŭ averto",
"compose_form.sensitive.marked": "Media is marked as sensitive",
"compose_form.sensitive.unmarked": "Media is not marked as sensitive",
"compose_form.spoiler.marked": "Text is hidden behind warning",
"compose_form.spoiler.unmarked": "Text is not hidden",
"compose_form.spoiler_placeholder": "Skribu vian averton ĉi tie",
"confirmation_modal.cancel": "Nuligi",
"confirmations.block.confirm": "Bloki",
@@ -147,7 +152,7 @@
"navigation_bar.edit_profile": "Redakti profilon",
"navigation_bar.favourites": "Stelumoj",
"navigation_bar.follow_requests": "Petoj de sekvado",
"navigation_bar.info": "Pri ĉiu tiu nodo",
"navigation_bar.info": "Pri ĉi tiu nodo",
"navigation_bar.keyboard_shortcuts": "Klavaraj mallongigoj",
"navigation_bar.lists": "Listoj",
"navigation_bar.logout": "Elsaluti",
@@ -208,21 +213,22 @@
"relative_time.minutes": "{number}m",
"relative_time.seconds": "{number}s",
"reply_indicator.cancel": "Nuligi",
"report.forward": "Forward to {target}",
"report.forward_hint": "The account is from another server. Send an anonymized copy of the report there as well?",
"report.hint": "The report will be sent to your instance moderators. You can provide an explanation of why you are reporting this account below:",
"report.forward": "Plusendi al {target}",
"report.forward_hint": "La konto estas en alia servilo. Ĉu sendi sennomigitan kopion de la signalo ankaŭ tien?",
"report.hint": "La signalo estos sendita al la kontrolantoj de via nodo. Vi povas doni klarigon pri kial vi signalas ĉi tiun konton sube:",
"report.placeholder": "Pliaj komentoj",
"report.submit": "Sendi",
"report.target": "Signali {target}",
"search.placeholder": "Serĉi",
"search_popout.search_format": "Detala serĉo",
"search_popout.tips.full_text": "Simplaj tekstoj montras la mesaĝojn, kiujn vi skribis, stelumis, diskonigis, aŭ en kiuj vi estis menciita, sed ankaŭ kongruajn uzantnomojn, montratajn nomojn, kaj kradvortojn.",
"search_popout.tips.hashtag": "kradvorto",
"search_popout.tips.status": "mesaĝoj",
"search_popout.tips.text": "Simpla teksto montras la kongruajn afiŝitajn nomojn, uzantnomojn kaj kradvortojn",
"search_popout.tips.user": "uzanto",
"search_results.accounts": "People",
"search_results.hashtags": "Hashtags",
"search_results.statuses": "Toots",
"search_results.accounts": "Homoj",
"search_results.hashtags": "Kradvortoj",
"search_results.statuses": "Mesaĝoj",
"search_results.total": "{count, number} {count, plural, one {rezulto} other {rezultoj}}",
"standalone.public_title": "Enrigardo…",
"status.block": "Bloki @{name}",
@@ -238,6 +244,7 @@
"status.mute_conversation": "Silentigi konversacion",
"status.open": "Grandigi ĉi tiun mesaĝon",
"status.pin": "Alpingli en la profilo",
"status.pinned": "Alpinglita mesaĝo",
"status.reblog": "Diskonigi",
"status.reblogged_by": "{name} diskonigis",
"status.reply": "Respondi",
@@ -247,10 +254,11 @@
"status.sensitive_warning": "Tikla enhavo",
"status.share": "Diskonigi",
"status.show_less": "Malgrandigi",
"status.show_less_all": "Show less for all",
"status.show_more": "Grandigi",
"status.show_more_all": "Show more for all",
"status.unmute_conversation": "Malsilentigi konversacion",
"status.unpin": "Depingli de profilo",
"tabs_bar.compose": "Ekskribi",
"tabs_bar.federated_timeline": "Fratara tempolinio",
"tabs_bar.home": "Hejmo",
"tabs_bar.local_timeline": "Loka tempolinio",
@@ -259,7 +267,7 @@
"upload_area.title": "Altreni kaj lasi por alŝuti",
"upload_button.label": "Aldoni aŭdovidaĵon",
"upload_form.description": "Priskribi por misvidantaj homoj",
"upload_form.focus": "Crop",
"upload_form.focus": "Stuci",
"upload_form.undo": "Malfari",
"upload_progress.label": "Alŝutado…",
"video.close": "Fermi videon",

View File

@@ -1,7 +1,9 @@
{
"account.block": "Bloquear",
"account.block_domain": "Ocultar todo de {domain}",
"account.blocked": "Bloqueado",
"account.disclaimer_full": "La siguiente información del usuario puede estar incompleta.",
"account.domain_blocked": "Dominio oculto",
"account.edit_profile": "Editar perfil",
"account.follow": "Seguir",
"account.followers": "Seguidores",
@@ -13,8 +15,9 @@
"account.moved_to": "{name} se ha mudado a:",
"account.mute": "Silenciar a @{name}",
"account.mute_notifications": "Silenciar notificaciones de @{name}",
"account.posts": "Publicaciones",
"account.posts_with_replies": "Toots with replies",
"account.muted": "Silenciado",
"account.posts": "Toots",
"account.posts_with_replies": "Toots con respuestas",
"account.report": "Reportar a @{name}",
"account.requested": "Esperando aprobación",
"account.share": "Compartir el perfil de @{name}",
@@ -51,14 +54,16 @@
"column_header.unpin": "Dejar de fijar",
"column_subheading.navigation": "Navegación",
"column_subheading.settings": "Ajustes",
"compose_form.hashtag_warning": "This toot won't be listed under any hashtag as it is unlisted. Only public toots can be searched by hashtag.",
"compose_form.hashtag_warning": "Este toot no se mostrará bajo hashtags porque no es público. Sólo los toots públicos se pueden buscar por hashtag.",
"compose_form.lock_disclaimer": "Tu cuenta no está bloqueada. Todos pueden seguirte para ver tus toots solo para seguidores.",
"compose_form.lock_disclaimer.lock": "bloqueado",
"compose_form.placeholder": "¿En qué estás pensando?",
"compose_form.publish": "Tootear",
"compose_form.publish_loud": "{publish}!",
"compose_form.sensitive": "Marcar contenido como sensible",
"compose_form.spoiler": "Ocultar texto tras una advertencia",
"compose_form.sensitive.marked": "Material marcado como sensible",
"compose_form.sensitive.unmarked": "Material no marcado como sensible",
"compose_form.spoiler.marked": "Texto oculto tras la advertencia",
"compose_form.spoiler.unmarked": "Texto no oculto",
"compose_form.spoiler_placeholder": "Advertencia de contenido",
"confirmation_modal.cancel": "Cancelar",
"confirmations.block.confirm": "Bloquear",
@@ -66,7 +71,7 @@
"confirmations.delete.confirm": "Eliminar",
"confirmations.delete.message": "¿Estás seguro de que quieres borrar este toot?",
"confirmations.delete_list.confirm": "Delete",
"confirmations.delete_list.message": "Are you sure you want to permanently delete this list?",
"confirmations.delete_list.message": "¿Seguro que quieres borrar esta lista permanentemente?",
"confirmations.domain_block.confirm": "Ocultar dominio entero",
"confirmations.domain_block.message": "¿Seguro de que quieres bloquear al dominio entero? En algunos casos es preferible bloquear o silenciar objetivos determinados.",
"confirmations.mute.confirm": "Silenciar",
@@ -129,18 +134,18 @@
"lightbox.close": "Cerrar",
"lightbox.next": "Siguiente",
"lightbox.previous": "Anterior",
"lists.account.add": "Add to list",
"lists.account.remove": "Remove from list",
"lists.account.add": "Añadir a lista",
"lists.account.remove": "Quitar de lista",
"lists.delete": "Delete list",
"lists.edit": "Edit list",
"lists.new.create": "Add list",
"lists.new.title_placeholder": "New list title",
"lists.search": "Search among people you follow",
"lists.subheading": "Your lists",
"lists.edit": "Editar lista",
"lists.new.create": "Añadir lista",
"lists.new.title_placeholder": "Título de la nueva lista",
"lists.search": "Buscar entre la gente a la que sigues",
"lists.subheading": "Tus listas",
"loading_indicator.label": "Cargando…",
"media_gallery.toggle_visible": "Cambiar visibilidad",
"missing_indicator.label": "No encontrado",
"missing_indicator.sublabel": "This resource could not be found",
"missing_indicator.sublabel": "No se encontró este recurso",
"mute_modal.hide_notifications": "Ocultar notificaciones de este usuario?",
"navigation_bar.blocks": "Usuarios bloqueados",
"navigation_bar.community_timeline": "Historia local",
@@ -149,7 +154,7 @@
"navigation_bar.follow_requests": "Solicitudes para seguirte",
"navigation_bar.info": "Información adicional",
"navigation_bar.keyboard_shortcuts": "Atajos de teclado",
"navigation_bar.lists": "Lists",
"navigation_bar.lists": "Listas",
"navigation_bar.logout": "Cerrar sesión",
"navigation_bar.mutes": "Usuarios silenciados",
"navigation_bar.pins": "Toots fijados",
@@ -176,8 +181,8 @@
"onboarding.page_four.home": "La línea de tiempo principal muestra toots de gente que sigues.",
"onboarding.page_four.notifications": "Las notificaciones se muestran cuando alguien interactúa contigo.",
"onboarding.page_one.federation": "Mastodon es una red de servidores federados que conforman una red social aún más grande. Llamamos a estos servidores instancias.",
"onboarding.page_one.full_handle": "Your full handle",
"onboarding.page_one.handle_hint": "This is what you would tell your friends to search for.",
"onboarding.page_one.full_handle": "Tu sobrenombre completo",
"onboarding.page_one.handle_hint": "Esto es lo que dirías a tus amistades que buscaran.",
"onboarding.page_one.welcome": "¡Bienvenido a Mastodon!",
"onboarding.page_six.admin": "El administrador de tu instancia es {admin}.",
"onboarding.page_six.almost_done": "Ya casi…",
@@ -200,27 +205,28 @@
"privacy.public.short": "Público",
"privacy.unlisted.long": "No mostrar en la historia federada",
"privacy.unlisted.short": "Sin federar",
"regeneration_indicator.label": "Loading…",
"regeneration_indicator.sublabel": "Your home feed is being prepared!",
"regeneration_indicator.label": "Cargando…",
"regeneration_indicator.sublabel": "¡Tu historia de inicio se está preparando!",
"relative_time.days": "{number}d",
"relative_time.hours": "{number}h",
"relative_time.just_now": "ahora",
"relative_time.minutes": "{number}m",
"relative_time.seconds": "{number}s",
"reply_indicator.cancel": "Cancelar",
"report.forward": "Forward to {target}",
"report.forward_hint": "The account is from another server. Send an anonymized copy of the report there as well?",
"report.hint": "The report will be sent to your instance moderators. You can provide an explanation of why you are reporting this account below:",
"report.forward": "Reenviar a {target}",
"report.forward_hint": "Esta cuenta es de otro servidor. ¿Enviar una copia anonimizada del informe allí también?",
"report.hint": "El informe se enviará a los moderadores de tu instancia. Puedes proporcionar una explicación de por qué informas sobre esta cuenta a continuación:",
"report.placeholder": "Comentarios adicionales",
"report.submit": "Publicar",
"report.target": "Reportando",
"search.placeholder": "Buscar",
"search_popout.search_format": "Formato de búsqueda avanzada",
"search_popout.tips.full_text": "Búsquedas de texto recuperan posts que has escrito, marcado como favoritos, retooteado o en los que has sido mencionado, así como usuarios, nombres y hashtags.",
"search_popout.tips.hashtag": "etiqueta",
"search_popout.tips.status": "status",
"search_popout.tips.text": "El texto simple devuelve correspondencias de nombre, usuario y hashtag",
"search_popout.tips.user": "usuario",
"search_results.accounts": "People",
"search_results.accounts": "Gente",
"search_results.hashtags": "Hashtags",
"search_results.statuses": "Toots",
"search_results.total": "{count, number} {count, plural, one {resultado} other {resultados}}",
@@ -234,10 +240,11 @@
"status.media_hidden": "Contenido multimedia oculto",
"status.mention": "Mencionar",
"status.more": "Más",
"status.mute": "Mute @{name}",
"status.mute": "Silenciar @{name}",
"status.mute_conversation": "Silenciar conversación",
"status.open": "Expandir estado",
"status.pin": "Fijar",
"status.pinned": "Toot fijado",
"status.reblog": "Retootear",
"status.reblogged_by": "Retooteado por {name}",
"status.reply": "Responder",
@@ -247,10 +254,11 @@
"status.sensitive_warning": "Contenido sensible",
"status.share": "Compartir",
"status.show_less": "Mostrar menos",
"status.show_less_all": "Mostrar menos para todo",
"status.show_more": "Mostrar más",
"status.show_more_all": "Mostrar más para todo",
"status.unmute_conversation": "Dejar de silenciar conversación",
"status.unpin": "Dejar de fijar",
"tabs_bar.compose": "Redactar",
"tabs_bar.federated_timeline": "Federado",
"tabs_bar.home": "Inicio",
"tabs_bar.local_timeline": "Local",
@@ -259,7 +267,7 @@
"upload_area.title": "Arrastra y suelta para subir",
"upload_button.label": "Subir multimedia",
"upload_form.description": "Describir para los usuarios con dificultad visual",
"upload_form.focus": "Crop",
"upload_form.focus": "Recortar",
"upload_form.undo": "Deshacer",
"upload_progress.label": "Subiendo…",
"video.close": "Cerrar video",

View File

@@ -1,7 +1,9 @@
{
"account.block": "مسدودسازی @{name}",
"account.block_domain": "پنهان‌سازی همه چیز از سرور {domain}",
"account.blocked": "Blocked",
"account.disclaimer_full": "اطلاعات زیر ممکن است نمایهٔ این کاربر را به تمامی نشان ندهد.",
"account.domain_blocked": "Domain hidden",
"account.edit_profile": "ویرایش نمایه",
"account.follow": "پی بگیرید",
"account.followers": "پیگیران",
@@ -13,6 +15,7 @@
"account.moved_to": "{name} منتقل شده است به:",
"account.mute": "بی‌صدا کردن @{name}",
"account.mute_notifications": "بی‌صداکردن اعلان‌ها از طرف @{name}",
"account.muted": "Muted",
"account.posts": "نوشته‌ها",
"account.posts_with_replies": "Toots with replies",
"account.report": "گزارش @{name}",
@@ -57,8 +60,10 @@
"compose_form.placeholder": "تازه چه خبر؟",
"compose_form.publish": "بوق",
"compose_form.publish_loud": "{publish}!",
"compose_form.sensitive": "تصاویر حساس هستند",
"compose_form.spoiler": "نوشته را پشت هشدار پنهان کنید",
"compose_form.sensitive.marked": "Media is marked as sensitive",
"compose_form.sensitive.unmarked": "Media is not marked as sensitive",
"compose_form.spoiler.marked": "Text is hidden behind warning",
"compose_form.spoiler.unmarked": "Text is not hidden",
"compose_form.spoiler_placeholder": "هشدار محتوا",
"confirmation_modal.cancel": "بی‌خیال",
"confirmations.block.confirm": "مسدود کن",
@@ -216,6 +221,7 @@
"report.target": "گزارش‌دادن",
"search.placeholder": "جستجو",
"search_popout.search_format": "راهنمای جستجوی پیشرفته",
"search_popout.tips.full_text": "Simple text returns statuses you have written, favourited, boosted, or have been mentioned in, as well as matching usernames, display names, and hashtags.",
"search_popout.tips.hashtag": "هشتگ",
"search_popout.tips.status": "نوشته",
"search_popout.tips.text": "جستجوی متنی ساده برای نام‌ها، نام‌های کاربری، و هشتگ‌ها",
@@ -238,6 +244,7 @@
"status.mute_conversation": "بی‌صداکردن گفتگو",
"status.open": "این نوشته را باز کن",
"status.pin": "نوشتهٔ ثابت نمایه",
"status.pinned": "Pinned toot",
"status.reblog": "بازبوقیدن",
"status.reblogged_by": "{name} بازبوقید",
"status.reply": "پاسخ",
@@ -247,10 +254,11 @@
"status.sensitive_warning": "محتوای حساس",
"status.share": "هم‌رسانی",
"status.show_less": "نهفتن",
"status.show_less_all": "Show less for all",
"status.show_more": "نمایش",
"status.show_more_all": "Show more for all",
"status.unmute_conversation": "باصداکردن گفتگو",
"status.unpin": "برداشتن نوشتهٔ ثابت نمایه",
"tabs_bar.compose": "بنویسید",
"tabs_bar.federated_timeline": "همگانی",
"tabs_bar.home": "خانه",
"tabs_bar.local_timeline": "محلی",

View File

@@ -1,7 +1,9 @@
{
"account.block": "Estä @{name}",
"account.block_domain": "Piilota kaikki sisältö verkkotunnuksesta {domain}",
"account.blocked": "Estetty",
"account.disclaimer_full": "Alla olevat käyttäjän profiilitiedot saattavat olla epätäydellisiä.",
"account.domain_blocked": "Verkko-osoite piilotettu",
"account.edit_profile": "Muokkaa",
"account.follow": "Seuraa",
"account.followers": "Seuraajia",
@@ -13,8 +15,9 @@
"account.moved_to": "{name} on muuttanut instanssiin:",
"account.mute": "Mykistä @{name}",
"account.mute_notifications": "Mykistä ilmoitukset käyttäjältä @{name}",
"account.muted": "Mykistetty",
"account.posts": "Töötit",
"account.posts_with_replies": "Toots with replies",
"account.posts_with_replies": "Töötit ja vastaukset",
"account.report": "Report @{name}",
"account.requested": "Odottaa hyväksyntää. Klikkaa peruuttaaksesi seurauspyynnön",
"account.share": "Jaa käyttäjän @{name} profiili",
@@ -57,8 +60,10 @@
"compose_form.placeholder": "Mitä sinulla on mielessä?",
"compose_form.publish": "Toot",
"compose_form.publish_loud": "{publish}!",
"compose_form.sensitive": "Merkitse media herkäksi",
"compose_form.spoiler": "Piiloita teksti varoituksen taakse",
"compose_form.sensitive.marked": "Media on merkitty arkaluontoiseksi",
"compose_form.sensitive.unmarked": "Mediaa ei ole merkitty arkaluontoiseksi",
"compose_form.spoiler.marked": "Teksti on piilotettu varoituksen taakse",
"compose_form.spoiler.unmarked": "Teksti ei ole piilotettu",
"compose_form.spoiler_placeholder": "Content warning",
"confirmation_modal.cancel": "Peruuta",
"confirmations.block.confirm": "Estä",
@@ -177,13 +182,13 @@
"onboarding.page_four.notifications": "Ilmoitukset-sarake näyttää sinulle, kun joku on viestii kanssasi.",
"onboarding.page_one.federation": "Mastodon on yhteisöpalvelu, joka toimii monen itsenäisen palvelimen muodostamassa verkossa. Me kutsumme näitä palvelimia instansseiksi.",
"onboarding.page_one.full_handle": "Koko käyttäjänimesi",
"onboarding.page_one.handle_hint": "This is what you would tell your friends to search for.",
"onboarding.page_one.handle_hint": "Tämä on se, mitä voisit ehdottaa ystäviäsi etsimään.",
"onboarding.page_one.welcome": "Tervetuloa Mastodoniin!",
"onboarding.page_six.admin": "Instanssisi ylläpitäjä on {admin}.",
"onboarding.page_six.almost_done": "Melkein valmista...",
"onboarding.page_six.appetoot": "Bon Appetööt!",
"onboarding.page_six.apps_available": "{apps} on saatavilla iOS:lle, Androidille ja muille alustoille.",
"onboarding.page_six.github": "Mastodon is free open-source software. You can report bugs, request features, or contribute to the code on {github}.",
"onboarding.page_six.github": "Mastodon on ilmainen, vapaan lähdekoodin ohjelma. Voit raportoida bugeja, pyytää ominaisuuksia tai osallistua kehittämiseen GitHub-palvelussa: {github}.",
"onboarding.page_six.guidelines": "yhteisön säännöt",
"onboarding.page_six.read_guidelines": "Ole hyvä ja lue {domain}:n {guidelines}!",
"onboarding.page_six.various_app": "mobiilisovellukset",
@@ -208,21 +213,22 @@
"relative_time.minutes": "{number}m",
"relative_time.seconds": "{number}s",
"reply_indicator.cancel": "Peruuta",
"report.forward": "Forward to {target}",
"report.forward_hint": "The account is from another server. Send an anonymized copy of the report there as well?",
"report.hint": "The report will be sent to your instance moderators. You can provide an explanation of why you are reporting this account below:",
"report.forward": "Uudelleenohjaa kohteeseen {target}",
"report.forward_hint": "Tämä tili on toiselta serveriltä. Haluatko, että myös sinne lähetetään anonymisoitu kopio ilmiantoraportista?",
"report.hint": "Ilmianto lähetetään instanssisi moderaattoreille. Voit antaa kuvauksen käyttäjän ilmiantamisen syystä alle:",
"report.placeholder": "Lisäkommentit",
"report.submit": "Submit",
"report.target": "Reporting",
"search.placeholder": "Hae",
"search_popout.search_format": "Tarkennettu haku",
"search_popout.tips.full_text": "Tekstihaku palauttaa statuspäivitykset jotka olet kirjoittanut, lisännyt suosikkeihisi, boostannut tai joissa sinut mainitaan, sekä käyttäjänimet, nimimerkit ja hastagit jotka sisältävät tekstin.",
"search_popout.tips.hashtag": "hashtagi",
"search_popout.tips.status": "status",
"search_popout.tips.text": "Pelkkä tekstihaku palauttaa hakua vastaavat nimimerkit, käyttäjänimet ja hastagit",
"search_popout.tips.user": "käyttäjä",
"search_results.accounts": "People",
"search_results.hashtags": "Hashtags",
"search_results.statuses": "Toots",
"search_results.accounts": "Ihmiset",
"search_results.hashtags": "Hashtagit",
"search_results.statuses": "Töötit",
"search_results.total": "{count, number} {count, plural, one {result} other {results}}",
"standalone.public_title": "Kurkistus sisälle...",
"status.block": "Block @{name}",
@@ -238,6 +244,7 @@
"status.mute_conversation": "Mykistä keskustelu",
"status.open": "Laajenna statuspäivitys",
"status.pin": "Kiinnitä profiiliin",
"status.pinned": "Kiinnitetty töötti",
"status.reblog": "Buustaa",
"status.reblogged_by": "{name} buustasi",
"status.reply": "Vastaa",
@@ -247,11 +254,12 @@
"status.sensitive_warning": "Arkaluontoista sisältöä",
"status.share": "Jaa",
"status.show_less": "Näytä vähemmän",
"status.show_less_all": "Näytä vähemmän kaikista",
"status.show_more": "Näytä lisää",
"status.show_more_all": "Näytä enemmän kaikista",
"status.unmute_conversation": "Poista mykistys keskustelulta",
"status.unpin": "Irrota profiilista",
"tabs_bar.compose": "Luo",
"tabs_bar.federated_timeline": "Federated",
"tabs_bar.federated_timeline": "Yleinen",
"tabs_bar.home": "Koti",
"tabs_bar.local_timeline": "Paikallinen",
"tabs_bar.notifications": "Ilmoitukset",
@@ -259,7 +267,7 @@
"upload_area.title": "Raahaa ja pudota tähän ladataksesi",
"upload_button.label": "Lisää mediaa",
"upload_form.description": "Anna kuvaus näkörajoitteisia varten",
"upload_form.focus": "Crop",
"upload_form.focus": "Rajaa",
"upload_form.undo": "Peru",
"upload_progress.label": "Ladataan...",
"video.close": "Sulje video",

View File

@@ -1,7 +1,9 @@
{
"account.block": "Bloquer @{name}",
"account.block_domain": "Tout masquer venant de {domain}",
"account.blocked": "Bloqué",
"account.disclaimer_full": "Les données ci-dessous peuvent ne pas refléter ce profil dans sa totalité.",
"account.domain_blocked": "Domaine caché",
"account.edit_profile": "Modifier le profil",
"account.follow": "Suivre",
"account.followers": "Abonné⋅e⋅s",
@@ -13,8 +15,9 @@
"account.moved_to": "{name} a déménagé vers :",
"account.mute": "Masquer @{name}",
"account.mute_notifications": "Ignorer les notifications de @{name}",
"account.posts": "Statuts",
"account.posts_with_replies": "Toots with replies",
"account.muted": "Silencé",
"account.posts": "Pouets",
"account.posts_with_replies": "Pouets avec réponses",
"account.report": "Signaler",
"account.requested": "Invitation envoyée",
"account.share": "Partager le profil de @{name}",
@@ -57,8 +60,10 @@
"compose_form.placeholder": "Quavez-vous en tête?",
"compose_form.publish": "Pouet",
"compose_form.publish_loud": "{publish}!",
"compose_form.sensitive": "Marquer le média comme sensible",
"compose_form.spoiler": "Masquer le texte derrière un avertissement",
"compose_form.sensitive.marked": "Média marqué comme sensible",
"compose_form.sensitive.unmarked": "Média non marqué comme sensible",
"compose_form.spoiler.marked": "Le texte est caché derrière un avertissement",
"compose_form.spoiler.unmarked": "Le texte n'est pas caché",
"compose_form.spoiler_placeholder": "Écrivez ici votre avertissement",
"confirmation_modal.cancel": "Annuler",
"confirmations.block.confirm": "Bloquer",
@@ -208,21 +213,22 @@
"relative_time.minutes": "{number} min",
"relative_time.seconds": "{number} s",
"reply_indicator.cancel": "Annuler",
"report.forward": "Forward to {target}",
"report.forward_hint": "The account is from another server. Send an anonymized copy of the report there as well?",
"report.hint": "The report will be sent to your instance moderators. You can provide an explanation of why you are reporting this account below:",
"report.forward": "Transférer à {target}",
"report.forward_hint": "Le compte provient d'un autre serveur. Envoyez également une copie anonyme du rapport ?",
"report.hint": "Le rapport sera envoyé aux modérateurs de votre instance. Vous pouvez expliquer pourquoi vous signalez ce compte ci-dessous :",
"report.placeholder": "Commentaires additionnels",
"report.submit": "Envoyer",
"report.target": "Signalement",
"search.placeholder": "Rechercher",
"search_popout.search_format": "Recherche avancée",
"search_popout.tips.full_text": "Les textes simples retournent les pouets que vous avez écris, mis en favori, épinglés, ou ayant été mentionnés, ainsi que les noms d'utilisateurs, les noms affichés, et les hashtags correspondant.",
"search_popout.tips.hashtag": "hashtag",
"search_popout.tips.status": "statuts",
"search_popout.tips.text": "Un texte simple renvoie les noms affichés, les noms dutilisateur⋅ice et les hashtags correspondants",
"search_popout.tips.user": "utilisateur⋅ice",
"search_results.accounts": "People",
"search_results.accounts": "Personnes",
"search_results.hashtags": "Hashtags",
"search_results.statuses": "Toots",
"search_results.statuses": "Pouets",
"search_results.total": "{count, number} {count, plural, one {résultat} other {résultats}}",
"standalone.public_title": "Jeter un coup dœil…",
"status.block": "Block @{name}",
@@ -238,6 +244,7 @@
"status.mute_conversation": "Masquer la conversation",
"status.open": "Déplier ce statut",
"status.pin": "Épingler sur le profil",
"status.pinned": "Pouet épinglé",
"status.reblog": "Partager",
"status.reblogged_by": "{name} a partagé:",
"status.reply": "Répondre",
@@ -247,10 +254,11 @@
"status.sensitive_warning": "Contenu sensible",
"status.share": "Partager",
"status.show_less": "Replier",
"status.show_less_all": "Tout replier",
"status.show_more": "Déplier",
"status.show_more_all": "Tout déplier",
"status.unmute_conversation": "Ne plus masquer la conversation",
"status.unpin": "Retirer du profil",
"tabs_bar.compose": "Composer",
"tabs_bar.federated_timeline": "Fil public global",
"tabs_bar.home": "Accueil",
"tabs_bar.local_timeline": "Fil public local",
@@ -259,7 +267,7 @@
"upload_area.title": "Glissez et déposez pour envoyer",
"upload_button.label": "Joindre un média",
"upload_form.description": "Décrire pour les malvoyants",
"upload_form.focus": "Crop",
"upload_form.focus": "Recadrer",
"upload_form.undo": "Annuler",
"upload_progress.label": "Envoi en cours…",
"video.close": "Fermer la vidéo",

View File

@@ -1,7 +1,9 @@
{
"account.block": "Bloquear @{name}",
"account.block_domain": "Ocultar calquer contido de {domain}",
"account.blocked": "Blocked",
"account.disclaimer_full": "A información inferior podería mostrar un perfil incompleto da usuaria.",
"account.domain_blocked": "Domain hidden",
"account.edit_profile": "Editar perfil",
"account.follow": "Seguir",
"account.followers": "Seguidoras",
@@ -13,6 +15,7 @@
"account.moved_to": "{name} marchou a:",
"account.mute": "Acalar @{name}",
"account.mute_notifications": "Acalar as notificacións de @{name}",
"account.muted": "Muted",
"account.posts": "Toots",
"account.posts_with_replies": "Toots with replies",
"account.report": "Informar sobre @{name}",
@@ -57,8 +60,10 @@
"compose_form.placeholder": "A qué andas?",
"compose_form.publish": "Toot",
"compose_form.publish_loud": "{publish}!",
"compose_form.sensitive": "Marcar medios como sensibles",
"compose_form.spoiler": "Agochar texto detrás de un aviso",
"compose_form.sensitive.marked": "Media is marked as sensitive",
"compose_form.sensitive.unmarked": "Media is not marked as sensitive",
"compose_form.spoiler.marked": "Text is hidden behind warning",
"compose_form.spoiler.unmarked": "Text is not hidden",
"compose_form.spoiler_placeholder": "Escriba o aviso aquí",
"confirmation_modal.cancel": "Cancelar",
"confirmations.block.confirm": "Bloquear",
@@ -216,6 +221,7 @@
"report.target": "Informar {target}",
"search.placeholder": "Buscar",
"search_popout.search_format": "Formato de busca avanzada",
"search_popout.tips.full_text": "Simple text returns statuses you have written, favourited, boosted, or have been mentioned in, as well as matching usernames, display names, and hashtags.",
"search_popout.tips.hashtag": "etiqueta",
"search_popout.tips.status": "estado",
"search_popout.tips.text": "Texto simple devolve coincidencias con nomes públicos, nomes de usuaria e etiquetas",
@@ -238,6 +244,7 @@
"status.mute_conversation": "Acalar conversa",
"status.open": "Expandir este estado",
"status.pin": "Fixar no perfil",
"status.pinned": "Pinned toot",
"status.reblog": "Promover",
"status.reblogged_by": "{name} promoveu",
"status.reply": "Resposta",
@@ -247,10 +254,11 @@
"status.sensitive_warning": "Contido sensible",
"status.share": "Compartir",
"status.show_less": "Mostrar menos",
"status.show_less_all": "Show less for all",
"status.show_more": "Mostrar máis",
"status.show_more_all": "Show more for all",
"status.unmute_conversation": "Non acalar a conversa",
"status.unpin": "Despegar do perfil",
"tabs_bar.compose": "Compoñer",
"tabs_bar.federated_timeline": "Federado",
"tabs_bar.home": "Inicio",
"tabs_bar.local_timeline": "Local",

View File

@@ -1,7 +1,9 @@
{
"account.block": "חסימת @{name}",
"account.block_domain": "להסתיר הכל מהקהילה {domain}",
"account.blocked": "Blocked",
"account.disclaimer_full": "המידע להלן עשוי להיות לא עדכני או לא שלם.",
"account.domain_blocked": "Domain hidden",
"account.edit_profile": "עריכת פרופיל",
"account.follow": "מעקב",
"account.followers": "עוקבים",
@@ -13,6 +15,7 @@
"account.moved_to": "החשבון {name} הועבר אל:",
"account.mute": "להשתיק את @{name}",
"account.mute_notifications": "להסתיר התראות מאת @{name}",
"account.muted": "Muted",
"account.posts": "הודעות",
"account.posts_with_replies": "Toots with replies",
"account.report": "לדווח על @{name}",
@@ -57,8 +60,10 @@
"compose_form.placeholder": "מה עובר לך בראש?",
"compose_form.publish": "ללחוש",
"compose_form.publish_loud": "לחצרץ!",
"compose_form.sensitive": "סימון תוכן כרגיש",
"compose_form.spoiler": "הסתרה מאחורי אזהרת תוכן",
"compose_form.sensitive.marked": "Media is marked as sensitive",
"compose_form.sensitive.unmarked": "Media is not marked as sensitive",
"compose_form.spoiler.marked": "Text is hidden behind warning",
"compose_form.spoiler.unmarked": "Text is not hidden",
"compose_form.spoiler_placeholder": "אזהרת תוכן",
"confirmation_modal.cancel": "ביטול",
"confirmations.block.confirm": "לחסום",
@@ -216,6 +221,7 @@
"report.target": "דיווח",
"search.placeholder": "חיפוש",
"search_popout.search_format": "מבנה חיפוש מתקדם",
"search_popout.tips.full_text": "Simple text returns statuses you have written, favourited, boosted, or have been mentioned in, as well as matching usernames, display names, and hashtags.",
"search_popout.tips.hashtag": "האשתג",
"search_popout.tips.status": "status",
"search_popout.tips.text": "טקסט פשוט מחזיר כינויים, שמות משתמש והאשתגים",
@@ -238,6 +244,7 @@
"status.mute_conversation": "השתקת שיחה",
"status.open": "הרחבת הודעה",
"status.pin": "לקבע באודות",
"status.pinned": "Pinned toot",
"status.reblog": "הדהוד",
"status.reblogged_by": "הודהד על ידי {name}",
"status.reply": "תגובה",
@@ -247,10 +254,11 @@
"status.sensitive_warning": "תוכן רגיש",
"status.share": "שיתוף",
"status.show_less": "הראה פחות",
"status.show_less_all": "Show less for all",
"status.show_more": "הראה יותר",
"status.show_more_all": "Show more for all",
"status.unmute_conversation": "הסרת השתקת שיחה",
"status.unpin": "לשחרר מקיבוע באודות",
"tabs_bar.compose": "חיבור",
"tabs_bar.federated_timeline": "ציר זמן בין-קהילתי",
"tabs_bar.home": "בבית",
"tabs_bar.local_timeline": "ציר זמן מקומי",

View File

@@ -1,7 +1,9 @@
{
"account.block": "Blokiraj @{name}",
"account.block_domain": "Sakrij sve sa {domain}",
"account.blocked": "Blocked",
"account.disclaimer_full": "Ovaj korisnik je sa druge instance. Ovaj broj bi mogao biti veći.",
"account.domain_blocked": "Domain hidden",
"account.edit_profile": "Uredi profil",
"account.follow": "Slijedi",
"account.followers": "Sljedbenici",
@@ -13,6 +15,7 @@
"account.moved_to": "{name} has moved to:",
"account.mute": "Utišaj @{name}",
"account.mute_notifications": "Mute notifications from @{name}",
"account.muted": "Muted",
"account.posts": "Postovi",
"account.posts_with_replies": "Toots with replies",
"account.report": "Prijavi @{name}",
@@ -57,8 +60,10 @@
"compose_form.placeholder": "Što ti je na umu?",
"compose_form.publish": "Toot",
"compose_form.publish_loud": "{publish}!",
"compose_form.sensitive": "Označi media sadržaj kao osjetljiv",
"compose_form.spoiler": "Sakrij text iza upozorenja",
"compose_form.sensitive.marked": "Media is marked as sensitive",
"compose_form.sensitive.unmarked": "Media is not marked as sensitive",
"compose_form.spoiler.marked": "Text is hidden behind warning",
"compose_form.spoiler.unmarked": "Text is not hidden",
"compose_form.spoiler_placeholder": "Upozorenje o sadržaju",
"confirmation_modal.cancel": "Otkaži",
"confirmations.block.confirm": "Blokiraj",
@@ -216,6 +221,7 @@
"report.target": "Prijavljivanje",
"search.placeholder": "Traži",
"search_popout.search_format": "Advanced search format",
"search_popout.tips.full_text": "Simple text returns statuses you have written, favourited, boosted, or have been mentioned in, as well as matching usernames, display names, and hashtags.",
"search_popout.tips.hashtag": "hashtag",
"search_popout.tips.status": "status",
"search_popout.tips.text": "Simple text returns matching display names, usernames and hashtags",
@@ -238,6 +244,7 @@
"status.mute_conversation": "Utišaj razgovor",
"status.open": "Proširi ovaj status",
"status.pin": "Pin on profile",
"status.pinned": "Pinned toot",
"status.reblog": "Podigni",
"status.reblogged_by": "{name} je podigao",
"status.reply": "Odgovori",
@@ -247,10 +254,11 @@
"status.sensitive_warning": "Osjetljiv sadržaj",
"status.share": "Share",
"status.show_less": "Pokaži manje",
"status.show_less_all": "Show less for all",
"status.show_more": "Pokaži više",
"status.show_more_all": "Show more for all",
"status.unmute_conversation": "Poništi utišavanje razgovora",
"status.unpin": "Unpin from profile",
"tabs_bar.compose": "Sastavi",
"tabs_bar.federated_timeline": "Federalni",
"tabs_bar.home": "Dom",
"tabs_bar.local_timeline": "Lokalno",

View File

@@ -1,7 +1,9 @@
{
"account.block": "@{name} letiltása",
"account.block_domain": "Minden elrejtése innen: {domain}",
"account.blocked": "Blocked",
"account.disclaimer_full": "Az alul található információk hiányosan mutathatják be a felhasználót.",
"account.domain_blocked": "Domain hidden",
"account.edit_profile": "Profil szerkesztése",
"account.follow": "Követés",
"account.followers": "Követők",
@@ -13,6 +15,7 @@
"account.moved_to": "{name} átköltözött:",
"account.mute": "@{name} némítása",
"account.mute_notifications": "@{name} értesítések némítása",
"account.muted": "Muted",
"account.posts": "Státuszok",
"account.posts_with_replies": "Toots with replies",
"account.report": "@{name} jelentése",
@@ -57,8 +60,10 @@
"compose_form.placeholder": "Mire gondolsz?",
"compose_form.publish": "Tülk",
"compose_form.publish_loud": "{publish}!",
"compose_form.sensitive": "Tartalom érzékenynek jelölése",
"compose_form.spoiler": "Szöveg figyelmeztetés mögé rejtése",
"compose_form.sensitive.marked": "Media is marked as sensitive",
"compose_form.sensitive.unmarked": "Media is not marked as sensitive",
"compose_form.spoiler.marked": "Text is hidden behind warning",
"compose_form.spoiler.unmarked": "Text is not hidden",
"compose_form.spoiler_placeholder": "Figyelmeztetését írja ide",
"confirmation_modal.cancel": "Bezár",
"confirmations.block.confirm": "Letilt",
@@ -216,6 +221,7 @@
"report.target": "Reporting",
"search.placeholder": "Keresés",
"search_popout.search_format": "Fejlett keresés",
"search_popout.tips.full_text": "Simple text returns statuses you have written, favourited, boosted, or have been mentioned in, as well as matching usernames, display names, and hashtags.",
"search_popout.tips.hashtag": "hashtag",
"search_popout.tips.status": "status",
"search_popout.tips.text": "Simple text returns matching display names, usernames and hashtags",
@@ -238,6 +244,7 @@
"status.mute_conversation": "Beszélgetés némítása",
"status.open": "Státusz kinagyítása",
"status.pin": "Kitűzés a profilra",
"status.pinned": "Pinned toot",
"status.reblog": "Reblog",
"status.reblogged_by": "{name} reblogolta",
"status.reply": "Válasz",
@@ -247,10 +254,11 @@
"status.sensitive_warning": "Érzékeny tartalom",
"status.share": "Megosztás",
"status.show_less": "Kevesebb",
"status.show_less_all": "Show less for all",
"status.show_more": "Többet",
"status.show_more_all": "Show more for all",
"status.unmute_conversation": "Beszélgetés némításának elvonása",
"status.unpin": "Kitűzés eltávolítása a profilról",
"tabs_bar.compose": "Összeállítás",
"tabs_bar.federated_timeline": "Federált",
"tabs_bar.home": "Kezdőlap",
"tabs_bar.local_timeline": "Local",

View File

@@ -1,7 +1,9 @@
{
"account.block": "Արգելափակել @{name}֊ին",
"account.block_domain": "Թաքցնել ամենը հետեւյալ տիրույթից՝ {domain}",
"account.blocked": "Blocked",
"account.disclaimer_full": "Ներքոհիշյալը կարող է ոչ ամբողջությամբ արտացոլել օգտատիրոջ էջի տվյալները։",
"account.domain_blocked": "Domain hidden",
"account.edit_profile": "Խմբագրել անձնական էջը",
"account.follow": "Հետեւել",
"account.followers": "Հետեւվողներ",
@@ -13,6 +15,7 @@
"account.moved_to": "{name}֊ը տեղափոխվել է՝",
"account.mute": "Լռեցնել @{name}֊ին",
"account.mute_notifications": "Անջատել ծանուցումները @{name}֊ից",
"account.muted": "Muted",
"account.posts": "Գրառումներ",
"account.posts_with_replies": "Toots with replies",
"account.report": "Բողոքել @{name}֊ից",
@@ -57,8 +60,10 @@
"compose_form.placeholder": "Ի՞նչ կա մտքիդ",
"compose_form.publish": "Թթել",
"compose_form.publish_loud": "Թթե՜լ",
"compose_form.sensitive": "Նշել բովանդակությունը որպես կասկածելի",
"compose_form.spoiler": "Թաքցնել տեքստը նախազգուշացման ետեւում",
"compose_form.sensitive.marked": "Media is marked as sensitive",
"compose_form.sensitive.unmarked": "Media is not marked as sensitive",
"compose_form.spoiler.marked": "Text is hidden behind warning",
"compose_form.spoiler.unmarked": "Text is not hidden",
"compose_form.spoiler_placeholder": "Գրիր նախազգուշացումդ այստեղ",
"confirmation_modal.cancel": "Չեղարկել",
"confirmations.block.confirm": "Արգելափակել",
@@ -216,6 +221,7 @@
"report.target": "Բողոքել {target}֊ի մասին",
"search.placeholder": "Փնտրել",
"search_popout.search_format": "Փնտրելու առաջադեմ ձեւ",
"search_popout.tips.full_text": "Simple text returns statuses you have written, favourited, boosted, or have been mentioned in, as well as matching usernames, display names, and hashtags.",
"search_popout.tips.hashtag": "պիտակ",
"search_popout.tips.status": "թութ",
"search_popout.tips.text": "Հասարակ տեքստը կվերադարձնի համընկնող անուններ, օգտանուններ ու պիտակներ",
@@ -238,6 +244,7 @@
"status.mute_conversation": "Լռեցնել խոսակցությունը",
"status.open": "Ընդարձակել այս թութը",
"status.pin": "Ամրացնել անձնական էջում",
"status.pinned": "Pinned toot",
"status.reblog": "Տարածել",
"status.reblogged_by": "{name} տարածել է",
"status.reply": "Պատասխանել",
@@ -247,10 +254,11 @@
"status.sensitive_warning": "Կասկածելի բովանդակություն",
"status.share": "Կիսվել",
"status.show_less": "Պակաս",
"status.show_less_all": "Show less for all",
"status.show_more": "Ավելին",
"status.show_more_all": "Show more for all",
"status.unmute_conversation": "Ապալռեցնել խոսակցությունը",
"status.unpin": "Հանել անձնական էջից",
"tabs_bar.compose": "Շարադրել",
"tabs_bar.federated_timeline": "Դաշնային",
"tabs_bar.home": "Հիմնական",
"tabs_bar.local_timeline": "Տեղական",

View File

@@ -1,128 +1,133 @@
{
"account.block": "Blokir @{name}",
"account.block_domain": "Hide everything from {domain}",
"account.disclaimer_full": "Information below may reflect the user's profile incompletely.",
"account.block_domain": "Sembunyikan segalanya dari {domain}",
"account.blocked": "Terblokir",
"account.disclaimer_full": "Informasi di bawah mungkin tidak mencerminkan profil user secara lengkap.",
"account.domain_blocked": "Domain disembunyikan",
"account.edit_profile": "Ubah profil",
"account.follow": "Ikuti",
"account.followers": "Pengikut",
"account.follows": "Mengikuti",
"account.follows_you": "Mengikuti anda",
"account.hide_reblogs": "Hide boosts from @{name}",
"account.hide_reblogs": "Sembunyikan boosts dari @{name}",
"account.media": "Media",
"account.mention": "Balasan @{name}",
"account.moved_to": "{name} has moved to:",
"account.moved_to": "{name} telah pindah ke:",
"account.mute": "Bisukan @{name}",
"account.mute_notifications": "Mute notifications from @{name}",
"account.posts": "Postingan",
"account.posts_with_replies": "Toots with replies",
"account.mute_notifications": "Sembunyikan notifikasi dari @{name}",
"account.muted": "Dibisukan",
"account.posts": "Toots",
"account.posts_with_replies": "Postingan dengan balasan",
"account.report": "Laporkan @{name}",
"account.requested": "Menunggu persetujuan",
"account.share": "Share @{name}'s profile",
"account.show_reblogs": "Show boosts from @{name}",
"account.requested": "Menunggu persetujuan. Klik untuk membatalkan permintaan",
"account.share": "Bagikan profil @{name}",
"account.show_reblogs": "Tampilkan boost dari @{name}",
"account.unblock": "Hapus blokir @{name}",
"account.unblock_domain": "Unhide {domain}",
"account.unblock_domain": "Tampilkan {domain}",
"account.unfollow": "Berhenti mengikuti",
"account.unmute": "Berhenti membisukan @{name}",
"account.unmute_notifications": "Unmute notifications from @{name}",
"account.view_full_profile": "View full profile",
"account.unmute_notifications": "Munculkan notifikasi dari @{name}",
"account.view_full_profile": "Lihat profil lengkap",
"boost_modal.combo": "Anda dapat menekan {combo} untuk melewati ini",
"bundle_column_error.body": "Something went wrong while loading this component.",
"bundle_column_error.retry": "Try again",
"bundle_column_error.body": "Kesalahan terjadi saat memuat komponen ini.",
"bundle_column_error.retry": "Coba lagi",
"bundle_column_error.title": "Network error",
"bundle_modal_error.close": "Close",
"bundle_modal_error.message": "Something went wrong while loading this component.",
"bundle_modal_error.retry": "Try again",
"bundle_modal_error.close": "Tutup",
"bundle_modal_error.message": "Kesalahan terjadi saat memuat komponen ini.",
"bundle_modal_error.retry": "Coba lagi",
"column.blocks": "Pengguna diblokir",
"column.community": "Linimasa Lokal",
"column.favourites": "Favorit",
"column.follow_requests": "Permintaan mengikuti",
"column.home": "Beranda",
"column.lists": "Lists",
"column.mutes": "Pengguna dibisukan",
"column.lists": "List",
"column.mutes": "Pengguna yang dibisukan",
"column.notifications": "Notifikasi",
"column.pins": "Pinned toot",
"column.public": "Linimasa gabunggan",
"column.public": "Linimasa gabungan",
"column_back_button.label": "Kembali",
"column_header.hide_settings": "Hide settings",
"column_header.moveLeft_settings": "Move column to the left",
"column_header.moveRight_settings": "Move column to the right",
"column_header.pin": "Pin",
"column_header.show_settings": "Show settings",
"column_header.unpin": "Unpin",
"column_header.hide_settings": "Sembunyikan pengaturan",
"column_header.moveLeft_settings": "Pindahkan kolom ke kiri",
"column_header.moveRight_settings": "Pindahkan kolom ke kanan",
"column_header.pin": "Sematkan",
"column_header.show_settings": "Tampilkan pengaturan",
"column_header.unpin": "Lepaskan",
"column_subheading.navigation": "Navigasi",
"column_subheading.settings": "Pengaturan",
"compose_form.hashtag_warning": "This toot won't be listed under any hashtag as it is unlisted. Only public toots can be searched by hashtag.",
"compose_form.hashtag_warning": "Toot ini tidak akan ada dalam daftar tagar manapun karena telah di set sebagai tidak terdaftar. Hanya postingan publik yang bisa dicari dengan tagar.",
"compose_form.lock_disclaimer": "Akun anda tidak {locked}. Semua orang dapat mengikuti anda untuk melihat postingan khusus untuk pengikut anda.",
"compose_form.lock_disclaimer.lock": "dikunci",
"compose_form.lock_disclaimer.lock": "terkunci",
"compose_form.placeholder": "Apa yang ada di pikiran anda?",
"compose_form.publish": "Toot",
"compose_form.publish_loud": "{publish}!",
"compose_form.sensitive": "Tandai media sensitif",
"compose_form.spoiler": "Sembunyikan teks dibalik peringatan",
"compose_form.sensitive.marked": "Sumber ini telah ditandai sebagai sumber sensitif.",
"compose_form.sensitive.unmarked": "Sumber ini tidak ditandai sebagai sumber sensitif",
"compose_form.spoiler.marked": "Teks disembunyikan dibalik peringatan",
"compose_form.spoiler.unmarked": "Teks tidak tersembunyi",
"compose_form.spoiler_placeholder": "Peringatan konten",
"confirmation_modal.cancel": "Batal",
"confirmations.block.confirm": "Blokir",
"confirmations.block.message": "Apa anda yakin ingin memblokir {name}?",
"confirmations.delete.confirm": "Hapus",
"confirmations.delete.message": "Apa anda yakin akan menghapus status ini?",
"confirmations.delete.message": "Apa anda yakin untuk menghapus status ini?",
"confirmations.delete_list.confirm": "Delete",
"confirmations.delete_list.message": "Are you sure you want to permanently delete this list?",
"confirmations.domain_block.confirm": "Hide entire domain",
"confirmations.domain_block.message": "Are you really, really sure you want to block the entire {domain}? In most cases a few targeted blocks or mutes are sufficient and preferable.",
"confirmations.delete_list.message": "Apakah anda yakin untuk menghapus daftar ini secara permanen?",
"confirmations.domain_block.confirm": "Sembunyikan keseluruhan domain",
"confirmations.domain_block.message": "Apakah anda benar benar yakin untuk memblokir keseluruhan {domain}? Dalam kasus tertentu beberapa pemblokiran atau penyembunyian lebih baik.",
"confirmations.mute.confirm": "Bisukan",
"confirmations.mute.message": "Apa anda yakin ingin membisukan {name}?",
"confirmations.unfollow.confirm": "Unfollow",
"confirmations.unfollow.message": "Are you sure you want to unfollow {name}?",
"embed.instructions": "Embed this status on your website by copying the code below.",
"embed.preview": "Here is what it will look like:",
"confirmations.unfollow.confirm": "Berhenti mengikuti",
"confirmations.unfollow.message": "Apakah anda ingin berhenti mengikuti {name}?",
"embed.instructions": "Sematkan status ini di website anda dengan menyalin kode di bawah ini.",
"embed.preview": "Seperti ini nantinya:",
"emoji_button.activity": "Aktivitas",
"emoji_button.custom": "Custom",
"emoji_button.custom": "Kustom",
"emoji_button.flags": "Bendera",
"emoji_button.food": "Makanan & Minuman",
"emoji_button.label": "Tambahkan emoji",
"emoji_button.nature": "Alam",
"emoji_button.not_found": "No emojos!! (╯°□°)╯︵ ┻━┻",
"emoji_button.not_found": "Katakan tidak pada emoji!! (╯°□°)╯︵ ┻━┻",
"emoji_button.objects": "Benda-benda",
"emoji_button.people": "Orang",
"emoji_button.recent": "Frequently used",
"emoji_button.recent": "Yang sering digunakan",
"emoji_button.search": "Cari...",
"emoji_button.search_results": "Search results",
"emoji_button.search_results": "Hasil pencarian",
"emoji_button.symbols": "Simbol",
"emoji_button.travel": "Tempat Wisata",
"empty_column.community": "Linimasa lokal masih kosong. Tulis sesuatu secara publik dan buat roda berputar!",
"empty_column.hashtag": "Tidak ada apapun dalam hashtag ini.",
"empty_column.home": "Anda sedang tidak mengikuti siapapun. Kunjungi {public} atau gunakan pencarian untuk memulai dan bertemu pengguna lain.",
"empty_column.home": "Linimasa anda kosong! Kunjungi {public} atau gunakan pencarian untuk memulai dan bertemu pengguna lain.",
"empty_column.home.public_timeline": "linimasa publik",
"empty_column.list": "There is nothing in this list yet.",
"empty_column.list": "Tidak ada postingan di list ini. Ketika anggota dari list ini memposting status baru, status tersebut akan tampil disini.",
"empty_column.notifications": "Anda tidak memiliki notifikasi apapun. Berinteraksi dengan orang lain untuk memulai percakapan.",
"empty_column.public": "Tidak ada apapun disini! Tulis sesuatu, atau ikuti pengguna lain dari server lain untuk mengisinya secara manual",
"empty_column.public": "Tidak ada apapun disini! Tulis sesuatu, atau ikuti pengguna lain dari server lain untuk mengisi ini",
"follow_request.authorize": "Izinkan",
"follow_request.reject": "Tolak",
"getting_started.appsshort": "Apps",
"getting_started.appsshort": "Aplikasi",
"getting_started.faq": "FAQ",
"getting_started.heading": "Mulai",
"getting_started.open_source_notice": "Mastodon adalah perangkat lunak yang bersifat open source. Anda dapat berkontribusi atau melaporkan permasalahan/bug di Github {github}.",
"getting_started.userguide": "User Guide",
"getting_started.open_source_notice": "Mastodon adalah perangkat lunak yang bersifat terbuka. Anda dapat berkontribusi atau melaporkan permasalahan/bug di Github {github}.",
"getting_started.userguide": "Panduan Pengguna",
"home.column_settings.advanced": "Tingkat Lanjut",
"home.column_settings.basic": "Dasar",
"home.column_settings.filter_regex": "Penyaringan dengan Regular Expression",
"home.column_settings.show_reblogs": "Tampilkan Boost",
"home.column_settings.filter_regex": "Saring dengan regular expressions",
"home.column_settings.show_reblogs": "Tampilkan boost",
"home.column_settings.show_replies": "Tampilkan balasan",
"home.settings": "Pengaturan kolom",
"keyboard_shortcuts.back": "to navigate back",
"keyboard_shortcuts.boost": "to boost",
"keyboard_shortcuts.column": "to focus a status in one of the columns",
"keyboard_shortcuts.compose": "to focus the compose textarea",
"keyboard_shortcuts.description": "Description",
"keyboard_shortcuts.down": "to move down in the list",
"keyboard_shortcuts.enter": "to open status",
"keyboard_shortcuts.favourite": "to favourite",
"keyboard_shortcuts.heading": "Keyboard Shortcuts",
"keyboard_shortcuts.back": "untuk kembali",
"keyboard_shortcuts.boost": "untuk menyebarkan",
"keyboard_shortcuts.column": "untuk fokus kepada sebuah status di sebuah kolom",
"keyboard_shortcuts.compose": "untuk fokus ke area penulisan",
"keyboard_shortcuts.description": "Deskripsi",
"keyboard_shortcuts.down": "untuk pindah ke bawah dalam sebuah daftar",
"keyboard_shortcuts.enter": "untuk membuka status",
"keyboard_shortcuts.favourite": "untuk memfavoritkan",
"keyboard_shortcuts.heading": "Pintasan keyboard",
"keyboard_shortcuts.hotkey": "Hotkey",
"keyboard_shortcuts.legend": "to display this legend",
"keyboard_shortcuts.mention": "to mention author",
"keyboard_shortcuts.reply": "to reply",
"keyboard_shortcuts.search": "to focus search",
"keyboard_shortcuts.search": "untuk fokus mencari",
"keyboard_shortcuts.toot": "to start a brand new toot",
"keyboard_shortcuts.unfocus": "to un-focus compose textarea/search",
"keyboard_shortcuts.up": "to move up in the list",
@@ -194,14 +199,14 @@
"privacy.change": "Tentukan privasi status",
"privacy.direct.long": "Kirim hanya ke pengguna yang disebut",
"privacy.direct.short": "Langsung",
"privacy.private.long": "Kirim hanya ke pengikut",
"privacy.private.long": "Kirim postingan hanya kepada pengikut",
"privacy.private.short": "Pribadi",
"privacy.public.long": "Kirim ke linimasa publik",
"privacy.public.short": "Publik",
"privacy.unlisted.long": "Tidak ditampilkan di linimasa publik",
"privacy.unlisted.short": "Tak Terdaftar",
"regeneration_indicator.label": "Loading…",
"regeneration_indicator.sublabel": "Your home feed is being prepared!",
"regeneration_indicator.sublabel": "Linimasa anda sedang disiapkan!",
"relative_time.days": "{number}d",
"relative_time.hours": "{number}h",
"relative_time.just_now": "now",
@@ -216,14 +221,15 @@
"report.target": "Melaporkan",
"search.placeholder": "Pencarian",
"search_popout.search_format": "Advanced search format",
"search_popout.tips.hashtag": "hashtag",
"search_popout.tips.full_text": "Simple text returns statuses you have written, favourited, boosted, or have been mentioned in, as well as matching usernames, display names, and hashtags.",
"search_popout.tips.hashtag": "tagar",
"search_popout.tips.status": "status",
"search_popout.tips.text": "Simple text returns matching display names, usernames and hashtags",
"search_popout.tips.user": "user",
"search_results.accounts": "People",
"search_results.hashtags": "Hashtags",
"search_results.statuses": "Toots",
"search_results.total": "{count} {count, plural, one {hasil} other {hasil}}",
"search_results.total": "{count, number} {count, plural, one {hasil} other {hasil}}",
"standalone.public_title": "A look inside...",
"status.block": "Block @{name}",
"status.cannot_reblog": "This post cannot be boosted",
@@ -238,6 +244,7 @@
"status.mute_conversation": "Mute conversation",
"status.open": "Tampilkan status ini",
"status.pin": "Pin on profile",
"status.pinned": "Pinned toot",
"status.reblog": "Boost",
"status.reblogged_by": "di-boost {name}",
"status.reply": "Balas",
@@ -247,24 +254,25 @@
"status.sensitive_warning": "Konten sensitif",
"status.share": "Share",
"status.show_less": "Tampilkan lebih sedikit",
"status.show_less_all": "Show less for all",
"status.show_more": "Tampilkan semua",
"status.show_more_all": "Show more for all",
"status.unmute_conversation": "Unmute conversation",
"status.unpin": "Unpin from profile",
"tabs_bar.compose": "Tulis",
"tabs_bar.federated_timeline": "Gabungan",
"tabs_bar.home": "Beranda",
"tabs_bar.local_timeline": "Lokal",
"tabs_bar.notifications": "Notifikasi",
"ui.beforeunload": "Your draft will be lost if you leave Mastodon.",
"ui.beforeunload": "Naskah anda akan hilang jika anda keluar dari Mastodon.",
"upload_area.title": "Seret & lepaskan untuk mengunggah",
"upload_button.label": "Tambahkan media",
"upload_form.description": "Describe for the visually impaired",
"upload_form.focus": "Crop",
"upload_form.description": "Deskripsikan untuk mereka yang tidak bisa melihat dengan jelas",
"upload_form.focus": "Potong",
"upload_form.undo": "Undo",
"upload_progress.label": "Mengunggah...",
"video.close": "Close video",
"video.exit_fullscreen": "Exit full screen",
"video.expand": "Expand video",
"video.exit_fullscreen": "Keluar dari layar penuh",
"video.expand": "Perbesar video",
"video.fullscreen": "Full screen",
"video.hide": "Hide video",
"video.mute": "Mute sound",

View File

@@ -1,7 +1,9 @@
{
"account.block": "Blokusar @{name}",
"account.block_domain": "Hide everything from {domain}",
"account.blocked": "Blocked",
"account.disclaimer_full": "Information below may reflect the user's profile incompletely.",
"account.domain_blocked": "Domain hidden",
"account.edit_profile": "Modifikar profilo",
"account.follow": "Sequar",
"account.followers": "Sequanti",
@@ -13,6 +15,7 @@
"account.moved_to": "{name} has moved to:",
"account.mute": "Celar @{name}",
"account.mute_notifications": "Mute notifications from @{name}",
"account.muted": "Muted",
"account.posts": "Mesaji",
"account.posts_with_replies": "Toots with replies",
"account.report": "Denuncar @{name}",
@@ -57,8 +60,10 @@
"compose_form.placeholder": "Quo esas en tua spirito?",
"compose_form.publish": "Siflar",
"compose_form.publish_loud": "{publish}!",
"compose_form.sensitive": "Markizar kontenajo kom trubliva",
"compose_form.spoiler": "Celar texto dop averto",
"compose_form.sensitive.marked": "Media is marked as sensitive",
"compose_form.sensitive.unmarked": "Media is not marked as sensitive",
"compose_form.spoiler.marked": "Text is hidden behind warning",
"compose_form.spoiler.unmarked": "Text is not hidden",
"compose_form.spoiler_placeholder": "Averto di kontenajo",
"confirmation_modal.cancel": "Cancel",
"confirmations.block.confirm": "Block",
@@ -216,6 +221,7 @@
"report.target": "Denuncante",
"search.placeholder": "Serchez",
"search_popout.search_format": "Advanced search format",
"search_popout.tips.full_text": "Simple text returns statuses you have written, favourited, boosted, or have been mentioned in, as well as matching usernames, display names, and hashtags.",
"search_popout.tips.hashtag": "hashtag",
"search_popout.tips.status": "status",
"search_popout.tips.text": "Simple text returns matching display names, usernames and hashtags",
@@ -238,6 +244,7 @@
"status.mute_conversation": "Mute conversation",
"status.open": "Detaligar ca mesajo",
"status.pin": "Pin on profile",
"status.pinned": "Pinned toot",
"status.reblog": "Repetar",
"status.reblogged_by": "{name} repetita",
"status.reply": "Respondar",
@@ -247,10 +254,11 @@
"status.sensitive_warning": "Trubliva kontenajo",
"status.share": "Share",
"status.show_less": "Montrar mine",
"status.show_less_all": "Show less for all",
"status.show_more": "Montrar plue",
"status.show_more_all": "Show more for all",
"status.unmute_conversation": "Unmute conversation",
"status.unpin": "Unpin from profile",
"tabs_bar.compose": "Kompozar",
"tabs_bar.federated_timeline": "Federata",
"tabs_bar.home": "Hemo",
"tabs_bar.local_timeline": "Lokala",

View File

@@ -1,7 +1,9 @@
{
"account.block": "Blocca @{name}",
"account.block_domain": "Hide everything from {domain}",
"account.blocked": "Blocked",
"account.disclaimer_full": "Information below may reflect the user's profile incompletely.",
"account.domain_blocked": "Domain hidden",
"account.edit_profile": "Modifica profilo",
"account.follow": "Segui",
"account.followers": "Seguaci",
@@ -13,6 +15,7 @@
"account.moved_to": "{name} has moved to:",
"account.mute": "Silenzia @{name}",
"account.mute_notifications": "Mute notifications from @{name}",
"account.muted": "Muted",
"account.posts": "Posts",
"account.posts_with_replies": "Toots with replies",
"account.report": "Segnala @{name}",
@@ -57,8 +60,10 @@
"compose_form.placeholder": "A cosa stai pensando?",
"compose_form.publish": "Toot",
"compose_form.publish_loud": "{publish}!",
"compose_form.sensitive": "Segnala file come sensibile",
"compose_form.spoiler": "Nascondi testo con avvertimento",
"compose_form.sensitive.marked": "Media is marked as sensitive",
"compose_form.sensitive.unmarked": "Media is not marked as sensitive",
"compose_form.spoiler.marked": "Text is hidden behind warning",
"compose_form.spoiler.unmarked": "Text is not hidden",
"compose_form.spoiler_placeholder": "Content warning",
"confirmation_modal.cancel": "Cancel",
"confirmations.block.confirm": "Block",
@@ -216,6 +221,7 @@
"report.target": "Invio la segnalazione",
"search.placeholder": "Cerca",
"search_popout.search_format": "Advanced search format",
"search_popout.tips.full_text": "Simple text returns statuses you have written, favourited, boosted, or have been mentioned in, as well as matching usernames, display names, and hashtags.",
"search_popout.tips.hashtag": "hashtag",
"search_popout.tips.status": "status",
"search_popout.tips.text": "Simple text returns matching display names, usernames and hashtags",
@@ -238,6 +244,7 @@
"status.mute_conversation": "Mute conversation",
"status.open": "Espandi questo post",
"status.pin": "Pin on profile",
"status.pinned": "Pinned toot",
"status.reblog": "Condividi",
"status.reblogged_by": "{name} ha condiviso",
"status.reply": "Rispondi",
@@ -247,10 +254,11 @@
"status.sensitive_warning": "Materiale sensibile",
"status.share": "Share",
"status.show_less": "Mostra meno",
"status.show_less_all": "Show less for all",
"status.show_more": "Mostra di più",
"status.show_more_all": "Show more for all",
"status.unmute_conversation": "Unmute conversation",
"status.unpin": "Unpin from profile",
"tabs_bar.compose": "Scrivi",
"tabs_bar.federated_timeline": "Federazione",
"tabs_bar.home": "Home",
"tabs_bar.local_timeline": "Locale",

View File

@@ -1,7 +1,9 @@
{
"account.block": "@{name}さんをブロック",
"account.block_domain": "{domain}全体を非表示",
"account.blocked": "ブロック済み",
"account.disclaimer_full": "以下の情報は不正確な可能性があります。",
"account.domain_blocked": "ドメイン非表示中",
"account.edit_profile": "プロフィールを編集",
"account.follow": "フォロー",
"account.followers": "フォロワー",
@@ -13,8 +15,9 @@
"account.moved_to": "{name}さんは引っ越しました:",
"account.mute": "@{name}さんをミュート",
"account.mute_notifications": "@{name}さんからの通知を受け取らない",
"account.muted": "ミュート済み",
"account.posts": "投稿",
"account.posts_with_replies": "トゥートと返信",
"account.posts_with_replies": "投稿と返信",
"account.report": "@{name}さんを通報",
"account.requested": "フォロー承認待ちです。クリックしてキャンセル",
"account.share": "@{name}さんのプロフィールを共有する",
@@ -57,8 +60,10 @@
"compose_form.placeholder": "今なにしてる?",
"compose_form.publish": "トゥート",
"compose_form.publish_loud": "{publish}",
"compose_form.sensitive": "メディア閲覧注意としてマークする",
"compose_form.spoiler": "テキストを隠す",
"compose_form.sensitive.marked": "メディア閲覧注意が設定されています",
"compose_form.sensitive.unmarked": "メディアに閲覧注意が設定されていません",
"compose_form.spoiler.marked": "閲覧注意が設定されています",
"compose_form.spoiler.unmarked": "閲覧注意が設定されていません",
"compose_form.spoiler_placeholder": "ここに警告を書いてください",
"confirmation_modal.cancel": "キャンセル",
"confirmations.block.confirm": "ブロック",
@@ -216,6 +221,7 @@
"report.target": "{target}さんを通報する",
"search.placeholder": "検索",
"search_popout.search_format": "高度な検索フォーマット",
"search_popout.tips.full_text": "表示名やユーザー名、ハッシュタグのほか、あなたのトゥートやお気に入り、ブーストしたトゥート、返信に一致する単純なテキスト。",
"search_popout.tips.hashtag": "ハッシュタグ",
"search_popout.tips.status": "トゥート",
"search_popout.tips.text": "表示名やユーザー名、ハッシュタグに一致する単純なテキスト",
@@ -238,8 +244,9 @@
"status.mute_conversation": "会話をミュート",
"status.open": "詳細を表示",
"status.pin": "プロフィールに固定表示",
"status.pinned": "固定されたトゥート",
"status.reblog": "ブースト",
"status.reblogged_by": "{name}さんブーストされました",
"status.reblogged_by": "{name}さんブースト",
"status.reply": "返信",
"status.replyAll": "全員に返信",
"status.report": "@{name}さんを通報",
@@ -247,15 +254,16 @@
"status.sensitive_warning": "閲覧注意",
"status.share": "共有",
"status.show_less": "隠す",
"status.show_less_all": "全て隠す",
"status.show_more": "もっと見る",
"status.show_more_all": "全て見る",
"status.unmute_conversation": "会話のミュートを解除",
"status.unpin": "プロフィールの固定表示を解除",
"tabs_bar.compose": "投稿",
"tabs_bar.federated_timeline": "連合",
"tabs_bar.home": "ホーム",
"tabs_bar.local_timeline": "ローカル",
"tabs_bar.notifications": "通知",
"ui.beforeunload": "Mastodonから離れるとあなたのドラフトは失われます。",
"ui.beforeunload": "Mastodonから離れると送信前の投稿は失われます。",
"upload_area.title": "ドラッグ&ドロップでアップロード",
"upload_button.label": "メディアを追加",
"upload_form.description": "視覚障害者のための説明",

View File

@@ -1,7 +1,9 @@
{
"account.block": "@{name}을 차단",
"account.block_domain": "{domain} 전체를 숨김",
"account.blocked": "차단 됨",
"account.disclaimer_full": "여기 있는 정보는 유저의 프로파일을 정확히 반영하지 못 할 수도 있습니다.",
"account.domain_blocked": "도메인 숨겨짐",
"account.edit_profile": "프로필 편집",
"account.follow": "팔로우",
"account.followers": "팔로워",
@@ -13,8 +15,9 @@
"account.moved_to": "{name}는 계정을 이동했습니다:",
"account.mute": "@{name} 뮤트",
"account.mute_notifications": "@{name}의 알림을 뮤트",
"account.muted": "뮤트 됨",
"account.posts": "게시물",
"account.posts_with_replies": "Toots with replies",
"account.posts_with_replies": "툿과 답장",
"account.report": "@{name} 신고",
"account.requested": "승인 대기 중. 클릭해서 취소하기",
"account.share": "@{name}의 프로파일 공유",
@@ -57,8 +60,10 @@
"compose_form.placeholder": "지금 무엇을 하고 있나요?",
"compose_form.publish": "툿",
"compose_form.publish_loud": "{publish}!",
"compose_form.sensitive": "이 미디어를 민감한 미디어로 취급",
"compose_form.spoiler": "텍스트 숨기기",
"compose_form.sensitive.marked": "Media is marked as sensitive",
"compose_form.sensitive.unmarked": "Media is not marked as sensitive",
"compose_form.spoiler.marked": "Text is hidden behind warning",
"compose_form.spoiler.unmarked": "Text is not hidden",
"compose_form.spoiler_placeholder": "경고",
"confirmation_modal.cancel": "취소",
"confirmations.block.confirm": "차단",
@@ -208,21 +213,22 @@
"relative_time.minutes": "{number}분 전",
"relative_time.seconds": "{number}초 전",
"reply_indicator.cancel": "취소",
"report.forward": "Forward to {target}",
"report.forward_hint": "The account is from another server. Send an anonymized copy of the report there as well?",
"report.hint": "The report will be sent to your instance moderators. You can provide an explanation of why you are reporting this account below:",
"report.forward": "{target}에 포워드 됨",
"report.forward_hint": "이 계정은 다른 서버에 있습니다. 익명화 된 사본을 해당 서버에도 전송할까요?",
"report.hint": "신고는 당신의 서버 스태프에게 전송 됩니다. 왜 이 계정을 신고하는 지에 대한 설명을 아래에 작성할 수 있습니다:",
"report.placeholder": "코멘트",
"report.submit": "신고하기",
"report.target": "문제가 된 사용자",
"search.placeholder": "검색",
"search_popout.search_format": "고급 검색 방법",
"search_popout.tips.full_text": "단순한 텍스트 검색은 당신이 작성했거나, 관심글로 지정했거나, 부스트했거나, 멘션을 받은 게시글, 그리고 유저네임, 디스플레이네임, 해시태그를 반환합니다.",
"search_popout.tips.hashtag": "해시태그",
"search_popout.tips.status": "툿",
"search_popout.tips.text": "단순한 텍스트 검색은 관계된 프로필 이름, 유저 이름 그리고 해시태그를 표시합니다",
"search_popout.tips.user": "유저",
"search_results.accounts": "People",
"search_results.hashtags": "Hashtags",
"search_results.statuses": "Toots",
"search_results.accounts": "사람",
"search_results.hashtags": "해시태그",
"search_results.statuses": "",
"search_results.total": "{count, number}건의 결과",
"standalone.public_title": "지금 이런 이야기를 하고 있습니다…",
"status.block": "@{name} 차단",
@@ -238,6 +244,7 @@
"status.mute_conversation": "이 대화를 뮤트",
"status.open": "상세 정보 표시",
"status.pin": "고정",
"status.pinned": "고정 된 툿",
"status.reblog": "부스트",
"status.reblogged_by": "{name}님이 부스트 했습니다",
"status.reply": "답장",
@@ -247,10 +254,11 @@
"status.sensitive_warning": "민감한 미디어",
"status.share": "공유",
"status.show_less": "숨기기",
"status.show_less_all": "Show less for all",
"status.show_more": "더 보기",
"status.show_more_all": "Show more for all",
"status.unmute_conversation": "이 대화의 뮤트 해제하기",
"status.unpin": "고정 해제",
"tabs_bar.compose": "포스트",
"tabs_bar.federated_timeline": "연합",
"tabs_bar.home": "홈",
"tabs_bar.local_timeline": "로컬",
@@ -259,7 +267,7 @@
"upload_area.title": "드래그 & 드롭으로 업로드",
"upload_button.label": "미디어 추가",
"upload_form.description": "시각장애인을 위한 설명",
"upload_form.focus": "Crop",
"upload_form.focus": "크롭",
"upload_form.undo": "재시도",
"upload_progress.label": "업로드 중...",
"video.close": "동영상 닫기",

View File

@@ -1,7 +1,9 @@
{
"account.block": "Blokkeer @{name}",
"account.block_domain": "Negeer alles van {domain}",
"account.blocked": "Geblokkeerd",
"account.disclaimer_full": "De informatie hieronder kan mogelijk een incompleet beeld geven van dit gebruikersprofiel.",
"account.domain_blocked": "Domein verborgen",
"account.edit_profile": "Profiel bewerken",
"account.follow": "Volgen",
"account.followers": "Volgers",
@@ -13,8 +15,9 @@
"account.moved_to": "{name} is verhuisd naar:",
"account.mute": "Negeer @{name}",
"account.mute_notifications": "Negeer meldingen van @{name}",
"account.muted": "Genegeerd",
"account.posts": "Toots",
"account.posts_with_replies": "Toots with replies",
"account.posts_with_replies": "Toots met reacties",
"account.report": "Rapporteer @{name}",
"account.requested": "Wacht op goedkeuring. Klik om het volgverzoek te annuleren",
"account.share": "Profiel van @{name} delen",
@@ -57,8 +60,10 @@
"compose_form.placeholder": "Wat wil je kwijt?",
"compose_form.publish": "Toot",
"compose_form.publish_loud": "{publish}!",
"compose_form.sensitive": "Media als gevoelig markeren (nsfw)",
"compose_form.spoiler": "Tekst achter waarschuwing verbergen",
"compose_form.sensitive.marked": "Media is als gevoelig gemarkeerd",
"compose_form.sensitive.unmarked": "Media is niet als gevoelig gemarkeerd",
"compose_form.spoiler.marked": "Tekst is achter een waarschuwing verborgen",
"compose_form.spoiler.unmarked": "Tekst is niet verborgen",
"compose_form.spoiler_placeholder": "Waarschuwingstekst",
"confirmation_modal.cancel": "Annuleren",
"confirmations.block.confirm": "Blokkeren",
@@ -208,19 +213,20 @@
"relative_time.minutes": "{number}m",
"relative_time.seconds": "{number}s",
"reply_indicator.cancel": "Annuleren",
"report.forward": "Forward to {target}",
"report.forward_hint": "The account is from another server. Send an anonymized copy of the report there as well?",
"report.hint": "The report will be sent to your instance moderators. You can provide an explanation of why you are reporting this account below:",
"report.forward": "Doorsturen naar {target}",
"report.forward_hint": "Het account bevindt zich op een andere server. Stuur daar eveneens een geanonimiseerde kopie van de gerapporteerde toot(s) naartoe?",
"report.hint": "De gerapporteerde toot(s) worden naar de moderatoren van jouw server gestuurd. Je kunt hieronder een uitleg geven waarom je dit account rapporteert:",
"report.placeholder": "Extra opmerkingen",
"report.submit": "Verzenden",
"report.target": "Rapporteer {target}",
"search.placeholder": "Zoeken",
"search_popout.search_format": "Geavanceerd zoeken",
"search_popout.tips.full_text": "Gebruik gewone tekst om te zoeken naar jouw toots, gebooste toots, favorieten en naar toots waarin jij bent vermeldt, en naar gebruikersnamen, weergavenamen en hashtags.",
"search_popout.tips.hashtag": "hashtag",
"search_popout.tips.status": "toot",
"search_popout.tips.text": "Gebruik gewone tekst om te zoeken op weergavenamen, gebruikersnamen en hashtags",
"search_popout.tips.user": "gebruiker",
"search_results.accounts": "People",
"search_results.accounts": "Gebruikers",
"search_results.hashtags": "Hashtags",
"search_results.statuses": "Toots",
"search_results.total": "{count, number} {count, plural, one {resultaat} other {resultaten}}",
@@ -238,6 +244,7 @@
"status.mute_conversation": "Negeer conversatie",
"status.open": "Toot volledig tonen",
"status.pin": "Aan profielpagina vastmaken",
"status.pinned": "Vastgemaakte toot",
"status.reblog": "Boost",
"status.reblogged_by": "{name} boostte",
"status.reply": "Reageren",
@@ -247,10 +254,11 @@
"status.sensitive_warning": "Gevoelige inhoud",
"status.share": "Delen",
"status.show_less": "Minder tonen",
"status.show_less_all": "Alles minder tonen",
"status.show_more": "Meer tonen",
"status.show_more_all": "Alles meer tonen",
"status.unmute_conversation": "Conversatie niet meer negeren",
"status.unpin": "Van profielpagina losmaken",
"tabs_bar.compose": "Schrijven",
"tabs_bar.federated_timeline": "Globaal",
"tabs_bar.home": "Start",
"tabs_bar.local_timeline": "Lokaal",
@@ -259,7 +267,7 @@
"upload_area.title": "Hierin slepen om te uploaden",
"upload_button.label": "Media toevoegen",
"upload_form.description": "Omschrijf dit voor mensen met een visuele beperking",
"upload_form.focus": "Crop",
"upload_form.focus": "Bijsnijden",
"upload_form.undo": "Ongedaan maken",
"upload_progress.label": "Uploaden...",
"video.close": "Video sluiten",

View File

@@ -1,7 +1,9 @@
{
"account.block": "Blokkér @{name}",
"account.block_domain": "Skjul alt fra {domain}",
"account.blocked": "Blocked",
"account.disclaimer_full": "Informasjonen nedenfor kan gi et ufullstendig bilde av brukerens profil.",
"account.domain_blocked": "Domain hidden",
"account.edit_profile": "Rediger profil",
"account.follow": "Følg",
"account.followers": "Følgere",
@@ -13,6 +15,7 @@
"account.moved_to": "{name} har flyttet til:",
"account.mute": "Demp @{name}",
"account.mute_notifications": "Ignorer varsler fra @{name}",
"account.muted": "Muted",
"account.posts": "Innlegg",
"account.posts_with_replies": "Toots with replies",
"account.report": "Rapportér @{name}",
@@ -57,8 +60,10 @@
"compose_form.placeholder": "Hva har du på hjertet?",
"compose_form.publish": "Tut",
"compose_form.publish_loud": "{publish}!",
"compose_form.sensitive": "Merk media som følsomt",
"compose_form.spoiler": "Skjul tekst bak advarsel",
"compose_form.sensitive.marked": "Media is marked as sensitive",
"compose_form.sensitive.unmarked": "Media is not marked as sensitive",
"compose_form.spoiler.marked": "Text is hidden behind warning",
"compose_form.spoiler.unmarked": "Text is not hidden",
"compose_form.spoiler_placeholder": "Innholdsadvarsel",
"confirmation_modal.cancel": "Avbryt",
"confirmations.block.confirm": "Blokkèr",
@@ -216,6 +221,7 @@
"report.target": "Rapporterer",
"search.placeholder": "Søk",
"search_popout.search_format": "Avansert søkeformat",
"search_popout.tips.full_text": "Simple text returns statuses you have written, favourited, boosted, or have been mentioned in, as well as matching usernames, display names, and hashtags.",
"search_popout.tips.hashtag": "emneknagg",
"search_popout.tips.status": "status",
"search_popout.tips.text": "Enkel tekst returnerer matchende visningsnavn, brukernavn og emneknagger",
@@ -238,6 +244,7 @@
"status.mute_conversation": "Demp samtale",
"status.open": "Utvid denne statusen",
"status.pin": "Fest på profilen",
"status.pinned": "Pinned toot",
"status.reblog": "Fremhev",
"status.reblogged_by": "Fremhevd av {name}",
"status.reply": "Svar",
@@ -247,10 +254,11 @@
"status.sensitive_warning": "Følsomt innhold",
"status.share": "Del",
"status.show_less": "Vis mindre",
"status.show_less_all": "Show less for all",
"status.show_more": "Vis mer",
"status.show_more_all": "Show more for all",
"status.unmute_conversation": "Ikke demp samtale",
"status.unpin": "Angre festing på profilen",
"tabs_bar.compose": "Komponer",
"tabs_bar.federated_timeline": "Felles",
"tabs_bar.home": "Hjem",
"tabs_bar.local_timeline": "Lokal",

View File

@@ -1,7 +1,9 @@
{
"account.block": "Blocar @{name}",
"account.block_domain": "Tot amagar del domeni {domain}",
"account.blocked": "Blocked",
"account.disclaimer_full": "Aquelas informacions de perfil pòdon èsser incomplètas.",
"account.domain_blocked": "Domain hidden",
"account.edit_profile": "Modificar lo perfil",
"account.follow": "Sègre",
"account.followers": "Seguidors",
@@ -13,6 +15,7 @@
"account.moved_to": "{name} a mudat los catons a:",
"account.mute": "Rescondre @{name}",
"account.mute_notifications": "Rescondre las notificacions de @{name}",
"account.muted": "Muted",
"account.posts": "Estatuts",
"account.posts_with_replies": "Toots with replies",
"account.report": "Senhalar @{name}",
@@ -57,8 +60,10 @@
"compose_form.placeholder": "A de qué pensatz?",
"compose_form.publish": "Tut",
"compose_form.publish_loud": "{publish}!",
"compose_form.sensitive": "Marcar lo mèdia coma sensible",
"compose_form.spoiler": "Rescondre lo tèxte darrièr un avertiment",
"compose_form.sensitive.marked": "Media is marked as sensitive",
"compose_form.sensitive.unmarked": "Media is not marked as sensitive",
"compose_form.spoiler.marked": "Text is hidden behind warning",
"compose_form.spoiler.unmarked": "Text is not hidden",
"compose_form.spoiler_placeholder": "Escrivètz lavertiment aquí",
"confirmation_modal.cancel": "Anullar",
"confirmations.block.confirm": "Blocar",
@@ -216,6 +221,7 @@
"report.target": "Senhalar {target}",
"search.placeholder": "Recercar",
"search_popout.search_format": "Format recèrca avançada",
"search_popout.tips.full_text": "Simple text returns statuses you have written, favourited, boosted, or have been mentioned in, as well as matching usernames, display names, and hashtags.",
"search_popout.tips.hashtag": "etiqueta",
"search_popout.tips.status": "estatut",
"search_popout.tips.text": "Lo tèxt brut tòrna escais, noms dutilizaire e etiquetas correspondents",
@@ -238,6 +244,7 @@
"status.mute_conversation": "Rescondre la conversacion",
"status.open": "Desplegar aqueste estatut",
"status.pin": "Penjar al perfil",
"status.pinned": "Pinned toot",
"status.reblog": "Partejar",
"status.reblogged_by": "{name} a partejat",
"status.reply": "Respondre",
@@ -247,10 +254,11 @@
"status.sensitive_warning": "Contengut sensible",
"status.share": "Partejar",
"status.show_less": "Tornar plegar",
"status.show_less_all": "Show less for all",
"status.show_more": "Desplegar",
"status.show_more_all": "Show more for all",
"status.unmute_conversation": "Tornar mostrar la conversacion",
"status.unpin": "Tirar del perfil",
"tabs_bar.compose": "Compausar",
"tabs_bar.federated_timeline": "Flux public global",
"tabs_bar.home": "Acuèlh",
"tabs_bar.local_timeline": "Flux public local",

View File

@@ -1,7 +1,9 @@
{
"account.block": "Blokuj @{name}",
"account.block_domain": "Blokuj wszystko z {domain}",
"account.blocked": "Zablokowany",
"account.disclaimer_full": "Poniższe informacje mogą nie odwzorowywać bezbłędnie profilu użytkownika.",
"account.domain_blocked": "Ukryto domenę",
"account.edit_profile": "Edytuj profil",
"account.follow": "Śledź",
"account.followers": "Śledzący",
@@ -13,6 +15,7 @@
"account.moved_to": "{name} przeniósł się do:",
"account.mute": "Wycisz @{name}",
"account.mute_notifications": "Wycisz powiadomienia o @{name}",
"account.muted": "Wyciszony",
"account.posts": "Wpisy",
"account.posts_with_replies": "Wpisy z odpowiedziami",
"account.report": "Zgłoś @{name}",
@@ -57,8 +60,10 @@
"compose_form.placeholder": "Co Ci chodzi po głowie?",
"compose_form.publish": "Wyślij",
"compose_form.publish_loud": "{publish}!",
"compose_form.sensitive": "Oznacz treści jako wrażliwe",
"compose_form.spoiler": "Ukryj tekst za ostrzeżeniem",
"compose_form.sensitive.marked": "Zawartość multimedia jest oznaczona jako wrażliwa",
"compose_form.sensitive.unmarked": "Zawartość multimedialna nie jest oznaczona jako wrażliwa",
"compose_form.spoiler.marked": "Tekst jest ukryty za ostrzeżeniem",
"compose_form.spoiler.unmarked": "Tekst nie jest ukryty",
"compose_form.spoiler_placeholder": "Wprowadź swoje ostrzeżenie o zawartości",
"confirmation_modal.cancel": "Anuluj",
"confirmations.block.confirm": "Zablokuj",
@@ -216,6 +221,7 @@
"report.target": "Zgłaszanie {target}",
"search.placeholder": "Szukaj",
"search_popout.search_format": "Zaawansowane wyszukiwanie",
"search_popout.tips.full_text": "Pozwala na wyszukiwanie wpisów które napisałeś, dodałeś do ulubionych, podbiłeś w których o Tobie wspomniano, oraz pasujące nazwy użytkowników, pełne nazwy i hashtagi.",
"search_popout.tips.hashtag": "hashtag",
"search_popout.tips.status": "wpis",
"search_popout.tips.text": "Proste wyszukiwanie pasujących pseudonimów, nazw użytkowników i hashtagów",
@@ -238,6 +244,7 @@
"status.mute_conversation": "Wycisz konwersację",
"status.open": "Rozszerz ten wpis",
"status.pin": "Przypnij do profilu",
"status.pinned": "Przypięty wpis",
"status.reblog": "Podbij",
"status.reblogged_by": "{name} podbił",
"status.reply": "Odpowiedz",
@@ -246,11 +253,12 @@
"status.sensitive_toggle": "Naciśnij aby wyświetlić",
"status.sensitive_warning": "Wrażliwa zawartość",
"status.share": "Udostępnij",
"status.show_less": "Pokaż mniej",
"status.show_more": "Pokaż więcej",
"status.show_less": "Zwiń",
"status.show_less_all": "Zwiń wszystkie",
"status.show_more": "Rozwiń",
"status.show_more_all": "Rozwiń wszystkie",
"status.unmute_conversation": "Cofnij wyciszenie konwersacji",
"status.unpin": "Odepnij z profilu",
"tabs_bar.compose": "Napisz",
"tabs_bar.federated_timeline": "Globalne",
"tabs_bar.home": "Strona główna",
"tabs_bar.local_timeline": "Lokalne",
@@ -259,7 +267,7 @@
"upload_area.title": "Przeciągnij i upuść aby wysłać",
"upload_button.label": "Dodaj zawartość multimedialną",
"upload_form.description": "Wprowadź opis dla niewidomych i niedowidzących",
"upload_form.focus": "Crop",
"upload_form.focus": "Przytnij",
"upload_form.undo": "Cofnij",
"upload_progress.label": "Wysyłanie",
"video.close": "Zamknij film",

View File

@@ -1,7 +1,9 @@
{
"account.block": "Bloquear @{name}",
"account.block_domain": "Esconder tudo de {domain}",
"account.blocked": "Bloqueado",
"account.disclaimer_full": "As informações abaixo podem refletir o perfil do usuário de maneira incompleta.",
"account.domain_blocked": "Domínio escondido",
"account.edit_profile": "Editar perfil",
"account.follow": "Seguir",
"account.followers": "Seguidores",
@@ -13,8 +15,9 @@
"account.moved_to": "{name} se mudou para:",
"account.mute": "Silenciar @{name}",
"account.mute_notifications": "Silenciar notificações de @{name}",
"account.posts": "Posts",
"account.posts_with_replies": "Toots with replies",
"account.muted": "Silenciado",
"account.posts": "Toots",
"account.posts_with_replies": "Toots e respostas",
"account.report": "Denunciar @{name}",
"account.requested": "Aguardando aprovação. Clique para cancelar a solicitação",
"account.share": "Compartilhar perfil de @{name}",
@@ -57,8 +60,10 @@
"compose_form.placeholder": "No que você está pensando?",
"compose_form.publish": "Publicar",
"compose_form.publish_loud": "{publish}!",
"compose_form.sensitive": "Marcar mídia como conteúdo sensível",
"compose_form.spoiler": "Esconder texto com aviso de conteúdo",
"compose_form.sensitive.marked": "Mídia está marcada como sensível",
"compose_form.sensitive.unmarked": "Mídia não está marcada como sensível",
"compose_form.spoiler.marked": "O texto está escondido por um aviso de conteúdo",
"compose_form.spoiler.unmarked": "O texto não está escondido",
"compose_form.spoiler_placeholder": "Aviso de conteúdo",
"confirmation_modal.cancel": "Cancelar",
"confirmations.block.confirm": "Bloquear",
@@ -208,19 +213,20 @@
"relative_time.minutes": "{number}m",
"relative_time.seconds": "{number}s",
"reply_indicator.cancel": "Cancelar",
"report.forward": "Forward to {target}",
"report.forward_hint": "The account is from another server. Send an anonymized copy of the report there as well?",
"report.hint": "The report will be sent to your instance moderators. You can provide an explanation of why you are reporting this account below:",
"report.forward": "Encaminhar para {target}",
"report.forward_hint": "Essa conta pertence à um outro servidor. Encaminhar uma cópia da denúncia com seus dados tornados anônimos para esse servidor?",
"report.hint": "A sua denúncia será enviada aos moderadores da instância. Você pode adicionar uma explicação de porque você está denunciando essa conta abaixo:",
"report.placeholder": "Comentários adicionais",
"report.submit": "Enviar",
"report.target": "Denunciar",
"search.placeholder": "Pesquisar",
"search_popout.search_format": "Formato de busca avançado",
"search_popout.tips.full_text": "Texto simples retorna status que você escreveu, favoritou, compartilhou ou em que tenha sido mencionado; também retorna nomes de exibição, usuários e hashtags correspondentes.",
"search_popout.tips.hashtag": "hashtag",
"search_popout.tips.status": "status",
"search_popout.tips.text": "Texto simples retorna nomes de exibição, usuários e hashtags correspondentes",
"search_popout.tips.user": "usuário",
"search_results.accounts": "People",
"search_results.accounts": "Pessoas",
"search_results.hashtags": "Hashtags",
"search_results.statuses": "Toots",
"search_results.total": "{count, number} {count, plural, one {resultado} other {resultados}}",
@@ -238,6 +244,7 @@
"status.mute_conversation": "Silenciar conversa",
"status.open": "Expandir",
"status.pin": "Fixar no perfil",
"status.pinned": "Toot fixado",
"status.reblog": "Compartilhar",
"status.reblogged_by": "{name} compartilhou",
"status.reply": "Responder",
@@ -247,10 +254,11 @@
"status.sensitive_warning": "Conteúdo sensível",
"status.share": "Compartilhar",
"status.show_less": "Mostrar menos",
"status.show_less_all": "Mostrar menos para todas as mensagens",
"status.show_more": "Mostrar mais",
"status.show_more_all": "Mostrar mais para todas as mensagens",
"status.unmute_conversation": "Desativar silêncio desta conversa",
"status.unpin": "Desafixar do perfil",
"tabs_bar.compose": "Criar",
"tabs_bar.federated_timeline": "Global",
"tabs_bar.home": "Página inicial",
"tabs_bar.local_timeline": "Local",
@@ -259,7 +267,7 @@
"upload_area.title": "Arraste e solte para enviar",
"upload_button.label": "Adicionar mídia",
"upload_form.description": "Descreva a imagem para deficientes visuais",
"upload_form.focus": "Crop",
"upload_form.focus": "Recortar",
"upload_form.undo": "Desfazer",
"upload_progress.label": "Salvando...",
"video.close": "Fechar vídeo",

View File

@@ -1,7 +1,9 @@
{
"account.block": "Bloquear @{name}",
"account.block_domain": "Esconder tudo do domínio {domain}",
"account.blocked": "Blocked",
"account.disclaimer_full": "As informações abaixo podem refletir o perfil do usuário de forma incompleta.",
"account.domain_blocked": "Domain hidden",
"account.edit_profile": "Editar perfil",
"account.follow": "Seguir",
"account.followers": "Seguidores",
@@ -13,6 +15,7 @@
"account.moved_to": "{name} mudou a sua conta para:",
"account.mute": "Silenciar @{name}",
"account.mute_notifications": "Silenciar notificações de @{name}",
"account.muted": "Muted",
"account.posts": "Posts",
"account.posts_with_replies": "Toots with replies",
"account.report": "Denunciar @{name}",
@@ -57,8 +60,10 @@
"compose_form.placeholder": "Em que estás a pensar?",
"compose_form.publish": "Publicar",
"compose_form.publish_loud": "{publish}!",
"compose_form.sensitive": "Marcar media como conteúdo sensível",
"compose_form.spoiler": "Esconder texto com aviso",
"compose_form.sensitive.marked": "Media is marked as sensitive",
"compose_form.sensitive.unmarked": "Media is not marked as sensitive",
"compose_form.spoiler.marked": "Text is hidden behind warning",
"compose_form.spoiler.unmarked": "Text is not hidden",
"compose_form.spoiler_placeholder": "Aviso de conteúdo",
"confirmation_modal.cancel": "Cancelar",
"confirmations.block.confirm": "Block",
@@ -216,6 +221,7 @@
"report.target": "Denunciar",
"search.placeholder": "Pesquisar",
"search_popout.search_format": "Formato avançado de pesquisa",
"search_popout.tips.full_text": "Simple text returns statuses you have written, favourited, boosted, or have been mentioned in, as well as matching usernames, display names, and hashtags.",
"search_popout.tips.hashtag": "hashtag",
"search_popout.tips.status": "status",
"search_popout.tips.text": "O texto simples retorna a correspondência de nomes, utilizadores e hashtags",
@@ -238,6 +244,7 @@
"status.mute_conversation": "Silenciar conversa",
"status.open": "Expandir",
"status.pin": "Fixar no perfil",
"status.pinned": "Pinned toot",
"status.reblog": "Partilhar",
"status.reblogged_by": "{name} partilhou",
"status.reply": "Responder",
@@ -247,10 +254,11 @@
"status.sensitive_warning": "Conteúdo sensível",
"status.share": "Compartilhar",
"status.show_less": "Mostrar menos",
"status.show_less_all": "Show less for all",
"status.show_more": "Mostrar mais",
"status.show_more_all": "Show more for all",
"status.unmute_conversation": "Deixar de silenciar esta conversa",
"status.unpin": "Não fixar no perfil",
"tabs_bar.compose": "Criar",
"tabs_bar.federated_timeline": "Global",
"tabs_bar.home": "Home",
"tabs_bar.local_timeline": "Local",

View File

@@ -1,7 +1,9 @@
{
"account.block": "Блокировать",
"account.block_domain": "Блокировать все с {domain}",
"account.blocked": "Blocked",
"account.disclaimer_full": "Нижеуказанная информация может не полностью отражать профиль пользователя.",
"account.domain_blocked": "Domain hidden",
"account.edit_profile": "Изменить профиль",
"account.follow": "Подписаться",
"account.followers": "Подписаны",
@@ -13,6 +15,7 @@
"account.moved_to": "Ищите {name} здесь:",
"account.mute": "Заглушить",
"account.mute_notifications": "Скрыть уведомления от @{name}",
"account.muted": "Muted",
"account.posts": "Посты",
"account.posts_with_replies": "Toots with replies",
"account.report": "Пожаловаться",
@@ -57,8 +60,10 @@
"compose_form.placeholder": "О чем Вы думаете?",
"compose_form.publish": "Трубить",
"compose_form.publish_loud": "{publish}!",
"compose_form.sensitive": "Отметить как чувствительный контент",
"compose_form.spoiler": "Скрыть текст за предупреждением",
"compose_form.sensitive.marked": "Media is marked as sensitive",
"compose_form.sensitive.unmarked": "Media is not marked as sensitive",
"compose_form.spoiler.marked": "Text is hidden behind warning",
"compose_form.spoiler.unmarked": "Text is not hidden",
"compose_form.spoiler_placeholder": "Напишите свое предупреждение здесь",
"confirmation_modal.cancel": "Отмена",
"confirmations.block.confirm": "Заблокировать",
@@ -216,6 +221,7 @@
"report.target": "Жалуемся на",
"search.placeholder": "Поиск",
"search_popout.search_format": "Продвинутый формат поиска",
"search_popout.tips.full_text": "Simple text returns statuses you have written, favourited, boosted, or have been mentioned in, as well as matching usernames, display names, and hashtags.",
"search_popout.tips.hashtag": "хэштег",
"search_popout.tips.status": "статус",
"search_popout.tips.text": "Простой ввод текста покажет совпадающие имена пользователей, отображаемые имена и хэштеги",
@@ -238,6 +244,7 @@
"status.mute_conversation": "Заглушить тред",
"status.open": "Развернуть статус",
"status.pin": "Закрепить в профиле",
"status.pinned": "Pinned toot",
"status.reblog": "Продвинуть",
"status.reblogged_by": "{name} продвинул(а)",
"status.reply": "Ответить",
@@ -247,10 +254,11 @@
"status.sensitive_warning": "Чувствительный контент",
"status.share": "Поделиться",
"status.show_less": "Свернуть",
"status.show_less_all": "Show less for all",
"status.show_more": "Развернуть",
"status.show_more_all": "Show more for all",
"status.unmute_conversation": "Снять глушение с треда",
"status.unpin": "Открепить от профиля",
"tabs_bar.compose": "Написать",
"tabs_bar.federated_timeline": "Глобальная",
"tabs_bar.home": "Главная",
"tabs_bar.local_timeline": "Локальная",

Some files were not shown because too many files have changed in this diff Show More