diff --git a/.github/ISSUE_TEMPLATE/3.support.md b/.github/ISSUE_TEMPLATE/3.support.md
new file mode 100644
index 00000000000000..e2217da8bcc887
--- /dev/null
+++ b/.github/ISSUE_TEMPLATE/3.support.md
@@ -0,0 +1,10 @@
+---
+name: Support
+about: Ask for help with your deployment
+title: DO NOT CREATE THIS ISSUE
+---
+
+We primarily use GitHub as a bug and feature tracker. For usage questions, troubleshooting of deployments and other individual technical assistance, please use one of the resources below:
+
+- https://discourse.joinmastodon.org
+- #mastodon on irc.freenode.net
diff --git a/.github/ISSUE_TEMPLATE/config.yml b/.github/ISSUE_TEMPLATE/config.yml
index 7c0dbaf6702ea1..465ad1cd8435e6 100644
--- a/.github/ISSUE_TEMPLATE/config.yml
+++ b/.github/ISSUE_TEMPLATE/config.yml
@@ -1,7 +1,7 @@
blank_issues_enabled: false
contact_links:
- name: GitHub Discussions
- url: https://github.com/mastodon/mastodon/discussions
+ url: https://github.com/stsecurity/mastodon/discussions
about: Please ask and answer questions here.
- name: Bug Bounty Program
url: https://app.intigriti.com/programs/mastodon/mastodonio/detail
diff --git a/.github/workflows/build-image.yml b/.github/workflows/build-image.yml
new file mode 100644
index 00000000000000..92a164c40e1c56
--- /dev/null
+++ b/.github/workflows/build-image.yml
@@ -0,0 +1,40 @@
+name: Build container image
+on:
+ workflow_dispatch:
+ push:
+ branches:
+ - "main"
+ tags:
+ - "*"
+ pull_request:
+ paths:
+ - .github/workflows/build-image.yml
+ - Dockerfile
+jobs:
+ build-image:
+ runs-on: ubuntu-latest
+ steps:
+ - uses: actions/checkout@v2
+ - uses: docker/setup-qemu-action@v1
+ - uses: docker/setup-buildx-action@v1
+ - uses: docker/login-action@v1
+ with:
+ username: ${{ secrets.DOCKERHUB_USERNAME }}
+ password: ${{ secrets.DOCKERHUB_TOKEN }}
+ - uses: docker/metadata-action@v3
+ id: meta
+ with:
+ images: tootsuite/mastodon
+ flavor: |
+ latest=auto
+ tags: |
+ type=edge,branch=main
+ type=semver,pattern={{ raw }}
+ - uses: docker/build-push-action@v2
+ with:
+ context: .
+ platforms: linux/amd64,linux/arm64
+ push: ${{ github.event_name != 'pull_request' }}
+ tags: ${{ steps.meta.outputs.tags }}
+ cache-from: type=registry,ref=tootsuite/mastodon:latest
+ cache-to: type=inline
diff --git a/.github/workflows/check-i18n.yml b/.github/workflows/check-i18n.yml
index be38a096de6f15..9cb98dd125641f 100644
--- a/.github/workflows/check-i18n.yml
+++ b/.github/workflows/check-i18n.yml
@@ -2,9 +2,9 @@ name: Check i18n
on:
push:
- branches: [main]
+ branches: [ main ]
pull_request:
- branches: [main]
+ branches: [ main ]
env:
RAILS_ENV: test
@@ -14,21 +14,21 @@ jobs:
runs-on: ubuntu-latest
steps:
- - uses: actions/checkout@v2
- - name: Install system dependencies
- run: |
- sudo apt-get update
- sudo apt-get install -y libicu-dev libidn11-dev
- - name: Set up Ruby
- uses: ruby/setup-ruby@v1
- with:
- ruby-version: '3.0'
- bundler-cache: true
- - name: Check locale file normalization
- run: bundle exec i18n-tasks check-normalized
- - name: Check for unused strings
- run: bundle exec i18n-tasks unused -l en
- - name: Check for wrong string interpolations
- run: bundle exec i18n-tasks check-consistent-interpolations
- - name: Check that all required locale files exist
- run: bundle exec rake repo:check_locales_files
+ - uses: actions/checkout@v2
+ - name: Install system dependencies
+ run: |
+ sudo apt-get update
+ sudo apt-get install -y libicu-dev libidn11-dev
+ - name: Set up Ruby
+ uses: ruby/setup-ruby@v1
+ with:
+ ruby-version: '3.0'
+ bundler-cache: true
+ - name: Check locale file normalization
+ run: bundle exec i18n-tasks check-normalized
+ - name: Check for unused strings
+ run: bundle exec i18n-tasks unused -l en
+ - name: Check for wrong string interpolations
+ run: bundle exec i18n-tasks check-consistent-interpolations
+ - name: Check that all required locale files exist
+ run: bundle exec rake repo:check_locales_files
diff --git a/.gitignore b/.gitignore
index 25c8388e16a024..701cd0df99f156 100644
--- a/.gitignore
+++ b/.gitignore
@@ -66,3 +66,6 @@ yarn-debug.log
# Ignore Docker option files
docker-compose.override.yml
+
+# Ignore Docker compose files
+docker-compose.yml
\ No newline at end of file
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 90583d5e5cee8f..d8521176ccf231 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,5 +1,4 @@
-Changelog
-=========
+# Changelog
All notable changes to this project will be documented in this file.
@@ -600,11 +599,13 @@ This means that no security fix will be made available for this branch after thi
- Fix handling of recursive toots in WebUI ([ClearlyClaire](https://github.com/mastodon/mastodon/pull/17041))
## [3.4.3] - 2021-11-06
+
### Fixed
- Fix login being broken due to inaccurately applied backport fix in 3.4.2 ([Gargron](https://github.com/mastodon/mastodon/commit/5c47a18c8df3231aa25c6d1f140a71a7fac9cbf9))
## [3.4.2] - 2021-11-06
+
### Added
- Add `configuration` attribute to `GET /api/v1/instance` ([Gargron](https://github.com/mastodon/mastodon/pull/16485))
@@ -648,6 +649,7 @@ This means that no security fix will be made available for this branch after thi
- Fix revoking a specific session not working ([ClearlyClaire](https://github.com/mastodon/mastodon/pull/16943))
## [3.4.1] - 2021-06-03
+
### Added
- Add new emoji assets from Twemoji 13.1.0 ([Gargron](https://github.com/mastodon/mastodon/pull/16345))
@@ -667,6 +669,7 @@ This means that no security fix will be made available for this branch after thi
- Fix mailer jobs for deleted notifications erroring out ([ClearlyClaire](https://github.com/mastodon/mastodon/pull/16294))
## [3.4.0] - 2021-05-16
+
### Added
- **Add follow recommendations for onboarding** ([Gargron](https://github.com/mastodon/mastodon/pull/15945), [Gargron](https://github.com/mastodon/mastodon/pull/16161), [Gargron](https://github.com/mastodon/mastodon/pull/16060), [Gargron](https://github.com/mastodon/mastodon/pull/16077), [Gargron](https://github.com/mastodon/mastodon/pull/16078), [Gargron](https://github.com/mastodon/mastodon/pull/16160), [Gargron](https://github.com/mastodon/mastodon/pull/16079), [noellabo](https://github.com/mastodon/mastodon/pull/16044), [noellabo](https://github.com/mastodon/mastodon/pull/16045), [Gargron](https://github.com/mastodon/mastodon/pull/16152), [Gargron](https://github.com/mastodon/mastodon/pull/16153), [ClearlyClaire](https://github.com/mastodon/mastodon/pull/16082), [ClearlyClaire](https://github.com/mastodon/mastodon/pull/16173), [ClearlyClaire](https://github.com/mastodon/mastodon/pull/16159), [ClearlyClaire](https://github.com/mastodon/mastodon/pull/16189))
@@ -702,7 +705,7 @@ This means that no security fix will be made available for this branch after thi
- This method allows an app through which a user signed-up to request a new confirmation e-mail to be sent, or to change the e-mail of the account before it is confirmed
- Add `GET /api/v1/accounts/lookup` to REST API ([Gargron](https://github.com/mastodon/mastodon/pull/15740), [ClearlyClaire](https://github.com/mastodon/mastodon/pull/15750))
- This method allows to quickly convert a username of a known account to an ID that can be used with the REST API, or to check if a username is available
- for sign-up
+ for sign-up
- Add `policy` param to `POST /api/v1/push/subscriptions` in REST API ([Gargron](https://github.com/mastodon/mastodon/pull/16040))
- This param allows an app to control from whom notifications should be delivered as push notifications to the app
- Add `details` to error response for `POST /api/v1/accounts` in REST API ([Gargron](https://github.com/mastodon/mastodon/pull/15803))
@@ -812,6 +815,7 @@ This means that no security fix will be made available for this branch after thi
- Fix app name, website and redirect URIs not having a maximum length ([Gargron](https://github.com/mastodon/mastodon/pull/16042))
## [3.3.0] - 2020-12-27
+
### Added
- **Add hotkeys for audio/video control in web UI** ([Gargron](https://github.com/mastodon/mastodon/pull/15158), [Gargron](https://github.com/mastodon/mastodon/pull/15198))
@@ -988,6 +992,7 @@ This means that no security fix will be made available for this branch after thi
- Fix resolving accounts sometimes creating duplicate records for a given ActivityPub identifier ([ClearlyClaire](https://github.com/mastodon/mastodon/pull/15364))
## [3.2.2] - 2020-12-19
+
### Added
- Add `tootctl maintenance fix-duplicates` ([ClearlyClaire](https://github.com/mastodon/mastodon/pull/14860), [Gargron](https://github.com/mastodon/mastodon/pull/15223))
@@ -1014,6 +1019,7 @@ This means that no security fix will be made available for this branch after thi
- Fix resolving accounts sometimes creating duplicate records for a given ActivityPub identifier ([ClearlyClaire](https://github.com/mastodon/mastodon/pull/15364))
## [3.2.1] - 2020-10-19
+
### Added
- Add support for latest HTTP Signatures spec draft ([ClearlyClaire](https://github.com/mastodon/mastodon/pull/14556))
@@ -1043,6 +1049,7 @@ This means that no security fix will be made available for this branch after thi
- Fix files served as `application/octet-stream` being rejected without attempting mime type detection ([ClearlyClaire](https://github.com/mastodon/mastodon/pull/14452))
## [3.2.0] - 2020-07-27
+
### Added
- Add `SMTP_SSL` environment variable ([OmmyZhang](https://github.com/mastodon/mastodon/pull/14309))
@@ -1178,7 +1185,7 @@ This means that no security fix will be made available for this branch after thi
- Fix unique username constraint for local users not being enforced in database ([ClearlyClaire](https://github.com/mastodon/mastodon/pull/14099))
- Fix unnecessary gap under video modal in web UI ([mfmfuyu](https://github.com/mastodon/mastodon/pull/14098))
- Fix 2FA and sign in token pages not respecting user locale ([mfmfuyu](https://github.com/mastodon/mastodon/pull/14087))
-- Fix unapproved users being able to view profiles when in limited-federation mode *and* requiring approval for sign-ups ([ClearlyClaire](https://github.com/mastodon/mastodon/pull/14093))
+- Fix unapproved users being able to view profiles when in limited-federation mode _and_ requiring approval for sign-ups ([ClearlyClaire](https://github.com/mastodon/mastodon/pull/14093))
- Fix initial audio volume not corresponding to what's displayed in audio player in web UI ([ClearlyClaire](https://github.com/mastodon/mastodon/pull/14057))
- Fix timelines sometimes jumping when closing modals in web UI ([ClearlyClaire](https://github.com/mastodon/mastodon/pull/14019))
- Fix memory usage of downloading remote files ([Gargron](https://github.com/mastodon/mastodon/pull/14184), [Gargron](https://github.com/mastodon/mastodon/pull/14181), [noellabo](https://github.com/mastodon/mastodon/pull/14356))
@@ -1196,6 +1203,7 @@ This means that no security fix will be made available for this branch after thi
- Clear out media attachments in a separate worker (slow)
## [3.1.5] - 2020-07-07
+
### Security
- Fix media attachment enumeration ([ClearlyClaire](https://github.com/mastodon/mastodon/pull/14254))
@@ -1203,6 +1211,7 @@ This means that no security fix will be made available for this branch after thi
- Fix other sessions not being logged out on password change ([Gargron](https://github.com/mastodon/mastodon/pull/14252))
## [3.1.4] - 2020-05-14
+
### Added
- Add `vi` to available locales ([taicv](https://github.com/mastodon/mastodon/pull/13542))
@@ -1241,7 +1250,7 @@ This means that no security fix will be made available for this branch after thi
- Fix regression in `tootctl media remove-orphans` ([Gargron](https://github.com/mastodon/mastodon/pull/13405))
- Fix old unique jobs digests not having been cleaned up ([Gargron](https://github.com/mastodon/mastodon/pull/13683))
- Fix own following/followers not showing muted users ([ClearlyClaire](https://github.com/mastodon/mastodon/pull/13614))
-- Fix list of followed people ignoring sorting on Follows & Followers page ([taras2358](https://github.com/mastodon/mastodon/pull/13676))
+- Fix list of followed people ignoring sorting on Follows & Followers page ([taras2358](https://github.com/mastodon/mastodon/pull/13676))
- Fix wrong pgHero Content-Security-Policy when `CDN_HOST` is set ([ClearlyClaire](https://github.com/mastodon/mastodon/pull/13595))
- Fix needlessly deduplicating usernames on collisions with remote accounts when signing-up through SAML/CAS ([kaiyou](https://github.com/mastodon/mastodon/pull/13581))
- Fix page incorrectly scrolling when bringing up dropdown menus in web UI ([ClearlyClaire](https://github.com/mastodon/mastodon/pull/13574))
@@ -1270,6 +1279,7 @@ This means that no security fix will be made available for this branch after thi
- The issue only affects developers of apps who are shared between multiple users, such as server-side apps like cross-posters
## [3.1.3] - 2020-04-05
+
### Added
- Add ability to filter audit log in admin UI ([Gargron](https://github.com/mastodon/mastodon/pull/13381))
@@ -1343,6 +1353,7 @@ This means that no security fix will be made available for this branch after thi
- Fix re-sending of e-mail confirmation not being rate limited ([Gargron](https://github.com/mastodon/mastodon/pull/13360))
## [v3.1.2] - 2020-02-27
+
### Added
- Add `--reset-password` option to `tootctl accounts modify` ([ClearlyClaire](https://github.com/mastodon/mastodon/pull/13126))
@@ -1369,11 +1380,13 @@ This means that no security fix will be made available for this branch after thi
- Fix leak of arbitrary statuses through unfavourite action in REST API ([Gargron](https://github.com/mastodon/mastodon/pull/13161))
## [3.1.1] - 2020-02-10
+
### Fixed
- Fix yanked dependency preventing installation ([mayaeh](https://github.com/mastodon/mastodon/pull/13059))
## [3.1.0] - 2020-02-09
+
### Added
- Add bookmarks ([ClearlyClaire](https://github.com/mastodon/mastodon/pull/7107), [Gargron](https://github.com/mastodon/mastodon/pull/12494), [Gomasy](https://github.com/mastodon/mastodon/pull/12381))
@@ -1538,6 +1551,7 @@ This means that no security fix will be made available for this branch after thi
- Fix settings pages being cacheable by the browser ([Gargron](https://github.com/mastodon/mastodon/pull/12714))
## [3.0.1] - 2019-10-10
+
### Added
- Add `tootctl media usage` command ([Gargron](https://github.com/mastodon/mastodon/pull/12115))
@@ -1571,6 +1585,7 @@ This means that no security fix will be made available for this branch after thi
- Fix `tootctl accounts cull` advertising unused option flag ([Kjwon15](https://github.com/mastodon/mastodon/pull/12074))
## [3.0.0] - 2019-10-03
+
### Added
- Add "not available" label to unloaded media attachments in web UI ([Gargron](https://github.com/mastodon/mastodon/pull/11715), [Gargron](https://github.com/mastodon/mastodon/pull/11745))
@@ -1767,6 +1782,7 @@ This means that no security fix will be made available for this branch after thi
- Fix performance of GIF re-encoding and always strip EXIF data from videos ([Gargron](https://github.com/mastodon/mastodon/pull/12057))
## [2.9.3] - 2019-08-10
+
### Added
- Add GIF and WebP support for custom emojis ([Gargron](https://github.com/mastodon/mastodon/pull/11519))
@@ -1826,6 +1842,7 @@ This means that no security fix will be made available for this branch after thi
- Fix blocked domains still being able to fill database with account records ([Gargron](https://github.com/mastodon/mastodon/pull/11219))
## [2.9.2] - 2019-06-22
+
### Added
- Add `short_description` and `approval_required` to `GET /api/v1/instance` ([Gargron](https://github.com/mastodon/mastodon/pull/11146))
@@ -1840,6 +1857,7 @@ This means that no security fix will be made available for this branch after thi
- Fix audio not being downloaded from remote servers ([Gargron](https://github.com/mastodon/mastodon/pull/11145))
## [2.9.1] - 2019-06-22
+
### Added
- Add moderation API ([Gargron](https://github.com/mastodon/mastodon/pull/9387))
@@ -1865,6 +1883,7 @@ This means that no security fix will be made available for this branch after thi
- Fix scrolling behaviour in compose form ([ClearlyClaire](https://github.com/mastodon/mastodon/pull/11093))
## [2.9.0] - 2019-06-13
+
### Added
- **Add single-column mode in web UI** ([Gargron](https://github.com/mastodon/mastodon/pull/10807), [Gargron](https://github.com/mastodon/mastodon/pull/10848), [Gargron](https://github.com/mastodon/mastodon/pull/11003), [Gargron](https://github.com/mastodon/mastodon/pull/10961), [Hanage999](https://github.com/mastodon/mastodon/pull/10915), [noellabo](https://github.com/mastodon/mastodon/pull/10917), [abcang](https://github.com/mastodon/mastodon/pull/10859), [Gargron](https://github.com/mastodon/mastodon/pull/10820), [Gargron](https://github.com/mastodon/mastodon/pull/10835), [Gargron](https://github.com/mastodon/mastodon/pull/10809), [Gargron](https://github.com/mastodon/mastodon/pull/10963), [noellabo](https://github.com/mastodon/mastodon/pull/10883), [Hanage999](https://github.com/mastodon/mastodon/pull/10839))
@@ -1919,6 +1938,7 @@ This means that no security fix will be made available for this branch after thi
- Fix login sometimes redirecting to paths that are not pages ([Gargron](https://github.com/mastodon/mastodon/pull/11019))
## [2.8.4] - 2019-05-24
+
### Fixed
- Fix delivery not retrying on some inbox errors that should be retriable ([ClearlyClaire](https://github.com/mastodon/mastodon/pull/10812))
@@ -1930,6 +1950,7 @@ This means that no security fix will be made available for this branch after thi
- Require specific OAuth scopes for specific endpoints of the streaming API, instead of merely requiring a token for all endpoints, and allow using WebSockets protocol negotiation to specify the access token instead of using a query string ([ClearlyClaire](https://github.com/mastodon/mastodon/pull/10818))
## [2.8.3] - 2019-05-19
+
### Added
- Add `og:image:alt` OpenGraph tag ([BenLubar](https://github.com/mastodon/mastodon/pull/10779))
@@ -1952,6 +1973,7 @@ This means that no security fix will be made available for this branch after thi
- Fix "invited by" not showing up in admin UI ([ClearlyClaire](https://github.com/mastodon/mastodon/pull/10791))
## [2.8.2] - 2019-05-05
+
### Added
- Add `SOURCE_TAG` environment variable ([ushitora-anqou](https://github.com/mastodon/mastodon/pull/10698))
@@ -1964,6 +1986,7 @@ This means that no security fix will be made available for this branch after thi
- Fix closing video modal scrolling timelines to top ([ClearlyClaire](https://github.com/mastodon/mastodon/pull/10695))
## [2.8.1] - 2019-05-04
+
### Added
- Add link to existing domain block when trying to block an already-blocked domain ([ClearlyClaire](https://github.com/mastodon/mastodon/pull/10663))
@@ -2003,6 +2026,7 @@ This means that no security fix will be made available for this branch after thi
- Fix confirmation modals being too narrow for a secondary action button ([ClearlyClaire](https://github.com/mastodon/mastodon/pull/10586))
## [2.8.0] - 2019-04-10
+
### Added
- Add polls ([Gargron](https://github.com/mastodon/mastodon/pull/10111), [ClearlyClaire](https://github.com/mastodon/mastodon/pull/10155), [Gargron](https://github.com/mastodon/mastodon/pull/10184), [ClearlyClaire](https://github.com/mastodon/mastodon/pull/10196), [Gargron](https://github.com/mastodon/mastodon/pull/10248), [ClearlyClaire](https://github.com/mastodon/mastodon/pull/10255), [ClearlyClaire](https://github.com/mastodon/mastodon/pull/10322), [Gargron](https://github.com/mastodon/mastodon/pull/10138), [Gargron](https://github.com/mastodon/mastodon/pull/10139), [Gargron](https://github.com/mastodon/mastodon/pull/10144), [Gargron](https://github.com/mastodon/mastodon/pull/10145),[Gargron](https://github.com/mastodon/mastodon/pull/10146), [Gargron](https://github.com/mastodon/mastodon/pull/10148), [Gargron](https://github.com/mastodon/mastodon/pull/10151), [ClearlyClaire](https://github.com/mastodon/mastodon/pull/10150), [Gargron](https://github.com/mastodon/mastodon/pull/10168), [Gargron](https://github.com/mastodon/mastodon/pull/10165), [Gargron](https://github.com/mastodon/mastodon/pull/10172), [Gargron](https://github.com/mastodon/mastodon/pull/10170), [Gargron](https://github.com/mastodon/mastodon/pull/10171), [Gargron](https://github.com/mastodon/mastodon/pull/10186), [Gargron](https://github.com/mastodon/mastodon/pull/10189), [ClearlyClaire](https://github.com/mastodon/mastodon/pull/10200), [rinsuki](https://github.com/mastodon/mastodon/pull/10203), [Gargron](https://github.com/mastodon/mastodon/pull/10213), [Gargron](https://github.com/mastodon/mastodon/pull/10246), [Gargron](https://github.com/mastodon/mastodon/pull/10265), [Gargron](https://github.com/mastodon/mastodon/pull/10261), [ClearlyClaire](https://github.com/mastodon/mastodon/pull/10333), [Gargron](https://github.com/mastodon/mastodon/pull/10352), [ClearlyClaire](https://github.com/mastodon/mastodon/pull/10140), [ClearlyClaire](https://github.com/mastodon/mastodon/pull/10142), [ClearlyClaire](https://github.com/mastodon/mastodon/pull/10141), [ClearlyClaire](https://github.com/mastodon/mastodon/pull/10162), [ClearlyClaire](https://github.com/mastodon/mastodon/pull/10161), [ClearlyClaire](https://github.com/mastodon/mastodon/pull/10158), [ClearlyClaire](https://github.com/mastodon/mastodon/pull/10156), [ClearlyClaire](https://github.com/mastodon/mastodon/pull/10160), [Gargron](https://github.com/mastodon/mastodon/pull/10185), [Gargron](https://github.com/mastodon/mastodon/pull/10188), [ClearlyClaire](https://github.com/mastodon/mastodon/pull/10195), [ClearlyClaire](https://github.com/mastodon/mastodon/pull/10208), [Gargron](https://github.com/mastodon/mastodon/pull/10187), [ClearlyClaire](https://github.com/mastodon/mastodon/pull/10214), [ClearlyClaire](https://github.com/mastodon/mastodon/pull/10209))
@@ -2086,6 +2110,7 @@ This means that no security fix will be made available for this branch after thi
- Fix `tootctl accounts cull` sometimes removing accounts that are temporarily unreachable ([BenLubar](https://github.com/mastodon/mastodon/pull/10460))
## [2.7.4] - 2019-03-05
+
### Fixed
- Fix web UI not cleaning up notifications after block ([Gargron](https://github.com/mastodon/mastodon/pull/10108))
@@ -2100,6 +2125,7 @@ This means that no security fix will be made available for this branch after thi
- Fix edit profile page crash for suspended-then-unsuspended users ([ClearlyClaire](https://github.com/mastodon/mastodon/pull/10178))
## [2.7.3] - 2019-02-23
+
### Added
- Add domain filter to the admin federation page ([ClearlyClaire](https://github.com/mastodon/mastodon/pull/10071))
@@ -2117,6 +2143,7 @@ This means that no security fix will be made available for this branch after thi
- Change custom emojis to randomize stored file name ([hinaloe](https://github.com/mastodon/mastodon/pull/10090))
## [2.7.2] - 2019-02-17
+
### Added
- Add support for IPv6 in e-mail validation ([zoc](https://github.com/mastodon/mastodon/pull/10009))
@@ -2158,6 +2185,7 @@ This means that no security fix will be made available for this branch after thi
- Change error graphic to hover-to-play ([Gargron](https://github.com/mastodon/mastodon/pull/10055))
## [2.7.1] - 2019-01-28
+
### Fixed
- Fix SSO authentication not working due to missing agreement boolean ([Gargron](https://github.com/mastodon/mastodon/pull/9915))
@@ -2172,6 +2200,7 @@ This means that no security fix will be made available for this branch after thi
- Fix missing strong style for landing page description ([Kjwon15](https://github.com/mastodon/mastodon/pull/9892))
## [2.7.0] - 2019-01-20
+
### Added
- Add link for adding a user to a list from their profile ([namelessGonbai](https://github.com/mastodon/mastodon/pull/9062))
@@ -2301,6 +2330,7 @@ This means that no security fix will be made available for this branch after thi
- Add tombstones for remote statuses to prevent replay attacks ([ClearlyClaire](https://github.com/mastodon/mastodon/pull/9830))
## [2.6.5] - 2018-12-01
+
### Changed
- Change lists to display replies to others on the list and list owner ([ClearlyClaire](https://github.com/mastodon/mastodon/pull/9324))
@@ -2310,11 +2340,13 @@ This means that no security fix will be made available for this branch after thi
- Fix failures caused by commonly-used JSON-LD contexts being unavailable ([ClearlyClaire](https://github.com/mastodon/mastodon/pull/9412))
## [2.6.4] - 2018-11-30
+
### Fixed
- Fix yarn dependencies not installing due to yanked event-stream package ([Gargron](https://github.com/mastodon/mastodon/pull/9401))
## [2.6.3] - 2018-11-30
+
### Added
- Add hyphen to characters allowed in remote usernames ([ClearlyClaire](https://github.com/mastodon/mastodon/pull/9345))
@@ -2334,6 +2366,7 @@ This means that no security fix will be made available for this branch after thi
- Fix TLS handshake timeout not being enforced ([Gargron](https://github.com/mastodon/mastodon/pull/9381))
## [2.6.2] - 2018-11-23
+
### Added
- Add Page to whitelisted ActivityPub types ([mbajur](https://github.com/mastodon/mastodon/pull/9188))
@@ -2368,12 +2401,14 @@ This means that no security fix will be made available for this branch after thi
- Fix HTTP connection timeout of 10s not being enforced ([Gargron](https://github.com/mastodon/mastodon/pull/9329))
## [2.6.1] - 2018-10-30
+
### Fixed
- Fix resolving resources by URL not working due to a regression in [valerauko](https://github.com/mastodon/mastodon/pull/9132) ([Gargron](https://github.com/mastodon/mastodon/pull/9171))
- Fix reducer error in web UI when a conversation has no last status ([Gargron](https://github.com/mastodon/mastodon/pull/9173))
## [2.6.0] - 2018-10-30
+
### Added
- Add link ownership verification ([Gargron](https://github.com/mastodon/mastodon/pull/8703))
@@ -2478,11 +2513,13 @@ This means that no security fix will be made available for this branch after thi
- Fix handling of content types with profile ([valerauko](https://github.com/mastodon/mastodon/pull/9132))
## [2.5.2] - 2018-10-12
+
### Security
- Fix XSS vulnerability ([Gargron](https://github.com/mastodon/mastodon/pull/8959))
## [2.5.1] - 2018-10-07
+
### Fixed
- Fix database migrations for PostgreSQL below 9.5 ([Gargron](https://github.com/mastodon/mastodon/pull/8903))
diff --git a/Dockerfile b/Dockerfile
index 4458be1d986dc7..48d0875c0d37ad 100644
--- a/Dockerfile
+++ b/Dockerfile
@@ -49,7 +49,7 @@ ENV PATH="${PATH}:/opt/ruby/bin:/opt/node/bin"
RUN npm install -g npm@9 && \
npm install -g yarn && \
- gem install bundler && \
+ gem install bundler -v 2.3.0 && \
apt-get update && \
apt-get install -y --no-install-recommends git libicu-dev libidn11-dev \
libpq-dev shared-mime-info
diff --git a/app.json b/app.json
index c694908c539fb1..4c429365fc26e4 100644
--- a/app.json
+++ b/app.json
@@ -1,7 +1,7 @@
{
"name": "Mastodon",
"description": "A GNU Social-compatible microblogging server",
- "repository": "https://github.com/mastodon/mastodon",
+ "repository": "https://github.com/stsecurity/mastodon",
"logo": "https://github.com/mastodon.png",
"env": {
"HEROKU": {
diff --git a/app/controllers/admin/accounts_controller.rb b/app/controllers/admin/accounts_controller.rb
index 6ad393676dc853..daf5f1691f6616 100644
--- a/app/controllers/admin/accounts_controller.rb
+++ b/app/controllers/admin/accounts_controller.rb
@@ -164,5 +164,19 @@ def action_from_button
'reject'
end
end
+
+ def form_account_batch_params
+ params.require(:form_account_batch).permit(:action, account_ids: [])
+ end
+
+ def action_from_button
+ if params[:suspend]
+ 'suspend'
+ elsif params[:approve]
+ 'approve'
+ elsif params[:reject]
+ 'reject'
+ end
+ end
end
end
diff --git a/app/controllers/admin/instances_controller.rb b/app/controllers/admin/instances_controller.rb
index 7c44e88b7b227b..15810887316eb8 100644
--- a/app/controllers/admin/instances_controller.rb
+++ b/app/controllers/admin/instances_controller.rb
@@ -22,6 +22,15 @@ def destroy
redirect_to admin_instances_path, notice: I18n.t('admin.instances.destroyed_msg', domain: @instance.domain)
end
+ def destroy
+ authorize :instance, :destroy?
+
+ Admin::DomainPurgeWorker.perform_async(@instance.domain)
+
+ log_action :destroy, @instance
+ redirect_to admin_instances_path, notice: I18n.t('admin.instances.destroyed_msg', domain: @instance.domain)
+ end
+
def clear_delivery_errors
authorize :delivery, :clear_delivery_errors?
@instance.delivery_failure_tracker.clear_failures!
diff --git a/app/controllers/admin/sign_in_token_authentications_controller.rb b/app/controllers/admin/sign_in_token_authentications_controller.rb
new file mode 100644
index 00000000000000..e620ab2926d4b3
--- /dev/null
+++ b/app/controllers/admin/sign_in_token_authentications_controller.rb
@@ -0,0 +1,27 @@
+# frozen_string_literal: true
+
+module Admin
+ class SignInTokenAuthenticationsController < BaseController
+ before_action :set_target_user
+
+ def create
+ authorize @user, :enable_sign_in_token_auth?
+ @user.update(skip_sign_in_token: false)
+ log_action :enable_sign_in_token_auth, @user
+ redirect_to admin_account_path(@user.account_id)
+ end
+
+ def destroy
+ authorize @user, :disable_sign_in_token_auth?
+ @user.update(skip_sign_in_token: true)
+ log_action :disable_sign_in_token_auth, @user
+ redirect_to admin_account_path(@user.account_id)
+ end
+
+ private
+
+ def set_target_user
+ @user = User.find(params[:user_id])
+ end
+ end
+end
diff --git a/app/controllers/api/v1/admin/accounts_controller.rb b/app/controllers/api/v1/admin/accounts_controller.rb
index 423ef7cc45dcfa..7e048ea87667f4 100644
--- a/app/controllers/api/v1/admin/accounts_controller.rb
+++ b/app/controllers/api/v1/admin/accounts_controller.rb
@@ -1,6 +1,8 @@
# frozen_string_literal: true
class Api::V1::Admin::AccountsController < Api::BaseController
+ protect_from_forgery with: :exception
+
include Authorization
include AccountableConcern
diff --git a/app/controllers/api/v1/admin/reports_controller.rb b/app/controllers/api/v1/admin/reports_controller.rb
index 865ba3d23c8824..fbfd0ee128c1bd 100644
--- a/app/controllers/api/v1/admin/reports_controller.rb
+++ b/app/controllers/api/v1/admin/reports_controller.rb
@@ -1,6 +1,8 @@
# frozen_string_literal: true
class Api::V1::Admin::ReportsController < Api::BaseController
+ protect_from_forgery with: :exception
+
include Authorization
include AccountableConcern
diff --git a/app/helpers/languages_helper.rb b/app/helpers/languages_helper.rb
index 4077e19bdf0052..1904bc61b21cc0 100644
--- a/app/helpers/languages_helper.rb
+++ b/app/helpers/languages_helper.rb
@@ -206,6 +206,7 @@ module LanguagesHelper
'pt-PT': 'Português (Portugal)',
'sr-Latn': 'Srpski (latinica)',
'zh-CN': '简体中文',
+ 'zh-YR': '简体中文(百合)',
'zh-HK': '繁體中文(香港)',
'zh-TW': '繁體中文(臺灣)',
}.freeze
diff --git a/app/javascript/mastodon/features/compose/components/compose_form.js b/app/javascript/mastodon/features/compose/components/compose_form.js
index 4620d1c4313074..713a3b0756d5e4 100644
--- a/app/javascript/mastodon/features/compose/components/compose_form.js
+++ b/app/javascript/mastodon/features/compose/components/compose_form.js
@@ -24,6 +24,8 @@ import Icon from 'mastodon/components/icon';
const allowedAroundShortCode = '><\u0085\u0020\u00a0\u1680\u2000\u2001\u2002\u2003\u2004\u2005\u2006\u2007\u2008\u2009\u200a\u202f\u205f\u3000\u2028\u2029\u0009\u000a\u000b\u000c\u000d';
+const maxTootLength = 3000;
+
const messages = defineMessages({
placeholder: { id: 'compose_form.placeholder', defaultMessage: 'What is on your mind?' },
spoiler_placeholder: { id: 'compose_form.spoiler_placeholder', defaultMessage: 'Write your warning here' },
@@ -90,7 +92,7 @@ class ComposeForm extends ImmutablePureComponent {
const fulltext = this.getFulltextForCharacterCounting();
const isOnlyWhitespace = fulltext.length !== 0 && fulltext.trim().length === 0;
- return !(isSubmitting || isUploading || isChangingUpload || length(fulltext) > 500 || (isOnlyWhitespace && !anyMedia));
+ return !(isSubmitting || isUploading || isChangingUpload || length(fulltext) > maxTootLength || (isOnlyWhitespace && !anyMedia));
}
handleSubmit = () => {
@@ -273,7 +275,7 @@ class ComposeForm extends ImmutablePureComponent {
-
+
diff --git a/app/javascript/mastodon/features/compose/components/poll_form.js b/app/javascript/mastodon/features/compose/components/poll_form.js
index db49f90eb4cb95..333821301dd685 100644
--- a/app/javascript/mastodon/features/compose/components/poll_form.js
+++ b/app/javascript/mastodon/features/compose/components/poll_form.js
@@ -157,7 +157,7 @@ class PollForm extends ImmutablePureComponent {
-
= 4} className='button button-secondary' onClick={this.handleAddOption}>
+
= 12} className='button button-secondary' onClick={this.handleAddOption}>
{/* eslint-disable-next-line jsx-a11y/no-onchange */}
diff --git a/app/javascript/mastodon/locales/da.json b/app/javascript/mastodon/locales/da.json
index 5a03f86d08e6af..8f6182fa765087 100644
--- a/app/javascript/mastodon/locales/da.json
+++ b/app/javascript/mastodon/locales/da.json
@@ -1,5 +1,5 @@
{
- "account.account_note_header": "Notat",
+ "account.account_note_header": "Note",
"account.add_or_remove_from_list": "Tilføj eller fjern fra lister",
"account.badges.bot": "Bot",
"account.badges.group": "Gruppe",
@@ -16,7 +16,7 @@
"account.endorse": "Fremhæv på profil",
"account.follow": "Følg",
"account.followers": "Følgere",
- "account.followers.empty": "Ingen følger denne bruger endnu.",
+ "account.followers.empty": "Ingen følger brugeren endnu.",
"account.followers_counter": "{count, plural, one {{counter} Følger} other {{counter} Følgere}}",
"account.following": "Følger",
"account.following_counter": "{count, plural, one {{counter} Følges} other {{counter} Følges}}",
@@ -60,13 +60,13 @@
"announcement.announcement": "Bekendtgørelse",
"attachments_list.unprocessed": "(ubehandlet)",
"autosuggest_hashtag.per_week": "{count} pr. uge",
- "boost_modal.combo": "Du kan trykke på {combo} for at overspringe dette næste gang",
+ "boost_modal.combo": "Du kan trykke på {combo} for at springe over næste gang",
"bundle_column_error.body": "Noget gik galt under indlæsningen af denne komponent.",
- "bundle_column_error.retry": "Forsøg igen",
+ "bundle_column_error.retry": "Prøv igen",
"bundle_column_error.title": "Netværksfejl",
"bundle_modal_error.close": "Luk",
"bundle_modal_error.message": "Noget gik galt under indlæsningen af denne komponent.",
- "bundle_modal_error.retry": "Forsøg igen",
+ "bundle_modal_error.retry": "Prøv igen",
"column.blocks": "Blokerede brugere",
"column.bookmarks": "Bogmærker",
"column.community": "Lokal tidslinje",
@@ -99,11 +99,11 @@
"compose_form.hashtag_warning": "Da indlægget ikke er offentligt, vises det ikke under noget hashtag, idet kun offentlige indlæg kan søges via hashtags.",
"compose_form.lock_disclaimer": "Din konto er ikke {locked}. Enhver kan følge dig og se indlæg kun beregnet for følgere.",
"compose_form.lock_disclaimer.lock": "låst",
- "compose_form.placeholder": "Hvad tænker du på?",
+ "compose_form.placeholder": "Hvad vil du gerne fortælle om?",
"compose_form.poll.add_option": "Tilføj valgmulighed",
"compose_form.poll.duration": "Afstemningens varighed",
"compose_form.poll.option_placeholder": "Valgmulighed {number}",
- "compose_form.poll.remove_option": "Fjern denne valgmulighed",
+ "compose_form.poll.remove_option": "Fjern valgmulighed",
"compose_form.poll.switch_to_multiple": "Ændr afstemning til flervalgstype",
"compose_form.poll.switch_to_single": "Ændr afstemning til enkeltvalgstype",
"compose_form.publish": "Udgiv",
@@ -118,7 +118,7 @@
"confirmation_modal.cancel": "Afbryd",
"confirmations.block.block_and_report": "Blokér og Anmeld",
"confirmations.block.confirm": "Blokér",
- "confirmations.block.message": "Sikker på, at du vil blokere {name}?",
+ "confirmations.block.message": "Er du sikker på, du vil blokere {name}?",
"confirmations.delete.confirm": "Slet",
"confirmations.delete.message": "Sikker på, at du vil slette dette indlæg?",
"confirmations.delete_list.confirm": "Slet",
@@ -140,9 +140,9 @@
"confirmations.unfollow.message": "Sikker på, at du ikke længere vil følge {name}?",
"conversation.delete": "Slet samtale",
"conversation.mark_as_read": "Markér som læst",
- "conversation.open": "Vis konversation",
+ "conversation.open": "Vis samtale",
"conversation.with": "Med {names}",
- "directory.federated": "Fra kendt fedivers",
+ "directory.federated": "Fra kendt Fællesvers",
"directory.local": "Kun fra {domain}",
"directory.new_arrivals": "Nye ankomster",
"directory.recently_active": "Nyligt aktive",
@@ -158,7 +158,7 @@
"emoji_button.not_found": "Ingen matchende emojis fundet",
"emoji_button.objects": "Objekter",
"emoji_button.people": "Personer",
- "emoji_button.recent": "Oftest brugt",
+ "emoji_button.recent": "De sædvanlige",
"emoji_button.search": "Søg...",
"emoji_button.search_results": "Søgeresultater",
"emoji_button.symbols": "Symboler",
@@ -216,7 +216,7 @@
"hashtag.column_header.tag_mode.none": "uden {additional}",
"hashtag.column_settings.select.no_options_message": "Ingen forslag fundet",
"hashtag.column_settings.select.placeholder": "Angiv hashtags…",
- "hashtag.column_settings.tag_mode.all": "Alle disse",
+ "hashtag.column_settings.tag_mode.all": "Allesammen",
"hashtag.column_settings.tag_mode.any": "Nogle af disse",
"hashtag.column_settings.tag_mode.none": "Ingen af disse",
"hashtag.column_settings.tag_toggle": "Inkludér ekstra tags for denne kolonne",
@@ -502,7 +502,7 @@
"time_remaining.days": "{number, plural, one {# dag} other {# dage}} tilbage",
"time_remaining.hours": "{number, plural, one {# time} other {# timer}} tilbage",
"time_remaining.minutes": "{number, plural, one {# minut} other {# minutter}} tilbage",
- "time_remaining.moments": "Få øjeblikke tilbage",
+ "time_remaining.moments": "Et øjeblik tilbage",
"time_remaining.seconds": "{number, plural, one {# sekund} other {# sekunder}} tilbage",
"timeline_hint.remote_resource_not_displayed": "{resource} fra andre servere vises ikke.",
"timeline_hint.resources.followers": "Følgere",
@@ -529,7 +529,7 @@
"upload_modal.apply": "Anvend",
"upload_modal.applying": "Effektuerer…",
"upload_modal.choose_image": "Vælg billede",
- "upload_modal.description_placeholder": "En hurtig brun ræv hopper over den dovne hund",
+ "upload_modal.description_placeholder": "Høj bly gom vandt fræk sexquiz på wc",
"upload_modal.detect_text": "Detektér tekst i billede",
"upload_modal.edit_media": "Redigér medie",
"upload_modal.hint": "Klik eller træk cirklen i forhåndsvisningen for at vælge det fokuspunkt, der altid vil være synligt på alle miniaturer.",
@@ -545,5 +545,5 @@
"video.mute": "Tavsgør lyd",
"video.pause": "Pausér",
"video.play": "Afspil",
- "video.unmute": "Fjern lydtavsgørelse"
+ "video.unmute": "Tænd for lyden"
}
diff --git a/app/javascript/mastodon/locales/eo.json b/app/javascript/mastodon/locales/eo.json
index 9179945614a73a..a4ad9e5a8b696d 100644
--- a/app/javascript/mastodon/locales/eo.json
+++ b/app/javascript/mastodon/locales/eo.json
@@ -26,7 +26,7 @@
"account.joined": "Kuniĝis {date}",
"account.link_verified_on": "La posedanto de tiu ligilo estis kontrolita je {date}",
"account.locked_info": "La privateco de tiu konto estas elektita kiel fermita. La posedanto povas mane akcepti tiun, kiu povas sekvi rin.",
- "account.media": "Aŭdovidaĵoj",
+ "account.media": "Amaskomunikiloj",
"account.mention": "Mencii @{name}",
"account.moved_to": "{name} moviĝis al:",
"account.mute": "Silentigi @{name}",
diff --git a/app/javascript/mastodon/locales/kmr.json b/app/javascript/mastodon/locales/kmr.json
new file mode 100644
index 00000000000000..2ef4da0a47f058
--- /dev/null
+++ b/app/javascript/mastodon/locales/kmr.json
@@ -0,0 +1,502 @@
+{
+ "account.account_note_header": "Nîşe",
+ "account.add_or_remove_from_list": "Tevlî bike an rake ji rêzokê",
+ "account.badges.bot": "Bot",
+ "account.badges.group": "Kom",
+ "account.block": "@{name} asteng bike",
+ "account.block_domain": "{domain} navpar asteng bike",
+ "account.blocked": "Astengkirî",
+ "account.browse_more_on_origin_server": "Li pelên resen bêhtir bigere",
+ "account.cancel_follow_request": "Daxwaza şopandinê rake",
+ "account.direct": "Peyamekê bişîne @{name}",
+ "account.disable_notifications": "Êdî min agahdar neke gava @{name} diweşîne",
+ "account.domain_blocked": "Navper hate astengkirin",
+ "account.edit_profile": "Profîl serrast bike",
+ "account.enable_notifications": "Min agahdar bike gava @{name} diweşîne",
+ "account.endorse": "Taybetiyên li ser profîl",
+ "account.follow": "Bişopîne",
+ "account.followers": "Şopîner",
+ "account.followers.empty": "Kesekî hin ev bikarhêner neşopandiye.",
+ "account.followers_counter": "{count, plural, one {{counter} Follower} other {{counter} Followers}}",
+ "account.following_counter": "{count, plural, one {{counter} Dişopîne} other {{counter} Dişopîne}}",
+ "account.follows.empty": "Ev bikarhêner hin kesekî heya niha neşopandiye.",
+ "account.follows_you": "Te dişopîne",
+ "account.hide_reblogs": "Bilindkirinên ji @{name} veşêre",
+ "account.joined": "Tevlîbû di {date} de",
+ "account.last_status": "Çalakiya dawî",
+ "account.link_verified_on": "Xwedaniya li vê girêdanê di {date} de hatiye kontrolkirin",
+ "account.locked_info": "Rewşa vê ajimêrê wek kilît kirî hatiye saz kirin. Xwedî yê ajimêrê, kesên vê bişopîne bi dest vekolin dike.",
+ "account.media": "Medya",
+ "account.mention": "Qal @{name} bike",
+ "account.moved_to": "{name} hate livandin bo:",
+ "account.mute": "@{name} Bêdeng bike",
+ "account.mute_notifications": "Agahdariyan ji @{name} bêdeng bike",
+ "account.muted": "Bêdengkirî",
+ "account.never_active": "Tu car",
+ "account.posts": "Şandî",
+ "account.posts_with_replies": "Şandî û bersiv",
+ "account.report": "@{name} Ragihîne",
+ "account.requested": "Li benda erêkirinê ye. Ji bo betal kirina daxwazê pêl bikin",
+ "account.share": "Profîla @{name} parve bike",
+ "account.show_reblogs": "Bilindkirinên ji @{name} nîşan bike",
+ "account.statuses_counter": "{count, plural,one {{counter} şandî}other {{counter} şandî}}",
+ "account.unblock": "Astengê li ser @{name} rake",
+ "account.unblock_domain": "Astengê li ser navperê {domain} rake",
+ "account.unendorse": "Li ser profîl nîşan neke",
+ "account.unfollow": "Neşopîne",
+ "account.unmute": "@{name} Bêdeng bike",
+ "account.unmute_notifications": "Agahdariyan ji @{name} bêdeng bike",
+ "account_note.placeholder": "Bitikîne bo nîşeyekê tevlî bikî",
+ "admin.dashboard.daily_retention": "Rêjeya ragirtina bikarhêner bi roj piştî tomarkirinê",
+ "admin.dashboard.monthly_retention": "Rêjeya ragirtina bikarhêner bi meh piştî tomarkirinê",
+ "admin.dashboard.retention.average": "Navîn",
+ "admin.dashboard.retention.cohort": "Meha tomarkirinê",
+ "admin.dashboard.retention.cohort_size": "Bikarhênerên nû",
+ "alert.rate_limited.message": "Jkx dîsa biceribîne piştî {retry_time, time, medium}.\n \n",
+ "alert.rate_limited.title": "Rêje sînorkirî ye",
+ "alert.unexpected.message": "Çewtiyeke bêhêvî çê bû.",
+ "alert.unexpected.title": "Wey li min!",
+ "announcement.announcement": "Daxuyanî",
+ "attachments_list.unprocessed": "(bêpêvajo)",
+ "autosuggest_hashtag.per_week": "Her hefte {count}",
+ "boost_modal.combo": "Ji bo derbas bî carekî din de pêlê {combo} bike",
+ "bundle_column_error.body": "Di dema barkirina vê hêmanê de tiştek çewt çê bû.",
+ "bundle_column_error.retry": "Dîsa biceribîne",
+ "bundle_column_error.title": "Çewtiya torê",
+ "bundle_modal_error.close": "Bigire",
+ "bundle_modal_error.message": "Di dema barkirina vê hêmanê de tiştek çewt çê bû.",
+ "bundle_modal_error.retry": "Dîsa bicerbîne",
+ "column.blocks": "Bikarhênerên astengkirî",
+ "column.bookmarks": "Şûnpel",
+ "column.community": "Demnameya herêmî",
+ "column.direct": "Peyamên taybet",
+ "column.directory": "Li profîlan bigere",
+ "column.domain_blocks": "Navperên astengkirî",
+ "column.favourites": "Bijarte",
+ "column.follow_requests": "Daxwazên şopandinê",
+ "column.home": "Serrûpel",
+ "column.lists": "Rêzok",
+ "column.mutes": "Bikarhênerên bêdengkirî",
+ "column.notifications": "Agahdarî",
+ "column.pins": "Şandiya derzîkirî",
+ "column.public": "Demnameyê federalîkirî",
+ "column_back_button.label": "Veger",
+ "column_header.hide_settings": "Sazkariyan veşêre",
+ "column_header.moveLeft_settings": "Stûnê bilivîne bo çepê",
+ "column_header.moveRight_settings": "Stûnê bilivîne bo rastê",
+ "column_header.pin": "Bi derzî bike",
+ "column_header.show_settings": "Sazkariyan nîşan bide",
+ "column_header.unpin": "Bi derzî neke",
+ "column_subheading.settings": "Sazkarî",
+ "community.column_settings.local_only": "Tenê herêmî",
+ "community.column_settings.media_only": "Tenê media",
+ "community.column_settings.remote_only": "Tenê ji dûr ve",
+ "compose_form.direct_message_warning": "Ev şandî tenê ji bikarhênerên qalkirî re wê were şandin.",
+ "compose_form.direct_message_warning_learn_more": "Bêtir fêr bibe",
+ "compose_form.hashtag_warning": "Ev şandî ji ber ku nehatiye tomarkirin dê di binê hashtagê de neyê tomar kirin. Tenê peyamên gelemperî dikarin bi hashtagê werin lêgerîn.",
+ "compose_form.lock_disclaimer": "Ajimêrê te {locked} nîne. Herkes dikare te bişopîne da ku şandiyên te yên tenê şopînerên te ra xûya dibin bibînin.",
+ "compose_form.lock_disclaimer.lock": "girtî ye",
+ "compose_form.placeholder": "Çi di hişê te derbas dibe?",
+ "compose_form.poll.add_option": "Hilbijarekî tevlî bike",
+ "compose_form.poll.duration": "Dema rapirsî yê",
+ "compose_form.poll.option_placeholder": "{number} Hilbijêre",
+ "compose_form.poll.remove_option": "Vê hilbijarê rake",
+ "compose_form.poll.switch_to_multiple": "Rapirsî yê biguherînin da ku destûr bidin vebijarkên pirjimar",
+ "compose_form.poll.switch_to_single": "Rapirsîyê biguherîne da ku mafê bidî tenê vebijêrkek",
+ "compose_form.publish": "Toot",
+ "compose_form.publish_loud": "{publish}!",
+ "compose_form.save_changes": "Guhertinan tomar bike",
+ "compose_form.sensitive.hide": "{count, plural, one {Medya wekî hestiyar nîşan bide} other {Medya wekî hestiyar nîşan bide}}",
+ "compose_form.sensitive.marked": "{count, plural, one {Medya wekî hestiyar hate nîşan} other {Medya wekî hestiyar nîşan}}",
+ "compose_form.sensitive.unmarked": "{count, plural, one {Medya wekî hestiyar nehatiye nîşan} other {Medya wekî hestiyar nehatiye nîşan}}",
+ "compose_form.spoiler.marked": "Hişyariya naverokê rake",
+ "compose_form.spoiler.unmarked": "Hişyariya naverokê tevlî bike",
+ "compose_form.spoiler_placeholder": "Li vir hişyariya xwe binivîse",
+ "confirmation_modal.cancel": "Dev jê berde",
+ "confirmations.block.block_and_report": "Asteng bike & ragihîne",
+ "confirmations.block.confirm": "Asteng bike",
+ "confirmations.block.message": "Ma tu dixwazî ku {name} asteng bikî?",
+ "confirmations.delete.confirm": "Jê bibe",
+ "confirmations.delete.message": "Ma tu dixwazî vê şandiyê jê bibî?",
+ "confirmations.delete_list.confirm": "Jê bibe",
+ "confirmations.delete_list.message": "Ma tu dixwazî bi awayekî herdemî vê rêzokê jê bibî?",
+ "confirmations.discard_edit_media.confirm": "Biavêje",
+ "confirmations.discard_edit_media.message": "Guhertinên neqedandî di danasîna an pêşdîtina medyayê de hene, wan bi her awayî bavêje?",
+ "confirmations.domain_block.confirm": "Hemî navperê asteng bike",
+ "confirmations.domain_block.message": "Tu ji xwe bawerî, bi rastî tu dixwazî hemû {domain} asteng bikî? Di gelek rewşan de asteng kirin an jî bêdeng kirin têrê dike û tê tercîh kirin. Tu nikarî naveroka vê navperê di demnameyê an jî agahdariyên xwe de bibînî. Şopînerên te yê di vê navperê were jêbirin.",
+ "confirmations.logout.confirm": "Derkeve",
+ "confirmations.logout.message": "Ma tu dixwazî ku derkevî?",
+ "confirmations.mute.confirm": "Bêdeng bike",
+ "confirmations.mute.explanation": "Ev ê şandinên ji wan tê û şandinên ku behsa wan dike veşêre, lê hê jî maf dide ku ew şandinên te bibînin û te bişopînin.",
+ "confirmations.mute.message": "Bi rastî tu dixwazî {name} bêdeng bikî?",
+ "confirmations.redraft.confirm": "Jê bibe & ji nû ve serrast bike",
+ "confirmations.redraft.message": "Bi rastî tu dixwazî şandî ye jê bibî û nûve reşnivîsek çê bikî? Bijare û şandî wê wenda bibin û bersivên ji bo şandiyê resen wê sêwî bimînin.",
+ "confirmations.reply.confirm": "Bersivê bide",
+ "confirmations.reply.message": "Bersiva niha li ser peyama ku tu niha berhev dikî dê binivsîne. Ma pê bawer î ku tu dixwazî bidomînî?",
+ "confirmations.unfollow.confirm": "Neşopîne",
+ "confirmations.unfollow.message": "Ma tu dixwazî ku dev ji şopa {name} berdî?",
+ "conversation.delete": "Axaftinê jê bibe",
+ "conversation.mark_as_read": "Wekî xwendî nîşan bide",
+ "conversation.open": "Axaftinê nîşan bide",
+ "conversation.with": "Bi {names} re",
+ "directory.federated": "Ji fediversên naskirî",
+ "directory.local": "Tenê ji {domain}",
+ "directory.new_arrivals": "Kesên ku nû hatine",
+ "directory.recently_active": "Di demên dawî de çalak",
+ "embed.instructions": "Bi jêgirtina koda jêrîn vê şandiyê li ser malpera xwe bicîh bikin.",
+ "embed.preview": "Wa ye wê wusa xuya bike:",
+ "emoji_button.activity": "Çalakî",
+ "emoji_button.custom": "Kesanekirî",
+ "emoji_button.flags": "Nîşankirî",
+ "emoji_button.food": "Xwarin û vexwarin",
+ "emoji_button.label": "Emoji têxe",
+ "emoji_button.nature": "Sirûştî",
+ "emoji_button.not_found": "Hestokên lihevhatî nehate dîtin",
+ "emoji_button.objects": "Tişt",
+ "emoji_button.people": "Mirov",
+ "emoji_button.recent": "Pir caran tê bikaranîn",
+ "emoji_button.search": "Bigere...",
+ "emoji_button.search_results": "Encamên lêgerînê",
+ "emoji_button.symbols": "Sembol",
+ "emoji_button.travel": "Geşt û şûn",
+ "empty_column.account_suspended": "Ajimêr hatiye rawestandin",
+ "empty_column.account_timeline": "Li vir şandî tune!",
+ "empty_column.account_unavailable": "Profîl nayê peydakirin",
+ "empty_column.blocks": "Te tu bikarhêner asteng nekiriye.",
+ "empty_column.bookmarked_statuses": "Hîn tu peyamên şûnpelkirî tuneye. Gava ku hûn yek şûnpel bikin, ew ê li vir xûya bike.",
+ "empty_column.community": "Demnameya herêmî vala ye. Tiştek ji raya giştî re binivsînin da ku rûpel biherike!",
+ "empty_column.direct": "Hêj peyameke te yê rasterast tuneye. Gava ku tu yekî bişeynî an jî bigirî, ew ê li vir xûya bike.",
+ "empty_column.domain_blocks": "Hê jî navperên hatine asteng kirin tune ne.",
+ "empty_column.favourited_statuses": "Hîn tu peyamên te yên bijare tunene. Gava ku te yekî bijart, ew ê li vir xûya bike.",
+ "empty_column.favourites": "Hîn tu kes vê peyamê nebijartiye. Gava ku hin kes bijartin, ew ê li vir xûya bikin.",
+ "empty_column.follow_recommendations": "Wusa dixuye ku ji bo we tu pêşniyar nehatine çêkirin. Hûn dikarin lêgerînê bikarbînin da ku li kesên ku hûn nas dikin bigerin an hashtagên trendî bigerin.",
+ "empty_column.follow_requests": "Hê jî daxwaza şopandinê tunne ye. Dema daxwazek hat, yê li vir were nîşan kirin.",
+ "empty_column.hashtag": "Di vê hashtagê de hêj tiştekî tune.",
+ "empty_column.home": "Demnameya mala we vala ye! Ji bona tijîkirinê bêtir mirovan bişopînin. {suggestions}",
+ "empty_column.home.suggestions": "Hinek pêşniyaran bibîne",
+ "empty_column.list": "Di vê rêzokê de hîn tiştek tune ye. Gava ku endamên vê rêzokê peyamên nû biweşînin, ew ê li vir xuya bibin.",
+ "empty_column.lists": "Hêj qet rêzokê te tunne ye. Dema yek peyda bû, yê li vir were nîşan kirin.",
+ "empty_column.mutes": "Te tu bikarhêner bêdeng nekiriye.",
+ "empty_column.notifications": "Hêj hişyariyên te tunene. Dema ku mirovên din bi we re têkilî danîn, hûn ê wê li vir bibînin.",
+ "empty_column.public": "Li vir tiştekî tuneye! Ji raya giştî re tiştekî binivîsîne, an ji bo tijîkirinê ji rajekerên din bikarhêneran bi destan bişopînin",
+ "error.unexpected_crash.explanation": "Ji ber xeletîyeke di koda me da an jî ji ber mijara lihevhatina gerokan, ev rûpel rast nehat nîşandan.",
+ "error.unexpected_crash.explanation_addons": "Ev rûpel bi awayekî rast nehat nîşandan. Ev çewtî mimkûn e ji ber lêzêdekirina gerokan an jî amûrên wergera xweberî pêk tê.",
+ "error.unexpected_crash.next_steps": "Nûkirina rûpelê biceribîne. Heke ev bi kêr neyê, dibe ku te hîn jî bi rêya gerokeke cuda an jî sepana xwecîhê Mastodonê bi kar bîne.",
+ "error.unexpected_crash.next_steps_addons": "Ne çalak kirin û nûkirina rûpelê biceribîne. Heke ev bi kêr neyê, dibe ku te hîn jî bi rêya gerokeke cuda an jî sepana xwecîhê Mastodonê bi kar bîne.",
+ "errors.unexpected_crash.copy_stacktrace": "Şopa gemara (stacktrace) tûrikê ra jê bigire",
+ "errors.unexpected_crash.report_issue": "Pirsgirêkekê ragihîne",
+ "follow_recommendations.done": "Qediya",
+ "follow_recommendations.heading": "Mirovên ku tu dixwazî ji wan peyaman bibînî bişopîne! Hin pêşnîyar li vir in.",
+ "follow_recommendations.lead": "Li gorî rêza kronolojîkî peyamên mirovên ku tu dişopînî dê demnameya te de xûya bike. Ji xeletiyan netirse, bi awayekî hêsan her wextî tu dikarî dev ji şopandinê berdî!",
+ "follow_request.authorize": "Mafê bide",
+ "follow_request.reject": "Nepejirîne",
+ "follow_requests.unlocked_explanation": "Tevlî ku ajimêra te ne kilît kiriye, karmendên {domain} digotin qey tu dixwazî ku pêşdîtina daxwazên şopandinê bi destan bike.",
+ "generic.saved": "Tomarkirî",
+ "getting_started.developers": "Pêşdebir",
+ "getting_started.directory": "Rêgeha profîlê",
+ "getting_started.documentation": "Pelbend",
+ "getting_started.heading": "Destpêkirin",
+ "getting_started.invite": "Mirovan Vexwîne",
+ "getting_started.open_source_notice": "Mastodon nermalava çavkaniya vekirî ye. Tu dikarî pirsgirêkan li ser GitHub-ê ragihînî di {github} de an jî dikarî tevkariyê bikî.",
+ "getting_started.security": "Sazkariyên ajimêr",
+ "getting_started.terms": "Mercên karûberan",
+ "hashtag.column_header.tag_mode.all": "û {additional}",
+ "hashtag.column_header.tag_mode.any": "an {additional}",
+ "hashtag.column_header.tag_mode.none": "bêyî {additional}",
+ "hashtag.column_settings.select.no_options_message": "Ti pêşniyar nehatin dîtin",
+ "hashtag.column_settings.select.placeholder": "Têkeve hashtagê…",
+ "hashtag.column_settings.tag_mode.all": "Van hemûyan",
+ "hashtag.column_settings.tag_mode.any": "Yek ji van",
+ "hashtag.column_settings.tag_mode.none": "Ne yek ji van",
+ "hashtag.column_settings.tag_toggle": "Ji bo vê stûnê hin pêvekan tevlî bike",
+ "home.column_settings.basic": "Bingehîn",
+ "home.column_settings.show_reblogs": "Bilindkirinan nîşan bike",
+ "home.column_settings.show_replies": "Bersivan nîşan bide",
+ "home.hide_announcements": "Reklaman veşêre",
+ "home.show_announcements": "Reklaman nîşan bide",
+ "intervals.full.days": "{number, plural, one {# roj} other {# roj}}",
+ "intervals.full.hours": "{number, plural, one {# demjimêr} other {# demjimêr}}\n \n",
+ "intervals.full.minutes": "{number, plural, one {# xulek} other {# xulek}}",
+ "keyboard_shortcuts.back": "Vegere paşê",
+ "keyboard_shortcuts.blocked": "Rêzoka bikarhênerên astengkirî veke",
+ "keyboard_shortcuts.boost": "Şandiyê bilind bike",
+ "keyboard_shortcuts.column": "Stûna balkişandinê",
+ "keyboard_shortcuts.compose": "Bal bikşîne cîhê nivîsê/textarea",
+ "keyboard_shortcuts.description": "Danasîn",
+ "keyboard_shortcuts.direct": "Ji stûnê peyamên rasterast veke",
+ "keyboard_shortcuts.down": "Di rêzokê de dakêşe jêr",
+ "keyboard_shortcuts.enter": "Şandiyê veke",
+ "keyboard_shortcuts.favourite": "Şandiya bijarte",
+ "keyboard_shortcuts.favourites": "Rêzokên bijarte veke",
+ "keyboard_shortcuts.federated": "Demnameyê federalîkirî veke",
+ "keyboard_shortcuts.heading": "Kurterêyên klavyeyê",
+ "keyboard_shortcuts.home": "Demnameyê veke",
+ "keyboard_shortcuts.hotkey": "Bişkoka kurterê",
+ "keyboard_shortcuts.legend": "Vê çîrokê nîşan bike",
+ "keyboard_shortcuts.local": "Demnameya herêmî veke",
+ "keyboard_shortcuts.mention": "Qala nivîskarî/ê bike",
+ "keyboard_shortcuts.muted": "Rêzoka bikarhênerên bêdeng kirî veke",
+ "keyboard_shortcuts.my_profile": "Profîla xwe veke",
+ "keyboard_shortcuts.notifications": "Stûnê agahdariyan veke",
+ "keyboard_shortcuts.open_media": "Medya veke",
+ "keyboard_shortcuts.pinned": "Şandiyên derzîkirî veke",
+ "keyboard_shortcuts.profile": "Profîla nivîskaran veke",
+ "keyboard_shortcuts.reply": "Bersivê bide şandiyê",
+ "keyboard_shortcuts.requests": "Rêzoka daxwazên şopandinê veke",
+ "keyboard_shortcuts.search": "Bal bide şivika lêgerînê",
+ "keyboard_shortcuts.spoilers": "Zeviya hişyariya naverokê nîşan bide/veşêre",
+ "keyboard_shortcuts.start": "Stûna \"destpêkê\" veke",
+ "keyboard_shortcuts.toggle_hidden": "Nivîsa paş hişyariya naverokê nîşan bide/veşêre",
+ "keyboard_shortcuts.toggle_sensitivity": "Medyayê nîşan bide/veşêre",
+ "keyboard_shortcuts.toot": "Dest bi şandiyeke nû bike",
+ "keyboard_shortcuts.unfocus": "Bal nede cîhê nivîsê /lêgerînê",
+ "keyboard_shortcuts.up": "Di rêzokê de rake jor",
+ "lightbox.close": "Bigire",
+ "lightbox.compress": "Qutîya wêneya nîşan dike bitepisîne",
+ "lightbox.expand": "Qutîya wêneya nîşan dike fireh bike",
+ "lightbox.next": "Pêş",
+ "lightbox.previous": "Paş",
+ "lists.account.add": "Tevlî rêzokê bike",
+ "lists.account.remove": "Ji rêzokê rake",
+ "lists.delete": "Rêzokê jê bibe",
+ "lists.edit": "Rêzokê serrast bike",
+ "lists.edit.submit": "Sernavê biguherîne",
+ "lists.new.create": "Rêzokê tevlî bike",
+ "lists.new.title_placeholder": "Sernavê rêzoka nû",
+ "lists.replies_policy.followed": "Bikarhênereke şopandî",
+ "lists.replies_policy.list": "Endamên rêzokê",
+ "lists.replies_policy.none": "Ne yek",
+ "lists.replies_policy.title": "Bersivan nîşan bide:",
+ "lists.search": "Di navbera kesên ku te dişopînin bigere",
+ "lists.subheading": "Rêzokên te",
+ "load_pending": "{count, plural, one {# hêmaneke nû} other {#hêmaneke nû}}",
+ "loading_indicator.label": "Tê barkirin...",
+ "media_gallery.toggle_visible": "{number, plural, one {Wêneyê veşêre} other {Wêneyan veşêre}}",
+ "missing_indicator.label": "Nehate dîtin",
+ "missing_indicator.sublabel": "Ev çavkanî nehat dîtin",
+ "mute_modal.duration": "Dem",
+ "mute_modal.hide_notifications": "Agahdariyan ji ev bikarhêner veşêre?",
+ "mute_modal.indefinite": "Nediyar",
+ "navigation_bar.apps": "Sepana mobîl",
+ "navigation_bar.blocks": "Bikarhênerên astengkirî",
+ "navigation_bar.bookmarks": "Şûnpel",
+ "navigation_bar.community_timeline": "Demnameya herêmî",
+ "navigation_bar.compose": "Şandiyeke nû binivsîne",
+ "navigation_bar.direct": "Peyamên rasterast",
+ "navigation_bar.discover": "Vekolê",
+ "navigation_bar.domain_blocks": "Navparên astengkirî",
+ "navigation_bar.edit_profile": "Profîl serrast bike",
+ "navigation_bar.favourites": "Bijarte",
+ "navigation_bar.filters": "Peyvên bêdengkirî",
+ "navigation_bar.follow_requests": "Daxwazên şopandinê",
+ "navigation_bar.follows_and_followers": "Yên tê şopandin û şopîner",
+ "navigation_bar.info": "Derbarê vî rajekarî",
+ "navigation_bar.keyboard_shortcuts": "Bişkoka kurterê",
+ "navigation_bar.lists": "Rêzok",
+ "navigation_bar.logout": "Derkeve",
+ "navigation_bar.mutes": "Bikarhênerên bêdengkirî",
+ "navigation_bar.personal": "Kesanî",
+ "navigation_bar.pins": "Şandiya derzîkirî",
+ "navigation_bar.preferences": "Sazkarî",
+ "navigation_bar.public_timeline": "Demnameyê federalîkirî",
+ "navigation_bar.security": "Ewlehî",
+ "notification.favourite": "{name} şandiya te hez kir",
+ "notification.follow": "{name} te şopand",
+ "notification.follow_request": "{name} dixwazê te bişopîne",
+ "notification.mention": "{name} qale te kir",
+ "notification.own_poll": "Rapirsîya te qediya",
+ "notification.poll": "Rapirsiyeke ku te deng daye qediya",
+ "notification.reblog": "{name} şandiya te bilind kir",
+ "notification.status": "{name} niha şand",
+ "notification.update": "{name} edited a post",
+ "notifications.clear": "Agahdariyan pak bike",
+ "notifications.clear_confirmation": "Bi rastî tu dixwazî bi awayekî dawî hemû agahdariyên xwe pak bikî?",
+ "notifications.column_settings.alert": "Agahdariyên sermaseyê",
+ "notifications.column_settings.favourite": "Bijarte:",
+ "notifications.column_settings.filter_bar.advanced": "Hemû beşan nîşan bide",
+ "notifications.column_settings.filter_bar.category": "Şivika parzûna bilêz",
+ "notifications.column_settings.filter_bar.show_bar": "Darika parzûnê nîşan bide",
+ "notifications.column_settings.follow": "Şopînerên nû:",
+ "notifications.column_settings.follow_request": "Daxwazên şopandinê nû:",
+ "notifications.column_settings.mention": "Qalkirin:",
+ "notifications.column_settings.poll": "Encamên rapirsiyê:",
+ "notifications.column_settings.push": "Agahdarîyên yekser",
+ "notifications.column_settings.reblog": "Bilindkirî:",
+ "notifications.column_settings.show": "Di nav stûnê de nîşan bike",
+ "notifications.column_settings.sound": "Deng lêxe",
+ "notifications.column_settings.status": "Şandiyên nû:",
+ "notifications.column_settings.unread_notifications.category": "Agahdariyên nexwendî",
+ "notifications.column_settings.unread_notifications.highlight": "Agahiyên nexwendî nîşan bike",
+ "notifications.column_settings.update": "Edits:",
+ "notifications.filter.all": "Hemû",
+ "notifications.filter.boosts": "Bilindkirî",
+ "notifications.filter.favourites": "Bijarte",
+ "notifications.filter.follows": "Şopîner",
+ "notifications.filter.mentions": "Qalkirin",
+ "notifications.filter.polls": "Encamên rapirsiyê",
+ "notifications.filter.statuses": "Ji kesên tu dişopînî re rojanekirin",
+ "notifications.grant_permission": "Destûrê bide.",
+ "notifications.group": "{count} agahdarî",
+ "notifications.mark_as_read": "Hemî agahdarîya wek xwendî nîşan bike",
+ "notifications.permission_denied": "Agahdarîyên sermaseyê naxebite ji ber ku berê de daxwazî ya destûr dayîna gerokê hati bû red kirin",
+ "notifications.permission_denied_alert": "Agahdarîyên sermaseyê nay çalak kirin, ji ber ku destûr kirina gerokê pêşî de hati bû red kirin",
+ "notifications.permission_required": "Agahdarîyên sermaseyê naxebite çunkî mafê pêwîst dike nehatiye dayîn.",
+ "notifications_permission_banner.enable": "Agahdarîyên sermaseyê çalak bike",
+ "notifications_permission_banner.how_to_control": "Da ku agahdariyên mastodon bistînî gava ne vekirî be. Agahdariyên sermaseyê çalak bike\n Tu dikarî agahdariyên sermaseyê bi rê ve bibî ku bi hemû cureyên çalakiyên ên ku agahdariyan rû didin ku bi riya tikandînê li ser bişkoka {icon} çalak dibe.",
+ "notifications_permission_banner.title": "Tu tiştî bîr neke",
+ "picture_in_picture.restore": "Vegerîne paş",
+ "poll.closed": "Girtî",
+ "poll.refresh": "Nû bike",
+ "poll.total_people": "{count, plural, one {# kes} other {# kes}}",
+ "poll.total_votes": "{count, plural, one {# deng} other {# deng}}",
+ "poll.vote": "Deng bide",
+ "poll.voted": "Te dengê xwe da vê bersivê",
+ "poll.votes": "{votes, plural, one {# deng} other {# deng}}",
+ "poll_button.add_poll": "Rapirsîyek zêde bike",
+ "poll_button.remove_poll": "Rapirsî yê rake",
+ "privacy.change": "Nepênîtiya şandiyan biguherîne",
+ "privacy.direct.long": "Tenê ji bo bikarhênerên qalkirî tê dîtin",
+ "privacy.direct.short": "Taybet",
+ "privacy.private.long": "Tenê bo şopîneran xuyabar e",
+ "privacy.private.short": "Tenê şopîneran",
+ "privacy.public.long": "Ji bo herkesî li berçav e, di demnameyên gelemperî de dê xûyakirin",
+ "privacy.public.short": "Gelemperî",
+ "privacy.unlisted.long": "Ji herkesî ra tê xûya, lê demnameyê gelemperî ra nay xûyakirin",
+ "privacy.unlisted.short": "Nerêzok",
+ "refresh": "Nû bike",
+ "regeneration_indicator.label": "Tê barkirin…",
+ "regeneration_indicator.sublabel": "Mala te da tê amedekirin!",
+ "relative_time.days": "{number}r",
+ "relative_time.full.days": "{number, plural, one {# roj} other {# roj}} berê",
+ "relative_time.full.hours": "{number, plural, one {# demjimêr} other {# demjimêr}} berê",
+ "relative_time.full.just_now": "hema niha",
+ "relative_time.full.minutes": "{number, plural, one {# xulek} other {# xulek}} berê",
+ "relative_time.full.seconds": "{number, plural, one {# çirke} other {# çirke}} xulek",
+ "relative_time.hours": "{number}d",
+ "relative_time.just_now": "niha",
+ "relative_time.minutes": "{number}x",
+ "relative_time.seconds": "{number}ç",
+ "relative_time.today": "îro",
+ "reply_indicator.cancel": "Dev jê berde",
+ "report.categories.other": "Yên din",
+ "report.categories.spam": "Nexwestî (Spam)",
+ "report.categories.violation": "Naverok yek an çend rêbazên rajekar binpê dike",
+ "report.forward": "Biçe bo {target}",
+ "report.forward_hint": "Ajimêr ji rajekarek din da ne. Tu kopîyeka anonîm ya raporê bişînî li wur?",
+ "report.hint": "Ev rapor yê rajekarê lihevkarên te ra were şandin. Tu dikarî şiroveyekê pêşkêş bikî bê ka tu çima vê ajimêrê jor radigîhînî:",
+ "report.placeholder": "Şiroveyên zêde",
+ "report.submit": "Bişîne",
+ "report.target": "Ragihandin {target}",
+ "search.placeholder": "Bigere",
+ "search_popout.search_format": "Dirûva lêgerîna pêşketî",
+ "search_popout.tips.full_text": "Nivîsên hêsan, şandiyên ku te nivîsandiye, bijare kiriye, bilind kiriye an jî yên behsa te kirine û her wiha navê bikarhêneran, navên xûya dike û hashtagan vedigerîne.",
+ "search_popout.tips.hashtag": "hashtag",
+ "search_popout.tips.status": "şandî",
+ "search_popout.tips.text": "Nivîsên hêsan, navên xûya ên ku li hev hatî, bikarhêner û hashtagan vedigerîne",
+ "search_popout.tips.user": "bikarhêner",
+ "search_results.accounts": "Mirov",
+ "search_results.hashtags": "Hashtag",
+ "search_results.statuses": "Şandî",
+ "search_results.statuses_fts_disabled": "Di vê rajekara Mastodonê da lêgerîna şandîyên li gorî naveroka wan ne çalak e.",
+ "search_results.total": "{count, number} {count, plural, one {encam} other {encam}}",
+ "status.admin_account": "Ji bo @{name} navrûya venihêrtinê veke",
+ "status.admin_status": "Vê şandîyê di navrûya venihêrtinê de veke",
+ "status.block": "@{name} asteng bike",
+ "status.bookmark": "Şûnpel",
+ "status.cancel_reblog_private": "Bilind neke",
+ "status.cannot_reblog": "Ev şandî nayê bilindkirin",
+ "status.copy": "Girêdanê jê bigire bo weşankirinê",
+ "status.delete": "Jê bibe",
+ "status.detailed_status": "Dîtina axaftina berfireh",
+ "status.direct": "Peyama rasterast @{name}",
+ "status.edit": "Serrast bike",
+ "status.edited": "Di {date} de hate serrastkirin",
+ "status.edited_x_times": "{count, plural, one {{count} car} other {{count} car}} hate serrastkirin",
+ "status.embed": "Hedimandî",
+ "status.favourite": "Bijarte",
+ "status.filtered": "Parzûnkirî",
+ "status.history.created": "{name} {date} afirand",
+ "status.history.edited": "{name} {date} serrast kir",
+ "status.load_more": "Bêtir bar bike",
+ "status.media_hidden": "Medya veşartî ye",
+ "status.mention": "Qal @{name} bike",
+ "status.more": "Bêtir",
+ "status.mute": "@{name} Bêdeng bike",
+ "status.mute_conversation": "Axaftinê bêdeng bike",
+ "status.open": "Vê şandiyê berferh bike",
+ "status.pin": "Li ser profîlê derzî bike",
+ "status.pinned": "Şandiya derzîkirî",
+ "status.read_more": "Bêtir bixwîne",
+ "status.reblog": "Bilind bike",
+ "status.reblog_private": "Bi dîtina resen bilind bike",
+ "status.reblogged_by": "{name} bilind kir",
+ "status.reblogs.empty": "Kesekî hin ev şandî bilind nekiriye. Gava kesek bilind bike, ew ên li vir werin xuyakirin.",
+ "status.redraft": "Jê bibe & ji nû ve reşnivîs bike",
+ "status.remove_bookmark": "Şûnpêlê jê rake",
+ "status.reply": "Bersivê bide",
+ "status.replyAll": "Mijarê bibersivîne",
+ "status.report": "{name} gilî bike",
+ "status.sensitive_warning": "Naveroka hestiyarî",
+ "status.share": "Parve bike",
+ "status.show_less": "Kêmtir nîşan bide",
+ "status.show_less_all": "Ji bo hemîyan kêmtir nîşan bide",
+ "status.show_more": "Hêj zehftir nîşan bide",
+ "status.show_more_all": "Bêtir nîşan bide bo hemûyan",
+ "status.show_thread": "Mijarê nîşan bide",
+ "status.uncached_media_warning": "Tune ye",
+ "status.unmute_conversation": "Axaftinê bêdeng neke",
+ "status.unpin": "Şandiya derzîkirî ji profîlê rake",
+ "suggestions.dismiss": "Pêşniyarê paşguh bike",
+ "suggestions.header": "Dibe ku bala te bikşîne…",
+ "tabs_bar.federated_timeline": "Giştî",
+ "tabs_bar.home": "Serrûpel",
+ "tabs_bar.local_timeline": "Herêmî",
+ "tabs_bar.notifications": "Agahdarî",
+ "tabs_bar.search": "Bigere",
+ "time_remaining.days": "{number, plural, one {# roj} other {# roj}} maye",
+ "time_remaining.hours": "{number, plural, one {# demjimêr} other {# demjimêr}} maye",
+ "time_remaining.minutes": "{number, plural, one {# xulek} other {# xulek}} maye",
+ "time_remaining.moments": "Demên mayî",
+ "time_remaining.seconds": "{number, plural, one {# çirke} other {# çirke}} maye",
+ "timeline_hint.remote_resource_not_displayed": "{resource} Ji rajekerên din nayê dîtin.",
+ "timeline_hint.resources.followers": "Şopîner",
+ "timeline_hint.resources.follows": "Şopîner",
+ "timeline_hint.resources.statuses": "Şandiyên kevn",
+ "trends.counter_by_accounts": "{count, plural, one {{counter} kes} other {{counter} kes}} diaxivin",
+ "trends.trending_now": "Rojev",
+ "ui.beforeunload": "Ger ji Mastodonê veketi wê reşnivîsa te jî winda bibe.",
+ "units.short.billion": "{count}B",
+ "units.short.million": "{count}M",
+ "units.short.thousand": "{count}H",
+ "upload_area.title": "Ji bo barkirinê kaş bike û deyne",
+ "upload_button.label": "Wêne, vîdeoyek an jî pelê dengî tevlî bike",
+ "upload_error.limit": "Sînora barkirina pelan derbas bû.",
+ "upload_error.poll": "Di rapirsîyan de mafê barkirina pelan nayê dayîn.",
+ "upload_form.audio_description": "Ji bona kesên kêm dibihîsin re pênase bike",
+ "upload_form.description": "Ji bona astengdarên dîtinê re vebêje",
+ "upload_form.edit": "Serrast bike",
+ "upload_form.thumbnail": "Wêneyê biçûk biguherîne",
+ "upload_form.undo": "Jê bibe",
+ "upload_form.video_description": "Ji bo kesên kerr û lalan pênase bike",
+ "upload_modal.analyzing_picture": "Wêne tê analîzkirin…",
+ "upload_modal.apply": "Bisepîne",
+ "upload_modal.applying": "Tê sepandin…",
+ "upload_modal.choose_image": "Wêneyê hilbijêre",
+ "upload_modal.description_placeholder": "Rovîyek qehweyî û bilez li ser kûçikê tîral banz dide",
+ "upload_modal.detect_text": "Ji nivîsa wêneyê re serwext be",
+ "upload_modal.edit_media": "Medyayê sererast bike",
+ "upload_modal.hint": "Ji bo hilbijartina xala navendê her tim dîmenê piçûk de pêşdîtina çerxê bitikîne an jî kaş bike.",
+ "upload_modal.preparing_ocr": "OCR dihê amadekirin…",
+ "upload_modal.preview_label": "Pêşdîtin ({ratio})",
+ "upload_progress.label": "Tê barkirin...",
+ "video.close": "Vîdyoyê bigire",
+ "video.download": "Pelê daxe",
+ "video.exit_fullscreen": "Ji dîmendera tijî derkeve",
+ "video.expand": "Vîdyoyê berferh bike",
+ "video.fullscreen": "Dimendera tijî",
+ "video.hide": "Vîdyo veşêre",
+ "video.mute": "Dengê qut bike",
+ "video.pause": "Rawestîne",
+ "video.play": "Vêxe",
+ "video.unmute": "Dengê qut neke"
+}
diff --git a/app/javascript/mastodon/locales/ru.json b/app/javascript/mastodon/locales/ru.json
index 58811783026716..3d728a735e44ca 100644
--- a/app/javascript/mastodon/locales/ru.json
+++ b/app/javascript/mastodon/locales/ru.json
@@ -44,7 +44,7 @@
"account.unblock_short": "Разблокировать",
"account.unendorse": "Не рекомендовать в профиле",
"account.unfollow": "Отписаться",
- "account.unmute": "Не игнорировать @{name}",
+ "account.unmute": "Убрать {name} из игнорируемых",
"account.unmute_notifications": "Показывать уведомления от @{name}",
"account.unmute_short": "Не игнорировать",
"account_note.placeholder": "Текст заметки",
diff --git a/app/javascript/mastodon/locales/whitelist_kmr.json b/app/javascript/mastodon/locales/whitelist_kmr.json
new file mode 100644
index 00000000000000..0d4f101c7a37a4
--- /dev/null
+++ b/app/javascript/mastodon/locales/whitelist_kmr.json
@@ -0,0 +1,2 @@
+[
+]
diff --git a/app/javascript/mastodon/locales/whitelist_zh-YR.json b/app/javascript/mastodon/locales/whitelist_zh-YR.json
new file mode 100644
index 00000000000000..0d4f101c7a37a4
--- /dev/null
+++ b/app/javascript/mastodon/locales/whitelist_zh-YR.json
@@ -0,0 +1,2 @@
+[
+]
diff --git a/app/javascript/mastodon/locales/zh-YR.json b/app/javascript/mastodon/locales/zh-YR.json
new file mode 100644
index 00000000000000..f3890a4ac1c182
--- /dev/null
+++ b/app/javascript/mastodon/locales/zh-YR.json
@@ -0,0 +1,544 @@
+{
+ "account.account_note_header": "备注",
+ "account.add_or_remove_from_list": "从列表中添加或删除",
+ "account.badges.bot": "机器人",
+ "account.badges.group": "群组",
+ "account.block": "屏蔽 @{name}",
+ "account.block_domain": "屏蔽 {domain} 实例",
+ "account.blocked": "已屏蔽",
+ "account.browse_more_on_origin_server": "在原始个人资料页面上浏览详情",
+ "account.cancel_follow_request": "取消关注请求",
+ "account.direct": "发送私信给 @{name}",
+ "account.disable_notifications": "当 @{name} 发贴时不要通知我",
+ "account.domain_blocked": "域名已屏蔽",
+ "account.edit_profile": "学生证(修改个人资料)",
+ "account.enable_notifications": "当 @{name} 发贴时通知我",
+ "account.endorse": "在个人资料中推荐此用户",
+ "account.follow": "关注",
+ "account.followers": "关注者",
+ "account.followers.empty": "目前无人关注此用户。",
+ "account.followers_counter": "被 {counter} 人关注",
+ "account.following": "正在关注",
+ "account.following_counter": "正在关注 {counter} 人",
+ "account.follows.empty": "此用户目前尚未关注任何人。",
+ "account.follows_you": "关注了你",
+ "account.hide_reblogs": "隐藏来自 @{name} 的转贴",
+ "account.joined": "加入于 {date}",
+ "account.link_verified_on": "此链接的所有权已在 {date} 检查",
+ "account.locked_info": "此账户已锁贴。账户的主人会手动审核关注者。",
+ "account.media": "媒体",
+ "account.mention": "提及 @{name}",
+ "account.moved_to": "{name} 已经迁移到:",
+ "account.mute": "隐藏 @{name}",
+ "account.mute_notifications": "隐藏来自 @{name} 的通知",
+ "account.muted": "已隐藏",
+ "account.posts": "贴文",
+ "account.posts_with_replies": "贴文和回复",
+ "account.report": "举报 @{name}",
+ "account.requested": "正在等待对方同意。点击以取消发送关注请求",
+ "account.share": "分享 @{name} 的个人资料",
+ "account.show_reblogs": "显示来自 @{name} 的转贴",
+ "account.statuses_counter": "{counter} 条贴文",
+ "account.unblock": "解除屏蔽 @{name}",
+ "account.unblock_domain": "不再屏蔽 {domain} 实例",
+ "account.unblock_short": "移出黑名单",
+ "account.unendorse": "不在个人资料中推荐此用户",
+ "account.unfollow": "取消关注",
+ "account.unmute": "不再隐藏 @{name}",
+ "account.unmute_notifications": "不再隐藏来自 @{name} 的通知",
+ "account.unmute_short": "恢复消息提醒",
+ "account_note.placeholder": "点击添加备注",
+ "admin.dashboard.daily_retention": "注册后用户留存率(按日计算)",
+ "admin.dashboard.monthly_retention": "注册后用户留存率(按月计算)",
+ "admin.dashboard.retention.average": "平均",
+ "admin.dashboard.retention.cohort": "注册月",
+ "admin.dashboard.retention.cohort_size": "新用户",
+ "alert.rate_limited.message": "请在{retry_time, time, medium}后重试。",
+ "alert.rate_limited.title": "频率受限",
+ "alert.unexpected.message": "发生了意外错误。",
+ "alert.unexpected.title": "哎呀!",
+ "announcement.announcement": "公告",
+ "attachments_list.unprocessed": "(未处理)",
+ "autosuggest_hashtag.per_week": "每星期 {count} 条",
+ "boost_modal.combo": "下次按住 {combo} 即可跳过此提示",
+ "bundle_column_error.body": "载入这个组件时发生了错误。",
+ "bundle_column_error.retry": "重试",
+ "bundle_column_error.title": "网络错误",
+ "bundle_modal_error.close": "关闭",
+ "bundle_modal_error.message": "载入这个组件时发生了错误。",
+ "bundle_modal_error.retry": "重试",
+ "column.blocks": "职员室(已屏蔽的用户)",
+ "column.bookmarks": "图书馆(书签)",
+ "column.community": "中庭(本站时间轴)",
+ "column.direct": "悄悄话(私信)",
+ "column.directory": "走廊(浏览用户资料)",
+ "column.domain_blocks": "警察局(已屏蔽的网站)",
+ "column.favourites": "命运的红线(喜欢)",
+ "column.follow_requests": "鞋柜(关注请求)",
+ "column.home": "宿舍(主页)",
+ "column.lists": "社团中心(列表)",
+ "column.mutes": "厕所(已隐藏的用户)",
+ "column.notifications": "传达室(通知)",
+ "column.pins": "布告栏(置顶贴文)",
+ "column.public": "文化祭(跨站公共时间轴)",
+ "column_back_button.label": "返回",
+ "column_header.hide_settings": "隐藏设置",
+ "column_header.moveLeft_settings": "将此栏左移",
+ "column_header.moveRight_settings": "将此栏右移",
+ "column_header.pin": "置顶",
+ "column_header.show_settings": "显示设置",
+ "column_header.unpin": "取消置顶",
+ "column_subheading.settings": "设置",
+ "community.column_settings.local_only": "只显示本站",
+ "community.column_settings.media_only": "仅媒体",
+ "community.column_settings.remote_only": "只显示外站",
+ "compose_form.direct_message_warning": "这条贴文仅对所有被提及的用户可见。",
+ "compose_form.direct_message_warning_learn_more": "了解详情",
+ "compose_form.hashtag_warning": "这条贴文被设置为“不公开”,因此它不会出现在任何话题标签的列表下。只有公开的贴文才能通过话题标签进行搜索。",
+ "compose_form.lock_disclaimer": "你的帐户没有{locked}。任何人都可以在关注你后立即查看仅关注者可见的贴文。",
+ "compose_form.lock_disclaimer.lock": "开启保护",
+ "compose_form.placeholder": "我现在冷静到快要吓死了。",
+ "compose_form.poll.add_option": "添加选项",
+ "compose_form.poll.duration": "投票持续时间",
+ "compose_form.poll.option_placeholder": "选项 {number}",
+ "compose_form.poll.remove_option": "移除这个选项",
+ "compose_form.poll.switch_to_multiple": "将投票改为多选",
+ "compose_form.poll.switch_to_single": "将投票改为单选",
+ "compose_form.publish": "贴贴",
+ "compose_form.publish_loud": "{publish}!",
+ "compose_form.save_changes": "保存更改",
+ "compose_form.sensitive.hide": "标记媒体为敏感内容",
+ "compose_form.sensitive.marked": "媒体已被标记为敏感内容",
+ "compose_form.sensitive.unmarked": "媒体未被标记为敏感内容",
+ "compose_form.spoiler.marked": "正文已被折叠在警告信息之后",
+ "compose_form.spoiler.unmarked": "正文未被折叠",
+ "compose_form.spoiler_placeholder": "折叠部分的警告消息",
+ "confirmation_modal.cancel": "取消",
+ "confirmations.block.block_and_report": "屏蔽与举报",
+ "confirmations.block.confirm": "屏蔽",
+ "confirmations.block.message": "你确定要屏蔽 {name} 吗?",
+ "confirmations.delete.confirm": "删除",
+ "confirmations.delete.message": "你确定要删除这条贴文吗?",
+ "confirmations.delete_list.confirm": "删除",
+ "confirmations.delete_list.message": "你确定要永久删除这个列表吗?",
+ "confirmations.discard_edit_media.confirm": "取消编辑",
+ "confirmations.discard_edit_media.message": "你对于媒体文件的描述尚未保存,确定要取消吗?",
+ "confirmations.domain_block.confirm": "隐藏整个网站的内容",
+ "confirmations.domain_block.message": "你真的确定要屏蔽所有来自 {domain} 的内容吗?多数情况下,屏蔽或隐藏几个特定的用户就已经足够了。来自该网站的内容将不再出现在你的任何公共时间轴或通知列表里。来自该网站的关注者将会被移除。",
+ "confirmations.logout.confirm": "登出",
+ "confirmations.logout.message": "你确定要登出吗?",
+ "confirmations.mute.confirm": "隐藏",
+ "confirmations.mute.explanation": "这将隐藏他们的贴文以及提到他们的贴文,但他们仍可以看到你的贴文并关注你。",
+ "confirmations.mute.message": "你确定要隐藏 {name} 吗?",
+ "confirmations.redraft.confirm": "删除并重新编辑",
+ "confirmations.redraft.message": "你确定要删除这条贴文并重新编辑它吗?所有相关的转贴和喜欢都会被清除,回复将会失去关联。",
+ "confirmations.reply.confirm": "回复",
+ "confirmations.reply.message": "回复此消息将会覆盖当前正在编辑的信息。确定继续吗?",
+ "confirmations.unfollow.confirm": "取消关注",
+ "confirmations.unfollow.message": "你确定要取消关注 {name} 吗?",
+ "conversation.delete": "删除对话",
+ "conversation.mark_as_read": "标记为已读",
+ "conversation.open": "查看对话",
+ "conversation.with": "与 {names}",
+ "directory.federated": "来自联邦宇宙的已知部分",
+ "directory.local": "仅来自 {domain}",
+ "directory.new_arrivals": "新来者",
+ "directory.recently_active": "最近活跃",
+ "embed.instructions": "要在你的网站上嵌入此贴文,请复制以下代码。",
+ "embed.preview": "它会像这样显示出来:",
+ "emoji_button.activity": "活动",
+ "emoji_button.custom": "自定义",
+ "emoji_button.flags": "旗帜",
+ "emoji_button.food": "食物和饮料",
+ "emoji_button.label": "加入表情符号",
+ "emoji_button.nature": "自然",
+ "emoji_button.not_found": "木有这个表情符号!(╯°□°)╯︵ ┻━┻",
+ "emoji_button.objects": "物体",
+ "emoji_button.people": "人物",
+ "emoji_button.recent": "常用",
+ "emoji_button.search": "搜索…",
+ "emoji_button.search_results": "搜索结果",
+ "emoji_button.symbols": "符号",
+ "emoji_button.travel": "旅行和地点",
+ "empty_column.account_suspended": "账户已停用",
+ "empty_column.account_timeline": "这里没有贴文!",
+ "empty_column.account_unavailable": "个人资料不可用",
+ "empty_column.blocks": "你目前没有屏蔽任何用户。",
+ "empty_column.bookmarked_statuses": "你还没有给任何贴文添加过书签。在你添加书签后,贴文就会显示在这里。",
+ "empty_column.community": "本站时间轴暂时没有内容,快写点什么让它动起来吧!",
+ "empty_column.direct": "你还没有使用过私信。当你发出或者收到私信时,它会在这里显示。",
+ "empty_column.domain_blocks": "目前没有被隐藏的站点。",
+ "empty_column.explore_statuses": "目前没有热门话题,稍后再来看看吧!",
+ "empty_column.favourited_statuses": "你还没有喜欢过任何贴文。喜欢过的贴文会显示在这里。",
+ "empty_column.favourites": "没有人喜欢过这条贴文。如果有人喜欢了,就会显示在这里。",
+ "empty_column.follow_recommendations": "似乎无法为你生成任何建议。你可以尝试使用搜索寻找你可能知道的人或探索热门标签。",
+ "empty_column.follow_requests": "你没有收到新的关注请求。收到了之后就会显示在这里。",
+ "empty_column.hashtag": "这个话题标签下暂时没有内容。",
+ "empty_column.home": "你的主页时间线是空的!快去关注更多人吧。 {suggestions}",
+ "empty_column.home.suggestions": "查看一些建议",
+ "empty_column.list": "此列表中暂时没有内容。列表中用户所发送的的新贴文将会在这里显示。",
+ "empty_column.lists": "你还没有创建过列表。你创建的列表会在这里显示。",
+ "empty_column.mutes": "你没有隐藏任何用户。",
+ "empty_column.notifications": "你还没有收到过任何通知,快和其他用户互动吧。",
+ "empty_column.public": "这里什么都没有!写一些公开的贴文,或者关注其他服务器的用户后,这里就会有贴文出现了",
+ "error.unexpected_crash.explanation": "此页面无法正确显示,这可能是因为我们的代码中有错误,也可能是因为浏览器兼容问题。",
+ "error.unexpected_crash.explanation_addons": "此页面无法正确显示,这个错误很可能是由浏览器附加组件或自动翻译工具造成的。",
+ "error.unexpected_crash.next_steps": "刷新一下页面试试。如果没用,你可以换个浏览器或者用本地应用。",
+ "error.unexpected_crash.next_steps_addons": "请尝试禁用它们并刷新页面。如果没有帮助,你仍可以尝试使用其他浏览器或原生应用来使用 Mastodon。",
+ "errors.unexpected_crash.copy_stacktrace": "把堆栈跟踪信息复制到剪贴板",
+ "errors.unexpected_crash.report_issue": "报告问题",
+ "explore.search_results": "搜索结果",
+ "explore.suggested_follows": "为您推荐",
+ "explore.title": "走廊(探索)",
+ "explore.trending_links": "最新消息",
+ "explore.trending_statuses": "贴文",
+ "explore.trending_tags": "话题标签",
+ "follow_recommendations.done": "完成",
+ "follow_recommendations.heading": "关注你感兴趣的用户!这里有一些推荐。",
+ "follow_recommendations.lead": "你关注的人的贴文将按时间顺序在你的主页上显示。 别担心,你可以随时取消关注!",
+ "follow_request.authorize": "同意",
+ "follow_request.reject": "拒绝",
+ "follow_requests.unlocked_explanation": "虽说你没有锁贴,但是 {domain} 的工作人员觉得你可能想手工审核关注请求。",
+ "generic.saved": "已保存",
+ "getting_started.developers": "开发",
+ "getting_started.directory": "花名册(用户目录)",
+ "getting_started.documentation": "文档",
+ "getting_started.heading": "莉莉魔法女子学院(开始使用)",
+ "getting_started.invite": "邀请用户",
+ "getting_started.open_source_notice": "Mastodon 是开源软件。欢迎前往 GitHub({github})贡献代码或反馈问题。",
+ "getting_started.security": "帐户安全",
+ "getting_started.terms": "使用条款",
+ "hashtag.column_header.tag_mode.all": "以及 {additional}",
+ "hashtag.column_header.tag_mode.any": "或是 {additional}",
+ "hashtag.column_header.tag_mode.none": "而不用 {additional}",
+ "hashtag.column_settings.select.no_options_message": "没有找到建议",
+ "hashtag.column_settings.select.placeholder": "输入话题标签…",
+ "hashtag.column_settings.tag_mode.all": "全部",
+ "hashtag.column_settings.tag_mode.any": "任一",
+ "hashtag.column_settings.tag_mode.none": "全都不要",
+ "hashtag.column_settings.tag_toggle": "在此栏加入额外的标签",
+ "home.column_settings.basic": "基本设置",
+ "home.column_settings.show_reblogs": "显示转贴",
+ "home.column_settings.show_replies": "显示回复",
+ "home.hide_announcements": "隐藏公告",
+ "home.show_announcements": "显示公告",
+ "intervals.full.days": "{number} 天",
+ "intervals.full.hours": "{number} 小时",
+ "intervals.full.minutes": "{number} 分钟",
+ "keyboard_shortcuts.back": "返回上一页",
+ "keyboard_shortcuts.blocked": "打开被屏蔽用户列表",
+ "keyboard_shortcuts.boost": "转贴",
+ "keyboard_shortcuts.column": "选择某一栏中的贴文",
+ "keyboard_shortcuts.compose": "选择贴文撰写框",
+ "keyboard_shortcuts.description": "说明",
+ "keyboard_shortcuts.direct": "打开私信栏",
+ "keyboard_shortcuts.down": "在列表中让光标下移",
+ "keyboard_shortcuts.enter": "展开贴文",
+ "keyboard_shortcuts.favourite": "喜欢贴文",
+ "keyboard_shortcuts.favourites": "打开喜欢的贴文列表",
+ "keyboard_shortcuts.federated": "打开跨站时间轴",
+ "keyboard_shortcuts.heading": "快捷键列表",
+ "keyboard_shortcuts.home": "打开主页时间轴",
+ "keyboard_shortcuts.hotkey": "快捷键",
+ "keyboard_shortcuts.legend": "显示此列表",
+ "keyboard_shortcuts.local": "打开本站时间轴",
+ "keyboard_shortcuts.mention": "提及贴文作者",
+ "keyboard_shortcuts.muted": "打开已隐藏用户列表",
+ "keyboard_shortcuts.my_profile": "打开你的个人资料",
+ "keyboard_shortcuts.notifications": "打开通知栏",
+ "keyboard_shortcuts.open_media": "打开媒体",
+ "keyboard_shortcuts.pinned": "打开置顶贴文列表",
+ "keyboard_shortcuts.profile": "打开作者的个人资料",
+ "keyboard_shortcuts.reply": "回复贴文",
+ "keyboard_shortcuts.requests": "打开关注请求列表",
+ "keyboard_shortcuts.search": "选择搜索框",
+ "keyboard_shortcuts.spoilers": "显示或隐藏被折叠的正文",
+ "keyboard_shortcuts.start": "打开“开始使用”栏",
+ "keyboard_shortcuts.toggle_hidden": "显示或隐藏被折叠的正文",
+ "keyboard_shortcuts.toggle_sensitivity": "显示/隐藏媒体",
+ "keyboard_shortcuts.toot": "发送新贴文",
+ "keyboard_shortcuts.unfocus": "取消输入",
+ "keyboard_shortcuts.up": "在列表中让光标上移",
+ "lightbox.close": "关闭",
+ "lightbox.compress": "返回图片全览",
+ "lightbox.expand": "放大查看图片",
+ "lightbox.next": "下一个",
+ "lightbox.previous": "上一个",
+ "lists.account.add": "添加到列表",
+ "lists.account.remove": "从列表中移除",
+ "lists.delete": "删除列表",
+ "lists.edit": "编辑列表",
+ "lists.edit.submit": "更改标题",
+ "lists.new.create": "新建列表",
+ "lists.new.title_placeholder": "新列表的标题",
+ "lists.replies_policy.followed": "任何被关注的用户",
+ "lists.replies_policy.list": "列表成员",
+ "lists.replies_policy.none": "没有人",
+ "lists.replies_policy.title": "显示回复给:",
+ "lists.search": "搜索你关注的人",
+ "lists.subheading": "你的列表",
+ "load_pending": "{count} 项",
+ "loading_indicator.label": "加载中……",
+ "media_gallery.toggle_visible": "隐藏 {number} 张图片",
+ "missing_indicator.label": "找不到内容",
+ "missing_indicator.sublabel": "无法找到此资源",
+ "mute_modal.duration": "持续期间",
+ "mute_modal.hide_notifications": "同时隐藏来自这个用户的通知?",
+ "mute_modal.indefinite": "无期限",
+ "navigation_bar.apps": "移动应用",
+ "navigation_bar.blocks": "职员室(已屏蔽的用户)",
+ "navigation_bar.bookmarks": "图书馆(书签)",
+ "navigation_bar.community_timeline": "中庭(本站时间轴)",
+ "navigation_bar.compose": "撰写新贴文",
+ "navigation_bar.direct": "悄悄话(私信)",
+ "navigation_bar.discover": "走廊(发现)",
+ "navigation_bar.domain_blocks": "警察局(已屏蔽的网站)",
+ "navigation_bar.edit_profile": "学生证(修改个人资料)",
+ "navigation_bar.explore": "走廊(探索)",
+ "navigation_bar.favourites": "命运的红线(喜欢)",
+ "navigation_bar.filters": "职员室(隐藏关键词)",
+ "navigation_bar.follow_requests": "鞋柜(关注请求)",
+ "navigation_bar.follows_and_followers": "小团体(关注管理)",
+ "navigation_bar.info": "关于本站",
+ "navigation_bar.keyboard_shortcuts": "快捷键列表",
+ "navigation_bar.lists": "社团中心(列表)",
+ "navigation_bar.logout": "放学(登出)",
+ "navigation_bar.mutes": "厕所(已隐藏的用户)",
+ "navigation_bar.personal": "个人",
+ "navigation_bar.pins": "布告栏(置顶贴文)",
+ "navigation_bar.preferences": "理科室(首选项)",
+ "navigation_bar.public_timeline": "文化祭(跨站公共时间轴)",
+ "navigation_bar.security": "安全",
+ "notification.admin.sign_up": "{name} 注册了",
+ "notification.favourite": "{name} 喜欢了你的贴文",
+ "notification.follow": "{name} 开始关注你",
+ "notification.follow_request": "{name} 向你发送了关注请求",
+ "notification.mention": "{name} 提及了你",
+ "notification.own_poll": "你的投票已经结束",
+ "notification.poll": "你参与的一个投票已经结束",
+ "notification.reblog": "{name} 转贴了你的贴文",
+ "notification.status": "{name} 刚刚发贴",
+ "notification.update": "{name} 编辑了贴文",
+ "notifications.clear": "清空通知列表",
+ "notifications.clear_confirmation": "你确定要永久清空通知列表吗?",
+ "notifications.column_settings.admin.sign_up": "新注册:",
+ "notifications.column_settings.alert": "桌面通知",
+ "notifications.column_settings.favourite": "当你的贴文被喜欢时:",
+ "notifications.column_settings.filter_bar.advanced": "显示所有类别",
+ "notifications.column_settings.filter_bar.category": "快速过滤栏",
+ "notifications.column_settings.filter_bar.show": "显示",
+ "notifications.column_settings.follow": "当有人关注你时:",
+ "notifications.column_settings.follow_request": "新的关注请求:",
+ "notifications.column_settings.mention": "当有人在贴文中提及你时:",
+ "notifications.column_settings.poll": "投票结果:",
+ "notifications.column_settings.push": "推送通知",
+ "notifications.column_settings.reblog": "当有人转贴了你的贴文时:",
+ "notifications.column_settings.show": "在通知栏显示",
+ "notifications.column_settings.sound": "播放音效",
+ "notifications.column_settings.status": "新贴文:",
+ "notifications.column_settings.unread_markers.category": "未读通知标记",
+ "notifications.column_settings.unread_notifications.highlight": "高亮显示未读通知",
+ "notifications.column_settings.update": "编辑:",
+ "notifications.filter.all": "全部",
+ "notifications.filter.boosts": "转贴",
+ "notifications.filter.favourites": "喜欢",
+ "notifications.filter.follows": "关注",
+ "notifications.filter.mentions": "提及",
+ "notifications.filter.polls": "投票结果",
+ "notifications.filter.statuses": "你关注的人的动态",
+ "notifications.grant_permission": "授予权限",
+ "notifications.group": "{count} 条通知",
+ "notifications.mark_as_read": "将所有通知标为已读",
+ "notifications.permission_denied": "由于权限被拒绝,无法启用桌面通知。",
+ "notifications.permission_denied_alert": "由于在此之前浏览器权限请求就已被拒绝,所以启用桌面通知失败",
+ "notifications.permission_required": "所需权限未被授予,所以桌面通知不可用",
+ "notifications_permission_banner.enable": "启用桌面通知",
+ "notifications_permission_banner.how_to_control": "启用桌面通知以在 Mastodon 未打开时接收通知。你可以通过交互通过上面的 {icon} 按钮来精细控制可以发送桌面通知的交互类型。",
+ "notifications_permission_banner.title": "精彩不容错过",
+ "picture_in_picture.restore": "恢复",
+ "poll.closed": "已关闭",
+ "poll.refresh": "刷新",
+ "poll.total_people": "{count}人",
+ "poll.total_votes": "{count} 票",
+ "poll.vote": "投票",
+ "poll.voted": "你已经对这个答案投过票了",
+ "poll.votes": "{votes, plural, one {# vote} other {# votes}}",
+ "poll_button.add_poll": "发起投票",
+ "poll_button.remove_poll": "移除投票",
+ "privacy.change": "设置贴文的可见范围",
+ "privacy.direct.long": "只有被提及的用户能看到",
+ "privacy.direct.short": "私信",
+ "privacy.private.long": "只有关注你的用户能看到",
+ "privacy.private.short": "仅关注者",
+ "privacy.public.long": "所有人可见,并会出现在公共时间轴上",
+ "privacy.public.short": "公开",
+ "privacy.unlisted.long": "所有人可见,但不会出现在公共时间轴上",
+ "privacy.unlisted.short": "不公开",
+ "refresh": "刷新",
+ "regeneration_indicator.label": "加载中……",
+ "regeneration_indicator.sublabel": "你的主页动态正在准备中!",
+ "relative_time.days": "{number}天",
+ "relative_time.full.days": "{number, plural, one {# 天} other {# 天}}前",
+ "relative_time.full.hours": "{number, plural, one {# 小时} other {# 小时}}前",
+ "relative_time.full.just_now": "刚刚",
+ "relative_time.full.minutes": "{number, plural, one {# 分钟} other {# 分钟}}前",
+ "relative_time.full.seconds": "{number, plural, one {# 秒} other {# 秒}}前",
+ "relative_time.hours": "{number}时",
+ "relative_time.just_now": "刚刚",
+ "relative_time.minutes": "{number}分",
+ "relative_time.seconds": "{number}秒",
+ "relative_time.today": "今天",
+ "reply_indicator.cancel": "取消",
+ "report.block": "屏蔽",
+ "report.block_explanation": "你不会看到他们的帖子。他们也将无法看到你的帖子或关注你。他们将能够判断他们被屏蔽了。",
+ "report.categories.other": "其他",
+ "report.categories.spam": "垃圾信息",
+ "report.categories.violation": "内容违反一条或多条服务器规则",
+ "report.category.subtitle": "选择最佳匹配",
+ "report.category.title": "告诉我们这个 {type} 的情况",
+ "report.category.title_account": "个人资料",
+ "report.category.title_status": "嘟文",
+ "report.close": "完成",
+ "report.comment.title": "还有什么你认为我们应该知道的吗?",
+ "report.forward": "转发举报至 {target}",
+ "report.forward_hint": "这名用户来自另一个服务器。是否要向那个服务器发送一条匿名的举报?",
+ "report.mute": "静音",
+ "report.mute_explanation": "你将不会看到他们的嘟文。他们仍然可以关注你并看到你的嘟文,但他们不会知道他们被静音了。",
+ "report.next": "下一步",
+ "report.placeholder": "备注",
+ "report.reasons.dislike": "我不喜欢它",
+ "report.reasons.dislike_description": "这不是你想看到的东西",
+ "report.reasons.other": "其他原因",
+ "report.reasons.other_description": "该问题不符合其他类别",
+ "report.reasons.spam": "它是垃圾信息",
+ "report.reasons.spam_description": "恶意链接,虚假互动和重复回复",
+ "report.reasons.violation": "它违反了服务器规则",
+ "report.reasons.violation_description": "你清楚它违反了特定的规则",
+ "report.rules.subtitle": "选择所有适用选项",
+ "report.rules.title": "哪些规则被违反了?",
+ "report.statuses.subtitle": "选择所有适用选项",
+ "report.statuses.title": "是否有任何嘟文可以支持这一报告?",
+ "report.submit": "提交",
+ "report.target": "举报 {target}",
+ "report.thanks.take_action": "以下是您控制您在 Mastodon 上能看到哪些内容的选项:",
+ "report.thanks.take_action_actionable": "在我们审阅这个问题时,你可以对 @{name} 采取行动",
+ "report.thanks.title": "不想看到这个内容?",
+ "report.thanks.title_actionable": "感谢提交举报,我们将会进行处理。",
+ "report.unfollow": "取消关注 @{name}",
+ "report.unfollow_explanation": "你正在关注这个账户。如果要想在你的主页上不再看到他们的嘟文,请取消对他们的关注。",
+ "search.placeholder": "搜索",
+ "search_popout.search_format": "高级搜索格式",
+ "search_popout.tips.full_text": "输入关键词检索所有你发送、喜欢、转贴过或提及到你的贴文,以及其他用户公开的用户名、昵称和话题标签。",
+ "search_popout.tips.hashtag": "话题标签",
+ "search_popout.tips.status": "贴文",
+ "search_popout.tips.text": "输入关键词检索昵称、用户名和话题标签",
+ "search_popout.tips.user": "用户",
+ "search_results.accounts": "用户",
+ "search_results.all": "全部",
+ "search_results.hashtags": "话题标签",
+ "search_results.nothing_found": "无法找到符合这些搜索词的任何内容",
+ "search_results.statuses": "贴文",
+ "search_results.statuses_fts_disabled": "此Mastodon服务器未启用贴文内容搜索。",
+ "search_results.total": "共 {count, number} 个结果",
+ "status.admin_account": "打开 @{name} 的管理界面",
+ "status.admin_status": "打开这条贴文的管理界面",
+ "status.block": "屏蔽 @{name}",
+ "status.bookmark": "添加到书签",
+ "status.cancel_reblog_private": "取消转贴",
+ "status.cannot_reblog": "这条贴文不允许被转贴",
+ "status.copy": "复制贴文链接",
+ "status.delete": "删除",
+ "status.detailed_status": "对话详情",
+ "status.direct": "发送私信给 @{name}",
+ "status.edit": "编辑",
+ "status.edited": "编辑于 {date}",
+ "status.edited_x_times": "共编辑 {count, plural, one {{count} 次} other {{count} 次}}",
+ "status.embed": "嵌入",
+ "status.favourite": "喜欢",
+ "status.filtered": "已过滤",
+ "status.history.created": "{name} 创建于 {date}",
+ "status.history.edited": "{name} 编辑于 {date}",
+ "status.load_more": "加载更多",
+ "status.media_hidden": "已隐藏的媒体内容",
+ "status.mention": "提及 @{name}",
+ "status.more": "更多",
+ "status.mute": "隐藏 @{name}",
+ "status.mute_conversation": "将此对话静音",
+ "status.open": "展开贴文",
+ "status.pin": "在个人资料页面置顶",
+ "status.pinned": "置顶贴文",
+ "status.read_more": "阅读全文",
+ "status.reblog": "转贴",
+ "status.reblog_private": "转贴(可见者不变)",
+ "status.reblogged_by": "{name} 转贴了",
+ "status.reblogs.empty": "没有人转贴过此条贴文。如果有人转贴了,就会显示在这里。",
+ "status.redraft": "删除并重新编辑",
+ "status.remove_bookmark": "移除书签",
+ "status.reply": "回复",
+ "status.replyAll": "回复所有人",
+ "status.report": "举报 @{name}",
+ "status.sensitive_warning": "敏感内容",
+ "status.share": "分享",
+ "status.show_less": "隐藏内容",
+ "status.show_less_all": "隐藏所有内容",
+ "status.show_more": "显示内容",
+ "status.show_more_all": "显示所有内容",
+ "status.show_thread": "显示全部对话",
+ "status.uncached_media_warning": "暂不可用",
+ "status.unmute_conversation": "将此对话解除静音",
+ "status.unpin": "在个人资料页面取消置顶",
+ "suggestions.dismiss": "关闭建议",
+ "suggestions.header": "你可能会感兴趣…",
+ "tabs_bar.federated_timeline": "文化祭(跨站)",
+ "tabs_bar.home": "宿舍(主页)",
+ "tabs_bar.local_timeline": "中庭(本站)",
+ "tabs_bar.notifications": "传达室(通知)",
+ "tabs_bar.search": "搜索",
+ "time_remaining.days": "剩余 {number, plural, one {# 天} other {# 天}}",
+ "time_remaining.hours": "剩余 {number, plural, one {# 小时} other {# 小时}}",
+ "time_remaining.minutes": "剩余 {number, plural, one {# 分钟} other {# 分钟}}",
+ "time_remaining.moments": "即将结束",
+ "time_remaining.seconds": "剩余 {number, plural, one {# 秒} other {# 秒}}",
+ "timeline_hint.remote_resource_not_displayed": "不会显示来自其它服务器的 {resource}",
+ "timeline_hint.resources.followers": "关注者",
+ "timeline_hint.resources.follows": "关注",
+ "timeline_hint.resources.statuses": "更早的贴文",
+ "trends.counter_by_accounts": "{count, plural, one {{counter} 人} other {{counter} 人}}正在讨论",
+ "trends.trending_now": "现在流行",
+ "ui.beforeunload": "如果你现在离开 Mastodon,你的草稿内容将会丢失。",
+ "units.short.billion": "{count}B",
+ "units.short.million": "{count}M",
+ "units.short.thousand": "{count}K",
+ "upload_area.title": "将文件拖放到此处开始上传",
+ "upload_button.label": "上传图片、视频或音频",
+ "upload_error.limit": "文件大小超过限制。",
+ "upload_error.poll": "投票中不允许上传文件。",
+ "upload_form.audio_description": "为听障人士添加文字描述",
+ "upload_form.description": "为视觉障碍人士添加文字说明",
+ "upload_form.description_missing": "No description added",
+ "upload_form.edit": "编辑",
+ "upload_form.thumbnail": "更改缩略图",
+ "upload_form.undo": "删除",
+ "upload_form.video_description": "为听障人士和视障人士添加文字描述",
+ "upload_modal.analyzing_picture": "分析图片…",
+ "upload_modal.apply": "应用",
+ "upload_modal.applying": "正在应用…",
+ "upload_modal.choose_image": "选择图像",
+ "upload_modal.description_placeholder": "天地玄黄 宇宙洪荒 日月盈仄 辰宿列张",
+ "upload_modal.detect_text": "从图片中检测文本",
+ "upload_modal.edit_media": "编辑媒体",
+ "upload_modal.hint": "在预览图上点击或拖动圆圈,以选择缩略图的焦点。",
+ "upload_modal.preparing_ocr": "正在准备文字识别……",
+ "upload_modal.preview_label": "预览 ({ratio})",
+ "upload_progress.label": "上传中……",
+ "video.close": "关闭视频",
+ "video.download": "下载文件",
+ "video.exit_fullscreen": "退出全屏",
+ "video.expand": "展开视频",
+ "video.fullscreen": "全屏",
+ "video.hide": "隐藏视频",
+ "video.mute": "静音",
+ "video.pause": "暂停",
+ "video.play": "播放",
+ "video.unmute": "取消静音"
+}
diff --git a/app/javascript/styles/.PREVIEWS/README.MD b/app/javascript/styles/.PREVIEWS/README.MD
new file mode 100644
index 00000000000000..8b6cfa314e1399
--- /dev/null
+++ b/app/javascript/styles/.PREVIEWS/README.MD
@@ -0,0 +1,32 @@
+Table of contents
+---
+
+- [Themes](#themes)
+ - [Mastodon Flat](#mastodon-flat)
+ - [Midnight Blue](#midnight-blue)
+ - [Clean Slate](#clean-slate)
+ - [Droid](#droid)
+ - [Flamingo](#flamingo)
+ - [linernotes.club](#linernotesclub)
+ - [Linernotes Dark](#linernotes-dark)
+- [Tweaks](#tweaks)
+
+# Themes
+
+## Mastodon Flat
+
+### Midnight Blue
+![midnightblue](https://github.com/trwnh/mastomods/blob/master/.PREVIEWS/mfc-midnightBlue.png)
+### Clean Slate
+![cleanslate](https://github.com/trwnh/mastomods/blob/master/.PREVIEWS/mfc-cleanSlate.png)
+### Droid
+![droid](https://github.com/trwnh/mastomods/blob/master/.PREVIEWS/mfc-droid.png)
+### Flamingo
+![flamingo](https://github.com/trwnh/mastomods/blob/master/.PREVIEWS/mfc-flamingo.png)
+
+## linernotes.club
+
+### Linernotes Dark
+![linernotes_dark](https://github.com/trwnh/mastomods/blob/master/.PREVIEWS/linernotes_dark.png)
+
+# Tweaks
diff --git a/app/javascript/styles/.PREVIEWS/mods/1200px.png b/app/javascript/styles/.PREVIEWS/mods/1200px.png
new file mode 100644
index 00000000000000..c116d1fb39bfc9
Binary files /dev/null and b/app/javascript/styles/.PREVIEWS/mods/1200px.png differ
diff --git a/app/javascript/styles/.PREVIEWS/mods/1600px.png b/app/javascript/styles/.PREVIEWS/mods/1600px.png
new file mode 100644
index 00000000000000..6ff4029361776a
Binary files /dev/null and b/app/javascript/styles/.PREVIEWS/mods/1600px.png differ
diff --git a/app/javascript/styles/.PREVIEWS/mods/avatar_after.png b/app/javascript/styles/.PREVIEWS/mods/avatar_after.png
new file mode 100644
index 00000000000000..d2f8eb019c2625
Binary files /dev/null and b/app/javascript/styles/.PREVIEWS/mods/avatar_after.png differ
diff --git a/app/javascript/styles/.PREVIEWS/mods/avatar_before.png b/app/javascript/styles/.PREVIEWS/mods/avatar_before.png
new file mode 100644
index 00000000000000..c61b9d352b26a1
Binary files /dev/null and b/app/javascript/styles/.PREVIEWS/mods/avatar_before.png differ
diff --git a/app/javascript/styles/.PREVIEWS/mods/displayname-full_after.png b/app/javascript/styles/.PREVIEWS/mods/displayname-full_after.png
new file mode 100644
index 00000000000000..e250e07c4a2b7b
Binary files /dev/null and b/app/javascript/styles/.PREVIEWS/mods/displayname-full_after.png differ
diff --git a/app/javascript/styles/.PREVIEWS/mods/displayname-full_before.png b/app/javascript/styles/.PREVIEWS/mods/displayname-full_before.png
new file mode 100644
index 00000000000000..ac37589ef0d3ad
Binary files /dev/null and b/app/javascript/styles/.PREVIEWS/mods/displayname-full_before.png differ
diff --git a/app/javascript/styles/.PREVIEWS/mods/displayname-linebreak_after.png b/app/javascript/styles/.PREVIEWS/mods/displayname-linebreak_after.png
new file mode 100644
index 00000000000000..e6fa692de20938
Binary files /dev/null and b/app/javascript/styles/.PREVIEWS/mods/displayname-linebreak_after.png differ
diff --git a/app/javascript/styles/.PREVIEWS/mods/displayname-linebreak_before.png b/app/javascript/styles/.PREVIEWS/mods/displayname-linebreak_before.png
new file mode 100644
index 00000000000000..f36ac8bd805c91
Binary files /dev/null and b/app/javascript/styles/.PREVIEWS/mods/displayname-linebreak_before.png differ
diff --git a/app/javascript/styles/.PREVIEWS/mods/gettingstarted_after.png b/app/javascript/styles/.PREVIEWS/mods/gettingstarted_after.png
new file mode 100644
index 00000000000000..d3be8ceaa0f8a9
Binary files /dev/null and b/app/javascript/styles/.PREVIEWS/mods/gettingstarted_after.png differ
diff --git a/app/javascript/styles/.PREVIEWS/mods/gettingstarted_before.png b/app/javascript/styles/.PREVIEWS/mods/gettingstarted_before.png
new file mode 100644
index 00000000000000..24cb697aa6fdd8
Binary files /dev/null and b/app/javascript/styles/.PREVIEWS/mods/gettingstarted_before.png differ
diff --git a/app/javascript/styles/.PREVIEWS/mods/mobile_after.png b/app/javascript/styles/.PREVIEWS/mods/mobile_after.png
new file mode 100644
index 00000000000000..f49a51b40646ce
Binary files /dev/null and b/app/javascript/styles/.PREVIEWS/mods/mobile_after.png differ
diff --git a/app/javascript/styles/.PREVIEWS/mods/mobile_before.png b/app/javascript/styles/.PREVIEWS/mods/mobile_before.png
new file mode 100644
index 00000000000000..e9d7fa32621875
Binary files /dev/null and b/app/javascript/styles/.PREVIEWS/mods/mobile_before.png differ
diff --git a/app/javascript/styles/.PREVIEWS/mods/notifications_before.png b/app/javascript/styles/.PREVIEWS/mods/notifications_before.png
new file mode 100644
index 00000000000000..7b9f5c47fee5c2
Binary files /dev/null and b/app/javascript/styles/.PREVIEWS/mods/notifications_before.png differ
diff --git a/app/javascript/styles/.PREVIEWS/mods/notifications_collapsed.png b/app/javascript/styles/.PREVIEWS/mods/notifications_collapsed.png
new file mode 100644
index 00000000000000..c5cd832b0beb7f
Binary files /dev/null and b/app/javascript/styles/.PREVIEWS/mods/notifications_collapsed.png differ
diff --git a/app/javascript/styles/.PREVIEWS/mods/notifications_fade.png b/app/javascript/styles/.PREVIEWS/mods/notifications_fade.png
new file mode 100644
index 00000000000000..8f9d599af3ee99
Binary files /dev/null and b/app/javascript/styles/.PREVIEWS/mods/notifications_fade.png differ
diff --git a/app/javascript/styles/.PREVIEWS/mods/search_after.png b/app/javascript/styles/.PREVIEWS/mods/search_after.png
new file mode 100644
index 00000000000000..32c8874a4a1af2
Binary files /dev/null and b/app/javascript/styles/.PREVIEWS/mods/search_after.png differ
diff --git a/app/javascript/styles/.PREVIEWS/mods/search_before.png b/app/javascript/styles/.PREVIEWS/mods/search_before.png
new file mode 100644
index 00000000000000..0e56a0cc37545e
Binary files /dev/null and b/app/javascript/styles/.PREVIEWS/mods/search_before.png differ
diff --git a/app/javascript/styles/.PREVIEWS/themes/linernotes_dark.png b/app/javascript/styles/.PREVIEWS/themes/linernotes_dark.png
new file mode 100644
index 00000000000000..3ab3702ba7029b
Binary files /dev/null and b/app/javascript/styles/.PREVIEWS/themes/linernotes_dark.png differ
diff --git a/app/javascript/styles/.PREVIEWS/themes/mfc-cleanSlate.png b/app/javascript/styles/.PREVIEWS/themes/mfc-cleanSlate.png
new file mode 100644
index 00000000000000..7fc564dcd684c6
Binary files /dev/null and b/app/javascript/styles/.PREVIEWS/themes/mfc-cleanSlate.png differ
diff --git a/app/javascript/styles/.PREVIEWS/themes/mfc-droid.png b/app/javascript/styles/.PREVIEWS/themes/mfc-droid.png
new file mode 100644
index 00000000000000..8f3f065bcb5542
Binary files /dev/null and b/app/javascript/styles/.PREVIEWS/themes/mfc-droid.png differ
diff --git a/app/javascript/styles/.PREVIEWS/themes/mfc-flamingo.png b/app/javascript/styles/.PREVIEWS/themes/mfc-flamingo.png
new file mode 100644
index 00000000000000..58154e82c2efd8
Binary files /dev/null and b/app/javascript/styles/.PREVIEWS/themes/mfc-flamingo.png differ
diff --git a/app/javascript/styles/.PREVIEWS/themes/mfc-midnightBlue.png b/app/javascript/styles/.PREVIEWS/themes/mfc-midnightBlue.png
new file mode 100644
index 00000000000000..446965217510d5
Binary files /dev/null and b/app/javascript/styles/.PREVIEWS/themes/mfc-midnightBlue.png differ
diff --git a/app/javascript/styles/fault.scss b/app/javascript/styles/fault.scss
new file mode 100644
index 00000000000000..594aaec1374736
--- /dev/null
+++ b/app/javascript/styles/fault.scss
@@ -0,0 +1,19 @@
+@import "application";
+
+@import "mfc/mastodonFlat";
+@import "fault/palette";
+@import "fault/overrides";
+
+@import "mods/fixes";
+
+@import "mods/display_breakname";
+@import "mods/display_fullname";
+@import "mods/display_browserfont";
+@import "mods/display_circleavatar";
+@import "mods/display_collapsedinteractions";
+@import "mods/display_fadedinteractions";
+@import "mods/display_transparentmedia";
+@import "mods/layout_1600px";
+@import "mods/layout_elefriend";
+@import "mods/layout_widercolumns";
+@import "mods/layout_mobile_bottombar";
diff --git a/app/javascript/styles/fault/overrides.scss b/app/javascript/styles/fault/overrides.scss
new file mode 100644
index 00000000000000..edc35620eae30b
--- /dev/null
+++ b/app/javascript/styles/fault/overrides.scss
@@ -0,0 +1,93 @@
+/*---------
+MISC TWEAKS
+---------*/
+
+/* replace elephant friend with vinyl */
+//.drawer__inner__mastodon {background: url("data:image/svg+xml,%3C%3Fxml version='1.0' encoding='iso-8859-1'%3F%3E%3Csvg version='1.1' id='Layer_1' xmlns='http://www.w3.org/2000/svg' xmlns:xlink='http://www.w3.org/1999/xlink' x='0px' y='0px' viewBox='0 0 488.2 488.2' style='enable-background:new 0 0 488.2 488.2;' xml:space='preserve'%3E%3Ccircle style='fill:%232A3332;' cx='244.1' cy='244.2' r='244'/%3E%3Cpath style='fill:%23121C1B;' d='M71.3,71.4c95.2-95.2,249.6-95.2,344.8,0s95.2,249.6,0,344.8'/%3E%3Ccircle style='fill:%23F74D19;' cx='244.1' cy='244.2' r='104.8'/%3E%3Cpath style='fill:%23FF7A17;' d='M139.3,244.2c0-57.6,47.2-104.8,104.8-104.8s104.8,47.2,104.8,104.8'/%3E%3Cpath style='fill:%23E0360E;' d='M263.3,229l-36.8,36.8l69.6,69.6c8-4.8,15.2-10.4,22.4-16.8c5.6-5.6,11.2-12.8,15.2-19.2L263.3,229z' /%3E%3Ccircle style='fill:%23D3DEE2;' cx='244.1' cy='244.2' r='29.6'/%3E%3Cpath style='fill:%23EBF0F2;' d='M244.1,273.8c-16,0-29.6-13.6-29.6-29.6s12.8-29.6,29.6-29.6'/%3E%3Cg%3E%3Cpath style='fill:%23726C6A;' d='M244.1,448.2c-112.8,0-204-91.2-204-204c0-4,3.2-8,8-8c4,0,8,3.2,8,8c-0.8,104,84,188.8,188,188.8 c4,0,8,3.2,8,8C252.1,445,248.1,448.2,244.1,448.2z'/%3E%3Cpath style='fill:%23726C6A;' d='M440.9,252.2c-4,0-8-3.2-8-8c0-104-84.8-188.8-188.8-188.8c-4,0-8-3.2-8-8c0-4,3.2-8,8-8 c112.8,0,204,92,204,204C448.1,248.2,444.9,252.2,440.9,252.2z'/%3E%3Cpath style='fill:%23726C6A;' d='M244.1,401c-86.4,0-156.8-70.4-156.8-156.8c0-4,3.2-8,8-8c4,0,8,3.2,8,8 c0,77.6,63.2,141.6,141.6,141.6c4,0,8,3.2,8,8C252.1,397.8,248.1,401,244.1,401z'/%3E%3Cpath style='fill:%23726C6A;' d='M392.9,252.2c-4,0-8-3.2-8-8c0-77.6-63.2-141.6-141.6-141.6c-4,0-8-3.2-8-8c0-4,3.2-8,8-8 c86.4,0,156.8,70.4,156.8,156.8C400.9,248.2,397.7,252.2,392.9,252.2z'/%3E%3C/g%3E%3C/svg%3E%0A") no-repeat bottom center / contain !important;}
+.drawer__inner__mastodon {
+ background: var(--mascotUrl)
+ no-repeat bottom center / contain !important;
+}
+.drawer__inner__mastodon > img {
+ display: none;
+}
+
+/* repeating musical notes in background, to active columns only*/
+//.columns-area {background: url("data:image/svg+xml,%3Csvg version='1.0' xmlns='http://www.w3.org/2000/svg' width='75.000000pt' height='100.000000pt' viewBox='0 0 456.000000 599.000000' preserveAspectRatio='xMidYMid meet'%3E%3Cg transform='translate%280.000000,599.000000%29 scale%280.100000,-0.100000%29' fill='%2300000022' stroke='none'%3E%3Cpath d='M2261 5576 c-51 -226 -189 -845 -306 -1376 -254 -1146 -404 -1813 -410 -1819 -3 -2 -40 18 -82 45 -133 83 -256 142 -380 183 -149 50 -254 65 -397 58 -197 -9 -328 -66 -461 -201 -128 -129 -193 -275 -217 -484 -28 -253 33 -443 192 -603 108 -108 221 -176 365 -222 341 -107 706 -24 1038 237 154 120 139 89 213 442 35 170 78 370 94 444 285 1311 472 2165 475 2168 3 4 1446 -1016 1495 -1056 20 -17 20 -18 -134 -712 -198 -890 -315 -1402 -320 -1408 -3 -2 -45 20 -93 51 -397 248 -767 304 -1063 161 -113 -55 -232 -178 -294 -305 -52 -106 -71 -174 -87 -304 -11 -91 -6 -227 12 -300 48 -196 209 -374 433 -481 351 -166 755 -105 1122 169 37 29 92 74 122 102 l53 50 464 2009 463 2010 -24 18 c-214 169 -1623 1180 -1929 1385 -104 69 -213 137 -236 145 -13 5 -30 -60 -108 -406z m1037 -967 c406 -293 741 -536 746 -540 8 -8 -34 -235 -54 -292 -5 -15 -131 71 -760 519 -415 295 -757 538 -759 540 -2 2 11 74 29 159 26 121 37 155 48 150 7 -3 345 -244 750 -536z'/%3E%3C/g%3E%3C/svg%3E") space 0 0;}
+
+//background image
+//desktop<2560x1440px
+@media only screen and (max-width: 1440px) and (max-height: 2560px) and (min-width: 961px){
+ .ui {
+ background: url("https://s3.stsecurity.moe/public-media/fault_bg.jpg")
+ no-repeat center center / cover fixed !important;
+ -webkit-background-size: 100vw;
+ -moz-background-size: 100vw;
+ -o-background-size: 100vw;
+ background-size: 100vw;
+ }
+}
+//desktop>2560x1440px
+@media only screen and (min-width: 1440px), screen and (min-height: 2560px){
+ .ui {
+ background: url("https://s3.stsecurity.moe/public-media/fault_bg.jpg")
+ no-repeat center center / cover fixed !important;
+ -webkit-background-size: cover;
+ -moz-background-size: cover;
+ -o-background-size: cover;
+ background-size: cover;
+ }
+}
+//phones
+@media only screen and (max-width: 960px){
+ .ui {
+ background: url("https://s3.stsecurity.moe/public-media/fault_bg_mobile.jpg")
+ no-repeat center center / cover fixed !important;
+ -webkit-background-size: auto 100vh;
+ -moz-background-size: auto 100vh;
+ -o-background-size: auto 100vh;
+ background-size: auto 100vh;
+ }
+}
+
+//header image in start page and about page
+//.public-account-header__image img,
+//.hero-widget__img img {
+// content:var(--headerUrl);
+//}
+
+//mascot image in about page
+.landing-page__mascot img {
+ content:var(--aboutMascotUrl);
+}
+//phones
+ .ui {
+ background: url("https://s3.stsecurity.moe/public-media/fault_bg_mobile.jpg")
+ no-repeat center center fixed !important;
+ -webkit-background-size: auto 100vh;
+ -moz-background-size: auto 100vh;
+ -o-background-size: auto 100vh;
+ background-size: auto 100vh;
+ }
+
+//header image in start page and about page
+.public-account-header__image img,
+.hero-widget__img img {
+ content:var(--headerUrl);
+}
+
+//mascot image in about page
+.landing-page__mascot img {
+ content:var(--aboutMascotUrl);
+}
+
+/* vignette on headers and dropdowns */
+.column-header {
+ box-shadow: inset 0 0 8px 8px rgba(0, 0, 0, 0.3);
+}
+.column-header__button {
+ background: transparent !important;
+}
+.column-header__collapsible-inner {
+ box-shadow: inset 0 0 10em 10em rgba(0, 0, 0, 0.3);
+}
diff --git a/app/javascript/styles/fault/palette.scss b/app/javascript/styles/fault/palette.scss
new file mode 100644
index 00000000000000..c0ab9ee63f8a82
--- /dev/null
+++ b/app/javascript/styles/fault/palette.scss
@@ -0,0 +1,73 @@
+/*------------------------------------------------------------------------------
+* DEFINE COLOR PALETTE
+*
+* Choose the CSS Variables that will be applied to recolor elements.
+* The current format is to use hex codes, e.g. #00FF00.
+*
+* A future refactor to use rgb() instead may allow transparency mods
+* via using these variables with rgba(). Hex codes currently do not
+* work with rgba(), so no transparency mods are included except for
+* those defined in absolute terms (i.e., non-variable colors).
+*
+* Foreground Colors:
+* --bg: | Background for foreground elements.
+* --text: | A color that stands out against bg.
+* --textBold: | A color that stands out strongly against bg.
+* --textMuted: | A color that stands out slightly against bg.
+*
+* Background Colors:
+* --bgPage: | Background for non-foreground elements.
+* --textPage: | A color that stands out against bgPage.
+* --textPageBold: | A color that stands out strongly against bgPage.
+* --textPageMuted: | A color that stands out slightly against bgPage.
+*
+* Highlights Colors:
+* --bgHead: | Background for column headers.
+* --textHead: | A color that stands out (strongly) against bgHead.
+* --accent: | An accent color for links and buttons.
+* --accentText: | A color that stands out (strongly) against accent.
+*
+* Palette advisories for choosing colors:
+* - Consider using an off-white or off-black for text tones,
+* but not necessary as long as there is sufficient contrast.
+* - Bold colors are highly recommended to be strong colors,
+* like pure white or pure black.
+* - Muted colors can be off-grey or any mid-tone with slight contrast.
+* - It might be best to base the background palette on a slightly
+* darker or lighter version of the foreground palette, but
+* this is no longer strictly necessary; you may use mixed palettes.
+* It is now possible to use a dark bgPage and light bg, or vice-versa.
+------------------------------------------------------------------------------*/
+
+/* fault */
+
+:root {
+ --bg: #fff;
+ --text: #123;
+ --textBold: #000;
+ --textMuted: #666;
+
+ --bgPage: #ddd;
+ --bgPageTransparent: rgba(221, 221, 221, 0.5);
+ --textPage: var(--text);
+ --textPageBold: var(--textBold);
+ --textPageMuted: var(--textMuted);
+
+ --bgHead: #333;
+ --textHead: #fff;
+ --accent: #09f;
+ --accentText: var(--textHead);
+
+ //background image
+ --bgUrl:url("https://s3.stsecurity.moe/public-media/fault_bg.jpg");
+ //desktop>2560x1440px
+ --bg4kUrl:url("https://s3.stsecurity.moe/public-media/fault_bg.jpg");
+ //phones
+ --bgPhoneUrl:url("https://s3.stsecurity.moe/public-media/fault_bg_mobile.jpg");
+ //header image in start page and about page
+ --headerUrl:url("https://s3.stsecurity.moe/public-media/fault_public-account-header__image.png");
+ /* replace elephant friend with vinyl */
+ --mascotUrl:url("https://s3.stsecurity.moe/public-media/fault_mascot.png");
+ //mascot image in about page
+ --aboutMascotUrl:url("https://s3.stsecurity.moe/public-media/fault_mascot.png");
+}
diff --git a/app/javascript/styles/lilymagic.scss b/app/javascript/styles/lilymagic.scss
new file mode 100644
index 00000000000000..cb925c186ff087
--- /dev/null
+++ b/app/javascript/styles/lilymagic.scss
@@ -0,0 +1,19 @@
+@import "application";
+
+@import "mfc/mastodonFlat";
+@import "lilymagic/palette";
+@import "lilymagic/overrides";
+
+@import "mods/fixes";
+
+@import "mods/display_breakname";
+@import "mods/display_fullname";
+@import "mods/display_browserfont";
+@import "mods/display_circleavatar";
+@import "mods/display_collapsedinteractions";
+@import "mods/display_fadedinteractions";
+@import "mods/display_transparentmedia";
+@import "mods/layout_1600px";
+@import "mods/layout_elefriend";
+@import "mods/layout_widercolumns";
+@import "mods/layout_mobile_bottombar";
diff --git a/app/javascript/styles/lilymagic/overrides.scss b/app/javascript/styles/lilymagic/overrides.scss
new file mode 100644
index 00000000000000..4cf7637d73d8ef
--- /dev/null
+++ b/app/javascript/styles/lilymagic/overrides.scss
@@ -0,0 +1,73 @@
+/*---------
+MISC TWEAKS
+---------*/
+
+/* replace elephant friend with vinyl */
+//.drawer__inner__mastodon {background: url("data:image/svg+xml,%3C%3Fxml version='1.0' encoding='iso-8859-1'%3F%3E%3Csvg version='1.1' id='Layer_1' xmlns='http://www.w3.org/2000/svg' xmlns:xlink='http://www.w3.org/1999/xlink' x='0px' y='0px' viewBox='0 0 488.2 488.2' style='enable-background:new 0 0 488.2 488.2;' xml:space='preserve'%3E%3Ccircle style='fill:%232A3332;' cx='244.1' cy='244.2' r='244'/%3E%3Cpath style='fill:%23121C1B;' d='M71.3,71.4c95.2-95.2,249.6-95.2,344.8,0s95.2,249.6,0,344.8'/%3E%3Ccircle style='fill:%23F74D19;' cx='244.1' cy='244.2' r='104.8'/%3E%3Cpath style='fill:%23FF7A17;' d='M139.3,244.2c0-57.6,47.2-104.8,104.8-104.8s104.8,47.2,104.8,104.8'/%3E%3Cpath style='fill:%23E0360E;' d='M263.3,229l-36.8,36.8l69.6,69.6c8-4.8,15.2-10.4,22.4-16.8c5.6-5.6,11.2-12.8,15.2-19.2L263.3,229z' /%3E%3Ccircle style='fill:%23D3DEE2;' cx='244.1' cy='244.2' r='29.6'/%3E%3Cpath style='fill:%23EBF0F2;' d='M244.1,273.8c-16,0-29.6-13.6-29.6-29.6s12.8-29.6,29.6-29.6'/%3E%3Cg%3E%3Cpath style='fill:%23726C6A;' d='M244.1,448.2c-112.8,0-204-91.2-204-204c0-4,3.2-8,8-8c4,0,8,3.2,8,8c-0.8,104,84,188.8,188,188.8 c4,0,8,3.2,8,8C252.1,445,248.1,448.2,244.1,448.2z'/%3E%3Cpath style='fill:%23726C6A;' d='M440.9,252.2c-4,0-8-3.2-8-8c0-104-84.8-188.8-188.8-188.8c-4,0-8-3.2-8-8c0-4,3.2-8,8-8 c112.8,0,204,92,204,204C448.1,248.2,444.9,252.2,440.9,252.2z'/%3E%3Cpath style='fill:%23726C6A;' d='M244.1,401c-86.4,0-156.8-70.4-156.8-156.8c0-4,3.2-8,8-8c4,0,8,3.2,8,8 c0,77.6,63.2,141.6,141.6,141.6c4,0,8,3.2,8,8C252.1,397.8,248.1,401,244.1,401z'/%3E%3Cpath style='fill:%23726C6A;' d='M392.9,252.2c-4,0-8-3.2-8-8c0-77.6-63.2-141.6-141.6-141.6c-4,0-8-3.2-8-8c0-4,3.2-8,8-8 c86.4,0,156.8,70.4,156.8,156.8C400.9,248.2,397.7,252.2,392.9,252.2z'/%3E%3C/g%3E%3C/svg%3E%0A") no-repeat bottom center / contain !important;}
+.drawer__inner__mastodon {
+ background: var(--mascotUrl)
+ no-repeat bottom center / contain !important;
+}
+.drawer__inner__mastodon > img {
+ display: none;
+}
+
+/* repeating musical notes in background, to active columns only*/
+//.columns-area {background: url("data:image/svg+xml,%3Csvg version='1.0' xmlns='http://www.w3.org/2000/svg' width='75.000000pt' height='100.000000pt' viewBox='0 0 456.000000 599.000000' preserveAspectRatio='xMidYMid meet'%3E%3Cg transform='translate%280.000000,599.000000%29 scale%280.100000,-0.100000%29' fill='%2300000022' stroke='none'%3E%3Cpath d='M2261 5576 c-51 -226 -189 -845 -306 -1376 -254 -1146 -404 -1813 -410 -1819 -3 -2 -40 18 -82 45 -133 83 -256 142 -380 183 -149 50 -254 65 -397 58 -197 -9 -328 -66 -461 -201 -128 -129 -193 -275 -217 -484 -28 -253 33 -443 192 -603 108 -108 221 -176 365 -222 341 -107 706 -24 1038 237 154 120 139 89 213 442 35 170 78 370 94 444 285 1311 472 2165 475 2168 3 4 1446 -1016 1495 -1056 20 -17 20 -18 -134 -712 -198 -890 -315 -1402 -320 -1408 -3 -2 -45 20 -93 51 -397 248 -767 304 -1063 161 -113 -55 -232 -178 -294 -305 -52 -106 -71 -174 -87 -304 -11 -91 -6 -227 12 -300 48 -196 209 -374 433 -481 351 -166 755 -105 1122 169 37 29 92 74 122 102 l53 50 464 2009 463 2010 -24 18 c-214 169 -1623 1180 -1929 1385 -104 69 -213 137 -236 145 -13 5 -30 -60 -108 -406z m1037 -967 c406 -293 741 -536 746 -540 8 -8 -34 -235 -54 -292 -5 -15 -131 71 -760 519 -415 295 -757 538 -759 540 -2 2 11 74 29 159 26 121 37 155 48 150 7 -3 345 -244 750 -536z'/%3E%3C/g%3E%3C/svg%3E") space 0 0;}
+
+//background image
+//desktop<2560x1440px
+@media only screen and (max-width: 1440px) and (max-height: 2560px) and (min-width: 961px){
+ .ui {
+ background: url("https://s3.stsecurity.moe/public-media/lilymagic_bg.png")
+ no-repeat center center / cover fixed !important;
+ -webkit-background-size: 100vw;
+ -moz-background-size: 100vw;
+ -o-background-size: 100vw;
+ background-size: 100vw;
+ }
+}
+//desktop>2560x1440px
+@media only screen and (min-width: 1440px), screen and (min-height: 2560px){
+ .ui {
+ background: url("https://s3.stsecurity.moe/public-media/lilymagic_bg.png")
+ no-repeat center center / cover fixed !important;
+ -webkit-background-size: cover;
+ -moz-background-size: cover;
+ -o-background-size: cover;
+ background-size: cover;
+ }
+}
+//phones
+@media only screen and (max-width: 960px){
+ .ui {
+ background: url("https://s3.stsecurity.moe/public-media/fault_bg_mobile.jpg")
+ no-repeat center center / cover fixed !important;
+ -webkit-background-size: auto 100vh;
+ -moz-background-size: auto 100vh;
+ -o-background-size: auto 100vh;
+ background-size: auto 100vh;
+ }
+}
+
+//header image in start page and about page
+//.public-account-header__image img,
+//.hero-widget__img img {
+// content:var(--headerUrl);
+//}
+
+//mascot image in about page
+.landing-page__mascot img {
+ content:var(--aboutMascotUrl);
+}
+
+/* vignette on headers and dropdowns */
+.column-header {
+ box-shadow: inset 0 0 8px 8px rgba(0, 0, 0, 0.3);
+}
+.column-header__button {
+ background: transparent !important;
+}
+.column-header__collapsible-inner {
+ box-shadow: inset 0 0 10em 10em rgba(0, 0, 0, 0.3);
+}
diff --git a/app/javascript/styles/lilymagic/palette.scss b/app/javascript/styles/lilymagic/palette.scss
new file mode 100644
index 00000000000000..f43a0f1d490a2b
--- /dev/null
+++ b/app/javascript/styles/lilymagic/palette.scss
@@ -0,0 +1,73 @@
+/*------------------------------------------------------------------------------
+* DEFINE COLOR PALETTE
+*
+* Choose the CSS Variables that will be applied to recolor elements.
+* The current format is to use hex codes, e.g. #00FF00.
+*
+* A future refactor to use rgb() instead may allow transparency mods
+* via using these variables with rgba(). Hex codes currently do not
+* work with rgba(), so no transparency mods are included except for
+* those defined in absolute terms (i.e., non-variable colors).
+*
+* Foreground Colors:
+* --bg: | Background for foreground elements.
+* --text: | A color that stands out against bg.
+* --textBold: | A color that stands out strongly against bg.
+* --textMuted: | A color that stands out slightly against bg.
+*
+* Background Colors:
+* --bgPage: | Background for non-foreground elements.
+* --textPage: | A color that stands out against bgPage.
+* --textPageBold: | A color that stands out strongly against bgPage.
+* --textPageMuted: | A color that stands out slightly against bgPage.
+*
+* Highlights Colors:
+* --bgHead: | Background for column headers.
+* --textHead: | A color that stands out (strongly) against bgHead.
+* --accent: | An accent color for links and buttons.
+* --accentText: | A color that stands out (strongly) against accent.
+*
+* Palette advisories for choosing colors:
+* - Consider using an off-white or off-black for text tones,
+* but not necessary as long as there is sufficient contrast.
+* - Bold colors are highly recommended to be strong colors,
+* like pure white or pure black.
+* - Muted colors can be off-grey or any mid-tone with slight contrast.
+* - It might be best to base the background palette on a slightly
+* darker or lighter version of the foreground palette, but
+* this is no longer strictly necessary; you may use mixed palettes.
+* It is now possible to use a dark bgPage and light bg, or vice-versa.
+------------------------------------------------------------------------------*/
+
+/* fault */
+
+:root {
+ --bg: #fff;
+ --text: #123;
+ --textBold: #000;
+ --textMuted: #666;
+
+ --bgPage: #ddd;
+ --bgPageTransparent: rgba(221, 221, 221, 0.5);
+ --textPage: var(--text);
+ --textPageBold: var(--textBold);
+ --textPageMuted: var(--textMuted);
+
+ --bgHead: #333;
+ --textHead: #fff;
+ --accent: #09f;
+ --accentText: var(--textHead);
+
+ //background image
+ --bgUrl:url("https://s3.stsecurity.moe/public-media/lilymagic_bg.png");
+ //desktop>2560x1440px
+ --bg4kUrl:url("https://s3.stsecurity.moe/public-media/lilymagic_bg.png");
+ //phones
+ --bgPhoneUrl:url("https://s3.stsecurity.moe/public-media/fault_bg_mobile.jpg");
+ //header image in start page and about page
+ --headerUrl:url("https://s3.stsecurity.moe/public-media/lilymagic_header.png");
+ /* replace elephant friend with vinyl */
+ --mascotUrl:url("https://s3.stsecurity.moe/public-media/lilymagic_mascot.png");
+ //mascot image in about page
+ --aboutMascotUrl:url("https://s3.stsecurity.moe/public-media/lilymagic_mascot.png");
+}
diff --git a/app/javascript/styles/linernotes_dark.scss b/app/javascript/styles/linernotes_dark.scss
new file mode 100644
index 00000000000000..7140bb302405a9
--- /dev/null
+++ b/app/javascript/styles/linernotes_dark.scss
@@ -0,0 +1,17 @@
+@import 'application';
+
+@import 'mfc/mastodonFlat';
+@import 'linernotes_dark/palette';
+@import 'linernotes_dark/overrides';
+
+@import 'mods/display_breakname';
+@import 'mods/display_fullname';
+@import 'mods/display_browserfont';
+@import 'mods/display_circleavatar';
+@import 'mods/display_collapsedinteractions';
+@import 'mods/display_fadedinteractions';
+@import 'mods/display_transparentmedia';
+@import 'mods/layout_1600px';
+@import 'mods/layout_elefriend';
+@import 'mods/layout_widercolumns';
+@import 'mods/layout_mobile_bottombar';
diff --git a/app/javascript/styles/linernotes_dark/overrides.scss b/app/javascript/styles/linernotes_dark/overrides.scss
new file mode 100644
index 00000000000000..bcbdcf6cde692e
--- /dev/null
+++ b/app/javascript/styles/linernotes_dark/overrides.scss
@@ -0,0 +1,15 @@
+/*---------
+MISC TWEAKS
+---------*/
+
+/* replace elephant friend with vinyl */
+.drawer__inner__mastodon {background: url("data:image/svg+xml,%3C%3Fxml version='1.0' encoding='iso-8859-1'%3F%3E%3Csvg version='1.1' id='Layer_1' xmlns='http://www.w3.org/2000/svg' xmlns:xlink='http://www.w3.org/1999/xlink' x='0px' y='0px' viewBox='0 0 488.2 488.2' style='enable-background:new 0 0 488.2 488.2;' xml:space='preserve'%3E%3Ccircle style='fill:%232A3332;' cx='244.1' cy='244.2' r='244'/%3E%3Cpath style='fill:%23121C1B;' d='M71.3,71.4c95.2-95.2,249.6-95.2,344.8,0s95.2,249.6,0,344.8'/%3E%3Ccircle style='fill:%23F74D19;' cx='244.1' cy='244.2' r='104.8'/%3E%3Cpath style='fill:%23FF7A17;' d='M139.3,244.2c0-57.6,47.2-104.8,104.8-104.8s104.8,47.2,104.8,104.8'/%3E%3Cpath style='fill:%23E0360E;' d='M263.3,229l-36.8,36.8l69.6,69.6c8-4.8,15.2-10.4,22.4-16.8c5.6-5.6,11.2-12.8,15.2-19.2L263.3,229z' /%3E%3Ccircle style='fill:%23D3DEE2;' cx='244.1' cy='244.2' r='29.6'/%3E%3Cpath style='fill:%23EBF0F2;' d='M244.1,273.8c-16,0-29.6-13.6-29.6-29.6s12.8-29.6,29.6-29.6'/%3E%3Cg%3E%3Cpath style='fill:%23726C6A;' d='M244.1,448.2c-112.8,0-204-91.2-204-204c0-4,3.2-8,8-8c4,0,8,3.2,8,8c-0.8,104,84,188.8,188,188.8 c4,0,8,3.2,8,8C252.1,445,248.1,448.2,244.1,448.2z'/%3E%3Cpath style='fill:%23726C6A;' d='M440.9,252.2c-4,0-8-3.2-8-8c0-104-84.8-188.8-188.8-188.8c-4,0-8-3.2-8-8c0-4,3.2-8,8-8 c112.8,0,204,92,204,204C448.1,248.2,444.9,252.2,440.9,252.2z'/%3E%3Cpath style='fill:%23726C6A;' d='M244.1,401c-86.4,0-156.8-70.4-156.8-156.8c0-4,3.2-8,8-8c4,0,8,3.2,8,8 c0,77.6,63.2,141.6,141.6,141.6c4,0,8,3.2,8,8C252.1,397.8,248.1,401,244.1,401z'/%3E%3Cpath style='fill:%23726C6A;' d='M392.9,252.2c-4,0-8-3.2-8-8c0-77.6-63.2-141.6-141.6-141.6c-4,0-8-3.2-8-8c0-4,3.2-8,8-8 c86.4,0,156.8,70.4,156.8,156.8C400.9,248.2,397.7,252.2,392.9,252.2z'/%3E%3C/g%3E%3C/svg%3E%0A") no-repeat bottom center / contain !important;}
+.drawer__inner__mastodon > img {display: none;}
+
+/* repeating musical notes in background */
+.columns-area {background: url("data:image/svg+xml,%3Csvg version='1.0' xmlns='http://www.w3.org/2000/svg' width='75.000000pt' height='100.000000pt' viewBox='0 0 456.000000 599.000000' preserveAspectRatio='xMidYMid meet'%3E%3Cg transform='translate%280.000000,599.000000%29 scale%280.100000,-0.100000%29' fill='%2300000022' stroke='none'%3E%3Cpath d='M2261 5576 c-51 -226 -189 -845 -306 -1376 -254 -1146 -404 -1813 -410 -1819 -3 -2 -40 18 -82 45 -133 83 -256 142 -380 183 -149 50 -254 65 -397 58 -197 -9 -328 -66 -461 -201 -128 -129 -193 -275 -217 -484 -28 -253 33 -443 192 -603 108 -108 221 -176 365 -222 341 -107 706 -24 1038 237 154 120 139 89 213 442 35 170 78 370 94 444 285 1311 472 2165 475 2168 3 4 1446 -1016 1495 -1056 20 -17 20 -18 -134 -712 -198 -890 -315 -1402 -320 -1408 -3 -2 -45 20 -93 51 -397 248 -767 304 -1063 161 -113 -55 -232 -178 -294 -305 -52 -106 -71 -174 -87 -304 -11 -91 -6 -227 12 -300 48 -196 209 -374 433 -481 351 -166 755 -105 1122 169 37 29 92 74 122 102 l53 50 464 2009 463 2010 -24 18 c-214 169 -1623 1180 -1929 1385 -104 69 -213 137 -236 145 -13 5 -30 -60 -108 -406z m1037 -967 c406 -293 741 -536 746 -540 8 -8 -34 -235 -54 -292 -5 -15 -131 71 -760 519 -415 295 -757 538 -759 540 -2 2 11 74 29 159 26 121 37 155 48 150 7 -3 345 -244 750 -536z'/%3E%3C/g%3E%3C/svg%3E") space 0 0;}
+
+/* vignette on headers and dropdowns */
+.column-header {box-shadow: inset 0 0 8px 8px rgba(0,0,0,0.3);}
+.column-header__button {background: transparent !important;}
+.column-header__collapsible-inner {box-shadow: inset 0 0 10em 10em rgba(0,0,0,0.3);}
diff --git a/app/javascript/styles/linernotes_dark/palette.scss b/app/javascript/styles/linernotes_dark/palette.scss
new file mode 100644
index 00000000000000..17c6ed9f64c174
--- /dev/null
+++ b/app/javascript/styles/linernotes_dark/palette.scss
@@ -0,0 +1,59 @@
+/*------------------------------------------------------------------------------
+* DEFINE COLOR PALETTE
+*
+* Choose the CSS Variables that will be applied to recolor elements.
+* The current format is to use hex codes, e.g. #00FF00.
+*
+* A future refactor to use rgb() instead may allow transparency mods
+* via using these variables with rgba(). Hex codes currently do not
+* work with rgba(), so no transparency mods are included except for
+* those defined in absolute terms (i.e., non-variable colors).
+*
+* Foreground Colors:
+* --bg: | Background for foreground elements.
+* --text: | A color that stands out against bg.
+* --textBold: | A color that stands out strongly against bg.
+* --textMuted: | A color that stands out slightly against bg.
+*
+* Background Colors:
+* --bgPage: | Background for non-foreground elements.
+* --textPage: | A color that stands out against bgPage.
+* --textPageBold: | A color that stands out strongly against bgPage.
+* --textPageMuted: | A color that stands out slightly against bgPage.
+*
+* Highlights Colors:
+* --bgHead: | Background for column headers.
+* --textHead: | A color that stands out (strongly) against bgHead.
+* --accent: | An accent color for links and buttons.
+* --accentText: | A color that stands out (strongly) against accent.
+*
+* Palette advisories for choosing colors:
+* - Consider using an off-white or off-black for text tones,
+* but not necessary as long as there is sufficient contrast.
+* - Bold colors are highly recommended to be strong colors,
+* like pure white or pure black.
+* - Muted colors can be off-grey or any mid-tone with slight contrast.
+* - It might be best to base the background palette on a slightly
+* darker or lighter version of the foreground palette, but
+* this is no longer strictly necessary; you may use mixed palettes.
+* It is now possible to use a dark bgPage and light bg, or vice-versa.
+------------------------------------------------------------------------------*/
+
+/* linernotes dark */
+
+:root {
+--bg: rgb(26,21,21);
+--text: rgb(225,225,225);
+--textBold: rgb(255,255,255);
+--textMuted: rgb(153,157,156);
+
+--bgPage: rgb(42,38,37);
+--textPage: var(--text);
+--textPageBold: var(--textBold);
+--textPageMuted: var(--textMuted);
+
+--bgHead: rgb(255,122,23);
+--textHead: var(--textBold);
+--accent: rgb(3, 180, 0);
+--accentText: var(--textBold);
+}
\ No newline at end of file
diff --git a/app/javascript/styles/mastodon/admin.scss b/app/javascript/styles/mastodon/admin.scss
index 68e6d2482f020c..7a104dbf4a35f4 100644
--- a/app/javascript/styles/mastodon/admin.scss
+++ b/app/javascript/styles/mastodon/admin.scss
@@ -660,6 +660,18 @@ body,
text-decoration: underline;
}
}
+
+ &--inactive {
+ .log-entry__title {
+ text-decoration: line-through;
+ }
+
+ a,
+ .username,
+ .target {
+ color: $darker-text-color;
+ }
+ }
}
a.name-tag,
@@ -1213,6 +1225,17 @@ a.sparkline {
font-weight: 600;
padding: 4px 0;
}
+
+ a {
+ color: $ui-highlight-color;
+ text-decoration: none;
+
+ &:hover,
+ &:focus,
+ &:active {
+ text-decoration: underline;
+ }
+ }
}
&--horizontal {
diff --git a/app/javascript/styles/mastodon/variables.scss b/app/javascript/styles/mastodon/variables.scss
index f463419c8203ca..986ccb6c6c8055 100644
--- a/app/javascript/styles/mastodon/variables.scss
+++ b/app/javascript/styles/mastodon/variables.scss
@@ -44,7 +44,7 @@ $lighter-text-color: $ui-base-lighter-color !default;
$light-text-color: $ui-primary-color !default;
// Language codes that uses CJK fonts
-$cjk-langs: ja, ko, zh-CN, zh-HK, zh-TW;
+$cjk-langs: ja, ko, zh-CN, zh-HK, zh-TW, zh-YR;
// Variables for components
$media-modal-media-max-width: 100%;
diff --git a/app/javascript/styles/mfc/0-palette.css b/app/javascript/styles/mfc/0-palette.css
new file mode 100644
index 00000000000000..9e4e7e59f53740
--- /dev/null
+++ b/app/javascript/styles/mfc/0-palette.css
@@ -0,0 +1,127 @@
+/*------------------------------------------------------------------------------
+* DEFINE COLOR PALETTE
+*
+* Choose the CSS Variables that will be applied to recolor elements.
+* The current format is to use hex codes, e.g. #00FF00.
+*
+* A future refactor to use rgb() instead may allow transparency mods
+* via using these variables with rgba(). Hex codes currently do not
+* work with rgba(), so no transparency mods are included except for
+* those defined in absolute terms (i.e., non-variable colors).
+*
+* Foreground Colors:
+* --bg: | Background for foreground elements.
+* --text: | A color that stands out against bg.
+* --textBold: | A color that stands out strongly against bg.
+* --textMuted: | A color that stands out slightly against bg.
+*
+* Background Colors:
+* --bgPage: | Background for non-foreground elements.
+* --textPage: | A color that stands out against bgPage.
+* --textPageBold: | A color that stands out strongly against bgPage.
+* --textPageMuted: | A color that stands out slightly against bgPage.
+*
+* Highlights Colors:
+* --bgHead: | Background for column headers.
+* --textHead: | A color that stands out (strongly) against bgHead.
+* --accent: | An accent color for links and buttons.
+* --accentText: | A color that stands out (strongly) against accent.
+*
+* Palette advisories for choosing colors:
+* - Consider using an off-white or off-black for text tones,
+* but not necessary as long as there is sufficient contrast.
+* - Bold colors are highly recommended to be strong colors,
+* like pure white or pure black.
+* - Muted colors can be off-grey or any mid-tone with slight contrast.
+* - It might be best to base the background palette on a slightly
+* darker or lighter version of the foreground palette, but
+* this is no longer strictly necessary; you may use mixed palettes.
+* It is now possible to use a dark bgPage and light bg, or vice-versa.
+------------------------------------------------------------------------------*/
+
+/* copy and paste the desired palette at the end of this section,
+* or you can delete the ones you don't want, or comment them out.
+*/
+
+
+/* Clean Slate:
+* white columns on a light-grey page, with dark grey headers and blue accent.
+*/
+:root {
+--bg: #fff;
+--text: #123;
+--textBold: #000;
+--textMuted: #666;
+
+--bgPage: #ddd;
+--textPage: var(--text);
+--textPageBold: var(--textBold);
+--textPageMuted: var(--textMuted);
+
+--bgHead: #333;
+--textHead: #fff;
+--accent: #09f;
+--accentText: var(--textHead);
+}
+
+/* Droid/Flamingo:
+* dark neutral-grey page, with either Android Green or Flamingo accent.
+*/
+:root {
+--bg: #222;
+--text: #ddd;
+--textBold: #fff;
+--textMuted: #777;
+
+--bgPage: #111;
+--textPage: var(--text);
+--textPageBold: var(--textBold);
+--textPageMuted: var(--textMuted);
+
+--bgHead: #333;
+--textHead: var(--textBold);
+--accent: #a4c639; /* flamingo: #f09 */
+--accentText: var(--textHead);
+}
+
+/* Midnight Blue:
+* a dark blue palette based loosely on Twitter's night mode.
+*/
+:root {
+--bg: #123;
+--text: #d0d8e0;
+--textBold: #fff;
+--textMuted: #808890;
+
+--bgPage: #051119;
+--textPage: var(--text);
+--textPageBold: var(--textBold);
+--textPageMuted: var(--textMuted);
+
+--bgHead: #357;
+--textHead: var(--textBold);
+--accent: #09f;
+--accentText: var(--textBold);
+}
+
+/*
+* Testing palette:
+* light grey columns, slate grey background, turquoise headers, orange accents.
+* (a bit garish, but sufficiently "different" to catch any unthemed elements.)
+*/
+:root {
+--bg: #d9e1e8;
+--text: #234;
+--textBold: #000000;
+--textMuted: #579;
+
+--bgPage: #345;
+--textPage: #ddd;
+--textPageBold: #fff;
+--textPageMuted: #aaa;
+
+--bgHead: darkturquoise;
+--textHead: #345;
+--accent: #ff3e00;
+--accentText: var(--textPageBold);
+}
\ No newline at end of file
diff --git a/app/javascript/styles/mfc/1-foreground.css b/app/javascript/styles/mfc/1-foreground.css
new file mode 100644
index 00000000000000..b736ad90cbe3f0
--- /dev/null
+++ b/app/javascript/styles/mfc/1-foreground.css
@@ -0,0 +1,321 @@
+/*------------------------------------------------------------------------------
+* FOREGROUND COLOR PALETTE =====================================================
+------------------------------------------------------------------------------*/
+
+/*----------------------
+base background and text
+----------------------*/
+
+/* status columns */
+.column>.scrollable,
+.status,
+.detailed-status,
+ .detailed-status__action-bar,
+ .focusable:focus .detailed-status,
+ .focusable:focus .detailed-status__action-bar,
+.setting-text,
+ .setting-text:active,
+ .setting-text:focus,
+.status-direct .status__content .status__content__spoiler-link,
+.column-link,
+ .getting-started a.column-link,
+ .getting-started__trends .trends__item__current,
+.account__header__content,
+.account__header__bio .account__header__content,
+.account--panel,
+.account__header__bar,
+.account__header__account-note textarea,
+.follow_requests-unlocked_explanation,
+.account-authorize,
+.trends__header,
+/* new conversations */
+.conversation--unread,
+/* search */
+.search__input,
+ .search__input:focus,
+.search-results .account,
+.trends__item,
+ .trends__item__name a,
+ .trends__item__current,
+/* compose form */
+.reply-indicator__content, /* in reply to */
+.compose-form .spoiler-input__input, /* cw */
+.compose-form .autosuggest-textarea__textarea, /* post */
+.compose-form .compose-form__modifiers, /* image uploads */
+.compose-form .compose-form__buttons-wrapper, /* buttons */
+.compose-form .autosuggest-textarea__textarea::placeholder,
+.compose-form .spoiler-input__input::placeholder,
+/* settings page */
+.simple_form textarea,
+ .simple_form textarea:active,
+ .simple_form textarea:focus,
+.simple_form input[type=email],
+ .simple_form input[type=email]:active,
+ .simple_form input[type=email]:focus,
+.simple_form input[type=number],
+ .simple_form input[type=number]:active,
+ .simple_form input[type=number]:focus,
+.simple_form input[type=password],
+ .simple_form input[type=password]:active,
+ .simple_form input[type=password]:focus,
+.simple_form input[type=text],
+ .simple_form input[type=text]:active,
+ .simple_form input[type=text]:focus,
+.table td,
+ .table th,
+ .table.inline-table>tbody>tr:nth-child(odd)>td,
+ .table.inline-table>tbody>tr:nth-child(odd)>th,
+ .table>tbody>tr:nth-child(odd)>td,
+ .table>tbody>tr:nth-child(odd)>th,
+ .batch-table__row,
+ .batch-table__row:nth-child(2n),
+ .batch-table__row:hover,
+.column-inline-form label,
+.dashboard__counters > div > div, .dashboard__counters > div > a,
+.log-entry,
+ .log-entry__header,
+/* modals */
+.actions-modal,
+ .actions-modal .status,
+ .actions-modal ul li:not(:empty) a,
+ .status.light .display-name strong, .status.light .status__content,
+.boost-modal,
+.confirmation-modal,
+.dropdown-menu, .dropdown-menu__item a,
+.mute-modal,
+ .block-modal .setting-toggle__label, .mute-modal .setting-toggle__label,
+.report-modal,
+ .report-modal__statuses .status__content p,
+ .report-modal__comment .setting-toggle__label,
+.list-editor,
+ .list-editor .drawer__inner,
+ .list-editor .drawer__inner.backdrop,
+.account__moved-note,
+.introduction__pager,
+.introduction__text p,
+.reactions-bar__item,
+/* profile cards */
+.card__bar,
+ .card>a:active .card__bar,
+ .card>a:focus .card__bar,
+ .card>a:hover .card__bar,
+.directory__card__bar,
+ .directory__card__extra,
+ .accounts-table__count,
+ .directory__card__img,
+/* public pages */
+#new_user .lead,
+.landing .hero-widget__footer,
+ .landing .simple_form .user_agreement .label_input > label,
+ .landing .hero-widget h4,
+ .hero-widget__counter,
+ .landing .hero-widget__counter span,
+ .directory__tag > a, .directory__tag > div,
+.activity-stream .entry,
+.landing-page #mastodon-timeline,
+ .landing-page #mastodon-timeline p,
+ .landing-page__forms,
+ .landing-page__information,
+ .landing-page li,
+ .landing-page p,
+ .directory__tag h4 small,
+ .directory__tag h4 .fa,
+ .landing-page .features-list .features-list__row .text,
+ .landing-page .features-list .features-list__row .visual .fa,
+ .landing-page__short-description h1,
+ .landing-page .separator-or span,
+ .contact-widget,
+ .landing-page__information.contact-widget,
+.rich-formatting li,
+ .rich-formatting p,
+.public-layout .public-account-bio .account__header__content,
+ .public-layout .public-account-bio .roles,
+ .public-layout .public-account-bio__extra,
+ .public-layout .public-account-bio,
+.public-layout .public-account-header__bar::before,
+.account__header__fields dt,
+.account__header__fields dd,
+.hero-widget__text,
+.load-more,
+.button.button-secondary,
+.simple_form__overlay-area__overlay,
+.box-widget,
+.rules-list,
+.rules-list__text {
+ background: var(--bg);
+ color: var(--text);
+}
+
+/* border radius for some elements*/
+.search__input,
+.navigation-bar,
+.column-header__wrapper,
+.contact-widget {
+ border-radius: 4px 4px 4px 4px;
+}
+
+.compose-form__buttons-wrapper {
+ border-radius: 0 0 4px 4px;
+}
+
+/*--------------------
+override for bold text
+--------------------*/
+
+/* primary elements */
+.account__display-name strong,
+ .status__display-name strong,
+ .detailed-status__display-name strong,
+ .card__bar .display-name strong,
+ .directory__card__bar .display-name strong,
+ .account__header__tabs__name h1,
+ .account__header__extra__links a strong,
+.account__action-bar__tab strong, /* profile counters */
+.conversation__content__names a,
+.conversation--unread .conversation__content__relative-time,
+/* settings page*/
+.dashboard__counters__num, .dashboard__counters__text,
+.log-entry a,
+ .log-entry .username,
+ .log-entry .target,
+/* public pages */
+#new_user .lead strong,
+.landing-page h3,
+ .landing-page h4,
+ .landing-page em,
+ .landing-page h5,
+ .landing-page h6,
+ .directory__tag h4,
+.rich-formatting h3,
+ .rich-formatting h4,
+ .rich-formatting h2,
+ .rich-formatting,
+.public-layout .public-account-header__tabs__tabs .counter .counter-number,
+.account__header__fields dt {
+ color: var(--textBold);
+}
+
+/*---------------------
+override for muted text
+---------------------*/
+
+/* secondary elements */
+.display-name__account,
+ .account .account__display-name,
+ .card__bar .display-name span,
+ .directory__card__bar .display-name span,
+ .accounts-table__count small,
+ .account__header__tabs__name h1 small,
+ .account__header__extra__links a,
+ .account__header__account-note label,
+ .account__header__account-note textarea::placeholder,
+ .account__header__extra__links,
+.detailed-status__meta,
+.status__relative-time,
+.status__action-bar__counter__label,
+.status__prepend,
+ .status__prepend .status__display-name strong,
+.account__moved-note__message,
+.attachment-list.compact .fa,
+.icon-button,
+ .icon-button.disabled,
+ .icon-button.active:hover,
+.account__action-bar__tab>span,
+.compose-form .icon-button.inverted,
+ .compose-form .text-icon-button,
+ .compose-form .compose-form__buttons-wrapper .character-counter__wrapper .character-counter,
+ .compose-form .autosuggest-textarea__textarea::placeholder,
+ .compose-form .spoiler-input__input::placeholder,
+.upload-progress,
+.search__icon .fa,
+ .search__icon .fa-times-circle,
+ .trends__item__name,
+.search__input::placeholder,
+.notification__message,
+.muted .status__content,
+ .muted .status__content a,
+ .muted .status__content p,
+ .muted .status__display-name strong,
+.attachment-list__list a,
+a.table-action-link,
+ .table a.table-action-link,
+ button.table-action-link,
+.status__wrapper--filtered,
+.conversation__content__relative-time,
+/* settings page */
+.dashboard__counters__label,
+.log-entry__timestamp,
+/* public pages */
+.landing-page__short-description h1 small,
+ .landing-page__short-description h1 small span,
+.simple_form p.hint.subtle-hint,
+.public-layout .public-account-bio .roles,
+ .public-layout .public-account-bio__extra,
+ .public-layout .public-account-header__tabs__tabs .counter,
+.load-more,
+.account__disclaimer {
+ color: var(--textMuted);
+}
+
+/* fix announcement reactions */
+.reactions-bar__item__count {
+ color: inherit;
+}
+
+/* fix for conversations font weight */
+.conversation--unread .conversation__content__info {
+ font-weight: 400;
+}
+
+/* fix for border colors */
+.account--panel,
+.account__header__bar,
+.account__header__bio .account__header__fields,
+.status,
+.detailed-status__action-bar,
+.public-layout .public-account-header__tabs__tabs .counter,
+.account__header__fields,
+.account__header__fields dl,
+.account__header__bio .account__header__fields,
+.account,
+.directory__card__extra .account__header__content,
+.account__section-headline,
+.notification__filter-bar,
+.actions-modal .dropdown-menu__separator,
+.actions-modal .status {
+ border-color: var(--textMuted);
+}
+
+.account__header__account-note,
+.announcements-list {
+ border-color: transparent;
+}
+
+.account__section-headline a.active::after,
+.account__section-headline button.active::after,
+.notification__filter-bar a.active::after,
+.notification__filter-bar button.active::after {
+ border-color: transparent transparent var(--bg);
+}
+
+.notification {
+ background: var(--bgPage);
+}
+
+/*settings page administration section text color*/
+.dashboard__widgets__users a.name-tag {
+ color: var(--text);
+}
+.dashboard__widgets a:not(.name-tag) {
+ color: var(--text) !important;
+}
+
+.account-card .account-card__bio {
+ color: var(--textHead) !important;
+}
+
+/*fixing direct messages*/
+.status__content,
+.conversation__content__names {
+ color: var(--text);
+}
\ No newline at end of file
diff --git a/app/javascript/styles/mfc/2-background.css b/app/javascript/styles/mfc/2-background.css
new file mode 100644
index 00000000000000..a15bff29a929f7
--- /dev/null
+++ b/app/javascript/styles/mfc/2-background.css
@@ -0,0 +1,232 @@
+/*------------------------------------------------------------------------------
+* BACKGROUND COLOR PALETTE =====================================================
+------------------------------------------------------------------------------*/
+
+/*----------------------
+base background and text
+----------------------*/
+
+/* background and drawer */
+body.app-body,
+.announcements,
+/*.ui,*/
+.drawer__tab,
+.drawer__inner,
+.drawer__inner.darker,
+.drawer__inner__mastodon,
+.tabs-bar,
+/*.tabs-bar__wrapper,*/
+.getting-started,
+.search-results__section h5,
+.account__section-headline,
+ .account__section-headline button,
+.notification__filter-bar,
+ .notification__filter-bar button,
+.timeline-hint,
+.introduction,
+.account__header__image,
+.account__header__account-note textarea:focus,
+.flex-spacer, .getting-started, .getting-started__wrapper, .getting-started__footer,
+/* DMs */
+.status.status-direct,
+ .status.status-direct:not(.read),
+ .focusable:focus .status.status-direct,
+ .status-direct .status__content,
+ .notification-favourite .status.status-direct,
+/* column preferences */
+/*.column-settings__section,*/
+ .column-header__collapsible,
+ .column-header__collapsible-inner,
+/* .column-header__button.active,*/
+ .setting-meta__label,
+/* .setting-toggle__label,*/
+.column-subheading,
+.content-wrapper,
+.media-spoiler,
+ .video-player__spoiler,
+ .video-player__spoiler.active:active,
+ .video-player__spoiler.active:focus,
+.react-toggle-track,
+.filter-form,
+/* in reply to */
+.reply-indicator,
+.reply-indicator__display-name,
+.reply-indicator__content,
+.reply-indicator__cancel .icon-button.inverted,
+.reply-indicator__content .status__content__spoiler-link,
+/* cw show more */
+.status__content .status__content__spoiler-link,
+.compose__action-bar .icon-button,
+/* settings page */
+.admin-wrapper .sidebar-wrapper,
+ .admin-wrapper .sidebar-wrapper__inner,
+ .admin-wrapper .sidebar ul a,
+ .admin-wrapper .content .directory__tag h4,
+/* .admin-wrapper .sidebar ul a.selected,*/
+ .admin-wrapper .sidebar ul ul a,
+ .admin-wrapper .content h2,
+ .admin-wrapper .content h6,
+ .admin-wrapper .content h3,
+/* modals */
+.boost-modal__action-bar,
+ .confirmation-modal__action-bar,
+ .mute-modal__action-bar,
+.confirmation-modal__action-bar .confirmation-modal__cancel-button,
+ .confirmation-modal__action-bar .mute-modal__cancel-button,
+ .mute-modal__action-bar .confirmation-modal__cancel-button,
+ .mute-modal__action-bar .mute-modal__cancel-button,
+.error-column,
+ .regeneration-indicator,
+ .empty-column-indicator,
+.report-modal__comment .setting-text,
+.introduction__text p code,
+.list-editor .search__input,
+.announcements__item,
+/* opengraph previews */
+.status-card__image,
+.status-card__content,
+.status-card__description,
+.button:disabled,
+/* public pages */
+body.theme-default,
+.button.button-alternative,
+ .button.button-alternative-2,
+.landing-page__call-to-action,
+.public-layout .header,
+.public-layout .header .nav-link,
+.public-layout .header .nav-button,
+.nothing-here,
+/*.brand__tagline,*/
+.table-of-contents {
+ background: var(--bgPage);
+ color: var(--textPage);
+}
+
+/*notification section*/
+.setting-toggle__label,
+.column-settings__section {
+ color: var(--textPage);
+}
+
+/*--------------------
+override for bold text
+--------------------*/
+
+/* strong elements */
+.navigation-bar strong,
+.status-card__title,
+.status-direct .display-name strong,
+.reply-indicator__display-name strong,
+.admin-wrapper .content>p strong,
+.simple_form strong,
+.regeneration-indicator__label strong,
+.account__section-headline a.active,
+ .account__section-headline button.active,
+ .notification__filter-bar a.active,
+ .notification__filter-bar button.active,
+/* public pages */
+.information-board__section,
+ .information-board__section span:last-child,
+.endorsements-widget .display-name__html,
+ .endorsements-widget h4,
+.pagination .page,
+ .pagination .gap,
+ .pagination .newer,
+ .pagination .older,
+ .pagination a {
+ color: var(--textPageBold);
+}
+
+/*---------------------
+override for muted text
+---------------------*/
+
+/* de-emphasized elements */
+.navigation-bar,
+.navigation-panel hr,
+.getting-started,
+ .getting-started p,
+ .getting-started__footer p,
+.column-subheading,
+.account__section-headline a,
+.status-direct .icon-button,
+ .status-direct .display-name,
+ .status-direct .status__relative-time,
+ .status-direct .status__action-bar__counter__label,
+.status-direct.muted .status__content p,
+ .status-direct.muted .status__content a,
+ .status-direct.muted .display-name strong,
+.notification-favourite .status.status-direct .icon-button.disabled,
+.simple_form p.hint,
+ .simple_form span.hint,
+ .admin-wrapper .content .muted-hint,
+ .admin-wrapper .content>p,
+.status-card__host,
+.button:disabled,
+.loading-indicator,
+.list-editor .search__input::placeholder,
+ .list-editor .search__icon .fa,
+ .list-editor .search__icon .fa-times-circle,
+/* settings page */
+body .neutral-hint, .admin-wrapper .content .neutral-hint,
+/* public pages */
+.endorsements-widget .display-name__account,
+.public-layout .footer h4,
+ .public-layout .footer ul a,
+ .public-layout .footer ul li,
+ .public-layout .footer .grid .column-2 h4 a,
+ .public-layout .header .nav-button,
+/* log in, sign up, forgot passwd */
+.form-footer a,
+.lighter .simple_form p.hint.subtle-hint {
+ color: var(--textPageMuted);
+}
+
+/* border color fix */
+.status.status-direct:not(.read),
+.table-of-contents li a {
+ border-color: var(--textPageMuted);
+}
+
+/*table of contents color*/
+.table-of-contents li a {
+ color: var(--textPage) !important;
+}
+
+/*nav bar background*/
+.navigation-bar {
+ background: var(--bgPage);
+}
+
+/*transparent background for columns*/
+.columns-area__panels__pane__inner,
+.columns-area__panels__main,
+.column,
+.drawer {
+ background: var(--bgPageTransparent);
+}
+.tabs-bar__wrapper {
+ background: rgba(0, 0, 0, 0);
+}
+
+/*filter button color when focused*/
+.column-header__button.active {
+ background: var(--bgPage);
+}
+
+/*font color at setting page*/
+label.optional,
+label.required,
+.label_input ul li label,
+.optional ul li label,
+.user_setting_display_media ul label {
+ color: var(--textPage) !important;
+}
+
+.table-wrapper .table {
+ border-radius: 4px;
+}
+
+.activity-stream {
+ background: var(--bg);
+}
\ No newline at end of file
diff --git a/app/javascript/styles/mfc/3-highlights.css b/app/javascript/styles/mfc/3-highlights.css
new file mode 100644
index 00000000000000..f46dab6bf539dd
--- /dev/null
+++ b/app/javascript/styles/mfc/3-highlights.css
@@ -0,0 +1,243 @@
+/*------------------------------------------------------------------------------
+* HIGHLIGHTS COLOR PALETTE =====================================================
+------------------------------------------------------------------------------*/
+
+/* scrollbar fix */
+html {
+ scrollbar-color: var(--bg) var(--bgPage);
+}
+::-webkit-scrollbar-thumb {
+ background: var(--bg);
+}
+::-webkit-scrollbar-thumb:hover {
+ background: var(--accent);
+}
+::-webkit-scrollbar-track:hover {
+ background: rgba(0, 0, 0, 0.2);
+}
+
+/*------------------
+headers and warnings
+------------------*/
+
+/* columns view */
+.column-header,
+ .column-header__button,
+ .column-header__back-button,
+ .column-header__button:hover,
+ .column-header__back-button:hover,
+ .column-back-button,
+ .column-header>.column-header__back-button,
+ .column-header.active .column-header__icon,
+ .search-results__header,
+ .keyboard-shortcuts kbd,
+ .compose-form__warning,
+ .compose-form .compose-form__warning,
+ .react-toggle:hover:not(.react-toggle--disabled) .react-toggle-track,
+ .column-link__badge,
+ .column-link--transparent.active,
+/* settings pages */
+/* .list-editor h4,
+ .admin-wrapper .content h4,*/
+ .admin-wrapper .sidebar ul ul a.selected,
+ .flash-message,
+ .flash-message.notice,
+ .flash-message.translation-prompt,
+ .column-inline-form,
+ .column-inline-form .icon-button,
+ .simple_form .warning,
+ .table-form .warning,
+ .pagination .current,
+ .account-role,
+ .batch-table__toolbar {
+ background: var(--bgHead);
+ color: var(--textHead);
+}
+
+/*--------------
+accented buttons
+--------------*/
+
+/* primary buttons */
+.directory__tag:hover, .directory__tag:active, .directory__tag:focus,
+ .button,
+ .button:active,
+ .button:focus,
+ .button:hover,
+ .icon-button.overlayed:hover,
+ .floating-action-button,
+ .simple_form button,
+ .simple_form button:active,
+ .simple_form button:focus,
+ .simple_form button:hover,
+ .simple_form .button,
+ .simple_form .button:active,
+ .simple_form .button:focus,
+ .simple_form .button:hover,
+ .simple_form .block-button,
+ .simple_form .block-button:active,
+ .simple_form .block-button:focus,
+ .simple_form .block-button:hover,
+ .button.button-alternative:hover,
+ .button.button-alternative-2:hover,
+ .column-link:hover,
+ .column-link:active,
+ .column-link:focus,
+ .getting-started a.column-link:hover,
+ .column-header__button:hover,
+ .column-header__button.active:hover,
+ .column-header__back-button:hover,
+ .column-back-button:hover,
+ .drawer__header a:hover,
+ .react-toggle--checked .react-toggle-track,
+ .react-toggle--checked:hover:not(.react-toggle--disabled) .react-toggle-track,
+ .privacy-dropdown.active .privacy-dropdown__value.active,
+ .privacy-dropdown__option:hover,
+ .privacy-dropdown__option.active,
+ .privacy-dropdown__option.active:hover,
+ .admin-wrapper .sidebar ul a:hover,
+ .admin-wrapper .sidebar ul ul a.selected:hover,
+ .reply-indicator__content .status__content__spoiler-link:hover,
+ .status__content .status__content__spoiler-link:hover,
+ .load-more:hover,
+ .conversation__unread,
+/* modals */
+ .confirmation-modal__action-bar .confirmation-modal__cancel-button:active,
+ .confirmation-modal__action-bar .confirmation-modal__cancel-button:focus,
+ .confirmation-modal__action-bar .confirmation-modal__cancel-button:hover,
+ .confirmation-modal__action-bar .mute-modal__cancel-button:active,
+ .confirmation-modal__action-bar .mute-modal__cancel-button:focus,
+ .confirmation-modal__action-bar .mute-modal__cancel-button:hover,
+ .dropdown-menu__item a:active, .dropdown-menu__item a:focus, .dropdown-menu__item a:hover,
+ .mute-modal__action-bar .confirmation-modal__cancel-button:active,
+ .mute-modal__action-bar .confirmation-modal__cancel-button:focus,
+ .mute-modal__action-bar .confirmation-modal__cancel-button:hover,
+ .mute-modal__action-bar .mute-modal__cancel-button:active,
+ .mute-modal__action-bar .mute-modal__cancel-button:focus,
+ .mute-modal__action-bar .mute-modal__cancel-button:hover,
+ .upload-progress__tracker,
+ .radio-button__input.checked,
+/* public pages */
+ .public-layout .header .nav-button:hover,
+ .public-layout .header .brand:active,
+ .public-layout .header .brand:focus,
+ .public-layout .header .brand:hover,
+ .button.button-secondary:hover,
+/* settings pages */
+ .pagination .page.current:hover,
+ .introduction__dot.active,
+ .dashboard__counters > div > a:hover,
+ .dashboard__counters > div > a:focus,
+ .dashboard__counters > div > a:active,
+ .dashboard__counters > div > a:hover .dashboard__counters__label,
+ .dashboard__counters > div > a:focus .dashboard__counters__label,
+ .dashboard__counters > div > a:active .dashboard__counters__label,
+ .admin-wrapper .sidebar ul a.selected,
+/* video player ui */
+ .video-player__seek__buffer,
+ .video-player__seek__progress,
+ .video-player__volume__current,
+ .video-player__volume__handle,
+/* announcements */
+ .icon-with-badge__badge,
+ .announcements__item__unread,
+ .reactions-bar__item:hover {
+ background: var(--accent);
+ color: var(--accentText);
+}
+
+/*------------
+accented links
+------------*/
+
+/* status links */
+.status__content a,
+ .status__content a.unhandled-link,
+ .getting-started a,
+ .getting-started p a,
+ .getting-started__footer a,
+ .getting-started__footer p a,
+ .reply-indicator__content a,
+ .reply-indicator__content a.unhandled-link,
+ .reply-indicator__cancel .icon-button.inverted:hover,
+ .status__content__read-more-button,
+ .icon-button.active,
+ .icon-button:focus,
+ .icon-button:hover,
+ .search__icon .fa:hover,
+ .account__header__bio .account__header__fields a,
+ .notification-follow .account .icon-button:hover,
+ .notification__message .fa,
+ .notification__display-name:hover,
+ .admin-wrapper .content .muted-hint a,
+ .table a,
+ a.table-action-link:hover,
+ button.table-action-link:hover,
+ .media-spoiler:active,
+ .media-spoiler:focus,
+ .media-spoiler:hover,
+ .video-player__spoiler.active:hover,
+ .column-header__setting-btn:hover,
+ .column-inline-form .icon-button:hover,
+ .empty-column-indicator a, .error-column a,
+ .timeline-hint a,
+/* post options */
+ .compose-form .text-icon-button:hover,
+ .text-icon-button.active,
+ .drawer__inner .icon-button:hover,
+ .icon-button.inverted.active.disabled,
+ .navigation-bar__profile-edit:hover,
+ .navigation-bar__profile-account:hover,
+ .account__action-bar-dropdown .icon-button:hover,
+ .account__section-headline a:hover,
+ .compose-form .compose-form__warning a,
+/* modals */
+ .list-editor .account .icon-button:hover,
+ .list-editor .account .icon-button:active,
+ .list-editor .account .icon-button:focus,
+/* public pages */
+ .simple_form .input.boolean label a,
+ .landing-page__short-description p a,
+ .landing-page #mastodon-timeline p a,
+ .simple_form p.hint.subtle-hint a,
+ .contact-widget__mail a,
+ .public-layout .footer ul a:hover,
+ .public-layout .footer .grid .column-2 h4 a:hover,
+ .public-layout .public-account-bio .account__header__fields a,
+ .form-footer a:hover,
+/* settings pages */
+ .pagination a:hover,
+ .pagination .newer:hover,
+ .pagination .older:hover,
+ .filters .filter-subset a.selected,
+ .simple_form .hint a,
+/* mobile elements */
+ .tabs-bar__link.active {
+ color: var(--accent);
+}
+
+/* border fix */
+.react-toggle--checked .react-toggle-thumb,
+.radio-button__input.checked,
+.icon-with-badge__badge,
+.filters .filter-subset a.selected,
+.introduction__dot,
+.public-layout .public-account-header__tabs__tabs .counter.active::after,
+.notification.unread::before,
+.status__wrapper.unread::before {
+ border-color: var(--accent);
+}
+
+/* trends fix */
+.trends__item__sparkline path:first-child {
+ fill: transparent !important;
+}
+.trends__item__sparkline path:last-child {
+ stroke: var(--accent) !important;
+}
+
+/* focus fix */
+.focusable:focus {
+ background: transparent;
+ border: 2px solid var(--accent);
+}
diff --git a/app/javascript/styles/mfc/5-material.css b/app/javascript/styles/mfc/5-material.css
new file mode 100644
index 00000000000000..48142b4d52e998
--- /dev/null
+++ b/app/javascript/styles/mfc/5-material.css
@@ -0,0 +1,72 @@
+/*-----------------------
+material design overrides
+-----------------------*/
+
+/* turn statuses into cards */
+.drawer__inner darker {filter: drop-shadow(0 0 2px black)}
+.status,
+.notification,
+.conversation,
+.account-authorize__wrapper,
+.follow_requests-unlocked_explanation
+{
+ box-shadow: 0px 0px 2px black;
+ background: var(--bg);
+ margin: 8px;
+ border-radius: 2px;
+ border: 0
+}
+.status__wrapper--filtered {border: none}
+.detailed-status__wrapper {margin: 8px;} /*might look weird in older versions pre-2.6?*/
+
+/* recolors */
+.column>.scrollable,
+.landing-page #mastodon-timeline,
+.activity-stream .entry
+ {background: none !important}
+.account-timeline__header,
+.account,
+.conversation
+ {background: var(--bg)}
+.load-more
+ {background: var(--bgPage)}
+.status__prepend,
+.notification__message,
+.status__prepend .status__display-name strong,
+.keyboard-shortcuts
+ {color: var(--textPage) !important}
+.notification-follow .display-name__html
+ {color: var(--textPageBold)}
+.notification-follow .display-name__account,
+.notification-follow .account .icon-button,
+.status__wrapper--filtered,
+.load-more
+ {color: var(--textPageMuted)}
+
+/* borders */
+.account__section-headline, .notification__filter-bar,
+.account--panel, .account__header__bar, .account__header__bio .account__header__fields, .status, .detailed-status__action-bar, .account__header__fields dl, .account__header__bio .account__header__fields, .account, .directory__card__extra .account__header__content, .account__section-headline, .notification__filter-bar,
+.conversation,
+.account__header__fields,
+.account__header__account-note,
+.public-layout .public-account-header__tabs__tabs .counter
+
+{border-color: transparent}
+
+/* triangle tab indicator */
+.account__section-headline a.active:after,
+.account__section-headline a.active:before,
+.community-timeline__section-headline a.active:after,
+.community-timeline__section-headline a.active:before,
+.public-timeline__section-headline a.active:after,
+.public-timeline__section-headline a.active:before,
+.notification__filter-bar button.active::before,
+.notification__filter-bar button.active::after
+{
+ border-color: transparent transparent var(--bgHead)
+}
+
+/* fix rounding on end toots in stream */
+.activity-stream .entry:first-child .status,
+.activity-stream .entry:last-child .status
+{border-radius: 2px}
diff --git a/app/javascript/styles/mfc/6-actions.css b/app/javascript/styles/mfc/6-actions.css
new file mode 100644
index 00000000000000..71973993e7099c
--- /dev/null
+++ b/app/javascript/styles/mfc/6-actions.css
@@ -0,0 +1,120 @@
+/*------------------------------------------------------------------------------
+* RECOLOR STATUS ACTIONS
+*
+* This tweak gets its own section because it's pretty messy and long.
+*
+* Replies, follows, faves, dropdown menu, and share can easily be themed,
+* but I can't figure out how to make them look good against the palette.
+* Manual color selection may be required, if these colors don't fit well.
+*
+* Recoloring boosts is another nightmare altogether, because of improper
+* SVG embedding in the background-image rather than directly in HTML.
+* This leads to two options:
+*
+* 1) attempt to use filter() to manually add sepia tones and hue-shift
+* - complicated and imprecise adjustments of filter()
+* - cannot use CSS variables in url()s (as in background-image)
+* + however, it does preserve the SVG boosting animation
+*
+* 2) replace the background-image with a mask-image
+* + less complicated; override background-image with color
+* + can apply CSS variable colors from palette easily
+* - requires extremely long rule to add mask-image
+* - breaks boosting animation
+*
+* I have chosen option 2.
+*
+* EDIT: Option 1 is now better because of the new boost icons
+* introduced in 3.2.0 -- there is no longer just one icon.
+------------------------------------------------------------------------------*/
+
+/* add shadow on hover and active states */
+.status__action-bar .icon-button:hover,
+ .status__action-bar .icon-button:active,
+ .status__action-bar .icon-button.active,
+.detailed-status__action-bar .icon-button:hover,
+ .detailed-status__action-bar .icon-button:active,
+ .detailed-status__action-bar .icon-button.active
+{filter: drop-shadow(0px 1px 0px rgba(0,0,0,0.6))}
+
+/* remove bg color on hover and active states*/
+.icon-button:active, .icon-button:focus, .icon-button:hover
+{background-color: transparent}
+
+/* replies and follows */
+.status__action-bar-button.icon-button:nth-child(1):hover,
+.status__action-bar-button.icon-button:nth-child(1):active,
+.status__action-bar-button.icon-button:nth-child(1).active,
+.status__action-bar-button.icon-button:nth-child(1):focus,
+.detailed-status__action-bar .detailed-status__button:nth-child(1) .icon-button:hover,
+.detailed-status__action-bar .detailed-status__button:nth-child(1) .icon-button:active,
+.detailed-status__action-bar .detailed-status__button:nth-child(1) .icon-button.active,
+.detailed-status__action-bar .detailed-status__button:nth-child(1) .icon-button:focus,
+.notification__message .fa.fa-user-plus,
+.account .icon-button:active,
+.account .icon-button.active,
+.account .icon-button:focus,
+.account .icon-button:hover
+{color: #0bf;}
+
+/* favourites */
+.status__action-bar-button.icon-button:nth-child(3):hover,
+.status__action-bar-button.icon-button:nth-child(3):active,
+.status__action-bar-button.icon-button:nth-child(3).active,
+.status__action-bar-button.icon-button:nth-child(3):focus,
+.detailed-status__action-bar .detailed-status__button:nth-child(3) .icon-button:hover,
+.detailed-status__action-bar .detailed-status__button:nth-child(3) .icon-button:active,
+.detailed-status__action-bar .detailed-status__button:nth-child(3) .icon-button.active,
+
+.notification__favourite-icon-wrapper .star-icon
+{color: #f90;}
+
+/* menu */
+.status__action-bar-dropdown .icon-button:hover,
+.status__action-bar-dropdown .icon-button:active,
+.status__action-bar-dropdown .icon-button.active,
+.status__action-bar-dropdown .icon-button.focus,
+.detailed-status__action-bar-dropdown .icon-button:hover,
+.detailed-status__action-bar-dropdown .icon-button:active,
+.detailed-status__action-bar-dropdown .icon-button.active,
+.detailed-status__action-bar-dropdown .icon-button:focus
+{color: #90f;}
+
+/* share */
+.icon-button:hover .fa-share-alt
+{color: #f09;}
+
+/* boosts */
+.notification__message .fa.fa-retweet,
+.icon-button:hover .fa-retweet
+{color: #0d9;}
+
+/* recolor boosts (preserve animation, unthemeable default state */
+
+button.icon-button:hover i.fa-retweet,
+.no-reduce-motion button.icon-button.active i.fa-retweet
+{
+ filter: sepia(100%)
+ hue-rotate(110deg)
+ saturate(700%)
+ brightness(100%)
+ drop-shadow(0px 1px 0px rgba(0,0,0,0.6))
+}
+
+
+/* fully recolor boosts (while breaking animation) */
+/*
+button.icon-button:hover i.fa-retweet,
+button.icon-button.active i.fa-retweet
+{background: #0d9 !important;}
+
+.no-reduce-motion button.icon-button i.fa-retweet,
+ button.icon-button i.fa-retweet,
+ button.icon-button:hover i.fa-retweet,
+ button.icon-button.active i.fa-retweet
+{
+background: var(--textMuted);
+mask: url("data:image/svg+xml;utf8, ");
+-webkit-mask-image: url("data:image/svg+xml;utf8, ");
+}
+*/
diff --git a/app/javascript/styles/mfc/mastodonFlat.scss b/app/javascript/styles/mfc/mastodonFlat.scss
new file mode 100644
index 00000000000000..47d07e9dfd375c
--- /dev/null
+++ b/app/javascript/styles/mfc/mastodonFlat.scss
@@ -0,0 +1,6 @@
+@import '0-palette';
+@import '1-foreground';
+@import '2-background';
+@import '3-highlights';
+@import '5-material';
+@import '6-actions';
diff --git a/app/javascript/styles/mfc/variables.scss b/app/javascript/styles/mfc/variables.scss
new file mode 100644
index 00000000000000..42fbcb4884cf88
--- /dev/null
+++ b/app/javascript/styles/mfc/variables.scss
@@ -0,0 +1,11 @@
+/* define scss variables in palette.scss of subtheme */
+:root {
+--bgPage: $bgPage;
+--bg: $bg;
+--bgHead: $bgHead;
+--text: $text;
+--textBold: $textBold;
+--textMuted: $textMuted;
+--textHead: $textHead;
+--accent: $accent;
+}
\ No newline at end of file
diff --git a/app/javascript/styles/mods/deprecated/display_bettersearch.css b/app/javascript/styles/mods/deprecated/display_bettersearch.css
new file mode 100644
index 00000000000000..c9bf8850fbb2e9
--- /dev/null
+++ b/app/javascript/styles/mods/deprecated/display_bettersearch.css
@@ -0,0 +1,10 @@
+/*
+Make search results look better:
+- adds contrast to search icon
+- overlay-style shadowed background
+
+author: trwnh
+license: Public Domain
+*/
+.search__icon .fa.active {opacity: 1}
+.drawer__inner.darker {background: rgba(0,0,0,0.5)}
diff --git a/app/javascript/styles/mods/display_breakname.css b/app/javascript/styles/mods/display_breakname.css
new file mode 100644
index 00000000000000..fc3936e235e30b
--- /dev/null
+++ b/app/javascript/styles/mods/display_breakname.css
@@ -0,0 +1,9 @@
+/*
+Add a line break between display name and account handle:
+- this allows user/display names to expand more by default.
+- it also makes names look better in general.
+
+author: trwnh
+license: Public Domain
+*/
+.display-name__html {display: block;}
\ No newline at end of file
diff --git a/app/javascript/styles/mods/display_browserfont.css b/app/javascript/styles/mods/display_browserfont.css
new file mode 100644
index 00000000000000..6389f20b4e3849
--- /dev/null
+++ b/app/javascript/styles/mods/display_browserfont.css
@@ -0,0 +1,20 @@
+/*
+Use browser default font:
+- override mastodon-font-sans-serif with sans-serif
+- note: this is not the same as "use system default font"
+ in mastodon's preferences! that option uses a font that
+ would be *expected to load on that system*, and ignores
+ your browser's settings entirely. for example, if you
+ are running ms windows, you will see segoe ui, even if
+ your browser's default font is something else!
+
+author: trwnh
+license: Public Domain
+*/
+body,
+.landing-page #mastodon-timeline,
+.landing-page li,
+.landing-page p
+{
+ font-family: sans-serif
+}
\ No newline at end of file
diff --git a/app/javascript/styles/mods/display_circleavatar.css b/app/javascript/styles/mods/display_circleavatar.css
new file mode 100644
index 00000000000000..459108d0729de4
--- /dev/null
+++ b/app/javascript/styles/mods/display_circleavatar.css
@@ -0,0 +1,15 @@
+/*
+* Rounded avatars:
+* - adjust the border radius around all avatar elements.
+* - default override is 50% (i.e. turn squares into circles),
+* but you can set it to whatever you want.
+*
+* author: trwnh
+* license: Public Domain
+*/
+.card .avatar img,
+.activity-stream .status.light .status__avatar img,
+.account__avatar,
+ .account__avatar-overlay-base,
+ .account__avatar-overlay-overlay
+{border-radius: 50%}
\ No newline at end of file
diff --git a/app/javascript/styles/mods/display_collapsedinteractions.css b/app/javascript/styles/mods/display_collapsedinteractions.css
new file mode 100644
index 00000000000000..7cb1f7c6aa6b30
--- /dev/null
+++ b/app/javascript/styles/mods/display_collapsedinteractions.css
@@ -0,0 +1,37 @@
+/*
+Collapse fave/boost/poll notifications
+- limits display to just a few lines (~3), so you can at least identify it
+- hides the display name on fave/boost, because you already know you posted it
+- tighter margins, remove space between CW and content
+- hides the buttons, but you can expand a status to interact with it
+
+author: trwnh
+license: Public Domain
+*/
+
+.notification-favourite .status,
+.notification-reblog .status,
+.notification-poll .status{
+ max-height: 4em;
+ overflow: hidden;
+}
+
+.notification-favourite .display-name,
+.notification-reblog .display-name {
+ display: none;
+}
+
+.notification-favourite .status__content,
+.notification-reblog .status__content {
+ margin-top: -4px;
+}
+
+.notification-favourite .status__content p,
+.notification-reblog .status__content p {
+ margin-bottom: 0px;
+}
+
+.notification-favourite .status__action-bar,
+.notification-reblog .status__action-bar {
+ display: none;
+}
diff --git a/app/javascript/styles/mods/display_emojizoom.css b/app/javascript/styles/mods/display_emojizoom.css
new file mode 100644
index 00000000000000..32ba536bb3cb4e
--- /dev/null
+++ b/app/javascript/styles/mods/display_emojizoom.css
@@ -0,0 +1,23 @@
+ /*
+ Emoji hover zoom:
+ - makes emoji grow in size when moused over
+
+ author: noiob
+ license: CC0 - Public Domain
+ source: https://userstyles.org/styles/150165
+ */
+
+ .emojione:hover
+ {
+ width: 50px !important;
+ /* set the width and height of the expanded emojo here */
+ height: 50px !important;
+ transition: all 0.3s ease-in-out !important;
+ /* the 0.3s is the animation time for growing the emojo, it can be set to 0 */;
+ }
+
+ .emojione
+ {
+ transition: all 0.2s ease-in-out;
+ /* the 0.2s is the animation time for shrinking the emojo, it can be set to 0 */;
+ }
\ No newline at end of file
diff --git a/app/javascript/styles/mods/display_fadedinteractions.css b/app/javascript/styles/mods/display_fadedinteractions.css
new file mode 100644
index 00000000000000..8945d2699e896f
--- /dev/null
+++ b/app/javascript/styles/mods/display_fadedinteractions.css
@@ -0,0 +1,9 @@
+/*
+Fade out faved/boosted toots in notifications:
+- for "x favourited your toot" / "x boosted your toot",
+ make the faved/boosted toot half-transparent.
+
+author: trwnh
+license: Public Domain
+*/
+.status.muted {opacity: 0.5}
\ No newline at end of file
diff --git a/app/javascript/styles/mods/display_fullmedia.css b/app/javascript/styles/mods/display_fullmedia.css
new file mode 100644
index 00000000000000..04cdbf57420e5e
--- /dev/null
+++ b/app/javascript/styles/mods/display_fullmedia.css
@@ -0,0 +1,31 @@
+/*
+Full-height media previews:
+- normal media previews are forced to be 16:9 for consistency
+- use this if you prefer to see the aspect ratio unchanged
+
+author: Kevin
+license: CC0 - Public Domain
+source: https://userstyles.org/styles/167207 [in part]
+*/
+
+.media-gallery {
+ max-height: 100% !important;
+ height: 100% !important;
+}
+
+.media-gallery__item-gifv-thumbnail, .media-gallery__item-gifv-thumbnail img {
+ transform: translateY(0%) !important;
+ max-height: 100% !important;
+}
+
+.media-gallery__item-thumbnail, .media-gallery__item-thumbnail img, .media-gallery__gifv {
+ max-height: 100% !important;
+}
+
+.media-gallery__item {
+ width: 100% !important;
+ height: 100% !important;
+ max-height: 100% !important;
+ inset: 0 !important;
+ margin-bottom: 4px;
+}
diff --git a/app/javascript/styles/mods/display_fullname.css b/app/javascript/styles/mods/display_fullname.css
new file mode 100644
index 00000000000000..1f2e541af881df
--- /dev/null
+++ b/app/javascript/styles/mods/display_fullname.css
@@ -0,0 +1,11 @@
+/*
+Always show full name and handle:
+- this removes the `...` and allows text to overflow past the column.
+- this can look worse, but it can also prevent having to mouse over
+ to see the full name or handle.
+- by default, it will also break long names onto a new line.
+
+author: trwnh
+license: Public Domain
+*/
+.display-name {overflow: visible; white-space: normal; word-wrap: break-word}
\ No newline at end of file
diff --git a/app/javascript/styles/mods/display_hidefollowcounts.css b/app/javascript/styles/mods/display_hidefollowcounts.css
new file mode 100644
index 00000000000000..e9ac9ed563d54a
--- /dev/null
+++ b/app/javascript/styles/mods/display_hidefollowcounts.css
@@ -0,0 +1,10 @@
+/*
+Hide the following and follower counters on profiles.
+- full counts are still available by hovering over the text, though
+author: trwnh
+license: Public Domain
+*/
+.account__header__extra__links a:not(:first-child) strong
+{display: none}
+.details-counters .counter:not(:first-child) .counter-number
+{visibility: hidden}
diff --git a/app/javascript/styles/mods/display_hidereplycounts.css b/app/javascript/styles/mods/display_hidereplycounts.css
new file mode 100644
index 00000000000000..513251cde3f914
--- /dev/null
+++ b/app/javascript/styles/mods/display_hidereplycounts.css
@@ -0,0 +1,7 @@
+/*
+Hide the 0/1/1+ counters of replies.
+
+author: trwnh
+license: Public Domain
+*/
+.status__action-bar__counter__label {display: none}
\ No newline at end of file
diff --git a/app/javascript/styles/mods/display_starstohearts.css b/app/javascript/styles/mods/display_starstohearts.css
new file mode 100644
index 00000000000000..53efb55764a96e
--- /dev/null
+++ b/app/javascript/styles/mods/display_starstohearts.css
@@ -0,0 +1,16 @@
+/*
+Turn stars into hearts:
+- similar to twitter's change
+
+author: numimyon
+license: CC0 - Public Domain
+source: https://userstyles.org/styles/151233
+*/
+
+.notification__favourite-icon-wrapper .star-icon,
+.star-icon.active,
+.star-icon:hover,
+.star-icon:active
+{color: crimson !important;}
+
+.fa-star:before {content: "";}
diff --git a/app/javascript/styles/mods/display_transparentmedia.css b/app/javascript/styles/mods/display_transparentmedia.css
new file mode 100644
index 00000000000000..afa18a18a9fa66
--- /dev/null
+++ b/app/javascript/styles/mods/display_transparentmedia.css
@@ -0,0 +1,10 @@
+/*
+Remove the checker-board background from the media modal:
+- this makes transparent images actually transparent
+
+author: trwnh
+license: Public Domain
+*/
+.media-modal canvas,
+.media-modal img
+{background: none}
\ No newline at end of file
diff --git a/app/javascript/styles/mods/fixes.css b/app/javascript/styles/mods/fixes.css
new file mode 100644
index 00000000000000..b88031e02db412
--- /dev/null
+++ b/app/javascript/styles/mods/fixes.css
@@ -0,0 +1,32 @@
+.admin-wrapper .sidebar ul a {
+ border-radius: 0px !important;
+}
+
+.admin-wrapper .content .directory__tag h4 {
+ background: none;
+}
+
+.notification-favourite .status {
+ max-height: none !important;
+}
+
+.notification {
+ padding-bottom: 8px;
+}
+
+.batch-table__row:hover {
+ background: var(--bg);
+}
+
+.input-copy {
+ background: none;
+ border: none;
+}
+
+.directory__tag>a * {
+ background: none !important;
+}
+
+.getting-started__trends h4 {
+ color: var(--textPage) !important;
+}
\ No newline at end of file
diff --git a/app/javascript/styles/mods/layout_1600px.css b/app/javascript/styles/mods/layout_1600px.css
new file mode 100644
index 00000000000000..f81af0046847e1
--- /dev/null
+++ b/app/javascript/styles/mods/layout_1600px.css
@@ -0,0 +1,12 @@
+/*
+Allow for wider layout on bigger screens
+- vanilla max-width is 1200px
+- there is no penalty to slightly expanding flexbox on bigger screens
+- only applies on landing pages (webapp will expand as you add columns)
+
+author: trwnh
+license: Public Domain
+*/
+@media (min-width: 1600px) {
+ .landing-page .container {max-width: 1600px}
+}
\ No newline at end of file
diff --git a/app/javascript/styles/mods/layout_elefriend.css b/app/javascript/styles/mods/layout_elefriend.css
new file mode 100644
index 00000000000000..3d79a2cf2cb9f6
--- /dev/null
+++ b/app/javascript/styles/mods/layout_elefriend.css
@@ -0,0 +1,20 @@
+/*
+Release elephant friend from their confines:
+- elephant friend will now hang out in the corner of your browser,
+ instead of being trapped in the drawer.
+
+author: trwnh
+license: Public Domain
+*/
+.drawer__inner, .drawer__inner__mastodon {
+ background: none; z-index: 0
+}
+.drawer__inner__mastodon > img {
+ position: fixed;
+ bottom: 0;
+ left: 0;
+ height: 180px;
+ z-index: -1
+}
+.compose-form {z-index: 1}
+.drawer__inner {height: 100%} /* firefox bug highlights drawer text on click? */
\ No newline at end of file
diff --git a/app/javascript/styles/mods/layout_gettingstartedheight.css b/app/javascript/styles/mods/layout_gettingstartedheight.css
new file mode 100644
index 00000000000000..536a707511888d
--- /dev/null
+++ b/app/javascript/styles/mods/layout_gettingstartedheight.css
@@ -0,0 +1,13 @@
+/*
+Make "getting started" column height consistent with all other columns:
+- puts the footer back at the bottom of the page, instead of floating.
+
+author: trwnh
+license: Public Domain
+*/
+.getting-started {
+ height: 100%;
+ display: flex;
+ flex-flow: column;
+ justify-content: space-between
+}
\ No newline at end of file
diff --git a/app/javascript/styles/mods/layout_hidedisabled.css b/app/javascript/styles/mods/layout_hidedisabled.css
new file mode 100644
index 00000000000000..3196db9b1737db
--- /dev/null
+++ b/app/javascript/styles/mods/layout_hidedisabled.css
@@ -0,0 +1,17 @@
+/*
+Hide buttons that can't be clicked
+- columns on /about and tag pages have buttons that don't work.
+- so, this snippet hides those nonworking buttons to save space
+- and to avoid confusion.
+- unboostable buttons are made transparent on hover instead.
+
+this is fixed in https://github.com/tootsuite/mastodon/pull/10054
+
+author: trwnh
+license: Public Domain
+*/
+.status__action-bar .icon-button.disabled:hover,
+.notification-favourite .status.status-direct .icon-button.disabled:hover
+{color: transparent !important}
+
+#mastodon-timeline .status__action-bar {display: none}
\ No newline at end of file
diff --git a/app/javascript/styles/mods/layout_hidefiltered.css b/app/javascript/styles/mods/layout_hidefiltered.css
new file mode 100644
index 00000000000000..f701e5f53e6568
--- /dev/null
+++ b/app/javascript/styles/mods/layout_hidefiltered.css
@@ -0,0 +1,9 @@
+/*
+Remove the "Filtered" tombstone from timelines.
+- WARNING: this breaks keyboard scrolling with j/k!
+
+author: trwnh
+license: Public Domain
+*/
+
+.status__wrapper--filtered {display: none}
diff --git a/app/javascript/styles/mods/layout_mobile_bottombar.css b/app/javascript/styles/mods/layout_mobile_bottombar.css
new file mode 100644
index 00000000000000..e5ddaf4e740e86
--- /dev/null
+++ b/app/javascript/styles/mods/layout_mobile_bottombar.css
@@ -0,0 +1,29 @@
+/*
+Bottom tabs on mobile:
+- Places the tab bar at the bottom instead of the top.
+- Fixes layout errors that are a result of this change.
+
+author: trwnh
+license: Public Domain
+*/
+@media (max-width: 630px) {
+
+.tabs-bar {
+position: fixed;
+bottom: 0;
+z-index: 1;
+width: 100%;
+margin: 0 !important;
+}
+
+.getting-started {overflow: auto} /* can be removed after PR #10075 is merged */
+
+.columns-area {padding: 0}
+.getting-started__trends, .getting-started__wrapper, .search {margin: 0}
+.columns-area__panels__main, .tabs-bar__wrapper {padding: 0}
+
+.floating-action-button, .column .scrollable > div:last-child {margin-bottom: 50px}
+.react-swipeable-view-container {height: calc(100% - 50px)}
+.react-swipeable-view-container .columns-area {height: 100% !important}
+
+}
diff --git a/app/javascript/styles/mods/layout_singlecolumn.css b/app/javascript/styles/mods/layout_singlecolumn.css
new file mode 100644
index 00000000000000..88bf19ee472891
--- /dev/null
+++ b/app/javascript/styles/mods/layout_singlecolumn.css
@@ -0,0 +1,25 @@
+/*
+Single column layout:
+- re-uses tab bar from mobile layout
+- hides search from drawer (redundant with search tab)
+
+author: trwnh
+license: Public Domain
+*/
+
+@media (min-width: 1024px) {
+ /* place constraints on app layout */
+ .ui {max-width: 960px; max-height: 100vh;}
+ .drawer {width: 300px}
+ .column:last-child, .drawer:last-child
+ {display: flex; flex: 1 1 100%;}
+ /* show tabs bar (from mobile layout) as header */
+ .tabs-bar {display: flex;}
+ /* hide redundant ui elements */
+ .column,
+ .drawer__header,
+ .drawer:first-child .search,
+ .drawer:first-child .search-results
+ {display: none;}
+ .drawer:first-child .drawer__inner.darker {z-index: -1}
+}
\ No newline at end of file
diff --git a/app/javascript/styles/mods/layout_widercolumns.css b/app/javascript/styles/mods/layout_widercolumns.css
new file mode 100644
index 00000000000000..b5d91dc1efe58e
--- /dev/null
+++ b/app/javascript/styles/mods/layout_widercolumns.css
@@ -0,0 +1,10 @@
+/*
+* Wider columns:
+* - Make the multi-column layout use wider columns by default.
+*
+* author: trwnh
+* license: Public Domain
+*/
+@media (min-width: 640px) {
+ .column, #mastodon-timeline {min-width: 60ch;}
+}
\ No newline at end of file
diff --git a/app/javascript/styles/mods/test_colorizedlogo.css b/app/javascript/styles/mods/test_colorizedlogo.css
new file mode 100644
index 00000000000000..634ae5f0c6a9d6
--- /dev/null
+++ b/app/javascript/styles/mods/test_colorizedlogo.css
@@ -0,0 +1,12 @@
+/*
+Colorize logo on landing page:
+- DO NOT IMPORT. It works as standalone CSS, but it makes Sass choke.
+
+author: trwnh
+license: Public Domain
+*/
+
+.landing-page__logo img {
+ filter: sepia(100%) hue-rotate(160deg) saturate(400%) brightness(40%);
+ mix-blend-mode: normal
+}
\ No newline at end of file
diff --git a/app/lib/activitypub/activity/create.rb b/app/lib/activitypub/activity/create.rb
index b03b5a3bded2bf..ec8b33f712e2ff 100644
--- a/app/lib/activitypub/activity/create.rb
+++ b/app/lib/activitypub/activity/create.rb
@@ -177,6 +177,10 @@ def delivered_to_account
@delivered_to_account ||= Account.find(@options[:delivered_to_account_id])
end
+ def delivered_to_account
+ @delivered_to_account ||= Account.find(@options[:delivered_to_account_id])
+ end
+
def attach_tags(status)
@tags.each do |tag|
status.tags << tag
diff --git a/app/lib/formatter.rb b/app/lib/formatter.rb
new file mode 100644
index 00000000000000..6f02d669ac731e
--- /dev/null
+++ b/app/lib/formatter.rb
@@ -0,0 +1,292 @@
+# frozen_string_literal: true
+
+require 'singleton'
+
+class Formatter
+ include Singleton
+ include RoutingHelper
+
+ include ActionView::Helpers::TextHelper
+
+ def format(status, **options)
+ if status.respond_to?(:reblog?) && status.reblog?
+ prepend_reblog = status.reblog.account.acct
+ status = status.proper
+ else
+ prepend_reblog = false
+ end
+
+ raw_content = status.text
+
+ if options[:inline_poll_options] && status.preloadable_poll
+ raw_content = raw_content + "\n\n" + status.preloadable_poll.options.map { |title| "[ ] #{title}" }.join("\n")
+ end
+
+ return '' if raw_content.blank?
+
+ unless status.local?
+ html = reformat(raw_content)
+ html = encode_custom_emojis(html, status.emojis, options[:autoplay]) if options[:custom_emojify]
+ return html.html_safe # rubocop:disable Rails/OutputSafety
+ end
+
+ linkable_accounts = status.respond_to?(:active_mentions) ? status.active_mentions.map(&:account) : []
+ linkable_accounts << status.account
+
+ html = raw_content
+ html = "RT @#{prepend_reblog} #{html}" if prepend_reblog
+ html = encode_and_link_urls(html, linkable_accounts)
+ html = encode_custom_emojis(html, status.emojis, options[:autoplay]) if options[:custom_emojify]
+ html = simple_format(html, {}, sanitize: false)
+ html = html.delete("\n")
+
+ html.html_safe # rubocop:disable Rails/OutputSafety
+ end
+
+ def reformat(html)
+ sanitize(html, Sanitize::Config::MASTODON_STRICT)
+ rescue ArgumentError
+ ''
+ end
+
+ def plaintext(status)
+ return status.text if status.local?
+
+ text = status.text.gsub(/( | |<\/p>)+/) { |match| "#{match}\n" }
+ strip_tags(text)
+ end
+
+ def simplified_format(account, **options)
+ html = account.local? ? linkify(account.note) : reformat(account.note)
+ html = encode_custom_emojis(html, account.emojis, options[:autoplay]) if options[:custom_emojify]
+ html.html_safe # rubocop:disable Rails/OutputSafety
+ end
+
+ def sanitize(html, config)
+ Sanitize.fragment(html, config)
+ end
+
+ def format_spoiler(status, **options)
+ html = encode(status.spoiler_text)
+ html = encode_custom_emojis(html, status.emojis, options[:autoplay])
+ html.html_safe # rubocop:disable Rails/OutputSafety
+ end
+
+ def format_poll_option(status, option, **options)
+ html = encode(option.title)
+ html = encode_custom_emojis(html, status.emojis, options[:autoplay])
+ html.html_safe # rubocop:disable Rails/OutputSafety
+ end
+
+ def format_display_name(account, **options)
+ html = encode(account.display_name.presence || account.username)
+ html = encode_custom_emojis(html, account.emojis, options[:autoplay]) if options[:custom_emojify]
+ html.html_safe # rubocop:disable Rails/OutputSafety
+ end
+
+ def format_field(account, str, **options)
+ html = account.local? ? encode_and_link_urls(str, me: true, with_domain: true) : reformat(str)
+ html = encode_custom_emojis(html, account.emojis, options[:autoplay]) if options[:custom_emojify]
+ html.html_safe # rubocop:disable Rails/OutputSafety
+ end
+
+ def linkify(text)
+ html = encode_and_link_urls(text)
+ html = simple_format(html, {}, sanitize: false)
+ html = html.delete("\n")
+
+ html.html_safe # rubocop:disable Rails/OutputSafety
+ end
+
+ private
+
+ def html_entities
+ @html_entities ||= HTMLEntities.new
+ end
+
+ def encode(html)
+ html_entities.encode(html)
+ end
+
+ def encode_and_link_urls(html, accounts = nil, options = {})
+ entities = utf8_friendly_extractor(html, extract_url_without_protocol: false)
+
+ if accounts.is_a?(Hash)
+ options = accounts
+ accounts = nil
+ end
+
+ rewrite(html.dup, entities) do |entity|
+ if entity[:url]
+ link_to_url(entity, options)
+ elsif entity[:hashtag]
+ link_to_hashtag(entity)
+ elsif entity[:screen_name]
+ link_to_mention(entity, accounts, options)
+ end
+ end
+ end
+
+ def count_tag_nesting(tag)
+ if tag[1] == '/' then -1
+ elsif tag[-2] == '/' then 0
+ else 1
+ end
+ end
+
+ # rubocop:disable Metrics/BlockNesting
+ def encode_custom_emojis(html, emojis, animate = false)
+ return html if emojis.empty?
+
+ emoji_map = emojis.each_with_object({}) { |e, h| h[e.shortcode] = [full_asset_url(e.image.url), full_asset_url(e.image.url(:static))] }
+
+ i = -1
+ tag_open_index = nil
+ inside_shortname = false
+ shortname_start_index = -1
+ invisible_depth = 0
+
+ while i + 1 < html.size
+ i += 1
+
+ if invisible_depth.zero? && inside_shortname && html[i] == ':'
+ shortcode = html[shortname_start_index + 1..i - 1]
+ emoji = emoji_map[shortcode]
+
+ if emoji
+ original_url, static_url = emoji
+ replacement = begin
+ if animate
+ image_tag(original_url, draggable: false, class: 'emojione', alt: ":#{shortcode}:", title: ":#{shortcode}:")
+ else
+ image_tag(original_url, draggable: false, class: 'emojione custom-emoji', alt: ":#{shortcode}:", title: ":#{shortcode}:", data: { original: original_url, static: static_url })
+ end
+ end
+ before_html = shortname_start_index.positive? ? html[0..shortname_start_index - 1] : ''
+ html = before_html + replacement + html[i + 1..-1]
+ i += replacement.size - (shortcode.size + 2) - 1
+ else
+ i -= 1
+ end
+
+ inside_shortname = false
+ elsif tag_open_index && html[i] == '>'
+ tag = html[tag_open_index..i]
+ tag_open_index = nil
+ if invisible_depth.positive?
+ invisible_depth += count_tag_nesting(tag)
+ elsif tag == ''
+ invisible_depth = 1
+ end
+ elsif html[i] == '<'
+ tag_open_index = i
+ inside_shortname = false
+ elsif !tag_open_index && html[i] == ':'
+ inside_shortname = true
+ shortname_start_index = i
+ end
+ end
+
+ html
+ end
+ # rubocop:enable Metrics/BlockNesting
+
+ def rewrite(text, entities)
+ text = text.to_s
+
+ # Sort by start index
+ entities = entities.sort_by do |entity|
+ indices = entity.respond_to?(:indices) ? entity.indices : entity[:indices]
+ indices.first
+ end
+
+ result = []
+
+ last_index = entities.reduce(0) do |index, entity|
+ indices = entity.respond_to?(:indices) ? entity.indices : entity[:indices]
+ result << encode(text[index...indices.first])
+ result << yield(entity)
+ indices.last
+ end
+
+ result << encode(text[last_index..-1])
+
+ result.flatten.join
+ end
+
+ def utf8_friendly_extractor(text, options = {})
+ # Note: I couldn't obtain list_slug with @user/list-name format
+ # for mention so this requires additional check
+ special = Extractor.extract_urls_with_indices(text, options)
+ standard = Extractor.extract_entities_with_indices(text, options)
+ extra = Extractor.extract_extra_uris_with_indices(text, options)
+
+ Extractor.remove_overlapping_entities(special + standard + extra)
+ end
+
+ def link_to_url(entity, options = {})
+ url = Addressable::URI.parse(entity[:url])
+ html_attrs = { target: '_blank', rel: 'nofollow noopener noreferrer' }
+
+ html_attrs[:rel] = "me #{html_attrs[:rel]}" if options[:me]
+
+ Twitter::TwitterText::Autolink.send(:link_to_text, entity, link_html(entity[:url]), url, html_attrs)
+ rescue Addressable::URI::InvalidURIError, IDN::Idna::IdnaError
+ encode(entity[:url])
+ end
+
+ def link_to_mention(entity, linkable_accounts, options = {})
+ acct = entity[:screen_name]
+
+ return link_to_account(acct, options) unless linkable_accounts
+
+ same_username_hits = 0
+ account = nil
+ username, domain = acct.split('@')
+ domain = nil if TagManager.instance.local_domain?(domain)
+
+ linkable_accounts.each do |item|
+ same_username = item.username.casecmp(username).zero?
+ same_domain = item.domain.nil? ? domain.nil? : item.domain.casecmp(domain)&.zero?
+
+ if same_username && !same_domain
+ same_username_hits += 1
+ elsif same_username && same_domain
+ account = item
+ end
+ end
+
+ account ? mention_html(account, with_domain: same_username_hits.positive? || options[:with_domain]) : "@#{encode(acct)}"
+ end
+
+ def link_to_account(acct, options = {})
+ username, domain = acct.split('@')
+
+ domain = nil if TagManager.instance.local_domain?(domain)
+ account = EntityCache.instance.mention(username, domain)
+
+ account ? mention_html(account, with_domain: options[:with_domain]) : "@#{encode(acct)}"
+ end
+
+ def link_to_hashtag(entity)
+ hashtag_html(entity[:hashtag])
+ end
+
+ def link_html(url)
+ url = Addressable::URI.parse(url).to_s
+ prefix = url.match(/\A(https?:\/\/(www\.)?|xmpp:)/).to_s
+ text = url[prefix.length, 30]
+ suffix = url[prefix.length + 30..-1]
+ cutoff = url[prefix.length..-1].length > 30
+
+ "#{encode(prefix)} #{encode(text)} #{encode(suffix)} "
+ end
+
+ def hashtag_html(tag)
+ "##{encode(tag)} "
+ end
+
+ def mention_html(account, with_domain: false)
+ "@#{encode(with_domain ? account.pretty_acct : account.username)} "
+ end
+end
diff --git a/app/lib/link_details_extractor.rb b/app/lib/link_details_extractor.rb
index 78265fa1cbeb5f..00039c3a179dc3 100644
--- a/app/lib/link_details_extractor.rb
+++ b/app/lib/link_details_extractor.rb
@@ -17,6 +17,19 @@ class LinkDetailsExtractor
(//[\s]*\]\]>) # Single-line comment style closing
)[\s]*$}x
+ # Some publications wrap their JSON-LD data in their
+
+