Compare commits

...

153 Commits

Author SHA1 Message Date
Eugen Rochko
7447e7a2ea Merge branch 'matteoaquila-master' 2017-04-13 22:26:54 +02:00
Eugen Rochko
c8be05a4a7 Fix italian translation 2017-04-13 22:26:32 +02:00
Eugen Rochko
41c697fd81 Merge branch 'master' of git://github.com/matteoaquila/mastodon into matteoaquila-master 2017-04-13 22:25:07 +02:00
Eugen
c5afe573da Fix drag & drop overlay not appearing on firefox (#1721) 2017-04-13 22:21:34 +02:00
Eugen
485310a43c Make browserify respect changes in node_modules for asset compilation (#1722) 2017-04-13 22:21:20 +02:00
Matteo Aquila
9aae9ae40c Update devise.it.yml 2017-04-13 22:09:47 +02:00
Matteo Aquila
1fb125b630 Update devise.it.yml 2017-04-13 22:06:19 +02:00
Eugen
057567d548 Make boost modal off by default (#1719) 2017-04-13 21:53:59 +02:00
Yusuke Abe
169c68a739 Add filename extension to paperclip (#1718) 2017-04-13 21:52:56 +02:00
Matt Jankowski
9f182346d7 Use english in reported by translation (#1713) 2017-04-13 21:49:46 +02:00
spf
a58c935c3d Fr missing strings (#1708)
* Fix missing translation for username already taken

* fr - note too long translation
2017-04-13 21:49:34 +02:00
Thomas Citharel
c0c56db0fa Translate admin (#1702)
* Translate the domain_block panel

Signed-off-by: Thomas Citharel <tcit@tcit.fr>

* Translate PubSubHubbub section

Signed-off-by: Thomas Citharel <tcit@tcit.fr>

* translate account section and correct typos

* move reports translation & translate sidebar

Signed-off-by: Thomas Citharel <tcit@tcit.fr>

* normalize l18n
2017-04-13 21:49:07 +02:00
James Moore
d9dc0fe84e smtp delivery type fix (#1556)
* delivery fix

# Conflicts:
#	config/environments/production.rb

* added stub in .env file

* reordered and added a comment
2017-04-13 19:51:49 +02:00
Alyssa Ross
55b56e3f95 terms: remove redundant words (#1578)
"at least X" and "X or older" have identical meanings.
Using both together feels a little jarring.
2017-04-13 19:50:19 +02:00
R Tucker
c4d39b1b3d quick typo fix in en.jsx: Rejec -> Reject (#1701) 2017-04-13 19:37:04 +02:00
Eugen
ac54da9394 Fix #1220, fix #1671 - Hook up comment box to the Redux comment value (#1699)
Fix username styling regression introduced in #1063
Fix report screen background regression introduced in #1415
2017-04-13 19:36:41 +02:00
Eugen
043862f411 Fix #1609, fix #1628 - Revert #1397 (#1700)
When transmitting data in a HTML-encoded element like <content type="html" />,
relying on newlines being preserved is not wise, since HTML by itself
does not care for newlines - it cares for <p> and <br>

Additional fix: reset NSFW toggle after sending toot
2017-04-13 19:23:36 +02:00
Eugen
9e5c1c487e Apply i18n-tasks normalize to locales (#1696) 2017-04-13 19:18:32 +02:00
INAGAKI Hiroshi
5619099564 translation: Fix comment out issue in en.jsx (#1698) 2017-04-13 17:11:18 +02:00
Koala Yeung
ce80d0b0a9 Fix minor jslint issue (#1697)
* Undefined propType in react component `ColumnSettings`.
  Add proper PropTypes definition.
2017-04-13 17:10:48 +02:00
Eugen Rochko
6327f69cab Merge branch 'blackle-videolightbox' 2017-04-13 17:01:52 +02:00
Eugen Rochko
5f8155482a Add overlay style to buttons, continue video after expanding it 2017-04-13 17:01:09 +02:00
Matteo Aquila
efcf9448da Update settings_helper.rb 2017-04-13 16:16:39 +02:00
Eugen Rochko
e70b84b1dc Merge branch 'videolightbox' of git://github.com/blackle/mastodon into blackle-videolightbox 2017-04-13 16:14:11 +02:00
Lukas Burk
d7a4e8739a Ignore postgres/redis folder from docker-compose (#1645) 2017-04-13 16:04:20 +02:00
Rachel H
9362700137 Convert emoji shortnames when sending status (#1666) 2017-04-13 16:03:45 +02:00
Koala Yeung
1206627c59 Add Dutch translation (nl) (#1682)
* Dutch language files for Ruby code

 * Created RoR translation ymls:
   * config/locales/devise.nl.yml
   * config/locales/doorkeeper.nl.yml
   * config/locales/nl.yml
   * config/locales/simple_form.nl.yml

 * Modified RoR config and helper
   * app/helpers/settings_helper.rb
   * config/application.rb

* Dutch language javascript locale

 * Created javascript locale files:
   * app/assets/javascripts/components/locales/index.jsx
   * app/assets/javascripts/components/locales/nl.jsx

 * Reference the newly created locale files:
   * app/assets/javascripts/components/containers/mastodon.jsx

* Fix syntax error in locale file (nl)

* Fix missing translate in js locale (nl)

* Convert all ruby Dutch locale (nl) file to utf8

Fix yml conversion issues.

* Fix duplicated key in devise.nl.yml

* Fix indentation error in doorkeeper.nl.yml
2017-04-13 16:02:23 +02:00
Isabelle Knott
edefcfcf42 Fix issue where 'sensitive content click to show' item takes up whole screen on public view (#1692) 2017-04-13 16:00:56 +02:00
Matt Jankowski
b330d1f000 Organize coverage dirs (#1695)
* Add `Presenters` group to SimpleCov configuration

* Move validators to app/validators, add to simplecov config
2017-04-13 16:00:31 +02:00
Eugen
1a5a54eb4b Merge branch 'master' into master 2017-04-13 16:00:20 +02:00
Isabelle Knott
447b8bc44e Do not show media attachment as og:image if it was marked as NSFW (#1693) 2017-04-13 15:59:43 +02:00
Matt Jankowski
093879c177 Fix language export variables (#1689)
* Fix naming of JS locale constants

* Improve the translation instructions re: const names
2017-04-13 15:59:12 +02:00
Matteo Aquila
d2c20936f3 Merge remote-tracking branch 'origin/master' 2017-04-13 15:33:53 +02:00
Matteo Aquila
0220f3a171 Update application.rb 2017-04-13 15:33:45 +02:00
Matteo Aquila
905a4faa1c Update settings_helper.rb 2017-04-13 15:32:31 +02:00
Matteo Aquila
5355b7d930 Update settings_helper.rb 2017-04-13 15:31:38 +02:00
Matteo Aquila
aec2458d81 Merge remote-tracking branch 'tootsuite/master' 2017-04-13 15:30:04 +02:00
Matt Jankowski
4fe5e04ea4 Fix csv export coverage in export spec (#1691) 2017-04-13 15:29:30 +02:00
Matteo Aquila
3f42ad7d1a Rename simple_form.it.yml to config/locales/simple_form.it.yml 2017-04-13 15:28:30 +02:00
Matteo Aquila
f7c466c8d8 Rename devise.it.yml to config/locales/devise.it.yml 2017-04-13 15:28:15 +02:00
Matteo Aquila
245b9cb4ba Rename doorkeeper.it.yml to config/locales/doorkeeper.it.yml 2017-04-13 15:27:56 +02:00
Matteo Aquila
9275f92972 Rename it.yml to config/locales/it.yml 2017-04-13 15:27:37 +02:00
Matteo Aquila
4a6d3bac86 Merge pull request #1 from tootsuite/master
update
2017-04-13 15:23:20 +02:00
blackle
00cc3066a2 Allow video to be expanded into lightbox 2017-04-13 09:09:45 -04:00
YOSHIOKA Eiichiro
a57d30c680 [l10n] ja: update missing Japanese translations (#1687) 2017-04-13 14:39:14 +02:00
Musee U
467d32fce3 [l10n] ja: update missing Japanese translations (#1684) 2017-04-13 13:40:58 +02:00
Koala Yeung
8aadb7b0b2 Exclude javascript locale file from dup check (#1677)
* Exclude javascript locale files form Code Climate's duplication
  engine. It is silly to have duplication check with locale files.
  They are supposed to look similar.

* Prevent unnecessary blocking for translation updates (like #1661)
2017-04-13 13:40:25 +02:00
Matteo Aquila
79546799af Added Italian language (it) (#1679) 2017-04-13 13:39:00 +02:00
May Kittens Devour Your Soul
90d0018fd5 Adds Croatian language [Hrvatski] (#1000)
* Create simple.form.hr.yml

* Create hr.yml

* Update hr.yml

* Update hr.yml

* Create doorkeeper.hr.yml

* Create devise.hr.yml
2017-04-13 13:38:44 +02:00
Eugen
1a12fd14d4 Merge branch 'master' into master 2017-04-13 13:26:16 +02:00
Hugo Gameiro
282bb55c3c fix Portuguese translation (#1661)
* update portuguese translation

added the missing fields and improved the translation

* pt translations fix

* improve last translation commit

* fix damn quotes
2017-04-13 13:25:34 +02:00
新都心(Neet Shin)
0e4479bb3a Update Japanese translation files (#1640)
* [l10n] ja: Improve Japanese Translations

* ja: about: Fix highlighting
* ja: Update Translations
* ja: Translate admin settings

Signed-off-by: lindwurm <lindwurm.q@gmail.com>

* Update ja.jsx

* Update doorkeeper.ja.yml

* Update ja.yml

* Update ja.jsx

* Update ja.jsx
2017-04-13 13:23:45 +02:00
Ratmir Karabut
af7e880df5 Update Russian translation (#1570)
* Add Russian translation (ru)

* Fix a missing comma

* Fix the wording for better consistency

* Update Russian translation

* Arrange Russian setting alphabetically

* Fix syntax error
2017-04-13 13:23:23 +02:00
Svetlozar Todorov
aa7bf1515c Fix #624 - Add localization for Bulgarian (#645)
* Add translation files and declarations for Bulgarian

* Add a bunch of translations to bg.jsx

* Add rest of translations to bg.jsx

* Add devise translations

* Fix devise translations
2017-04-13 13:16:28 +02:00
Daijiro Wachi
4f781b17cc Use input type number for Two-factor code (#1683) 2017-04-13 13:13:17 +02:00
Matt Jankowski
137100dcf3 Clean up well-known routes/controllers (#1649)
* Add request spec for host meta route returning xml

* Add routing spec for xrd routes

* Update well-known routes

* Move webfinger and host-meta actions to their own controllers
2017-04-13 13:09:07 +02:00
Matt Jankowski
3a9eb81a80 Admin accounts controller cleanup (#1664)
* Remove unused account_params method in admin/accounts controller

* Introduce AccountFilter to find accounts

* Use AccountFilter in admin/accounts controller

* Use more restful routes admin silence and suspension area

* Add admin/silences and admin/suspensions controllers
2017-04-13 13:04:23 +02:00
Matt Jankowski
0e39cc6a35 Settings export refactor (#1646)
* Refactor Export to take an account and know about the export types

* Use Export instance in settings/exports#show
2017-04-13 13:02:02 +02:00
Koala Yeung
faefd8ec8f Update javascript English translation files and some defaultValue (#1676)
* Reorder javascript English locale file

 * Reorder translation string in order of the locale key.

* Add javascript English locale missing language keys

 * Search all javascript language keys by command:
   `grep -REho '<FormattedMessage .*\/>' ./app/assets/javascripts/.`

 * Add all the missing language keys and their values to `en.jsx`.

* Add javascript English locale missing language keys (2)

* Find all `defineMessages` calls with this command:
  `grep -Rl 'defineMessages({.*' ./app/assets/javascripts/.`

* Open all these files. Find the language key (`id`) in these
  statements.

* Add all the missing language keys and their values to `en.jsx`.

* Remove javascript English locale obsoleted language keys

 * Find all language keys that no longer exists in the source code
  and remove them. The removed keys include:

    * "compose_form.private"
    * "compose_form.unlisted"
    * "getting_started.about_addressing"
    * "getting_started.about_shortcuts"
    * "notification.mention"
    * "search.account"
    * "search.hashtag"
    * "tabs_bar.mentions"
    * "tabs_bar.public"

* Javascript English locale file add note

 * Add notes to contributors about the English translation files.
   Hope that will make translation process smoother.

* Update javascript locale defaultValue in code

 * Update the defaultValue in code according to the relevant
   translation in English locale file.
2017-04-13 12:57:41 +02:00
matteoaquila
a18fd491b9 Added Italian language (it) 2017-04-13 11:47:03 +02:00
Isabelle Knott
96715d9af5 Clear spoiler when replying to toot that doesn't also have a spoiler (#1662) 2017-04-13 03:17:34 +02:00
Matt Jankowski
f24daa399b Remove pending specs for methods that dont exist (#1658)
* Remote spec for non-existent entry_classes helper method

This method no longer exists, and is handled by a local variable in a partial
instead.

* Remove spec for non-existent Account#ping! method
2017-04-13 02:51:13 +02:00
tom
af96e71883 Smoother scrolling on older iOS devices (#1654)
This may address the scrolling issues mentioned here: https://github.com/tootsuite/mastodon/issues/1622
2017-04-13 02:50:56 +02:00
Eugen
5dc73339ae Fix tests issue introduced in #1607 (#1639) 2017-04-13 02:17:40 +02:00
Thor Harald Johansen
ccaf3dbc5a Adjustments to the Norwegian translation (#1648)
* Working translation for Norwegian.

* Fixes to Norwegian translation.

* Further adjustments to Norwegian translation.

* Further adjustments to Norwegian translation.
2017-04-13 02:17:26 +02:00
Isabelle Knott
1ea662963f Use shift+click instead of alt+click to bypass boost dialog (#1638) 2017-04-13 02:15:45 +02:00
Eugen
bd834add56 Fix visuals introduced in #1463 (#1634) 2017-04-12 20:54:49 +02:00
lindwurm
9966bd27c2 [l10n] ja: Improve Japanese Translations (#1631)
* ja: about: Fix highlighting
* ja: Update Translations
* ja: Translate admin settings

Signed-off-by: lindwurm <lindwurm.q@gmail.com>
2017-04-12 20:41:50 +02:00
Isabelle Knott
b0ab632531 Fix missing compose box when viewport width is exactly 1024px (#1632) 2017-04-12 20:40:03 +02:00
Knut Erik
e1264bbd92 Added norwegian version of terms of service and privacy policy (#1625) 2017-04-12 20:28:56 +02:00
pinfort
38e24a699b fix Japanese translation (#1623)
* add device.ja.yml

* update device.ja.yml

* add file simple_form.ja.yml

* 👍Added doorkeeper.ja.yml

* add ja.yml

* Update doorkeeper.ja.yml

fixed url to uri

* update ja.yml

* fix some translations

* fix japanese grammar of a translate

* fix some translates

* fix ja.yml

* add ja.jsx

* add Japanese user mail views

* Added japanese translate locales

* Added :ja to available_locales

こっちも

* Added "日本語" to HUMAN_LOCALES

* Imported/Added ja to addLocaleData

* update ja.jsx

* fix translations
翻訳の誤りの修正と改善

* freeの訳修正

* いいねをお気に入りに統一

* Revert "いいねをお気に入りに統一"

This reverts commit 568d5cccfa0b6620ccb6c9db8346c52c0396d99f.
間違ってtranslateブランチにコミットしたため取り消し

* Revert "freeの訳修正"

This reverts commit 565658a60583ff7e1e7a63ef597bf3ac6118e1bf.

* revertでミスったので戻す

* Revert "ミスったので戻す"

This reverts commit 00be7a748a6a2ae85a62be847172424278c52ec7, reversing
changes made to b4e1e06503c77e079cb2569a53ab4d6fcfcfd116.

* freeの訳修正

* いいねをお気に入りに統一

* 一回戻す

* 戻しきれてなかった

* 再度変更

* 戻す

* 再度変更

* 再度変更

* Update doorkeeper.ja.yml

Fixed a lot of unnatural translations

* Update ja.yml

資格情報 isn't easy to understanding. so fixed to ログイン情報.

* Update ja.yml

fixed some unnatural translations

* Update simple_form.ja.yml

Fixed wrong translate 「アカウント」 to 「アイコン画像」

* Update simple_form.ja.yml

* Update doorkeeper.ja.yml

* Revert "多くの不自然な翻訳を解消しました。"

* Update ja.jsx

fixed typo

* 文字化け修正
2017-04-12 20:19:38 +02:00
Manato Kameya
bf3e56b8ad Fix incorrect notation in simple_form.ja.yml (#1620) 2017-04-12 20:19:27 +02:00
goofy-bz
9b698bf448 Update fr.jsx (#1551)
fixing to minor typos
2017-04-12 20:11:09 +02:00
Ben Roberts
0254ee9795 significant improvement in microformats markup (#1063)
* significant improvement in microformats markup

This is a huge improvement and I believe will close #965.

Had these microformats reviewed by others in the community to help
ensure they are at least correct, if not complete.

I did not want to change the structure of the page, and so there it does
not fully mark up the entire ancestry chain, or reply chain, only the
direct decendants and direct ancestors are correctly associated, but
this is likely fine as the most important bit is to have access to the
urls for those toots which are now correctly fetchable.

* improve code climate

* trying to pass code climate tests

* code climate

* fix p-summary for content warning posts

* fix error introduced when merging via github
2017-04-12 20:04:33 +02:00
CgX
e32edd247f Update fr.yml (#1600)
Mistake on %{link} variable
2017-04-12 19:56:09 +02:00
karlyeurl
dab9b5bd3a locale-fr: fix typo (#1603)
In this case, the adjective applies to a feminine noun.
2017-04-12 19:54:16 +02:00
Eugen
e17b5b228d Improve docker update instructions (#1619) 2017-04-12 19:34:40 +02:00
Julien Deswaef
c4baa9fb6b vector (svg) logo with correct inner shape and colors (#1362)
* vector (svg) logo with correct inner shape and colors

* minimized svg logo
2017-04-12 19:10:52 +02:00
David Libeau
c2a31b8032 Fix target blank on "open_in_web" link (#1612)
When you render the "embed" view in an iframe, this link bugs when clicked, due to missing target blank.
2017-04-12 13:09:58 -04:00
Shel R
9e63bf446e Request documentation (#1616)
This addition to the submission guidelines requests that contributors remember to document their code. It's not a hard fast rule just a reminder.
2017-04-12 18:27:33 +02:00
Matt Jankowski
c44a700252 Quick best practice cleanup of views/helpers (#1546)
* Remove trailing whitespace

* Use query methods instead of explicit .blank? checks
2017-04-12 18:24:18 +02:00
Matt Jankowski
aa90798386 Webfinger resource to extract username from resource string (#1607)
* Add WebfingerResource class to extract usernames

* Use WebfingerResource in xrd#webfinger
2017-04-12 18:22:38 +02:00
Rachel H
0930ce5560 Focus textarea when text is inserted (#1320)
Place space after uploaded filenames
2017-04-12 18:21:07 +02:00
Matt Jankowski
7f0a865b05 Allow import/export of mutes list (#1541)
* Allow export of mutes list

* Allow importing of mutes list

* Refactor to use Settings::Exports::BaseController and DRY up exports code
2017-04-12 18:20:44 +02:00
Henry Smith
08fce08217 Add unit tests for https://github.com/tootsuite/mastodon/pull/1574 (#1584) 2017-04-12 17:16:59 +02:00
Carlos A. Escobar
3064ef96a1 Added missing Spanish translations strings (#1292)
* Added missing Spanish translations strings

* fix bad translation

* resolve conflict with tootsuite/master

* agreements from spanish community

* Add some missing strings
2017-04-12 17:16:40 +02:00
lindwurm
ee69ece7b5 [web] Moving docs link to tootsuite/documentation (#1581)
related: 1236529e39 and fc47c1d00e

Signed-off-by: lindwurm <lindwurm.q@gmail.com>
2017-04-12 17:13:19 +02:00
Thomas Citharel
d90d23699c Make Reporting admin section translatable (#1549)
* Make Reporting admin section translatable

And translate it into english and french

Signed-off-by: Thomas Citharel <tcit@tcit.fr>

* Make subject of emails translatable and improve french translation

Signed-off-by: Thomas Citharel <tcit@tcit.fr>

* Make error pages translatable and translate them in english and french

Signed-off-by: Thomas Citharel <tcit@tcit.fr>

* Translate site setting section

* Insert instance in registration emails and improve them a bit

Signed-off-by: Thomas Citharel <tcit@tcit.fr>
2017-04-12 17:11:49 +02:00
Thor Harald Johansen
1f5ff46fd9 Working translation for Norwegian. (#1611) 2017-04-12 17:07:51 +02:00
Matt Jankowski
13528f50c3 Params compact deprecation warning (#1580)
* Move filter_link_to class formation to separate method in admin/accounts helper

* Remove deprecated #compact method usage on strong parameters
2017-04-12 16:12:56 +02:00
Matt Jankowski
dd1ae3b109 Simplify the way the embed view is created (#1590)
* Add coverage for embedded status view

* Refactor embed view to eliminate @external_links variable
2017-04-12 16:12:42 +02:00
Matt Jankowski
b352a8e5d4 Default to json type for webfinger requests (#1583) 2017-04-12 16:03:37 +02:00
Matt Jankowski
fd102059aa Clean up stylesheet organization (#1591) 2017-04-12 16:01:59 +02:00
Matt Jankowski
323671a653 Silence more scope order warnings (#1604) 2017-04-12 15:58:08 +02:00
Matt Jankowski
b155e6ccf5 Fix issue with intermittent api/v1/notifications failure (#1606)
The spec was checking the activity_id of the activities held in notifications
within the controller.

Because the activities are different models, it is possible that they are
created with the same database IDs, and when they are this spec fails because an
activity which should not count as a match is counted as one.
2017-04-12 15:53:54 +02:00
Christopher Su
f16b9a4928 Fix redirect link on Tuning.md (#1595) 2017-04-12 12:40:37 +02:00
Koala Yeung
24eb45425e Add Traditional Chinese, Hong Kong translation (zh-HK) (#1544)
* Added Chinese Traditional Hong Kong (zh-HK) for Ruby

* Added translations for Ruby.

* Added Chinese Traditional Hong Kong (zh-HK) for JS

* Added translations for javascript code.

* Rearrange language references in mastodon.jsx

* Break `addLocaleData` into multiple lines. Make future commit more readable.
* Roughly re-sort the languages in alphabetical orders
  (only manually put English on top because it is default).

* Sort application.rb locale with alphabetical order

With exception that English (default language) goes first.
Improve code readability.

* Resort language selection box alphabetically

Sort HUMAN_LOCALES in the alphabetical order of display name
(except English, the default language, come first).

Improve usability.
2017-04-12 08:50:50 +02:00
Eric Blade
3442bc0ea3 update Docker section of README (#1231)
Re-ordered the steps so it doesn't read "Do this, but first, do this
other step"
Added note about keeping the REDIS and DB settings as they are for
Docker use
Add which variables you will NEED to set to make the Mastodon work
Add how to generate the secrets
Add how to connect to your Mastodon
Add a note to read the Production-guide
2017-04-12 01:14:56 +02:00
Eugen
40bdf43297 Change default log level in production from :debug to :info for less I/O (#1579) 2017-04-11 23:28:23 +02:00
Eugen
8ead070b94 Improve emoji picker look (#1493)
* Improve emoji picker look

* Add hover animation to emojis in picker
2017-04-11 23:23:34 +02:00
Eugen
b22b2cbfac Fix #1491 - Fix broken notifications, broken Link header parsing for exclude_types (#1548) 2017-04-11 22:53:58 +02:00
d0p1
2f2b84bfbb Add task in order to delete unconfirmed users (older than 2days) (#1571)
* add task in order to delete unconfirmed user

* change 7 days to 2
2017-04-11 22:51:17 +02:00
Eugen
5cdd2c2414 Fix #1535 - #1372 set a wrong default on :openssl_verify_mode (#1563) 2017-04-11 22:49:53 +02:00
Matt Jankowski
3ddd936b03 Refactor exports controller (#1567)
* Add basic coverage for settings/exports controller

* Remove unused @account variable from settings/exports controller

* Add coverage for download export actions

* Remove deprecated `render :text` in favor of `send_data` for csv downloads

* Add model to handle exports

* Use Export class in settings/exports controller

* Simplify settings/exports controller methods

* Move settings/export to more restful routes
2017-04-11 22:00:43 +02:00
Henry Smith
1921c5416b Fix console error when scrolling a column with no scrollable content (#1574) 2017-04-11 21:58:28 +02:00
lindwurm
fc47c1d00e [about] Moving docs link to tootsuite/documentation (#1573)
related: 1236529e39

Signed-off-by: lindwurm <lindwurm.q@gmail.com>
2017-04-11 21:57:05 +02:00
Thomas Citharel
327a6e166f Update french locale (#1525)
* Update devise.fr.yml fixing minor typos

* add missing strings

* Fix a translation and add translation for devise part

Signed-off-by: Thomas Citharel <tcit@tcit.fr>
2017-04-11 21:54:40 +02:00
pinfort
6f5268b02d Add Japanese translation (#1552)
* add device.ja.yml

* update device.ja.yml

* add file simple_form.ja.yml

* 👍Added doorkeeper.ja.yml

* add ja.yml

* Update doorkeeper.ja.yml

fixed url to uri

* update ja.yml

* fix some translations

* fix japanese grammar of a translate

* fix some translates

* fix ja.yml

* add ja.jsx

* add Japanese user mail views

* Added japanese translate locales

* Added :ja to available_locales

こっちも

* Added "日本語" to HUMAN_LOCALES

* Imported/Added ja to addLocaleData

* update ja.jsx
2017-04-11 21:52:09 +02:00
jukper
4964433190 Revised finnish translation (#1537) 2017-04-11 21:41:40 +02:00
Jantso Porali
9e3c4fd2d7 Update language files (#1516)
* update faq with default language

* update translation for about page

* update Minio config

Thanks to @Gargon for helping me. I hope this will help others as well

* update import and export translation

* translate emails to finnish

* add finnish translation for emails

* add finnish translation for emails

* add finnish translation

* add missing dot

* update finnish language to emails

* add finnish translation for emails

* add dot and fix typo

* updated some minor typos

* remove language change due breaking emails

And by dev request

* updated minio config by dev request

* updated about page translation

* fix for Amazon S3/Minio instance setups

If you were using S3 or Minio for your files, this will fix the Import issue :)

* update translation according to issue #1515

See issue #1515

* update translation for issue #1515

see issue #1515
2017-04-11 21:41:29 +02:00
Matt Jankowski
89e8e110c8 Imports controller errors (#1553)
* Add spec for settings/imports controller

* Add failing spec for settings/imports#create

* Fix broken imports

* Refactor ImportWorker
2017-04-11 21:40:14 +02:00
Eugen Rochko
9f7ea77d0c Merge branch 'master' of github.com:tootsuite/mastodon 2017-04-11 21:30:48 +02:00
Eugen Rochko
5f74397ef0 Merge branch 'blackle-master' 2017-04-11 21:30:34 +02:00
Eugen Rochko
960181fd99 Fix look of the modals 2017-04-11 21:24:17 +02:00
Eugen Rochko
2a7602cad4 Merge branch 'master' of https://github.com/blackle/mastodon into blackle-master 2017-04-11 20:43:56 +02:00
Komic
47aacb773b Better background-photo.jpeg (#1560)
Fixed the nasty banding & recompressed it at about the same level
2017-04-11 19:51:13 +02:00
Matt Jankowski
82d9336114 Increase breakpoint size for bio on account show (#1559) 2017-04-11 19:35:06 +02:00
Matt Jankowski
e60286a344 Remove unused methods from StreamEntriesHelper (#1365)
Removes:

- avatar_for_status_url
- relative_time
- reblogged_by_me_class
- favourited_by_me_class
2017-04-11 19:32:02 +02:00
Matt Jankowski
53850bce93 Remove unused AtomBuilderHelper (#1364)
This was used in the views/atom/user_stream.xml.ruby file, which no longer is
used.
2017-04-11 19:31:22 +02:00
Eugen
1236529e39 Moving docs to tootsuite/documentation (#1550) 2017-04-11 12:03:02 -04:00
blackle
06444bf050 Allow user to disable the boost confirm dialog in preferences 2017-04-11 10:10:16 -04:00
Valentin Ouvrard
b723ee73fc Add (commented) volume in docker-compose && Mitigating the HTTPoxy Vulnerability (#1253)
* enable commented volume in docker-compose.yml

* Disable unworking Nginx root directory && Mitigating the HTTPoxy Vulnerability

* add my instance to the list

* enable GZIP on nginx.conf

* readd root /home/mastodon/live/public;
2017-04-11 16:04:56 +02:00
Gavin Mogan
c35bda0551 fix(*): ruby version was updated in .ruby-version but not Vagrant. Make them match (#1502) 2017-04-11 15:06:07 +02:00
blackle
f53fb6aa66 Bypass boost confirm modal if alt is pressed 2017-04-11 08:34:14 -04:00
Yann GUERN
a85d4473aa Avoid user enumeration with devise paranoid mode (#1527) 2017-04-11 14:21:15 +02:00
Corey Dutson
c9b9225951 Adjust css for user detail page (#1463)
- details a background for contrast
- add 5px padding to the top of the `details-counters` children to line them up with the bio to the right (Which has a 5px padding on the top)
2017-04-11 14:20:18 +02:00
David Libeau
11898a6461 Add Mastodon.tools (#1457) 2017-04-10 23:30:40 -04:00
blackle
01e5447e35 Add boost confirm modal 2017-04-10 22:35:55 -04:00
Matt Jankowski
4ada50985a Pagination improvements (#1445)
* Replace will_paginate with kaminari

* Use #page instead of #paginate in controllers

* Replace will_paginate.page_gap with pagination.truncate in i18n

* Customize kaminari views to match prior styles

* Set kaminari options to match prior behavior

* Replace will_paginate with paginate in views
2017-04-11 01:11:41 +02:00
Effy Elden
a283786463 Add note about minimum docker-compose version (#1264)
Add a note to ensure users are using the right version of docker-compose.
2017-04-11 00:39:39 +02:00
Eugen
12f72e1740 When avatar/header are GIF, generate static versions (#1428)
* When avatar/header are GIF, generate static versions.
Account API returns "avatar"/"avatar_static", "header"/"header_static"
Static version is the same as original for other cases
Web UI de-animates avatars in toots, lists of users

Fix #441, fix #596, prerequisite for #1064

* Fix JS test

* Add rake task to generate static avatars/headers from GIF ones, add test
2017-04-11 00:38:58 +02:00
Matt Jankowski
b57eed4584 Remove order prior to .find_in_batches (#1470)
The `Status` class has a default order on it, so when this query gets built and
gets all the way to `find_in_batches` there is an order already there.

When `find_in_batches` is run it discards any existing order on the query, and
emits a warning to the logs if there is one there.

This change removes the order prior calling `find_in_batches`, which will stop
the logged warning from occurring as well.
2017-04-11 00:38:34 +02:00
Gavin Mogan
3672a799d4 Dev Tooling fixes (eslint/editorconfig) (#1398)
* Add eslint to dev dependancies so it gets installed for the repo

yarn add --dev eslint babel-eslint eslint-plugin-reac

project specific version of eslint, you can globally install eslint-cli
if you want the global runtime, or add .bin to your path

* fix eslint errors about inconsitent returns

* eslint ignore the same as git ignore. allows for eslint .

* Add editorconfig file so everyones editor will be setup to follow the same standards
2017-04-11 00:36:03 +02:00
Matthias Jouan
3fd5385e7b Add username as a title for mentions (#1385)
Add a title attribute on mention links for both notifications
and mentions in statuses.

Related to #1350
2017-04-11 00:35:35 +02:00
Ash Furrow
d439855a6d Adds error message to mastodon:confirm_email task. (#1476) 2017-04-11 00:13:08 +02:00
Eugen
2810013b93 API param to exclude notification types from response (#1341)
* Add exclude_types param to /api/v1/notifications

* Exclude notification types in web UI through exclude_types in the API
2017-04-10 23:45:29 +02:00
Matt Jankowski
0687ab8ae3 Clean up generation of account webfinger string (#1477)
* Consolidate webfinger string creation under Account#to_webfinger_s

* Introduce Account#local_username_and_domain for consolidation
2017-04-10 22:58:06 +02:00
Matt Jankowski
64dbde0dbf Version bumps for ruby and misc gems (#1159)
* Update rspec-rails to version 3.5.2

* Update addressable to version 2.5.1

* Update autoprefixer-rails to version 6.7.7.1

* Update bullet to version 5.5.1

* Update domain_name to version 0.5.20170404

* Update letter_opener_web to version 1.3.1

* Upate redis-rails to version 5.0.2

* Update active_record_query_trace to version 1.5.4

* Update capistrano-rails to version 1.2.3

* Update dotenv-rails to version 2.2.0

* Update pg to version 0.20.0

* Update tilt to version 2.0.7

* Update warden to version 1.2.7

* Update tins to version 1.13.2

* Update terminal-table to version 1.7.3

* Update oj to version 2.18.5

* Update simplecov to version 0.14.1

* Update uglifier to version 3.1.13

* Update hashdiff to version 0.3.2

* Update webmock to version 2.3.2

* Update devise to version 4.2.1

* Use ruby version 2.4.1

* Update sass to version 3.4.23

* Update puma to version 3.8.2

* Update will_paginate to version 3.1.5

* Update font-awesome-rails to version 4.7.0.1

* Update fuubar to version 2.2.0

* Update pry-rails to version 0.3.6

* Update simple-navigation to version 4.0.5

* Update rubocop to version 0.48.1

* Update doorkeeper to version 4.2.5

* Update faker to version 1.7.3

* Update aws-sdk to version 2.9.5

* Update fabrication to version 2.16.1

* Update hamlit-rails to version 0.2.0

* Update http to version 2.2.1

* Update httplog to version 0.99.2

* Update sidekiq to version 4.2.10

* Update rspec-sidekiq to version 3.0.0

* Update pghero to version 1.6.4

* Update rack-cors to version 0.4.1

* Update i18n-tasks to version 0.9.13

* Update ruby-oembed to version 0.12.0

* Update jquery-rails to version 4.3.1

* Update simple_form to version 3.4.0

* Update react-rails to version 1.11.0

* Update aws-sdk to version 2.9.6

* Update sidekiq-unique-jobs to version 5.0.0

* Update uglifier to version 3.2.0
2017-04-10 22:47:41 +02:00
Chris Martin
ae57b3a8c5 Add more specific class names to notification divs (#1120) 2017-04-10 22:41:52 +02:00
Alexander Mankuta
0dbbc16c69 More SMTP customization (#1372)
* Allow SMTP auth method customization

* Add SMTP openssl_verify_mode option support

Allows one use self-signed certs with their SMTP server.

* Add SMTP enable_starttls_auto option support
2017-04-10 21:48:30 +02:00
Rachel H
f690320fb9 Keep newlines in xml (#1397) 2017-04-10 21:32:45 +02:00
Stephen Burgess
553170b77a Fix #1097 When onClick is falsy, do not make status content clickable (#1434) 2017-04-10 21:31:26 +02:00
Jessica Stokes
8a6096a3de Allow typing a toot while an image uploads (#1429)
This patch stops disabling the toot text field when an image is uploading. Instead, you can type to your heart's content and when the image uploads it'll append the image URL, and restore the position of your cursor to wherever it was in the toot text just prior. Effectively, the image URL is appended to the toot, and typing is not interrupted at all! 
2017-04-10 21:30:58 +02:00
Matt Jankowski
d2f6d9b9fb Fix issue with missing emojify class in views (#1455)
* Add missing emojify class to landing strip

* Add missing emojify class to simple_status partial
2017-04-10 21:27:52 +02:00
Matt Jankowski
dbe9f33fdc Admin base controller (#1465)
* Add Admin::BaseController to wrap admin area

Extracts the setting of the `admin` layout and verifying that users are admins
to a common base class for the admin/ controllers.

* Add basic coverage for admin/reports and admin/settings controllers
2017-04-10 21:27:03 +02:00
Alexsander Akers
1be6aa0c7f Fix references to "v1" API (#1460)
References to `vi` API version replaced with `v1`
2017-04-10 21:11:59 +02:00
Corey Dutson
087ca3009b Adjust background of emoji panel (#1461)
Addresses #1451 which notes the emoji picker is too light. I agree, so I submit this adjustment.

Changes:
Changed the background to a darkened version of another system color
2017-04-10 20:56:14 +02:00
Jessica Stokes
db7c7d1af1 Improve scrolling behaviour (#1415)
* Replace column margin with padding

This improves horizontal scrolling behaviour significantly; scrolled flex elements are... a little weird.

* Move clear column button styling to css
2017-04-10 16:27:30 +02:00
Ram Lmn
42fb4faa0f Fix icon and emoji sizes (#1403)
Fixes icons and emoji size issues described in #1233.
2017-04-10 13:02:09 +02:00
Ryan Freebern
9bb398ee91 Minor clarifications (#1386) 2017-04-10 13:01:43 +02:00
Ratmir Karabut
9043b32183 Add Russian translation (ru) (#1023)
* Add Russian translation (ru)

* Fix a missing comma

* Fix the wording for better consistency
2017-04-10 12:52:06 +02:00
Jantso Porali
e30bbb1cb0 Fix for Import issue from URL (#1416) 2017-04-10 12:49:34 +02:00
Gavin Mogan
8bdf02812c Add search to emoji picker (#1395) 2017-04-10 09:58:09 +02:00
311 changed files with 7497 additions and 3920 deletions

View File

@@ -1,6 +1,8 @@
engines:
duplication:
enabled: true
exclude_paths:
- app/assets/javascripts/components/locales/
config:
languages:
- ruby

12
.editorconfig Normal file
View File

@@ -0,0 +1,12 @@
# EditorConfig is awesome: http://EditorConfig.org
# top-most EditorConfig file
root = true
# Unix-style newlines with a newline ending every file
[*]
end_of_line = lf
insert_final_newline = true
charset = utf-8
indent_style = space
indent_size = 2

View File

@@ -35,6 +35,11 @@ SMTP_PORT=587
SMTP_LOGIN=
SMTP_PASSWORD=
SMTP_FROM_ADDRESS=notifications@example.com
#SMTP_DELIVERY_METHOD=smtp # delivery method can also be sendmail
#SMTP_AUTH_METHOD=plain
#SMTP_OPENSSL_VERIFY_MODE=peer
#SMTP_ENABLE_STARTTLS_AUTO=true
# Optional asset host for multi-server setups
# CDN_HOST=assets.example.com

30
.eslintignore Normal file
View File

@@ -0,0 +1,30 @@
# See https://help.github.com/articles/ignoring-files for more about ignoring files.
#
# If you find yourself ignoring temporary files generated by your text editor
# or operating system, you probably want to add a global ignore instead:
# git config --global core.excludesfile '~/.gitignore_global'
# Ignore bundler config.
/.bundle
# Ignore the default SQLite database.
/db/*.sqlite3
/db/*.sqlite3-journal
# Ignore all logfiles and tempfiles.
/log/*
!/log/.keep
/tmp
coverage
public/system
public/assets
.env
.env.production
node_modules/
neo4j/
# Ignore Vagrant files
.vagrant/
# Ignore Capistrano customizations
config/deploy/*

8
.gitignore vendored
View File

@@ -28,3 +28,11 @@ neo4j/
# Ignore Capistrano customizations
config/deploy/*
# Ignore IDE files
.vscode/
# Ignore postgres + redis volume optionally created by docker-compose
postgres
redis

View File

@@ -1 +1 @@
2.3.1
2.4.1

View File

@@ -16,7 +16,7 @@ addons:
postgresql: 9.4
rvm:
- 2.3.1
- 2.4.1
services:
- redis-server

View File

@@ -7,7 +7,7 @@ There are three ways in which you can contribute to this repository:
2. By working on the back-end application
3. By working on the front-end application
Choosing what to work on in a large open source project is not easy. The list of GitHub issues may provide some ideas, but not every feature request has been greenlit. Likewise, not every change or feature that resolves a personal itch will be merged into the main repository. Some communication ahead of time may be wise.
Choosing what to work on in a large open source project is not easy. The list of GitHub issues may provide some ideas, but not every feature request has been greenlit. Likewise, not every change or feature that resolves a personal itch will be merged into the main repository. Some communication ahead of time may be wise. If your addition creates a new feature or setting, or otherwise changes how things work in some substantial way, please remember to submit a correlating pull request to document your changes in the [documentation](http://github.com/tootsuite/documentation).
Below are the guidelines for working on pull requests:
@@ -41,3 +41,4 @@ It is expected that you have a working development environment set up (see back-
* If you are introducing new strings, they must be using localization methods
If the JavaScript or CSS assets won't compile due to a syntax error, it's a good sign that the pull request isn't ready for submission yet.

View File

@@ -1,4 +1,4 @@
FROM ruby:2.3.1-alpine
FROM ruby:2.4.1-alpine
LABEL maintainer="https://github.com/tootsuite/mastodon" \
description="A GNU Social-compatible microblogging server"

View File

@@ -1,7 +1,7 @@
# frozen_string_literal: true
source 'https://rubygems.org'
ruby '2.3.1'
ruby '2.4.1'
gem 'rails', '~> 5.0.2'
gem 'sass-rails', '~> 5.0'
@@ -32,6 +32,7 @@ gem 'htmlentities'
gem 'http'
gem 'http_accept_language'
gem 'httplog'
gem 'kaminari'
gem 'link_header'
gem 'nokogiri'
gem 'oj'
@@ -52,7 +53,6 @@ gem 'simple_form'
gem 'statsd-instrument'
gem 'twitter-text'
gem 'tzinfo-data'
gem 'will_paginate'
gem 'react-rails'
gem 'browserify-rails'
@@ -68,6 +68,7 @@ end
group :test do
gem 'faker'
gem 'rails-controller-testing'
gem 'rspec-sidekiq'
gem 'simplecov', require: false
gem 'webmock'

View File

@@ -24,7 +24,7 @@ GEM
erubis (~> 2.7.0)
rails-dom-testing (~> 2.0)
rails-html-sanitizer (~> 1.0, >= 1.0.3)
active_record_query_trace (1.5.3)
active_record_query_trace (1.5.4)
activejob (5.0.2)
activesupport (= 5.0.2)
globalid (>= 0.3.6)
@@ -39,7 +39,7 @@ GEM
i18n (~> 0.7)
minitest (~> 5.1)
tzinfo (~> 1.1)
addressable (2.5.0)
addressable (2.5.1)
public_suffix (~> 2.0, >= 2.0.2)
airbrussh (1.1.2)
sshkit (>= 1.6.1, != 1.7.0)
@@ -47,17 +47,17 @@ GEM
ast (2.3.0)
attr_encrypted (3.0.3)
encryptor (~> 3.0.0)
autoprefixer-rails (6.5.0.2)
autoprefixer-rails (6.7.7.1)
execjs
av (0.9.0)
cocaine (~> 0.5.3)
aws-sdk (2.6.28)
aws-sdk-resources (= 2.6.28)
aws-sdk-core (2.6.28)
aws-sdk (2.9.6)
aws-sdk-resources (= 2.9.6)
aws-sdk-core (2.9.6)
aws-sigv4 (~> 1.0)
jmespath (~> 1.0)
aws-sdk-resources (2.6.28)
aws-sdk-core (= 2.6.28)
aws-sdk-resources (2.9.6)
aws-sdk-core (= 2.9.6)
aws-sigv4 (1.0.0)
babel-source (5.8.35)
babel-transpiler (0.7.0)
@@ -78,12 +78,11 @@ GEM
railties (>= 4.0.0, < 5.1)
sprockets (>= 3.6.0)
builder (3.2.3)
bullet (5.3.0)
bullet (5.5.1)
activesupport (>= 3.0.0)
uniform_notifier (~> 1.10.0)
capistrano (3.7.2)
capistrano (3.8.0)
airbrussh (>= 1.0.0)
capistrano-harrow
i18n
rake (>= 10.0.0)
sshkit (>= 1.9.0)
@@ -92,8 +91,7 @@ GEM
sshkit (~> 1.2)
capistrano-faster-assets (1.0.2)
capistrano (>= 3.1)
capistrano-harrow (0.5.3)
capistrano-rails (1.2.2)
capistrano-rails (1.2.3)
capistrano (~> 3.1)
capistrano-bundler (~> 1.1)
capistrano-rbenv (2.1.0)
@@ -119,7 +117,7 @@ GEM
crack (0.4.3)
safe_yaml (~> 1.0.0)
debug_inspector (0.0.2)
devise (4.2.0)
devise (4.2.1)
bcrypt (~> 3.0)
orm_adapter (~> 0.1)
railties (>= 4.1.0, < 5.1)
@@ -131,16 +129,16 @@ GEM
devise (~> 4.0)
railties
rotp (~> 2.0)
diff-lcs (1.2.5)
diff-lcs (1.3)
docile (1.1.5)
domain_name (0.5.20161129)
domain_name (0.5.20170404)
unf (>= 0.0.5, < 1.0.0)
doorkeeper (4.2.0)
doorkeeper (4.2.5)
railties (>= 4.2)
dotenv (2.1.1)
dotenv-rails (2.1.1)
dotenv (= 2.1.1)
railties (>= 4.0, < 5.1)
dotenv (2.2.0)
dotenv-rails (2.2.0)
dotenv (= 2.2.0)
railties (>= 3.2, < 5.1)
easy_translate (0.5.0)
json
thread
@@ -148,14 +146,14 @@ GEM
encryptor (3.0.0)
erubis (2.7.0)
execjs (2.7.0)
fabrication (2.15.2)
faker (1.6.6)
fabrication (2.16.1)
faker (1.7.3)
i18n (~> 0.5)
fast_blank (1.0.0)
font-awesome-rails (4.6.3.1)
font-awesome-rails (4.7.0.1)
railties (>= 3.2, < 5.1)
fuubar (2.1.1)
rspec (~> 3.0)
fuubar (2.2.0)
rspec-core (~> 3.0)
ruby-progressbar (~> 1.4)
globalid (0.3.7)
activesupport (>= 4.1.0)
@@ -163,20 +161,20 @@ GEM
addressable (~> 2.4)
http (~> 2.0)
nokogiri (~> 1.6)
hamlit (2.7.2)
temple (~> 0.7.6)
hamlit (2.8.1)
temple (>= 0.8.0)
thor
tilt
hamlit-rails (0.1.0)
hamlit-rails (0.2.0)
actionpack (>= 4.0.1)
activesupport (>= 4.0.1)
hamlit (>= 1.2.0)
railties (>= 4.0.1)
hashdiff (0.3.0)
hashdiff (0.3.2)
highline (1.7.8)
hiredis (0.6.1)
htmlentities (4.3.4)
http (2.1.0)
http (2.2.1)
addressable (~> 2.3)
http-cookie (~> 1.0)
http-form_data (~> 1.0.1)
@@ -186,10 +184,10 @@ GEM
http-form_data (1.0.1)
http_accept_language (2.1.0)
http_parser.rb (0.6.0)
httplog (0.3.2)
httplog (0.99.2)
colorize
i18n (0.8.1)
i18n-tasks (0.9.6)
i18n-tasks (0.9.13)
activesupport (>= 4.0.2)
ast (>= 2.1.0)
easy_translate (>= 0.5.0)
@@ -197,19 +195,31 @@ GEM
highline (>= 1.7.3)
i18n
parser (>= 2.2.3.0)
term-ansicolor (>= 1.3.2)
rainbow (~> 2.2)
terminal-table (>= 1.5.1)
jmespath (1.3.1)
jquery-rails (4.1.1)
jquery-rails (4.3.1)
rails-dom-testing (>= 1, < 3)
railties (>= 4.2.0)
thor (>= 0.14, < 2.0)
json (1.8.3)
json (2.0.3)
kaminari (1.0.1)
activesupport (>= 4.1.0)
kaminari-actionview (= 1.0.1)
kaminari-activerecord (= 1.0.1)
kaminari-core (= 1.0.1)
kaminari-actionview (1.0.1)
actionview
kaminari-core (= 1.0.1)
kaminari-activerecord (1.0.1)
activerecord
kaminari-core (= 1.0.1)
kaminari-core (1.0.1)
launchy (2.4.3)
addressable (~> 2.3)
letter_opener (1.4.1)
launchy (~> 2.2)
letter_opener_web (1.3.0)
letter_opener_web (1.3.1)
actionmailer (>= 3.2)
letter_opener (~> 1.0)
railties (>= 3.2)
@@ -231,11 +241,11 @@ GEM
minitest (5.10.1)
net-scp (1.2.1)
net-ssh (>= 2.6.5)
net-ssh (4.0.1)
net-ssh (4.1.0)
nio4r (2.0.0)
nokogiri (1.7.1)
mini_portile2 (~> 2.1.0)
oj (2.17.3)
oj (2.18.5)
orm_adapter (0.5.0)
ostatus2 (1.0.2)
addressable (~> 2.4)
@@ -251,26 +261,26 @@ GEM
paperclip-av-transcoder (0.6.4)
av (~> 0.9.0)
paperclip (>= 2.5.2)
parser (2.3.1.2)
parser (2.4.0.0)
ast (~> 2.2)
pg (0.18.4)
pghero (1.6.2)
pg (0.20.0)
pghero (1.6.4)
activerecord
powerpack (0.1.1)
pry (0.10.4)
coderay (~> 1.1.0)
method_source (~> 0.8.1)
slop (~> 3.4)
pry-rails (0.3.4)
pry (>= 0.9.10)
public_suffix (2.0.4)
puma (3.6.0)
pry-rails (0.3.6)
pry (>= 0.10.4)
public_suffix (2.0.5)
puma (3.8.2)
rabl (0.13.1)
activesupport (>= 2.3.14)
rack (2.0.1)
rack-attack (5.0.1)
rack
rack-cors (0.4.0)
rack-cors (0.4.1)
rack-protection (1.5.3)
rack
rack-test (0.6.3)
@@ -288,6 +298,10 @@ GEM
bundler (>= 1.3.0, < 2.0)
railties (= 5.0.2)
sprockets-rails (>= 2.0.0)
rails-controller-testing (1.0.1)
actionpack (~> 5.x)
actionview (~> 5.x)
activesupport (~> 5.x)
rails-dom-testing (2.0.2)
activesupport (>= 4.2.0, < 6.0)
nokogiri (~> 1.6)
@@ -306,42 +320,37 @@ GEM
method_source
rake (>= 0.8.7)
thor (>= 0.18.1, < 2.0)
rainbow (2.1.0)
rainbow (2.2.1)
rake (12.0.0)
react-rails (1.10.0)
react-rails (1.11.0)
babel-transpiler (>= 0.7.0)
coffee-script-source (~> 1.8)
connection_pool
execjs
railties (>= 3.2)
tilt
redis (3.3.2)
redis-actionpack (5.0.0)
actionpack (>= 4.0.0, < 6)
redis-rack (~> 2.0.0.pre)
redis-store (~> 1.2.0.pre)
redis-activesupport (5.0.1)
redis (3.3.3)
redis-actionpack (5.0.1)
actionpack (>= 4.0, < 6)
redis-rack (>= 1, < 3)
redis-store (>= 1.1.0, < 1.4.0)
redis-activesupport (5.0.2)
activesupport (>= 3, < 6)
redis-store (~> 1.2.0)
redis-rack (2.0.0)
rack (~> 2.0)
redis-store (~> 1.2.0)
redis-rails (5.0.1)
redis-actionpack (~> 5.0.0)
redis-activesupport (~> 5.0.0)
redis-store (~> 1.2.0)
redis-store (1.2.0)
redis-store (~> 1.3.0)
redis-rack (2.0.1)
rack (>= 2.0, < 3)
redis-store (>= 1.2, < 1.4)
redis-rails (5.0.2)
redis-actionpack (>= 5.0, < 6)
redis-activesupport (>= 5.0, < 6)
redis-store (>= 1.2, < 2)
redis-store (1.3.0)
redis (>= 2.2)
responders (2.3.0)
railties (>= 4.2.0, < 5.1)
rotp (2.1.2)
rqrcode (0.10.1)
chunky_png (~> 1.0)
rspec (3.5.0)
rspec-core (~> 3.5.0)
rspec-expectations (~> 3.5.0)
rspec-mocks (~> 3.5.0)
rspec-core (3.5.2)
rspec-core (3.5.4)
rspec-support (~> 3.5.0)
rspec-expectations (3.5.0)
diff-lcs (>= 1.2.0, < 2.0)
@@ -349,7 +358,7 @@ GEM
rspec-mocks (3.5.0)
diff-lcs (>= 1.2.0, < 2.0)
rspec-support (~> 3.5.0)
rspec-rails (3.5.1)
rspec-rails (3.5.2)
actionpack (>= 3.0)
activesupport (>= 3.0)
railties (>= 3.0)
@@ -357,40 +366,40 @@ GEM
rspec-expectations (~> 3.5.0)
rspec-mocks (~> 3.5.0)
rspec-support (~> 3.5.0)
rspec-sidekiq (2.2.0)
rspec (~> 3.0, >= 3.0.0)
rspec-sidekiq (3.0.0)
rspec-core (~> 3.0, >= 3.0.0)
sidekiq (>= 2.4.0)
rspec-support (3.5.0)
rubocop (0.42.0)
parser (>= 2.3.1.1, < 3.0)
rubocop (0.48.1)
parser (>= 2.3.3.1, < 3.0)
powerpack (~> 0.1)
rainbow (>= 1.99.1, < 3.0)
ruby-progressbar (~> 1.7)
unicode-display_width (~> 1.0, >= 1.0.1)
ruby-oembed (0.10.1)
ruby-oembed (0.12.0)
ruby-progressbar (1.8.1)
safe_yaml (1.0.4)
sass (3.4.22)
sass (3.4.23)
sass-rails (5.0.6)
railties (>= 4.0.0, < 6)
sass (~> 3.1)
sprockets (>= 2.8, < 4.0)
sprockets-rails (>= 2.0, < 4.0)
tilt (>= 1.1, < 3)
sidekiq (4.2.7)
sidekiq (4.2.10)
concurrent-ruby (~> 1.0)
connection_pool (~> 2.2, >= 2.2.0)
rack-protection (>= 1.5.0)
redis (~> 3.2, >= 3.2.1)
sidekiq-unique-jobs (4.0.18)
sidekiq (>= 2.6)
sidekiq-unique-jobs (5.0.0)
sidekiq (>= 4.0)
thor
simple-navigation (4.0.3)
simple-navigation (4.0.5)
activesupport (>= 2.3.2)
simple_form (3.2.1)
simple_form (3.4.0)
actionpack (> 4, < 5.1)
activemodel (> 4, < 5.1)
simplecov (0.12.0)
simplecov (0.14.1)
docile (~> 1.1.0)
json (>= 1.8, < 3)
simplecov-html (~> 0.10.0)
@@ -403,43 +412,39 @@ GEM
actionpack (>= 4.0)
activesupport (>= 4.0)
sprockets (>= 3.0.0)
sshkit (1.11.5)
sshkit (1.13.1)
net-scp (>= 1.1.2)
net-ssh (>= 2.8.0)
statsd-instrument (2.1.2)
temple (0.7.7)
term-ansicolor (1.4.0)
tins (~> 1.0)
terminal-table (1.7.0)
unicode-display_width (~> 1.1)
temple (0.8.0)
terminal-table (1.7.3)
unicode-display_width (~> 1.1.1)
thor (0.19.4)
thread (0.2.2)
thread_safe (0.3.6)
tilt (2.0.6)
tins (1.12.0)
tilt (2.0.7)
twitter-text (1.14.5)
unf (~> 0.1.0)
tzinfo (1.2.2)
tzinfo (1.2.3)
thread_safe (~> 0.1)
tzinfo-data (1.2017.2)
tzinfo (>= 1.0.0)
uglifier (3.0.1)
uglifier (3.2.0)
execjs (>= 0.3.0, < 3)
unf (0.1.4)
unf_ext
unf_ext (0.0.7.2)
unicode-display_width (1.1.0)
unicode-display_width (1.1.3)
uniform_notifier (1.10.0)
warden (1.2.6)
warden (1.2.7)
rack (>= 1.0)
webmock (2.1.0)
webmock (2.3.2)
addressable (>= 2.3.6)
crack (>= 0.3.2)
hashdiff
websocket-driver (0.6.5)
websocket-extensions (>= 0.1.0)
websocket-extensions (0.1.2)
will_paginate (3.1.0)
PLATFORMS
ruby
@@ -478,6 +483,7 @@ DEPENDENCIES
httplog
i18n-tasks (~> 0.9.6)
jquery-rails
kaminari
letter_opener
letter_opener_web
link_header
@@ -497,6 +503,7 @@ DEPENDENCIES
rack-cors
rack-timeout
rails (~> 5.0.2)
rails-controller-testing
rails-settings-cached
rails_12factor
react-rails
@@ -518,10 +525,9 @@ DEPENDENCIES
tzinfo-data
uglifier (>= 1.3.0)
webmock
will_paginate
RUBY VERSION
ruby 2.3.1p112
ruby 2.4.1p111
BUNDLED WITH
1.14.5
1.14.6

View File

@@ -25,11 +25,11 @@ If you would like, you can [support the development of this project on Patreon][
## Resources
- [List of Mastodon instances](docs/Using-Mastodon/List-of-Mastodon-instances.md)
- [List of Mastodon instances](https://github.com/tootsuite/documentation/blob/master/Using-Mastodon/List-of-Mastodon-instances.md)
- [Use this tool to find Twitter friends on Mastodon](https://mastodon-bridge.herokuapp.com)
- [API overview](docs/Using-the-API/API.md)
- [Frequently Asked Questions](docs/Using-Mastodon/FAQ.md)
- [List of apps](docs/Using-Mastodon/Apps.md)
- [API overview](https://github.com/tootsuite/documentation/blob/master/Using-the-API/API.md)
- [Frequently Asked Questions](https://github.com/tootsuite/documentation/blob/master/Using-Mastodon/FAQ.md)
- [List of apps](https://github.com/tootsuite/documentation/blob/master/Using-Mastodon/Apps.md)
## Features
@@ -67,23 +67,52 @@ Consult the example configuration file, `.env.production.sample` for the full li
[![](https://images.microbadger.com/badges/version/gargron/mastodon.svg)](https://microbadger.com/images/gargron/mastodon "Get your own version badge on microbadger.com") [![](https://images.microbadger.com/badges/image/gargron/mastodon.svg)](https://microbadger.com/images/gargron/mastodon "Get your own image badge on microbadger.com")
The project now includes a `Dockerfile` and a `docker-compose.yml`. You need to turn `.env.production.sample` into `.env.production` with all the variables set before you can:
The project now includes a `Dockerfile` and a `docker-compose.yml` file (which requires at least docker-compose version `1.10.0`).
Review the settings in `docker-compose.yml`. Note that it is not default to store the postgresql database and redis databases in a persistent storage location,
so you may need or want to adjust the settings there.
Before running the first time, you need to build the images:
docker-compose build
And finally
Then, you need to fill in the `.env.production` file:
docker-compose up -d
cp .env.production.sample .env.production
nano .env.production
As usual, the first thing you would need to do would be to run migrations:
Do NOT change the `REDIS_*` or `DB_*` settings when running with the default docker configurations.
You will need to fill in, at least: `LOCAL_DOMAIN`, `LOCAL_HTTPS`, `PAPERCLIP_SECRET`, `SECRET_KEY_BASE`, `OTP_SECRET`, and the `SMTP_*` settings. To generate the `PAPERCLIP_SECRET`, `SECRET_KEY_BASE`, and `OTP_SECRET`, you may use:
docker-compose run --rm web rake secret
Do this once for each of those keys, and copy the result into the `.env.production` file in the appropriate field.
Then you should run the `db:migrate` command to create the database, or migrate it from an older release:
docker-compose run --rm web rails db:migrate
And since the instance running in the container will be running in production mode, you need to pre-compile assets:
Then, you will also need to precompile the assets:
docker-compose run --rm web rails assets:precompile
The container has two volumes, for the assets and for user uploads. The default docker-compose.yml maps them to the repository's `public/assets` and `public/system` directories, you may wish to put them somewhere else. Likewise, the PostgreSQL and Redis images have data containers that you may wish to map somewhere where you know how to find them and back them up.
before you can launch the docker image with:
docker-compose up
If you wish to run this as a daemon process instead of monitoring it on console, use instead:
docker-compose up -d
Then you may login to your new Mastodon instance by browsing to http://localhost:3000/
Following that, make sure that you read the [production guide](docs/Running-Mastodon/Production-guide.md). You are probably going to want to understand how
to configure Nginx to make your Mastodon instance available to the rest of the world.
The container has two volumes, for the assets and for user uploads, and optionally two more, for the postgresql and redis databases.
The default docker-compose.yml maps them to the repository's `public/assets` and `public/system` directories, you may wish to put them somewhere else. Likewise, the PostgreSQL and Redis images have data containers that you may wish to map somewhere where you know how to find them and back them up.
**Note**: The `--rm` option for docker-compose will remove the container that is created to run a one-off command after it completes. As data is stored in volumes it is not affected by that container clean-up.
@@ -103,39 +132,33 @@ Running any of these tasks via docker-compose would look like this:
This approach makes updating to the latest version a real breeze.
git pull
To pull down the updates, re-run
docker-compose build
And finally,
docker-compose up -d
Which will re-create the updated containers, leaving databases and data as is. Depending on what files have been updated, you might need to re-run migrations and asset compilation.
1. `git pull` to download updates from the repository
2. `docker-compose build` to compile the Docker image out of the changed source files
3. (optional) `docker-compose run --rm web rails db:migrate` to perform database migrations. Does nothing if your database is up to date
4. (optional) `docker-compose run --rm web rails assets:precompile` to compile new JS and CSS assets
5. `docker-compose up -d` to re-create (restart) containers and pick up the changes
## Deployment without Docker
Docker is great for quickly trying out software, but it has its drawbacks too. If you prefer to run Mastodon without using Docker, refer to the [production guide](docs/Running-Mastodon/Production-guide.md) for examples, configuration and instructions.
Docker is great for quickly trying out software, but it has its drawbacks too. If you prefer to run Mastodon without using Docker, refer to the [production guide](https://github.com/tootsuite/documentation/blob/master/Running-Mastodon/Production-guide.md) for examples, configuration and instructions.
## Deployment on Scalingo
[![Deploy on Scalingo](https://cdn.scalingo.com/deploy/button.svg)](https://my.scalingo.com/deploy?source=https://github.com/tootsuite/mastodon#master)
[You can view a guide for deployment on Scalingo here.](docs/Running-Mastodon/Scalingo-guide.md)
[You can view a guide for deployment on Scalingo here.](https://github.com/tootsuite/documentation/blob/master/Running-Mastodon/Scalingo-guide.md)
## Deployment on Heroku (experimental)
[![Deploy](https://www.herokucdn.com/deploy/button.svg)](https://heroku.com/deploy)
Mastodon can theoretically run indefinitely on a free [Heroku](https://heroku.com) app. [You can view a guide for deployment on Heroku here.](docs/Running-Mastodon/Heroku-guide.md)
Mastodon can run on [Heroku](https://heroku.com), but it gets expensive and impractical due to how Heroku prices resource usage. [You can view a guide for deployment on Heroku here](https://github.com/tootsuite/documentation/blob/master/Running-Mastodon/Heroku-guide.md), but you have been warned.
## Development with Vagrant
A quick way to get a development environment up and running is with Vagrant. You will need recent versions of [Vagrant](https://www.vagrantup.com/) and [VirtualBox](https://www.virtualbox.org/) installed.
[You can find the guide for setting up a Vagrant development environment here.](docs/Running-Mastodon/Vagrant-guide.md)
[You can find the guide for setting up a Vagrant development environment here.](https://github.com/tootsuite/documentation/blob/master/Running-Mastodon/Vagrant-guide.md)
## Contributing

8
Vagrantfile vendored
View File

@@ -46,12 +46,12 @@ git clone https://github.com/rbenv/ruby-build.git ~/.rbenv/plugins/ruby-build
export PATH="$HOME/.rbenv/bin::$PATH"
eval "$(rbenv init -)"
echo "Compiling Ruby 2.3.1: warning, this takes a while!!!"
rbenv install 2.3.1
rbenv global 2.3.1
cd /vagrant
echo "Compiling Ruby $(cat .ruby-version): warning, this takes a while!!!"
rbenv install $(cat .ruby-version)
rbenv global $(cat .ruby-version)
# Configure database
sudo -u postgres createuser -U postgres vagrant -s
sudo -u postgres createdb -U postgres mastodon_development

View File

@@ -79,6 +79,18 @@
"SMTP_FROM_ADDRESS": {
"description": "Address to send emails from",
"required": false
},
"SMTP_AUTH_METHOD": {
"description": "Authentication method to use with SMTP server. Default is 'plain'.",
"required": false
},
"SMTP_OPENSSL_VERIFY_MODE": {
"description": "SMTP server certificate verification mode. Defaults is 'peer'.",
"required": false
},
"SMTP_ENABLE_STARTTLS_AUTO": {
"description": "Enable STARTTLS if SMTP server supports it? Default is true.",
"required": false
}
},
"buildpacks": [

Binary file not shown.

Before

Width:  |  Height:  |  Size: 209 KiB

After

Width:  |  Height:  |  Size: 258 KiB

View File

@@ -1,4 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" width="1000" height="1000" viewBox="0 0 1000 1000">
<path d="M527.194 543.7a28.362 28.362 0 0 0-56.723 0 25.73 25.73 0 0 0 2.67 11.674 26.42 26.42 0 0 0 5.672 8.34 28.2 28.2 0 0 0 40.04 0 31.87 31.87 0 0 0 6.006-8.34 28.8 28.8 0 0 0 2.336-11.674m-48.382-113.413a28.308 28.308 0 1 0 40.04 40.027 37.2 37.2 0 0 0 4.67-5.67 28.092 28.092 0 0 0 3.67-14.343 27.29 27.29 0 0 0-8.34-20.012 28.24 28.24 0 0 0-5.006-4 26.958 26.958 0 0 0-15.015-4.336 27.31 27.31 0 0 0-20.02 8.34m20.02-101.735a28.476 28.476 0 1 0 20.02 8.34 27.31 27.31 0 0 0-20.02-8.34M231.9 573.717a28.18 28.18 0 1 0 8.342 20.012 27.308 27.308 0 0 0-8.342-20.014m-40.04-93.4a28.352 28.352 0 0 0 20.02 48.366 26.958 26.958 0 0 0 15.015-4.336 28.255 28.255 0 0 0 5.005-4 27.29 27.29 0 0 0 8.342-20.013 28.09 28.09 0 0 0-3.67-14.343 37.21 37.21 0 0 0-4.67-5.67 28.2 28.2 0 0 0-40.04 0m40.04-93.4a28.2 28.2 0 0 0-40.04 0 26.425 26.425 0 0 0-5.673 8.34 25.73 25.73 0 0 0-2.67 11.673 28.315 28.315 0 0 0 48.38 20.018 27.29 27.29 0 0 0 8.342-20.012 28.8 28.8 0 0 0-2.336-11.674 31.87 31.87 0 0 0-6.006-8.34m550.55 178.453a28.476 28.476 0 1 0 20.02 8.34 27.31 27.31 0 0 0-20.02-8.34m20.02-85.057a28.2 28.2 0 0 0-40.04 0 37.2 37.2 0 0 0-4.672 5.67 28.092 28.092 0 0 0-3.67 14.343 27.29 27.29 0 0 0 8.342 20.013 28.248 28.248 0 0 0 5.005 4 26.96 26.96 0 0 0 15.015 4.336 28.3 28.3 0 0 0 20.02-48.366m-46.046-85.057a28.8 28.8 0 0 0-2.336 11.673 28.362 28.362 0 0 0 56.723 0 25.73 25.73 0 0 0-2.668-11.674 26.427 26.427 0 0 0-5.672-8.34 28.2 28.2 0 0 0-40.04 0 31.86 31.86 0 0 0-6.007 8.343z" fill="#2b90d9"/>
<path d="M853.52 146.764Q707.04 0 499.833 0 292.96 0 146.48 146.764 0 293.2 0 500q0 207.138 146.48 353.57T499.833 1000q207.207 0 353.687-146.43T1000 500q0-206.8-146.48-353.236zM213.547 708.806h-3.337q-43.043 0-73.407-30.02-30.03-30.354-30.03-73.382V395.93v-.666q1.335-41.027 30.03-69.713 30.364-30.35 73.407-30.35t73.073 30.354q29.363 29.02 30.364 70.38V615.41q2.336 55.037 46.713 93.4zM600.6 554.7q-1 41.36-30.364 70.38-30.03 30.353-73.073 30.354t-73.407-30.354q-28.7-28.686-30.03-69.713V345.23q0-43.03 30.03-73.382 30.364-30.02 73.407-30.02h150.15q-44.378 38.36-46.713 93.4zm286.954 50.7q0 43.03-30.03 73.382-30.364 30.02-73.407 30.02h-150.15q44.378-38.36 46.713-93.4v-219.47q1-41.362 30.364-70.38 30.03-30.355 73.073-30.355t73.407 30.354q28.7 28.687 30.03 69.714V605.4z" fill="#2b90d9"/>
</svg>
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 1000 1000" height="1000" width="1000"><g fill="#189efc"><path d="M500 0A500 500 0 0 0 0 500a500 500 0 0 0 500 500 500 500 0 0 0 500-500A500 500 0 0 0 500 0zm-2.5 271.1h107.24c-20.56 14.471-27.24 57.064-27.24 78.927v202.145c0 43.726-35.202 78.928-80 78.928s-80-35.202-80-78.928V350.027c0-43.725 35.202-78.927 80-78.927zm-276 48.9c44.798 0 80 35.202 80 78.928v202.144c0 21.863 6.68 64.456 27.24 78.928H221.5c-44.798 0-80-35.202-80-78.928V398.928c0-43.726 35.202-78.928 80-78.928zm550.24 0c44.799 0 80 35.202 80 78.928v202.144c0 43.726-35.201 78.928-80 78.928H664.5c20.56-14.472 27.24-57.065 27.24-78.928V398.928c0-43.726 35.202-78.928 80-78.928z"/><g transform="translate(-2)"><circle cx="223.5" cy="410.5" r="27.5"/><circle cx="223.5" cy="500.5" r="27.5"/><circle cx="223.5" cy="590.5" r="27.5"/></g><g transform="matrix(1 0 0 -1 274 951)"><circle cx="223.5" cy="410.5" r="27.5"/><circle cx="223.5" cy="500.5" r="27.5"/><circle cx="223.5" cy="590.5" r="27.5"/></g><g transform="matrix(-1 0 0 1 995 0)"><circle cx="223.5" cy="410.5" r="27.5"/><circle cx="223.5" cy="500.5" r="27.5"/><circle cx="223.5" cy="590.5" r="27.5"/></g></g></svg>

Before

Width:  |  Height:  |  Size: 2.3 KiB

After

Width:  |  Height:  |  Size: 1.2 KiB

View File

@@ -2,6 +2,8 @@ import api from '../api';
import { updateTimeline } from './timelines';
import * as emojione from 'emojione';
export const COMPOSE_CHANGE = 'COMPOSE_CHANGE';
export const COMPOSE_SUBMIT_REQUEST = 'COMPOSE_SUBMIT_REQUEST';
export const COMPOSE_SUBMIT_SUCCESS = 'COMPOSE_SUBMIT_SUCCESS';
@@ -72,9 +74,8 @@ export function mentionCompose(account, router) {
export function submitCompose() {
return function (dispatch, getState) {
dispatch(submitComposeRequest());
api(getState).post('/api/v1/statuses', {
status: getState().getIn(['compose', 'text'], ''),
status: emojione.shortnameToUnicode(getState().getIn(['compose', 'text'], '')),
in_reply_to_id: getState().getIn(['compose', 'in_reply_to'], null),
media_ids: getState().getIn(['compose', 'media_attachments']).map(item => item.get('id')),
sensitive: getState().getIn(['compose', 'sensitive']),

View File

@@ -50,6 +50,8 @@ export function updateNotifications(notification, intlMessages, intlLocale) {
};
};
const excludeTypesFromSettings = state => state.getIn(['settings', 'notifications', 'shows']).filter(enabled => !enabled).keySeq().toJS();
export function refreshNotifications() {
return (dispatch, getState) => {
dispatch(refreshNotificationsRequest());
@@ -61,6 +63,8 @@ export function refreshNotifications() {
params.since_id = ids.first().get('id');
}
params.exclude_types = excludeTypesFromSettings(getState());
api(getState).get('/api/v1/notifications', { params }).then(response => {
const next = getLinks(response).refs.find(link => link.rel === 'next');
@@ -105,11 +109,11 @@ export function expandNotifications() {
dispatch(expandNotificationsRequest());
api(getState).get(url, {
params: {
limit: 5
}
}).then(response => {
const params = {};
params.exclude_types = excludeTypesFromSettings(getState());
api(getState).get(url, params).then(response => {
const next = getLinks(response).refs.find(link => link.rel === 'next');
dispatch(expandNotificationsSuccess(response.data, next ? next.uri : null));

View File

@@ -7,7 +7,8 @@ export const REPORT_SUBMIT_REQUEST = 'REPORT_SUBMIT_REQUEST';
export const REPORT_SUBMIT_SUCCESS = 'REPORT_SUBMIT_SUCCESS';
export const REPORT_SUBMIT_FAIL = 'REPORT_SUBMIT_FAIL';
export const REPORT_STATUS_TOGGLE = 'REPORT_STATUS_TOGGLE';
export const REPORT_STATUS_TOGGLE = 'REPORT_STATUS_TOGGLE';
export const REPORT_COMMENT_CHANGE = 'REPORT_COMMENT_CHANGE';
export function initReport(account, status) {
return {
@@ -62,3 +63,10 @@ export function submitReportFail(error) {
error
};
};
export function changeReportComment(comment) {
return {
type: REPORT_COMMENT_CHANGE,
comment
};
};

View File

@@ -1,5 +1,5 @@
import axios from 'axios';
import LinkHeader from 'http-link-header';
import LinkHeader from './link_header';
export const getLinks = response => {
const value = response.headers.link;

View File

@@ -65,7 +65,7 @@ const Account = React.createClass({
<div className='account'>
<div style={{ display: 'flex' }}>
<Permalink key={account.get('id')} className='account__display-name' href={account.get('url')} to={`/accounts/${account.get('id')}`}>
<div style={{ float: 'left', marginLeft: '12px', marginRight: '10px' }}><Avatar src={account.get('avatar')} size={36} /></div>
<div style={{ float: 'left', marginLeft: '12px', marginRight: '10px' }}><Avatar src={account.get('avatar')} staticSrc={account.get('avatar_static')} size={36} /></div>
<DisplayName account={account} />
</Permalink>

View File

@@ -1,103 +1,18 @@
import PureRenderMixin from 'react-addons-pure-render-mixin';
// From: http://stackoverflow.com/a/18320662
const resample = (canvas, width, height, resize_canvas) => {
let width_source = canvas.width;
let height_source = canvas.height;
width = Math.round(width);
height = Math.round(height);
let ratio_w = width_source / width;
let ratio_h = height_source / height;
let ratio_w_half = Math.ceil(ratio_w / 2);
let ratio_h_half = Math.ceil(ratio_h / 2);
let ctx = canvas.getContext("2d");
let img = ctx.getImageData(0, 0, width_source, height_source);
let img2 = ctx.createImageData(width, height);
let data = img.data;
let data2 = img2.data;
for (let j = 0; j < height; j++) {
for (let i = 0; i < width; i++) {
let x2 = (i + j * width) * 4;
let weight = 0;
let weights = 0;
let weights_alpha = 0;
let gx_r = 0;
let gx_g = 0;
let gx_b = 0;
let gx_a = 0;
let center_y = (j + 0.5) * ratio_h;
let yy_start = Math.floor(j * ratio_h);
let yy_stop = Math.ceil((j + 1) * ratio_h);
for (let yy = yy_start; yy < yy_stop; yy++) {
let dy = Math.abs(center_y - (yy + 0.5)) / ratio_h_half;
let center_x = (i + 0.5) * ratio_w;
let w0 = dy * dy; //pre-calc part of w
let xx_start = Math.floor(i * ratio_w);
let xx_stop = Math.ceil((i + 1) * ratio_w);
for (let xx = xx_start; xx < xx_stop; xx++) {
let dx = Math.abs(center_x - (xx + 0.5)) / ratio_w_half;
let w = Math.sqrt(w0 + dx * dx);
if (w >= 1) {
// pixel too far
continue;
}
// hermite filter
weight = 2 * w * w * w - 3 * w * w + 1;
let pos_x = 4 * (xx + yy * width_source);
// alpha
gx_a += weight * data[pos_x + 3];
weights_alpha += weight;
// colors
if (data[pos_x + 3] < 255)
weight = weight * data[pos_x + 3] / 250;
gx_r += weight * data[pos_x];
gx_g += weight * data[pos_x + 1];
gx_b += weight * data[pos_x + 2];
weights += weight;
}
}
data2[x2] = gx_r / weights;
data2[x2 + 1] = gx_g / weights;
data2[x2 + 2] = gx_b / weights;
data2[x2 + 3] = gx_a / weights_alpha;
}
}
// clear and resize canvas
if (resize_canvas === true) {
canvas.width = width;
canvas.height = height;
} else {
ctx.clearRect(0, 0, width_source, height_source);
}
// draw
ctx.putImageData(img2, 0, 0);
};
const Avatar = React.createClass({
propTypes: {
src: React.PropTypes.string.isRequired,
staticSrc: React.PropTypes.string,
size: React.PropTypes.number.isRequired,
style: React.PropTypes.object,
animated: React.PropTypes.bool
animate: React.PropTypes.bool
},
getDefaultProps () {
return {
animated: true
animate: false
};
},
@@ -117,38 +32,30 @@ const Avatar = React.createClass({
this.setState({ hovering: false });
},
handleLoad () {
this.canvas.width = this.image.naturalWidth;
this.canvas.height = this.image.naturalHeight;
this.canvas.getContext('2d').drawImage(this.image, 0, 0);
resample(this.canvas, this.props.size * window.devicePixelRatio, this.props.size * window.devicePixelRatio, true);
},
setImageRef (c) {
this.image = c;
},
setCanvasRef (c) {
this.canvas = c;
},
render () {
const { src, size, staticSrc, animate } = this.props;
const { hovering } = this.state;
if (this.props.animated) {
return (
<div style={{ ...this.props.style, width: `${this.props.size}px`, height: `${this.props.size}px` }}>
<img src={this.props.src} width={this.props.size} height={this.props.size} alt='' style={{ borderRadius: '4px' }} />
</div>
);
const style = {
...this.props.style,
width: `${size}px`,
height: `${size}px`,
backgroundSize: `${size}px ${size}px`
};
if (hovering || animate) {
style.backgroundImage = `url(${src})`;
} else {
style.backgroundImage = `url(${staticSrc})`;
}
return (
<div onMouseEnter={this.handleMouseEnter} onMouseLeave={this.handleMouseLeave} style={{ ...this.props.style, width: `${this.props.size}px`, height: `${this.props.size}px`, position: 'relative' }}>
<img ref={this.setImageRef} onLoad={this.handleLoad} src={this.props.src} width={this.props.size} height={this.props.size} alt='' style={{ position: 'absolute', top: '0', left: '0', opacity: hovering ? '1' : '0', borderRadius: '4px' }} />
<canvas ref={this.setCanvasRef} style={{ borderRadius: '4px', width: this.props.size, height: this.props.size, opacity: hovering ? '0' : '1' }} />
</div>
<div
className='avatar'
onMouseEnter={this.handleMouseEnter}
onMouseLeave={this.handleMouseLeave}
style={style}
/>
);
}

View File

@@ -3,15 +3,43 @@ import PureRenderMixin from 'react-addons-pure-render-mixin';
const ExtendedVideoPlayer = React.createClass({
propTypes: {
src: React.PropTypes.string.isRequired
src: React.PropTypes.string.isRequired,
time: React.PropTypes.number,
controls: React.PropTypes.bool.isRequired,
muted: React.PropTypes.bool.isRequired
},
mixins: [PureRenderMixin],
handleLoadedData () {
if (this.props.time) {
this.video.currentTime = this.props.time;
}
},
componentDidMount () {
this.video.addEventListener('loadeddata', this.handleLoadedData);
},
componentWillUnmount () {
this.video.removeEventListener('loadeddata', this.handleLoadedData);
},
setRef (c) {
this.video = c;
},
render () {
return (
<div>
<video src={this.props.src} autoPlay muted loop />
<div className='extended-video-player'>
<video
ref={this.setRef}
src={this.props.src}
autoPlay
muted={this.props.muted}
controls={this.props.controls}
loop={!this.props.controls}
/>
</div>
);
},

View File

@@ -13,7 +13,8 @@ const IconButton = React.createClass({
activeStyle: React.PropTypes.object,
disabled: React.PropTypes.bool,
inverted: React.PropTypes.bool,
animate: React.PropTypes.bool
animate: React.PropTypes.bool,
overlay: React.PropTypes.bool
},
getDefaultProps () {
@@ -21,7 +22,8 @@ const IconButton = React.createClass({
size: 18,
active: false,
disabled: false,
animate: false
animate: false,
overlay: false
};
},
@@ -31,7 +33,7 @@ const IconButton = React.createClass({
e.preventDefault();
if (!this.props.disabled) {
this.props.onClick();
this.props.onClick(e);
}
},
@@ -39,7 +41,7 @@ const IconButton = React.createClass({
let style = {
fontSize: `${this.props.size}px`,
width: `${this.props.size * 1.28571429}px`,
height: `${this.props.size}px`,
height: `${this.props.size * 1.28571429}px`,
lineHeight: `${this.props.size}px`,
...this.props.style
};
@@ -48,13 +50,31 @@ const IconButton = React.createClass({
style = { ...style, ...this.props.activeStyle };
}
const classes = ['icon-button'];
if (this.props.active) {
classes.push('active');
}
if (this.props.disabled) {
classes.push('disabled');
}
if (this.props.inverted) {
classes.push('inverted');
}
if (this.props.overlay) {
classes.push('overlayed');
}
return (
<Motion defaultStyle={{ rotate: this.props.active ? -360 : 0 }} style={{ rotate: this.props.animate ? spring(this.props.active ? -360 : 0, { stiffness: 120, damping: 7 }) : 0 }}>
{({ rotate }) =>
<button
aria-label={this.props.title}
title={this.props.title}
className={`icon-button ${this.props.active ? 'active' : ''} ${this.props.disabled ? 'disabled' : ''} ${this.props.inverted ? 'inverted' : ''}`}
className={classes.join(' ')}
onClick={this.handleClick}
style={style}>
<i style={{ transform: `rotate(${rotate}deg)` }} className={`fa fa-fw fa-${this.props.icon}`} aria-hidden='true' />

View File

@@ -39,8 +39,8 @@ const spoilerSubSpanStyle = {
const spoilerButtonStyle = {
position: 'absolute',
top: '6px',
left: '8px',
top: '4px',
left: '4px',
zIndex: '100'
};
@@ -232,8 +232,8 @@ const MediaGallery = React.createClass({
return (
<div style={{ ...outerStyle, height: `${this.props.height}px` }}>
<div style={spoilerButtonStyle}>
<IconButton title={intl.formatMessage(messages.toggle_visible)} icon={this.state.visible ? 'eye' : 'eye-slash'} onClick={this.handleOpen} />
<div style={{ ...spoilerButtonStyle, display: !this.state.visible ? 'none' : 'block' }}>
<IconButton title={intl.formatMessage(messages.toggle_visible)} icon={this.state.visible ? 'eye' : 'eye-slash'} overlay onClick={this.handleOpen} />
</div>
{children}

View File

@@ -25,8 +25,10 @@ const Status = React.createClass({
onReblog: React.PropTypes.func,
onDelete: React.PropTypes.func,
onOpenMedia: React.PropTypes.func,
onOpenVideo: React.PropTypes.func,
onBlock: React.PropTypes.func,
me: React.PropTypes.number,
boostModal: React.PropTypes.bool,
muted: React.PropTypes.bool
},
@@ -75,7 +77,7 @@ const Status = React.createClass({
if (status.get('media_attachments').size > 0 && !this.props.muted) {
if (status.getIn(['media_attachments', 0, 'type']) === 'video') {
media = <VideoPlayer media={status.getIn(['media_attachments', 0])} sensitive={status.get('sensitive')} />;
media = <VideoPlayer media={status.getIn(['media_attachments', 0])} sensitive={status.get('sensitive')} onOpenVideo={this.props.onOpenVideo} />;
} else {
media = <MediaGallery media={status.get('media_attachments')} sensitive={status.get('sensitive')} height={110} onOpenMedia={this.props.onOpenMedia} />;
}
@@ -90,7 +92,7 @@ const Status = React.createClass({
<a onClick={this.handleAccountClick.bind(this, status.getIn(['account', 'id']))} href={status.getIn(['account', 'url'])} className='status__display-name' style={{ display: 'block', maxWidth: '100%', paddingRight: '25px' }}>
<div className='status__avatar' style={{ position: 'absolute', left: '10px', top: '10px', width: '48px', height: '48px' }}>
<Avatar src={status.getIn(['account', 'avatar'])} size={48} />
<Avatar src={status.getIn(['account', 'avatar'])} staticSrc={status.getIn(['account', 'avatar_static'])} size={48} />
</div>
<DisplayName account={status.get('account')} />

View File

@@ -46,8 +46,8 @@ const StatusActionBar = React.createClass({
this.props.onFavourite(this.props.status);
},
handleReblogClick () {
this.props.onReblog(this.props.status);
handleReblogClick (e) {
this.props.onReblog(this.props.status, e);
},
handleDeleteClick () {

View File

@@ -36,6 +36,7 @@ const StatusContent = React.createClass({
if (mention) {
link.addEventListener('click', this.onMentionClick.bind(this, mention), false);
link.setAttribute('title', mention.get('acct'));
} else if (link.textContent[0] === '#' || (link.previousSibling && link.previousSibling.textContent && link.previousSibling.textContent[link.previousSibling.textContent.length - 1] === '#')) {
link.addEventListener('click', this.onHashtagClick.bind(this, link.text), false);
} else if (media) {
@@ -125,7 +126,7 @@ const StatusContent = React.createClass({
<div style={{ display: hidden ? 'none' : 'block', ...directionStyle }} dangerouslySetInnerHTML={content} />
</div>
);
} else {
} else if (this.props.onClick) {
return (
<div
className='status__content'
@@ -135,6 +136,14 @@ const StatusContent = React.createClass({
dangerouslySetInnerHTML={content}
/>
);
} else {
return (
<div
className='status__content'
style={{ ...directionStyle }}
dangerouslySetInnerHTML={content}
/>
);
}
},

View File

@@ -6,7 +6,8 @@ import { isIOS } from '../is_mobile';
const messages = defineMessages({
toggle_sound: { id: 'video_player.toggle_sound', defaultMessage: 'Toggle sound' },
toggle_visible: { id: 'video_player.toggle_visible', defaultMessage: 'Toggle visibility' }
toggle_visible: { id: 'video_player.toggle_visible', defaultMessage: 'Toggle visibility' },
expand_video: { id: 'video_player.expand', defaultMessage: 'Expand video' }
});
const videoStyle = {
@@ -21,8 +22,8 @@ const videoStyle = {
const muteStyle = {
position: 'absolute',
top: '10px',
right: '10px',
top: '4px',
right: '4px',
color: 'white',
textShadow: "0px 1px 1px black, 1px 0px 1px black",
opacity: '0.8',
@@ -54,8 +55,17 @@ const spoilerSubSpanStyle = {
const spoilerButtonStyle = {
position: 'absolute',
top: '6px',
left: '8px',
top: '4px',
left: '4px',
color: 'white',
textShadow: "0px 1px 1px black, 1px 0px 1px black",
zIndex: '100'
};
const expandButtonStyle = {
position: 'absolute',
bottom: '4px',
right: '4px',
color: 'white',
textShadow: "0px 1px 1px black, 1px 0px 1px black",
zIndex: '100'
@@ -68,7 +78,8 @@ const VideoPlayer = React.createClass({
height: React.PropTypes.number,
sensitive: React.PropTypes.bool,
intl: React.PropTypes.object.isRequired,
autoplay: React.PropTypes.bool
autoplay: React.PropTypes.bool,
onOpenVideo: React.PropTypes.func.isRequired
},
getDefaultProps () {
@@ -116,6 +127,11 @@ const VideoPlayer = React.createClass({
});
},
handleExpand () {
this.video.pause();
this.props.onOpenVideo(this.props.media, this.video.currentTime);
},
setRef (c) {
this.video = c;
},
@@ -154,8 +170,14 @@ const VideoPlayer = React.createClass({
const { media, intl, width, height, sensitive, autoplay } = this.props;
let spoilerButton = (
<div style={spoilerButtonStyle} >
<IconButton title={intl.formatMessage(messages.toggle_visible)} icon={this.state.visible ? 'eye' : 'eye-slash'} onClick={this.handleVisibility} />
<div style={{...spoilerButtonStyle, display: !this.state.visible ? 'none' : 'block'}} >
<IconButton overlay title={intl.formatMessage(messages.toggle_visible)} icon={this.state.visible ? 'eye' : 'eye-slash'} onClick={this.handleVisibility} />
</div>
);
let expandButton = (
<div style={expandButtonStyle} >
<IconButton overlay title={intl.formatMessage(messages.expand_video)} icon='expand' onClick={this.handleExpand} />
</div>
);
@@ -164,7 +186,7 @@ const VideoPlayer = React.createClass({
if (this.state.hasAudio) {
muteButton = (
<div style={muteStyle}>
<IconButton title={intl.formatMessage(messages.toggle_sound)} icon={this.state.muted ? 'volume-off' : 'volume-up'} onClick={this.handleClick} />
<IconButton overlay title={intl.formatMessage(messages.toggle_sound)} icon={this.state.muted ? 'volume-off' : 'volume-up'} onClick={this.handleClick} />
</div>
);
}
@@ -202,6 +224,7 @@ const VideoPlayer = React.createClass({
<div style={{ cursor: 'default', marginTop: '8px', overflow: 'hidden', width: `${width}px`, height: `${height}px`, boxSizing: 'border-box', background: '#000', position: 'relative' }}>
{spoilerButton}
{muteButton}
{expandButton}
<video ref={this.setRef} src={media.get('url')} autoPlay={!isIOS()} loop={true} muted={this.state.muted} style={videoStyle} onClick={this.handleVideoClick} />
</div>
);

View File

@@ -41,13 +41,20 @@ import Report from '../features/report';
import { IntlProvider, addLocaleData } from 'react-intl';
import en from 'react-intl/locale-data/en';
import de from 'react-intl/locale-data/de';
import es from 'react-intl/locale-data/es';
import fr from 'react-intl/locale-data/fr';
import pt from 'react-intl/locale-data/pt';
import hu from 'react-intl/locale-data/hu';
import uk from 'react-intl/locale-data/uk';
import fi from 'react-intl/locale-data/fi';
import eo from 'react-intl/locale-data/eo';
import es from 'react-intl/locale-data/es';
import fi from 'react-intl/locale-data/fi';
import fr from 'react-intl/locale-data/fr';
import hu from 'react-intl/locale-data/hu';
import ja from 'react-intl/locale-data/ja';
import pt from 'react-intl/locale-data/pt';
import nl from 'react-intl/locale-data/nl';
import no from 'react-intl/locale-data/no';
import ru from 'react-intl/locale-data/ru';
import uk from 'react-intl/locale-data/uk';
import zh from 'react-intl/locale-data/zh';
import bg from 'react-intl/locale-data/bg';
import { localeData as zh_hk } from '../locales/zh-hk';
import getMessagesForLocale from '../locales';
import { hydrateStore } from '../actions/store';
import createStream from '../stream';
@@ -60,7 +67,24 @@ const browserHistory = useRouterHistory(createBrowserHistory)({
basename: '/web'
});
addLocaleData([...en, ...de, ...es, ...fr, ...pt, ...hu, ...uk, ...fi, ...eo]);
addLocaleData([
...en,
...de,
...eo,
...es,
...fi,
...fr,
...hu,
...ja,
...pt,
...nl,
...no,
...ru,
...uk,
...zh,
...zh_hk,
...bg,
]);
const Mastodon = React.createClass({

View File

@@ -26,7 +26,8 @@ const makeMapStateToProps = () => {
const mapStateToProps = (state, props) => ({
status: getStatus(state, props.id),
me: state.getIn(['meta', 'me'])
me: state.getIn(['meta', 'me']),
boostModal: state.getIn(['meta', 'boost_modal'])
});
return mapStateToProps;
@@ -38,11 +39,19 @@ const mapDispatchToProps = (dispatch) => ({
dispatch(replyCompose(status, router));
},
onReblog (status) {
onModalReblog (status) {
dispatch(reblog(status));
},
onReblog (status, e) {
if (status.get('reblogged')) {
dispatch(unreblog(status));
} else {
dispatch(reblog(status));
if (e.shiftKey || !this.boostModal) {
this.onModalReblog(status);
} else {
dispatch(openModal('BOOST', { status, onReblog: this.onModalReblog }));
}
}
},
@@ -66,6 +75,10 @@ const mapDispatchToProps = (dispatch) => ({
dispatch(openModal('MEDIA', { media, index }));
},
onOpenVideo (media, time) {
dispatch(openModal('VIDEO', { media, time }));
},
onBlock (account) {
dispatch(blockAccount(account.get('id')));
},

View File

@@ -14,7 +14,7 @@ import ColumnBackButtonSlim from '../../components/column_back_button_slim';
import createStream from '../../stream';
const messages = defineMessages({
title: { id: 'column.community', defaultMessage: 'Local' }
title: { id: 'column.community', defaultMessage: 'Local timeline' }
});
const mapStateToProps = state => ({

View File

@@ -4,7 +4,7 @@ import ImmutablePropTypes from 'react-immutable-proptypes';
const AutosuggestAccount = ({ account }) => (
<div style={{ overflow: 'hidden' }} className='autosuggest-account'>
<div style={{ float: 'left', marginRight: '5px' }}><Avatar src={account.get('avatar')} size={18} /></div>
<div style={{ float: 'left', marginRight: '5px' }}><Avatar src={account.get('avatar')} staticSrc={account.get('avatar_static')} size={18} /></div>
<DisplayName account={account} />
</div>
);

View File

@@ -19,7 +19,7 @@ import TextIconButton from './text_icon_button';
const messages = defineMessages({
placeholder: { id: 'compose_form.placeholder', defaultMessage: 'What is on your mind?' },
spoiler_placeholder: { id: 'compose_form.spoiler_placeholder', defaultMessage: 'Content warning' },
publish: { id: 'compose_form.publish', defaultMessage: 'Publish' }
publish: { id: 'compose_form.publish', defaultMessage: 'Toot' }
});
const ComposeForm = React.createClass({
@@ -83,11 +83,23 @@ const ComposeForm = React.createClass({
this.props.onChangeSpoilerText(e.target.value);
},
componentWillReceiveProps (nextProps) {
// If this is the update where we've finished uploading,
// save the last caret position so we can restore it below!
if (!nextProps.is_uploading && this.props.is_uploading) {
this._restoreCaret = this.autosuggestTextarea.textarea.selectionStart;
}
},
componentDidUpdate (prevProps) {
if (this.props.focusDate !== prevProps.focusDate) {
// If replying to zero or one users, places the cursor at the end of the textbox.
// If replying to more than one user, selects any usernames past the first;
// this provides a convenient shortcut to drop everyone else from the conversation.
// This statement does several things:
// - If we're beginning a reply, and,
// - Replying to zero or one users, places the cursor at the end of the textbox.
// - Replying to more than one user, selects any usernames past the first;
// this provides a convenient shortcut to drop everyone else from the conversation.
// - If we've just finished uploading an image, and have a saved caret position,
// restores the cursor to that position after the text changes!
if (this.props.focusDate !== prevProps.focusDate || (prevProps.is_uploading && !this.props.is_uploading && typeof this._restoreCaret === 'number')) {
let selectionEnd, selectionStart;
if (this.props.preselectDate !== prevProps.preselectDate) {
@@ -118,7 +130,7 @@ const ComposeForm = React.createClass({
render () {
const { intl, needsPrivacyWarning, mentionedDomains, onPaste } = this.props;
const disabled = this.props.is_submitting || this.props.is_uploading;
const disabled = this.props.is_submitting;
let publishText = '';
let privacyWarning = '';

View File

@@ -46,8 +46,8 @@ const EmojiPickerDropdown = React.createClass({
<img draggable="false" className="emojione" alt="🙂" src="/emoji/1f602.svg" />
</DropdownTrigger>
<DropdownContent className='dropdown__left'>
<EmojiPicker emojione={settings} onChange={this.handleChange} />
<DropdownContent className='dropdown__left light'>
<EmojiPicker emojione={settings} onChange={this.handleChange} search={true} />
</DropdownContent>
</Dropdown>
);

View File

@@ -17,7 +17,7 @@ const NavigationBar = React.createClass({
render () {
return (
<div className='navigation-bar'>
<Permalink href={this.props.account.get('url')} to={`/accounts/${this.props.account.get('id')}`} style={{ textDecoration: 'none' }}><Avatar src={this.props.account.get('avatar')} size={40} /></Permalink>
<Permalink href={this.props.account.get('url')} to={`/accounts/${this.props.account.get('id')}`} style={{ textDecoration: 'none' }}><Avatar src={this.props.account.get('avatar')} animate size={40} /></Permalink>
<div style={{ flex: '1 1 auto', marginLeft: '8px' }}>
<strong style={{ fontWeight: '500', display: 'block' }}>{this.props.account.get('acct')}</strong>

View File

@@ -50,7 +50,7 @@ const ReplyIndicator = React.createClass({
<div style={{ float: 'right', lineHeight: '24px' }}><IconButton title={intl.formatMessage(messages.cancel)} icon='times' onClick={this.handleClick} /></div>
<a href={status.getIn(['account', 'url'])} onClick={this.handleAccountClick} className='reply-indicator__display-name' style={{ display: 'block', maxWidth: '100%', paddingRight: '25px', textDecoration: 'none', overflow: 'hidden', lineHeight: '24px' }}>
<div style={{ float: 'left', marginRight: '5px' }}><Avatar size={24} src={status.getIn(['account', 'avatar'])} /></div>
<div style={{ float: 'left', marginRight: '5px' }}><Avatar size={24} src={status.getIn(['account', 'avatar'])} staticSrc={status.getIn(['account', 'avatar_static'])} /></div>
<DisplayName account={status.get('account')} />
</a>
</div>

View File

@@ -12,7 +12,7 @@ import SearchResultsContainer from './containers/search_results_container';
const messages = defineMessages({
start: { id: 'getting_started.heading', defaultMessage: 'Getting started' },
public: { id: 'navigation_bar.public_timeline', defaultMessage: 'Whole Known Network' },
public: { id: 'navigation_bar.public_timeline', defaultMessage: 'Federated timeline' },
community: { id: 'navigation_bar.community_timeline', defaultMessage: 'Local timeline' },
preferences: { id: 'navigation_bar.preferences', defaultMessage: 'Preferences' },
logout: { id: 'navigation_bar.logout', defaultMessage: 'Logout' }

View File

@@ -33,7 +33,7 @@ const AccountAuthorize = ({ intl, account, onAuthorize, onReject }) => {
<div>
<div style={outerStyle}>
<Permalink href={account.get('url')} to={`/accounts/${account.get('id')}`} className='detailed-status__display-name' style={{ display: 'block', overflow: 'hidden', marginBottom: '15px' }}>
<div style={{ float: 'left', marginRight: '10px' }}><Avatar src={account.get('avatar')} size={48} /></div>
<div style={{ float: 'left', marginRight: '10px' }}><Avatar src={account.get('avatar')} staticSrc={account.get('avatar_static')} size={48} /></div>
<DisplayName account={account} />
</Permalink>

View File

@@ -7,11 +7,11 @@ import ImmutablePropTypes from 'react-immutable-proptypes';
const messages = defineMessages({
heading: { id: 'getting_started.heading', defaultMessage: 'Getting started' },
public_timeline: { id: 'navigation_bar.public_timeline', defaultMessage: 'Whole Known Network' },
public_timeline: { id: 'navigation_bar.public_timeline', defaultMessage: 'Federated timeline' },
community_timeline: { id: 'navigation_bar.community_timeline', defaultMessage: 'Local timeline' },
preferences: { id: 'navigation_bar.preferences', defaultMessage: 'Preferences' },
follow_requests: { id: 'navigation_bar.follow_requests', defaultMessage: 'Follow requests' },
sign_out: { id: 'navigation_bar.logout', defaultMessage: 'Sign out' },
sign_out: { id: 'navigation_bar.logout', defaultMessage: 'Logout' },
favourites: { id: 'navigation_bar.favourites', defaultMessage: 'Favourites' },
blocks: { id: 'navigation_bar.blocks', defaultMessage: 'Blocked users' },
info: { id: 'navigation_bar.info', defaultMessage: 'Extended information' }
@@ -43,7 +43,7 @@ const GettingStarted = ({ intl, me }) => {
<div className='scrollable optionally-scrollable' style={{ display: 'flex', flexDirection: 'column' }}>
<div className='static-content getting-started'>
<p><FormattedMessage id='getting_started.open_source_notice' defaultMessage='Mastodon is open source software. You can contribute or report issues on GitHub at {github}. {apps}.' values={{ github: <a href="https://github.com/tootsuite/mastodon" target="_blank">tootsuite/mastodon</a>, apps: <a href="https://github.com/tootsuite/mastodon/blob/master/docs/Using-Mastodon/Apps.md" target="_blank"><FormattedMessage id='getting_started.apps' defaultMessage='Various apps are available' /></a> }} /></p>
<p><FormattedMessage id='getting_started.open_source_notice' defaultMessage='Mastodon is open source software. You can contribute or report issues on GitHub at {github}. {apps}.' values={{ github: <a href="https://github.com/tootsuite/mastodon" target="_blank">tootsuite/mastodon</a>, apps: <a href="https://github.com/tootsuite/documentation/blob/master/Using-Mastodon/Apps.md" target="_blank"><FormattedMessage id='getting_started.apps' defaultMessage='Various apps are available' /></a> }} /></p>
</div>
</div>
</Column>

View File

@@ -4,16 +4,6 @@ const messages = defineMessages({
clear: { id: 'notifications.clear', defaultMessage: 'Clear notifications' }
});
const iconStyle = {
fontSize: '16px',
padding: '15px',
position: 'absolute',
right: '48px',
top: '0',
cursor: 'pointer',
zIndex: '2'
};
const ClearColumnButton = React.createClass({
propTypes: {
@@ -25,7 +15,7 @@ const ClearColumnButton = React.createClass({
const { intl } = this.props;
return (
<div title={intl.formatMessage(messages.clear)} className='column-icon' tabIndex='0' style={iconStyle} onClick={this.props.onClick}>
<div title={intl.formatMessage(messages.clear)} className='column-icon column-icon-clear' tabIndex='0' onClick={this.props.onClick}>
<i className='fa fa-eraser' />
</div>
);

View File

@@ -27,8 +27,9 @@ const ColumnSettings = React.createClass({
propTypes: {
settings: ImmutablePropTypes.map.isRequired,
intl: React.PropTypes.object.isRequired,
onChange: React.PropTypes.func.isRequired,
onSave: React.PropTypes.func.isRequired
onSave: React.PropTypes.func.isRequired,
},
mixins: [PureRenderMixin],

View File

@@ -21,7 +21,7 @@ const Notification = React.createClass({
renderFollow (account, link) {
return (
<div className='notification'>
<div className='notification notification-follow'>
<div className='notification__message'>
<div style={{ position: 'absolute', 'left': '-26px'}}>
<i className='fa fa-fw fa-user-plus' />
@@ -41,7 +41,7 @@ const Notification = React.createClass({
renderFavourite (notification, link) {
return (
<div className='notification'>
<div className='notification notification-favourite'>
<div className='notification__message'>
<div style={{ position: 'absolute', 'left': '-26px'}}>
<i className='fa fa-fw fa-star' style={{ color: '#ca8f04' }} />
@@ -57,7 +57,7 @@ const Notification = React.createClass({
renderReblog (notification, link) {
return (
<div className='notification'>
<div className='notification notification-reblog'>
<div className='notification__message'>
<div style={{ position: 'absolute', 'left': '-26px'}}>
<i className='fa fa-fw fa-retweet' />
@@ -76,17 +76,17 @@ const Notification = React.createClass({
const account = notification.get('account');
const displayName = account.get('display_name').length > 0 ? account.get('display_name') : account.get('username');
const displayNameHTML = { __html: emojify(escapeTextContentForBrowser(displayName)) };
const link = <Permalink className='notification__display-name' style={linkStyle} href={account.get('url')} to={`/accounts/${account.get('id')}`} dangerouslySetInnerHTML={displayNameHTML} />;
const link = <Permalink className='notification__display-name' style={linkStyle} href={account.get('url')} title={account.get('acct')} to={`/accounts/${account.get('id')}`} dangerouslySetInnerHTML={displayNameHTML} />;
switch(notification.get('type')) {
case 'follow':
return this.renderFollow(account, link);
case 'mention':
return this.renderMention(notification);
case 'favourite':
return this.renderFavourite(notification, link);
case 'reblog':
return this.renderReblog(notification, link);
case 'follow':
return this.renderFollow(account, link);
case 'mention':
return this.renderMention(notification);
case 'favourite':
return this.renderFavourite(notification, link);
case 'reblog':
return this.renderReblog(notification, link);
}
}

View File

@@ -14,7 +14,7 @@ import ColumnBackButtonSlim from '../../components/column_back_button_slim';
import createStream from '../../stream';
const messages = defineMessages({
title: { id: 'column.public', defaultMessage: 'Whole Known Network' }
title: { id: 'column.public', defaultMessage: 'Federated timeline' }
});
const mapStateToProps = state => ({

View File

@@ -47,7 +47,7 @@ const Report = React.createClass({
propTypes: {
isSubmitting: React.PropTypes.bool,
account: ImmutablePropTypes.map,
statusIds: ImmutablePropTypes.list.isRequired,
statusIds: ImmutablePropTypes.orderedSet.isRequired,
comment: React.PropTypes.string.isRequired,
dispatch: React.PropTypes.func.isRequired,
intl: React.PropTypes.object.isRequired
@@ -94,7 +94,8 @@ const Report = React.createClass({
return (
<Column heading={intl.formatMessage(messages.heading)} icon='flag'>
<ColumnBackButtonSlim />
<div className='report' style={{ display: 'flex', flexDirection: 'column', maxHeight: '100%', boxSizing: 'border-box' }}>
<div className='report scrollable' style={{ display: 'flex', flexDirection: 'column', maxHeight: '100%', boxSizing: 'border-box' }}>
<div className='report__target' style={{ flex: '0 0 auto', padding: '10px' }}>
<FormattedMessage id='report.target' defaultMessage='Reporting' />
<strong>{account.get('acct')}</strong>
@@ -106,7 +107,7 @@ const Report = React.createClass({
</div>
</div>
<div style={{ flex: '0 0 160px', padding: '10px' }}>
<div style={{ flex: '0 0 100px', padding: '10px' }}>
<textarea
className='report__textarea'
placeholder={intl.formatMessage(messages.placeholder)}

View File

@@ -37,8 +37,8 @@ const ActionBar = React.createClass({
this.props.onReply(this.props.status);
},
handleReblogClick () {
this.props.onReblog(this.props.status);
handleReblogClick (e) {
this.props.onReblog(this.props.status, e);
},
handleFavouriteClick () {

View File

@@ -17,7 +17,8 @@ const DetailedStatus = React.createClass({
propTypes: {
status: ImmutablePropTypes.map.isRequired,
onOpenMedia: React.PropTypes.func.isRequired
onOpenMedia: React.PropTypes.func.isRequired,
onOpenVideo: React.PropTypes.func.isRequired,
},
mixins: [PureRenderMixin],
@@ -39,7 +40,7 @@ const DetailedStatus = React.createClass({
if (status.get('media_attachments').size > 0) {
if (status.getIn(['media_attachments', 0, 'type']) === 'video') {
media = <VideoPlayer sensitive={status.get('sensitive')} media={status.getIn(['media_attachments', 0])} width={300} height={150} autoplay />;
media = <VideoPlayer sensitive={status.get('sensitive')} media={status.getIn(['media_attachments', 0])} width={300} height={150} onOpenVideo={this.props.onOpenVideo} autoplay />;
} else {
media = <MediaGallery sensitive={status.get('sensitive')} media={status.get('media_attachments')} height={300} onOpenMedia={this.props.onOpenMedia} />;
}
@@ -54,7 +55,7 @@ const DetailedStatus = React.createClass({
return (
<div style={{ padding: '14px 10px' }} className='detailed-status'>
<a href={status.getIn(['account', 'url'])} onClick={this.handleAccountClick} className='detailed-status__display-name' style={{ display: 'block', overflow: 'hidden', marginBottom: '15px' }}>
<div style={{ float: 'left', marginRight: '10px' }}><Avatar src={status.getIn(['account', 'avatar'])} size={48} /></div>
<div style={{ float: 'left', marginRight: '10px' }}><Avatar src={status.getIn(['account', 'avatar'])} staticSrc={status.getIn(['account', 'avatar_static'])} size={48} /></div>
<DisplayName account={status.get('account')} />
</a>

View File

@@ -38,7 +38,8 @@ const makeMapStateToProps = () => {
status: getStatus(state, Number(props.params.statusId)),
ancestorsIds: state.getIn(['timelines', 'ancestors', Number(props.params.statusId)]),
descendantsIds: state.getIn(['timelines', 'descendants', Number(props.params.statusId)]),
me: state.getIn(['meta', 'me'])
me: state.getIn(['meta', 'me']),
boostModal: state.getIn(['meta', 'boost_modal'])
});
return mapStateToProps;
@@ -55,7 +56,8 @@ const Status = React.createClass({
status: ImmutablePropTypes.map,
ancestorsIds: ImmutablePropTypes.list,
descendantsIds: ImmutablePropTypes.list,
me: React.PropTypes.number
me: React.PropTypes.number,
boostModal: React.PropTypes.bool
},
mixins: [PureRenderMixin],
@@ -82,11 +84,19 @@ const Status = React.createClass({
this.props.dispatch(replyCompose(status, this.context.router));
},
handleReblogClick (status) {
handleModalReblog (status) {
this.props.dispatch(reblog(status));
},
handleReblogClick (status, e) {
if (status.get('reblogged')) {
this.props.dispatch(unreblog(status));
} else {
this.props.dispatch(reblog(status));
if (e.shiftKey || !this.props.boostModal) {
this.handleModalReblog(status);
} else {
this.props.dispatch(openModal('BOOST', { status, onReblog: this.handleModalReblog }));
}
}
},
@@ -102,6 +112,10 @@ const Status = React.createClass({
this.props.dispatch(openModal('MEDIA', { media, index }));
},
handleOpenVideo (media, time) {
this.props.dispatch(openModal('VIDEO', { media, time }));
},
handleReport (status) {
this.props.dispatch(initReport(status.get('account'), status));
},
@@ -141,7 +155,7 @@ const Status = React.createClass({
<div className='scrollable'>
{ancestors}
<DetailedStatus status={status} me={me} onOpenMedia={this.handleOpenMedia} />
<DetailedStatus status={status} me={me} onOpenVideo={this.handleOpenVideo} onOpenMedia={this.handleOpenMedia} />
<ActionBar status={status} me={me} onReply={this.handleReplyClick} onFavourite={this.handleFavouriteClick} onReblog={this.handleReblogClick} onDelete={this.handleDeleteClick} onMention={this.handleMentionClick} onReport={this.handleReport} />
{descendants}

View File

@@ -0,0 +1,77 @@
import PureRenderMixin from 'react-addons-pure-render-mixin';
import ImmutablePropTypes from 'react-immutable-proptypes';
import { defineMessages, injectIntl, FormattedMessage } from 'react-intl';
import IconButton from '../../../components/icon_button';
import Button from '../../../components/button';
import StatusContent from '../../../components/status_content';
import Avatar from '../../../components/avatar';
import RelativeTimestamp from '../../../components/relative_timestamp';
import DisplayName from '../../../components/display_name';
const messages = defineMessages({
reblog: { id: 'status.reblog', defaultMessage: 'Boost' }
});
const BoostModal = React.createClass({
contextTypes: {
router: React.PropTypes.object
},
propTypes: {
status: ImmutablePropTypes.map.isRequired,
onReblog: React.PropTypes.func.isRequired,
onClose: React.PropTypes.func.isRequired,
intl: React.PropTypes.object.isRequired
},
mixins: [PureRenderMixin],
handleReblog() {
this.props.onReblog(this.props.status);
this.props.onClose();
},
handleAccountClick (e) {
if (e.button === 0) {
e.preventDefault();
this.props.onClose();
this.context.router.push(`/accounts/${this.props.status.getIn(['account', 'id'])}`);
}
},
render () {
const { status, intl, onClose } = this.props;
return (
<div className='modal-root__modal boost-modal'>
<div className='boost-modal__container'>
<div className='status light'>
<div style={{ fontSize: '15px' }}>
<div style={{ float: 'right', fontSize: '14px' }}>
<a href={status.get('url')} className='status__relative-time' target='_blank' rel='noopener'><RelativeTimestamp timestamp={status.get('created_at')} /></a>
</div>
<a onClick={this.handleAccountClick} href={status.getIn(['account', 'url'])} className='status__display-name' style={{ display: 'block', maxWidth: '100%', paddingRight: '25px' }}>
<div className='status__avatar' style={{ position: 'absolute', left: '10px', top: '10px', width: '48px', height: '48px' }}>
<Avatar src={status.getIn(['account', 'avatar'])} staticSrc={status.getIn(['account', 'avatar_static'])} size={48} />
</div>
<DisplayName account={status.get('account')} />
</a>
</div>
<StatusContent status={status} />
</div>
</div>
<div className='boost-modal__action-bar'>
<div><FormattedMessage id='boost_modal.combo' defaultMessage='You can press {combo} to skip this next time' values={{ combo: <span>Shift + <i className='fa fa-retweet' /></span> }} /></div>
<Button text={intl.formatMessage(messages.reblog)} onClick={this.handleReblog} />
</div>
</div>
);
}
});
export default injectIntl(BoostModal);

View File

@@ -41,8 +41,11 @@ const Column = React.createClass({
mixins: [PureRenderMixin],
handleHeaderClick () {
let node = ReactDOM.findDOMNode(this);
this._interruptScrollAnimation = scrollTop(node.querySelector('.scrollable'));
const scrollable = ReactDOM.findDOMNode(this).querySelector('.scrollable');
if (!scrollable) {
return;
}
this._interruptScrollAnimation = scrollTop(scrollable);
},
handleWheel () {

View File

@@ -111,7 +111,7 @@ const MediaModal = React.createClass({
if (attachment.get('type') === 'image') {
content = <ImageLoader src={url} imgProps={{ style: { display: 'block' } }} />;
} else if (attachment.get('type') === 'gifv') {
content = <ExtendedVideoPlayer src={url} />;
content = <ExtendedVideoPlayer src={url} muted={true} controls={false} />;
}
return (

View File

@@ -1,9 +1,13 @@
import PureRenderMixin from 'react-addons-pure-render-mixin';
import MediaModal from './media_modal';
import VideoModal from './video_modal';
import BoostModal from './boost_modal';
import { TransitionMotion, spring } from 'react-motion';
const MODAL_COMPONENTS = {
'MEDIA': MediaModal
'MEDIA': MediaModal,
'VIDEO': VideoModal,
'BOOST': BoostModal
};
const ModalRoot = React.createClass({

View File

@@ -0,0 +1,47 @@
import LoadingIndicator from '../../../components/loading_indicator';
import PureRenderMixin from 'react-addons-pure-render-mixin';
import ImmutablePropTypes from 'react-immutable-proptypes';
import ExtendedVideoPlayer from '../../../components/extended_video_player';
import { defineMessages, injectIntl } from 'react-intl';
import IconButton from '../../../components/icon_button';
const messages = defineMessages({
close: { id: 'lightbox.close', defaultMessage: 'Close' }
});
const closeStyle = {
position: 'absolute',
zIndex: '100',
top: '4px',
right: '4px'
};
const VideoModal = React.createClass({
propTypes: {
media: ImmutablePropTypes.map.isRequired,
time: React.PropTypes.number,
onClose: React.PropTypes.func.isRequired,
intl: React.PropTypes.object.isRequired
},
mixins: [PureRenderMixin],
render () {
const { media, intl, time, onClose } = this.props;
const url = media.get('url');
return (
<div className='modal-root__modal media-modal'>
<div>
<div style={closeStyle}><IconButton title={intl.formatMessage(messages.close)} icon='times' overlay onClick={onClose} /></div>
<ExtendedVideoPlayer src={url} muted={false} controls={true} time={time} />
</div>
</div>
);
}
});
export default injectIntl(VideoModal);

View File

@@ -47,7 +47,7 @@ const UI = React.createClass({
this.dragTargets.push(e.target);
}
if (e.dataTransfer && e.dataTransfer.files.length > 0) {
if (e.dataTransfer && e.dataTransfer.items.length > 0) {
this.setState({ draggingOver: true });
}
},

View File

@@ -0,0 +1,33 @@
import Link from 'http-link-header';
import querystring from 'querystring';
Link.parseAttrs = (link, parts) => {
let match = null
let attr = ''
let value = ''
let attrs = ''
let uriAttrs = /<(.*)>;\s*(.*)/gi.exec(parts)
if(uriAttrs) {
attrs = uriAttrs[2]
link = Link.parseParams(link, uriAttrs[1])
}
while(match = Link.attrPattern.exec(attrs)) {
attr = match[1].toLowerCase()
value = match[4] || match[3] || match[2]
if( /\*$/.test(attr)) {
Link.setAttr(link, attr, Link.parseExtendedValue(value))
} else if(/%/.test(value)) {
Link.setAttr(link, attr, querystring.decode(value))
} else {
Link.setAttr(link, attr, value)
}
}
return link
};
export default Link;

View File

@@ -0,0 +1,68 @@
const bg = {
"column_back_button.label": "Назад",
"lightbox.close": "Затвори",
"loading_indicator.label": "Зареждане...",
"status.mention": "Споменаване",
"status.delete": "Изтриване",
"status.reply": "Отговор",
"status.reblog": "Споделяне",
"status.favourite": "Предпочитани",
"status.reblogged_by": "{name} сподели",
"status.sensitive_warning": "Деликатно съдържание",
"status.sensitive_toggle": "Покажи",
"video_player.toggle_sound": "Звук",
"account.mention": "Споменаване",
"account.edit_profile": "Редактирай профила си",
"account.unblock": "Не блокирай",
"account.unfollow": "Не следвай",
"account.block": "Блокирай",
"account.follow": "Последвай",
"account.posts": "Публикации",
"account.follows": "Следвам",
"account.followers": "Последователи",
"account.follows_you": "Твой последовател",
"account.requested": "В очакване на одобрение",
"getting_started.heading": "Първи стъпки",
"getting_started.about_addressing": "Можеш да последваш потребител, ако знаеш потребителското му име и домейна, на който се намира, като в полето за търсене ги въведеш по този начин: име@домейн",
"getting_started.about_shortcuts": "Ако с търсения потребител се намирате на един и същ домейн, достатъчно е да въведеш само името. Същото важи и за споменаване на хора в публикации.",
"getting_started.about_developer": "Можеш да потърсиш разработчика на този проект като: Gargron@mastodon.social",
"getting_started.open_source_notice": "Mastodon е софтуер с отворен код. Можеш да помогнеш или да докладваш за проблеми в Github: {github}.",
"column.home": "Начало",
"column.mentions": "Споменавания",
"column.public": "Публичен канал",
"column.notifications": "Известия",
"tabs_bar.compose": "Съставяне",
"tabs_bar.home": "Начало",
"tabs_bar.mentions": "Споменавания",
"tabs_bar.public": "Публичен канал",
"tabs_bar.notifications": "Известия",
"compose_form.placeholder": "Какво си мислиш?",
"compose_form.publish": "Раздумай",
"compose_form.sensitive": "Отбележи съдържанието като деликатно",
"compose_form.spoiler": "Скрий текста зад предупреждение",
"compose_form.private": "Отбележи като поверително",
"compose_form.privacy_disclaimer": "Поверителни публикации ще бъдат изпратени до споменатите потребители на {domains}. Доверяваш ли се на {domainsCount, plural, one {that server} other {those servers}}, че няма да издаде твоята публикация?",
"compose_form.unlisted": "Не показвай в публичния канал",
"navigation_bar.edit_profile": "Редактирай профил",
"navigation_bar.preferences": "Предпочитания",
"navigation_bar.public_timeline": "Публичен канал",
"navigation_bar.logout": "Излизане",
"reply_indicator.cancel": "Отказ",
"search.placeholder": "Търсене",
"search.account": "Акаунт",
"search.hashtag": "Хаштаг",
"upload_button.label": "Добави медия",
"upload_form.undo": "Отмяна",
"notification.follow": "{name} те последва",
"notification.favourite": "{name} хареса твоята публикация",
"notification.reblog": "{name} сподели твоята публикация",
"notification.mention": "{name} те спомена",
"notifications.column_settings.alert": "Десктоп известия",
"notifications.column_settings.show": "Покажи в колона",
"notifications.column_settings.follow": "Нови последователи:",
"notifications.column_settings.favourite": "Предпочитани:",
"notifications.column_settings.mention": "Споменавания:",
"notifications.column_settings.reblog": "Споделяния:",
};
export default bg;

View File

@@ -1,4 +1,4 @@
const en = {
const de = {
"column_back_button.label": "Zurück",
"lightbox.close": "Schließen",
"loading_indicator.label": "Lade…",
@@ -74,4 +74,4 @@ const en = {
"missing_indicator.label": "Nicht gefunden"
};
export default en;
export default de;

View File

@@ -1,72 +1,131 @@
/**
* Note for Contributors:
* This file (en.jsx) serve as a template for other languages.
* To make other contributors' life easier, please REMEMBER:
* 1. to add your new string here; and
* 2. to remove old strings that are no longer needed; and
* 3. to sort the strings by the key.
* 4. To rename the `en` const name and export default name to match your locale.
* Thanks!
*/
const en = {
"column_back_button.label": "Back",
"lightbox.close": "Close",
"loading_indicator.label": "Loading...",
"status.mention": "Mention @{name}",
"status.delete": "Delete",
"status.reply": "Reply",
"status.reblog": "Boost",
"status.favourite": "Favourite",
"status.reblogged_by": "{name} boosted",
"status.sensitive_warning": "Sensitive content",
"status.sensitive_toggle": "Click to view",
"status.show_more": "Show more",
"status.show_less": "Show less",
"status.open": "Expand this status",
"status.report": "Report @{name}",
"video_player.toggle_sound": "Toggle sound",
"account.mention": "Mention @{name}",
"account.edit_profile": "Edit profile",
"account.unblock": "Unblock @{name}",
"account.unfollow": "Unfollow",
"account.block": "Block @{name}",
"account.disclaimer": "This user is from another instance. This number may be larger.",
"account.edit_profile": "Edit profile",
"account.follow": "Follow",
"account.posts": "Posts",
"account.follows": "Follows",
"account.followers": "Followers",
"account.follows_you": "Follows you",
"account.follows": "Follows",
"account.mention": "Mention @{name}",
"account.mute": "Mute @{name}",
"account.posts": "Posts",
"account.report": "Report @{name}",
"account.requested": "Awaiting approval",
"getting_started.heading": "Getting started",
"getting_started.about_addressing": "You can follow people if you know their username and the domain they are on by entering an e-mail-esque address into the search form.",
"getting_started.about_shortcuts": "If the target user is on the same domain as you, just the username will work. The same rule applies to mentioning people in statuses.",
"getting_started.open_source_notice": "Mastodon is open source software. You can contribute or report issues on GitHub at {github}. {apps}.",
"column.home": "Home",
"account.unblock": "Unblock @{name}",
"account.unfollow": "Unfollow",
"account.unmute": "Unmute @{name}",
"boost_modal.combo": "You can press {combo} to skip this next time",
"column_back_button.label": "Back",
"column.blocks": "Blocked users",
"column.community": "Local timeline",
"column.public": "Federated timeline",
"column.favourites": "Favourites",
"column.follow_requests": "Follow requests",
"column.home": "Home",
"column.notifications": "Notifications",
"tabs_bar.compose": "Compose",
"tabs_bar.home": "Home",
"tabs_bar.mentions": "Mentions",
"tabs_bar.public": "Federated timeline",
"tabs_bar.notifications": "Notifications",
"column.public": "Federated timeline",
"compose_form.placeholder": "What is on your mind?",
"compose_form.privacy_disclaimer": "Your private status will be delivered to mentioned users on {domains}. Do you trust {domainsCount, plural, one {that server} other {those servers}}? Post privacy only works on Mastodon instances. If {domains} {domainsCount, plural, one {is not a Mastodon instance} other {are not Mastodon instances}}, there will be no indication that your post is private, and it may be boosted or otherwise made visible to unintended recipients.",
"compose_form.publish": "Toot",
"compose_form.sensitive": "Mark media as sensitive",
"compose_form.spoiler_placeholder": "Content warning",
"compose_form.spoiler": "Hide text behind warning",
"compose_form.private": "Mark as private",
"compose_form.privacy_disclaimer": "Your private status will be delivered to mentioned users on {domains}. Do you trust {domainsCount, plural, one {that server} other {those servers}}? Post privacy only works on Mastodon instances. If {domains} {domainsCount, plural, one {is not a Mastodon instance} other {are not Mastodon instances}}, there will be no indication that your post is private, and it may be boosted or otherwise made visible to unintended recipients.",
"compose_form.unlisted": "Do not display on public timelines",
"navigation_bar.edit_profile": "Edit profile",
"navigation_bar.preferences": "Preferences",
"emoji_button.label": "Insert emoji",
"empty_column.community": "The local timeline is empty. Write something publicly to get the ball rolling!",
"empty_column.hashtag": "There is nothing in this hashtag yet.",
"empty_column.home.public_timeline": "the public timeline",
"empty_column.home": "You aren't following anyone yet. Visit {public} or use search to get started and meet other users.",
"empty_column.notifications": "You don't have any notifications yet. Interact with others to start the conversation.",
"empty_column.public": "There is nothing here! Write something publicly, or manually follow users from other instances to fill it up",
"follow_request.authorize": "Authorize",
"follow_request.reject": "Reject",
"getting_started.apps": "Various apps are available",
"getting_started.heading": "Getting started",
"getting_started.open_source_notice": "Mastodon is open source software. You can contribute or report issues on GitHub at {github}. {apps}.",
"home.column_settings.advanced": "Advanced",
"home.column_settings.basic": "Basic",
"home.column_settings.filter_regex": "Filter out by regular expressions",
"home.column_settings.show_reblogs": "Show boosts",
"home.column_settings.show_replies": "Show replies",
"home.settings": "Column settings",
"lightbox.close": "Close",
"loading_indicator.label": "Loading...",
"media_gallery.toggle_visible": "Toggle visibility",
"missing_indicator.label": "Not found",
"navigation_bar.blocks": "Blocked users",
"navigation_bar.community_timeline": "Local timeline",
"navigation_bar.public_timeline": "Federated timeline",
"navigation_bar.edit_profile": "Edit profile",
"navigation_bar.favourites": "Favourites",
"navigation_bar.follow_requests": "Follow requests",
"navigation_bar.info": "Extended information",
"navigation_bar.logout": "Logout",
"reply_indicator.cancel": "Cancel",
"search.placeholder": "Search",
"search.account": "Account",
"search.hashtag": "Hashtag",
"upload_button.label": "Add media",
"upload_form.undo": "Undo",
"notification.follow": "{name} followed you",
"navigation_bar.preferences": "Preferences",
"navigation_bar.public_timeline": "Federated timeline",
"notification.favourite": "{name} favourited your status",
"notification.follow": "{name} followed you",
"notification.reblog": "{name} boosted your status",
"notification.mention": "{name} mentioned you",
"notifications.clear_confirmation": "Are you sure you want to clear all your notifications?",
"notifications.clear": "Clear notifications",
"notifications.column_settings.alert": "Desktop notifications",
"notifications.column_settings.show": "Show in column",
"notifications.column_settings.follow": "New followers:",
"notifications.column_settings.favourite": "Favourites:",
"notifications.column_settings.follow": "New followers:",
"notifications.column_settings.mention": "Mentions:",
"notifications.column_settings.reblog": "Boosts:",
"notifications.column_settings.show": "Show in column",
"notifications.column_settings.sound": "Play sound",
"notifications.settings": "Column settings",
"privacy.change": "Adjust status privacy",
"privacy.direct.long": "Post to mentioned users only",
"privacy.direct.short": "Direct",
"privacy.private.long": "Post to followers only",
"privacy.private.short": "Private",
"privacy.public.long": "Post to public timelines",
"privacy.public.short": "Public",
"privacy.unlisted.long": "Do not show in public timelines",
"privacy.unlisted.short": "Unlisted",
"reply_indicator.cancel": "Cancel",
"report.heading": "New report",
"report.placeholder": "Additional comments",
"report.submit": "Submit",
"report.target": "Reporting",
"search_results.total": "{count} {count, plural, one {result} other {results}}",
"search.placeholder": "Search",
"search.status_by": "Status by {name}",
"status.delete": "Delete",
"status.favourite": "Favourite",
"status.load_more": "Load more",
"status.media_hidden": "Media hidden",
"status.mention": "Mention @{name}",
"status.open": "Expand this status",
"status.reblog": "Boost",
"status.reblogged_by": "{name} boosted",
"status.reply": "Reply",
"status.report": "Report @{name}",
"status.sensitive_toggle": "Click to view",
"status.sensitive_warning": "Sensitive content",
"status.show_less": "Show less",
"status.show_more": "Show more",
"tabs_bar.compose": "Compose",
"tabs_bar.federated_timeline": "Federated",
"tabs_bar.home": "Home",
"tabs_bar.local_timeline": "Local",
"tabs_bar.notifications": "Notifications",
"upload_area.title": "Drag & drop to upload",
"upload_button.label": "Add media",
"upload_form.undo": "Undo",
"upload_progress.label": "Uploading...",
"video_player.toggle_sound": "Toggle sound",
"video_player.toggle_visible": "Toggle visibility",
"video_player.expand": "Expand video",
};
export default en;

View File

@@ -5,28 +5,35 @@ const es = {
"status.mention": "Mencionar",
"status.delete": "Borrar",
"status.reply": "Responder",
"status.reblog": "Republicar",
"status.reblog": "Retoot",
"status.favourite": "Favorito",
"status.reblogged_by": "{name} republicado",
"status.reblogged_by": "Retooteado por {name}",
"status.sensitive_warning": "Contenido sensible",
"status.sensitive_toggle": "Click para ver",
"status.show_more": "Mostrar más",
"status.show_less": "Mostrar menos",
"status.open": "Expandir estado",
"status.report": "Reportar",
"video_player.toggle_sound": "Act/Desac. sonido",
"account.mention": "Mención",
"account.mention": "Mencionar",
"account.edit_profile": "Editar perfil",
"account.unblock": "Desbloquear",
"account.unfollow": "Dejar de seguir",
"account.mute": "Silenciar",
"account.block": "Bloquear",
"account.follow": "Seguir",
"account.block": "Bloquear",
"account.posts": "Publicaciones",
"account.follows": "Seguir",
"account.followers": "Seguidores",
"account.follows_you": "Te sigue",
"account.requested": "Esperando aprobación",
"getting_started.heading": "Primeros pasos",
"getting_started.about_addressing": "Puedes seguir a gente si conoces su nombre de usuario y el dominio en el que están registrados, introduciendo algo similar a una dirección de correo electrónico en el formulario en la parte superior de la barra lateral.",
"getting_started.about_shortcuts": "Si el usuario que buscas está en el mismo dominio que tú, simplemente funcionará introduciendo el nombre de usuario. La misma regla se aplica para mencionar a usuarios.",
"getting_started.about_developer": "Puedes seguir al desarrollador de este proyecto en Gargron@mastodon.social",
"getting_started.open_source_notice": "Mastodon es software libre. Puedes contribuir o reportar errores en {github}. {apps}.",
"column.home": "Inicio",
"column.mentions": "Menciones",
"column.public": "Historia pública",
"column.community": "Historia local",
"column.public": "Historia federada",
"column.notifications": "Notificaciones",
"tabs_bar.compose": "Redactar",
"tabs_bar.home": "Inicio",
@@ -34,23 +41,47 @@ const es = {
"tabs_bar.public": "Público",
"tabs_bar.notifications": "Notificaciones",
"compose_form.placeholder": "¿En qué estás pensando?",
"compose_form.publish": "Publicar",
"compose_form.sensitive": "Marcar el contenido como sensible",
"compose_form.unlisted": "Privado",
"compose_form.publish": "Tootear",
"compose_form.sensitive": "Marcar contenido como sensible",
"compose_form.spoiler": "Ocultar texto tras advertencia",
"compose_form.spoiler_placeholder": "Advertencia de contenido",
"composer_form.private": "Marcar como privado",
"composer_form.privacy_disclaimer": "Tu estado se mostrará a los usuarios mencionados en {domains}. Tu estado podrá ser visto en otras instancias, quizás no quieras que tu estado sea visto por otros usuarios.",
"compose_form.unlisted": "No mostrar en la historia federada",
"navigation_bar.edit_profile": "Editar perfil",
"navigation_bar.preferences": "Preferencias",
"navigation_bar.public_timeline": "Público",
"navigation_bar.community_timeline": "Historia local",
"navigation_bar.public_timeline": "Historia federada",
"navigation_bar.favourites": "Favoritos",
"navigation_bar.blocks": "Usuarios bloqueados",
"navigation_bar.info": "Información adicional",
"navigation_bar.logout": "Cerrar sesión",
"reply_indicator.cancel": "Cancelar",
"search.placeholder": "Buscar",
"search.account": "Cuenta",
"search.hashtag": "Etiqueta",
"upload_button.label": "Añadir medio",
"upload_button.label": "Subir multimedia",
"upload_form.undo": "Deshacer",
"notification.follow": "{name} le esta ahora siguiendo",
"notification.favourite": "{name} marcó como favorito su estado",
"notification.reblog": "{name} volvió a publicar su estado",
"notification.mention": "Fue mencionado por {name}"
"notification.follow": "{name} te empezó a seguir",
"notification.favourite": "{name} marcó tu estado como favorito",
"notification.reblog": "{name} ha retooteado tu estado",
"notification.mention": "{name} te ha mencionado",
"notifications.column_settings.alert": "Notificaciones de escritorio",
"notifications.column_settings.show": "Mostrar en columna",
"notifications.column_settings.follow": "Nuevos seguidores:",
"notifications.column_settings.favourite": "Favoritos:",
"notifications.column_settings.mention": "Menciones:",
"notifications.column_settings.reblog": "Retoots:",
"emoji_button.label": "Insertar emoji",
"privacy.public.short": "Público",
"privacy.public.long": "Mostrar en la historia federada",
"privacy.unlisted.short": "Sin federar",
"privacy.unlisted.long": "No mostrar en la historia federada",
"privacy.private.short": "Privado",
"privacy.private.long": "Sólo mostrar a seguidores",
"privacy.direct.short": "Directo",
"privacy.direct.long": "Sólo mostrar a los usuarios mencionados",
"privacy.change": "Ajustar privacidad"
};
export default es;

View File

@@ -12,10 +12,12 @@ const fr = {
"status.sensitive_toggle": "Cliquer pour dévoiler",
"status.show_more": "Déplier",
"status.show_less": "Replier",
"status.open": "Déplier ce status",
"status.open": "Déplier ce statut",
"status.report": "Signaler @{name}",
"status.load_more": "Charger plus",
"status.media_hidden": "Média caché",
"video_player.toggle_sound": "Mettre/Couper le son",
"video_player.toggle_visible": "Afficher/Cacher la vidéo",
"account.mention": "Mentionner",
"account.edit_profile": "Modifier le profil",
"account.unblock": "Débloquer",
@@ -32,7 +34,7 @@ const fr = {
"account.report": "Signaler",
"account.disclaimer": "Ce compte est situé sur une autre instance. Les nombres peuvent être plus grands.",
"getting_started.heading": "Pour commencer",
"getting_started.about_addressing": "Vous pouvez suivre les statuts de quelquun en entrant dans le champs de recherche leur identifiant et le domaine de leur instance, séparés par un @ à la manière dune adresse courriel.",
"getting_started.about_addressing": "Vous pouvez suivre les statuts de quelquun en entrant dans le champ de recherche leur identifiant et le domaine de leur instance, séparés par un @ à la manière dune adresse courriel.",
"getting_started.about_shortcuts": "Si cette personne utilise la même instance que vous, lidentifiant suffit. Cest le même principe pour mentionner quelquun dans vos statuts.",
"getting_started.about_developer": "Pour suivre le développeur de ce projet, cest Gargron@mastodon.social",
"getting_started.open_source_notice": "Mastodon est un logiciel libre. Vous pouvez contribuer et envoyer vos commentaires et rapports de bogues via {github} sur GitHub.",
@@ -42,16 +44,25 @@ const fr = {
"column.notifications": "Notifications",
"column.blocks": "Utilisateurs bloqués",
"column.favourites": "Favoris",
"column.follow_requests": "Demandes de suivi",
"empty_column.notifications": "Vous navez pas encore de notification. Interagissez avec dautres utilisateurs⋅trices pour débuter la conversation.",
"empty_column.public": "Il n'y a rien ici ! Écrivez quelque chose publiquement, ou bien suivez manuellement des utilisateurs d'autres instances pour remplir le fil public.",
"empty_column.home": "Vous ne suivez encore personne. Visitez {public} ou bien utilisez la recherche pour vous connecter à d'autres utilisateurs.",
"empty_column.home.public_timeline": "le fil public",
"empty_column.community": "Le fil public local est vide. Écrivez-donc quelque chose pour le remplir !",
"empty_column.hashtag": "Il n'y a encore aucun contenu relatif à ce hashtag",
"tabs_bar.compose": "Composer",
"tabs_bar.home": "Accueil",
"tabs_bar.mentions": "Mentions",
"tabs_bar.public": "Fil public global",
"tabs_bar.notifications": "Notifications",
"tabs_bar.local_timeline": "Fil public local",
"tabs_bar.federated_timeline": "Fil public global",
"compose_form.placeholder": "Quavez-vous en tête ?",
"compose_form.publish": "Pouet",
"compose_form.sensitive": "Marquer le média comme délicat",
"compose_form.spoiler": "Masquer le texte derrière un avertissement",
"compose_form.spoiler_placeholder": "Avertissement",
"compose_form.private": "Rendre privé",
"compose_form.privacy_disclaimer": "Votre statut privé va être transmis aux personnes mentionnées sur {domains}. Avez-vous confiance en {domainsCount, plural, one {ce serveur} other {ces serveurs}} pour ne pas divulguer votre statut ? Les statuts privés ne fonctionnent que sur les instances de Mastodons. Si {domains} {domainsCount, plural, one {n'est pas une instance de Mastodon} other {ne sont pas des instances de Mastodon}}, il n'y aura aucune indication que votre statut est privé, et il pourrait être partagé ou rendu visible d'une autre manière à d'autres personnes imprévues",
"compose_form.unlisted": "Ne pas afficher dans les fils publics",
@@ -64,23 +75,31 @@ const fr = {
"navigation_bar.favourites": "Favoris",
"navigation_bar.info": "Plus d'informations",
"navigation_bar.logout": "Déconnexion",
"navigation_bar.follow_requests": "Demandes de suivi",
"reply_indicator.cancel": "Annuler",
"search.placeholder": "Chercher",
"search.placeholder": "Rechercher",
"search.account": "Compte",
"search.hashtag": "Mot-clé",
"search_results.total": "{count} {count, plural, one {résultat} other {résultats}}",
"search.status_by": "Statuts de {name}",
"upload_button.label": "Joindre un média",
"upload_form.undo": "Annuler",
"upload_progress.label": "Envoi en cours…",
"upload_area.title": "Glissez et déposez pour envoyer",
"notification.follow": "{name} vous suit.",
"notification.favourite": "{name} a ajouté à ses favoris :",
"notification.reblog": "{name} a partagé votre statut :",
"notification.mention": "{name} vous a mentionné⋅e :",
"notifications.column_settings.alert": "Notifications locales",
"notifications.column_settings.show": "Afficher dans la colonne",
"notifications.column_settings.sound": "Émettre un son",
"notifications.column_settings.follow": "Nouveaux abonnés :",
"notifications.column_settings.favourite": "Favoris :",
"notifications.column_settings.mention": "Mentions :",
"notifications.column_settings.reblog": "Partages :",
"notifications.clear": "Nettoyer",
"notifications.clear_confirmation": "Voulez-vous vraiment nettoyer toutes vos notifications ?",
"notifications.settings": "Paramètres de la colonne",
"privacy.public.short": "Public",
"privacy.public.long": "Afficher dans les fils publics",
"privacy.unlisted.short": "Non-listé",
@@ -88,8 +107,22 @@ const fr = {
"privacy.private.short": "Privé",
"privacy.private.long": "Nafficher que pour vos abonné⋅e⋅s",
"privacy.direct.short": "Direct",
"privacy.direct.long": "Nafficher que pour les personnes mentionné⋅e⋅s",
"privacy.direct.long": "Nafficher que pour les personnes mentionnées",
"privacy.change": "Ajuster la confidentialité du message",
"media_gallery.toggle_visible": "Modifier la visibilité",
"missing_indicator.label": "Non trouvé",
"follow_request.authorize": "Autoriser",
"follow_request.reject": "Rejeter",
"home.settings": "Paramètres de la colonne",
"home.column_settings.basic": "Basique",
"home.column_settings.show_reblogs": "Afficher les partages",
"home.column_settings.show_replies": "Afficher les réponses",
"home.column_settings.advanced": "Avancé",
"home.column_settings.filter_regex": "Filtrer avec une expression rationnelle",
"report.heading": "Nouveau signalement",
"report.placeholder": "Commentaires additionnels",
"report.submit": "Envoyer",
"report.target": "Signalement"
};
export default fr;

View File

@@ -3,10 +3,16 @@ import de from './de';
import es from './es';
import hu from './hu';
import fr from './fr';
import nl from './nl';
import no from './no';
import pt from './pt';
import uk from './uk';
import fi from './fi';
import eo from './eo';
import ru from './ru';
import ja from './ja';
import zh_hk from './zh-hk';
import bg from './bg';
const locales = {
en,
@@ -14,10 +20,16 @@ const locales = {
es,
hu,
fr,
nl,
no,
pt,
uk,
fi,
eo
eo,
ru,
ja,
'zh-HK': zh_hk,
bg,
};
export default function getMessagesForLocale (locale) {

View File

@@ -0,0 +1,119 @@
const ja = {
"column_back_button.label": "戻る",
"lightbox.close": "閉じる",
"loading_indicator.label": "読み込み中...",
"status.mention": "@{name} さんへの返信",
"status.delete": "削除",
"status.reply": "返信",
"status.reblog": "ブースト",
"status.favourite": "お気に入り",
"status.reblogged_by": "{name} さんにブーストされました",
"status.sensitive_warning": "不適切なコンテンツ",
"status.sensitive_toggle": "クリックして表示",
"status.show_more": "もっと見る",
"status.load_more": "もっと見る",
"status.show_less": "隠す",
"status.open": "Expand this status",
"status.report": "@{name} さんを通報",
"status.media_hidden": "非表示のメデイア",
"video_player.toggle_sound": "音の切り替え",
"account.mention": "@{name} さんに返信",
"account.edit_profile": "プロフィールを編集",
"account.unblock": "@{name} さんのブロックを解除",
"account.unfollow": "フォロー解除",
"account.block": "@{name} さんをブロック",
"account.mute": "ミュート",
"account.unmute": "ミュート解除",
"account.follow": "フォロー",
"account.report": "@{name}を通報する",
"account.posts": "投稿",
"account.follows": "フォロー",
"account.followers": "フォロワー",
"account.follows_you": "フォローされています",
"account.requested": "承認待ち",
"follow_request.authorize": "許可",
"follow_request.reject": "拒否",
"getting_started.heading": "スタート",
"getting_started.about_addressing": "ドメインとユーザー名を知っているなら検索フォームに入力すればフォローできます。",
"getting_started.about_shortcuts": "対象のアカウントがあなたと同じドメインのユーザーならばユーザー名のみで検索できます。これは返信のときも一緒です。",
"getting_started.open_source_notice": "Mastodon はオープンソースソフトウェアです。誰でも GitHub{github})から開発に参加したり、問題を報告したりできます。 {apps}",
"column.home": "ホーム",
"column.community": "ローカルタイムライン",
"column.public": "連合タイムライン",
"column.notifications": "通知",
"column.favourites": "お気に入り",
"tabs_bar.compose": "投稿",
"tabs_bar.home": "ホーム",
"tabs_bar.mentions": "返信",
"tabs_bar.local_timeline": "ローカル",
"tabs_bar.federated_timeline": "連合",
"tabs_bar.notifications": "通知",
"compose_form.placeholder": "今なにしてる?",
"compose_form.publish": "トゥート",
"compose_form.sensitive": "メディアを不適切なコンテンツとしてマークする",
"compose_form.spoiler": "テキストを隠す",
"compose_form.spoiler_placeholder": "内容注意メッセージ",
"compose_form.private": "非公開にする",
"compose_form.privacy_disclaimer": "あなたの非公開トゥートは返信先のユーザーat {domains})に公開されます。{domainsCount, plural, one {that server} other {those servers}}を信頼しますか投稿のプライバシー保護はMastodonサーバー内でのみ有効です。 もし{domains} {domainsCount, plural, one {is not a Mastodon instance} other {are not Mastodon instances}}ならばあなたの投稿のプライバシーは保護されず、ブーストされたり予期しないユーザーに見られる可能性があります。",
"compose_form.unlisted": "公開タイムラインに表示しない",
"privacy.public.short": "公開",
"privacy.public.long": "公開TLに投稿する",
"privacy.unlisted.short": "未収載",
"privacy.unlisted.long": "公開TLで表示しない",
"privacy.private.short": "非公開",
"privacy.private.long": "フォロワーだけに公開",
"privacy.direct.short": "ダイレクト",
"privacy.direct.long": "含んだユーザーだけに公開",
"privacy.change": "投稿のプライバシーを変更2",
"report.heading": "新規通報",
"report.placeholder": "コメント",
"report.target": "問題のユーザー",
"report.submit": "通報する",
"navigation_bar.edit_profile": "プロフィールを編集",
"navigation_bar.preferences": "ユーザー設定",
"navigation_bar.community_timeline": "ローカルタイムライン",
"navigation_bar.public_timeline": "連合タイムライン",
"navigation_bar.logout": "ログアウト",
"navigation_bar.favourites": "お気に入り",
"navigation_bar.blocks": "ブロックしたユーザー",
"navigation_bar.info": "サーバー情報",
"reply_indicator.cancel": "キャンセル",
"search.placeholder": "検索",
"search.account": "アカウント",
"search.hashtag": "ハッシュタグ",
"search.status_by": "{uuuname}からの投稿",
"upload_area.title": "ファイルをこちらにドラッグしてください",
"upload_button.label": "メディアを追加",
"upload_form.undo": "やり直す",
"notification.follow": "{name} さんにフォローされました",
"notification.favourite": "{name} さんがあなたのトゥートをお気に入りに登録しました",
"notification.reblog": "{name} さんがあなたのトゥートをブーストしました",
"notification.mention": "{name} さんがあなたに返信しました",
"notifications.clear": "通知を片付ける",
"notifications.clear_confirmation": "通知を全部片付けます。大丈夫ですか?",
"notifications.column_settings.alert": "デスクトップ通知",
"notifications.column_settings.show": "カラムに表示",
"notifications.column_settings.follow": "新しいフォロワー",
"notifications.column_settings.favourite": "お気に入り",
"notifications.column_settings.mention": "返信",
"notifications.column_settings.reblog": "ブースト",
"notifications.column_settings.sound": "通知音を再生",
"empty_column.home": "まだ誰もフォローしていません。{public}を見に行くか、検索を使って他のユーザーを見つけましょう。",
"empty_column.home.public_timeline": "連合タイムライン",
"empty_column.notifications": "まだ通知がありません。他の人とふれ合って会話を始めましょう。",
"empty_column.public": "ここにはまだ何もありません!公開で何かを投稿したり、他のインスタンスのユーザーをフォローしたりしていっぱいにしましょう!",
"empty_column.hashtag": "このハッシュタグはまだ使っていません。",
"upload_progress.label": "アップロード中…",
"emoji_button.label": "絵文字を追加",
"home.column_settings.basic": "シンプル",
"home.column_settings.advanced": "エキスパート",
"home.column_settings.show_reblogs": "ブースト表示",
"home.column_settings.show_replies": "返信表示",
"home.column_settings.filter_regex": "正規表現でフィルター",
"home.settings": "カラム設定",
"notification.settings": "カラム設定",
"missing_indicator.label": "見つかりません",
"boost_modal.combo": "次は{combo}を押せば、これをスキップできます。"
};
export default ja;

View File

@@ -0,0 +1,68 @@
const nl = {
"column_back_button.label": "terug",
"lightbox.close": "Sluiten",
"loading_indicator.label": "Laden...",
"status.mention": "Vermeld @{name}",
"status.delete": "Verwijder",
"status.reply": "Reageer",
"status.reblog": "Boost",
"status.favourite": "Favoriet",
"status.reblogged_by": "{name} boostte",
"status.sensitive_warning": "Gevoelige inhoud",
"status.sensitive_toggle": "Klik om te zien",
"video_player.toggle_sound": "Geluid omschakelen",
"account.mention": "Vermeld @{name}",
"account.edit_profile": "Bewerk profiel",
"account.unblock": "Deblokkeer @{name}",
"account.unfollow": "Ontvolg",
"account.block": "Blokkeer @{name}",
"account.follow": "Volg",
"account.posts": "Berichten",
"account.follows": "Volgt",
"account.followers": "Volgers",
"account.follows_you": "Volgt jou",
"account.requested": "Wacht op goedkeuring",
"getting_started.heading": "Beginnen",
"getting_started.about_addressing": "Je kunt mensen volgen als je hun gebruikersnaam en het domein van hun server kent, door het e-mailachtige adres in het zoekscherm in te voeren.",
"getting_started.about_shortcuts": "Als de gezochte gebruiker op hetzelfde domein zit als jijzelf, is invoeren van de gebruikersnaam genoeg. Dat geldt ook als je mensen in de statussen wilt vermelden.",
"getting_started.open_source_notice": "Mastodon is open source software. Je kunt bijdragen of problemen melden op GitHub via {github}. {apps}.",
"column.home": "Thuis",
"column.community": "Lokale tijdlijn",
"column.public": "Federatietijdlijn",
"column.notifications": "Meldingen",
"tabs_bar.compose": "Schrijven",
"tabs_bar.home": "Thuis",
"tabs_bar.mentions": "Vermeldingen",
"tabs_bar.public": "Federatietijdlijn",
"tabs_bar.notifications": "Meldingen",
"compose_form.placeholder": "Waar ben je mee bezig?",
"compose_form.publish": "Toot",
"compose_form.sensitive": "Markeer media als gevoelig",
"compose_form.spoiler": "Verberg tekst achter waarschuwing",
"compose_form.private": "Mark als priv<69>",
"compose_form.privacy_disclaimer": "Je besloten status wordt afgeleverd aan vermelde gebruikers op {domains}. Vertrouw je {domainsCount, plural, one {that server} andere {those servers}}? Priv<69> plaatsen werkt alleen op Mastodon servers. Als {domains} {domainsCount, plural, een {is not a Mastodon instance} andere {are not Mastodon instances}}, dan wordt er geen indicatie gegeven dat he bericht besloten is, waardoor het kan worden geboost of op andere manier zichtbaar worden voor niet bedoelde lezers.",
"compose_form.unlisted": "Niet tonen op openbare tijdlijnen",
"navigation_bar.edit_profile": "Bewerk profiel",
"navigation_bar.preferences": "Voorkeuren",
"navigation_bar.community_timeline": "Lokale tijdlijn",
"navigation_bar.public_timeline": "Federatietijdlijn",
"navigation_bar.logout": "Uitloggen",
"reply_indicator.cancel": "Annuleren",
"search.placeholder": "Zoeken",
"search.account": "Account",
"search.hashtag": "Hashtag",
"upload_button.label": "Toevoegen media",
"upload_form.undo": "Ongedaan maken",
"notification.follow": "{name} volgde jou",
"notification.favourite": "{name} markeerde je status als favoriet",
"notification.reblog": "{name} boostte je status",
"notification.mention": "{name} vermeldde jou",
"notifications.column_settings.alert": "Desktopmeldingen",
"notifications.column_settings.show": "Tonen in kolom",
"notifications.column_settings.follow": "Nieuwe volgers:",
"notifications.column_settings.favourite": "Favoriten:",
"notifications.column_settings.mention": "Vermeldingen:",
"notifications.column_settings.reblog": "Boosts:",
};
export default nl;

View File

@@ -0,0 +1,77 @@
const no = {
"column_back_button.label": "Tilbake",
"lightbox.close": "Lukk",
"loading_indicator.label": "Laster...",
"status.mention": "Nevn @{name}",
"status.delete": "Slett",
"status.reply": "Svar",
"status.reblog": "Reblogg",
"status.favourite": "Lik",
"status.reblogged_by": "{name} reblogget",
"status.sensitive_warning": "Sensitivt innhold",
"status.sensitive_toggle": "Klikk for å vise",
"status.show_more": "Vis mer",
"status.show_less": "Vis mindre",
"status.open": "Utvid denne statusen",
"status.report": "Rapporter @{name}",
"video_player.toggle_sound": "Veksle lyd",
"account.mention": "Nevn @{name}",
"account.edit_profile": "Rediger profil",
"account.unblock": "Avblokker @{name}",
"account.unfollow": "Avfølg",
"account.block": "Blokker @{name}",
"account.follow": "Følg",
"account.posts": "Poster",
"account.follows": "Følginger",
"account.followers": "Følgere",
"account.follows_you": "Folger deg",
"account.requested": "Venter på godkjennelse",
"getting_started.heading": "Kom i gang",
"getting_started.about_addressing": "Du kan følge noen hvis du vet brukernavnet deres og domenet de er på ved å skrive en e-postadresse inn i søkeskjemaet.",
"getting_started.about_shortcuts": "Hvis målbrukeren er på samme domene som deg, vil kun brukernavnet også fungere. Den samme regelen gjelder når man nevner noen i statuser.",
"getting_started.open_source_notice": "Mastodon er programvare med fri kildekode. Du kan bidra eller rapportere problemer på GitHub på {github}. {apps}.",
"column.home": "Hjem",
"column.community": "Lokal tidslinje",
"column.public": "Forent tidslinje",
"column.notifications": "Varslinger",
"column.blocks": "Blokkerte brukere",
"column.favourites": "Likt",
"tabs_bar.compose": "Komponer",
"tabs_bar.home": "Hjem",
"tabs_bar.mentions": "Nevninger",
"tabs_bar.public": "Forent tidslinje",
"tabs_bar.notifications": "Varslinger",
"compose_form.placeholder": "Hva har du på hjertet?",
"compose_form.publish": "Tut",
"compose_form.sensitive": "Merk media som følsomt",
"compose_form.spoiler": "Skjul tekst bak advarsel",
"compose_form.private": "Merk som privat",
"compose_form.privacy_disclaimer": "Din private status vil leveres til nevnte brukere på {domains}. Stoler du på {domainsCount, plural, one {den serveren} other {de serverne}}? Synlighet fungerer kun på Mastodon-instanser. Hvis {domains} {domainsCount, plural, one {ike er en Mastodon-instans} other {ikke er Mastodon-instanser}}, vil det ikke indikeres at posten din er privat, og den kan kanskje bli reblogget eller på annen måte bli synlig for uventede mottakere.",
"compose_form.unlisted": "Ikke vis på offentlige tidslinjer",
"navigation_bar.edit_profile": "Rediger profil",
"navigation_bar.preferences": "Preferanser",
"navigation_bar.community_timeline": "Lokal tidslinje",
"navigation_bar.public_timeline": "Forent tidslinje",
"navigation_bar.logout": "Logg ut",
"navigation_bar.blocks": "Blokkerte brukere",
"navigation_bar.info": "Utvidet informasjon",
"navigation_bar.favourites": "Likt",
"reply_indicator.cancel": "Avbryt",
"search.placeholder": "Søk",
"search.account": "Konto",
"search.hashtag": "Hashtag",
"upload_button.label": "Legg til media",
"upload_form.undo": "Angre",
"notification.follow": "{name} fulgte deg",
"notification.favourite": "{name} likte din status",
"notification.reblog": "{name} reblogget din status",
"notification.mention": "{name} nevnte deg",
"notifications.column_settings.alert": "Skrivebordsvarslinger",
"notifications.column_settings.show": "Vis i kolonne",
"notifications.column_settings.follow": "Nye følgere:",
"notifications.column_settings.favourite": "Likt:",
"notifications.column_settings.mention": "Nevninger:",
"notifications.column_settings.reblog": "Reblogginger:",
};
export default no;

View File

@@ -14,59 +14,115 @@ const pt = {
"status.show_less": "Mostrar menos",
"status.open": "Expandir",
"status.report": "Reportar @{name}",
"status.load_more": "Carregar mais",
"status.media_hidden": "Media escondida",
"video_player.toggle_sound": "Ligar/Desligar som",
"video_player.toggle_visible": "Ligar/Desligar vídeo",
"account.mention": "Mencionar @{name}",
"account.edit_profile": "Editar perfil",
"account.unblock": "Não bloquear @{name}",
"account.unfollow": "Não seguir",
"account.block": "Bloquear @{name}",
"account.mute": "Mute",
"account.unmute": "Remover Mute",
"account.follow": "Seguir",
"account.posts": "Posts",
"account.follows": "Segue",
"account.followers": "Seguidores",
"account.follows_you": "É teu seguidor",
"account.requested": "A aguardar aprovação",
"account.report": "Denunciar",
"account.disclaimer": "Essa conta está localizado em outra instância. Os nomes podem ser maiores.",
"getting_started.heading": "Primeiros passos",
"getting_started.about_addressing": "Podes seguir pessoas se sabes o nome de usuário deles e o domínio em que estão colocando um endereço similar a e-mail no campo no topo da barra lateral.",
"getting_started.about_shortcuts": "Se o usuário alvo está no mesmo domínio, só o nome funcionará. A mesma regra se aplica a mencionar pessoas nas postagens.",
"getting_started.about_developer": "Pode seguir o developer deste projecto em Gargron@mastodon.social",
"getting_started.open_source_notice": "Mastodon é software de fonte aberta. Podes contribuir ou repostar problemas no GitHub do projecto: {github}. {apps}.",
"column.home": "Home",
"column.community": "Local",
"column.public": "Público",
"column.public": "Global",
"column.notifications": "Notificações",
"column.blocks": "Utilizadores Bloqueados",
"column.favourites": "Favoritos",
"column.follow_requests": "Seguidores Pendentes",
"empty_column.notifications": "Não tens notificações. Interage com outros utilizadores para iniciar uma conversa.",
"empty_column.public": "Não há nada aqui! Escreve algo publicamente ou segue outros utilizadores para ver aqui os conteúdos públicos.",
"empty_column.home": "Ainda não segues qualquer utilizador. Visita {public} ou utiliza a pesquisa para procurar outros utilizadores.",
"empty_column.home.public_timeline": "global",
"empty_column.community": "Ainda não existem conteúdo local para mostrar!",
"empty_column.hashtag": "Não existe qualquer conteúdo com essa hashtag",
"tabs_bar.compose": "Criar",
"tabs_bar.home": "Home",
"tabs_bar.mentions": "Menções",
"tabs_bar.public": "Público",
"tabs_bar.notifications": "Notificações",
"tabs_bar.local_timeline": "Local",
"tabs_bar.federated_timeline": "Global",
"compose_form.placeholder": "Em que estás a pensar?",
"compose_form.publish": "Publicar",
"compose_form.sensitive": "Media com conteúdo sensível",
"compose_form.sensitive": "Marcar media como conteúdo sensível",
"compose_form.spoiler": "Esconder texto com aviso",
"compose_form.spoiler_placeholder": "Aviso",
"compose_form.private": "Tornar privado",
"compose_form.privacy_disclaimer": "O teu conteúdo privado vai ser partilhado com os utilizadores do {domains}. Confias {domainsCount, plural, one {neste servidor} other {nestes servidores}}? A privacidade só funciona em instâncias do Mastodon. Se {domains} {domainsCount, plural, one {não é uma instância} other {não são instâncias}}, não existem indicadores da privacidade da tua partilha, e podem ser partilhados com outros.",
"compose_form.unlisted": "Não mostrar na listagem pública",
"emoji_button.label": "Inserir Emoji",
"navigation_bar.edit_profile": "Editar perfil",
"navigation_bar.preferences": "Preferências",
"navigation_bar.community_timeline": "Local",
"navigation_bar.public_timeline": "Público",
"navigation_bar.public_timeline": "Global",
"navigation_bar.blocks": "Utilizadores bloqueados",
"navigation_bar.favourites": "Favoritos",
"navigation_bar.info": "Mais informações",
"navigation_bar.logout": "Sair",
"navigation_bar.follow_requests": "Seguidores pendentes",
"reply_indicator.cancel": "Cancelar",
"search.placeholder": "Pesquisar",
"search.account": "Conta",
"search.hashtag": "Hashtag",
"search_results.total": "{count} {count, plural, one {resultado} other {resultados}}",
"search.status_by": "Post de {name}",
"upload_button.label": "Adicionar media",
"upload_form.undo": "Anular",
"upload_progress.label": "A gravar…",
"upload_area.title": "Arraste e solte para enviar",
"notification.follow": "{name} seguiu-te",
"notification.favourite": "{name} adicionou o teu post aos favoritos",
"notification.reblog": "{name} partilhou o teu post",
"notification.mention": "{name} mencionou-te",
"notifications.column_settings.alert": "Notificações no computador",
"notifications.column_settings.show": "Mostrar nas colunas",
"notifications.column_settings.sound": "Reproduzir som",
"notifications.column_settings.follow": "Novos seguidores:",
"notifications.column_settings.favourite": "Favoritos:",
"notifications.column_settings.mention": "Menções:",
"notifications.column_settings.reblog": "Partilhas:",
"notifications.clear": "Limpar notificações",
"notifications.clear_confirmation": "Queres mesmo limpar todas as notificações?",
"notifications.settings": "Parâmetros da lista de Notificações",
"privacy.public.short": "Público",
"privacy.public.long": "Publicar em todos os feeds",
"privacy.unlisted.short": "Não listar",
"privacy.unlisted.long": "Não publicar nos feeds públicos",
"privacy.private.short": "Privado",
"privacy.private.long": "Apenas para os seguidores",
"privacy.direct.short": "Directo",
"privacy.direct.long": "Apenas para utilizadores mencionados",
"privacy.change": "Ajustar a privacidade da mensagem",
"media_gallery.toggle_visible": "Modificar a visibilidade",
"missing_indicator.label": "Não encontrado",
"follow_request.authorize": "Autorizar",
"follow_request.reject": "Rejeitar",
"home.settings": "Parâmetros da coluna Home",
"home.column_settings.basic": "Básico",
"home.column_settings.show_reblogs": "Mostrar as partilhas",
"home.column_settings.show_replies": "Mostrar as respostas",
"home.column_settings.advanced": "Avançadas",
"home.column_settings.filter_regex": "Filtrar com uma expressão regular",
"report.heading": "Nova denuncia",
"report.placeholder": "Comentários adicionais",
"report.submit": "Enviar",
"report.target": "Denunciar"
};
export default pt;

View File

@@ -0,0 +1,101 @@
const ru = {
"column_back_button.label": "Назад",
"lightbox.close": "Закрыть",
"loading_indicator.label": "Загрузка...",
"status.mention": "Упомянуть @{name}",
"status.delete": "Удалить",
"status.reply": "Ответить",
"status.reblog": "Продвинуть",
"status.favourite": "Нравится",
"status.reblogged_by": "{name} продвинул(а)",
"status.sensitive_warning": "Чувствительный контент",
"status.sensitive_toggle": "Нажмите для просмотра",
"status.show_more": "Развернуть",
"status.show_less": "Свернуть",
"status.open": "Развернуть статус",
"status.report": "Пожаловаться",
"status.load_more": "Показать еще",
"video_player.toggle_sound": "Вкл./выкл. звук",
"account.mention": "Упомянуть",
"account.edit_profile": "Изменить профиль",
"account.unblock": "Разблокировать",
"account.unfollow": "Отписаться",
"account.block": "Блокировать",
"account.mute": "Заглушить",
"account.follow": "Подписаться",
"account.posts": "Посты",
"account.follows": "Подписки",
"account.followers": "Подписаны",
"account.follows_you": "Подписан(а) на Вас",
"account.requested": "Ожидает подтверждения",
"getting_started.heading": "Добро пожаловать",
"getting_started.about_addressing": "Вы можете подписаться на человека, зная имя пользователя и домен, на котором он находится, введя e-mail-подобный адрес в форму поиска.",
"getting_started.about_shortcuts": "Если пользователь находится на одном с Вами домене, можно использовать только имя. То же правило применимо к упоминанию пользователей в статусах.",
"getting_started.open_source_notice": "Mastodon - программа с открытым исходным кодом. Вы можете помочь проекту или сообщить о проблемах на GitHub по адресу {github}. {apps}.",
"getting_started.apps": "Доступны различные приложения.",
"column.home": "Главная",
"column.community": "Локальная лента",
"column.public": "Глобальная лента",
"column.notifications": "Уведомления",
"tabs_bar.compose": "Написать",
"tabs_bar.home": "Главная",
"tabs_bar.mentions": "Упоминания",
"tabs_bar.public": "Глобальная лента",
"tabs_bar.notifications": "Уведомления",
"compose_form.placeholder": "О чем Вы думаете?",
"compose_form.publish": "Трубить",
"compose_form.sensitive": "Отметить как чувствительный контент",
"compose_form.spoiler": "Скрыть текст за предупреждением",
"compose_form.private": "Отметить как приватное",
"compose_form.privacy_disclaimer": "Ваш приватный статус будет доставлен упомянутым пользователям на доменах {domains}. Доверяете ли вы {domainsCount, plural, one {этому серверу} other {этим серверам}}? Приватность постов работает только на узлах Mastodon. Если {domains} {domainsCount, plural, one {не является узлом Mastodon} other {не являются узлами Mastodon}}, приватность поста не будет указана, и он может оказаться продвинут или иным образом показан не обозначенным Вами пользователям.",
"compose_form.unlisted": "Не отображать в публичных лентах",
"navigation_bar.edit_profile": "Изменить профиль",
"navigation_bar.preferences": "Опции",
"navigation_bar.community_timeline": "Локальная лента",
"navigation_bar.public_timeline": "Глобальная лента",
"navigation_bar.logout": "Выйти",
"navigation_bar.info": "Об узле",
"navigation_bar.favourites": "Понравившееся",
"navigation_bar.blocks": "Список блокировки",
"reply_indicator.cancel": "Отмена",
"search.placeholder": "Поиск",
"search.account": "Аккаунт",
"search.hashtag": "Хэштег",
"upload_button.label": "Добавить медиаконтент",
"upload_form.undo": "Отменить",
"notification.follow": "{name} подписался(-лась) на Вас",
"notification.favourite": "{name} понравился Ваш статус",
"notification.reblog": "{name} продвинул(а) Ваш статус",
"notification.mention": "{name} упомянул(а) Вас",
"home.settings": "Настройки колонки",
"home.column_settings.basic": "Основные",
"home.column_settings.advanced": "Дополнительные",
"home.column_settings.filter_regex": "Отфильтровать регулярным выражением",
"home.column_settings.show_replies": "Показывать продвижения",
"home.column_settings.show_replies": "Показывать ответы",
"notifications.clear": "Очистить уведомления",
"notifications.settings": "Настройки колонки",
"notifications.column_settings.alert": "Десктопные уведомления",
"notifications.column_settings.show": "Показывать в колонке",
"notifications.column_settings.follow": "Новые подписчики:",
"notifications.column_settings.favourite": "Нравится:",
"notifications.column_settings.mention": "Упоминания:",
"notifications.column_settings.reblog": "Продвижения:",
"notifications.column_settings.sound": "Проигрывать звук",
"empty_column.notifications": "У Вас еще нет уведомлений. Заведите знакомство с другими пользователями, чтобы начать разговор.",
"empty_column.hashtag": "Статусов с таким хэштегом еще не существует.",
"empty_column.community": "Локальная лента пуста. Напишите что-нибудь, чтобы разогреть народ!",
"empty_column.public": "Здесь ничего нет! Опубликуйте что-нибудь или подпишитесь на пользователей с других узлов, чтобы заполнить ленту.",
"empty_column.home": "Пока Вы ни на кого не подписаны. Полистайте {public} или используйте поиск, чтобы освоиться и завести новые знакомства.",
"empty_column.home.public_timeline": "публичные ленты",
"privacy.public.short": "Публичный",
"privacy.public.long": "Показать в публичных лентах",
"privacy.unlisted.short": "Скрытый",
"privacy.unlisted.long": "Не показывать в лентах",
"privacy.private.short": "Приватный",
"privacy.private.long": "Показать только подписчикам",
"privacy.direct.short": "Направленный",
"privacy.direct.long": "Показать только упомянутым",
};
export default ru;

View File

@@ -0,0 +1,113 @@
import zh from 'react-intl/locale-data/zh';
const localeData = zh.reduce(function (acc, localeData) {
if (localeData.locale === "zh-Hant-HK") {
// rename the locale "zh-Hant-HK" as "zh-HK"
// (match the code usually used in Accepted-Language header)
acc.push(Object.assign({},
localeData,
{
"locale": "zh-HK",
"parentLocale": "zh-Hant-HK",
}
));
}
return acc;
}, []);
export { localeData as localeData };
const zh_hk = {
"account.block": "封鎖 @{name}",
"account.edit_profile": "修改個人資料",
"account.follow": "關注",
"account.followers": "關注的人",
"account.follows_you": "關注你",
"account.follows": "正在關注",
"account.mention": "提及 @{name}",
"account.posts": "文章",
"account.requested": "等候審批",
"account.unblock": "解除對 @{name} 的封鎖",
"account.unfollow": "取消關注",
"column_back_button.label": "先前顯示",
"column.community": "本站時間軸",
"column.home": "家",
"column.notifications": "通知",
"column.public": "跨站公共時間軸",
"compose_form.placeholder": "你在想甚麼?",
"compose_form.privacy_disclaimer": "你的私人文章,將被遞送至你所提及的 {domains} 用戶。你是否信任 {domainsCount, plural, one {這個網站} other {這些網站}}?請留意,文章私隱設定只適用於各 Mastodon 服務站,如果 {domains} {domainsCount, plural, one {不是 Mastodon 服務站} other {之中有些不是 Mastodon 服務站}},對方將無法收到這篇文章的私隱設定,然後可能被轉推給不能預知的用戶閱讀。",
"compose_form.private": "標示為「只有關注你的人能看」",
"compose_form.publish": "發文",
"compose_form.sensitive": "將媒體檔案標示為「敏感內容」",
"compose_form.spoiler": "將部份文字藏於警告訊息之後",
"compose_form.unlisted": "請勿在公共時間軸顯示",
"empty_column.community": "本站時間軸暫時未有內容,快貼文來搶頭香啊!",
"empty_column.hashtag": "這個標籤暫時未有內容。",
"empty_column.home": "你還沒有關注任何用戶。快看看{public},向其他用戶搭訕吧。",
"empty_column.home.public_timeline": "公共時間軸",
"empty_column.notifications": "You don't have any notifications yet. Interact with others to start the conversation.",
"empty_column.public": "There is nothing here! Write something publicly, or manually follow users from other instances to fill it up.",
"getting_started.about_addressing": "只要你知道一位用戶的用戶名稱和域名,你可以用「@用戶名稱@域名」的格式在搜尋欄尋找該用戶。",
"getting_started.about_shortcuts": "只要該用戶是在你現在的服務站開立,你可以直接輸入用戶𠱷搜尋。同樣的規則適用於在文章提及別的用戶。",
"getting_started.apps": "手機或桌面應用程式",
"getting_started.heading": "開始使用",
"getting_started.open_source_notice": "Mastodon 是一個開放源碼的軟件。你可以在官方 GitHub ({github}) 貢獻或者回報問題。你亦可透過{apps}閱讀 Mastodon 上的消息。",
"home.column_settings.basic": "基本",
"home.column_settings.show_reblogs": "顯示被轉推的文章",
"home.column_settings.show_replies": "顯示回應文章",
"home.column_settings.advanced": "進階",
"lightbox.close": "關閉",
"loading_indicator.label": "載入中...",
"missing_indicator.label": "找不到內容",
"navigation_bar.community_timeline": "本站時間軸",
"navigation_bar.edit_profile": "修改個人資料",
"navigation_bar.logout": "登出",
"navigation_bar.preferences": "個人設定",
"navigation_bar.public_timeline": "跨站公共時間軸",
"notification.favourite": "{name} 喜歡你的文章",
"notification.follow": "{name} 開始開始你",
"notification.mention": "{name} 提及你",
"notification.reblog": "{name} 轉推你的文章",
"notifications.column_settings.alert": "顯示桌面通知",
"notifications.column_settings.favourite": "喜歡你的文章:",
"notifications.column_settings.follow": "關注你:",
"notifications.column_settings.mention": "提及你:",
"notifications.column_settings.reblog": "轉推你的文章:",
"notifications.column_settings.show": "在通知欄顯示",
"notifications.column_settings.sound": "播放音效",
"reply_indicator.cancel": "取消",
"report.target": "Reporting",
"search.account": "用戶",
"search.hashtag": "標籤",
"search.placeholder": "搜尋",
"search_results.total": "{count} 項結果",
"search.status_by": "按用戶名稱搜尋文章",
"status.delete": "刪除",
"status.favourite": "喜歡",
"status.load_more": "載入更多",
"status.media_hidden": "隱藏媒體內容",
"status.mention": "提及 @{name}",
"status.open": "展開文章",
"status.reblog": "轉推",
"status.reblogged_by": "{name} 轉推",
"status.reply": "回應",
"status.report": "舉報 @{name}",
"status.sensitive_toggle": "點擊顯示",
"status.sensitive_warning": "敏感內容",
"status.show_less": "減少顯示",
"status.show_more": "顯示更多",
"tabs_bar.compose": "撰寫",
"tabs_bar.home": "家",
"tabs_bar.local_timeline": "本站",
"tabs_bar.mentions": "提及",
"tabs_bar.notifications": "通知",
"tabs_bar.public": "跨站公共時間軸",
"tabs_bar.federated_timeline": "跨站",
"upload_area.title": "將檔案拖放至此上載",
"upload_button.label": "上載媒體檔案",
"upload_progress.label": "上載中……",
"upload_form.undo": "還原",
"video_player.toggle_sound": "開關音效",
};
export default zh_hk;

View File

@@ -67,6 +67,7 @@ function clearAll(state) {
map.set('is_submitting', false);
map.set('in_reply_to', null);
map.set('privacy', state.get('default_privacy'));
map.set('sensitive', false);
map.update('media_attachments', list => list.clear());
});
};
@@ -76,7 +77,8 @@ function appendMedia(state, media) {
map.update('media_attachments', list => list.push(media));
map.set('is_uploading', false);
map.set('resetFileKey', Math.floor((Math.random() * 0x10000)));
map.update('text', oldText => `${oldText} ${media.get('text_url')}`.trim());
map.set('focusDate', new Date());
map.update('text', oldText => `${oldText.trim()} ${media.get('text_url')}`.trim() + ' ');
});
};
@@ -156,6 +158,9 @@ export default function compose(state = initialState, action) {
if (action.status.get('spoiler_text').length > 0) {
map.set('spoiler', true);
map.set('spoiler_text', action.status.get('spoiler_text'));
} else {
map.set('spoiler', false);
map.set('spoiler_text', '');
}
});
case COMPOSE_REPLY_CANCEL:

View File

@@ -4,7 +4,8 @@ import {
REPORT_SUBMIT_SUCCESS,
REPORT_SUBMIT_FAIL,
REPORT_CANCEL,
REPORT_STATUS_TOGGLE
REPORT_STATUS_TOGGLE,
REPORT_COMMENT_CHANGE
} from '../actions/reports';
import Immutable from 'immutable';
@@ -39,6 +40,8 @@ export default function reports(state = initialState, action) {
return set.remove(action.statusId);
});
case REPORT_COMMENT_CHANGE:
return state.setIn(['new', 'comment'], action.comment);
case REPORT_SUBMIT_REQUEST:
return state.setIn(['new', 'isSubmitting'], true);
case REPORT_SUBMIT_FAIL:

View File

@@ -14,7 +14,7 @@
}
&:after {
background: rgba($color8, 0.5);
background: linear-gradient(rgba($color8, 0.5), rgba($color8, 0.8));
display: block;
content: "";
position: absolute;
@@ -83,7 +83,7 @@
.counter {
width: 80px;
color: $color3;
padding: 0 10px;
padding: 5px 10px 0px;
margin-bottom: 10px;
border-right: 1px solid $color3;
cursor: default;
@@ -148,7 +148,7 @@
order: 1;
}
@media screen and (max-width: 360px) {
@media screen and (max-width: 480px) {
.details {
display: block;
}
@@ -173,7 +173,7 @@
text-align: center;
overflow: hidden;
a, .current, .next_page, .previous_page, .gap {
a, .current, .page, .gap {
font-size: 14px;
color: $color5;
font-weight: 500;
@@ -193,12 +193,12 @@
cursor: default;
}
.previous_page, .next_page {
.prev, .next {
text-transform: uppercase;
color: $color2;
}
.previous_page {
.prev {
float: left;
padding-left: 0;
@@ -208,7 +208,7 @@
}
}
.next_page {
.next {
float: right;
padding-right: 0;
@@ -226,11 +226,11 @@
@media screen and (max-width: 360px) {
padding: 30px 20px;
a, .current, .next_page, .previous_page, .gap {
a, .current, .next, .prev, .gap {
display: none;
}
.next_page, .previous_page {
.next, .prev {
display: inline-block;
}
}
@@ -387,6 +387,5 @@
.account__header__content {
font-size: 14px;
color: $color1;
text-shadow: 0 0 2px $color8;
}
}

View File

@@ -4,305 +4,13 @@
@import 'fonts/montserrat';
@import 'font-awesome';
/* http://meyerweb.com/eric/tools/css/reset/
v2.0 | 20110126
License: none (public domain)
*/
html, body, div, span, applet, object, iframe,
h1, h2, h3, h4, h5, h6, p, blockquote, pre,
a, abbr, acronym, address, big, cite, code,
del, dfn, em, img, ins, kbd, q, s, samp,
small, strike, strong, sub, sup, tt, var,
b, u, i, center,
dl, dt, dd, ol, ul, li,
fieldset, form, label, legend,
table, caption, tbody, tfoot, thead, tr, th, td,
article, aside, canvas, details, embed,
figure, figcaption, footer, header, hgroup,
menu, nav, output, ruby, section, summary,
time, mark, audio, video {
margin: 0;
padding: 0;
border: 0;
font-size: 100%;
font: inherit;
vertical-align: baseline;
}
/* HTML5 display-role reset for older browsers */
article, aside, details, figcaption, figure,
footer, header, hgroup, menu, nav, section {
display: block;
}
body {
line-height: 1;
}
ol, ul {
list-style: none;
}
blockquote, q {
quotes: none;
}
blockquote:before, blockquote:after,
q:before, q:after {
content: '';
content: none;
}
table {
border-collapse: collapse;
border-spacing: 0;
}
::-webkit-scrollbar {
width: 8px;
height: 8px;
}
::-webkit-scrollbar-thumb {
background: lighten($color1, 4%);
border: 0px none $color5;
border-radius: 50px;
}
::-webkit-scrollbar-thumb:hover {
background: lighten($color1, 6%);
}
::-webkit-scrollbar-thumb:active {
background: lighten($color1, 4%);
}
::-webkit-scrollbar-track {
border: 0px none $color5;
border-radius: 0;
background: rgba($color8, 0.1);
}
::-webkit-scrollbar-track:hover {
background: $color1;
}
::-webkit-scrollbar-track:active {
background: $color1;
}
::-webkit-scrollbar-corner {
background: transparent;
}
body {
font-family: 'Roboto', sans-serif;
background: $color1 image-url('background-photo.jpeg');
background-size: cover;
background-attachment: fixed;
font-size: 13px;
line-height: 18px;
font-weight: 400;
color: $color5;
padding-bottom: 140px;
text-rendering: optimizelegibility;
font-feature-settings: "kern";
text-size-adjust: none;
&.app-body {
position: fixed;
width: 100%;
height: 100%;
padding: 0;
background: $color1;
}
&.embed {
background: transparent;
margin: 0;
.container {
position: absolute;
width: 100%;
height: 100%;
overflow: hidden;
}
}
&.admin {
background: darken($color1, 4%);
position: fixed;
width: 100%;
height: 100%;
padding: 0;
}
@media screen and (max-width: 360px) {
padding-bottom: 0;
}
}
button:focus {
outline: none;
}
.app-holder {
display: flex;
width: 100%;
height: 100%;
align-items: center;
justify-content: center;
}
.container {
width: 700px;
margin: 0 auto;
margin-top: 40px;
@media screen and (max-width: 700px) {
width: 100%;
margin: 0;
}
}
.logo-container {
max-width: 400px;
margin: 100px auto;
margin-bottom: 0;
cursor: default;
@media screen and (max-width: 360px) {
margin: 30px auto;
}
h1 {
display: block;
text-align: center;
color: $color5;
font-size: 48px;
font-weight: 500;
img {
display: block;
margin: 20px auto;
width: 180px;
height: 180px;
}
a {
color: inherit;
text-decoration: none;
outline: 0;
img {
opacity: 0.8;
transition: all 0.8s ease;
}
&:hover {
img {
opacity: 1;
transition-duration: 0.2s;
}
}
}
small {
display: block;
font-size: 12px;
font-weight: 400;
font-family: 'Roboto Mono', monospace;
}
}
}
.no-list {
list-style: none;
li {
display: inline-block;
margin: 0 5px;
}
}
.footer {
text-align: center;
margin-top: 30px;
font-size: 12px;
color: darken($color2, 25%);
.domain {
font-weight: 500;
a {
color: inherit;
text-decoration: none;
}
}
.powered-by {
font-weight: 400;
a {
color: inherit;
text-decoration: underline;
font-weight: 500;
&:hover {
text-decoration: none;
}
}
}
}
.compact-header {
h1 {
font-size: 24px;
line-height: 28px;
color: $color3;
overflow: hidden;
font-weight: 500;
margin-bottom: 20px;
a {
color: inherit;
text-decoration: none;
}
small {
font-weight: 400;
color: $color2;
}
img {
display: inline-block;
margin-bottom: -5px;
margin-right: 15px;
width: 36px;
height: 36px;
}
}
}
.landing-strip {
background: rgba(darken($color1, 7%), 0.8);
color: $color3;
font-weight: 400;
padding: 14px;
border-radius: 4px;
margin-bottom: 20px;
strong, a {
font-weight: 500;
}
a {
color: inherit;
text-decoration: underline;
}
}
@import 'reset';
@import 'basics';
@import 'containers';
@import 'lists';
@import 'footer';
@import 'compact_header';
@import 'landing_strip';
@import 'forms';
@import 'accounts';
@import 'stream_entries';

View File

@@ -0,0 +1,58 @@
body {
font-family: 'Roboto', sans-serif;
background: $color1 image-url('background-photo.jpeg');
background-size: cover;
background-attachment: fixed;
font-size: 13px;
line-height: 18px;
font-weight: 400;
color: $color5;
padding-bottom: 140px;
text-rendering: optimizelegibility;
font-feature-settings: "kern";
text-size-adjust: none;
&.app-body {
position: fixed;
width: 100%;
height: 100%;
padding: 0;
background: $color1;
}
&.embed {
background: transparent;
margin: 0;
.container {
position: absolute;
width: 100%;
height: 100%;
overflow: hidden;
}
}
&.admin {
background: darken($color1, 4%);
position: fixed;
width: 100%;
height: 100%;
padding: 0;
}
@media screen and (max-width: 360px) {
padding-bottom: 0;
}
}
button:focus {
outline: none;
}
.app-holder {
display: flex;
width: 100%;
height: 100%;
align-items: center;
justify-content: center;
}

File diff suppressed because one or more lines are too long

View File

@@ -0,0 +1,28 @@
.compact-header {
h1 {
font-size: 24px;
line-height: 28px;
color: $color3;
overflow: hidden;
font-weight: 500;
margin-bottom: 20px;
a {
color: inherit;
text-decoration: none;
}
small {
font-weight: 400;
color: $color2;
}
img {
display: inline-block;
margin-bottom: -5px;
margin-right: 15px;
width: 36px;
height: 36px;
}
}
}

View File

@@ -1,7 +1,8 @@
@import 'variables';
.app-body{
-ms-overflow-style: -ms-autohiding-scrollbar;
-webkit-overflow-scrolling: touch;
-ms-overflow-style: -ms-autohiding-scrollbar;
}
.button {
@@ -49,6 +50,22 @@
}
}
.column-icon-clear {
font-size: 16px;
padding: 15px;
position: absolute;
right: 48px;
top: 0;
cursor: pointer;
z-index: 2;
}
@media screen and (min-width: 1025px) {
.column-icon-clear {
top: 10px;
}
}
.icon-button {
display: inline-block;
padding: 0;
@@ -95,6 +112,18 @@
color: $color3;
}
}
&.overlayed {
box-sizing: content-box;
background: rgba($color8, 0.6);
color: rgba($color5, 0.7);
border-radius: 4px;
padding: 2px;
&:hover {
background: rgba($color8, 0.9);
}
}
}
.text-icon-button {
@@ -149,6 +178,14 @@
}
}
.avatar {
border-radius: 4px;
background: transparent no-repeat;
background-position: 50%;
background-clip: padding-box;
position: relative;
}
.lightbox .icon-button {
color: $color1;
}
@@ -325,6 +362,43 @@ a.status__content__spoiler-link {
.status__display-name {
color: lighten($color1, 26%);
}
&.light {
.status__relative-time {
color: $color3;
}
.status__display-name {
color: $color1;
}
.display-name {
strong {
color: $color1;
}
span {
color: $color3;
}
}
.status__content {
color: $color1;
a {
color: $color4;
}
a.status__content__spoiler-link {
color: $color5;
background: $color3;
&:hover {
background: lighten($color3, 8%);
}
}
}
}
}
.status-check-box {
@@ -643,6 +717,12 @@ a.status__content__spoiler-link {
left: 8px;
}
&.light {
&:before {
border-color: transparent transparent $color5 transparent;
}
}
& > ul {
list-style: none;
background: $color2;
@@ -660,7 +740,7 @@ a.status__content__spoiler-link {
}
& > .emoji-dialog {
left: -249px;
left: -210px;
}
}
@@ -714,15 +794,7 @@ a.status__content__spoiler-link {
@media screen and (min-width: 360px) {
.columns-area {
margin: 0;
}
.column:first-child, .drawer:first-child {
margin-left: 0;
}
.column:last-child, .drawer:last-child {
margin-right: 0;
padding: 10px;
}
}
@@ -730,9 +802,12 @@ a.status__content__spoiler-link {
width: 330px;
position: relative;
box-sizing: border-box;
background: $color1;
display: flex;
flex-direction: column;
> .scrollable {
background: $color1;
}
}
.ui {
@@ -764,6 +839,58 @@ a.status__content__spoiler-link {
border-bottom: 2px solid transparent;
}
.column, .drawer {
flex: 1 1 100%;
overflow: hidden;
}
@media screen and (min-width: 360px) {
.tabs-bar {
margin: 10px;
margin-bottom: 0;
}
.search {
margin-bottom: 10px;
}
}
@media screen and (max-width: 1024px) {
.column, .drawer {
width: 100%;
padding: 0;
}
.columns-area {
flex-direction: column;
}
.search__input, .autosuggest-textarea__textarea {
font-size: 16px;
}
}
@media screen and (min-width: 1025px) {
.columns-area {
padding: 0;
}
.column, .drawer {
flex: 0 0 auto;
padding: 10px;
padding-left: 5px;
padding-right: 5px;
&:first-child {
padding-left: 10px;
}
&:last-child {
padding-right: 10px;
}
}
}
@media screen and (min-width: 2560px) {
.columns-area {
justify-content: center;
@@ -823,38 +950,6 @@ a.status__content__spoiler-link {
}
}
.column, .drawer {
margin: 10px;
margin-left: 5px;
margin-right: 5px;
flex: 0 0 auto;
overflow: hidden;
}
.column:first-child, .drawer:first-child {
margin-left: 10px;
}
.column:last-child, .drawer:last-child {
margin-right: 10px;
}
@media screen and (max-width: 1024px) {
.column, .drawer {
width: 100%;
margin: 0;
flex: 1 1 100%;
}
.columns-area {
flex-direction: column;
}
.search__input, .autosuggest-textarea__textarea {
font-size: 16px;
}
}
.tabs-bar {
display: flex;
background: lighten($color1, 8%);
@@ -865,17 +960,18 @@ a.status__content__spoiler-link {
.tabs-bar__link {
display: block;
flex: 1 1 auto;
padding: 10px 5px;
padding: 15px 10px;
color: $color5;
text-decoration: none;
text-align: center;
font-size:12px;
font-size: 14px;
font-weight: 500;
border-bottom: 2px solid lighten($color1, 8%);
transition: all 200ms linear;
.fa {
font-weight: 400;
font-size: 16px;
}
&.active {
@@ -889,37 +985,13 @@ a.status__content__spoiler-link {
}
span {
margin-left: 5px;
display: none;
}
}
@media screen and (min-width: 360px) {
.columns-area {
margin: 10px;
}
.tabs-bar {
margin: 10px;
margin-bottom: 0;
}
.search {
margin-bottom: 10px;
}
}
@media screen and (min-width: 1024px) {
.columns-area {
margin: 0;
}
}
@media screen and (min-width: 600px) {
.tabs-bar__link {
.fa {
margin-right: 5px;
}
span {
display: inline;
}
@@ -1199,7 +1271,7 @@ a.status__content__spoiler-link {
@import 'boost';
button i.fa-retweet {
button.icon-button i.fa-retweet {
height: 19px;
width: 22px;
background-position: 0 0;
@@ -1211,7 +1283,7 @@ button i.fa-retweet {
}
}
button.active i.fa-retweet {
button.icon-button.active i.fa-retweet {
transition-duration: 0.9s;
background-position: 0 100%;
}
@@ -1381,12 +1453,15 @@ button.active i.fa-retweet {
.empty-column-indicator {
color: lighten($color1, 20%);
background: $color1;
text-align: center;
padding: 20px;
padding-top: 100px;
font-size: 15px;
font-weight: 400;
cursor: default;
display: flex;
flex: 1 1 auto;
align-items: center;
a {
color: $color4;
@@ -1412,22 +1487,23 @@ button.active i.fa-retweet {
}
.emoji-dialog {
width: 280px;
height: 220px;
background: $color2;
width: 245px;
height: 270px;
background: $color5;
box-sizing: border-box;
border-radius: 2px;
border-radius: 4px;
overflow: hidden;
position: relative;
box-shadow: 0 0 15px rgba($color8, 0.4);
box-shadow: 0 0 8px rgba($color8, 0.2);
.emojione {
margin: 0;
width: 100%;
height: auto;
}
.emoji-dialog-header {
padding: 0 10px;
background-color: $color3;
ul {
padding: 0;
@@ -1438,18 +1514,29 @@ button.active i.fa-retweet {
li {
display: inline-block;
box-sizing: border-box;
height: 42px;
padding: 9px 5px;
padding: 10px 5px;
cursor: pointer;
border-bottom: 2px solid transparent;
.emoji {
width: 18px;
height: 18px;
}
img, svg {
width: 22px;
height: 22px;
width: 18px;
height: 18px;
filter: grayscale(100%);
}
&:hover {
img, svg {
filter: grayscale(0);
}
}
&.active {
background: lighten($color3, 6%);
border-bottom-color: $color4;
img, svg {
filter: grayscale(0);
@@ -1473,7 +1560,7 @@ button.active i.fa-retweet {
.emoji-category-header {
box-sizing: border-box;
overflow-y: hidden;
padding: 8px 16px 0;
padding: 10px 8px 10px 16px;
display: table;
> * {
@@ -1483,10 +1570,10 @@ button.active i.fa-retweet {
}
.emoji-category-title {
font-size: 14px;
font-family: sans-serif;
font-weight: normal;
color: $color1;
font-size: 12px;
text-transform: uppercase;
font-weight: 500;
color: darken($color2, 18%);
cursor: default;
}
@@ -1526,7 +1613,7 @@ button.active i.fa-retweet {
width: 7px;
height: 7px;
border-radius: 10px;
border: 2px solid $color1;
border: 2px solid $color5;
top: 2px;
left: 2px;
}
@@ -1534,14 +1621,20 @@ button.active i.fa-retweet {
}
.emoji-search-wrapper {
padding: 6px 16px;
padding: 10px;
border-bottom: 1px solid lighten($color2, 4%);
}
.emoji-search {
font-size: 12px;
padding: 6px 4px;
font-size: 14px;
font-weight: 400;
padding: 7px 9px;
font-family: inherit;
display: block;
width: 100%;
border: 1px solid #ddd;
background: rgba($color2, 0.3);
color: darken($color2, 18%);
border: 1px solid $color2;
border-radius: 4px;
}
@@ -1554,11 +1647,21 @@ button.active i.fa-retweet {
}
.emoji-search-wrapper + .emoji-categories-wrapper {
top: 83px;
top: 93px;
}
.emoji-row .emoji:hover {
background: lighten($color2, 3%);
.emoji-row .emoji {
img, svg {
transition: transform 60ms ease-in-out;
}
&:hover {
background: lighten($color2, 3%);
img, svg {
transform: translateZ(0) scale(1.2);
}
}
}
.emoji {
@@ -1915,3 +2018,41 @@ button.active i.fa-retweet {
max-height: 80vh;
}
}
.boost-modal {
background: lighten($color2, 8%);
color: $color1;
border-radius: 8px;
overflow: hidden;
max-width: 90vw;
width: 480px;
position: relative;
flex-direction: column;
}
.boost-modal__container {
padding: 10px;
.status {
user-select: text;
border-bottom: 0;
}
}
.boost-modal__action-bar {
display: flex;
background: $color2;
padding: 10px;
line-height: 36px;
& > div {
flex: 1 1 auto;
text-align: right;
color: lighten($color1, 33%);
padding-right: 10px;
}
.button {
flex: 0 0 auto;
}
}

View File

@@ -0,0 +1,61 @@
.container {
width: 700px;
margin: 0 auto;
margin-top: 40px;
@media screen and (max-width: 700px) {
width: 100%;
margin: 0;
}
}
.logo-container {
max-width: 400px;
margin: 100px auto;
margin-bottom: 0;
cursor: default;
@media screen and (max-width: 360px) {
margin: 30px auto;
}
h1 {
display: block;
text-align: center;
color: $color5;
font-size: 48px;
font-weight: 500;
img {
display: block;
margin: 20px auto;
width: 180px;
height: 180px;
}
a {
color: inherit;
text-decoration: none;
outline: 0;
img {
opacity: 0.8;
transition: all 0.8s ease;
}
&:hover {
img {
opacity: 1;
transition-duration: 0.2s;
}
}
}
small {
display: block;
font-size: 12px;
font-weight: 400;
font-family: 'Roboto Mono', monospace;
}
}
}

View File

@@ -0,0 +1,29 @@
.footer {
text-align: center;
margin-top: 30px;
font-size: 12px;
color: darken($color2, 25%);
.domain {
font-weight: 500;
a {
color: inherit;
text-decoration: none;
}
}
.powered-by {
font-weight: 400;
a {
color: inherit;
text-decoration: underline;
font-weight: 500;
&:hover {
text-decoration: none;
}
}
}
}

View File

@@ -88,7 +88,7 @@ code {
}
}
input[type=text], input[type=email], input[type=password], textarea {
input[type=text], input[type=number], input[type=email], input[type=password], textarea {
background: transparent;
box-sizing: border-box;
border: 0;

View File

@@ -0,0 +1,17 @@
.landing-strip {
background: rgba(darken($color1, 7%), 0.8);
color: $color3;
font-weight: 400;
padding: 14px;
border-radius: 4px;
margin-bottom: 20px;
strong, a {
font-weight: 500;
}
a {
color: inherit;
text-decoration: underline;
}
}

View File

@@ -0,0 +1,8 @@
.no-list {
list-style: none;
li {
display: inline-block;
margin: 0 5px;
}
}

View File

@@ -0,0 +1,91 @@
/* http://meyerweb.com/eric/tools/css/reset/
v2.0 | 20110126
License: none (public domain)
*/
html, body, div, span, applet, object, iframe,
h1, h2, h3, h4, h5, h6, p, blockquote, pre,
a, abbr, acronym, address, big, cite, code,
del, dfn, em, img, ins, kbd, q, s, samp,
small, strike, strong, sub, sup, tt, var,
b, u, i, center,
dl, dt, dd, ol, ul, li,
fieldset, form, label, legend,
table, caption, tbody, tfoot, thead, tr, th, td,
article, aside, canvas, details, embed,
figure, figcaption, footer, header, hgroup,
menu, nav, output, ruby, section, summary,
time, mark, audio, video {
margin: 0;
padding: 0;
border: 0;
font-size: 100%;
font: inherit;
vertical-align: baseline;
}
/* HTML5 display-role reset for older browsers */
article, aside, details, figcaption, figure,
footer, header, hgroup, menu, nav, section {
display: block;
}
body {
line-height: 1;
}
ol, ul {
list-style: none;
}
blockquote, q {
quotes: none;
}
blockquote:before, blockquote:after,
q:before, q:after {
content: '';
content: none;
}
table {
border-collapse: collapse;
border-spacing: 0;
}
::-webkit-scrollbar {
width: 8px;
height: 8px;
}
::-webkit-scrollbar-thumb {
background: lighten($color1, 4%);
border: 0px none $color5;
border-radius: 50px;
}
::-webkit-scrollbar-thumb:hover {
background: lighten($color1, 6%);
}
::-webkit-scrollbar-thumb:active {
background: lighten($color1, 4%);
}
::-webkit-scrollbar-track {
border: 0px none $color5;
border-radius: 0;
background: rgba($color8, 0.1);
}
::-webkit-scrollbar-track:hover {
background: $color1;
}
::-webkit-scrollbar-track:active {
background: $color1;
}
::-webkit-scrollbar-corner {
background: transparent;
}

View File

@@ -218,6 +218,7 @@
margin-top: 8px;
height: 300px;
overflow: hidden;
position: relative;
video {
position: relative;

View File

@@ -35,11 +35,11 @@ class AccountsController < ApplicationController
end
def followers
@followers = @account.followers.order('follows.created_at desc').paginate(page: params[:page], per_page: 12)
@followers = @account.followers.order('follows.created_at desc').page(params[:page]).per(12)
end
def following
@following = @account.following.order('follows.created_at desc').paginate(page: params[:page], per_page: 12)
@following = @account.following.order('follows.created_at desc').page(params[:page]).per(12)
end
private
@@ -53,7 +53,7 @@ class AccountsController < ApplicationController
end
def webfinger_account_url
webfinger_url(resource: "acct:#{@account.acct}@#{Rails.configuration.x.local_domain}")
webfinger_url(resource: @account.to_webfinger_s)
end
def check_account_suspension

View File

@@ -1,51 +1,30 @@
# frozen_string_literal: true
class Admin::AccountsController < ApplicationController
before_action :require_admin!
before_action :set_account, except: :index
module Admin
class AccountsController < BaseController
def index
@accounts = filtered_accounts.page(params[:page])
end
layout 'admin'
def show
@account = Account.find(params[:id])
end
def index
@accounts = Account.alphabetic.paginate(page: params[:page], per_page: 40)
private
@accounts = @accounts.local if params[:local].present?
@accounts = @accounts.remote if params[:remote].present?
@accounts = @accounts.where(domain: params[:by_domain]) if params[:by_domain].present?
@accounts = @accounts.silenced if params[:silenced].present?
@accounts = @accounts.recent if params[:recent].present?
@accounts = @accounts.suspended if params[:suspended].present?
end
def filtered_accounts
AccountFilter.new(filter_params).results
end
def show; end
def suspend
Admin::SuspensionWorker.perform_async(@account.id)
redirect_to admin_accounts_path
end
def unsuspend
@account.update(suspended: false)
redirect_to admin_accounts_path
end
def silence
@account.update(silenced: true)
redirect_to admin_accounts_path
end
def unsilence
@account.update(silenced: false)
redirect_to admin_accounts_path
end
private
def set_account
@account = Account.find(params[:id])
end
def account_params
params.require(:account).permit(:silenced, :suspended)
def filter_params
params.permit(
:local,
:remote,
:by_domain,
:silenced,
:recent,
:suspended
)
end
end
end

View File

@@ -0,0 +1,9 @@
# frozen_string_literal: true
module Admin
class BaseController < ApplicationController
before_action :require_admin!
layout 'admin'
end
end

View File

@@ -1,32 +1,30 @@
# frozen_string_literal: true
class Admin::DomainBlocksController < ApplicationController
before_action :require_admin!
module Admin
class DomainBlocksController < BaseController
def index
@blocks = DomainBlock.page(params[:page])
end
layout 'admin'
def new
@domain_block = DomainBlock.new
end
def index
@blocks = DomainBlock.paginate(page: params[:page], per_page: 40)
end
def create
@domain_block = DomainBlock.new(resource_params)
def new
@domain_block = DomainBlock.new
end
if @domain_block.save
DomainBlockWorker.perform_async(@domain_block.id)
redirect_to admin_domain_blocks_path, notice: 'Domain block is now being processed'
else
render action: :new
end
end
def create
@domain_block = DomainBlock.new(resource_params)
private
if @domain_block.save
DomainBlockWorker.perform_async(@domain_block.id)
redirect_to admin_domain_blocks_path, notice: 'Domain block is now being processed'
else
render action: :new
def resource_params
params.require(:domain_block).permit(:domain, :severity)
end
end
private
def resource_params
params.require(:domain_block).permit(:domain, :severity)
end
end

View File

@@ -1,11 +1,9 @@
# frozen_string_literal: true
class Admin::PubsubhubbubController < ApplicationController
before_action :require_admin!
layout 'admin'
def index
@subscriptions = Subscription.order('id desc').includes(:account).paginate(page: params[:page], per_page: 40)
module Admin
class PubsubhubbubController < BaseController
def index
@subscriptions = Subscription.order('id desc').includes(:account).page(params[:page])
end
end
end

View File

@@ -1,45 +1,44 @@
# frozen_string_literal: true
class Admin::ReportsController < ApplicationController
before_action :require_admin!
before_action :set_report, except: [:index]
module Admin
class ReportsController < BaseController
before_action :set_report, except: [:index]
layout 'admin'
def index
@reports = Report.includes(:account, :target_account).order('id desc').page(params[:page])
@reports = params[:action_taken].present? ? @reports.resolved : @reports.unresolved
end
def index
@reports = Report.includes(:account, :target_account).order('id desc').paginate(page: params[:page], per_page: 40)
@reports = params[:action_taken].present? ? @reports.resolved : @reports.unresolved
end
def show
@statuses = Status.where(id: @report.status_ids)
end
def show
@statuses = Status.where(id: @report.status_ids)
end
def resolve
@report.update(action_taken: true, action_taken_by_account_id: current_account.id)
redirect_to admin_report_path(@report)
end
def resolve
@report.update(action_taken: true, action_taken_by_account_id: current_account.id)
redirect_to admin_report_path(@report)
end
def suspend
Admin::SuspensionWorker.perform_async(@report.target_account.id)
Report.unresolved.where(target_account: @report.target_account).update_all(action_taken: true, action_taken_by_account_id: current_account.id)
redirect_to admin_report_path(@report)
end
def suspend
Admin::SuspensionWorker.perform_async(@report.target_account.id)
Report.unresolved.where(target_account: @report.target_account).update_all(action_taken: true, action_taken_by_account_id: current_account.id)
redirect_to admin_report_path(@report)
end
def silence
@report.target_account.update(silenced: true)
Report.unresolved.where(target_account: @report.target_account).update_all(action_taken: true, action_taken_by_account_id: current_account.id)
redirect_to admin_report_path(@report)
end
def silence
@report.target_account.update(silenced: true)
Report.unresolved.where(target_account: @report.target_account).update_all(action_taken: true, action_taken_by_account_id: current_account.id)
redirect_to admin_report_path(@report)
end
def remove
RemovalWorker.perform_async(params[:status_id])
redirect_to admin_report_path(@report)
end
def remove
RemovalWorker.perform_async(params[:status_id])
redirect_to admin_report_path(@report)
end
private
private
def set_report
@report = Report.find(params[:id])
def set_report
@report = Report.find(params[:id])
end
end
end

View File

@@ -1,35 +1,33 @@
# frozen_string_literal: true
class Admin::SettingsController < ApplicationController
before_action :require_admin!
layout 'admin'
def index
@settings = Setting.all_as_records
end
def update
@setting = Setting.where(var: params[:id]).first_or_initialize(var: params[:id])
value = settings_params[:value]
# Special cases
value = value == 'true' if @setting.var == 'open_registrations'
if @setting.value != value
@setting.value = value
@setting.save
module Admin
class SettingsController < BaseController
def index
@settings = Setting.all_as_records
end
respond_to do |format|
format.html { redirect_to admin_settings_path }
format.json { respond_with_bip(@setting) }
def update
@setting = Setting.where(var: params[:id]).first_or_initialize(var: params[:id])
value = settings_params[:value]
# Special cases
value = value == 'true' if @setting.var == 'open_registrations'
if @setting.value != value
@setting.value = value
@setting.save
end
respond_to do |format|
format.html { redirect_to admin_settings_path }
format.json { respond_with_bip(@setting) }
end
end
end
private
private
def settings_params
params.require(:setting).permit(:value)
def settings_params
params.require(:setting).permit(:value)
end
end
end

View File

@@ -0,0 +1,23 @@
# frozen_string_literal: true
module Admin
class SilencesController < BaseController
before_action :set_account
def create
@account.update(silenced: true)
redirect_to admin_accounts_path
end
def destroy
@account.update(silenced: false)
redirect_to admin_accounts_path
end
private
def set_account
@account = Account.find(params[:account_id])
end
end
end

View File

@@ -0,0 +1,23 @@
# frozen_string_literal: true
module Admin
class SuspensionsController < BaseController
before_action :set_account
def create
Admin::SuspensionWorker.perform_async(@account.id)
redirect_to admin_accounts_path
end
def destroy
@account.update(suspended: false)
redirect_to admin_accounts_path
end
private
def set_account
@account = Account.find(params[:account_id])
end
end
end

View File

@@ -9,7 +9,7 @@ class Api::V1::NotificationsController < ApiController
DEFAULT_NOTIFICATIONS_LIMIT = 15
def index
@notifications = Notification.where(account: current_account).browserable.paginate_by_max_id(limit_param(DEFAULT_NOTIFICATIONS_LIMIT), params[:max_id], params[:since_id])
@notifications = Notification.where(account: current_account).browserable(exclude_types).paginate_by_max_id(limit_param(DEFAULT_NOTIFICATIONS_LIMIT), params[:max_id], params[:since_id])
@notifications = cache_collection(@notifications, Notification)
statuses = @notifications.select { |n| !n.target_status.nil? }.map(&:target_status)
@@ -32,7 +32,13 @@ class Api::V1::NotificationsController < ApiController
private
def exclude_types
val = params.permit(exclude_types: [])[:exclude_types] || []
val = [val] unless val.is_a?(Enumerable)
val
end
def pagination_params(core_params)
params.permit(:limit).merge(core_params)
params.permit(:limit, exclude_types: []).merge(core_params)
end
end

View File

@@ -26,7 +26,7 @@ module Localized
end
def default_locale
ENV.fetch('DEFAULT_LOCALE') {
ENV.fetch('DEFAULT_LOCALE') {
http_accept_language.compatible_language_from(I18n.available_locales) || I18n.default_locale
}
end

View File

@@ -25,7 +25,7 @@ class RemoteFollowController < ApplicationController
session[:remote_follow] = @remote_follow.acct
redirect_to Addressable::Template.new(redirect_url_link.template).expand(uri: "#{@account.username}@#{Rails.configuration.x.local_domain}").to_s
redirect_to Addressable::Template.new(redirect_url_link.template).expand(uri: @account.to_webfinger_s).to_s
else
render :new
end

View File

@@ -0,0 +1,23 @@
# frozen_string_literal: true
module Settings
module Exports
class BaseController < ApplicationController
before_action :authenticate_user!
def index
@export = Export.new(current_account)
respond_to do |format|
format.csv { send_data export_data, filename: export_filename }
end
end
private
def export_filename
"#{controller_name}.csv"
end
end
end
end

View File

@@ -0,0 +1,13 @@
# frozen_string_literal: true
module Settings
module Exports
class BlockedAccountsController < BaseController
private
def export_data
@export.to_blocked_accounts_csv
end
end
end
end

View File

@@ -0,0 +1,13 @@
# frozen_string_literal: true
module Settings
module Exports
class FollowingAccountsController < BaseController
private
def export_data
@export.to_following_accounts_csv
end
end
end
end

View File

@@ -0,0 +1,13 @@
# frozen_string_literal: true
module Settings
module Exports
class MutedAccountsController < BaseController
private
def export_data
@export.to_muted_accounts_csv
end
end
end
end

View File

@@ -1,46 +1,11 @@
# frozen_string_literal: true
require 'csv'
class Settings::ExportsController < ApplicationController
layout 'admin'
before_action :authenticate_user!
before_action :set_account
def show
@total_storage = current_account.media_attachments.sum(:file_file_size)
@total_follows = current_account.following.count
@total_blocks = current_account.blocking.count
end
def download_following_list
@accounts = current_account.following
respond_to do |format|
format.csv { render text: accounts_list_to_csv(@accounts) }
end
end
def download_blocking_list
@accounts = current_account.blocking
respond_to do |format|
format.csv { render text: accounts_list_to_csv(@accounts) }
end
end
private
def set_account
@account = current_user.account
end
def accounts_list_to_csv(list)
CSV.generate do |csv|
list.each do |account|
csv << [(account.local? ? "#{account.username}@#{Rails.configuration.x.local_domain}" : account.acct)]
end
end
@export = Export.new(current_account)
end
end

View File

@@ -23,8 +23,9 @@ class Settings::PreferencesController < ApplicationController
}
current_user.settings['default_privacy'] = user_params[:setting_default_privacy]
current_user.settings['boost_modal'] = user_params[:setting_boost_modal] == '1'
if current_user.update(user_params.except(:notification_emails, :interactions, :setting_default_privacy))
if current_user.update(user_params.except(:notification_emails, :interactions, :setting_default_privacy, :setting_boost_modal))
redirect_to settings_preferences_path, notice: I18n.t('generic.changes_saved_msg')
else
render action: :show
@@ -34,6 +35,6 @@ class Settings::PreferencesController < ApplicationController
private
def user_params
params.require(:user).permit(:locale, :setting_default_privacy, notification_emails: [:follow, :follow_request, :reblog, :favourite, :mention, :digest], interactions: [:must_be_follower, :must_be_following])
params.require(:user).permit(:locale, :setting_default_privacy, :setting_boost_modal, notification_emails: [:follow, :follow_request, :reblog, :favourite, :mention, :digest], interactions: [:must_be_follower, :must_be_following])
end
end

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