mirror of
https://github.com/glitch-soc/mastodon.git
synced 2025-12-16 09:18:46 +00:00
Compare commits
219 Commits
v4.5.0
...
stable-4.4
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
6558a1e07a | ||
|
|
b64101ee64 | ||
|
|
0bde273b3d | ||
|
|
22fe977ffe | ||
|
|
8e945bef2a | ||
|
|
d5f12debe0 | ||
|
|
5d0ec718fd | ||
|
|
c7aa312307 | ||
|
|
dc1d4eda7c | ||
|
|
931a29b4f3 | ||
|
|
99b2307350 | ||
|
|
375f2e6ebf | ||
|
|
f0a1da78ba | ||
|
|
b554ecfcb4 | ||
|
|
50244ba682 | ||
|
|
01cf5c103d | ||
|
|
5bda54d15a | ||
|
|
07f5573cd6 | ||
|
|
2b0b537152 | ||
|
|
c49e261ad0 | ||
|
|
915bcb267f | ||
|
|
ff37011057 | ||
|
|
8f5e95a159 | ||
|
|
16ee628d24 | ||
|
|
64a0b060a8 | ||
|
|
fa52f4361a | ||
|
|
7f7d6697c1 | ||
|
|
c2fb12d22d | ||
|
|
2dc4552229 | ||
|
|
95868643a2 | ||
|
|
7c46fdfbf1 | ||
|
|
8965e1bfa9 | ||
|
|
1e27ab0885 | ||
|
|
cef2c50a71 | ||
|
|
a54af6b06c | ||
|
|
81cf6715de | ||
|
|
d7f4eca801 | ||
|
|
adf291631e | ||
|
|
cbef4c9e65 | ||
|
|
1631fb80e8 | ||
|
|
8477bec2f2 | ||
|
|
6796765363 | ||
|
|
044a20f12d | ||
|
|
15d05121df | ||
|
|
07dea4e140 | ||
|
|
3c2570c88a | ||
|
|
81955c10b1 | ||
|
|
e4bdbccba8 | ||
|
|
958d4df6cf | ||
|
|
21d4abf7cc | ||
|
|
d7d6407d41 | ||
|
|
a186bad399 | ||
|
|
67575e59e6 | ||
|
|
d9113976c8 | ||
|
|
3386f225e4 | ||
|
|
97bc82c710 | ||
|
|
0e7cb713d1 | ||
|
|
670316499f | ||
|
|
3c725240fd | ||
|
|
d8ddf95485 | ||
|
|
4c12c2ed60 | ||
|
|
636ecd1d03 | ||
|
|
cb0065cfe9 | ||
|
|
6ae1b4fae9 | ||
|
|
fd7adcc9d4 | ||
|
|
3c5f07c28e | ||
|
|
048a42b8a7 | ||
|
|
c475623418 | ||
|
|
dc6d8f8825 | ||
|
|
dd0647ca45 | ||
|
|
70e2eb49df | ||
|
|
bef28b2e51 | ||
|
|
0b66bd591f | ||
|
|
a94d7bf520 | ||
|
|
c8551a3eca | ||
|
|
df322e50c0 | ||
|
|
1f3588a5a7 | ||
|
|
06c2393805 | ||
|
|
4e85b9073b | ||
|
|
cd573a346d | ||
|
|
793296b5eb | ||
|
|
661dbede1c | ||
|
|
89f423aa00 | ||
|
|
19c89f1bbd | ||
|
|
c966d75600 | ||
|
|
6f1fd0c2a7 | ||
|
|
68c219e753 | ||
|
|
a795743c3f | ||
|
|
1ab5ea9bfb | ||
|
|
48f55e3224 | ||
|
|
6044270d69 | ||
|
|
be1bd91e6d | ||
|
|
34cd5a716f | ||
|
|
ec5128bc1f | ||
|
|
28a79eb239 | ||
|
|
1e2a9167bc | ||
|
|
1947f3a18b | ||
|
|
5a46e3a234 | ||
|
|
75ba0f757a | ||
|
|
62ed8d633e | ||
|
|
02d92d3b68 | ||
|
|
2a87fe5860 | ||
|
|
00c61317d3 | ||
|
|
c0f9e7f4c3 | ||
|
|
1137a0ca3a | ||
|
|
1faf520ce4 | ||
|
|
8777443c9b | ||
|
|
bd6d1f0e3f | ||
|
|
1a1a23f6f0 | ||
|
|
a48567784c | ||
|
|
b71216a08a | ||
|
|
36974aaa99 | ||
|
|
567f337db3 | ||
|
|
97f118013a | ||
|
|
ea5d1f0297 | ||
|
|
7a862d3308 | ||
|
|
1675eab561 | ||
|
|
5f4116a311 | ||
|
|
0741381670 | ||
|
|
e61900cadc | ||
|
|
cbb9a4dbe3 | ||
|
|
4ef0ce033e | ||
|
|
48315a719d | ||
|
|
45932983fc | ||
|
|
1ed3e4cc77 | ||
|
|
5478ef9b32 | ||
|
|
e2592419d9 | ||
|
|
e330447b0e | ||
|
|
b15861528c | ||
|
|
83dc7dc16e | ||
|
|
7d3cc51148 | ||
|
|
cabb33bc49 | ||
|
|
ba6f4de3e4 | ||
|
|
6af9646bbd | ||
|
|
fcb7917344 | ||
|
|
208cb8276a | ||
|
|
4ae47f4263 | ||
|
|
08b2f255fc | ||
|
|
8242f06eca | ||
|
|
5429351889 | ||
|
|
6ff4e83937 | ||
|
|
5e639d7384 | ||
|
|
26e524836d | ||
|
|
cfc4bb1dc0 | ||
|
|
d7099b1b38 | ||
|
|
69fb382424 | ||
|
|
47d469ec5e | ||
|
|
4d3e2efb69 | ||
|
|
92fc7a30dc | ||
|
|
4311369ab8 | ||
|
|
18653ce15d | ||
|
|
71a35d3953 | ||
|
|
e69c1479a8 | ||
|
|
77d2cdb302 | ||
|
|
c727197760 | ||
|
|
d6859c9658 | ||
|
|
7a9e98f4d6 | ||
|
|
7924a27ae7 | ||
|
|
d664b9d8ff | ||
|
|
4558cfadd8 | ||
|
|
713965467d | ||
|
|
aec6d0f807 | ||
|
|
e103815d2d | ||
|
|
d73b9fba90 | ||
|
|
a89d11bc08 | ||
|
|
a250928934 | ||
|
|
1d1b17b04b | ||
|
|
2aff51013c | ||
|
|
8c3c1faaec | ||
|
|
a2888f1bb2 | ||
|
|
77fe044f03 | ||
|
|
da0cc0f5b9 | ||
|
|
ee83f3a8b9 | ||
|
|
7ae78b1032 | ||
|
|
c4b7c3bdda | ||
|
|
a79dbf8334 | ||
|
|
ef6f5f9357 | ||
|
|
f65f6ad6f1 | ||
|
|
c0e242cb73 | ||
|
|
6c1cc5b25a | ||
|
|
ec6f93ae45 | ||
|
|
609a40181e | ||
|
|
93ce44d21d | ||
|
|
fb3ff194b5 | ||
|
|
81b363b338 | ||
|
|
1151b05c2d | ||
|
|
f96743fcfb | ||
|
|
7a51ad7ebd | ||
|
|
06a46e77b8 | ||
|
|
69e14246b8 | ||
|
|
174370dec2 | ||
|
|
18e08bf493 | ||
|
|
c1794fb948 | ||
|
|
061c966ab3 | ||
|
|
326f6bc12a | ||
|
|
bd442485d0 | ||
|
|
333a17a478 | ||
|
|
388e09e1a3 | ||
|
|
2dcededcf0 | ||
|
|
2db8a328cd | ||
|
|
b4a950c2fc | ||
|
|
194645aada | ||
|
|
48aaecec7b | ||
|
|
6cac651ff2 | ||
|
|
385dd5ea37 | ||
|
|
0c5ce23ae4 | ||
|
|
cb937a920e | ||
|
|
7051458467 | ||
|
|
025abf7325 | ||
|
|
28373a9c88 | ||
|
|
42884d8727 | ||
|
|
000ff9c05f | ||
|
|
921af5d27d | ||
|
|
878e1e65eb | ||
|
|
06f5f270cc | ||
|
|
961c22a6fd | ||
|
|
07b4fa55c8 | ||
|
|
041bce9ed6 | ||
|
|
d7a08d81b6 |
4
.github/workflows/build-releases.yml
vendored
4
.github/workflows/build-releases.yml
vendored
@@ -20,7 +20,7 @@ jobs:
|
|||||||
# Only tag with latest when ran against the latest stable branch
|
# Only tag with latest when ran against the latest stable branch
|
||||||
# This needs to be updated after each minor version release
|
# This needs to be updated after each minor version release
|
||||||
flavor: |
|
flavor: |
|
||||||
latest=${{ startsWith(github.ref, 'refs/tags/v4.3.') }}
|
latest=${{ startsWith(github.ref, 'refs/tags/v4.4.') }}
|
||||||
tags: |
|
tags: |
|
||||||
type=pep440,pattern={{raw}}
|
type=pep440,pattern={{raw}}
|
||||||
type=pep440,pattern=v{{major}}.{{minor}}
|
type=pep440,pattern=v{{major}}.{{minor}}
|
||||||
@@ -37,7 +37,7 @@ jobs:
|
|||||||
# Only tag with latest when ran against the latest stable branch
|
# Only tag with latest when ran against the latest stable branch
|
||||||
# This needs to be updated after each minor version release
|
# This needs to be updated after each minor version release
|
||||||
flavor: |
|
flavor: |
|
||||||
latest=${{ startsWith(github.ref, 'refs/tags/v4.3.') }}
|
latest=${{ startsWith(github.ref, 'refs/tags/v4.4.') }}
|
||||||
tags: |
|
tags: |
|
||||||
type=pep440,pattern={{raw}}
|
type=pep440,pattern={{raw}}
|
||||||
type=pep440,pattern=v{{major}}.{{minor}}
|
type=pep440,pattern=v{{major}}.{{minor}}
|
||||||
|
|||||||
@@ -9,7 +9,7 @@ permissions:
|
|||||||
jobs:
|
jobs:
|
||||||
download-translations-stable:
|
download-translations-stable:
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
if: github.repository == 'mastodon/mastodon'
|
if: github.repository == 'glitch-soc/mastodon'
|
||||||
|
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout
|
- name: Checkout
|
||||||
|
|||||||
1
.github/workflows/crowdin-upload.yml
vendored
1
.github/workflows/crowdin-upload.yml
vendored
@@ -14,6 +14,7 @@ on:
|
|||||||
- config/locales-glitch/devise.en.yml
|
- config/locales-glitch/devise.en.yml
|
||||||
- config/locales-glitch/doorkeeper.en.yml
|
- config/locales-glitch/doorkeeper.en.yml
|
||||||
- .github/workflows/crowdin-upload.yml
|
- .github/workflows/crowdin-upload.yml
|
||||||
|
workflow_dispatch:
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
upload-translations:
|
upload-translations:
|
||||||
|
|||||||
169
CHANGELOG.md
169
CHANGELOG.md
@@ -2,7 +2,165 @@
|
|||||||
|
|
||||||
All notable changes to this project will be documented in this file.
|
All notable changes to this project will be documented in this file.
|
||||||
|
|
||||||
## [4.4.0] - UNRELEASED
|
## [4.4.10] - 2025-12-08
|
||||||
|
|
||||||
|
### Security
|
||||||
|
|
||||||
|
- Fix inconsistent error handling leaking information on existence of private posts ([GHSA-gwhw-gcjx-72v8](https://github.com/mastodon/mastodon/security/advisories/GHSA-gwhw-gcjx-72v8))
|
||||||
|
|
||||||
|
### Fixed
|
||||||
|
|
||||||
|
- Fix YouTube embeds by sending referer (#37126 by @ChaosExAnima)
|
||||||
|
- Fix YouTube iframe not being able to start at a defined time (#26584 by @BrunoViveiros)
|
||||||
|
- Fix streamed quoted polls not being hydrated correctly (#37118 by @ClearlyClaire)
|
||||||
|
- Fix error handling when re-fetching already-known statuses (#37077 by @ClearlyClaire)
|
||||||
|
- Fix known expensive S3 batch delete operation failing because of short timeouts (#37004 by @ClearlyClaire)
|
||||||
|
|
||||||
|
## [4.4.9] - 2025-11-20
|
||||||
|
|
||||||
|
### Fixed
|
||||||
|
|
||||||
|
- Fix `tootctl upgrade storage-schema` failing with `ArgumentError` (#36914 by @shugo)
|
||||||
|
- Fix old previously-undiscovered posts being treated as new when receiving an `Update` (#36848 by @ClearlyClaire)
|
||||||
|
- Fix filters not being applied to quotes in detailed view (#36843 by @ClearlyClaire)
|
||||||
|
|
||||||
|
## [4.4.8] - 2025-10-21
|
||||||
|
|
||||||
|
### Security
|
||||||
|
|
||||||
|
- Fix quote control bypass ([GHSA-8h43-rcqj-wpc6](https://github.com/mastodon/mastodon/security/advisories/GHSA-8h43-rcqj-wpc6))
|
||||||
|
|
||||||
|
## [4.4.7] - 2025-10-15
|
||||||
|
|
||||||
|
### Fixed
|
||||||
|
|
||||||
|
- Fix forwarder being called with `nil` status when quote post is soft-deleted (#36463 by @ClearlyClaire)
|
||||||
|
- Fix moderation warning e-mails that include posts (#36462 by @ClearlyClaire)
|
||||||
|
- Fix allow_referrer_origin typo (#36460 by @ShadowJonathan)
|
||||||
|
|
||||||
|
## [4.4.6] - 2025-10-13
|
||||||
|
|
||||||
|
### Security
|
||||||
|
|
||||||
|
- Update dependencies `rack` and `uri`
|
||||||
|
- Fix streaming server connection not being closed on user suspension (by @ThisIsMissEm, [GHSA-r2fh-jr9c-9pxh](https://github.com/mastodon/mastodon/security/advisories/GHSA-r2fh-jr9c-9pxh))
|
||||||
|
- Fix password change through admin CLI not invalidating existing sessions and access tokens (by @ThisIsMissEm, [GHSA-f3q3-rmf7-9655](https://github.com/mastodon/mastodon/security/advisories/GHSA-f3q3-rmf7-9655))
|
||||||
|
- Fix streaming server allowing access to public timelines even without the `read` or `read:statuses` OAuth scopes (by @ThisIsMissEm, [GHSA-7gwh-mw97-qjgp](https://github.com/mastodon/mastodon/security/advisories/GHSA-7gwh-mw97-qjgp))
|
||||||
|
|
||||||
|
### Added
|
||||||
|
|
||||||
|
- Add support for processing quotes of deleted posts signaled through a `Tombstone` (#36381 by @ClearlyClaire)
|
||||||
|
|
||||||
|
### Fixed
|
||||||
|
|
||||||
|
- Fix quote post state sometimes not being updated through streaming server (#36408 by @ClearlyClaire)
|
||||||
|
- Fix inconsistent “pending tags” count on admin dashboard (#36404 by @mjankowski)
|
||||||
|
- Fix JSON payload being potentially mutated when processing interaction policies (#36392 by @ClearlyClaire)
|
||||||
|
- Fix quotes not being displayed in email notifications (#36379 by @diondiondion)
|
||||||
|
- Fix redirect to external object when URL is missing or malformed (#36347 by @ClearlyClaire)
|
||||||
|
- Fix quotes not being displayed in the featured carousel (#36335 by @diondiondion)
|
||||||
|
|
||||||
|
## [4.4.5] - 2025-09-23
|
||||||
|
|
||||||
|
### Security
|
||||||
|
|
||||||
|
- Update dependencies
|
||||||
|
|
||||||
|
### Added
|
||||||
|
|
||||||
|
- Add support for `has:quote` in search (#36217 by @ClearlyClaire)
|
||||||
|
|
||||||
|
### Changed
|
||||||
|
|
||||||
|
- Change quoted posts from silenced accounts to use a click-through rather than being hidden (#36166 and #36167 by @ClearlyClaire)
|
||||||
|
|
||||||
|
### Fixed
|
||||||
|
|
||||||
|
- Fix processing of out-of-order `Update` as implicit updates (#36190 by @ClearlyClaire)
|
||||||
|
- Fix getting `Create` and `Update` out of order (#36176 by @ClearlyClaire)
|
||||||
|
- Fix quotes with Content Warnings but no text being shown without Content Warnings (#36150 by @ClearlyClaire)
|
||||||
|
|
||||||
|
## [4.4.4] - 2025-09-16
|
||||||
|
|
||||||
|
### Security
|
||||||
|
|
||||||
|
- Update dependencies
|
||||||
|
|
||||||
|
### Fixed
|
||||||
|
|
||||||
|
- Fix missing memoization in `Web::PushNotificationWorker` (#36085 by @ClearlyClaire)
|
||||||
|
- Fix unresponsive areas around GIFV modals in some cases (#36059 by @ClearlyClaire)
|
||||||
|
- Fix missing `beforeUnload` confirmation when a poll is being authored (#36030 by @ClearlyClaire)
|
||||||
|
- Fix processing of remote edited statuses with new media and no text (#35970 by @unfokus)
|
||||||
|
- Fix polls not being displayed in moderation interface (#35644 and #35933 by @ThisIsMissEm)
|
||||||
|
- Fix WebUI handling of deleted quoted posts (#35909 and #35918 by @ClearlyClaire and @diondiondion)
|
||||||
|
- Fix “Edit” and “Delete & Redraft” on a poll not inserting empty option (#35892 by @ClearlyClaire)
|
||||||
|
- Fix loading of some compatibility CSS on some configurations (#35876 by @shleeable)
|
||||||
|
- Fix HttpLog not being enabled with `RAILS_LOG_LEVEL=debug` (#35833 by @mjankowski)
|
||||||
|
- Fix self-destruct scheduler behavior on some Redis setups (#35823 by @ClearlyClaire)
|
||||||
|
- Fix `tootctl admin create` not bypassing reserved username checks (#35779 by @ClearlyClaire)
|
||||||
|
- Fix interaction policy changes in implicit updates not being saved (#35751 by @ClearlyClaire)
|
||||||
|
- Fix quote revocation not being streamed (#35710 by @ClearlyClaire)
|
||||||
|
- Fix export of large user archives by enabling Zip64 (#35850 by @ClearlyClaire)
|
||||||
|
|
||||||
|
### Changed
|
||||||
|
|
||||||
|
- Change labels for quote policy settings (#35893 by @ClearlyClaire)
|
||||||
|
- Change standalone “Share” page to redirect to web interface after posting (#35763 by @ChaosExAnima)
|
||||||
|
|
||||||
|
## [4.4.3] - 2025-08-05
|
||||||
|
|
||||||
|
### Security
|
||||||
|
|
||||||
|
- Update dependencies
|
||||||
|
- Fix incorrect rate-limit handling [GHSA-84ch-6436-c7mg](https://github.com/mastodon/mastodon/security/advisories/GHSA-84ch-6436-c7mg)
|
||||||
|
|
||||||
|
### Fixed
|
||||||
|
|
||||||
|
- Fix race condition caused by ActiveRecord query cache in `Create` critical path (#35662 by @ClearlyClaire)
|
||||||
|
- Fix race condition caused by quote post processing (#35657 by @ClearlyClaire)
|
||||||
|
- Fix WebUI crashing for accounts with `null` URL (#35651 by @ClearlyClaire)
|
||||||
|
- Fix friends-of-friends recommendations suggesting already-requested accounts (#35604 by @ClearlyClaire)
|
||||||
|
- Fix synchronous recursive fetching of deeply-nested quoted posts (#35600 by @ClearlyClaire)
|
||||||
|
- Fix “Expand this post” link including user `@undefined` (#35478 by @ClearlyClaire)
|
||||||
|
|
||||||
|
### Changed
|
||||||
|
|
||||||
|
- Change `StatusReachFinder` to consider quotes as well as reblogs (#35601 by @ClearlyClaire)
|
||||||
|
- Add restrictions on which quote posts can trend (#35507 by @ClearlyClaire)
|
||||||
|
- Change quote verification to not bypass authorization flow for mentions (#35528 by @ClearlyClaire)
|
||||||
|
|
||||||
|
## [4.4.2] - 2025-07-23
|
||||||
|
|
||||||
|
### Security
|
||||||
|
|
||||||
|
- Update dependencies
|
||||||
|
|
||||||
|
### Fixed
|
||||||
|
|
||||||
|
- Fix menu not clickable in Firefox (#35390 and #35414 by @diondiondion)
|
||||||
|
- Add `lang` attribute to current composer language in alt text modal (#35412 by @diondiondion)
|
||||||
|
- Fix quote posts styling on notifications page (#35411 by @diondiondion)
|
||||||
|
- Improve a11y of custom select menus in notifications settings (#35403 by @diondiondion)
|
||||||
|
- Fix selected item in poll select menus is unreadable in Firefox (#35402 by @diondiondion)
|
||||||
|
- Update age limit wording (#35387 by @diondiondion)
|
||||||
|
- Fix support for quote verification in implicit status updates (#35384 by @ClearlyClaire)
|
||||||
|
- Improve `Dropdown` component accessibility (#35373 by @diondiondion)
|
||||||
|
- Fix processing some incoming quotes failing because of missing JSON-LD context (#35354 and #35380 by @ClearlyClaire)
|
||||||
|
- Make bio hashtags open the local page instead of the remote instance (#35349 by @ChaosExAnima)
|
||||||
|
- Fix styling of external log-in button (#35320 by @ClearlyClaire)
|
||||||
|
|
||||||
|
## [4.4.1] - 2025-07-09
|
||||||
|
|
||||||
|
### Fixed
|
||||||
|
|
||||||
|
- Fix nearly every sub-directory being crawled as part of Vite build (#35323 by @ClearlyClaire)
|
||||||
|
- Fix assets not building when Redis is unavailable (#35321 by @oneiros)
|
||||||
|
- Fix replying from media modal or pop-in-player tagging user `@undefined` (#35317 by @ClearlyClaire)
|
||||||
|
- Fix support for special characters in various environment variables (#35314 by @mjankowski and @ClearlyClaire)
|
||||||
|
- Fix some database migrations failing for indexes manually removed by admins (#35309 by @mjankowski)
|
||||||
|
|
||||||
|
## [4.4.0] - 2025-07-08
|
||||||
|
|
||||||
### Added
|
### Added
|
||||||
|
|
||||||
@@ -38,7 +196,7 @@ All notable changes to this project will be documented in this file.
|
|||||||
Server administrators can now chose to opt in to transmit referrer information when following an external link. Only the domain name is transmitted, not the referrer path.
|
Server administrators can now chose to opt in to transmit referrer information when following an external link. Only the domain name is transmitted, not the referrer path.
|
||||||
- Add double tap to zoom and swipe to dismiss to media modal in web UI (#34210 by @Gargron)
|
- Add double tap to zoom and swipe to dismiss to media modal in web UI (#34210 by @Gargron)
|
||||||
- Add link from Web UI for Hashtags to the Moderation UI (#31448 by @ThisIsMissEm)
|
- Add link from Web UI for Hashtags to the Moderation UI (#31448 by @ThisIsMissEm)
|
||||||
- **Add terms of service** (#33055, #33233, #33230, #33703, #33699, #33994, #33993, #34105, #34122, #34200, #34527, #35053, #35115, #35126 and #35127 by @ClearlyClaire, @Gargron, @mjankowski, and @oneiros)\
|
- **Add terms of service** (#33055, #33233, #33230, #33703, #33699, #33994, #33993, #34105, #34122, #34200, #34527, #35053, #35115, #35126, #35127 and #35233 by @ClearlyClaire, @Gargron, @mjankowski, and @oneiros)\
|
||||||
Server administrators can now fill in Terms of Service and notify their users of upcoming changes.
|
Server administrators can now fill in Terms of Service and notify their users of upcoming changes.
|
||||||
- Add optional bulk mailer settings (#35191 and #35203 by @oneiros)\
|
- Add optional bulk mailer settings (#35191 and #35203 by @oneiros)\
|
||||||
This adds the optional environment variables `BULK_SMTP_PORT`, `BULK_SMTP_SERVER`, `BULK_SMTP_LOGIN` and so on analogous to `SMTP_PORT`, `SMTP_SERVER`, `SMTP_LOGIN` and related SMTP configuration environment variables.\
|
This adds the optional environment variables `BULK_SMTP_PORT`, `BULK_SMTP_SERVER`, `BULK_SMTP_LOGIN` and so on analogous to `SMTP_PORT`, `SMTP_SERVER`, `SMTP_LOGIN` and related SMTP configuration environment variables.\
|
||||||
@@ -51,7 +209,7 @@ All notable changes to this project will be documented in this file.
|
|||||||
- Add ability to dismiss alt text badge by tapping it in web UI (#33737 by @Gargron)
|
- Add ability to dismiss alt text badge by tapping it in web UI (#33737 by @Gargron)
|
||||||
- Add loading indicator to timeline gap indicators in web UI (#33762 by @Gargron)
|
- Add loading indicator to timeline gap indicators in web UI (#33762 by @Gargron)
|
||||||
- Add interaction modal when trying to interact with a poll while logged out (#32609 by @ThisIsMissEm)
|
- Add interaction modal when trying to interact with a poll while logged out (#32609 by @ThisIsMissEm)
|
||||||
- **Add experimental FASP support** (#34031, #34415, #34765, #34965, #34964, #34033 and #35218 by @oneiros)\
|
- **Add experimental FASP support** (#34031, #34415, #34765, #34965, #34964, #34033, #35218, #35262 and #35263 by @oneiros)\
|
||||||
This is a first step towards supporting “Fediverse Auxiliary Service Providers” (https://github.com/mastodon/fediverse_auxiliary_service_provider_specifications). This is mostly interesting to developers who would like to implement their own FASP, but also includes the capability to share data with a discovery provider (see https://www.fediscovery.org).
|
This is a first step towards supporting “Fediverse Auxiliary Service Providers” (https://github.com/mastodon/fediverse_auxiliary_service_provider_specifications). This is mostly interesting to developers who would like to implement their own FASP, but also includes the capability to share data with a discovery provider (see https://www.fediscovery.org).
|
||||||
- Add ability for admins to send announcements to all users via email (#33928 and #34411 by @ClearlyClaire)\
|
- Add ability for admins to send announcements to all users via email (#33928 and #34411 by @ClearlyClaire)\
|
||||||
This is meant for critical announcements only, as this will potentially send a lot of emails and cannot be opted out of by users.
|
This is meant for critical announcements only, as this will potentially send a lot of emails and cannot be opted out of by users.
|
||||||
@@ -64,7 +222,7 @@ All notable changes to this project will be documented in this file.
|
|||||||
- Add dropdown menu with quick actions to lists of accounts in web UI (#34391, #34709, and #34767 by @Gargron, @diondiondion, and @mkljczk)
|
- Add dropdown menu with quick actions to lists of accounts in web UI (#34391, #34709, and #34767 by @Gargron, @diondiondion, and @mkljczk)
|
||||||
- Add support for displaying “year in review” notification in web UI (#32710, #32765, #32709, #32807, #32914, #33148, and #33882 by @Gargron and @mjankowski)\
|
- Add support for displaying “year in review” notification in web UI (#32710, #32765, #32709, #32807, #32914, #33148, and #33882 by @Gargron and @mjankowski)\
|
||||||
Note that the notification is currently not generated automatically, and at the moment requires a manual undocumented administrator action.
|
Note that the notification is currently not generated automatically, and at the moment requires a manual undocumented administrator action.
|
||||||
- Add experimental support for receiving HTTP Message Signatures (RFC9421) (#34814, #35033 and #35109 by @oneiros)\
|
- Add experimental support for receiving HTTP Message Signatures (RFC9421) (#34814, #35033, #35109 and #35278 by @oneiros)\
|
||||||
For now, this needs to be explicitly enabled through the `http_message_signatures` feature flag (`EXPERIMENTAL_FEATURES=http_message_signatures`). This currently only covers verifying such signatures (inbound HTTP requests), not issuing them (outbound HTTP requests).
|
For now, this needs to be explicitly enabled through the `http_message_signatures` feature flag (`EXPERIMENTAL_FEATURES=http_message_signatures`). This currently only covers verifying such signatures (inbound HTTP requests), not issuing them (outbound HTTP requests).
|
||||||
- Add experimental Async Refreshes API (#34918 by @oneiros)
|
- Add experimental Async Refreshes API (#34918 by @oneiros)
|
||||||
- Add experimental server-side feature to fetch remote replies (#32615, #34147, #34149, #34151, #34615, #34682, and #34702 by @ClearlyClaire and @sneakers-the-rat)\
|
- Add experimental server-side feature to fetch remote replies (#32615, #34147, #34149, #34151, #34615, #34682, and #34702 by @ClearlyClaire and @sneakers-the-rat)\
|
||||||
@@ -218,6 +376,7 @@ All notable changes to this project will be documented in this file.
|
|||||||
- Fix admin dashboard crash on specific Elasticsearch connection errors (#34683 by @ClearlyClaire)
|
- Fix admin dashboard crash on specific Elasticsearch connection errors (#34683 by @ClearlyClaire)
|
||||||
- Fix OIDC account creation failing for long display names (#34639 by @defnull)
|
- Fix OIDC account creation failing for long display names (#34639 by @defnull)
|
||||||
- Fix use of the deprecated `/api/v1/instance` endpoint in the moderation interface (#34613 by @renchap)
|
- Fix use of the deprecated `/api/v1/instance` endpoint in the moderation interface (#34613 by @renchap)
|
||||||
|
- Fix inaccessible “Clear search” button (#35152 and #35281 by @diondiondion)
|
||||||
- Fix search operators sometimes getting lost (#35190 by @ClearlyClaire)
|
- Fix search operators sometimes getting lost (#35190 by @ClearlyClaire)
|
||||||
- Fix directory scroll position reset (#34560 by @przucidlo)
|
- Fix directory scroll position reset (#34560 by @przucidlo)
|
||||||
- Fix needlessly complex SVG paths for oEmbed and logo (#34538 by @edent)
|
- Fix needlessly complex SVG paths for oEmbed and logo (#34538 by @edent)
|
||||||
@@ -232,7 +391,7 @@ All notable changes to this project will be documented in this file.
|
|||||||
- Fix extra space under left-indented vertical videos (#34313 by @ClearlyClaire)
|
- Fix extra space under left-indented vertical videos (#34313 by @ClearlyClaire)
|
||||||
- Fix glitchy iOS media attachment drag interactions (#35057 by @diondiondion)
|
- Fix glitchy iOS media attachment drag interactions (#35057 by @diondiondion)
|
||||||
- Fix zoomed images being blurry in Safari (#35052 by @diondiondion)
|
- Fix zoomed images being blurry in Safari (#35052 by @diondiondion)
|
||||||
- Fix redundant focus stop within status component in Web UI and make focus style more noticeable (#35037, #35051, #35096 and #35150 by @diondiondion)
|
- Fix redundant focus stop within status component in Web UI and make focus style more noticeable (#35037, #35051, #35096, #35150 and #35251 by @diondiondion)
|
||||||
- Fix digits in media player time readout not having a consistent width (#35038 by @diondiondion)
|
- Fix digits in media player time readout not having a consistent width (#35038 by @diondiondion)
|
||||||
- Fix wrong text color for “Open in advanced web interface” banner in high-contrast theme (#35032 by @diondiondion)
|
- Fix wrong text color for “Open in advanced web interface” banner in high-contrast theme (#35032 by @diondiondion)
|
||||||
- Fix hover card for limited accounts not hiding information as expected (#35024 by @diondiondion)
|
- Fix hover card for limited accounts not hiding information as expected (#35024 by @diondiondion)
|
||||||
|
|||||||
3
Gemfile
3
Gemfile
@@ -159,6 +159,9 @@ group :test do
|
|||||||
|
|
||||||
# Stub web requests for specs
|
# Stub web requests for specs
|
||||||
gem 'webmock', '~> 3.18'
|
gem 'webmock', '~> 3.18'
|
||||||
|
|
||||||
|
# Websocket driver for testing integration between rails/sidekiq and streaming
|
||||||
|
gem 'websocket-driver', '~> 0.8', require: false
|
||||||
end
|
end
|
||||||
|
|
||||||
group :development do
|
group :development do
|
||||||
|
|||||||
123
Gemfile.lock
123
Gemfile.lock
@@ -10,29 +10,29 @@ GIT
|
|||||||
GEM
|
GEM
|
||||||
remote: https://rubygems.org/
|
remote: https://rubygems.org/
|
||||||
specs:
|
specs:
|
||||||
actioncable (8.0.2)
|
actioncable (8.0.2.1)
|
||||||
actionpack (= 8.0.2)
|
actionpack (= 8.0.2.1)
|
||||||
activesupport (= 8.0.2)
|
activesupport (= 8.0.2.1)
|
||||||
nio4r (~> 2.0)
|
nio4r (~> 2.0)
|
||||||
websocket-driver (>= 0.6.1)
|
websocket-driver (>= 0.6.1)
|
||||||
zeitwerk (~> 2.6)
|
zeitwerk (~> 2.6)
|
||||||
actionmailbox (8.0.2)
|
actionmailbox (8.0.2.1)
|
||||||
actionpack (= 8.0.2)
|
actionpack (= 8.0.2.1)
|
||||||
activejob (= 8.0.2)
|
activejob (= 8.0.2.1)
|
||||||
activerecord (= 8.0.2)
|
activerecord (= 8.0.2.1)
|
||||||
activestorage (= 8.0.2)
|
activestorage (= 8.0.2.1)
|
||||||
activesupport (= 8.0.2)
|
activesupport (= 8.0.2.1)
|
||||||
mail (>= 2.8.0)
|
mail (>= 2.8.0)
|
||||||
actionmailer (8.0.2)
|
actionmailer (8.0.2.1)
|
||||||
actionpack (= 8.0.2)
|
actionpack (= 8.0.2.1)
|
||||||
actionview (= 8.0.2)
|
actionview (= 8.0.2.1)
|
||||||
activejob (= 8.0.2)
|
activejob (= 8.0.2.1)
|
||||||
activesupport (= 8.0.2)
|
activesupport (= 8.0.2.1)
|
||||||
mail (>= 2.8.0)
|
mail (>= 2.8.0)
|
||||||
rails-dom-testing (~> 2.2)
|
rails-dom-testing (~> 2.2)
|
||||||
actionpack (8.0.2)
|
actionpack (8.0.2.1)
|
||||||
actionview (= 8.0.2)
|
actionview (= 8.0.2.1)
|
||||||
activesupport (= 8.0.2)
|
activesupport (= 8.0.2.1)
|
||||||
nokogiri (>= 1.8.5)
|
nokogiri (>= 1.8.5)
|
||||||
rack (>= 2.2.4)
|
rack (>= 2.2.4)
|
||||||
rack-session (>= 1.0.1)
|
rack-session (>= 1.0.1)
|
||||||
@@ -40,15 +40,15 @@ GEM
|
|||||||
rails-dom-testing (~> 2.2)
|
rails-dom-testing (~> 2.2)
|
||||||
rails-html-sanitizer (~> 1.6)
|
rails-html-sanitizer (~> 1.6)
|
||||||
useragent (~> 0.16)
|
useragent (~> 0.16)
|
||||||
actiontext (8.0.2)
|
actiontext (8.0.2.1)
|
||||||
actionpack (= 8.0.2)
|
actionpack (= 8.0.2.1)
|
||||||
activerecord (= 8.0.2)
|
activerecord (= 8.0.2.1)
|
||||||
activestorage (= 8.0.2)
|
activestorage (= 8.0.2.1)
|
||||||
activesupport (= 8.0.2)
|
activesupport (= 8.0.2.1)
|
||||||
globalid (>= 0.6.0)
|
globalid (>= 0.6.0)
|
||||||
nokogiri (>= 1.8.5)
|
nokogiri (>= 1.8.5)
|
||||||
actionview (8.0.2)
|
actionview (8.0.2.1)
|
||||||
activesupport (= 8.0.2)
|
activesupport (= 8.0.2.1)
|
||||||
builder (~> 3.1)
|
builder (~> 3.1)
|
||||||
erubi (~> 1.11)
|
erubi (~> 1.11)
|
||||||
rails-dom-testing (~> 2.2)
|
rails-dom-testing (~> 2.2)
|
||||||
@@ -58,22 +58,22 @@ GEM
|
|||||||
activemodel (>= 4.1)
|
activemodel (>= 4.1)
|
||||||
case_transform (>= 0.2)
|
case_transform (>= 0.2)
|
||||||
jsonapi-renderer (>= 0.1.1.beta1, < 0.3)
|
jsonapi-renderer (>= 0.1.1.beta1, < 0.3)
|
||||||
activejob (8.0.2)
|
activejob (8.0.2.1)
|
||||||
activesupport (= 8.0.2)
|
activesupport (= 8.0.2.1)
|
||||||
globalid (>= 0.3.6)
|
globalid (>= 0.3.6)
|
||||||
activemodel (8.0.2)
|
activemodel (8.0.2.1)
|
||||||
activesupport (= 8.0.2)
|
activesupport (= 8.0.2.1)
|
||||||
activerecord (8.0.2)
|
activerecord (8.0.2.1)
|
||||||
activemodel (= 8.0.2)
|
activemodel (= 8.0.2.1)
|
||||||
activesupport (= 8.0.2)
|
activesupport (= 8.0.2.1)
|
||||||
timeout (>= 0.4.0)
|
timeout (>= 0.4.0)
|
||||||
activestorage (8.0.2)
|
activestorage (8.0.2.1)
|
||||||
actionpack (= 8.0.2)
|
actionpack (= 8.0.2.1)
|
||||||
activejob (= 8.0.2)
|
activejob (= 8.0.2.1)
|
||||||
activerecord (= 8.0.2)
|
activerecord (= 8.0.2.1)
|
||||||
activesupport (= 8.0.2)
|
activesupport (= 8.0.2.1)
|
||||||
marcel (~> 1.0)
|
marcel (~> 1.0)
|
||||||
activesupport (8.0.2)
|
activesupport (8.0.2.1)
|
||||||
base64
|
base64
|
||||||
benchmark (>= 0.3)
|
benchmark (>= 0.3)
|
||||||
bigdecimal
|
bigdecimal
|
||||||
@@ -458,7 +458,7 @@ GEM
|
|||||||
net-smtp (0.5.1)
|
net-smtp (0.5.1)
|
||||||
net-protocol
|
net-protocol
|
||||||
nio4r (2.7.4)
|
nio4r (2.7.4)
|
||||||
nokogiri (1.18.8)
|
nokogiri (1.18.9)
|
||||||
mini_portile2 (~> 2.8.2)
|
mini_portile2 (~> 2.8.2)
|
||||||
racc (~> 1.4)
|
racc (~> 1.4)
|
||||||
oj (3.16.11)
|
oj (3.16.11)
|
||||||
@@ -494,7 +494,7 @@ GEM
|
|||||||
tzinfo
|
tzinfo
|
||||||
validate_url
|
validate_url
|
||||||
webfinger (~> 2.0)
|
webfinger (~> 2.0)
|
||||||
openssl (3.3.0)
|
openssl (3.3.1)
|
||||||
openssl-signature_algorithm (1.3.0)
|
openssl-signature_algorithm (1.3.0)
|
||||||
openssl (> 2.0)
|
openssl (> 2.0)
|
||||||
opentelemetry-api (1.5.0)
|
opentelemetry-api (1.5.0)
|
||||||
@@ -642,7 +642,7 @@ GEM
|
|||||||
activesupport (>= 3.0.0)
|
activesupport (>= 3.0.0)
|
||||||
raabro (1.4.0)
|
raabro (1.4.0)
|
||||||
racc (1.8.1)
|
racc (1.8.1)
|
||||||
rack (3.1.16)
|
rack (3.1.18)
|
||||||
rack-attack (6.7.0)
|
rack-attack (6.7.0)
|
||||||
rack (>= 1.0, < 4)
|
rack (>= 1.0, < 4)
|
||||||
rack-cors (3.0.0)
|
rack-cors (3.0.0)
|
||||||
@@ -668,20 +668,20 @@ GEM
|
|||||||
rack (>= 1.3)
|
rack (>= 1.3)
|
||||||
rackup (2.2.1)
|
rackup (2.2.1)
|
||||||
rack (>= 3)
|
rack (>= 3)
|
||||||
rails (8.0.2)
|
rails (8.0.2.1)
|
||||||
actioncable (= 8.0.2)
|
actioncable (= 8.0.2.1)
|
||||||
actionmailbox (= 8.0.2)
|
actionmailbox (= 8.0.2.1)
|
||||||
actionmailer (= 8.0.2)
|
actionmailer (= 8.0.2.1)
|
||||||
actionpack (= 8.0.2)
|
actionpack (= 8.0.2.1)
|
||||||
actiontext (= 8.0.2)
|
actiontext (= 8.0.2.1)
|
||||||
actionview (= 8.0.2)
|
actionview (= 8.0.2.1)
|
||||||
activejob (= 8.0.2)
|
activejob (= 8.0.2.1)
|
||||||
activemodel (= 8.0.2)
|
activemodel (= 8.0.2.1)
|
||||||
activerecord (= 8.0.2)
|
activerecord (= 8.0.2.1)
|
||||||
activestorage (= 8.0.2)
|
activestorage (= 8.0.2.1)
|
||||||
activesupport (= 8.0.2)
|
activesupport (= 8.0.2.1)
|
||||||
bundler (>= 1.15.0)
|
bundler (>= 1.15.0)
|
||||||
railties (= 8.0.2)
|
railties (= 8.0.2.1)
|
||||||
rails-dom-testing (2.2.0)
|
rails-dom-testing (2.2.0)
|
||||||
activesupport (>= 5.0.0)
|
activesupport (>= 5.0.0)
|
||||||
minitest
|
minitest
|
||||||
@@ -692,9 +692,9 @@ GEM
|
|||||||
rails-i18n (8.0.1)
|
rails-i18n (8.0.1)
|
||||||
i18n (>= 0.7, < 2)
|
i18n (>= 0.7, < 2)
|
||||||
railties (>= 8.0.0, < 9)
|
railties (>= 8.0.0, < 9)
|
||||||
railties (8.0.2)
|
railties (8.0.2.1)
|
||||||
actionpack (= 8.0.2)
|
actionpack (= 8.0.2.1)
|
||||||
activesupport (= 8.0.2)
|
activesupport (= 8.0.2.1)
|
||||||
irb (~> 1.13)
|
irb (~> 1.13)
|
||||||
rackup (>= 1.0.0)
|
rackup (>= 1.0.0)
|
||||||
rake (>= 12.2)
|
rake (>= 12.2)
|
||||||
@@ -725,7 +725,7 @@ GEM
|
|||||||
responders (3.1.1)
|
responders (3.1.1)
|
||||||
actionpack (>= 5.2)
|
actionpack (>= 5.2)
|
||||||
railties (>= 5.2)
|
railties (>= 5.2)
|
||||||
rexml (3.4.1)
|
rexml (3.4.4)
|
||||||
rotp (6.3.0)
|
rotp (6.3.0)
|
||||||
rouge (4.5.2)
|
rouge (4.5.2)
|
||||||
rpam2 (4.0.2)
|
rpam2 (4.0.2)
|
||||||
@@ -801,7 +801,7 @@ GEM
|
|||||||
ruby-prof (1.7.2)
|
ruby-prof (1.7.2)
|
||||||
base64
|
base64
|
||||||
ruby-progressbar (1.13.0)
|
ruby-progressbar (1.13.0)
|
||||||
ruby-saml (1.18.0)
|
ruby-saml (1.18.1)
|
||||||
nokogiri (>= 1.13.10)
|
nokogiri (>= 1.13.10)
|
||||||
rexml
|
rexml
|
||||||
ruby-vips (2.2.4)
|
ruby-vips (2.2.4)
|
||||||
@@ -869,7 +869,7 @@ GEM
|
|||||||
terrapin (1.1.0)
|
terrapin (1.1.0)
|
||||||
climate_control
|
climate_control
|
||||||
test-prof (1.4.4)
|
test-prof (1.4.4)
|
||||||
thor (1.3.2)
|
thor (1.4.0)
|
||||||
tilt (2.6.0)
|
tilt (2.6.0)
|
||||||
timeout (0.4.3)
|
timeout (0.4.3)
|
||||||
tpm-key_attestation (0.14.1)
|
tpm-key_attestation (0.14.1)
|
||||||
@@ -899,7 +899,7 @@ GEM
|
|||||||
unicode-display_width (3.1.4)
|
unicode-display_width (3.1.4)
|
||||||
unicode-emoji (~> 4.0, >= 4.0.4)
|
unicode-emoji (~> 4.0, >= 4.0.4)
|
||||||
unicode-emoji (4.0.4)
|
unicode-emoji (4.0.4)
|
||||||
uri (1.0.3)
|
uri (1.0.4)
|
||||||
useragent (0.16.11)
|
useragent (0.16.11)
|
||||||
validate_url (1.0.15)
|
validate_url (1.0.15)
|
||||||
activemodel (>= 3.0.0)
|
activemodel (>= 3.0.0)
|
||||||
@@ -932,7 +932,7 @@ GEM
|
|||||||
crack (>= 0.3.2)
|
crack (>= 0.3.2)
|
||||||
hashdiff (>= 0.4.0, < 2.0.0)
|
hashdiff (>= 0.4.0, < 2.0.0)
|
||||||
webrick (1.9.1)
|
webrick (1.9.1)
|
||||||
websocket-driver (0.7.7)
|
websocket-driver (0.8.0)
|
||||||
base64
|
base64
|
||||||
websocket-extensions (>= 0.1.0)
|
websocket-extensions (>= 0.1.0)
|
||||||
websocket-extensions (0.1.5)
|
websocket-extensions (0.1.5)
|
||||||
@@ -1096,6 +1096,7 @@ DEPENDENCIES
|
|||||||
webauthn (~> 3.0)
|
webauthn (~> 3.0)
|
||||||
webmock (~> 3.18)
|
webmock (~> 3.18)
|
||||||
webpush!
|
webpush!
|
||||||
|
websocket-driver (~> 0.8)
|
||||||
xorcist (~> 1.1)
|
xorcist (~> 1.1)
|
||||||
|
|
||||||
RUBY VERSION
|
RUBY VERSION
|
||||||
|
|||||||
11
SECURITY.md
11
SECURITY.md
@@ -13,8 +13,9 @@ A "vulnerability in Mastodon" is a vulnerability in the code distributed through
|
|||||||
|
|
||||||
## Supported Versions
|
## Supported Versions
|
||||||
|
|
||||||
| Version | Supported |
|
| Version | Supported |
|
||||||
| ------- | --------- |
|
| ------- | ---------------- |
|
||||||
| 4.3.x | Yes |
|
| 4.4.x | Yes |
|
||||||
| 4.2.x | Yes |
|
| 4.3.x | Until 2026-05-06 |
|
||||||
| < 4.2 | No |
|
| 4.2.x | Until 2026-01-08 |
|
||||||
|
| < 4.2 | No |
|
||||||
|
|||||||
@@ -22,7 +22,7 @@ class ActivityPub::LikesController < ActivityPub::BaseController
|
|||||||
def set_status
|
def set_status
|
||||||
@status = @account.statuses.find(params[:status_id])
|
@status = @account.statuses.find(params[:status_id])
|
||||||
authorize @status, :show?
|
authorize @status, :show?
|
||||||
rescue Mastodon::NotPermittedError
|
rescue ActiveRecord::RecordNotFound, Mastodon::NotPermittedError
|
||||||
not_found
|
not_found
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|||||||
@@ -25,7 +25,7 @@ class ActivityPub::RepliesController < ActivityPub::BaseController
|
|||||||
def set_status
|
def set_status
|
||||||
@status = @account.statuses.find(params[:status_id])
|
@status = @account.statuses.find(params[:status_id])
|
||||||
authorize @status, :show?
|
authorize @status, :show?
|
||||||
rescue Mastodon::NotPermittedError
|
rescue ActiveRecord::RecordNotFound, Mastodon::NotPermittedError
|
||||||
not_found
|
not_found
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|||||||
@@ -22,7 +22,7 @@ class ActivityPub::SharesController < ActivityPub::BaseController
|
|||||||
def set_status
|
def set_status
|
||||||
@status = @account.statuses.find(params[:status_id])
|
@status = @account.statuses.find(params[:status_id])
|
||||||
authorize @status, :show?
|
authorize @status, :show?
|
||||||
rescue Mastodon::NotPermittedError
|
rescue ActiveRecord::RecordNotFound, Mastodon::NotPermittedError
|
||||||
not_found
|
not_found
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|||||||
@@ -14,16 +14,20 @@ module Admin
|
|||||||
def create
|
def create
|
||||||
authorize @account, :show?
|
authorize @account, :show?
|
||||||
|
|
||||||
account_action = Admin::AccountAction.new(resource_params)
|
@account_action = Admin::AccountAction.new(resource_params)
|
||||||
account_action.target_account = @account
|
@account_action.target_account = @account
|
||||||
account_action.current_account = current_account
|
@account_action.current_account = current_account
|
||||||
|
|
||||||
account_action.save!
|
if @account_action.save
|
||||||
|
if @account_action.with_report?
|
||||||
if account_action.with_report?
|
redirect_to admin_reports_path, notice: I18n.t('admin.reports.processed_msg', id: resource_params[:report_id])
|
||||||
redirect_to admin_reports_path, notice: I18n.t('admin.reports.processed_msg', id: resource_params[:report_id])
|
else
|
||||||
|
redirect_to admin_account_path(@account.id)
|
||||||
|
end
|
||||||
else
|
else
|
||||||
redirect_to admin_account_path(@account.id)
|
@warning_presets = AccountWarningPreset.all
|
||||||
|
|
||||||
|
render :new
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|||||||
@@ -9,10 +9,16 @@ module Admin
|
|||||||
|
|
||||||
@pending_appeals_count = Appeal.pending.async_count
|
@pending_appeals_count = Appeal.pending.async_count
|
||||||
@pending_reports_count = Report.unresolved.async_count
|
@pending_reports_count = Report.unresolved.async_count
|
||||||
@pending_tags_count = Tag.pending_review.async_count
|
@pending_tags_count = pending_tags.async_count
|
||||||
@pending_users_count = User.pending.async_count
|
@pending_users_count = User.pending.async_count
|
||||||
@system_checks = Admin::SystemCheck.perform(current_user)
|
@system_checks = Admin::SystemCheck.perform(current_user)
|
||||||
@time_period = (29.days.ago.to_date...Time.now.utc.to_date)
|
@time_period = (29.days.ago.to_date...Time.now.utc.to_date)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
private
|
||||||
|
|
||||||
|
def pending_tags
|
||||||
|
::Trends::TagFilter.new(status: :pending_review).results
|
||||||
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|||||||
@@ -17,7 +17,7 @@ class Api::V1::Polls::VotesController < Api::BaseController
|
|||||||
def set_poll
|
def set_poll
|
||||||
@poll = Poll.find(params[:poll_id])
|
@poll = Poll.find(params[:poll_id])
|
||||||
authorize @poll.status, :show?
|
authorize @poll.status, :show?
|
||||||
rescue Mastodon::NotPermittedError
|
rescue ActiveRecord::RecordNotFound, Mastodon::NotPermittedError
|
||||||
not_found
|
not_found
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|||||||
@@ -17,7 +17,7 @@ class Api::V1::PollsController < Api::BaseController
|
|||||||
def set_poll
|
def set_poll
|
||||||
@poll = Poll.find(params[:id])
|
@poll = Poll.find(params[:id])
|
||||||
authorize @poll.status, :show?
|
authorize @poll.status, :show?
|
||||||
rescue Mastodon::NotPermittedError
|
rescue ActiveRecord::RecordNotFound, Mastodon::NotPermittedError
|
||||||
not_found
|
not_found
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|||||||
@@ -10,7 +10,7 @@ class Api::V1::Statuses::BaseController < Api::BaseController
|
|||||||
def set_status
|
def set_status
|
||||||
@status = Status.find(params[:status_id])
|
@status = Status.find(params[:status_id])
|
||||||
authorize @status, :show?
|
authorize @status, :show?
|
||||||
rescue Mastodon::NotPermittedError
|
rescue ActiveRecord::RecordNotFound, Mastodon::NotPermittedError
|
||||||
not_found
|
not_found
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|||||||
@@ -23,7 +23,7 @@ class Api::V1::Statuses::BookmarksController < Api::V1::Statuses::BaseController
|
|||||||
bookmark&.destroy!
|
bookmark&.destroy!
|
||||||
|
|
||||||
render json: @status, serializer: REST::StatusSerializer, relationships: StatusRelationshipsPresenter.new([@status], current_account.id, bookmarks_map: { @status.id => false })
|
render json: @status, serializer: REST::StatusSerializer, relationships: StatusRelationshipsPresenter.new([@status], current_account.id, bookmarks_map: { @status.id => false })
|
||||||
rescue Mastodon::NotPermittedError
|
rescue ActiveRecord::RecordNotFound, Mastodon::NotPermittedError
|
||||||
not_found
|
not_found
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|||||||
@@ -25,7 +25,7 @@ class Api::V1::Statuses::FavouritesController < Api::V1::Statuses::BaseControlle
|
|||||||
|
|
||||||
relationships = StatusRelationshipsPresenter.new([@status], current_account.id, favourites_map: { @status.id => false }, attributes_map: { @status.id => { favourites_count: count } })
|
relationships = StatusRelationshipsPresenter.new([@status], current_account.id, favourites_map: { @status.id => false }, attributes_map: { @status.id => { favourites_count: count } })
|
||||||
render json: @status, serializer: REST::StatusSerializer, relationships: relationships
|
render json: @status, serializer: REST::StatusSerializer, relationships: relationships
|
||||||
rescue Mastodon::NotPermittedError
|
rescue ActiveRecord::RecordNotFound, Mastodon::NotPermittedError
|
||||||
not_found
|
not_found
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|||||||
@@ -36,7 +36,7 @@ class Api::V1::Statuses::ReblogsController < Api::V1::Statuses::BaseController
|
|||||||
|
|
||||||
relationships = StatusRelationshipsPresenter.new([@status], current_account.id, reblogs_map: { @reblog.id => false }, attributes_map: { @reblog.id => { reblogs_count: count } })
|
relationships = StatusRelationshipsPresenter.new([@status], current_account.id, reblogs_map: { @reblog.id => false }, attributes_map: { @reblog.id => { reblogs_count: count } })
|
||||||
render json: @reblog, serializer: REST::StatusSerializer, relationships: relationships
|
render json: @reblog, serializer: REST::StatusSerializer, relationships: relationships
|
||||||
rescue Mastodon::NotPermittedError
|
rescue ActiveRecord::RecordNotFound, Mastodon::NotPermittedError
|
||||||
not_found
|
not_found
|
||||||
end
|
end
|
||||||
|
|
||||||
@@ -45,7 +45,7 @@ class Api::V1::Statuses::ReblogsController < Api::V1::Statuses::BaseController
|
|||||||
def set_reblog
|
def set_reblog
|
||||||
@reblog = Status.find(params[:status_id])
|
@reblog = Status.find(params[:status_id])
|
||||||
authorize @reblog, :show?
|
authorize @reblog, :show?
|
||||||
rescue Mastodon::NotPermittedError
|
rescue ActiveRecord::RecordNotFound, Mastodon::NotPermittedError
|
||||||
not_found
|
not_found
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|||||||
@@ -129,7 +129,7 @@ class Api::V1::StatusesController < Api::BaseController
|
|||||||
def set_status
|
def set_status
|
||||||
@status = Status.find(params[:id])
|
@status = Status.find(params[:id])
|
||||||
authorize @status, :show?
|
authorize @status, :show?
|
||||||
rescue Mastodon::NotPermittedError
|
rescue ActiveRecord::RecordNotFound, Mastodon::NotPermittedError
|
||||||
not_found
|
not_found
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|||||||
@@ -30,7 +30,7 @@ class Api::Web::EmbedsController < Api::Web::BaseController
|
|||||||
def set_status
|
def set_status
|
||||||
@status = Status.find(params[:id])
|
@status = Status.find(params[:id])
|
||||||
authorize @status, :show?
|
authorize @status, :show?
|
||||||
rescue Mastodon::NotPermittedError
|
rescue ActiveRecord::RecordNotFound, Mastodon::NotPermittedError
|
||||||
not_found
|
not_found
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|||||||
@@ -21,7 +21,7 @@ class AuthorizeInteractionsController < ApplicationController
|
|||||||
def set_resource
|
def set_resource
|
||||||
@resource = located_resource
|
@resource = located_resource
|
||||||
authorize(@resource, :show?) if @resource.is_a?(Status)
|
authorize(@resource, :show?) if @resource.is_a?(Status)
|
||||||
rescue Mastodon::NotPermittedError
|
rescue ActiveRecord::RecordNotFound, Mastodon::NotPermittedError
|
||||||
not_found
|
not_found
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|||||||
@@ -64,6 +64,9 @@ module SignatureVerification
|
|||||||
return (@signed_request_actor = actor) if signed_request.verified?(actor)
|
return (@signed_request_actor = actor) if signed_request.verified?(actor)
|
||||||
|
|
||||||
fail_with! "Verification failed for #{actor.to_log_human_identifier} #{actor.uri}"
|
fail_with! "Verification failed for #{actor.to_log_human_identifier} #{actor.uri}"
|
||||||
|
rescue Mastodon::MalformedHeaderError => e
|
||||||
|
@signature_verification_failure_code = 400
|
||||||
|
fail_with! e.message
|
||||||
rescue Mastodon::SignatureVerificationError => e
|
rescue Mastodon::SignatureVerificationError => e
|
||||||
fail_with! e.message
|
fail_with! e.message
|
||||||
rescue *Mastodon::HTTP_CONNECTION_ERRORS => e
|
rescue *Mastodon::HTTP_CONNECTION_ERRORS => e
|
||||||
|
|||||||
@@ -50,6 +50,13 @@ module WebAppControllerConcern
|
|||||||
return unless current_user&.require_tos_interstitial?
|
return unless current_user&.require_tos_interstitial?
|
||||||
|
|
||||||
@terms_of_service = TermsOfService.published.first
|
@terms_of_service = TermsOfService.published.first
|
||||||
|
|
||||||
|
# Handle case where terms of service have been removed from the database
|
||||||
|
if @terms_of_service.nil?
|
||||||
|
current_user.update(require_tos_interstitial: false)
|
||||||
|
return
|
||||||
|
end
|
||||||
|
|
||||||
render 'terms_of_service_interstitial/show', layout: 'auth'
|
render 'terms_of_service_interstitial/show', layout: 'auth'
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|||||||
@@ -34,7 +34,7 @@ class MediaController < ApplicationController
|
|||||||
|
|
||||||
def verify_permitted_status!
|
def verify_permitted_status!
|
||||||
authorize @media_attachment.status, :show?
|
authorize @media_attachment.status, :show?
|
||||||
rescue Mastodon::NotPermittedError
|
rescue ActiveRecord::RecordNotFound, Mastodon::NotPermittedError
|
||||||
not_found
|
not_found
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|||||||
@@ -59,7 +59,7 @@ class StatusesController < ApplicationController
|
|||||||
def set_status
|
def set_status
|
||||||
@status = @account.statuses.find(params[:id])
|
@status = @account.statuses.find(params[:id])
|
||||||
authorize @status, :show?
|
authorize @status, :show?
|
||||||
rescue Mastodon::NotPermittedError
|
rescue ActiveRecord::RecordNotFound, Mastodon::NotPermittedError
|
||||||
not_found
|
not_found
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|||||||
@@ -66,7 +66,7 @@ module ApplicationHelper
|
|||||||
|
|
||||||
def provider_sign_in_link(provider)
|
def provider_sign_in_link(provider)
|
||||||
label = Devise.omniauth_configs[provider]&.strategy&.display_name.presence || I18n.t("auth.providers.#{provider}", default: provider.to_s.chomp('_oauth2').capitalize)
|
label = Devise.omniauth_configs[provider]&.strategy&.display_name.presence || I18n.t("auth.providers.#{provider}", default: provider.to_s.chomp('_oauth2').capitalize)
|
||||||
link_to label, omniauth_authorize_path(:user, provider), class: "button button-#{provider}", method: :post
|
link_to label, omniauth_authorize_path(:user, provider), class: "btn button-#{provider}", method: :post
|
||||||
end
|
end
|
||||||
|
|
||||||
def locale_direction
|
def locale_direction
|
||||||
@@ -102,6 +102,16 @@ module ApplicationHelper
|
|||||||
policy(record).public_send(:"#{action}?")
|
policy(record).public_send(:"#{action}?")
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def conditional_link_to(condition, name, options = {}, html_options = {}, &block)
|
||||||
|
if condition && !current_page?(block_given? ? name : options)
|
||||||
|
link_to(name, options, html_options, &block)
|
||||||
|
elsif block_given?
|
||||||
|
content_tag(:span, options, html_options, &block)
|
||||||
|
else
|
||||||
|
content_tag(:span, name, html_options)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
def material_symbol(icon, attributes = {})
|
def material_symbol(icon, attributes = {})
|
||||||
safe_join(
|
safe_join(
|
||||||
[
|
[
|
||||||
|
|||||||
@@ -27,6 +27,12 @@ module ContextHelper
|
|||||||
suspended: { 'toot' => 'http://joinmastodon.org/ns#', 'suspended' => 'toot:suspended' },
|
suspended: { 'toot' => 'http://joinmastodon.org/ns#', 'suspended' => 'toot:suspended' },
|
||||||
attribution_domains: { 'toot' => 'http://joinmastodon.org/ns#', 'attributionDomains' => { '@id' => 'toot:attributionDomains', '@type' => '@id' } },
|
attribution_domains: { 'toot' => 'http://joinmastodon.org/ns#', 'attributionDomains' => { '@id' => 'toot:attributionDomains', '@type' => '@id' } },
|
||||||
quote_requests: { 'QuoteRequest' => 'https://w3id.org/fep/044f#QuoteRequest' },
|
quote_requests: { 'QuoteRequest' => 'https://w3id.org/fep/044f#QuoteRequest' },
|
||||||
|
quotes: {
|
||||||
|
'quote' => 'https://w3id.org/fep/044f#quote',
|
||||||
|
'quoteUri' => 'http://fedibird.com/ns#quoteUri',
|
||||||
|
'_misskey_quote' => 'https://misskey-hub.net/ns#_misskey_quote',
|
||||||
|
'quoteAuthorization' => { '@id' => 'https://w3id.org/fep/044f#quoteAuthorization', '@type' => '@id' },
|
||||||
|
},
|
||||||
interaction_policies: {
|
interaction_policies: {
|
||||||
'gts' => 'https://gotosocial.org/ns#',
|
'gts' => 'https://gotosocial.org/ns#',
|
||||||
'interactionPolicy' => { '@id' => 'gts:interactionPolicy', '@type' => '@id' },
|
'interactionPolicy' => { '@id' => 'gts:interactionPolicy', '@type' => '@id' },
|
||||||
|
|||||||
@@ -101,12 +101,17 @@ export const ensureComposeIsVisible = (getState) => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
export function setComposeToStatus(status, text, spoiler_text, content_type) {
|
export function setComposeToStatus(status, text, spoiler_text, content_type) {
|
||||||
return{
|
return (dispatch, getState) => {
|
||||||
type: COMPOSE_SET_STATUS,
|
const maxOptions = getState().server.getIn(['server', 'configuration', 'polls', 'max_options']);
|
||||||
status,
|
|
||||||
text,
|
dispatch({
|
||||||
spoiler_text,
|
type: COMPOSE_SET_STATUS,
|
||||||
content_type,
|
status,
|
||||||
|
text,
|
||||||
|
spoiler_text,
|
||||||
|
content_type,
|
||||||
|
maxOptions
|
||||||
|
});
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -193,8 +198,9 @@ export function directCompose(account) {
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* @param {null | string} overridePrivacy
|
* @param {null | string} overridePrivacy
|
||||||
|
* @param {undefined | Function} successCallback
|
||||||
*/
|
*/
|
||||||
export function submitCompose(overridePrivacy = null) {
|
export function submitCompose(overridePrivacy = null, successCallback = undefined) {
|
||||||
return function (dispatch, getState) {
|
return function (dispatch, getState) {
|
||||||
let status = getState().getIn(['compose', 'text'], '');
|
let status = getState().getIn(['compose', 'text'], '');
|
||||||
const media = getState().getIn(['compose', 'media_attachments']);
|
const media = getState().getIn(['compose', 'media_attachments']);
|
||||||
@@ -259,6 +265,9 @@ export function submitCompose(overridePrivacy = null) {
|
|||||||
|
|
||||||
dispatch(insertIntoTagHistory(response.data.tags, status));
|
dispatch(insertIntoTagHistory(response.data.tags, status));
|
||||||
dispatch(submitComposeSuccess({ ...response.data }));
|
dispatch(submitComposeSuccess({ ...response.data }));
|
||||||
|
if (typeof successCallback === 'function') {
|
||||||
|
successCallback(response.data);
|
||||||
|
}
|
||||||
|
|
||||||
// To make the app more responsive, immediately push the status
|
// To make the app more responsive, immediately push the status
|
||||||
// into the columns
|
// into the columns
|
||||||
|
|||||||
@@ -21,6 +21,15 @@ export function normalizeFilterResult(result) {
|
|||||||
return normalResult;
|
return normalResult;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function stripQuoteFallback(text) {
|
||||||
|
const wrapper = document.createElement('div');
|
||||||
|
wrapper.innerHTML = text;
|
||||||
|
|
||||||
|
wrapper.querySelector('.quote-inline')?.remove();
|
||||||
|
|
||||||
|
return wrapper.innerHTML;
|
||||||
|
}
|
||||||
|
|
||||||
export function normalizeStatus(status, normalOldStatus, settings) {
|
export function normalizeStatus(status, normalOldStatus, settings) {
|
||||||
const normalStatus = { ...status };
|
const normalStatus = { ...status };
|
||||||
|
|
||||||
@@ -78,6 +87,11 @@ export function normalizeStatus(status, normalOldStatus, settings) {
|
|||||||
normalStatus.spoilerHtml = emojify(escapeTextContentForBrowser(spoilerText), emojiMap);
|
normalStatus.spoilerHtml = emojify(escapeTextContentForBrowser(spoilerText), emojiMap);
|
||||||
normalStatus.hidden = (spoilerText.length > 0 || normalStatus.sensitive) && autoHideCW(settings, spoilerText);
|
normalStatus.hidden = (spoilerText.length > 0 || normalStatus.sensitive) && autoHideCW(settings, spoilerText);
|
||||||
|
|
||||||
|
// Remove quote fallback link from the DOM so it doesn't mess with paragraph margins
|
||||||
|
if (normalStatus.quote) {
|
||||||
|
normalStatus.contentHtml = stripQuoteFallback(normalStatus.contentHtml);
|
||||||
|
}
|
||||||
|
|
||||||
if (normalStatus.url && !(normalStatus.url.startsWith('http://') || normalStatus.url.startsWith('https://'))) {
|
if (normalStatus.url && !(normalStatus.url.startsWith('http://') || normalStatus.url.startsWith('https://'))) {
|
||||||
normalStatus.url = null;
|
normalStatus.url = null;
|
||||||
}
|
}
|
||||||
@@ -117,6 +131,11 @@ export function normalizeStatusTranslation(translation, status) {
|
|||||||
spoiler_text: translation.spoiler_text,
|
spoiler_text: translation.spoiler_text,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// Remove quote fallback link from the DOM so it doesn't mess with paragraph margins
|
||||||
|
if (status.get('quote')) {
|
||||||
|
normalTranslation.contentHtml = stripQuoteFallback(normalTranslation.contentHtml);
|
||||||
|
}
|
||||||
|
|
||||||
return normalTranslation;
|
return normalTranslation;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -3,7 +3,7 @@ import { browserHistory } from 'flavours/glitch/components/router';
|
|||||||
import api from '../api';
|
import api from '../api';
|
||||||
|
|
||||||
import { ensureComposeIsVisible, setComposeToStatus } from './compose';
|
import { ensureComposeIsVisible, setComposeToStatus } from './compose';
|
||||||
import { importFetchedStatus, importFetchedStatuses, importFetchedAccount } from './importer';
|
import { importFetchedStatus, importFetchedAccount } from './importer';
|
||||||
import { fetchContext } from './statuses_typed';
|
import { fetchContext } from './statuses_typed';
|
||||||
import { deleteFromTimelines } from './timelines';
|
import { deleteFromTimelines } from './timelines';
|
||||||
|
|
||||||
@@ -48,7 +48,18 @@ export function fetchStatusRequest(id, skipLoading) {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
export function fetchStatus(id, forceFetch = false, alsoFetchContext = true) {
|
/**
|
||||||
|
* @param {string} id
|
||||||
|
* @param {Object} [options]
|
||||||
|
* @param {boolean} [options.forceFetch]
|
||||||
|
* @param {boolean} [options.alsoFetchContext]
|
||||||
|
* @param {string | null | undefined} [options.parentQuotePostId]
|
||||||
|
*/
|
||||||
|
export function fetchStatus(id, {
|
||||||
|
forceFetch = false,
|
||||||
|
alsoFetchContext = true,
|
||||||
|
parentQuotePostId,
|
||||||
|
} = {}) {
|
||||||
return (dispatch, getState) => {
|
return (dispatch, getState) => {
|
||||||
const skipLoading = !forceFetch && getState().getIn(['statuses', id], null) !== null;
|
const skipLoading = !forceFetch && getState().getIn(['statuses', id], null) !== null;
|
||||||
|
|
||||||
@@ -66,7 +77,9 @@ export function fetchStatus(id, forceFetch = false, alsoFetchContext = true) {
|
|||||||
dispatch(importFetchedStatus(response.data));
|
dispatch(importFetchedStatus(response.data));
|
||||||
dispatch(fetchStatusSuccess(skipLoading));
|
dispatch(fetchStatusSuccess(skipLoading));
|
||||||
}).catch(error => {
|
}).catch(error => {
|
||||||
dispatch(fetchStatusFail(id, error, skipLoading));
|
dispatch(fetchStatusFail(id, error, skipLoading, parentQuotePostId));
|
||||||
|
if (error.status === 404)
|
||||||
|
dispatch(deleteFromTimelines(id));
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
@@ -78,22 +91,28 @@ export function fetchStatusSuccess(skipLoading) {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
export function fetchStatusFail(id, error, skipLoading) {
|
export function fetchStatusFail(id, error, skipLoading, parentQuotePostId) {
|
||||||
return {
|
return {
|
||||||
type: STATUS_FETCH_FAIL,
|
type: STATUS_FETCH_FAIL,
|
||||||
id,
|
id,
|
||||||
error,
|
error,
|
||||||
|
parentQuotePostId,
|
||||||
skipLoading,
|
skipLoading,
|
||||||
skipAlert: true,
|
skipAlert: true,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
export function redraft(status, raw_text, content_type) {
|
export function redraft(status, raw_text, content_type) {
|
||||||
return {
|
return (dispatch, getState) => {
|
||||||
type: REDRAFT,
|
const maxOptions = getState().server.getIn(['server', 'configuration', 'polls', 'max_options']);
|
||||||
status,
|
|
||||||
raw_text,
|
dispatch({
|
||||||
content_type,
|
type: REDRAFT,
|
||||||
|
status,
|
||||||
|
raw_text,
|
||||||
|
content_type,
|
||||||
|
maxOptions,
|
||||||
|
});
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,10 +1,11 @@
|
|||||||
import { apiRequestPost } from 'flavours/glitch/api';
|
import { apiRequestPost } from 'flavours/glitch/api';
|
||||||
import type { Status, StatusVisibility } from 'flavours/glitch/models/status';
|
import type { ApiStatusJSON } from 'flavours/glitch/api_types/statuses';
|
||||||
|
import type { StatusVisibility } from 'flavours/glitch/models/status';
|
||||||
|
|
||||||
export const apiReblog = (statusId: string, visibility: StatusVisibility) =>
|
export const apiReblog = (statusId: string, visibility: StatusVisibility) =>
|
||||||
apiRequestPost<{ reblog: Status }>(`v1/statuses/${statusId}/reblog`, {
|
apiRequestPost<{ reblog: ApiStatusJSON }>(`v1/statuses/${statusId}/reblog`, {
|
||||||
visibility,
|
visibility,
|
||||||
});
|
});
|
||||||
|
|
||||||
export const apiUnreblog = (statusId: string) =>
|
export const apiUnreblog = (statusId: string) =>
|
||||||
apiRequestPost<Status>(`v1/statuses/${statusId}/unreblog`);
|
apiRequestPost<ApiStatusJSON>(`v1/statuses/${statusId}/unreblog`);
|
||||||
|
|||||||
@@ -1,12 +1,30 @@
|
|||||||
|
import { useCallback } from 'react';
|
||||||
|
|
||||||
import { useLinks } from 'flavours/glitch/hooks/useLinks';
|
import { useLinks } from 'flavours/glitch/hooks/useLinks';
|
||||||
|
|
||||||
export const AccountBio: React.FC<{
|
interface AccountBioProps {
|
||||||
note: string;
|
note: string;
|
||||||
className: string;
|
className: string;
|
||||||
}> = ({ note, className }) => {
|
dropdownAccountId?: string;
|
||||||
const handleClick = useLinks();
|
}
|
||||||
|
|
||||||
if (note.length === 0 || note === '<p></p>') {
|
export const AccountBio: React.FC<AccountBioProps> = ({
|
||||||
|
note,
|
||||||
|
className,
|
||||||
|
dropdownAccountId,
|
||||||
|
}) => {
|
||||||
|
const handleClick = useLinks(!!dropdownAccountId);
|
||||||
|
const handleNodeChange = useCallback(
|
||||||
|
(node: HTMLDivElement | null) => {
|
||||||
|
if (!dropdownAccountId || !node || node.childNodes.length === 0) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
addDropdownToHashtags(node, dropdownAccountId);
|
||||||
|
},
|
||||||
|
[dropdownAccountId],
|
||||||
|
);
|
||||||
|
|
||||||
|
if (note.length === 0) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -15,6 +33,28 @@ export const AccountBio: React.FC<{
|
|||||||
className={`${className} translate`}
|
className={`${className} translate`}
|
||||||
dangerouslySetInnerHTML={{ __html: note }}
|
dangerouslySetInnerHTML={{ __html: note }}
|
||||||
onClickCapture={handleClick}
|
onClickCapture={handleClick}
|
||||||
|
ref={handleNodeChange}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
function addDropdownToHashtags(node: HTMLElement | null, accountId: string) {
|
||||||
|
if (!node) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
for (const childNode of node.childNodes) {
|
||||||
|
if (!(childNode instanceof HTMLElement)) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if (
|
||||||
|
childNode instanceof HTMLAnchorElement &&
|
||||||
|
(childNode.classList.contains('hashtag') ||
|
||||||
|
childNode.innerText.startsWith('#')) &&
|
||||||
|
!childNode.dataset.menuHashtag
|
||||||
|
) {
|
||||||
|
childNode.dataset.menuHashtag = accountId;
|
||||||
|
} else if (childNode.childNodes.length > 0) {
|
||||||
|
addDropdownToHashtags(childNode, accountId);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -5,6 +5,7 @@ import {
|
|||||||
useCallback,
|
useCallback,
|
||||||
cloneElement,
|
cloneElement,
|
||||||
Children,
|
Children,
|
||||||
|
useId,
|
||||||
} from 'react';
|
} from 'react';
|
||||||
|
|
||||||
import classNames from 'classnames';
|
import classNames from 'classnames';
|
||||||
@@ -16,6 +17,7 @@ import Overlay from 'react-overlays/Overlay';
|
|||||||
import type {
|
import type {
|
||||||
OffsetValue,
|
OffsetValue,
|
||||||
UsePopperOptions,
|
UsePopperOptions,
|
||||||
|
Placement,
|
||||||
} from 'react-overlays/esm/usePopper';
|
} from 'react-overlays/esm/usePopper';
|
||||||
|
|
||||||
import { fetchRelationships } from 'flavours/glitch/actions/accounts';
|
import { fetchRelationships } from 'flavours/glitch/actions/accounts';
|
||||||
@@ -295,6 +297,11 @@ interface DropdownProps<Item = MenuItem> {
|
|||||||
title?: string;
|
title?: string;
|
||||||
disabled?: boolean;
|
disabled?: boolean;
|
||||||
scrollable?: boolean;
|
scrollable?: boolean;
|
||||||
|
placement?: Placement;
|
||||||
|
/**
|
||||||
|
* Prevent the `ScrollableList` with this scrollKey
|
||||||
|
* from being scrolled while the dropdown is open
|
||||||
|
*/
|
||||||
scrollKey?: string;
|
scrollKey?: string;
|
||||||
status?: ImmutableMap<string, unknown>;
|
status?: ImmutableMap<string, unknown>;
|
||||||
forceDropdown?: boolean;
|
forceDropdown?: boolean;
|
||||||
@@ -316,6 +323,7 @@ export const Dropdown = <Item = MenuItem,>({
|
|||||||
title = 'Menu',
|
title = 'Menu',
|
||||||
disabled,
|
disabled,
|
||||||
scrollable,
|
scrollable,
|
||||||
|
placement = 'bottom',
|
||||||
status,
|
status,
|
||||||
forceDropdown = false,
|
forceDropdown = false,
|
||||||
renderItem,
|
renderItem,
|
||||||
@@ -331,16 +339,15 @@ export const Dropdown = <Item = MenuItem,>({
|
|||||||
);
|
);
|
||||||
const [currentId] = useState(id++);
|
const [currentId] = useState(id++);
|
||||||
const open = currentId === openDropdownId;
|
const open = currentId === openDropdownId;
|
||||||
const activeElement = useRef<HTMLElement | null>(null);
|
const buttonRef = useRef<HTMLButtonElement | null>(null);
|
||||||
const targetRef = useRef<HTMLButtonElement | null>(null);
|
const menuId = useId();
|
||||||
const prefetchAccountId = status
|
const prefetchAccountId = status
|
||||||
? status.getIn(['account', 'id'])
|
? status.getIn(['account', 'id'])
|
||||||
: undefined;
|
: undefined;
|
||||||
|
|
||||||
const handleClose = useCallback(() => {
|
const handleClose = useCallback(() => {
|
||||||
if (activeElement.current) {
|
if (buttonRef.current) {
|
||||||
activeElement.current.focus({ preventScroll: true });
|
buttonRef.current.focus({ preventScroll: true });
|
||||||
activeElement.current = null;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
dispatch(
|
dispatch(
|
||||||
@@ -375,7 +382,7 @@ export const Dropdown = <Item = MenuItem,>({
|
|||||||
[handleClose, onItemClick, items],
|
[handleClose, onItemClick, items],
|
||||||
);
|
);
|
||||||
|
|
||||||
const handleClick = useCallback(
|
const toggleDropdown = useCallback(
|
||||||
(e: React.MouseEvent | React.KeyboardEvent) => {
|
(e: React.MouseEvent | React.KeyboardEvent) => {
|
||||||
const { type } = e;
|
const { type } = e;
|
||||||
|
|
||||||
@@ -423,38 +430,6 @@ export const Dropdown = <Item = MenuItem,>({
|
|||||||
],
|
],
|
||||||
);
|
);
|
||||||
|
|
||||||
const handleMouseDown = useCallback(() => {
|
|
||||||
if (!open && document.activeElement instanceof HTMLElement) {
|
|
||||||
activeElement.current = document.activeElement;
|
|
||||||
}
|
|
||||||
}, [open]);
|
|
||||||
|
|
||||||
const handleButtonKeyDown = useCallback(
|
|
||||||
(e: React.KeyboardEvent) => {
|
|
||||||
switch (e.key) {
|
|
||||||
case ' ':
|
|
||||||
case 'Enter':
|
|
||||||
handleMouseDown();
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
},
|
|
||||||
[handleMouseDown],
|
|
||||||
);
|
|
||||||
|
|
||||||
const handleKeyPress = useCallback(
|
|
||||||
(e: React.KeyboardEvent) => {
|
|
||||||
switch (e.key) {
|
|
||||||
case ' ':
|
|
||||||
case 'Enter':
|
|
||||||
handleClick(e);
|
|
||||||
e.stopPropagation();
|
|
||||||
e.preventDefault();
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
},
|
|
||||||
[handleClick],
|
|
||||||
);
|
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
return () => {
|
return () => {
|
||||||
if (currentId === openDropdownId) {
|
if (currentId === openDropdownId) {
|
||||||
@@ -465,14 +440,16 @@ export const Dropdown = <Item = MenuItem,>({
|
|||||||
|
|
||||||
let button: React.ReactElement;
|
let button: React.ReactElement;
|
||||||
|
|
||||||
|
const buttonProps = {
|
||||||
|
disabled,
|
||||||
|
onClick: toggleDropdown,
|
||||||
|
'aria-expanded': open,
|
||||||
|
'aria-controls': menuId,
|
||||||
|
ref: buttonRef,
|
||||||
|
};
|
||||||
|
|
||||||
if (children) {
|
if (children) {
|
||||||
button = cloneElement(Children.only(children), {
|
button = cloneElement(Children.only(children), buttonProps);
|
||||||
onClick: handleClick,
|
|
||||||
onMouseDown: handleMouseDown,
|
|
||||||
onKeyDown: handleButtonKeyDown,
|
|
||||||
onKeyPress: handleKeyPress,
|
|
||||||
ref: targetRef,
|
|
||||||
});
|
|
||||||
} else if (icon && iconComponent) {
|
} else if (icon && iconComponent) {
|
||||||
button = (
|
button = (
|
||||||
<IconButton
|
<IconButton
|
||||||
@@ -480,12 +457,7 @@ export const Dropdown = <Item = MenuItem,>({
|
|||||||
iconComponent={iconComponent}
|
iconComponent={iconComponent}
|
||||||
title={title}
|
title={title}
|
||||||
active={open}
|
active={open}
|
||||||
disabled={disabled}
|
{...buttonProps}
|
||||||
onClick={handleClick}
|
|
||||||
onMouseDown={handleMouseDown}
|
|
||||||
onKeyDown={handleButtonKeyDown}
|
|
||||||
onKeyPress={handleKeyPress}
|
|
||||||
ref={targetRef}
|
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
} else {
|
} else {
|
||||||
@@ -499,13 +471,13 @@ export const Dropdown = <Item = MenuItem,>({
|
|||||||
<Overlay
|
<Overlay
|
||||||
show={open}
|
show={open}
|
||||||
offset={offset}
|
offset={offset}
|
||||||
placement='bottom'
|
placement={placement}
|
||||||
flip
|
flip
|
||||||
target={targetRef}
|
target={buttonRef}
|
||||||
popperConfig={popperConfig}
|
popperConfig={popperConfig}
|
||||||
>
|
>
|
||||||
{({ props, arrowProps, placement }) => (
|
{({ props, arrowProps, placement }) => (
|
||||||
<div {...props}>
|
<div {...props} id={menuId}>
|
||||||
<div className={`dropdown-animation dropdown-menu ${placement}`}>
|
<div className={`dropdown-animation dropdown-menu ${placement}`}>
|
||||||
<div
|
<div
|
||||||
className={`dropdown-menu__arrow ${placement}`}
|
className={`dropdown-menu__arrow ${placement}`}
|
||||||
|
|||||||
@@ -20,7 +20,7 @@ import { useDrag } from '@use-gesture/react';
|
|||||||
import { expandAccountFeaturedTimeline } from '@/flavours/glitch/actions/timelines';
|
import { expandAccountFeaturedTimeline } from '@/flavours/glitch/actions/timelines';
|
||||||
import { Icon } from '@/flavours/glitch/components/icon';
|
import { Icon } from '@/flavours/glitch/components/icon';
|
||||||
import { IconButton } from '@/flavours/glitch/components/icon_button';
|
import { IconButton } from '@/flavours/glitch/components/icon_button';
|
||||||
import StatusContainer from '@/flavours/glitch/containers/status_container';
|
import { StatusQuoteManager } from '@/flavours/glitch/components/status_quoted';
|
||||||
import { usePrevious } from '@/flavours/glitch/hooks/usePrevious';
|
import { usePrevious } from '@/flavours/glitch/hooks/usePrevious';
|
||||||
import { useAppDispatch, useAppSelector } from '@/flavours/glitch/store';
|
import { useAppDispatch, useAppSelector } from '@/flavours/glitch/store';
|
||||||
import ChevronLeftIcon from '@/material-icons/400-24px/chevron_left.svg?react';
|
import ChevronLeftIcon from '@/material-icons/400-24px/chevron_left.svg?react';
|
||||||
@@ -218,12 +218,7 @@ const FeaturedCarouselItem: React.FC<
|
|||||||
ref={handleRef}
|
ref={handleRef}
|
||||||
{...props}
|
{...props}
|
||||||
>
|
>
|
||||||
<StatusContainer
|
<StatusQuoteManager id={statusId} contextType='account' withCounters />
|
||||||
// @ts-expect-error inferred props are wrong
|
|
||||||
id={statusId}
|
|
||||||
contextType='account'
|
|
||||||
withCounters
|
|
||||||
/>
|
|
||||||
</animated.div>
|
</animated.div>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -14,7 +14,6 @@ interface Props {
|
|||||||
onClick?: React.MouseEventHandler<HTMLButtonElement>;
|
onClick?: React.MouseEventHandler<HTMLButtonElement>;
|
||||||
onMouseDown?: React.MouseEventHandler<HTMLButtonElement>;
|
onMouseDown?: React.MouseEventHandler<HTMLButtonElement>;
|
||||||
onKeyDown?: React.KeyboardEventHandler<HTMLButtonElement>;
|
onKeyDown?: React.KeyboardEventHandler<HTMLButtonElement>;
|
||||||
onKeyPress?: React.KeyboardEventHandler<HTMLButtonElement>;
|
|
||||||
active?: boolean;
|
active?: boolean;
|
||||||
expanded?: boolean;
|
expanded?: boolean;
|
||||||
style?: React.CSSProperties;
|
style?: React.CSSProperties;
|
||||||
@@ -47,7 +46,6 @@ export const IconButton = forwardRef<HTMLButtonElement, Props>(
|
|||||||
activeStyle,
|
activeStyle,
|
||||||
onClick,
|
onClick,
|
||||||
onKeyDown,
|
onKeyDown,
|
||||||
onKeyPress,
|
|
||||||
onMouseDown,
|
onMouseDown,
|
||||||
active = false,
|
active = false,
|
||||||
disabled = false,
|
disabled = false,
|
||||||
@@ -89,16 +87,6 @@ export const IconButton = forwardRef<HTMLButtonElement, Props>(
|
|||||||
[disabled, onClick],
|
[disabled, onClick],
|
||||||
);
|
);
|
||||||
|
|
||||||
const handleKeyPress: React.KeyboardEventHandler<HTMLButtonElement> =
|
|
||||||
useCallback(
|
|
||||||
(e) => {
|
|
||||||
if (!disabled) {
|
|
||||||
onKeyPress?.(e);
|
|
||||||
}
|
|
||||||
},
|
|
||||||
[disabled, onKeyPress],
|
|
||||||
);
|
|
||||||
|
|
||||||
const handleMouseDown: React.MouseEventHandler<HTMLButtonElement> =
|
const handleMouseDown: React.MouseEventHandler<HTMLButtonElement> =
|
||||||
useCallback(
|
useCallback(
|
||||||
(e) => {
|
(e) => {
|
||||||
@@ -166,7 +154,6 @@ export const IconButton = forwardRef<HTMLButtonElement, Props>(
|
|||||||
onClick={handleClick}
|
onClick={handleClick}
|
||||||
onMouseDown={handleMouseDown}
|
onMouseDown={handleMouseDown}
|
||||||
onKeyDown={handleKeyDown}
|
onKeyDown={handleKeyDown}
|
||||||
onKeyPress={handleKeyPress} // eslint-disable-line @typescript-eslint/no-deprecated
|
|
||||||
style={buttonStyle}
|
style={buttonStyle}
|
||||||
tabIndex={tabIndex}
|
tabIndex={tabIndex}
|
||||||
disabled={disabled}
|
disabled={disabled}
|
||||||
|
|||||||
@@ -190,7 +190,7 @@ class StatusContent extends PureComponent {
|
|||||||
link.classList.add('unhandled-link');
|
link.classList.add('unhandled-link');
|
||||||
|
|
||||||
link.setAttribute('target', '_blank');
|
link.setAttribute('target', '_blank');
|
||||||
link.setAttribute('rel', 'noopener nofollow noreferrer');
|
link.setAttribute('rel', 'noopener nofollow');
|
||||||
|
|
||||||
try {
|
try {
|
||||||
if (tagLinks && isLinkMisleading(link)) {
|
if (tagLinks && isLinkMisleading(link)) {
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import { useEffect, useMemo } from 'react';
|
import { useCallback, useEffect, useMemo } from 'react';
|
||||||
|
|
||||||
import { FormattedMessage } from 'react-intl';
|
import { FormattedMessage } from 'react-intl';
|
||||||
|
|
||||||
@@ -12,12 +12,15 @@ import ArticleIcon from '@/material-icons/400-24px/article.svg?react';
|
|||||||
import ChevronRightIcon from '@/material-icons/400-24px/chevron_right.svg?react';
|
import ChevronRightIcon from '@/material-icons/400-24px/chevron_right.svg?react';
|
||||||
import { Icon } from 'flavours/glitch/components/icon';
|
import { Icon } from 'flavours/glitch/components/icon';
|
||||||
import StatusContainer from 'flavours/glitch/containers/status_container';
|
import StatusContainer from 'flavours/glitch/containers/status_container';
|
||||||
|
import { domain } from 'flavours/glitch/initial_state';
|
||||||
import type { Status } from 'flavours/glitch/models/status';
|
import type { Status } from 'flavours/glitch/models/status';
|
||||||
import type { RootState } from 'flavours/glitch/store';
|
import type { RootState } from 'flavours/glitch/store';
|
||||||
import { useAppDispatch, useAppSelector } from 'flavours/glitch/store';
|
import { useAppDispatch, useAppSelector } from 'flavours/glitch/store';
|
||||||
|
|
||||||
|
import { revealAccount } from '../actions/accounts_typed';
|
||||||
import { fetchStatus } from '../actions/statuses';
|
import { fetchStatus } from '../actions/statuses';
|
||||||
import { makeGetStatus } from '../selectors';
|
import { makeGetStatus } from '../selectors';
|
||||||
|
import { getAccountHidden } from '../selectors/accounts';
|
||||||
|
|
||||||
const MAX_QUOTE_POSTS_NESTING_LEVEL = 1;
|
const MAX_QUOTE_POSTS_NESTING_LEVEL = 1;
|
||||||
|
|
||||||
@@ -37,9 +40,7 @@ const QuoteWrapper: React.FC<{
|
|||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
const NestedQuoteLink: React.FC<{
|
const NestedQuoteLink: React.FC<{ status: Status }> = ({ status }) => {
|
||||||
status: Status;
|
|
||||||
}> = ({ status }) => {
|
|
||||||
const accountId = status.get('account') as string;
|
const accountId = status.get('account') as string;
|
||||||
const account = useAppSelector((state) =>
|
const account = useAppSelector((state) =>
|
||||||
accountId ? state.accounts.get(accountId) : undefined,
|
accountId ? state.accounts.get(accountId) : undefined,
|
||||||
@@ -75,24 +76,74 @@ type GetStatusSelector = (
|
|||||||
props: { id?: string | null; contextType?: string },
|
props: { id?: string | null; contextType?: string },
|
||||||
) => Status | null;
|
) => Status | null;
|
||||||
|
|
||||||
|
const LimitedAccountHint: React.FC<{ accountId: string }> = ({ accountId }) => {
|
||||||
|
const dispatch = useAppDispatch();
|
||||||
|
const reveal = useCallback(() => {
|
||||||
|
dispatch(revealAccount({ id: accountId }));
|
||||||
|
}, [dispatch, accountId]);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<FormattedMessage
|
||||||
|
id='status.quote_error.limited_account_hint.title'
|
||||||
|
defaultMessage='This account has been hidden by the moderators of {domain}.'
|
||||||
|
values={{ domain }}
|
||||||
|
/>
|
||||||
|
<button onClick={reveal} className='link-button'>
|
||||||
|
<FormattedMessage
|
||||||
|
id='status.quote_error.limited_account_hint.action'
|
||||||
|
defaultMessage='Show anyway'
|
||||||
|
/>
|
||||||
|
</button>
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
export const QuotedStatus: React.FC<{
|
export const QuotedStatus: React.FC<{
|
||||||
quote: QuoteMap;
|
quote: QuoteMap;
|
||||||
contextType?: string;
|
contextType?: string;
|
||||||
|
parentQuotePostId?: string | null;
|
||||||
variant?: 'full' | 'link';
|
variant?: 'full' | 'link';
|
||||||
nestingLevel?: number;
|
nestingLevel?: number;
|
||||||
}> = ({ quote, contextType, nestingLevel = 1, variant = 'full' }) => {
|
}> = ({
|
||||||
|
quote,
|
||||||
|
contextType,
|
||||||
|
parentQuotePostId,
|
||||||
|
nestingLevel = 1,
|
||||||
|
variant = 'full',
|
||||||
|
}) => {
|
||||||
const dispatch = useAppDispatch();
|
const dispatch = useAppDispatch();
|
||||||
|
const quoteState = useAppSelector((state) =>
|
||||||
|
parentQuotePostId
|
||||||
|
? state.statuses.getIn([parentQuotePostId, 'quote', 'state'])
|
||||||
|
: quote.get('state'),
|
||||||
|
);
|
||||||
|
|
||||||
const quotedStatusId = quote.get('quoted_status');
|
const quotedStatusId = quote.get('quoted_status');
|
||||||
const quoteState = quote.get('state');
|
|
||||||
const status = useAppSelector((state) =>
|
const status = useAppSelector((state) =>
|
||||||
quotedStatusId ? state.statuses.get(quotedStatusId) : undefined,
|
quotedStatusId ? state.statuses.get(quotedStatusId) : undefined,
|
||||||
);
|
);
|
||||||
|
|
||||||
|
const shouldLoadQuote = !status?.get('isLoading') && quoteState !== 'deleted';
|
||||||
|
|
||||||
|
const accountId: string | null = status?.get('account', null) as
|
||||||
|
| string
|
||||||
|
| null;
|
||||||
|
|
||||||
|
const hiddenAccount = useAppSelector(
|
||||||
|
(state) => accountId && getAccountHidden(state, accountId),
|
||||||
|
);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (!status && quotedStatusId) {
|
if (shouldLoadQuote && quotedStatusId) {
|
||||||
dispatch(fetchStatus(quotedStatusId));
|
dispatch(
|
||||||
|
fetchStatus(quotedStatusId, {
|
||||||
|
parentQuotePostId,
|
||||||
|
alsoFetchContext: false,
|
||||||
|
}),
|
||||||
|
);
|
||||||
}
|
}
|
||||||
}, [status, quotedStatusId, dispatch]);
|
}, [shouldLoadQuote, quotedStatusId, parentQuotePostId, dispatch]);
|
||||||
|
|
||||||
// In order to find out whether the quoted post should be completely hidden
|
// In order to find out whether the quoted post should be completely hidden
|
||||||
// due to a matching filter, we run it through the selector used by `status_container`.
|
// due to a matching filter, we run it through the selector used by `status_container`.
|
||||||
@@ -147,6 +198,8 @@ export const QuotedStatus: React.FC<{
|
|||||||
defaultMessage='This post cannot be displayed.'
|
defaultMessage='This post cannot be displayed.'
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
|
} else if (hiddenAccount && accountId) {
|
||||||
|
quoteError = <LimitedAccountHint accountId={accountId} />;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (quoteError) {
|
if (quoteError) {
|
||||||
@@ -173,6 +226,7 @@ export const QuotedStatus: React.FC<{
|
|||||||
{canRenderChildQuote && (
|
{canRenderChildQuote && (
|
||||||
<QuotedStatus
|
<QuotedStatus
|
||||||
quote={childQuote}
|
quote={childQuote}
|
||||||
|
parentQuotePostId={quotedStatusId}
|
||||||
contextType={contextType}
|
contextType={contextType}
|
||||||
variant={
|
variant={
|
||||||
nestingLevel === MAX_QUOTE_POSTS_NESTING_LEVEL ? 'link' : 'full'
|
nestingLevel === MAX_QUOTE_POSTS_NESTING_LEVEL ? 'link' : 'full'
|
||||||
@@ -209,7 +263,11 @@ export const StatusQuoteManager = (props: StatusQuoteManagerProps) => {
|
|||||||
return (
|
return (
|
||||||
/* @ts-expect-error Status is not yet typed */
|
/* @ts-expect-error Status is not yet typed */
|
||||||
<StatusContainer {...props}>
|
<StatusContainer {...props}>
|
||||||
<QuotedStatus quote={quote} contextType={props.contextType} />
|
<QuotedStatus
|
||||||
|
quote={quote}
|
||||||
|
parentQuotePostId={status?.get('id') as string}
|
||||||
|
contextType={props.contextType}
|
||||||
|
/>
|
||||||
</StatusContainer>
|
</StatusContainer>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -6,6 +6,7 @@ import classNames from 'classnames';
|
|||||||
import { Helmet } from 'react-helmet';
|
import { Helmet } from 'react-helmet';
|
||||||
import { NavLink } from 'react-router-dom';
|
import { NavLink } from 'react-router-dom';
|
||||||
|
|
||||||
|
import { AccountBio } from '@/flavours/glitch/components/account_bio';
|
||||||
import CheckIcon from '@/material-icons/400-24px/check.svg?react';
|
import CheckIcon from '@/material-icons/400-24px/check.svg?react';
|
||||||
import LockIcon from '@/material-icons/400-24px/lock.svg?react';
|
import LockIcon from '@/material-icons/400-24px/lock.svg?react';
|
||||||
import MoreHorizIcon from '@/material-icons/400-24px/more_horiz.svg?react';
|
import MoreHorizIcon from '@/material-icons/400-24px/more_horiz.svg?react';
|
||||||
@@ -777,7 +778,6 @@ export const AccountHeader: React.FC<{
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
const content = { __html: account.note_emojified };
|
|
||||||
const displayNameHtml = { __html: account.display_name_html };
|
const displayNameHtml = { __html: account.display_name_html };
|
||||||
const fields = account.fields;
|
const fields = account.fields;
|
||||||
const isLocal = !account.acct.includes('@');
|
const isLocal = !account.acct.includes('@');
|
||||||
@@ -901,12 +901,11 @@ export const AccountHeader: React.FC<{
|
|||||||
<AccountNote accountId={accountId} />
|
<AccountNote accountId={accountId} />
|
||||||
)}
|
)}
|
||||||
|
|
||||||
{account.note.length > 0 && account.note !== '<p></p>' && (
|
<AccountBio
|
||||||
<div
|
note={account.note_emojified}
|
||||||
className='account__header__content translate'
|
dropdownAccountId={accountId}
|
||||||
dangerouslySetInnerHTML={content}
|
className='account__header__content'
|
||||||
/>
|
/>
|
||||||
)}
|
|
||||||
|
|
||||||
<div className='account__header__fields'>
|
<div className='account__header__fields'>
|
||||||
<dl>
|
<dl>
|
||||||
|
|||||||
@@ -261,7 +261,9 @@ export const AltTextModal = forwardRef<ModalRef, Props & Partial<RestoreProps>>(
|
|||||||
);
|
);
|
||||||
const lang = useAppSelector(
|
const lang = useAppSelector(
|
||||||
(state) =>
|
(state) =>
|
||||||
(state.compose as ImmutableMap<string, unknown>).get('lang') as string,
|
(state.compose as ImmutableMap<string, unknown>).get(
|
||||||
|
'language',
|
||||||
|
) as string,
|
||||||
);
|
);
|
||||||
const focusX =
|
const focusX =
|
||||||
(media?.getIn(['meta', 'focus', 'x'], 0) as number | undefined) ?? 0;
|
(media?.getIn(['meta', 'focus', 'x'], 0) as number | undefined) ?? 0;
|
||||||
|
|||||||
@@ -81,6 +81,7 @@ class ComposeForm extends ImmutablePureComponent {
|
|||||||
singleColumn: PropTypes.bool,
|
singleColumn: PropTypes.bool,
|
||||||
lang: PropTypes.string,
|
lang: PropTypes.string,
|
||||||
maxChars: PropTypes.number,
|
maxChars: PropTypes.number,
|
||||||
|
redirectOnSuccess: PropTypes.bool,
|
||||||
};
|
};
|
||||||
|
|
||||||
static defaultProps = {
|
static defaultProps = {
|
||||||
@@ -336,7 +337,7 @@ class ComposeForm extends ImmutablePureComponent {
|
|||||||
>
|
>
|
||||||
{intl.formatMessage(
|
{intl.formatMessage(
|
||||||
this.props.isEditing ?
|
this.props.isEditing ?
|
||||||
messages.saveChanges :
|
messages.saveChanges :
|
||||||
(this.props.isInReply ? messages.reply : messages.publish)
|
(this.props.isInReply ? messages.reply : messages.publish)
|
||||||
)}
|
)}
|
||||||
</Button>
|
</Button>
|
||||||
|
|||||||
@@ -47,10 +47,6 @@ const labelForRecentSearch = (search: RecentSearch) => {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const unfocus = () => {
|
|
||||||
document.querySelector('.ui')?.parentElement?.focus();
|
|
||||||
};
|
|
||||||
|
|
||||||
const ClearButton: React.FC<{
|
const ClearButton: React.FC<{
|
||||||
onClick: () => void;
|
onClick: () => void;
|
||||||
hasValue: boolean;
|
hasValue: boolean;
|
||||||
@@ -107,6 +103,11 @@ export const Search: React.FC<{
|
|||||||
}, [initialValue]);
|
}, [initialValue]);
|
||||||
const searchOptions: SearchOption[] = [];
|
const searchOptions: SearchOption[] = [];
|
||||||
|
|
||||||
|
const unfocus = useCallback(() => {
|
||||||
|
document.querySelector('.ui')?.parentElement?.focus();
|
||||||
|
setExpanded(false);
|
||||||
|
}, []);
|
||||||
|
|
||||||
if (searchEnabled) {
|
if (searchEnabled) {
|
||||||
searchOptions.push(
|
searchOptions.push(
|
||||||
{
|
{
|
||||||
@@ -282,7 +283,7 @@ export const Search: React.FC<{
|
|||||||
history.push({ pathname: '/search', search: queryParams.toString() });
|
history.push({ pathname: '/search', search: queryParams.toString() });
|
||||||
unfocus();
|
unfocus();
|
||||||
},
|
},
|
||||||
[dispatch, history],
|
[dispatch, history, unfocus],
|
||||||
);
|
);
|
||||||
|
|
||||||
const handleChange = useCallback(
|
const handleChange = useCallback(
|
||||||
@@ -402,7 +403,7 @@ export const Search: React.FC<{
|
|||||||
|
|
||||||
setQuickActions(newQuickActions);
|
setQuickActions(newQuickActions);
|
||||||
},
|
},
|
||||||
[dispatch, history, signedIn, setValue, setQuickActions, submit],
|
[signedIn, dispatch, unfocus, history, submit],
|
||||||
);
|
);
|
||||||
|
|
||||||
const handleClear = useCallback(() => {
|
const handleClear = useCallback(() => {
|
||||||
@@ -410,7 +411,7 @@ export const Search: React.FC<{
|
|||||||
setQuickActions([]);
|
setQuickActions([]);
|
||||||
setSelectedOption(-1);
|
setSelectedOption(-1);
|
||||||
unfocus();
|
unfocus();
|
||||||
}, [setValue, setQuickActions, setSelectedOption]);
|
}, [unfocus]);
|
||||||
|
|
||||||
const handleKeyDown = useCallback(
|
const handleKeyDown = useCallback(
|
||||||
(e: React.KeyboardEvent) => {
|
(e: React.KeyboardEvent) => {
|
||||||
@@ -461,7 +462,7 @@ export const Search: React.FC<{
|
|||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
[navigableOptions, value, selectedOption, setSelectedOption, submit],
|
[unfocus, navigableOptions, selectedOption, submit, value],
|
||||||
);
|
);
|
||||||
|
|
||||||
const handleFocus = useCallback(() => {
|
const handleFocus = useCallback(() => {
|
||||||
@@ -481,12 +482,38 @@ export const Search: React.FC<{
|
|||||||
}, [setExpanded, setSelectedOption, singleColumn]);
|
}, [setExpanded, setSelectedOption, singleColumn]);
|
||||||
|
|
||||||
const handleBlur = useCallback(() => {
|
const handleBlur = useCallback(() => {
|
||||||
setExpanded(false);
|
|
||||||
setSelectedOption(-1);
|
setSelectedOption(-1);
|
||||||
}, [setExpanded, setSelectedOption]);
|
}, [setSelectedOption]);
|
||||||
|
|
||||||
|
const formRef = useRef<HTMLFormElement>(null);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
// If the search popover is expanded, close it when tabbing or
|
||||||
|
// clicking outside of it or the search form, while allowing
|
||||||
|
// tabbing or clicking inside of the popover
|
||||||
|
if (expanded) {
|
||||||
|
function closeOnLeave(event: FocusEvent | MouseEvent) {
|
||||||
|
const form = formRef.current;
|
||||||
|
const isClickInsideForm =
|
||||||
|
form &&
|
||||||
|
(form === event.target || form.contains(event.target as Node));
|
||||||
|
if (!isClickInsideForm) {
|
||||||
|
setExpanded(false);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
document.addEventListener('focusin', closeOnLeave);
|
||||||
|
document.addEventListener('click', closeOnLeave);
|
||||||
|
|
||||||
|
return () => {
|
||||||
|
document.removeEventListener('focusin', closeOnLeave);
|
||||||
|
document.removeEventListener('click', closeOnLeave);
|
||||||
|
};
|
||||||
|
}
|
||||||
|
return () => null;
|
||||||
|
}, [expanded]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<form className={classNames('search', { active: expanded })}>
|
<form ref={formRef} className={classNames('search', { active: expanded })}>
|
||||||
<input
|
<input
|
||||||
ref={searchInputRef}
|
ref={searchInputRef}
|
||||||
className='search__input'
|
className='search__input'
|
||||||
@@ -506,7 +533,7 @@ export const Search: React.FC<{
|
|||||||
|
|
||||||
<ClearButton hasValue={hasValue} onClick={handleClear} />
|
<ClearButton hasValue={hasValue} onClick={handleClear} />
|
||||||
|
|
||||||
<div className='search__popout'>
|
<div className='search__popout' tabIndex={-1}>
|
||||||
{!hasValue && (
|
{!hasValue && (
|
||||||
<>
|
<>
|
||||||
<h4>
|
<h4>
|
||||||
|
|||||||
@@ -56,7 +56,7 @@ const mapStateToProps = state => ({
|
|||||||
maxChars: state.getIn(['server', 'server', 'configuration', 'statuses', 'max_characters'], 500),
|
maxChars: state.getIn(['server', 'server', 'configuration', 'statuses', 'max_characters'], 500),
|
||||||
});
|
});
|
||||||
|
|
||||||
const mapDispatchToProps = (dispatch) => ({
|
const mapDispatchToProps = (dispatch, props) => ({
|
||||||
|
|
||||||
onChange (text) {
|
onChange (text) {
|
||||||
dispatch(changeCompose(text));
|
dispatch(changeCompose(text));
|
||||||
@@ -69,7 +69,11 @@ const mapDispatchToProps = (dispatch) => ({
|
|||||||
modalProps: { overridePrivacy },
|
modalProps: { overridePrivacy },
|
||||||
}));
|
}));
|
||||||
} else {
|
} else {
|
||||||
dispatch(submitCompose(overridePrivacy));
|
dispatch(submitCompose(overridePrivacy, (status) => {
|
||||||
|
if (props.redirectOnSuccess) {
|
||||||
|
window.location.assign(status.url);
|
||||||
|
}
|
||||||
|
}));
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
|
|||||||
@@ -53,16 +53,22 @@ export const MoreLink: React.FC = () => {
|
|||||||
|
|
||||||
const menu = useMemo(() => {
|
const menu = useMemo(() => {
|
||||||
const arr: MenuItem[] = [
|
const arr: MenuItem[] = [
|
||||||
{ text: intl.formatMessage(messages.filters), href: '/filters' },
|
|
||||||
{ text: intl.formatMessage(messages.mutes), to: '/mutes' },
|
|
||||||
{ text: intl.formatMessage(messages.blocks), to: '/blocks' },
|
|
||||||
{
|
{
|
||||||
text: intl.formatMessage(messages.domainBlocks),
|
href: '/filters',
|
||||||
to: '/domain_blocks',
|
text: intl.formatMessage(messages.filters),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
to: '/mutes',
|
||||||
|
text: intl.formatMessage(messages.mutes),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
to: '/blocks',
|
||||||
|
text: intl.formatMessage(messages.blocks),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
to: '/domain_blocks',
|
||||||
|
text: intl.formatMessage(messages.domainBlocks),
|
||||||
},
|
},
|
||||||
];
|
|
||||||
|
|
||||||
arr.push(
|
|
||||||
null,
|
null,
|
||||||
{
|
{
|
||||||
href: '/settings/privacy',
|
href: '/settings/privacy',
|
||||||
@@ -80,7 +86,7 @@ export const MoreLink: React.FC = () => {
|
|||||||
href: '/settings/export',
|
href: '/settings/export',
|
||||||
text: intl.formatMessage(messages.importExport),
|
text: intl.formatMessage(messages.importExport),
|
||||||
},
|
},
|
||||||
);
|
];
|
||||||
|
|
||||||
if (canManageReports(permissions)) {
|
if (canManageReports(permissions)) {
|
||||||
arr.push(null, {
|
arr.push(null, {
|
||||||
@@ -109,7 +115,7 @@ export const MoreLink: React.FC = () => {
|
|||||||
}, [intl, dispatch, permissions]);
|
}, [intl, dispatch, permissions]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Dropdown items={menu}>
|
<Dropdown items={menu} placement='bottom-start'>
|
||||||
<button className='column-link column-link--transparent'>
|
<button className='column-link column-link--transparent'>
|
||||||
<Icon id='' icon={MoreHorizIcon} className='column-link__icon' />
|
<Icon id='' icon={MoreHorizIcon} className='column-link__icon' />
|
||||||
|
|
||||||
|
|||||||
@@ -466,6 +466,7 @@ export const CollapsibleNavigationPanel: React.FC = () => {
|
|||||||
filterTaps: true,
|
filterTaps: true,
|
||||||
bounds: isLtrDir ? { left: 0 } : { right: 0 },
|
bounds: isLtrDir ? { left: 0 } : { right: 0 },
|
||||||
rubberband: true,
|
rubberband: true,
|
||||||
|
enabled: openable,
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
|
|
||||||
|
|||||||
@@ -122,98 +122,93 @@ export const PolicyControls: React.FC = () => {
|
|||||||
value={notificationPolicy.for_not_following}
|
value={notificationPolicy.for_not_following}
|
||||||
onChange={handleFilterNotFollowing}
|
onChange={handleFilterNotFollowing}
|
||||||
options={options}
|
options={options}
|
||||||
>
|
label={
|
||||||
<strong>
|
|
||||||
<FormattedMessage
|
<FormattedMessage
|
||||||
id='notifications.policy.filter_not_following_title'
|
id='notifications.policy.filter_not_following_title'
|
||||||
defaultMessage="People you don't follow"
|
defaultMessage="People you don't follow"
|
||||||
/>
|
/>
|
||||||
</strong>
|
}
|
||||||
<span className='hint'>
|
hint={
|
||||||
<FormattedMessage
|
<FormattedMessage
|
||||||
id='notifications.policy.filter_not_following_hint'
|
id='notifications.policy.filter_not_following_hint'
|
||||||
defaultMessage='Until you manually approve them'
|
defaultMessage='Until you manually approve them'
|
||||||
/>
|
/>
|
||||||
</span>
|
}
|
||||||
</SelectWithLabel>
|
/>
|
||||||
|
|
||||||
<SelectWithLabel
|
<SelectWithLabel
|
||||||
value={notificationPolicy.for_not_followers}
|
value={notificationPolicy.for_not_followers}
|
||||||
onChange={handleFilterNotFollowers}
|
onChange={handleFilterNotFollowers}
|
||||||
options={options}
|
options={options}
|
||||||
>
|
label={
|
||||||
<strong>
|
|
||||||
<FormattedMessage
|
<FormattedMessage
|
||||||
id='notifications.policy.filter_not_followers_title'
|
id='notifications.policy.filter_not_followers_title'
|
||||||
defaultMessage='People not following you'
|
defaultMessage='People not following you'
|
||||||
/>
|
/>
|
||||||
</strong>
|
}
|
||||||
<span className='hint'>
|
hint={
|
||||||
<FormattedMessage
|
<FormattedMessage
|
||||||
id='notifications.policy.filter_not_followers_hint'
|
id='notifications.policy.filter_not_followers_hint'
|
||||||
defaultMessage='Including people who have been following you fewer than {days, plural, one {one day} other {# days}}'
|
defaultMessage='Including people who have been following you fewer than {days, plural, one {one day} other {# days}}'
|
||||||
values={{ days: 3 }}
|
values={{ days: 3 }}
|
||||||
/>
|
/>
|
||||||
</span>
|
}
|
||||||
</SelectWithLabel>
|
/>
|
||||||
|
|
||||||
<SelectWithLabel
|
<SelectWithLabel
|
||||||
value={notificationPolicy.for_new_accounts}
|
value={notificationPolicy.for_new_accounts}
|
||||||
onChange={handleFilterNewAccounts}
|
onChange={handleFilterNewAccounts}
|
||||||
options={options}
|
options={options}
|
||||||
>
|
label={
|
||||||
<strong>
|
|
||||||
<FormattedMessage
|
<FormattedMessage
|
||||||
id='notifications.policy.filter_new_accounts_title'
|
id='notifications.policy.filter_new_accounts_title'
|
||||||
defaultMessage='New accounts'
|
defaultMessage='New accounts'
|
||||||
/>
|
/>
|
||||||
</strong>
|
}
|
||||||
<span className='hint'>
|
hint={
|
||||||
<FormattedMessage
|
<FormattedMessage
|
||||||
id='notifications.policy.filter_new_accounts.hint'
|
id='notifications.policy.filter_new_accounts.hint'
|
||||||
defaultMessage='Created within the past {days, plural, one {one day} other {# days}}'
|
defaultMessage='Created within the past {days, plural, one {one day} other {# days}}'
|
||||||
values={{ days: 30 }}
|
values={{ days: 30 }}
|
||||||
/>
|
/>
|
||||||
</span>
|
}
|
||||||
</SelectWithLabel>
|
/>
|
||||||
|
|
||||||
<SelectWithLabel
|
<SelectWithLabel
|
||||||
value={notificationPolicy.for_private_mentions}
|
value={notificationPolicy.for_private_mentions}
|
||||||
onChange={handleFilterPrivateMentions}
|
onChange={handleFilterPrivateMentions}
|
||||||
options={options}
|
options={options}
|
||||||
>
|
label={
|
||||||
<strong>
|
|
||||||
<FormattedMessage
|
<FormattedMessage
|
||||||
id='notifications.policy.filter_private_mentions_title'
|
id='notifications.policy.filter_private_mentions_title'
|
||||||
defaultMessage='Unsolicited private mentions'
|
defaultMessage='Unsolicited private mentions'
|
||||||
/>
|
/>
|
||||||
</strong>
|
}
|
||||||
<span className='hint'>
|
hint={
|
||||||
<FormattedMessage
|
<FormattedMessage
|
||||||
id='notifications.policy.filter_private_mentions_hint'
|
id='notifications.policy.filter_private_mentions_hint'
|
||||||
defaultMessage="Filtered unless it's in reply to your own mention or if you follow the sender"
|
defaultMessage="Filtered unless it's in reply to your own mention or if you follow the sender"
|
||||||
/>
|
/>
|
||||||
</span>
|
}
|
||||||
</SelectWithLabel>
|
/>
|
||||||
|
|
||||||
<SelectWithLabel
|
<SelectWithLabel
|
||||||
value={notificationPolicy.for_limited_accounts}
|
value={notificationPolicy.for_limited_accounts}
|
||||||
onChange={handleFilterLimitedAccounts}
|
onChange={handleFilterLimitedAccounts}
|
||||||
options={options}
|
options={options}
|
||||||
>
|
label={
|
||||||
<strong>
|
|
||||||
<FormattedMessage
|
<FormattedMessage
|
||||||
id='notifications.policy.filter_limited_accounts_title'
|
id='notifications.policy.filter_limited_accounts_title'
|
||||||
defaultMessage='Moderated accounts'
|
defaultMessage='Moderated accounts'
|
||||||
/>
|
/>
|
||||||
</strong>
|
}
|
||||||
<span className='hint'>
|
hint={
|
||||||
<FormattedMessage
|
<FormattedMessage
|
||||||
id='notifications.policy.filter_limited_accounts_hint'
|
id='notifications.policy.filter_limited_accounts_hint'
|
||||||
defaultMessage='Limited by server moderators'
|
defaultMessage='Limited by server moderators'
|
||||||
/>
|
/>
|
||||||
</span>
|
}
|
||||||
</SelectWithLabel>
|
/>
|
||||||
</div>
|
</div>
|
||||||
</section>
|
</section>
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
import type { PropsWithChildren } from 'react';
|
import type { PropsWithChildren } from 'react';
|
||||||
import { useCallback, useState, useRef } from 'react';
|
import { useCallback, useState, useRef, useId } from 'react';
|
||||||
|
|
||||||
import classNames from 'classnames';
|
import classNames from 'classnames';
|
||||||
|
|
||||||
@@ -16,6 +16,8 @@ interface DropdownProps {
|
|||||||
options: SelectItem[];
|
options: SelectItem[];
|
||||||
disabled?: boolean;
|
disabled?: boolean;
|
||||||
onChange: (value: string) => void;
|
onChange: (value: string) => void;
|
||||||
|
'aria-labelledby': string;
|
||||||
|
'aria-describedby'?: string;
|
||||||
placement?: Placement;
|
placement?: Placement;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -24,51 +26,33 @@ const Dropdown: React.FC<DropdownProps> = ({
|
|||||||
options,
|
options,
|
||||||
disabled,
|
disabled,
|
||||||
onChange,
|
onChange,
|
||||||
|
'aria-labelledby': ariaLabelledBy,
|
||||||
|
'aria-describedby': ariaDescribedBy,
|
||||||
placement: initialPlacement = 'bottom-end',
|
placement: initialPlacement = 'bottom-end',
|
||||||
}) => {
|
}) => {
|
||||||
const activeElementRef = useRef<Element | null>(null);
|
const containerRef = useRef<HTMLDivElement>(null);
|
||||||
const containerRef = useRef(null);
|
const buttonRef = useRef<HTMLButtonElement>(null);
|
||||||
const [isOpen, setOpen] = useState<boolean>(false);
|
const [isOpen, setOpen] = useState<boolean>(false);
|
||||||
const [placement, setPlacement] = useState<Placement>(initialPlacement);
|
const [placement, setPlacement] = useState<Placement>(initialPlacement);
|
||||||
|
const uniqueId = useId();
|
||||||
const handleToggle = useCallback(() => {
|
const menuId = `${uniqueId}-menu`;
|
||||||
if (
|
const buttonLabelId = `${uniqueId}-button`;
|
||||||
isOpen &&
|
|
||||||
activeElementRef.current &&
|
|
||||||
activeElementRef.current instanceof HTMLElement
|
|
||||||
) {
|
|
||||||
activeElementRef.current.focus({ preventScroll: true });
|
|
||||||
}
|
|
||||||
|
|
||||||
setOpen(!isOpen);
|
|
||||||
}, [isOpen, setOpen]);
|
|
||||||
|
|
||||||
const handleMouseDown = useCallback(() => {
|
|
||||||
if (!isOpen) activeElementRef.current = document.activeElement;
|
|
||||||
}, [isOpen]);
|
|
||||||
|
|
||||||
const handleKeyDown = useCallback(
|
|
||||||
(e: React.KeyboardEvent) => {
|
|
||||||
switch (e.key) {
|
|
||||||
case ' ':
|
|
||||||
case 'Enter':
|
|
||||||
if (!isOpen) activeElementRef.current = document.activeElement;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
},
|
|
||||||
[isOpen],
|
|
||||||
);
|
|
||||||
|
|
||||||
const handleClose = useCallback(() => {
|
const handleClose = useCallback(() => {
|
||||||
if (
|
if (isOpen && buttonRef.current) {
|
||||||
isOpen &&
|
buttonRef.current.focus({ preventScroll: true });
|
||||||
activeElementRef.current &&
|
}
|
||||||
activeElementRef.current instanceof HTMLElement
|
|
||||||
)
|
|
||||||
activeElementRef.current.focus({ preventScroll: true });
|
|
||||||
setOpen(false);
|
setOpen(false);
|
||||||
}, [isOpen]);
|
}, [isOpen]);
|
||||||
|
|
||||||
|
const handleToggle = useCallback(() => {
|
||||||
|
if (isOpen) {
|
||||||
|
handleClose();
|
||||||
|
} else {
|
||||||
|
setOpen(true);
|
||||||
|
}
|
||||||
|
}, [isOpen, handleClose]);
|
||||||
|
|
||||||
const handleOverlayEnter = useCallback(
|
const handleOverlayEnter = useCallback(
|
||||||
(state: Partial<PopperState>) => {
|
(state: Partial<PopperState>) => {
|
||||||
if (state.placement) setPlacement(state.placement);
|
if (state.placement) setPlacement(state.placement);
|
||||||
@@ -82,13 +66,18 @@ const Dropdown: React.FC<DropdownProps> = ({
|
|||||||
<div ref={containerRef}>
|
<div ref={containerRef}>
|
||||||
<button
|
<button
|
||||||
type='button'
|
type='button'
|
||||||
|
ref={buttonRef}
|
||||||
onClick={handleToggle}
|
onClick={handleToggle}
|
||||||
onMouseDown={handleMouseDown}
|
|
||||||
onKeyDown={handleKeyDown}
|
|
||||||
disabled={disabled}
|
disabled={disabled}
|
||||||
|
aria-expanded={isOpen}
|
||||||
|
aria-controls={menuId}
|
||||||
|
aria-labelledby={`${ariaLabelledBy} ${buttonLabelId}`}
|
||||||
|
aria-describedby={ariaDescribedBy}
|
||||||
className={classNames('dropdown-button', { active: isOpen })}
|
className={classNames('dropdown-button', { active: isOpen })}
|
||||||
>
|
>
|
||||||
<span className='dropdown-button__label'>{valueOption?.text}</span>
|
<span id={buttonLabelId} className='dropdown-button__label'>
|
||||||
|
{valueOption?.text}
|
||||||
|
</span>
|
||||||
<Icon id='down' icon={ArrowDropDownIcon} />
|
<Icon id='down' icon={ArrowDropDownIcon} />
|
||||||
</button>
|
</button>
|
||||||
|
|
||||||
@@ -101,7 +90,7 @@ const Dropdown: React.FC<DropdownProps> = ({
|
|||||||
popperConfig={{ strategy: 'fixed', onFirstUpdate: handleOverlayEnter }}
|
popperConfig={{ strategy: 'fixed', onFirstUpdate: handleOverlayEnter }}
|
||||||
>
|
>
|
||||||
{({ props, placement }) => (
|
{({ props, placement }) => (
|
||||||
<div {...props}>
|
<div {...props} id={menuId}>
|
||||||
<div
|
<div
|
||||||
className={`dropdown-animation privacy-dropdown__dropdown ${placement}`}
|
className={`dropdown-animation privacy-dropdown__dropdown ${placement}`}
|
||||||
>
|
>
|
||||||
@@ -123,6 +112,8 @@ const Dropdown: React.FC<DropdownProps> = ({
|
|||||||
interface Props {
|
interface Props {
|
||||||
value: string;
|
value: string;
|
||||||
options: SelectItem[];
|
options: SelectItem[];
|
||||||
|
label: string | React.ReactElement;
|
||||||
|
hint: string | React.ReactElement;
|
||||||
disabled?: boolean;
|
disabled?: boolean;
|
||||||
onChange: (value: string) => void;
|
onChange: (value: string) => void;
|
||||||
}
|
}
|
||||||
@@ -130,13 +121,26 @@ interface Props {
|
|||||||
export const SelectWithLabel: React.FC<PropsWithChildren<Props>> = ({
|
export const SelectWithLabel: React.FC<PropsWithChildren<Props>> = ({
|
||||||
value,
|
value,
|
||||||
options,
|
options,
|
||||||
|
label,
|
||||||
|
hint,
|
||||||
disabled,
|
disabled,
|
||||||
children,
|
|
||||||
onChange,
|
onChange,
|
||||||
}) => {
|
}) => {
|
||||||
|
const uniqueId = useId();
|
||||||
|
const labelId = `${uniqueId}-label`;
|
||||||
|
const descId = `${uniqueId}-desc`;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
// This label is only used for its click-forwarding behaviour,
|
||||||
|
// accessible names are assigned manually
|
||||||
|
// eslint-disable-next-line jsx-a11y/label-has-associated-control
|
||||||
<label className='app-form__toggle'>
|
<label className='app-form__toggle'>
|
||||||
<div className='app-form__toggle__label'>{children}</div>
|
<div className='app-form__toggle__label'>
|
||||||
|
<strong id={labelId}>{label}</strong>
|
||||||
|
<span className='hint' id={descId}>
|
||||||
|
{hint}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
|
||||||
<div className='app-form__toggle__toggle'>
|
<div className='app-form__toggle__toggle'>
|
||||||
<div>
|
<div>
|
||||||
@@ -144,6 +148,8 @@ export const SelectWithLabel: React.FC<PropsWithChildren<Props>> = ({
|
|||||||
value={value}
|
value={value}
|
||||||
onChange={onChange}
|
onChange={onChange}
|
||||||
disabled={disabled}
|
disabled={disabled}
|
||||||
|
aria-labelledby={labelId}
|
||||||
|
aria-describedby={descId}
|
||||||
options={options}
|
options={options}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import { useCallback } from 'react';
|
import { useCallback, useMemo } from 'react';
|
||||||
|
|
||||||
import { defineMessages, useIntl } from 'react-intl';
|
import { defineMessages, useIntl } from 'react-intl';
|
||||||
|
|
||||||
@@ -24,6 +24,10 @@ import { openModal } from 'flavours/glitch/actions/modal';
|
|||||||
import { IconButton } from 'flavours/glitch/components/icon_button';
|
import { IconButton } from 'flavours/glitch/components/icon_button';
|
||||||
import { useIdentity } from 'flavours/glitch/identity_context';
|
import { useIdentity } from 'flavours/glitch/identity_context';
|
||||||
import { me } from 'flavours/glitch/initial_state';
|
import { me } from 'flavours/glitch/initial_state';
|
||||||
|
import type { Account } from 'flavours/glitch/models/account';
|
||||||
|
import type { Status } from 'flavours/glitch/models/status';
|
||||||
|
import { makeGetStatus } from 'flavours/glitch/selectors';
|
||||||
|
import type { RootState } from 'flavours/glitch/store';
|
||||||
import { useAppSelector, useAppDispatch } from 'flavours/glitch/store';
|
import { useAppSelector, useAppDispatch } from 'flavours/glitch/store';
|
||||||
|
|
||||||
const messages = defineMessages({
|
const messages = defineMessages({
|
||||||
@@ -50,6 +54,11 @@ const messages = defineMessages({
|
|||||||
open: { id: 'status.open', defaultMessage: 'Expand this status' },
|
open: { id: 'status.open', defaultMessage: 'Expand this status' },
|
||||||
});
|
});
|
||||||
|
|
||||||
|
type GetStatusSelector = (
|
||||||
|
state: RootState,
|
||||||
|
props: { id?: string | null; contextType?: string },
|
||||||
|
) => Status | null;
|
||||||
|
|
||||||
export const Footer: React.FC<{
|
export const Footer: React.FC<{
|
||||||
statusId: string;
|
statusId: string;
|
||||||
withOpenButton?: boolean;
|
withOpenButton?: boolean;
|
||||||
@@ -59,11 +68,9 @@ export const Footer: React.FC<{
|
|||||||
const intl = useIntl();
|
const intl = useIntl();
|
||||||
const history = useHistory();
|
const history = useHistory();
|
||||||
const dispatch = useAppDispatch();
|
const dispatch = useAppDispatch();
|
||||||
const status = useAppSelector((state) => state.statuses.get(statusId));
|
const getStatus = useMemo(() => makeGetStatus(), []) as GetStatusSelector;
|
||||||
const accountId = status?.get('account') as string | undefined;
|
const status = useAppSelector((state) => getStatus(state, { id: statusId }));
|
||||||
const account = useAppSelector((state) =>
|
const account = status?.get('account') as Account | undefined;
|
||||||
accountId ? state.accounts.get(accountId) : undefined,
|
|
||||||
);
|
|
||||||
const askReplyConfirmation = useAppSelector(
|
const askReplyConfirmation = useAppSelector(
|
||||||
(state) => (state.compose.get('text') as string).trim().length !== 0,
|
(state) => (state.compose.get('text') as string).trim().length !== 0,
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -5,7 +5,7 @@ import ModalContainer from 'flavours/glitch/features/ui/containers/modal_contain
|
|||||||
|
|
||||||
const Compose = () => (
|
const Compose = () => (
|
||||||
<>
|
<>
|
||||||
<ComposeFormContainer autoFocus withoutNavigation />
|
<ComposeFormContainer autoFocus withoutNavigation redirectOnSuccess />
|
||||||
<AlertsController />
|
<AlertsController />
|
||||||
<ModalContainer />
|
<ModalContainer />
|
||||||
<LoadingBarContainer className='loading-bar' />
|
<LoadingBarContainer className='loading-bar' />
|
||||||
|
|||||||
@@ -38,7 +38,7 @@ const Embed: React.FC<{ id: string }> = ({ id }) => {
|
|||||||
const dispatchRenderSignal = useRenderSignal();
|
const dispatchRenderSignal = useRenderSignal();
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
dispatch(fetchStatus(id, false, false));
|
dispatch(fetchStatus(id, { alsoFetchContext: false }));
|
||||||
}, [dispatch, id]);
|
}, [dispatch, id]);
|
||||||
|
|
||||||
const handleToggleHidden = useCallback(() => {
|
const handleToggleHidden = useCallback(() => {
|
||||||
|
|||||||
@@ -26,18 +26,23 @@ const getHostname = url => {
|
|||||||
|
|
||||||
const domParser = new DOMParser();
|
const domParser = new DOMParser();
|
||||||
|
|
||||||
const addAutoPlay = html => {
|
const handleIframeUrl = (html, url, providerName) => {
|
||||||
const document = domParser.parseFromString(html, 'text/html').documentElement;
|
const document = domParser.parseFromString(html, 'text/html').documentElement;
|
||||||
const iframe = document.querySelector('iframe');
|
const iframe = document.querySelector('iframe');
|
||||||
|
const startTime = new URL(url).searchParams.get('t')
|
||||||
|
|
||||||
if (iframe) {
|
if (iframe) {
|
||||||
if (iframe.src.indexOf('?') !== -1) {
|
const iframeUrl = new URL(iframe.src)
|
||||||
iframe.src += '&';
|
|
||||||
} else {
|
iframeUrl.searchParams.set('autoplay', 1)
|
||||||
iframe.src += '?';
|
iframeUrl.searchParams.set('auto_play', 1)
|
||||||
|
|
||||||
|
if (providerName === 'YouTube') {
|
||||||
|
iframeUrl.searchParams.set('start', startTime || '');
|
||||||
|
iframe.referrerPolicy = 'strict-origin-when-cross-origin';
|
||||||
}
|
}
|
||||||
|
|
||||||
iframe.src += 'autoplay=1&auto_play=1';
|
iframe.src = iframeUrl.href
|
||||||
|
|
||||||
// DOM parser creates html/body elements around original HTML fragment,
|
// DOM parser creates html/body elements around original HTML fragment,
|
||||||
// so we need to get innerHTML out of the body and not the entire document
|
// so we need to get innerHTML out of the body and not the entire document
|
||||||
@@ -103,7 +108,7 @@ export default class Card extends PureComponent {
|
|||||||
|
|
||||||
renderVideo () {
|
renderVideo () {
|
||||||
const { card } = this.props;
|
const { card } = this.props;
|
||||||
const content = { __html: addAutoPlay(card.get('html')) };
|
const content = { __html: handleIframeUrl(card.get('html'), card.get('url'), card.get('provider_name')) };
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div
|
<div
|
||||||
|
|||||||
@@ -418,7 +418,11 @@ export const DetailedStatus: React.FC<{
|
|||||||
{hashtagBar}
|
{hashtagBar}
|
||||||
|
|
||||||
{status.get('quote') && (
|
{status.get('quote') && (
|
||||||
<QuotedStatus quote={status.get('quote')} />
|
<QuotedStatus
|
||||||
|
quote={status.get('quote')}
|
||||||
|
parentQuotePostId={status.get('id')}
|
||||||
|
contextType='thread'
|
||||||
|
/>
|
||||||
)}
|
)}
|
||||||
</>
|
</>
|
||||||
)}
|
)}
|
||||||
|
|||||||
@@ -38,7 +38,7 @@ class FilterModal extends ImmutablePureComponent {
|
|||||||
|
|
||||||
handleSuccess = () => {
|
handleSuccess = () => {
|
||||||
const { dispatch, statusId } = this.props;
|
const { dispatch, statusId } = this.props;
|
||||||
dispatch(fetchStatus(statusId, true));
|
dispatch(fetchStatus(statusId, {forceFetch: true}));
|
||||||
this.setState({ isSubmitting: false, isSubmitted: true, step: 'submitted' });
|
this.setState({ isSubmitting: false, isSubmitted: true, step: 'submitted' });
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -201,8 +201,6 @@ class MediaModal extends ImmutablePureComponent {
|
|||||||
preview={image.get('preview_url')}
|
preview={image.get('preview_url')}
|
||||||
blurhash={image.get('blurhash')}
|
blurhash={image.get('blurhash')}
|
||||||
src={image.get('url')}
|
src={image.get('url')}
|
||||||
width={image.get('width')}
|
|
||||||
height={image.get('height')}
|
|
||||||
frameRate={image.getIn(['meta', 'original', 'frame_rate'])}
|
frameRate={image.getIn(['meta', 'original', 'frame_rate'])}
|
||||||
startTime={currentTime || 0}
|
startTime={currentTime || 0}
|
||||||
startPlaying={autoPlay || false}
|
startPlaying={autoPlay || false}
|
||||||
@@ -218,8 +216,6 @@ class MediaModal extends ImmutablePureComponent {
|
|||||||
return (
|
return (
|
||||||
<GIFV
|
<GIFV
|
||||||
src={image.get('url')}
|
src={image.get('url')}
|
||||||
width={width}
|
|
||||||
height={height}
|
|
||||||
key={image.get('url')}
|
key={image.get('url')}
|
||||||
alt={description}
|
alt={description}
|
||||||
lang={lang}
|
lang={lang}
|
||||||
|
|||||||
@@ -92,8 +92,7 @@ const messages = defineMessages({
|
|||||||
|
|
||||||
const mapStateToProps = state => ({
|
const mapStateToProps = state => ({
|
||||||
layout: state.getIn(['meta', 'layout']),
|
layout: state.getIn(['meta', 'layout']),
|
||||||
hasComposingText: state.getIn(['compose', 'text']).trim().length !== 0,
|
hasComposingContents: state.getIn(['compose', 'text']).trim().length !== 0 || state.getIn(['compose', 'media_attachments']).size > 0 || state.getIn(['compose', 'poll']) !== null,
|
||||||
hasMediaAttachments: state.getIn(['compose', 'media_attachments']).size > 0,
|
|
||||||
canUploadMore: !state.getIn(['compose', 'media_attachments']).some(x => ['audio', 'video'].includes(x.get('type'))) && state.getIn(['compose', 'media_attachments']).size < 4,
|
canUploadMore: !state.getIn(['compose', 'media_attachments']).some(x => ['audio', 'video'].includes(x.get('type'))) && state.getIn(['compose', 'media_attachments']).size < 4,
|
||||||
isWide: state.getIn(['local_settings', 'stretch']),
|
isWide: state.getIn(['local_settings', 'stretch']),
|
||||||
fullWidthColumns: state.getIn(['local_settings', 'fullwidth_columns']),
|
fullWidthColumns: state.getIn(['local_settings', 'fullwidth_columns']),
|
||||||
@@ -284,8 +283,7 @@ class UI extends PureComponent {
|
|||||||
fullWidthColumns: PropTypes.bool,
|
fullWidthColumns: PropTypes.bool,
|
||||||
systemFontUi: PropTypes.bool,
|
systemFontUi: PropTypes.bool,
|
||||||
isComposing: PropTypes.bool,
|
isComposing: PropTypes.bool,
|
||||||
hasComposingText: PropTypes.bool,
|
hasComposingContents: PropTypes.bool,
|
||||||
hasMediaAttachments: PropTypes.bool,
|
|
||||||
canUploadMore: PropTypes.bool,
|
canUploadMore: PropTypes.bool,
|
||||||
intl: PropTypes.object.isRequired,
|
intl: PropTypes.object.isRequired,
|
||||||
unreadNotifications: PropTypes.number,
|
unreadNotifications: PropTypes.number,
|
||||||
@@ -304,11 +302,11 @@ class UI extends PureComponent {
|
|||||||
};
|
};
|
||||||
|
|
||||||
handleBeforeUnload = e => {
|
handleBeforeUnload = e => {
|
||||||
const { intl, dispatch, hasComposingText, hasMediaAttachments } = this.props;
|
const { intl, dispatch, hasComposingContents } = this.props;
|
||||||
|
|
||||||
dispatch(synchronouslySubmitMarkers());
|
dispatch(synchronouslySubmitMarkers());
|
||||||
|
|
||||||
if (hasComposingText || hasMediaAttachments) {
|
if (hasComposingContents) {
|
||||||
// Setting returnValue to any string causes confirmation dialog.
|
// Setting returnValue to any string causes confirmation dialog.
|
||||||
// Many browsers no longer display this text to users,
|
// Many browsers no longer display this text to users,
|
||||||
// but we set user-friendly message for other browsers, e.g. Edge.
|
// but we set user-friendly message for other browsers, e.g. Edge.
|
||||||
|
|||||||
@@ -8,13 +8,14 @@ import { openURL } from 'flavours/glitch/actions/search';
|
|||||||
import { useAppDispatch } from 'flavours/glitch/store';
|
import { useAppDispatch } from 'flavours/glitch/store';
|
||||||
|
|
||||||
const isMentionClick = (element: HTMLAnchorElement) =>
|
const isMentionClick = (element: HTMLAnchorElement) =>
|
||||||
element.classList.contains('mention');
|
element.classList.contains('mention') &&
|
||||||
|
!element.classList.contains('hashtag');
|
||||||
|
|
||||||
const isHashtagClick = (element: HTMLAnchorElement) =>
|
const isHashtagClick = (element: HTMLAnchorElement) =>
|
||||||
element.textContent?.[0] === '#' ||
|
element.textContent?.[0] === '#' ||
|
||||||
element.previousSibling?.textContent?.endsWith('#');
|
element.previousSibling?.textContent?.endsWith('#');
|
||||||
|
|
||||||
export const useLinks = () => {
|
export const useLinks = (skipHashtags?: boolean) => {
|
||||||
const history = useHistory();
|
const history = useHistory();
|
||||||
const dispatch = useAppDispatch();
|
const dispatch = useAppDispatch();
|
||||||
|
|
||||||
@@ -61,12 +62,12 @@ export const useLinks = () => {
|
|||||||
if (isMentionClick(target)) {
|
if (isMentionClick(target)) {
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
void handleMentionClick(target);
|
void handleMentionClick(target);
|
||||||
} else if (isHashtagClick(target)) {
|
} else if (isHashtagClick(target) && !skipHashtags) {
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
handleHashtagClick(target);
|
handleHashtagClick(target);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
[handleMentionClick, handleHashtagClick],
|
[skipHashtags, handleMentionClick, handleHashtagClick],
|
||||||
);
|
);
|
||||||
|
|
||||||
return handleClick;
|
return handleClick;
|
||||||
|
|||||||
@@ -6,12 +6,8 @@
|
|||||||
"account.view_full_profile": "عرض الملف الشخصي كاملاً",
|
"account.view_full_profile": "عرض الملف الشخصي كاملاً",
|
||||||
"boost_modal.missing_description": "هذا المنشور يحتوي على وسائط بلا وصف",
|
"boost_modal.missing_description": "هذا المنشور يحتوي على وسائط بلا وصف",
|
||||||
"column.favourited_by": "المفضلة من قبل",
|
"column.favourited_by": "المفضلة من قبل",
|
||||||
"column.heading": "متنوعة",
|
|
||||||
"column.reblogged_by": "المرقى من قبل",
|
"column.reblogged_by": "المرقى من قبل",
|
||||||
"column.subheading": "خيارات متنوعة",
|
|
||||||
"column_header.profile": "الملف الشخصي",
|
"column_header.profile": "الملف الشخصي",
|
||||||
"column_subheading.lists": "القوائم",
|
|
||||||
"column_subheading.navigation": "التنقل",
|
|
||||||
"community.column_settings.allow_local_only": "إظهار المنشورات المحلية فقط",
|
"community.column_settings.allow_local_only": "إظهار المنشورات المحلية فقط",
|
||||||
"compose.attach.doodle": "الرسوم و التخمين",
|
"compose.attach.doodle": "الرسوم و التخمين",
|
||||||
"compose.change_federation": "تغيير اعدادات الفيديرالية",
|
"compose.change_federation": "تغيير اعدادات الفيديرالية",
|
||||||
@@ -36,8 +32,6 @@
|
|||||||
"keyboard_shortcuts.secondary_toot": "لإرسال التبويق باستخدام إعدادات الخصوصية الثانوية",
|
"keyboard_shortcuts.secondary_toot": "لإرسال التبويق باستخدام إعدادات الخصوصية الثانوية",
|
||||||
"moved_to_warning": "عُلِّم هذا الحساب بأنه انتقل إلى {moved_to_link}، لذا قد لا يقبل متابعات جديدة.",
|
"moved_to_warning": "عُلِّم هذا الحساب بأنه انتقل إلى {moved_to_link}، لذا قد لا يقبل متابعات جديدة.",
|
||||||
"navigation_bar.app_settings": "إعدادات التطبيق",
|
"navigation_bar.app_settings": "إعدادات التطبيق",
|
||||||
"navigation_bar.keyboard_shortcuts": "اختصارات لوحة المفاتيح",
|
|
||||||
"navigation_bar.misc": "متنوع",
|
|
||||||
"settings.always_show_spoilers_field": "تمكين دائما حقل تحذير المحتوى",
|
"settings.always_show_spoilers_field": "تمكين دائما حقل تحذير المحتوى",
|
||||||
"settings.close": "إغلاق",
|
"settings.close": "إغلاق",
|
||||||
"settings.content_warnings": "Content warnings",
|
"settings.content_warnings": "Content warnings",
|
||||||
|
|||||||
@@ -6,12 +6,8 @@
|
|||||||
"account.view_full_profile": "Zobrazit celý profil",
|
"account.view_full_profile": "Zobrazit celý profil",
|
||||||
"boost_modal.missing_description": "Příspěvek obsahuje obrázky bez popisků",
|
"boost_modal.missing_description": "Příspěvek obsahuje obrázky bez popisků",
|
||||||
"column.favourited_by": "Oblíbeno uživatelem",
|
"column.favourited_by": "Oblíbeno uživatelem",
|
||||||
"column.heading": "Různé",
|
|
||||||
"column.reblogged_by": "Boostnuto uživatelem",
|
"column.reblogged_by": "Boostnuto uživatelem",
|
||||||
"column.subheading": "Různé",
|
|
||||||
"column_header.profile": "Profil",
|
"column_header.profile": "Profil",
|
||||||
"column_subheading.lists": "Seznamy",
|
|
||||||
"column_subheading.navigation": "Navigace",
|
|
||||||
"community.column_settings.allow_local_only": "Zobrazit pouze místní tooty",
|
"community.column_settings.allow_local_only": "Zobrazit pouze místní tooty",
|
||||||
"compose.attach.doodle": "Nakreslete něco",
|
"compose.attach.doodle": "Nakreslete něco",
|
||||||
"compose.change_federation": "Změnit nastavení federace",
|
"compose.change_federation": "Změnit nastavení federace",
|
||||||
@@ -45,8 +41,6 @@
|
|||||||
"keyboard_shortcuts.secondary_toot": "pro odeslání příspěvku s sekundárním nastavením soukromí",
|
"keyboard_shortcuts.secondary_toot": "pro odeslání příspěvku s sekundárním nastavením soukromí",
|
||||||
"moved_to_warning": "Tento účet je označen jako přesunut na {moved_to_link}, a proto nemusí přijímat nové sledování.",
|
"moved_to_warning": "Tento účet je označen jako přesunut na {moved_to_link}, a proto nemusí přijímat nové sledování.",
|
||||||
"navigation_bar.app_settings": "Nastavení aplikace",
|
"navigation_bar.app_settings": "Nastavení aplikace",
|
||||||
"navigation_bar.keyboard_shortcuts": "Klávesové zkratky",
|
|
||||||
"navigation_bar.misc": "Různé",
|
|
||||||
"notifications.column_settings.filter_bar.show_bar": "Zobrazit panel filtrů",
|
"notifications.column_settings.filter_bar.show_bar": "Zobrazit panel filtrů",
|
||||||
"settings.always_show_spoilers_field": "Vždy zobrazit pole pro varování o obsahu",
|
"settings.always_show_spoilers_field": "Vždy zobrazit pole pro varování o obsahu",
|
||||||
"settings.close": "Zavřít",
|
"settings.close": "Zavřít",
|
||||||
|
|||||||
@@ -6,12 +6,8 @@
|
|||||||
"account.view_full_profile": "Dangos proffil cyfan",
|
"account.view_full_profile": "Dangos proffil cyfan",
|
||||||
"boost_modal.missing_description": "Mae'r tŵt yma'n cynnwys ychydig gyfryngau heb ddisgrifiad",
|
"boost_modal.missing_description": "Mae'r tŵt yma'n cynnwys ychydig gyfryngau heb ddisgrifiad",
|
||||||
"column.favourited_by": "Wedi'i hoffi gan",
|
"column.favourited_by": "Wedi'i hoffi gan",
|
||||||
"column.heading": "Misg",
|
|
||||||
"column.reblogged_by": "Wedi'i bŵstio gan",
|
"column.reblogged_by": "Wedi'i bŵstio gan",
|
||||||
"column.subheading": "Opsiynnau arall",
|
|
||||||
"column_header.profile": "Proffil",
|
"column_header.profile": "Proffil",
|
||||||
"column_subheading.lists": "Rhestri",
|
|
||||||
"column_subheading.navigation": "Llywio",
|
|
||||||
"community.column_settings.allow_local_only": "Dangos tŵtiau lleol yn unig",
|
"community.column_settings.allow_local_only": "Dangos tŵtiau lleol yn unig",
|
||||||
"compose.content-type.html": "HTML",
|
"compose.content-type.html": "HTML",
|
||||||
"compose.content-type.markdown": "Markdown",
|
"compose.content-type.markdown": "Markdown",
|
||||||
|
|||||||
@@ -2,7 +2,6 @@
|
|||||||
"home.column_settings.advanced": "Avanceret",
|
"home.column_settings.advanced": "Avanceret",
|
||||||
"home.column_settings.show_direct": "Vis private omtaler",
|
"home.column_settings.show_direct": "Vis private omtaler",
|
||||||
"navigation_bar.app_settings": "Appindstillinger",
|
"navigation_bar.app_settings": "Appindstillinger",
|
||||||
"navigation_bar.misc": "Diverse",
|
|
||||||
"settings.always_show_spoilers_field": "Vis altid feltet til indholdsadvarsel",
|
"settings.always_show_spoilers_field": "Vis altid feltet til indholdsadvarsel",
|
||||||
"settings.close": "Luk",
|
"settings.close": "Luk",
|
||||||
"settings.content_warnings": "Indholdsadvarsler",
|
"settings.content_warnings": "Indholdsadvarsler",
|
||||||
|
|||||||
@@ -6,12 +6,8 @@
|
|||||||
"account.view_full_profile": "Vollständiges Profil anzeigen",
|
"account.view_full_profile": "Vollständiges Profil anzeigen",
|
||||||
"boost_modal.missing_description": "Dieser Toot enthält Medien ohne Beschreibung",
|
"boost_modal.missing_description": "Dieser Toot enthält Medien ohne Beschreibung",
|
||||||
"column.favourited_by": "Favorisiert von",
|
"column.favourited_by": "Favorisiert von",
|
||||||
"column.heading": "Sonstiges",
|
|
||||||
"column.reblogged_by": "Geteilt von",
|
"column.reblogged_by": "Geteilt von",
|
||||||
"column.subheading": "Sonstige Optionen",
|
|
||||||
"column_header.profile": "Profil",
|
"column_header.profile": "Profil",
|
||||||
"column_subheading.lists": "Listen",
|
|
||||||
"column_subheading.navigation": "Navigation",
|
|
||||||
"community.column_settings.allow_local_only": "Nur-lokale Toots anzeigen",
|
"community.column_settings.allow_local_only": "Nur-lokale Toots anzeigen",
|
||||||
"compose.attach.doodle": "Male etwas",
|
"compose.attach.doodle": "Male etwas",
|
||||||
"compose.change_federation": "Föderationseinstellungen ändern",
|
"compose.change_federation": "Föderationseinstellungen ändern",
|
||||||
@@ -45,8 +41,6 @@
|
|||||||
"keyboard_shortcuts.secondary_toot": "Toot mit sekundärer Privatsphäreeinstellung absenden",
|
"keyboard_shortcuts.secondary_toot": "Toot mit sekundärer Privatsphäreeinstellung absenden",
|
||||||
"moved_to_warning": "Dieses Konto ist als verschoben zu {moved_to_link} markiert und akzeptiert daher keine neuen Follower.",
|
"moved_to_warning": "Dieses Konto ist als verschoben zu {moved_to_link} markiert und akzeptiert daher keine neuen Follower.",
|
||||||
"navigation_bar.app_settings": "App-Einstellungen",
|
"navigation_bar.app_settings": "App-Einstellungen",
|
||||||
"navigation_bar.keyboard_shortcuts": "Tastaturkürzel",
|
|
||||||
"navigation_bar.misc": "Sonstiges",
|
|
||||||
"notifications.column_settings.filter_bar.show_bar": "Filterleiste anzeigen",
|
"notifications.column_settings.filter_bar.show_bar": "Filterleiste anzeigen",
|
||||||
"settings.always_show_spoilers_field": "Das Inhaltswarnungs-Feld immer aktivieren",
|
"settings.always_show_spoilers_field": "Das Inhaltswarnungs-Feld immer aktivieren",
|
||||||
"settings.close": "Schließen",
|
"settings.close": "Schließen",
|
||||||
|
|||||||
@@ -1,4 +1,92 @@
|
|||||||
{
|
{
|
||||||
"settings.content_warnings": "Content warnings",
|
"about.fork_disclaimer": "Το Glitch-soc είναι ελεύθερο λογισμικό ανοιχτού κώδικα που είναι αντιγραφή από το Mastodon.",
|
||||||
"settings.preferences": "Preferences"
|
"account.disclaimer_full": "Οι παρακάτω πληροφορίες μπορεί να αντικατοπτρίζουν το προφίλ του χρήστη ελλιπώς.",
|
||||||
|
"account.follows": "Ακολουθεί",
|
||||||
|
"account.suspended_disclaimer_full": "Αυτός ο χρήστης έχει ανασταλεί από έναν συντονιστή.",
|
||||||
|
"account.view_full_profile": "Προβολή πλήρους προφίλ",
|
||||||
|
"boost_modal.missing_description": "Αυτό το τουτ περιέχει κάποια μέσα χωρίς περιγραφή",
|
||||||
|
"column.favourited_by": "Αγαπήθηκε από",
|
||||||
|
"column.reblogged_by": "Ενισχύθηκε από",
|
||||||
|
"column_header.profile": "Προφίλ",
|
||||||
|
"community.column_settings.allow_local_only": "Εμφάνιση τοπικών τουτ",
|
||||||
|
"compose.attach.doodle": "Σχεδίασε κάτι",
|
||||||
|
"compose.change_federation": "Αλλαγή ρυθμίσεων ομοσπονδίας",
|
||||||
|
"compose.content-type.change": "Αλλαγή προηγμένων επιλογών μορφοποίησης",
|
||||||
|
"compose.content-type.html": "HTML",
|
||||||
|
"compose.content-type.html_meta": "Μορφοποίηση των αναρτήσεων σας με χρήση HTML",
|
||||||
|
"compose.content-type.markdown": "Markdown",
|
||||||
|
"compose.content-type.markdown_meta": "Μορφοποίηση των αναρτήσεων σας με χρήση Markdown",
|
||||||
|
"compose.content-type.plain": "Απλό κείμενο",
|
||||||
|
"compose.content-type.plain_meta": "Γράψιμο χωρίς προηγμένη μορφοποίηση",
|
||||||
|
"compose.disable_threaded_mode": "Απενεργοποίηση λειτουργίας νημάτων",
|
||||||
|
"compose.enable_threaded_mode": "Ενεργοποίηση λειτουργίας νημάτων",
|
||||||
|
"compose_form.sensitive.hide": "{count, plural, one {Επισήμανση πολυμέσου ως ευαίσθητο} other {Επισήμανση πολυμέσων ως ευαίσθητα}}",
|
||||||
|
"compose_form.sensitive.marked": "{count, plural, one {Το πολυμέσο έχει σημανθεί ως ευαίσθητο} other {Τα πολυμέσα έχουν σημανθεί ως ευαίσθητα}}",
|
||||||
|
"compose_form.sensitive.unmarked": "{count, plural, one {Το πολυμέσο δεν έχει σημανθεί ως ευαίσθητο} other {Τα πολυμέσα δεν έχουν σημανθεί ως ευαίσθητα}}",
|
||||||
|
"confirmation_modal.do_not_ask_again": "Να μην ζητηθεί επιβεβαίωση ξανά",
|
||||||
|
"confirmations.deprecated_settings.confirm": "Χρήση προτιμήσεων του Mastodon",
|
||||||
|
"direct.group_by_conversations": "Ομαδοποίηση ανά συζήτηση",
|
||||||
|
"favourite_modal.favourite": "Αγάπησε την ανάρτηση;",
|
||||||
|
"federation.federated.long": "Επιτρέψτε σε αυτήν την ανάρτηση να φτάσει σε άλλους διακομιστές",
|
||||||
|
"federation.local_only.long": "Αποτρέψτε αυτήν την ανάρτηση να φτάσει σε άλλους διακομιστές",
|
||||||
|
"federation.local_only.short": "Τοπικά μόνο",
|
||||||
|
"firehose.column_settings.allow_local_only": "Εμφάνιση τοπικών αναρτήσεων σε \"Όλα\"",
|
||||||
|
"home.column_settings.advanced": "Για προχωρημένους",
|
||||||
|
"home.column_settings.filter_regex": "Φιλτράρισμα βάσει των κανονικών εκφράσεων",
|
||||||
|
"home.column_settings.show_direct": "Εμφάνιση ιδιωτικών επισημάνσεων",
|
||||||
|
"home.settings": "Ρυθμίσεις στήλης",
|
||||||
|
"keyboard_shortcuts.bookmark": "για να σελιδοποιήσει",
|
||||||
|
"keyboard_shortcuts.secondary_toot": "για αποστολή τουτ χρησιμοποιώντας δευτερεύουσα ρύθμιση απορρήτου",
|
||||||
|
"moved_to_warning": "Αυτός ο λογαριασμός έχει σημανθεί ως μετακινημένος στο {moved_to_link} και έτσι δεν μπορεί να δεχτεί νέους ακολούθους.",
|
||||||
|
"navigation_bar.app_settings": "Ρυθμίσεις εφαρμογής",
|
||||||
|
"notifications.column_settings.filter_bar.show_bar": "Εμφάνιση μπάρας φίλτρου",
|
||||||
|
"settings.always_show_spoilers_field": "Πάντα ενεργοποίηση του πεδίου Προειδοποίηση Περιεχομένου",
|
||||||
|
"settings.close": "Κλείσιμο",
|
||||||
|
"settings.compose_box_opts": "Πλαίσιο σύνθεσης",
|
||||||
|
"settings.content_warnings": "Προειδοποιήσεις περιεχομένου",
|
||||||
|
"settings.content_warnings.regexp": "Κανονική έκφραση (regex)",
|
||||||
|
"settings.content_warnings_filter": "Προειδοποιήσεις περιεχομένου να μην ξεδιπλώνονται αυτόματα:",
|
||||||
|
"settings.content_warnings_shared_state": "Εμφάνιση/απόκρυψη περιεχομένου όλων των αντιγράφων ταυτόχρονα",
|
||||||
|
"settings.content_warnings_unfold_opts": "Επιλογές αυτόματου ξεδιπλώματος",
|
||||||
|
"settings.deprecated_setting": "Αυτή η ρύθμιση τώρα ελέγχεται από τις {settings_page_link} του Mastodon",
|
||||||
|
"settings.enable_content_warnings_auto_unfold": "Αυτόματη ξεδίπλωμα προειδοποιήσεων περιεχομένου",
|
||||||
|
"settings.general": "Γενικά",
|
||||||
|
"settings.layout_opts": "Επιλογές διάταξης",
|
||||||
|
"settings.media": "Πολυμέσα",
|
||||||
|
"settings.notifications.tab_badge": "Σήμα μη αναγνωσμένων ειδοποιήσεων",
|
||||||
|
"settings.notifications.tab_badge.hint": "Εμφανίζει ένα σήμα για μη αναγνωσμένες ειδοποιήσεις στα εικονίδια της στήλης όταν η στήλη ειδοποιήσεων δεν είναι ανοιχτή",
|
||||||
|
"settings.notifications_opts": "Ρυθμίσεις ειδοποιήσεων",
|
||||||
|
"settings.pop_in_left": "Αριστερά",
|
||||||
|
"settings.pop_in_right": "Δεξιά",
|
||||||
|
"settings.preferences": "Προτιμήσεις χρήστη",
|
||||||
|
"settings.preselect_on_reply": "Προεπιλογή ονομάτων χρηστών στην απάντηση",
|
||||||
|
"settings.preselect_on_reply_hint": "Όταν απαντάτε σε μια συνομιλία με πολλαπλούς συμμετέχοντες, προεπιλέξτε τα ονόματα χρηστών μετά τον πρώτο",
|
||||||
|
"settings.rewrite_mentions": "Ξαναγράψε επισημάνσεις στις προβαλλόμενες καταστάσεις",
|
||||||
|
"settings.rewrite_mentions_acct": "Ξαναγράψε με όνομα χρήστη και τομέα (όταν ο λογαριασμός είναι απομακρυσμένος)",
|
||||||
|
"settings.rewrite_mentions_no": "Μην ξαναγράψεις επισημάνσεις",
|
||||||
|
"settings.rewrite_mentions_username": "Ξαναγράψε με όνομα χρήστη",
|
||||||
|
"settings.shared_settings_link": "προτιμήσεις χρήστη",
|
||||||
|
"settings.side_arm": "Δευτερεύον κουμπί τουτ:",
|
||||||
|
"settings.side_arm.none": "Κανένα",
|
||||||
|
"settings.side_arm_reply_mode": "Όταν απαντάτε σε ένα τουτ, το κουμπί δευτερεύοντος τουτ πρέπει να:",
|
||||||
|
"settings.side_arm_reply_mode.copy": "Αντιγράψει τη ρύθμιση απορρήτου του τουτ που απαντάται",
|
||||||
|
"settings.side_arm_reply_mode.keep": "Διατηρήσει το καθορισμένο απόρρητο",
|
||||||
|
"settings.side_arm_reply_mode.restrict": "Περιορίσει τη ρύθμιση απορρήτου σε εκείνη του τουτ που απαντάται",
|
||||||
|
"settings.status_icons": "Εικονίδια τουτ",
|
||||||
|
"settings.status_icons_language": "Ένδειξη γλώσσας",
|
||||||
|
"settings.status_icons_local_only": "Ένδειξη τοπικό μόνο",
|
||||||
|
"settings.status_icons_media": "Ενδείξεις μέσων και δημοσκοπήσεων",
|
||||||
|
"settings.status_icons_reply": "Ένδειξη απαντήσεων",
|
||||||
|
"settings.status_icons_visibility": "Ένδειξη απορρήτου τουτ",
|
||||||
|
"settings.tag_misleading_links": "Σήμανση παραπλανητικών συνδέσμων",
|
||||||
|
"settings.tag_misleading_links.hint": "Προσθήκη οπτικής ένδειξης με τον εξυπηρετητή στόχο του συνδέσμου σε κάθε σύνδεσμο που δεν αναφέρεται ρητά",
|
||||||
|
"status.filtered": "Φιλτραρισμένο",
|
||||||
|
"status.has_audio": "Διαθέτει συνημμένα αρχεία ήχου",
|
||||||
|
"status.has_pictures": "Διαθέτει συνημμένες εικόνες",
|
||||||
|
"status.has_preview_card": "Διαθέτει συνημμένη κάρτα προεπισκόπησης",
|
||||||
|
"status.has_video": "Διαθέτει συνημμένα βίντεο",
|
||||||
|
"status.hide": "Απόκρυψη ανάρτησης",
|
||||||
|
"status.in_reply_to": "Αυτό το τουτ είναι απάντηση",
|
||||||
|
"status.is_poll": "Αυτό το τουτ είναι δημοσκόπηση",
|
||||||
|
"status.show_filter_reason": "Εμφάνιση ούτως ή άλλως"
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -6,12 +6,8 @@
|
|||||||
"account.view_full_profile": "Vidi plenan profilon",
|
"account.view_full_profile": "Vidi plenan profilon",
|
||||||
"boost_modal.missing_description": "Ĉi tiu afiŝo enhavas plurmedion, ke ne havas priskribon",
|
"boost_modal.missing_description": "Ĉi tiu afiŝo enhavas plurmedion, ke ne havas priskribon",
|
||||||
"column.favourited_by": "Stelumita per",
|
"column.favourited_by": "Stelumita per",
|
||||||
"column.heading": "Diversaj aferoj",
|
|
||||||
"column.reblogged_by": "Diskonigita de",
|
"column.reblogged_by": "Diskonigita de",
|
||||||
"column.subheading": "Diversaj agordoj",
|
|
||||||
"column_header.profile": "Profilo",
|
"column_header.profile": "Profilo",
|
||||||
"column_subheading.lists": "Listoj",
|
|
||||||
"column_subheading.navigation": "Navigado",
|
|
||||||
"community.column_settings.allow_local_only": "Montri nur-lokajn afiŝojn",
|
"community.column_settings.allow_local_only": "Montri nur-lokajn afiŝojn",
|
||||||
"compose.attach.doodle": "Skribu ion",
|
"compose.attach.doodle": "Skribu ion",
|
||||||
"compose.change_federation": "Ŝanĝi agordojn de federacio",
|
"compose.change_federation": "Ŝanĝi agordojn de federacio",
|
||||||
@@ -45,8 +41,6 @@
|
|||||||
"keyboard_shortcuts.secondary_toot": "sendi afiŝon per dua agordo de privateco",
|
"keyboard_shortcuts.secondary_toot": "sendi afiŝon per dua agordo de privateco",
|
||||||
"moved_to_warning": "Ĉi tiun konton sigelas, kiel movinta al {moved_to_link}, kaj pro ne permesos novajn sekvantojn.",
|
"moved_to_warning": "Ĉi tiun konton sigelas, kiel movinta al {moved_to_link}, kaj pro ne permesos novajn sekvantojn.",
|
||||||
"navigation_bar.app_settings": "Agordoj de aplikaĵo",
|
"navigation_bar.app_settings": "Agordoj de aplikaĵo",
|
||||||
"navigation_bar.keyboard_shortcuts": "Fulmoklavoj",
|
|
||||||
"navigation_bar.misc": "Aliaj",
|
|
||||||
"notifications.column_settings.filter_bar.show_bar": "Montri mezuron de filtrilo",
|
"notifications.column_settings.filter_bar.show_bar": "Montri mezuron de filtrilo",
|
||||||
"settings.always_show_spoilers_field": "Ĉiam ŝaltiĝu la arealo de Enhava Averto",
|
"settings.always_show_spoilers_field": "Ĉiam ŝaltiĝu la arealo de Enhava Averto",
|
||||||
"settings.close": "Fermi",
|
"settings.close": "Fermi",
|
||||||
|
|||||||
@@ -6,12 +6,8 @@
|
|||||||
"account.view_full_profile": "Ver perfil completo",
|
"account.view_full_profile": "Ver perfil completo",
|
||||||
"boost_modal.missing_description": "Esta publicación contiene medios sin descripción",
|
"boost_modal.missing_description": "Esta publicación contiene medios sin descripción",
|
||||||
"column.favourited_by": "Marcado como favorito por",
|
"column.favourited_by": "Marcado como favorito por",
|
||||||
"column.heading": "Misc",
|
|
||||||
"column.reblogged_by": "Impulsado por",
|
"column.reblogged_by": "Impulsado por",
|
||||||
"column.subheading": "Opciones misceláneas",
|
|
||||||
"column_header.profile": "Perfil",
|
"column_header.profile": "Perfil",
|
||||||
"column_subheading.lists": "Listas",
|
|
||||||
"column_subheading.navigation": "Navegación",
|
|
||||||
"community.column_settings.allow_local_only": "Mostrar sólo toots locales",
|
"community.column_settings.allow_local_only": "Mostrar sólo toots locales",
|
||||||
"compose.attach.doodle": "Dibujá algo",
|
"compose.attach.doodle": "Dibujá algo",
|
||||||
"compose.change_federation": "Cambiar configuración de la federación",
|
"compose.change_federation": "Cambiar configuración de la federación",
|
||||||
@@ -45,8 +41,6 @@
|
|||||||
"keyboard_shortcuts.secondary_toot": "para enviar un toot usando lac onfiguración de privacidad secundaria",
|
"keyboard_shortcuts.secondary_toot": "para enviar un toot usando lac onfiguración de privacidad secundaria",
|
||||||
"moved_to_warning": "Esta cuenta está marcada como movida a {moved_to_link}, y por lo tanto no aceptará nuevos seguimientos.",
|
"moved_to_warning": "Esta cuenta está marcada como movida a {moved_to_link}, y por lo tanto no aceptará nuevos seguimientos.",
|
||||||
"navigation_bar.app_settings": "Ajustes de aplicación",
|
"navigation_bar.app_settings": "Ajustes de aplicación",
|
||||||
"navigation_bar.keyboard_shortcuts": "Atajos de teclado",
|
|
||||||
"navigation_bar.misc": "Misc",
|
|
||||||
"notifications.column_settings.filter_bar.show_bar": "Mostrar barra de filtros",
|
"notifications.column_settings.filter_bar.show_bar": "Mostrar barra de filtros",
|
||||||
"settings.always_show_spoilers_field": "Siempre mostrar el campo de advertencia de contenido",
|
"settings.always_show_spoilers_field": "Siempre mostrar el campo de advertencia de contenido",
|
||||||
"settings.close": "Cerrar",
|
"settings.close": "Cerrar",
|
||||||
|
|||||||
@@ -6,12 +6,8 @@
|
|||||||
"account.view_full_profile": "Ver perfil completo",
|
"account.view_full_profile": "Ver perfil completo",
|
||||||
"boost_modal.missing_description": "Esta publicación contiene medios sin descripción",
|
"boost_modal.missing_description": "Esta publicación contiene medios sin descripción",
|
||||||
"column.favourited_by": "Marcado como favorito por",
|
"column.favourited_by": "Marcado como favorito por",
|
||||||
"column.heading": "Misc",
|
|
||||||
"column.reblogged_by": "Impulsado por",
|
"column.reblogged_by": "Impulsado por",
|
||||||
"column.subheading": "Opciones misceláneas",
|
|
||||||
"column_header.profile": "Perfil",
|
"column_header.profile": "Perfil",
|
||||||
"column_subheading.lists": "Listas",
|
|
||||||
"column_subheading.navigation": "Navegación",
|
|
||||||
"community.column_settings.allow_local_only": "Mostrar sólo toots locales",
|
"community.column_settings.allow_local_only": "Mostrar sólo toots locales",
|
||||||
"compose.attach.doodle": "Dibujar algo",
|
"compose.attach.doodle": "Dibujar algo",
|
||||||
"compose.change_federation": "Cambiar configuración de la federación",
|
"compose.change_federation": "Cambiar configuración de la federación",
|
||||||
@@ -41,8 +37,6 @@
|
|||||||
"keyboard_shortcuts.secondary_toot": "para enviar un toot usando lac onfiguración de privacidad secundaria",
|
"keyboard_shortcuts.secondary_toot": "para enviar un toot usando lac onfiguración de privacidad secundaria",
|
||||||
"moved_to_warning": "Esta cuenta está marcada como movida a {moved_to_link}, y por lo tanto no aceptará nuevos seguimientos.",
|
"moved_to_warning": "Esta cuenta está marcada como movida a {moved_to_link}, y por lo tanto no aceptará nuevos seguimientos.",
|
||||||
"navigation_bar.app_settings": "Ajustes de aplicación",
|
"navigation_bar.app_settings": "Ajustes de aplicación",
|
||||||
"navigation_bar.keyboard_shortcuts": "Atajos de teclado",
|
|
||||||
"navigation_bar.misc": "Misc",
|
|
||||||
"settings.always_show_spoilers_field": "Siempre mostrar el campo de advertencia de contenido",
|
"settings.always_show_spoilers_field": "Siempre mostrar el campo de advertencia de contenido",
|
||||||
"settings.close": "Cerrar",
|
"settings.close": "Cerrar",
|
||||||
"settings.compose_box_opts": "Cuadro de redacción",
|
"settings.compose_box_opts": "Cuadro de redacción",
|
||||||
|
|||||||
@@ -6,12 +6,8 @@
|
|||||||
"account.view_full_profile": "Ver perfil completo",
|
"account.view_full_profile": "Ver perfil completo",
|
||||||
"boost_modal.missing_description": "Esta publicación contiene medios sin descripción",
|
"boost_modal.missing_description": "Esta publicación contiene medios sin descripción",
|
||||||
"column.favourited_by": "Marcado como favorito por",
|
"column.favourited_by": "Marcado como favorito por",
|
||||||
"column.heading": "Misc",
|
|
||||||
"column.reblogged_by": "Impulsado por",
|
"column.reblogged_by": "Impulsado por",
|
||||||
"column.subheading": "Opciones misceláneas",
|
|
||||||
"column_header.profile": "Perfil",
|
"column_header.profile": "Perfil",
|
||||||
"column_subheading.lists": "Listas",
|
|
||||||
"column_subheading.navigation": "Navegación",
|
|
||||||
"community.column_settings.allow_local_only": "Mostrar toots solo-locales",
|
"community.column_settings.allow_local_only": "Mostrar toots solo-locales",
|
||||||
"compose.attach.doodle": "Dibujar algo",
|
"compose.attach.doodle": "Dibujar algo",
|
||||||
"compose.change_federation": "Cambiar configuración de la federación",
|
"compose.change_federation": "Cambiar configuración de la federación",
|
||||||
@@ -41,8 +37,6 @@
|
|||||||
"keyboard_shortcuts.secondary_toot": "para enviar un toot usando lac onfiguración de privacidad secundaria",
|
"keyboard_shortcuts.secondary_toot": "para enviar un toot usando lac onfiguración de privacidad secundaria",
|
||||||
"moved_to_warning": "Esta cuenta está marcada como movida a {moved_to_link}, y por lo tanto no aceptará nuevos seguimientos.",
|
"moved_to_warning": "Esta cuenta está marcada como movida a {moved_to_link}, y por lo tanto no aceptará nuevos seguimientos.",
|
||||||
"navigation_bar.app_settings": "Ajustes de la aplicación",
|
"navigation_bar.app_settings": "Ajustes de la aplicación",
|
||||||
"navigation_bar.keyboard_shortcuts": "Atajos de teclado",
|
|
||||||
"navigation_bar.misc": "Misc",
|
|
||||||
"settings.always_show_spoilers_field": "Siempre mostrar el campo de advertencia de contenido",
|
"settings.always_show_spoilers_field": "Siempre mostrar el campo de advertencia de contenido",
|
||||||
"settings.close": "Cerrar",
|
"settings.close": "Cerrar",
|
||||||
"settings.compose_box_opts": "Cuadro de redacción",
|
"settings.compose_box_opts": "Cuadro de redacción",
|
||||||
|
|||||||
@@ -8,7 +8,6 @@
|
|||||||
"compose.content-type.plain": "متن ساده",
|
"compose.content-type.plain": "متن ساده",
|
||||||
"home.column_settings.advanced": "پیشرفته",
|
"home.column_settings.advanced": "پیشرفته",
|
||||||
"navigation_bar.app_settings": "تنظیمات کاره",
|
"navigation_bar.app_settings": "تنظیمات کاره",
|
||||||
"navigation_bar.keyboard_shortcuts": "میانبرهای صفحهکلید",
|
|
||||||
"settings.close": "بستن",
|
"settings.close": "بستن",
|
||||||
"settings.content_warnings": "هشدارهای محتوا",
|
"settings.content_warnings": "هشدارهای محتوا",
|
||||||
"settings.media": "رسانه",
|
"settings.media": "رسانه",
|
||||||
|
|||||||
@@ -6,12 +6,8 @@
|
|||||||
"account.view_full_profile": "Voir le profil complet",
|
"account.view_full_profile": "Voir le profil complet",
|
||||||
"boost_modal.missing_description": "Ce post contient des médias sans description",
|
"boost_modal.missing_description": "Ce post contient des médias sans description",
|
||||||
"column.favourited_by": "Ajouté en favori par",
|
"column.favourited_by": "Ajouté en favori par",
|
||||||
"column.heading": "Divers",
|
|
||||||
"column.reblogged_by": "Partagé par",
|
"column.reblogged_by": "Partagé par",
|
||||||
"column.subheading": "Autres options",
|
|
||||||
"column_header.profile": "Profil",
|
"column_header.profile": "Profil",
|
||||||
"column_subheading.lists": "Listes",
|
|
||||||
"column_subheading.navigation": "Navigation",
|
|
||||||
"community.column_settings.allow_local_only": "Afficher seulement les posts locaux",
|
"community.column_settings.allow_local_only": "Afficher seulement les posts locaux",
|
||||||
"compose.attach.doodle": "Dessinez quelque chose",
|
"compose.attach.doodle": "Dessinez quelque chose",
|
||||||
"compose.change_federation": "Changer les paramètres de fédération",
|
"compose.change_federation": "Changer les paramètres de fédération",
|
||||||
@@ -24,6 +20,9 @@
|
|||||||
"compose.content-type.plain_meta": "Écrire sans formatage avancé",
|
"compose.content-type.plain_meta": "Écrire sans formatage avancé",
|
||||||
"compose.disable_threaded_mode": "Désactiver le mode thread",
|
"compose.disable_threaded_mode": "Désactiver le mode thread",
|
||||||
"compose.enable_threaded_mode": "Activer le mode thread",
|
"compose.enable_threaded_mode": "Activer le mode thread",
|
||||||
|
"compose_form.sensitive.hide": "{count, plural, one {Marquer le média comme sensible} other {Marquer les médias comme sensibles}}",
|
||||||
|
"compose_form.sensitive.marked": "{count, plural, one {Le média est marqué comme sensible} other {Les médias sont marqués comme sensibles}}",
|
||||||
|
"compose_form.sensitive.unmarked": "{count, plural, one {Le média n’est pas marqué comme sensible} other {Les médias ne sont pas marqué comme sensible}}",
|
||||||
"confirmation_modal.do_not_ask_again": "Ne plus demander confirmation",
|
"confirmation_modal.do_not_ask_again": "Ne plus demander confirmation",
|
||||||
"confirmations.deprecated_settings.confirm": "Utiliser les préférences de Mastodon",
|
"confirmations.deprecated_settings.confirm": "Utiliser les préférences de Mastodon",
|
||||||
"confirmations.deprecated_settings.message": "Certaines {app_settings} de glitch-soc que vous utilisez ont été remplacées par les {preferences} de Mastodon et seront remplacées :",
|
"confirmations.deprecated_settings.message": "Certaines {app_settings} de glitch-soc que vous utilisez ont été remplacées par les {preferences} de Mastodon et seront remplacées :",
|
||||||
@@ -42,8 +41,7 @@
|
|||||||
"keyboard_shortcuts.secondary_toot": "Envoyer le post en utilisant les paramètres secondaires de confidentialité",
|
"keyboard_shortcuts.secondary_toot": "Envoyer le post en utilisant les paramètres secondaires de confidentialité",
|
||||||
"moved_to_warning": "Ce compte a déménagé vers {moved_to_link} et ne peut donc plus accepter de nouveaux abonné·e·s.",
|
"moved_to_warning": "Ce compte a déménagé vers {moved_to_link} et ne peut donc plus accepter de nouveaux abonné·e·s.",
|
||||||
"navigation_bar.app_settings": "Paramètres de l'application",
|
"navigation_bar.app_settings": "Paramètres de l'application",
|
||||||
"navigation_bar.keyboard_shortcuts": "Raccourcis clavier",
|
"notifications.column_settings.filter_bar.show_bar": "Afficher la barre de filtre",
|
||||||
"navigation_bar.misc": "Autres",
|
|
||||||
"settings.always_show_spoilers_field": "Toujours activer le champ de rédaction de l'avertissement de contenu",
|
"settings.always_show_spoilers_field": "Toujours activer le champ de rédaction de l'avertissement de contenu",
|
||||||
"settings.close": "Fermer",
|
"settings.close": "Fermer",
|
||||||
"settings.compose_box_opts": "Zone de rédaction",
|
"settings.compose_box_opts": "Zone de rédaction",
|
||||||
@@ -57,6 +55,8 @@
|
|||||||
"settings.content_warnings_unfold_opts": "Options de dépliement automatique",
|
"settings.content_warnings_unfold_opts": "Options de dépliement automatique",
|
||||||
"settings.deprecated_setting": "Cette option est maintenant définie par les {settings_page_link} de Mastodon",
|
"settings.deprecated_setting": "Cette option est maintenant définie par les {settings_page_link} de Mastodon",
|
||||||
"settings.enable_content_warnings_auto_unfold": "Déplier automatiquement les avertissements de contenu",
|
"settings.enable_content_warnings_auto_unfold": "Déplier automatiquement les avertissements de contenu",
|
||||||
|
"settings.fullwidth_view": "Étirer les colonnes sur toute la largeur (mode bureau uniquement)",
|
||||||
|
"settings.fullwidth_view_hint": "Étire les colonnes pour remplir tout l'espace disponible.",
|
||||||
"settings.general": "Général",
|
"settings.general": "Général",
|
||||||
"settings.hicolor_privacy_icons": "Indicateurs de confidentialité en couleurs",
|
"settings.hicolor_privacy_icons": "Indicateurs de confidentialité en couleurs",
|
||||||
"settings.hicolor_privacy_icons.hint": "Affiche les indicateurs de confidentialité dans des couleurs facilement distinguables",
|
"settings.hicolor_privacy_icons.hint": "Affiche les indicateurs de confidentialité dans des couleurs facilement distinguables",
|
||||||
@@ -105,11 +105,14 @@
|
|||||||
"settings.tag_misleading_links.hint": "Ajouter une indication visuelle avec l'hôte cible du lien à chaque lien ne le mentionnant pas explicitement",
|
"settings.tag_misleading_links.hint": "Ajouter une indication visuelle avec l'hôte cible du lien à chaque lien ne le mentionnant pas explicitement",
|
||||||
"settings.wide_view": "Vue élargie (mode ordinateur uniquement)",
|
"settings.wide_view": "Vue élargie (mode ordinateur uniquement)",
|
||||||
"settings.wide_view_hint": "Étire les colonnes pour mieux remplir l'espace disponible.",
|
"settings.wide_view_hint": "Étire les colonnes pour mieux remplir l'espace disponible.",
|
||||||
|
"status.filtered": "Filtré",
|
||||||
"status.has_audio": "Contient des fichiers audio attachés",
|
"status.has_audio": "Contient des fichiers audio attachés",
|
||||||
"status.has_pictures": "Contient des images attachées",
|
"status.has_pictures": "Contient des images attachées",
|
||||||
"status.has_preview_card": "Contient une carte de prévisualisation attachée",
|
"status.has_preview_card": "Contient une carte de prévisualisation attachée",
|
||||||
"status.has_video": "Contient des vidéos attachées",
|
"status.has_video": "Contient des vidéos attachées",
|
||||||
|
"status.hide": "Masquer la publication",
|
||||||
"status.in_reply_to": "Ce post est une réponse",
|
"status.in_reply_to": "Ce post est une réponse",
|
||||||
"status.is_poll": "Ce post est un sondage",
|
"status.is_poll": "Ce post est un sondage",
|
||||||
"status.local_only": "Visible uniquement depuis votre instance"
|
"status.local_only": "Visible uniquement depuis votre instance",
|
||||||
|
"status.show_filter_reason": "Afficher quand même"
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,16 +2,12 @@
|
|||||||
"about.fork_disclaimer": "Glitch-soc est un logiciel gratuit et open source, fork de Mastodon.",
|
"about.fork_disclaimer": "Glitch-soc est un logiciel gratuit et open source, fork de Mastodon.",
|
||||||
"account.disclaimer_full": "Les informations ci-dessous peuvent être incomplètes.",
|
"account.disclaimer_full": "Les informations ci-dessous peuvent être incomplètes.",
|
||||||
"account.follows": "Abonnements",
|
"account.follows": "Abonnements",
|
||||||
"account.suspended_disclaimer_full": "Cet utilisateur a été suspendu par un modérateur.",
|
"account.suspended_disclaimer_full": "Ce compte a été suspendu par un modérateur.",
|
||||||
"account.view_full_profile": "Voir le profil complet",
|
"account.view_full_profile": "Voir le profil complet",
|
||||||
"boost_modal.missing_description": "Ce post contient des médias sans description",
|
"boost_modal.missing_description": "Ce post contient des médias sans description",
|
||||||
"column.favourited_by": "Ajouté en favori par",
|
"column.favourited_by": "Ajouté en favori par",
|
||||||
"column.heading": "Divers",
|
|
||||||
"column.reblogged_by": "Partagé par",
|
"column.reblogged_by": "Partagé par",
|
||||||
"column.subheading": "Autres options",
|
|
||||||
"column_header.profile": "Profil",
|
"column_header.profile": "Profil",
|
||||||
"column_subheading.lists": "Listes",
|
|
||||||
"column_subheading.navigation": "Navigation",
|
|
||||||
"community.column_settings.allow_local_only": "Afficher seulement les posts locaux",
|
"community.column_settings.allow_local_only": "Afficher seulement les posts locaux",
|
||||||
"compose.attach.doodle": "Dessinez quelque chose",
|
"compose.attach.doodle": "Dessinez quelque chose",
|
||||||
"compose.change_federation": "Changer les paramètres de fédération",
|
"compose.change_federation": "Changer les paramètres de fédération",
|
||||||
@@ -20,10 +16,13 @@
|
|||||||
"compose.content-type.html_meta": "Formatez vos messages en HTML",
|
"compose.content-type.html_meta": "Formatez vos messages en HTML",
|
||||||
"compose.content-type.markdown": "Markdown",
|
"compose.content-type.markdown": "Markdown",
|
||||||
"compose.content-type.markdown_meta": "Formatez vos messages en Markdown",
|
"compose.content-type.markdown_meta": "Formatez vos messages en Markdown",
|
||||||
"compose.content-type.plain": "Text brut",
|
"compose.content-type.plain": "Texte brut",
|
||||||
"compose.content-type.plain_meta": "Écrire sans formatage avancé",
|
"compose.content-type.plain_meta": "Écrire sans formatage avancé",
|
||||||
"compose.disable_threaded_mode": "Désactiver le mode thread",
|
"compose.disable_threaded_mode": "Désactiver le mode thread",
|
||||||
"compose.enable_threaded_mode": "Activer le mode thread",
|
"compose.enable_threaded_mode": "Activer le mode thread",
|
||||||
|
"compose_form.sensitive.hide": "{count, plural, one {Marquer le média comme sensible} other {Marquer les médias comme sensibles}}",
|
||||||
|
"compose_form.sensitive.marked": "{count, plural, one {Le média est marqué comme sensible} other {Les médias sont marqués comme sensibles}}",
|
||||||
|
"compose_form.sensitive.unmarked": "{count, plural, one {Le média n’est pas marqué comme sensible} other {Les médias ne sont pas marqué comme sensible}}",
|
||||||
"confirmation_modal.do_not_ask_again": "Ne plus demander confirmation",
|
"confirmation_modal.do_not_ask_again": "Ne plus demander confirmation",
|
||||||
"confirmations.deprecated_settings.confirm": "Utiliser les préférences de Mastodon",
|
"confirmations.deprecated_settings.confirm": "Utiliser les préférences de Mastodon",
|
||||||
"confirmations.deprecated_settings.message": "Certaines {app_settings} de glitch-soc que vous utilisez ont été remplacées par les {preferences} de Mastodon et seront remplacées :",
|
"confirmations.deprecated_settings.message": "Certaines {app_settings} de glitch-soc que vous utilisez ont été remplacées par les {preferences} de Mastodon et seront remplacées :",
|
||||||
@@ -42,8 +41,7 @@
|
|||||||
"keyboard_shortcuts.secondary_toot": "Envoyer le post en utilisant les paramètres secondaires de confidentialité",
|
"keyboard_shortcuts.secondary_toot": "Envoyer le post en utilisant les paramètres secondaires de confidentialité",
|
||||||
"moved_to_warning": "Ce compte a déménagé vers {moved_to_link} et ne peut donc plus accepter de nouveaux abonné·e·s.",
|
"moved_to_warning": "Ce compte a déménagé vers {moved_to_link} et ne peut donc plus accepter de nouveaux abonné·e·s.",
|
||||||
"navigation_bar.app_settings": "Paramètres de l'application",
|
"navigation_bar.app_settings": "Paramètres de l'application",
|
||||||
"navigation_bar.keyboard_shortcuts": "Raccourcis clavier",
|
"notifications.column_settings.filter_bar.show_bar": "Afficher la barre de filtre",
|
||||||
"navigation_bar.misc": "Autres",
|
|
||||||
"settings.always_show_spoilers_field": "Toujours activer le champ de rédaction de l'avertissement de contenu",
|
"settings.always_show_spoilers_field": "Toujours activer le champ de rédaction de l'avertissement de contenu",
|
||||||
"settings.close": "Fermer",
|
"settings.close": "Fermer",
|
||||||
"settings.compose_box_opts": "Zone de rédaction",
|
"settings.compose_box_opts": "Zone de rédaction",
|
||||||
@@ -57,6 +55,8 @@
|
|||||||
"settings.content_warnings_unfold_opts": "Options de dépliement automatique",
|
"settings.content_warnings_unfold_opts": "Options de dépliement automatique",
|
||||||
"settings.deprecated_setting": "Cette option est maintenant définie par les {settings_page_link} de Mastodon",
|
"settings.deprecated_setting": "Cette option est maintenant définie par les {settings_page_link} de Mastodon",
|
||||||
"settings.enable_content_warnings_auto_unfold": "Déplier automatiquement les avertissements de contenu",
|
"settings.enable_content_warnings_auto_unfold": "Déplier automatiquement les avertissements de contenu",
|
||||||
|
"settings.fullwidth_view": "Étirer les colonnes sur toute la largeur (mode bureau uniquement)",
|
||||||
|
"settings.fullwidth_view_hint": "Étire les colonnes pour remplir tout l'espace disponible.",
|
||||||
"settings.general": "Général",
|
"settings.general": "Général",
|
||||||
"settings.hicolor_privacy_icons": "Indicateurs de confidentialité en couleurs",
|
"settings.hicolor_privacy_icons": "Indicateurs de confidentialité en couleurs",
|
||||||
"settings.hicolor_privacy_icons.hint": "Affiche les indicateurs de confidentialité dans des couleurs facilement distinguables",
|
"settings.hicolor_privacy_icons.hint": "Affiche les indicateurs de confidentialité dans des couleurs facilement distinguables",
|
||||||
@@ -76,7 +76,7 @@
|
|||||||
"settings.pop_in_player": "Activer le lecteur pop-in",
|
"settings.pop_in_player": "Activer le lecteur pop-in",
|
||||||
"settings.pop_in_position": "Position du lecteur pop-in :",
|
"settings.pop_in_position": "Position du lecteur pop-in :",
|
||||||
"settings.pop_in_right": "Droite",
|
"settings.pop_in_right": "Droite",
|
||||||
"settings.preferences": "Preferences",
|
"settings.preferences": "Préferences",
|
||||||
"settings.prepend_cw_re": "Préfixer les avertissements avec \"re: \" lors d'une réponse",
|
"settings.prepend_cw_re": "Préfixer les avertissements avec \"re: \" lors d'une réponse",
|
||||||
"settings.preselect_on_reply": "Présélectionner les noms d’utilisateur·rices lors de la réponse",
|
"settings.preselect_on_reply": "Présélectionner les noms d’utilisateur·rices lors de la réponse",
|
||||||
"settings.preselect_on_reply_hint": "Présélectionner les noms d'utilisateurs après le premier lors d'une réponse à une conversation à plusieurs participants",
|
"settings.preselect_on_reply_hint": "Présélectionner les noms d'utilisateurs après le premier lors d'une réponse à une conversation à plusieurs participants",
|
||||||
@@ -105,11 +105,14 @@
|
|||||||
"settings.tag_misleading_links.hint": "Ajouter une indication visuelle avec l'hôte cible du lien à chaque lien ne le mentionnant pas explicitement",
|
"settings.tag_misleading_links.hint": "Ajouter une indication visuelle avec l'hôte cible du lien à chaque lien ne le mentionnant pas explicitement",
|
||||||
"settings.wide_view": "Vue élargie (mode ordinateur uniquement)",
|
"settings.wide_view": "Vue élargie (mode ordinateur uniquement)",
|
||||||
"settings.wide_view_hint": "Étire les colonnes pour mieux remplir l'espace disponible.",
|
"settings.wide_view_hint": "Étire les colonnes pour mieux remplir l'espace disponible.",
|
||||||
|
"status.filtered": "Filtré",
|
||||||
"status.has_audio": "Contient des fichiers audio attachés",
|
"status.has_audio": "Contient des fichiers audio attachés",
|
||||||
"status.has_pictures": "Contient des images attachées",
|
"status.has_pictures": "Contient des images attachées",
|
||||||
"status.has_preview_card": "Contient une carte de prévisualisation attachée",
|
"status.has_preview_card": "Contient une carte de prévisualisation attachée",
|
||||||
"status.has_video": "Contient des vidéos attachées",
|
"status.has_video": "Contient des vidéos attachées",
|
||||||
|
"status.hide": "Masquer la publication",
|
||||||
"status.in_reply_to": "Ce post est une réponse",
|
"status.in_reply_to": "Ce post est une réponse",
|
||||||
"status.is_poll": "Ce post est un sondage",
|
"status.is_poll": "Ce post est un sondage",
|
||||||
"status.local_only": "Visible uniquement depuis votre instance"
|
"status.local_only": "Visible uniquement depuis votre instance",
|
||||||
|
"status.show_filter_reason": "Afficher quand même"
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -6,12 +6,8 @@
|
|||||||
"account.view_full_profile": "Ver perfil completo",
|
"account.view_full_profile": "Ver perfil completo",
|
||||||
"boost_modal.missing_description": "Esta publicación contén multimedia sen descrición",
|
"boost_modal.missing_description": "Esta publicación contén multimedia sen descrición",
|
||||||
"column.favourited_by": "Favorecida por",
|
"column.favourited_by": "Favorecida por",
|
||||||
"column.heading": "Varios",
|
|
||||||
"column.reblogged_by": "Promovida por",
|
"column.reblogged_by": "Promovida por",
|
||||||
"column.subheading": "Opcións diversas",
|
|
||||||
"column_header.profile": "Perfil",
|
"column_header.profile": "Perfil",
|
||||||
"column_subheading.lists": "Listas",
|
|
||||||
"column_subheading.navigation": "Navegación",
|
|
||||||
"community.column_settings.allow_local_only": "Ver só mensaxes de ámbito local",
|
"community.column_settings.allow_local_only": "Ver só mensaxes de ámbito local",
|
||||||
"compose.attach.doodle": "Debuxa algo",
|
"compose.attach.doodle": "Debuxa algo",
|
||||||
"compose.change_federation": "Cambiar axustes de federación",
|
"compose.change_federation": "Cambiar axustes de federación",
|
||||||
@@ -45,8 +41,6 @@
|
|||||||
"keyboard_shortcuts.secondary_toot": "para enviar publicación usando axuste secundario de privacidade",
|
"keyboard_shortcuts.secondary_toot": "para enviar publicación usando axuste secundario de privacidade",
|
||||||
"moved_to_warning": "Esta conta moveuse a {moved_to_link}, e non acepta novos seguimentos.",
|
"moved_to_warning": "Esta conta moveuse a {moved_to_link}, e non acepta novos seguimentos.",
|
||||||
"navigation_bar.app_settings": "Axustes da app",
|
"navigation_bar.app_settings": "Axustes da app",
|
||||||
"navigation_bar.keyboard_shortcuts": "Atallos de teclado",
|
|
||||||
"navigation_bar.misc": "Varios",
|
|
||||||
"notifications.column_settings.filter_bar.show_bar": "Amosar barra de filtros",
|
"notifications.column_settings.filter_bar.show_bar": "Amosar barra de filtros",
|
||||||
"settings.always_show_spoilers_field": "Activar sempre o campo de Aviso sobre o contido CW",
|
"settings.always_show_spoilers_field": "Activar sempre o campo de Aviso sobre o contido CW",
|
||||||
"settings.close": "Pechar",
|
"settings.close": "Pechar",
|
||||||
|
|||||||
@@ -6,12 +6,8 @@
|
|||||||
"account.view_full_profile": "Tampilkan profil lengkap",
|
"account.view_full_profile": "Tampilkan profil lengkap",
|
||||||
"boost_modal.missing_description": "Toot ini berisi beberapa media tanpa deskripsi",
|
"boost_modal.missing_description": "Toot ini berisi beberapa media tanpa deskripsi",
|
||||||
"column.favourited_by": "Disukai oleh",
|
"column.favourited_by": "Disukai oleh",
|
||||||
"column.heading": "Lainnya",
|
|
||||||
"column.reblogged_by": "Dibagikan oleh",
|
"column.reblogged_by": "Dibagikan oleh",
|
||||||
"column.subheading": "Opsi lain-lain",
|
|
||||||
"column_header.profile": "Profil",
|
"column_header.profile": "Profil",
|
||||||
"column_subheading.lists": "Daftar",
|
|
||||||
"column_subheading.navigation": "Penelusuran",
|
|
||||||
"community.column_settings.allow_local_only": "Tampilkan toot lokal saja",
|
"community.column_settings.allow_local_only": "Tampilkan toot lokal saja",
|
||||||
"compose.change_federation": "Ubah pengaturan federasi",
|
"compose.change_federation": "Ubah pengaturan federasi",
|
||||||
"compose.content-type.change": "Ubah opsi pemformatan lanjutan",
|
"compose.content-type.change": "Ubah opsi pemformatan lanjutan",
|
||||||
@@ -38,8 +34,6 @@
|
|||||||
"keyboard_shortcuts.secondary_toot": "untuk mengirim toot menggunakan pengaturan privasi sekunder",
|
"keyboard_shortcuts.secondary_toot": "untuk mengirim toot menggunakan pengaturan privasi sekunder",
|
||||||
"moved_to_warning": "Akun ini ditandai sebagai dipindahkan ke {moved_to_link}, dan karenanya tidak menerima pengikut baru.",
|
"moved_to_warning": "Akun ini ditandai sebagai dipindahkan ke {moved_to_link}, dan karenanya tidak menerima pengikut baru.",
|
||||||
"navigation_bar.app_settings": "Pengaturan aplikasi",
|
"navigation_bar.app_settings": "Pengaturan aplikasi",
|
||||||
"navigation_bar.keyboard_shortcuts": "Pintasan keyboard",
|
|
||||||
"navigation_bar.misc": "Lainnya",
|
|
||||||
"settings.always_show_spoilers_field": "Selalu aktifkan bidang Peringatan Konten",
|
"settings.always_show_spoilers_field": "Selalu aktifkan bidang Peringatan Konten",
|
||||||
"settings.close": "Tutup",
|
"settings.close": "Tutup",
|
||||||
"settings.compose_box_opts": "Kotak tulis",
|
"settings.compose_box_opts": "Kotak tulis",
|
||||||
@@ -49,6 +43,7 @@
|
|||||||
"settings.content_warnings.regexp": "Ekspresi reguler",
|
"settings.content_warnings.regexp": "Ekspresi reguler",
|
||||||
"settings.content_warnings_filter": "Peringatan konten yang tidak akan dibuka secara otomatis:",
|
"settings.content_warnings_filter": "Peringatan konten yang tidak akan dibuka secara otomatis:",
|
||||||
"settings.content_warnings_shared_state": "Tampilkan/sembunyikan konten semua salinan sekaligus",
|
"settings.content_warnings_shared_state": "Tampilkan/sembunyikan konten semua salinan sekaligus",
|
||||||
|
"settings.pop_in_right": "Kanan",
|
||||||
"settings.preferences": "Preferences",
|
"settings.preferences": "Preferences",
|
||||||
"settings.status_icons_reply": "Indikator balasan",
|
"settings.status_icons_reply": "Indikator balasan",
|
||||||
"settings.status_icons_visibility": "Indikator privasi toot",
|
"settings.status_icons_visibility": "Indikator privasi toot",
|
||||||
|
|||||||
@@ -6,12 +6,8 @@
|
|||||||
"account.view_full_profile": "正確な情報を見る",
|
"account.view_full_profile": "正確な情報を見る",
|
||||||
"boost_modal.missing_description": "このトゥートには少なくとも1つの画像に説明が付与されていません",
|
"boost_modal.missing_description": "このトゥートには少なくとも1つの画像に説明が付与されていません",
|
||||||
"column.favourited_by": "お気に入りしたユーザー",
|
"column.favourited_by": "お気に入りしたユーザー",
|
||||||
"column.heading": "その他",
|
|
||||||
"column.reblogged_by": "ブーストしたユーザー",
|
"column.reblogged_by": "ブーストしたユーザー",
|
||||||
"column.subheading": "その他のオプション",
|
|
||||||
"column_header.profile": "プロフィール",
|
"column_header.profile": "プロフィール",
|
||||||
"column_subheading.lists": "リスト",
|
|
||||||
"column_subheading.navigation": "ナビゲーション",
|
|
||||||
"community.column_settings.allow_local_only": "ローカル限定投稿を表示する",
|
"community.column_settings.allow_local_only": "ローカル限定投稿を表示する",
|
||||||
"compose.content-type.html": "HTML",
|
"compose.content-type.html": "HTML",
|
||||||
"compose.content-type.html_meta": "投稿に HTML を使用する",
|
"compose.content-type.html_meta": "投稿に HTML を使用する",
|
||||||
@@ -33,8 +29,6 @@
|
|||||||
"keyboard_shortcuts.secondary_toot": "セカンダリートゥートの公開範囲でトゥートする",
|
"keyboard_shortcuts.secondary_toot": "セカンダリートゥートの公開範囲でトゥートする",
|
||||||
"moved_to_warning": "このアカウント{moved_to_link}に引っ越したため、新しいフォロワーを受け入れていません。",
|
"moved_to_warning": "このアカウント{moved_to_link}に引っ越したため、新しいフォロワーを受け入れていません。",
|
||||||
"navigation_bar.app_settings": "アプリ設定",
|
"navigation_bar.app_settings": "アプリ設定",
|
||||||
"navigation_bar.keyboard_shortcuts": "キーボードショートカット",
|
|
||||||
"navigation_bar.misc": "その他",
|
|
||||||
"settings.always_show_spoilers_field": "常にコンテンツワーニング設定を表示する(指定がない場合は通常投稿)",
|
"settings.always_show_spoilers_field": "常にコンテンツワーニング設定を表示する(指定がない場合は通常投稿)",
|
||||||
"settings.close": "閉じる",
|
"settings.close": "閉じる",
|
||||||
"settings.compose_box_opts": "コンポーズボックス設定",
|
"settings.compose_box_opts": "コンポーズボックス設定",
|
||||||
|
|||||||
@@ -1,6 +1,5 @@
|
|||||||
{
|
{
|
||||||
"compose.attach.doodle": "Ssuneɣ-d kra",
|
"compose.attach.doodle": "Ssuneɣ-d kra",
|
||||||
"navigation_bar.keyboard_shortcuts": "Inezgumen n unasiw",
|
|
||||||
"settings.close": "Mdel",
|
"settings.close": "Mdel",
|
||||||
"settings.content_warnings": "Content warnings",
|
"settings.content_warnings": "Content warnings",
|
||||||
"settings.preferences": "Preferences"
|
"settings.preferences": "Preferences"
|
||||||
|
|||||||
@@ -6,12 +6,8 @@
|
|||||||
"account.view_full_profile": "전체 프로필 보기",
|
"account.view_full_profile": "전체 프로필 보기",
|
||||||
"boost_modal.missing_description": "이 게시물은 설명이 없는 미디어를 포함하고 있습니다",
|
"boost_modal.missing_description": "이 게시물은 설명이 없는 미디어를 포함하고 있습니다",
|
||||||
"column.favourited_by": "즐겨찾기 한 사람",
|
"column.favourited_by": "즐겨찾기 한 사람",
|
||||||
"column.heading": "기타",
|
|
||||||
"column.reblogged_by": "부스트 한 사람",
|
"column.reblogged_by": "부스트 한 사람",
|
||||||
"column.subheading": "다양한 옵션",
|
|
||||||
"column_header.profile": "프로필",
|
"column_header.profile": "프로필",
|
||||||
"column_subheading.lists": "리스트",
|
|
||||||
"column_subheading.navigation": "탐색",
|
|
||||||
"community.column_settings.allow_local_only": "로컬 전용 글 보기",
|
"community.column_settings.allow_local_only": "로컬 전용 글 보기",
|
||||||
"compose.attach.doodle": "뭔가 그려보세요",
|
"compose.attach.doodle": "뭔가 그려보세요",
|
||||||
"compose.change_federation": "연합 설정 변경",
|
"compose.change_federation": "연합 설정 변경",
|
||||||
@@ -45,8 +41,6 @@
|
|||||||
"keyboard_shortcuts.secondary_toot": "보조 프라이버시 설정으로 글 보내기",
|
"keyboard_shortcuts.secondary_toot": "보조 프라이버시 설정으로 글 보내기",
|
||||||
"moved_to_warning": "이 계정은 {moved_to_link}로 이동한 것으로 표시되었고, 새 팔로우를 받지 않는 것 같습니다.",
|
"moved_to_warning": "이 계정은 {moved_to_link}로 이동한 것으로 표시되었고, 새 팔로우를 받지 않는 것 같습니다.",
|
||||||
"navigation_bar.app_settings": "앱 설정",
|
"navigation_bar.app_settings": "앱 설정",
|
||||||
"navigation_bar.keyboard_shortcuts": "키보드 단축기",
|
|
||||||
"navigation_bar.misc": "다양한 옵션들",
|
|
||||||
"notifications.column_settings.filter_bar.show_bar": "필터 막대 표시",
|
"notifications.column_settings.filter_bar.show_bar": "필터 막대 표시",
|
||||||
"settings.always_show_spoilers_field": "열람주의 항목을 언제나 활성화",
|
"settings.always_show_spoilers_field": "열람주의 항목을 언제나 활성화",
|
||||||
"settings.close": "닫기",
|
"settings.close": "닫기",
|
||||||
|
|||||||
@@ -6,12 +6,8 @@
|
|||||||
"account.view_full_profile": "Peržiūrėti visą profilį",
|
"account.view_full_profile": "Peržiūrėti visą profilį",
|
||||||
"boost_modal.missing_description": "Šis įrašas turi šiek tiek medijų be aprašų.",
|
"boost_modal.missing_description": "Šis įrašas turi šiek tiek medijų be aprašų.",
|
||||||
"column.favourited_by": "Pamėgo",
|
"column.favourited_by": "Pamėgo",
|
||||||
"column.heading": "Įvairūs",
|
|
||||||
"column.reblogged_by": "Pasidalino",
|
"column.reblogged_by": "Pasidalino",
|
||||||
"column.subheading": "Įvairios parinktys",
|
|
||||||
"column_header.profile": "Profilis",
|
"column_header.profile": "Profilis",
|
||||||
"column_subheading.lists": "Sąrašai",
|
|
||||||
"column_subheading.navigation": "Naršymas",
|
|
||||||
"community.column_settings.allow_local_only": "Rodyti tik vietinius įrašus",
|
"community.column_settings.allow_local_only": "Rodyti tik vietinius įrašus",
|
||||||
"compose.attach.doodle": "Nupiešti kažką",
|
"compose.attach.doodle": "Nupiešti kažką",
|
||||||
"compose.change_federation": "Keisti federacijos nustatymus",
|
"compose.change_federation": "Keisti federacijos nustatymus",
|
||||||
@@ -45,8 +41,6 @@
|
|||||||
"keyboard_shortcuts.secondary_toot": "siųsti įrašą naudojant antrinį privatumo nustatymą",
|
"keyboard_shortcuts.secondary_toot": "siųsti įrašą naudojant antrinį privatumo nustatymą",
|
||||||
"moved_to_warning": "Ši paskyra pažymėta kaip perkelta į {moved_to_link}, todėl negali priimti naujų sekimų.",
|
"moved_to_warning": "Ši paskyra pažymėta kaip perkelta į {moved_to_link}, todėl negali priimti naujų sekimų.",
|
||||||
"navigation_bar.app_settings": "Programėlės nustatymai",
|
"navigation_bar.app_settings": "Programėlės nustatymai",
|
||||||
"navigation_bar.keyboard_shortcuts": "Spartieji klavišai",
|
|
||||||
"navigation_bar.misc": "Įvairūs",
|
|
||||||
"notifications.column_settings.filter_bar.show_bar": "Rodyti filtro juostą",
|
"notifications.column_settings.filter_bar.show_bar": "Rodyti filtro juostą",
|
||||||
"settings.always_show_spoilers_field": "Visada įjungti turinio įspėjimo lauką",
|
"settings.always_show_spoilers_field": "Visada įjungti turinio įspėjimo lauką",
|
||||||
"settings.close": "Užverti",
|
"settings.close": "Užverti",
|
||||||
|
|||||||
@@ -3,12 +3,8 @@
|
|||||||
"account.view_full_profile": "Volledig profiel weergeven",
|
"account.view_full_profile": "Volledig profiel weergeven",
|
||||||
"boost_modal.missing_description": "Deze toot bevat media zonder beschrijving",
|
"boost_modal.missing_description": "Deze toot bevat media zonder beschrijving",
|
||||||
"column.favourited_by": "Favoriet door",
|
"column.favourited_by": "Favoriet door",
|
||||||
"column.heading": "Overige",
|
|
||||||
"column.reblogged_by": "Geboost door",
|
"column.reblogged_by": "Geboost door",
|
||||||
"column.subheading": "Diverse opties",
|
|
||||||
"column_header.profile": "Profiel",
|
"column_header.profile": "Profiel",
|
||||||
"column_subheading.lists": "Lijsten",
|
|
||||||
"column_subheading.navigation": "Navigatie",
|
|
||||||
"community.column_settings.allow_local_only": "Toon alleen lokale toots",
|
"community.column_settings.allow_local_only": "Toon alleen lokale toots",
|
||||||
"compose.content-type.html": "HTML",
|
"compose.content-type.html": "HTML",
|
||||||
"compose.content-type.markdown": "Markdown",
|
"compose.content-type.markdown": "Markdown",
|
||||||
|
|||||||
@@ -6,12 +6,8 @@
|
|||||||
"account.view_full_profile": "Pokaż pełny profil",
|
"account.view_full_profile": "Pokaż pełny profil",
|
||||||
"boost_modal.missing_description": "Ten wpis zawiera multimedialne załączniki bez opisu",
|
"boost_modal.missing_description": "Ten wpis zawiera multimedialne załączniki bez opisu",
|
||||||
"column.favourited_by": "Polubiony przez",
|
"column.favourited_by": "Polubiony przez",
|
||||||
"column.heading": "Różne",
|
|
||||||
"column.reblogged_by": "Podbity przez",
|
"column.reblogged_by": "Podbity przez",
|
||||||
"column.subheading": "Różne opcje",
|
|
||||||
"column_header.profile": "Profil",
|
"column_header.profile": "Profil",
|
||||||
"column_subheading.lists": "Listy",
|
|
||||||
"column_subheading.navigation": "Nawigacja",
|
|
||||||
"community.column_settings.allow_local_only": "Pokazuj wyłącznie wpisy lokalne",
|
"community.column_settings.allow_local_only": "Pokazuj wyłącznie wpisy lokalne",
|
||||||
"compose.content-type.html": "HTML",
|
"compose.content-type.html": "HTML",
|
||||||
"compose.content-type.markdown": "Markdown",
|
"compose.content-type.markdown": "Markdown",
|
||||||
@@ -28,8 +24,6 @@
|
|||||||
"keyboard_shortcuts.secondary_toot": "aby opublikować wpis używając dodatkowych ustawień prywatności",
|
"keyboard_shortcuts.secondary_toot": "aby opublikować wpis używając dodatkowych ustawień prywatności",
|
||||||
"moved_to_warning": "To konto oznaczone jest jako przeniesione do {moved_to_link} i może z tego powodu nie akceptować nowych obserwujących.",
|
"moved_to_warning": "To konto oznaczone jest jako przeniesione do {moved_to_link} i może z tego powodu nie akceptować nowych obserwujących.",
|
||||||
"navigation_bar.app_settings": "Ustawienia aplikacji",
|
"navigation_bar.app_settings": "Ustawienia aplikacji",
|
||||||
"navigation_bar.keyboard_shortcuts": "Skróty klawiszowe",
|
|
||||||
"navigation_bar.misc": "Różne",
|
|
||||||
"settings.always_show_spoilers_field": "Zawsze pokazuj pole ostrzeżenia o zawartości",
|
"settings.always_show_spoilers_field": "Zawsze pokazuj pole ostrzeżenia o zawartości",
|
||||||
"settings.close": "Zamknij",
|
"settings.close": "Zamknij",
|
||||||
"settings.compose_box_opts": "Pole edycji",
|
"settings.compose_box_opts": "Pole edycji",
|
||||||
|
|||||||
@@ -6,12 +6,8 @@
|
|||||||
"account.view_full_profile": "Ver o perfil completo",
|
"account.view_full_profile": "Ver o perfil completo",
|
||||||
"boost_modal.missing_description": "Este toot contém algumas mídias sem descrição",
|
"boost_modal.missing_description": "Este toot contém algumas mídias sem descrição",
|
||||||
"column.favourited_by": "Favoritado por",
|
"column.favourited_by": "Favoritado por",
|
||||||
"column.heading": "Diversos",
|
|
||||||
"column.reblogged_by": "Inpulsionado por",
|
"column.reblogged_by": "Inpulsionado por",
|
||||||
"column.subheading": "Opções diversas",
|
|
||||||
"column_header.profile": "Perfil",
|
"column_header.profile": "Perfil",
|
||||||
"column_subheading.lists": "Listas",
|
|
||||||
"column_subheading.navigation": "Navegação",
|
|
||||||
"community.column_settings.allow_local_only": "Mostrar os toots apenas locais",
|
"community.column_settings.allow_local_only": "Mostrar os toots apenas locais",
|
||||||
"compose.content-type.html": "HTML",
|
"compose.content-type.html": "HTML",
|
||||||
"compose.content-type.markdown": "Markdown",
|
"compose.content-type.markdown": "Markdown",
|
||||||
@@ -28,8 +24,6 @@
|
|||||||
"keyboard_shortcuts.secondary_toot": "para enviar toot usando a configuração de privacidade secundária",
|
"keyboard_shortcuts.secondary_toot": "para enviar toot usando a configuração de privacidade secundária",
|
||||||
"moved_to_warning": "Esta conta foi como movida para {moved_to_link} e, portanto, pode não aceitar novos seguidores.",
|
"moved_to_warning": "Esta conta foi como movida para {moved_to_link} e, portanto, pode não aceitar novos seguidores.",
|
||||||
"navigation_bar.app_settings": "Configurações do aplicativo",
|
"navigation_bar.app_settings": "Configurações do aplicativo",
|
||||||
"navigation_bar.keyboard_shortcuts": "Atalhos de teclado",
|
|
||||||
"navigation_bar.misc": "Diversos",
|
|
||||||
"settings.always_show_spoilers_field": "Sempre ativar o campo Aviso de Conteúdo",
|
"settings.always_show_spoilers_field": "Sempre ativar o campo Aviso de Conteúdo",
|
||||||
"settings.close": "Fechar",
|
"settings.close": "Fechar",
|
||||||
"settings.compose_box_opts": "Caixa de composição",
|
"settings.compose_box_opts": "Caixa de composição",
|
||||||
|
|||||||
@@ -6,7 +6,6 @@
|
|||||||
"account.view_full_profile": "Ver o perfil completo",
|
"account.view_full_profile": "Ver o perfil completo",
|
||||||
"boost_modal.missing_description": "Este post contém alguns media sem descrição",
|
"boost_modal.missing_description": "Este post contém alguns media sem descrição",
|
||||||
"column.favourited_by": "Adicionado aos favoritos de",
|
"column.favourited_by": "Adicionado aos favoritos de",
|
||||||
"column.heading": "Diversos",
|
|
||||||
"moved_to_warning": "Esta conta mudou-se para {moved_to_link} e, portanto, pode não aceitar novos seguidores.",
|
"moved_to_warning": "Esta conta mudou-se para {moved_to_link} e, portanto, pode não aceitar novos seguidores.",
|
||||||
"notifications.column_settings.filter_bar.show_bar": "Mostrar barra de filtros",
|
"notifications.column_settings.filter_bar.show_bar": "Mostrar barra de filtros",
|
||||||
"settings.always_show_spoilers_field": "Mostrar sempre o campo Aviso de Conteúdo",
|
"settings.always_show_spoilers_field": "Mostrar sempre o campo Aviso de Conteúdo",
|
||||||
|
|||||||
@@ -6,12 +6,8 @@
|
|||||||
"account.view_full_profile": "Открыть полный профиль",
|
"account.view_full_profile": "Открыть полный профиль",
|
||||||
"boost_modal.missing_description": "Этот пост содержит медиафайлы без описания",
|
"boost_modal.missing_description": "Этот пост содержит медиафайлы без описания",
|
||||||
"column.favourited_by": "Добавили в избранное",
|
"column.favourited_by": "Добавили в избранное",
|
||||||
"column.heading": "Прочее",
|
|
||||||
"column.reblogged_by": "Продвинули",
|
"column.reblogged_by": "Продвинули",
|
||||||
"column.subheading": "Прочие разделы",
|
|
||||||
"column_header.profile": "Профиль",
|
"column_header.profile": "Профиль",
|
||||||
"column_subheading.lists": "Списки",
|
|
||||||
"column_subheading.navigation": "Навигация",
|
|
||||||
"community.column_settings.allow_local_only": "Показывать нефедерируемые посты",
|
"community.column_settings.allow_local_only": "Показывать нефедерируемые посты",
|
||||||
"compose.attach.doodle": "Нарисовать что-нибудь",
|
"compose.attach.doodle": "Нарисовать что-нибудь",
|
||||||
"compose.change_federation": "Изменить настройки федерации",
|
"compose.change_federation": "Изменить настройки федерации",
|
||||||
@@ -39,8 +35,6 @@
|
|||||||
"keyboard_shortcuts.bookmark": "добавить закладку",
|
"keyboard_shortcuts.bookmark": "добавить закладку",
|
||||||
"moved_to_warning": "Этот аккаунт переехал на {moved_to_link}, и скорее всего не принимает новых подписчиков.",
|
"moved_to_warning": "Этот аккаунт переехал на {moved_to_link}, и скорее всего не принимает новых подписчиков.",
|
||||||
"navigation_bar.app_settings": "Настройки приложения",
|
"navigation_bar.app_settings": "Настройки приложения",
|
||||||
"navigation_bar.keyboard_shortcuts": "Сочетания клавиш",
|
|
||||||
"navigation_bar.misc": "Прочее",
|
|
||||||
"notifications.column_settings.filter_bar.show_bar": "Показать панель фильтров",
|
"notifications.column_settings.filter_bar.show_bar": "Показать панель фильтров",
|
||||||
"settings.always_show_spoilers_field": "Всегда ставить предупреждение о содержании",
|
"settings.always_show_spoilers_field": "Всегда ставить предупреждение о содержании",
|
||||||
"settings.close": "Закрыть",
|
"settings.close": "Закрыть",
|
||||||
|
|||||||
@@ -6,12 +6,8 @@
|
|||||||
"account.view_full_profile": "View full profile",
|
"account.view_full_profile": "View full profile",
|
||||||
"boost_modal.missing_description": "This post contains some media withoot description",
|
"boost_modal.missing_description": "This post contains some media withoot description",
|
||||||
"column.favourited_by": "Favouritit by",
|
"column.favourited_by": "Favouritit by",
|
||||||
"column.heading": "Misc",
|
|
||||||
"column.reblogged_by": "Boosted by",
|
"column.reblogged_by": "Boosted by",
|
||||||
"column.subheading": "Miscellaneous options",
|
|
||||||
"column_header.profile": "Profile",
|
"column_header.profile": "Profile",
|
||||||
"column_subheading.lists": "Lists",
|
|
||||||
"column_subheading.navigation": "Navigation",
|
|
||||||
"community.column_settings.allow_local_only": "Show local-only posts",
|
"community.column_settings.allow_local_only": "Show local-only posts",
|
||||||
"compose.attach.doodle": "Draw something",
|
"compose.attach.doodle": "Draw something",
|
||||||
"compose.change_federation": "Change federation settins",
|
"compose.change_federation": "Change federation settins",
|
||||||
@@ -45,8 +41,6 @@
|
|||||||
"keyboard_shortcuts.secondary_toot": "tae send post using secondary privacy settin",
|
"keyboard_shortcuts.secondary_toot": "tae send post using secondary privacy settin",
|
||||||
"moved_to_warning": "This account is marked as moved tae {moved_to_link}, an' may not accept new follows.",
|
"moved_to_warning": "This account is marked as moved tae {moved_to_link}, an' may not accept new follows.",
|
||||||
"navigation_bar.app_settings": "App settins",
|
"navigation_bar.app_settings": "App settins",
|
||||||
"navigation_bar.keyboard_shortcuts": "Keyboard shortcuts",
|
|
||||||
"navigation_bar.misc": "Misc",
|
|
||||||
"notifications.column_settings.filter_bar.show_bar": "Show filter bar",
|
"notifications.column_settings.filter_bar.show_bar": "Show filter bar",
|
||||||
"settings.always_show_spoilers_field": "Always enable the Content Warning field",
|
"settings.always_show_spoilers_field": "Always enable the Content Warning field",
|
||||||
"settings.close": "Close",
|
"settings.close": "Close",
|
||||||
|
|||||||
@@ -4,12 +4,8 @@
|
|||||||
"account.view_full_profile": "Visa full profil",
|
"account.view_full_profile": "Visa full profil",
|
||||||
"boost_modal.missing_description": "Denna toot innehåller viss media utan beskrivning",
|
"boost_modal.missing_description": "Denna toot innehåller viss media utan beskrivning",
|
||||||
"column.favourited_by": "Favoritmarkerad av",
|
"column.favourited_by": "Favoritmarkerad av",
|
||||||
"column.heading": "Övrigt",
|
|
||||||
"column.reblogged_by": "Boostad av",
|
"column.reblogged_by": "Boostad av",
|
||||||
"column.subheading": "Övriga val",
|
|
||||||
"column_header.profile": "Profil",
|
"column_header.profile": "Profil",
|
||||||
"column_subheading.lists": "Listor",
|
|
||||||
"column_subheading.navigation": "Navigering",
|
|
||||||
"community.column_settings.allow_local_only": "Visa endast lokala toots",
|
"community.column_settings.allow_local_only": "Visa endast lokala toots",
|
||||||
"compose.content-type.html": "HTML",
|
"compose.content-type.html": "HTML",
|
||||||
"compose.content-type.markdown": "Markdown",
|
"compose.content-type.markdown": "Markdown",
|
||||||
|
|||||||
@@ -1,8 +1,5 @@
|
|||||||
{
|
{
|
||||||
"about.fork_disclaimer": ".",
|
"account.follows": "ติดตาม",
|
||||||
"account.disclaimer_full": ".",
|
|
||||||
"account.follows": "",
|
|
||||||
"community.column_settings.allow_local_only": "",
|
|
||||||
"settings.content_warnings": "Content warnings",
|
"settings.content_warnings": "Content warnings",
|
||||||
"settings.preferences": "Preferences"
|
"settings.preferences": "Preferences"
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -5,12 +5,8 @@
|
|||||||
"account.view_full_profile": "Tam görünüm",
|
"account.view_full_profile": "Tam görünüm",
|
||||||
"boost_modal.missing_description": "Bu gönderi açıklaması olmayan medya içerir",
|
"boost_modal.missing_description": "Bu gönderi açıklaması olmayan medya içerir",
|
||||||
"column.favourited_by": "Tarafından favorilere eklendi",
|
"column.favourited_by": "Tarafından favorilere eklendi",
|
||||||
"column.heading": "Diğer",
|
|
||||||
"column.reblogged_by": "Tarafından yükseltildi",
|
"column.reblogged_by": "Tarafından yükseltildi",
|
||||||
"column.subheading": "Diğer seçenekler",
|
|
||||||
"column_header.profile": "Profil",
|
"column_header.profile": "Profil",
|
||||||
"column_subheading.lists": "Listeler",
|
|
||||||
"column_subheading.navigation": "Gezinme",
|
|
||||||
"community.column_settings.allow_local_only": "Sadece yerel gönderileri göster",
|
"community.column_settings.allow_local_only": "Sadece yerel gönderileri göster",
|
||||||
"compose.content-type.html": "HTML",
|
"compose.content-type.html": "HTML",
|
||||||
"compose.content-type.markdown": "Markdown modu",
|
"compose.content-type.markdown": "Markdown modu",
|
||||||
|
|||||||
@@ -6,12 +6,8 @@
|
|||||||
"account.view_full_profile": "Переглянути повний профіль",
|
"account.view_full_profile": "Переглянути повний профіль",
|
||||||
"boost_modal.missing_description": "Цей дмух містить деякі медіа без опису",
|
"boost_modal.missing_description": "Цей дмух містить деякі медіа без опису",
|
||||||
"column.favourited_by": "Уподобані",
|
"column.favourited_by": "Уподобані",
|
||||||
"column.heading": "Різне",
|
|
||||||
"column.reblogged_by": "Поширено",
|
"column.reblogged_by": "Поширено",
|
||||||
"column.subheading": "Інші параметри",
|
|
||||||
"column_header.profile": "Профіль",
|
"column_header.profile": "Профіль",
|
||||||
"column_subheading.lists": "Списки",
|
|
||||||
"column_subheading.navigation": "Навігація",
|
|
||||||
"community.column_settings.allow_local_only": "Показувати тільки локальні дмухи",
|
"community.column_settings.allow_local_only": "Показувати тільки локальні дмухи",
|
||||||
"compose.content-type.html": "HTML",
|
"compose.content-type.html": "HTML",
|
||||||
"compose.content-type.markdown": "Markdown",
|
"compose.content-type.markdown": "Markdown",
|
||||||
@@ -29,8 +25,6 @@
|
|||||||
"keyboard_shortcuts.secondary_toot": "надсилати повідомлення з використанням вторинних налаштувань конфіденційності",
|
"keyboard_shortcuts.secondary_toot": "надсилати повідомлення з використанням вторинних налаштувань конфіденційності",
|
||||||
"moved_to_warning": "Цей обліковий запис позначено як переміщений до {moved_to_link} і тому не може прийняти нових підписок.",
|
"moved_to_warning": "Цей обліковий запис позначено як переміщений до {moved_to_link} і тому не може прийняти нових підписок.",
|
||||||
"navigation_bar.app_settings": "Налаштування програми",
|
"navigation_bar.app_settings": "Налаштування програми",
|
||||||
"navigation_bar.keyboard_shortcuts": "Комбінації клавіш",
|
|
||||||
"navigation_bar.misc": "Різне",
|
|
||||||
"settings.always_show_spoilers_field": "Завжди вмикати попередження про вміст",
|
"settings.always_show_spoilers_field": "Завжди вмикати попередження про вміст",
|
||||||
"settings.close": "Закрити",
|
"settings.close": "Закрити",
|
||||||
"settings.compose_box_opts": "Compose box",
|
"settings.compose_box_opts": "Compose box",
|
||||||
|
|||||||
@@ -6,12 +6,8 @@
|
|||||||
"account.view_full_profile": "查看完整资料",
|
"account.view_full_profile": "查看完整资料",
|
||||||
"boost_modal.missing_description": "这条嘟文未包含媒体描述",
|
"boost_modal.missing_description": "这条嘟文未包含媒体描述",
|
||||||
"column.favourited_by": "点赞",
|
"column.favourited_by": "点赞",
|
||||||
"column.heading": "杂项",
|
|
||||||
"column.reblogged_by": "转嘟",
|
"column.reblogged_by": "转嘟",
|
||||||
"column.subheading": "其他选项",
|
|
||||||
"column_header.profile": "资料卡",
|
"column_header.profile": "资料卡",
|
||||||
"column_subheading.lists": "列表",
|
|
||||||
"column_subheading.navigation": "导航",
|
|
||||||
"community.column_settings.allow_local_only": "只显示仅本站可见的嘟文",
|
"community.column_settings.allow_local_only": "只显示仅本站可见的嘟文",
|
||||||
"compose.attach.doodle": "涂鸦",
|
"compose.attach.doodle": "涂鸦",
|
||||||
"compose.change_federation": "更改联动设置",
|
"compose.change_federation": "更改联动设置",
|
||||||
@@ -45,8 +41,6 @@
|
|||||||
"keyboard_shortcuts.secondary_toot": "使用另一隐私设置发送嘟文",
|
"keyboard_shortcuts.secondary_toot": "使用另一隐私设置发送嘟文",
|
||||||
"moved_to_warning": "此账户已被标记为移至 {moved_to_link},并且似乎没有收到新粉丝。",
|
"moved_to_warning": "此账户已被标记为移至 {moved_to_link},并且似乎没有收到新粉丝。",
|
||||||
"navigation_bar.app_settings": "应用设置",
|
"navigation_bar.app_settings": "应用设置",
|
||||||
"navigation_bar.keyboard_shortcuts": "键盘快捷键",
|
|
||||||
"navigation_bar.misc": "杂项",
|
|
||||||
"notifications.column_settings.filter_bar.show_bar": "显示筛选栏",
|
"notifications.column_settings.filter_bar.show_bar": "显示筛选栏",
|
||||||
"settings.always_show_spoilers_field": "始终显示内容警告框",
|
"settings.always_show_spoilers_field": "始终显示内容警告框",
|
||||||
"settings.close": "关闭",
|
"settings.close": "关闭",
|
||||||
|
|||||||
@@ -6,12 +6,8 @@
|
|||||||
"account.view_full_profile": "查看完整個人資料",
|
"account.view_full_profile": "查看完整個人資料",
|
||||||
"boost_modal.missing_description": "此貼文包含未加說明的媒體檔案",
|
"boost_modal.missing_description": "此貼文包含未加說明的媒體檔案",
|
||||||
"column.favourited_by": "誰按了最愛",
|
"column.favourited_by": "誰按了最愛",
|
||||||
"column.heading": "雜項",
|
|
||||||
"column.reblogged_by": "被誰轉貼",
|
"column.reblogged_by": "被誰轉貼",
|
||||||
"column.subheading": "其他選項",
|
|
||||||
"column_header.profile": "個人檔案",
|
"column_header.profile": "個人檔案",
|
||||||
"column_subheading.lists": "列表",
|
|
||||||
"column_subheading.navigation": "導覽",
|
|
||||||
"community.column_settings.allow_local_only": "顯示僅限本地的貼文",
|
"community.column_settings.allow_local_only": "顯示僅限本地的貼文",
|
||||||
"compose.attach.doodle": "塗鴉",
|
"compose.attach.doodle": "塗鴉",
|
||||||
"compose.change_federation": "變更聯邦設定",
|
"compose.change_federation": "變更聯邦設定",
|
||||||
@@ -24,6 +20,9 @@
|
|||||||
"compose.content-type.plain_meta": "不使用進階格式撰寫",
|
"compose.content-type.plain_meta": "不使用進階格式撰寫",
|
||||||
"compose.disable_threaded_mode": "停用貼文串模式",
|
"compose.disable_threaded_mode": "停用貼文串模式",
|
||||||
"compose.enable_threaded_mode": "啟用貼文串模式",
|
"compose.enable_threaded_mode": "啟用貼文串模式",
|
||||||
|
"compose_form.sensitive.hide": "{count, plural, other {將媒體標記為敏感內容}}",
|
||||||
|
"compose_form.sensitive.marked": "{count, plural, other {媒體已被標記為敏感內容}}",
|
||||||
|
"compose_form.sensitive.unmarked": "{count, plural, other {媒體未被標記為敏感內容}}",
|
||||||
"confirmation_modal.do_not_ask_again": "不要再顯示確認訊息",
|
"confirmation_modal.do_not_ask_again": "不要再顯示確認訊息",
|
||||||
"confirmations.deprecated_settings.confirm": "使用 Mastodon 偏好",
|
"confirmations.deprecated_settings.confirm": "使用 Mastodon 偏好",
|
||||||
"confirmations.deprecated_settings.message": "您正在使用的某些特定於 glitch-soc 設備的 {app_settings} 已被 Mastodon {preferences} 所取代,並將被覆蓋:",
|
"confirmations.deprecated_settings.message": "您正在使用的某些特定於 glitch-soc 設備的 {app_settings} 已被 Mastodon {preferences} 所取代,並將被覆蓋:",
|
||||||
@@ -42,8 +41,6 @@
|
|||||||
"keyboard_shortcuts.secondary_toot": "使用次要隱私設定來發布貼文",
|
"keyboard_shortcuts.secondary_toot": "使用次要隱私設定來發布貼文",
|
||||||
"moved_to_warning": "此帳戶已標記為移至 {moved_to_link},因此可能不接受新的追隨者。",
|
"moved_to_warning": "此帳戶已標記為移至 {moved_to_link},因此可能不接受新的追隨者。",
|
||||||
"navigation_bar.app_settings": "應用程式設定",
|
"navigation_bar.app_settings": "應用程式設定",
|
||||||
"navigation_bar.keyboard_shortcuts": "鍵盤快速鍵",
|
|
||||||
"navigation_bar.misc": "雜項",
|
|
||||||
"notifications.column_settings.filter_bar.show_bar": "顯示過濾器",
|
"notifications.column_settings.filter_bar.show_bar": "顯示過濾器",
|
||||||
"settings.always_show_spoilers_field": "永遠啟用內容警告欄位",
|
"settings.always_show_spoilers_field": "永遠啟用內容警告欄位",
|
||||||
"settings.close": "關閉",
|
"settings.close": "關閉",
|
||||||
@@ -58,6 +55,8 @@
|
|||||||
"settings.content_warnings_unfold_opts": "自動展開選項",
|
"settings.content_warnings_unfold_opts": "自動展開選項",
|
||||||
"settings.deprecated_setting": "此設定現在已由 Mastodon 的 {settings_page_link} 控制。",
|
"settings.deprecated_setting": "此設定現在已由 Mastodon 的 {settings_page_link} 控制。",
|
||||||
"settings.enable_content_warnings_auto_unfold": "自動展開內容警告",
|
"settings.enable_content_warnings_auto_unfold": "自動展開內容警告",
|
||||||
|
"settings.fullwidth_view": "將欄位延展至全寬(僅限桌面模式)",
|
||||||
|
"settings.fullwidth_view_hint": "將欄位延展以填滿所有可用空間。",
|
||||||
"settings.general": "一般設定",
|
"settings.general": "一般設定",
|
||||||
"settings.hicolor_privacy_icons": "隱私圖示使用對比色",
|
"settings.hicolor_privacy_icons": "隱私圖示使用對比色",
|
||||||
"settings.hicolor_privacy_icons.hint": "用明亮且易於區分的顏色顯示隱私圖示",
|
"settings.hicolor_privacy_icons.hint": "用明亮且易於區分的顏色顯示隱私圖示",
|
||||||
|
|||||||
@@ -126,6 +126,9 @@ export function createAccountFromServerJSON(serverJSON: ApiAccountJSON) {
|
|||||||
? accountJSON.username
|
? accountJSON.username
|
||||||
: accountJSON.display_name;
|
: accountJSON.display_name;
|
||||||
|
|
||||||
|
const accountNote =
|
||||||
|
accountJSON.note && accountJSON.note !== '<p></p>' ? accountJSON.note : '';
|
||||||
|
|
||||||
return AccountFactory({
|
return AccountFactory({
|
||||||
...accountJSON,
|
...accountJSON,
|
||||||
moved: moved?.id,
|
moved: moved?.id,
|
||||||
@@ -142,8 +145,8 @@ export function createAccountFromServerJSON(serverJSON: ApiAccountJSON) {
|
|||||||
escapeTextContentForBrowser(displayName),
|
escapeTextContentForBrowser(displayName),
|
||||||
emojiMap,
|
emojiMap,
|
||||||
),
|
),
|
||||||
note_emojified: emojify(accountJSON.note, emojiMap),
|
note_emojified: emojify(accountNote, emojiMap),
|
||||||
note_plain: unescapeHTML(accountJSON.note),
|
note_plain: unescapeHTML(accountNote),
|
||||||
url:
|
url:
|
||||||
accountJSON.url.startsWith('http://') ||
|
accountJSON.url.startsWith('http://') ||
|
||||||
accountJSON.url.startsWith('https://')
|
accountJSON.url.startsWith('https://')
|
||||||
|
|||||||
@@ -619,8 +619,13 @@ export const composeReducer = (state = initialState, action) => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (action.status.get('poll')) {
|
if (action.status.get('poll')) {
|
||||||
|
let options = ImmutableList(action.status.get('poll').options.map(x => x.title));
|
||||||
|
if (options.size < action.maxOptions) {
|
||||||
|
options = options.push('');
|
||||||
|
}
|
||||||
|
|
||||||
map.set('poll', ImmutableMap({
|
map.set('poll', ImmutableMap({
|
||||||
options: ImmutableList(action.status.get('poll').options.map(x => x.title)),
|
options: options,
|
||||||
multiple: action.status.get('poll').multiple,
|
multiple: action.status.get('poll').multiple,
|
||||||
expires_in: expiresInFromExpiresAt(action.status.get('poll').expires_at),
|
expires_in: expiresInFromExpiresAt(action.status.get('poll').expires_at),
|
||||||
}));
|
}));
|
||||||
@@ -650,8 +655,13 @@ export const composeReducer = (state = initialState, action) => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (action.status.get('poll')) {
|
if (action.status.get('poll')) {
|
||||||
|
let options = ImmutableList(action.status.get('poll').options.map(x => x.title));
|
||||||
|
if (options.size < action.maxOptions) {
|
||||||
|
options = options.push('');
|
||||||
|
}
|
||||||
|
|
||||||
map.set('poll', ImmutableMap({
|
map.set('poll', ImmutableMap({
|
||||||
options: ImmutableList(action.status.get('poll').options.map(x => x.title)),
|
options: options,
|
||||||
multiple: action.status.get('poll').multiple,
|
multiple: action.status.get('poll').multiple,
|
||||||
expires_in: expiresInFromExpiresAt(action.status.get('poll').expires_at),
|
expires_in: expiresInFromExpiresAt(action.status.get('poll').expires_at),
|
||||||
}));
|
}));
|
||||||
|
|||||||
@@ -10,7 +10,7 @@ const initialState = ImmutableMap({
|
|||||||
stretch : true,
|
stretch : true,
|
||||||
side_arm : 'none',
|
side_arm : 'none',
|
||||||
side_arm_reply_mode : 'keep',
|
side_arm_reply_mode : 'keep',
|
||||||
show_reply_count : false,
|
show_reply_count : true,
|
||||||
always_show_spoilers_field: false,
|
always_show_spoilers_field: false,
|
||||||
confirm_boost_missing_media_description: false,
|
confirm_boost_missing_media_description: false,
|
||||||
confirm_before_clearing_draft: true,
|
confirm_before_clearing_draft: true,
|
||||||
|
|||||||
@@ -64,6 +64,10 @@ const statusTranslateUndo = (state, id) => {
|
|||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const removeStatusStub = (state, id) => {
|
||||||
|
return state.getIn([id, 'id']) ? state.deleteIn([id, 'isLoading']) : state.delete(id);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
/** @type {ImmutableMap<string, import('flavours/glitch/models/status').Status>} */
|
/** @type {ImmutableMap<string, import('flavours/glitch/models/status').Status>} */
|
||||||
const initialState = ImmutableMap();
|
const initialState = ImmutableMap();
|
||||||
@@ -73,8 +77,14 @@ export default function statuses(state = initialState, action) {
|
|||||||
switch(action.type) {
|
switch(action.type) {
|
||||||
case STATUS_FETCH_REQUEST:
|
case STATUS_FETCH_REQUEST:
|
||||||
return state.setIn([action.id, 'isLoading'], true);
|
return state.setIn([action.id, 'isLoading'], true);
|
||||||
case STATUS_FETCH_FAIL:
|
case STATUS_FETCH_FAIL: {
|
||||||
return state.delete(action.id);
|
if (action.parentQuotePostId && action.error.status === 404) {
|
||||||
|
return removeStatusStub(state, action.id)
|
||||||
|
.setIn([action.parentQuotePostId, 'quote', 'state'], 'deleted')
|
||||||
|
} else {
|
||||||
|
return removeStatusStub(state, action.id);
|
||||||
|
}
|
||||||
|
}
|
||||||
case STATUS_IMPORT:
|
case STATUS_IMPORT:
|
||||||
return importStatus(state, action.status);
|
return importStatus(state, action.status);
|
||||||
case STATUSES_IMPORT:
|
case STATUSES_IMPORT:
|
||||||
|
|||||||
@@ -1893,7 +1893,7 @@ a.sparkline {
|
|||||||
font-size: 15px;
|
font-size: 15px;
|
||||||
line-height: 22px;
|
line-height: 22px;
|
||||||
|
|
||||||
li {
|
> li {
|
||||||
counter-increment: step 1;
|
counter-increment: step 1;
|
||||||
padding-inline-start: 2.5rem;
|
padding-inline-start: 2.5rem;
|
||||||
padding-bottom: 8px;
|
padding-bottom: 8px;
|
||||||
|
|||||||
@@ -992,6 +992,7 @@ body > [data-popper-placement] {
|
|||||||
line-height: 20px;
|
line-height: 20px;
|
||||||
letter-spacing: 0.1px;
|
letter-spacing: 0.1px;
|
||||||
color: $highlight-text-color;
|
color: $highlight-text-color;
|
||||||
|
background-color: var(--input-background-color);
|
||||||
white-space: nowrap;
|
white-space: nowrap;
|
||||||
text-overflow: ellipsis;
|
text-overflow: ellipsis;
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
@@ -1922,7 +1923,10 @@ body > [data-popper-placement] {
|
|||||||
}
|
}
|
||||||
|
|
||||||
.status__quote {
|
.status__quote {
|
||||||
--quote-margin: 36px;
|
// --status-gutter-width is currently only set inside of
|
||||||
|
// .notification-ungrouped, so everywhere else this will fall back
|
||||||
|
// to the pixel values
|
||||||
|
--quote-margin: var(--status-gutter-width, 36px);
|
||||||
|
|
||||||
position: relative;
|
position: relative;
|
||||||
margin-block-start: 16px;
|
margin-block-start: 16px;
|
||||||
@@ -1933,7 +1937,7 @@ body > [data-popper-placement] {
|
|||||||
border: var(--nested-card-border);
|
border: var(--nested-card-border);
|
||||||
|
|
||||||
@container (width > 460px) {
|
@container (width > 460px) {
|
||||||
--quote-margin: 56px;
|
--quote-margin: var(--status-gutter-width, 56px);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -2394,6 +2398,7 @@ a .account__avatar {
|
|||||||
.detailed-status__display-name,
|
.detailed-status__display-name,
|
||||||
.detailed-status__datetime,
|
.detailed-status__datetime,
|
||||||
.detailed-status__application,
|
.detailed-status__application,
|
||||||
|
.detailed-status__link,
|
||||||
.account__display-name {
|
.account__display-name {
|
||||||
text-decoration: none;
|
text-decoration: none;
|
||||||
}
|
}
|
||||||
@@ -2426,7 +2431,8 @@ a.account__display-name {
|
|||||||
}
|
}
|
||||||
|
|
||||||
.detailed-status__application,
|
.detailed-status__application,
|
||||||
.detailed-status__datetime {
|
.detailed-status__datetime,
|
||||||
|
.detailed-status__link {
|
||||||
color: inherit;
|
color: inherit;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -2612,8 +2618,9 @@ a.account__display-name {
|
|||||||
}
|
}
|
||||||
|
|
||||||
.status__relative-time,
|
.status__relative-time,
|
||||||
.detailed-status__datetime {
|
.detailed-status__datetime,
|
||||||
&:hover {
|
.detailed-status__link {
|
||||||
|
&:is(a):hover {
|
||||||
text-decoration: underline;
|
text-decoration: underline;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -2913,7 +2920,6 @@ a.account__display-name {
|
|||||||
&__pane {
|
&__pane {
|
||||||
height: 100%;
|
height: 100%;
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
pointer-events: none;
|
|
||||||
display: flex;
|
display: flex;
|
||||||
justify-content: flex-end;
|
justify-content: flex-end;
|
||||||
min-width: 285px;
|
min-width: 285px;
|
||||||
@@ -2925,7 +2931,6 @@ a.account__display-name {
|
|||||||
&__inner {
|
&__inner {
|
||||||
position: fixed;
|
position: fixed;
|
||||||
width: 285px;
|
width: 285px;
|
||||||
pointer-events: auto;
|
|
||||||
height: 100%;
|
height: 100%;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -3939,16 +3944,18 @@ a.account__display-name {
|
|||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
gap: 8px;
|
gap: 8px;
|
||||||
|
width: 100%;
|
||||||
|
padding: 12px;
|
||||||
font-size: 16px;
|
font-size: 16px;
|
||||||
font-weight: 400;
|
font-weight: 400;
|
||||||
padding: 12px;
|
|
||||||
text-decoration: none;
|
text-decoration: none;
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
white-space: nowrap;
|
white-space: nowrap;
|
||||||
border: 0;
|
|
||||||
background: transparent;
|
|
||||||
color: $secondary-text-color;
|
color: $secondary-text-color;
|
||||||
|
background: transparent;
|
||||||
|
border: 0;
|
||||||
border-left: 4px solid transparent;
|
border-left: 4px solid transparent;
|
||||||
|
box-sizing: border-box;
|
||||||
|
|
||||||
&:hover,
|
&:hover,
|
||||||
&:focus,
|
&:focus,
|
||||||
@@ -11130,21 +11137,23 @@ noscript {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.status {
|
.status:not(.status--is-quote) {
|
||||||
border: 0;
|
border: 0;
|
||||||
padding: 0;
|
padding: 0;
|
||||||
|
|
||||||
&__avatar {
|
|
||||||
width: 40px;
|
|
||||||
height: 40px;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.status__wrapper-direct {
|
.status__wrapper-direct {
|
||||||
background: transparent;
|
background: transparent;
|
||||||
}
|
}
|
||||||
|
|
||||||
$icon-margin: 48px; // 40px avatar + 8px gap
|
.status {
|
||||||
|
// 40px avatar + 8px gap
|
||||||
|
--status-gutter-width: 48px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.status--is-quote {
|
||||||
|
--status-gutter-width: 0;
|
||||||
|
}
|
||||||
|
|
||||||
.status__content,
|
.status__content,
|
||||||
.status__action-bar,
|
.status__action-bar,
|
||||||
@@ -11158,16 +11167,16 @@ noscript {
|
|||||||
.hashtag-bar,
|
.hashtag-bar,
|
||||||
.content-warning,
|
.content-warning,
|
||||||
.filter-warning {
|
.filter-warning {
|
||||||
margin-inline-start: $icon-margin;
|
margin-inline-start: var(--status-gutter-width);
|
||||||
width: calc(100% - $icon-margin);
|
width: calc(100% - var(--status-gutter-width));
|
||||||
}
|
}
|
||||||
|
|
||||||
.more-from-author {
|
.more-from-author {
|
||||||
width: calc(100% - $icon-margin + 2px);
|
width: calc(100% - var(--status-gutter-width) + 2px);
|
||||||
}
|
}
|
||||||
|
|
||||||
.status__content__read-more-button {
|
.status__content__read-more-button {
|
||||||
margin-inline-start: $icon-margin;
|
margin-inline-start: var(--status-gutter-width);
|
||||||
}
|
}
|
||||||
|
|
||||||
.notification__report {
|
.notification__report {
|
||||||
|
|||||||
@@ -589,11 +589,14 @@ code {
|
|||||||
}
|
}
|
||||||
|
|
||||||
.stacked-actions {
|
.stacked-actions {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 10px;
|
||||||
margin-top: 30px;
|
margin-top: 30px;
|
||||||
margin-bottom: 15px;
|
margin-bottom: 15px;
|
||||||
}
|
}
|
||||||
|
|
||||||
button:not(.button, .link-button, .help-button) {
|
.btn {
|
||||||
display: block;
|
display: block;
|
||||||
width: 100%;
|
width: 100%;
|
||||||
border: 0;
|
border: 0;
|
||||||
@@ -610,8 +613,6 @@ code {
|
|||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
font-weight: 500;
|
font-weight: 500;
|
||||||
outline: 0;
|
outline: 0;
|
||||||
margin-bottom: 10px;
|
|
||||||
margin-inline-end: 10px;
|
|
||||||
|
|
||||||
&:last-child {
|
&:last-child {
|
||||||
margin-inline-end: 0;
|
margin-inline-end: 0;
|
||||||
|
|||||||
@@ -101,7 +101,7 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Change the background colors of statuses
|
// Change the background colors of statuses
|
||||||
.focusable:focus {
|
.focusable:focus-visible {
|
||||||
background: lighten($white, 4%);
|
background: lighten($white, 4%);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -107,7 +107,8 @@
|
|||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
}
|
}
|
||||||
|
|
||||||
&.editable {
|
&.editable,
|
||||||
|
&.disabled {
|
||||||
align-items: center;
|
align-items: center;
|
||||||
overflow: visible;
|
overflow: visible;
|
||||||
}
|
}
|
||||||
@@ -165,7 +166,8 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
&__option.editable &__input {
|
&__option.editable &__input,
|
||||||
|
&__option.disabled &__input {
|
||||||
&:active,
|
&:active,
|
||||||
&:focus,
|
&:focus,
|
||||||
&:hover {
|
&:hover {
|
||||||
|
|||||||
@@ -96,12 +96,17 @@ export const ensureComposeIsVisible = (getState) => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
export function setComposeToStatus(status, text, spoiler_text) {
|
export function setComposeToStatus(status, text, spoiler_text) {
|
||||||
return{
|
return (dispatch, getState) => {
|
||||||
type: COMPOSE_SET_STATUS,
|
const maxOptions = getState().server.getIn(['server', 'configuration', 'polls', 'max_options']);
|
||||||
status,
|
|
||||||
text,
|
dispatch({
|
||||||
spoiler_text,
|
type: COMPOSE_SET_STATUS,
|
||||||
};
|
status,
|
||||||
|
text,
|
||||||
|
spoiler_text,
|
||||||
|
maxOptions,
|
||||||
|
});
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export function changeCompose(text) {
|
export function changeCompose(text) {
|
||||||
@@ -183,7 +188,7 @@ export function directCompose(account) {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
export function submitCompose() {
|
export function submitCompose(successCallback) {
|
||||||
return function (dispatch, getState) {
|
return function (dispatch, getState) {
|
||||||
const status = getState().getIn(['compose', 'text'], '');
|
const status = getState().getIn(['compose', 'text'], '');
|
||||||
const media = getState().getIn(['compose', 'media_attachments']);
|
const media = getState().getIn(['compose', 'media_attachments']);
|
||||||
@@ -239,6 +244,9 @@ export function submitCompose() {
|
|||||||
|
|
||||||
dispatch(insertIntoTagHistory(response.data.tags, status));
|
dispatch(insertIntoTagHistory(response.data.tags, status));
|
||||||
dispatch(submitComposeSuccess({ ...response.data }));
|
dispatch(submitComposeSuccess({ ...response.data }));
|
||||||
|
if (typeof successCallback === 'function') {
|
||||||
|
successCallback(response.data);
|
||||||
|
}
|
||||||
|
|
||||||
// To make the app more responsive, immediately push the status
|
// To make the app more responsive, immediately push the status
|
||||||
// into the columns
|
// into the columns
|
||||||
|
|||||||
@@ -21,6 +21,15 @@ export function normalizeFilterResult(result) {
|
|||||||
return normalResult;
|
return normalResult;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function stripQuoteFallback(text) {
|
||||||
|
const wrapper = document.createElement('div');
|
||||||
|
wrapper.innerHTML = text;
|
||||||
|
|
||||||
|
wrapper.querySelector('.quote-inline')?.remove();
|
||||||
|
|
||||||
|
return wrapper.innerHTML;
|
||||||
|
}
|
||||||
|
|
||||||
export function normalizeStatus(status, normalOldStatus) {
|
export function normalizeStatus(status, normalOldStatus) {
|
||||||
const normalStatus = { ...status };
|
const normalStatus = { ...status };
|
||||||
|
|
||||||
@@ -72,7 +81,7 @@ export function normalizeStatus(status, normalOldStatus) {
|
|||||||
} else {
|
} else {
|
||||||
// If the status has a CW but no contents, treat the CW as if it were the
|
// If the status has a CW but no contents, treat the CW as if it were the
|
||||||
// status' contents, to avoid having a CW toggle with seemingly no effect.
|
// status' contents, to avoid having a CW toggle with seemingly no effect.
|
||||||
if (normalStatus.spoiler_text && !normalStatus.content) {
|
if (normalStatus.spoiler_text && !normalStatus.content && !normalStatus.quote) {
|
||||||
normalStatus.content = normalStatus.spoiler_text;
|
normalStatus.content = normalStatus.spoiler_text;
|
||||||
normalStatus.spoiler_text = '';
|
normalStatus.spoiler_text = '';
|
||||||
}
|
}
|
||||||
@@ -86,6 +95,11 @@ export function normalizeStatus(status, normalOldStatus) {
|
|||||||
normalStatus.spoilerHtml = emojify(escapeTextContentForBrowser(spoilerText), emojiMap);
|
normalStatus.spoilerHtml = emojify(escapeTextContentForBrowser(spoilerText), emojiMap);
|
||||||
normalStatus.hidden = expandSpoilers ? false : spoilerText.length > 0 || normalStatus.sensitive;
|
normalStatus.hidden = expandSpoilers ? false : spoilerText.length > 0 || normalStatus.sensitive;
|
||||||
|
|
||||||
|
// Remove quote fallback link from the DOM so it doesn't mess with paragraph margins
|
||||||
|
if (normalStatus.quote) {
|
||||||
|
normalStatus.contentHtml = stripQuoteFallback(normalStatus.contentHtml);
|
||||||
|
}
|
||||||
|
|
||||||
if (normalStatus.url && !(normalStatus.url.startsWith('http://') || normalStatus.url.startsWith('https://'))) {
|
if (normalStatus.url && !(normalStatus.url.startsWith('http://') || normalStatus.url.startsWith('https://'))) {
|
||||||
normalStatus.url = null;
|
normalStatus.url = null;
|
||||||
}
|
}
|
||||||
@@ -125,6 +139,11 @@ export function normalizeStatusTranslation(translation, status) {
|
|||||||
spoiler_text: translation.spoiler_text,
|
spoiler_text: translation.spoiler_text,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// Remove quote fallback link from the DOM so it doesn't mess with paragraph margins
|
||||||
|
if (status.get('quote')) {
|
||||||
|
normalTranslation.contentHtml = stripQuoteFallback(normalTranslation.contentHtml);
|
||||||
|
}
|
||||||
|
|
||||||
return normalTranslation;
|
return normalTranslation;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -3,7 +3,7 @@ import { browserHistory } from 'mastodon/components/router';
|
|||||||
import api from '../api';
|
import api from '../api';
|
||||||
|
|
||||||
import { ensureComposeIsVisible, setComposeToStatus } from './compose';
|
import { ensureComposeIsVisible, setComposeToStatus } from './compose';
|
||||||
import { importFetchedStatus, importFetchedStatuses, importFetchedAccount } from './importer';
|
import { importFetchedStatus, importFetchedAccount } from './importer';
|
||||||
import { fetchContext } from './statuses_typed';
|
import { fetchContext } from './statuses_typed';
|
||||||
import { deleteFromTimelines } from './timelines';
|
import { deleteFromTimelines } from './timelines';
|
||||||
|
|
||||||
@@ -48,7 +48,18 @@ export function fetchStatusRequest(id, skipLoading) {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
export function fetchStatus(id, forceFetch = false, alsoFetchContext = true) {
|
/**
|
||||||
|
* @param {string} id
|
||||||
|
* @param {Object} [options]
|
||||||
|
* @param {boolean} [options.forceFetch]
|
||||||
|
* @param {boolean} [options.alsoFetchContext]
|
||||||
|
* @param {string | null | undefined} [options.parentQuotePostId]
|
||||||
|
*/
|
||||||
|
export function fetchStatus(id, {
|
||||||
|
forceFetch = false,
|
||||||
|
alsoFetchContext = true,
|
||||||
|
parentQuotePostId,
|
||||||
|
} = {}) {
|
||||||
return (dispatch, getState) => {
|
return (dispatch, getState) => {
|
||||||
const skipLoading = !forceFetch && getState().getIn(['statuses', id], null) !== null;
|
const skipLoading = !forceFetch && getState().getIn(['statuses', id], null) !== null;
|
||||||
|
|
||||||
@@ -66,7 +77,9 @@ export function fetchStatus(id, forceFetch = false, alsoFetchContext = true) {
|
|||||||
dispatch(importFetchedStatus(response.data));
|
dispatch(importFetchedStatus(response.data));
|
||||||
dispatch(fetchStatusSuccess(skipLoading));
|
dispatch(fetchStatusSuccess(skipLoading));
|
||||||
}).catch(error => {
|
}).catch(error => {
|
||||||
dispatch(fetchStatusFail(id, error, skipLoading));
|
dispatch(fetchStatusFail(id, error, skipLoading, parentQuotePostId));
|
||||||
|
if (error.status === 404)
|
||||||
|
dispatch(deleteFromTimelines(id));
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
@@ -78,21 +91,27 @@ export function fetchStatusSuccess(skipLoading) {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
export function fetchStatusFail(id, error, skipLoading) {
|
export function fetchStatusFail(id, error, skipLoading, parentQuotePostId) {
|
||||||
return {
|
return {
|
||||||
type: STATUS_FETCH_FAIL,
|
type: STATUS_FETCH_FAIL,
|
||||||
id,
|
id,
|
||||||
error,
|
error,
|
||||||
|
parentQuotePostId,
|
||||||
skipLoading,
|
skipLoading,
|
||||||
skipAlert: true,
|
skipAlert: true,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
export function redraft(status, raw_text) {
|
export function redraft(status, raw_text) {
|
||||||
return {
|
return (dispatch, getState) => {
|
||||||
type: REDRAFT,
|
const maxOptions = getState().server.getIn(['server', 'configuration', 'polls', 'max_options']);
|
||||||
status,
|
|
||||||
raw_text,
|
dispatch({
|
||||||
|
type: REDRAFT,
|
||||||
|
status,
|
||||||
|
raw_text,
|
||||||
|
maxOptions,
|
||||||
|
});
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,10 +1,11 @@
|
|||||||
import { apiRequestPost } from 'mastodon/api';
|
import { apiRequestPost } from 'mastodon/api';
|
||||||
import type { Status, StatusVisibility } from 'mastodon/models/status';
|
import type { ApiStatusJSON } from 'mastodon/api_types/statuses';
|
||||||
|
import type { StatusVisibility } from 'mastodon/models/status';
|
||||||
|
|
||||||
export const apiReblog = (statusId: string, visibility: StatusVisibility) =>
|
export const apiReblog = (statusId: string, visibility: StatusVisibility) =>
|
||||||
apiRequestPost<{ reblog: Status }>(`v1/statuses/${statusId}/reblog`, {
|
apiRequestPost<{ reblog: ApiStatusJSON }>(`v1/statuses/${statusId}/reblog`, {
|
||||||
visibility,
|
visibility,
|
||||||
});
|
});
|
||||||
|
|
||||||
export const apiUnreblog = (statusId: string) =>
|
export const apiUnreblog = (statusId: string) =>
|
||||||
apiRequestPost<Status>(`v1/statuses/${statusId}/unreblog`);
|
apiRequestPost<ApiStatusJSON>(`v1/statuses/${statusId}/unreblog`);
|
||||||
|
|||||||
@@ -37,7 +37,7 @@ export interface BaseApiAccountJSON {
|
|||||||
roles?: ApiAccountJSON[];
|
roles?: ApiAccountJSON[];
|
||||||
statuses_count: number;
|
statuses_count: number;
|
||||||
uri: string;
|
uri: string;
|
||||||
url: string;
|
url?: string;
|
||||||
username: string;
|
username: string;
|
||||||
moved?: ApiAccountJSON;
|
moved?: ApiAccountJSON;
|
||||||
suspended?: boolean;
|
suspended?: boolean;
|
||||||
|
|||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user