mirror of
https://github.com/glitch-soc/mastodon.git
synced 2025-12-12 23:38:20 +00:00
Compare commits
219 Commits
v4.5.0-bet
...
6558a1e07a
| 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
|
||||
# This needs to be updated after each minor version release
|
||||
flavor: |
|
||||
latest=${{ startsWith(github.ref, 'refs/tags/v4.3.') }}
|
||||
latest=${{ startsWith(github.ref, 'refs/tags/v4.4.') }}
|
||||
tags: |
|
||||
type=pep440,pattern={{raw}}
|
||||
type=pep440,pattern=v{{major}}.{{minor}}
|
||||
@@ -37,7 +37,7 @@ jobs:
|
||||
# Only tag with latest when ran against the latest stable branch
|
||||
# This needs to be updated after each minor version release
|
||||
flavor: |
|
||||
latest=${{ startsWith(github.ref, 'refs/tags/v4.3.') }}
|
||||
latest=${{ startsWith(github.ref, 'refs/tags/v4.4.') }}
|
||||
tags: |
|
||||
type=pep440,pattern={{raw}}
|
||||
type=pep440,pattern=v{{major}}.{{minor}}
|
||||
|
||||
@@ -9,7 +9,7 @@ permissions:
|
||||
jobs:
|
||||
download-translations-stable:
|
||||
runs-on: ubuntu-latest
|
||||
if: github.repository == 'mastodon/mastodon'
|
||||
if: github.repository == 'glitch-soc/mastodon'
|
||||
|
||||
steps:
|
||||
- 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/doorkeeper.en.yml
|
||||
- .github/workflows/crowdin-upload.yml
|
||||
workflow_dispatch:
|
||||
|
||||
jobs:
|
||||
upload-translations:
|
||||
|
||||
169
CHANGELOG.md
169
CHANGELOG.md
@@ -2,7 +2,165 @@
|
||||
|
||||
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
|
||||
|
||||
@@ -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.
|
||||
- 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 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.
|
||||
- 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.\
|
||||
@@ -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 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 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).
|
||||
- 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.
|
||||
@@ -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 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.
|
||||
- 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).
|
||||
- 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)\
|
||||
@@ -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 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 inaccessible “Clear search” button (#35152 and #35281 by @diondiondion)
|
||||
- Fix search operators sometimes getting lost (#35190 by @ClearlyClaire)
|
||||
- Fix directory scroll position reset (#34560 by @przucidlo)
|
||||
- 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 glitchy iOS media attachment drag interactions (#35057 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 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)
|
||||
|
||||
3
Gemfile
3
Gemfile
@@ -159,6 +159,9 @@ group :test do
|
||||
|
||||
# Stub web requests for specs
|
||||
gem 'webmock', '~> 3.18'
|
||||
|
||||
# Websocket driver for testing integration between rails/sidekiq and streaming
|
||||
gem 'websocket-driver', '~> 0.8', require: false
|
||||
end
|
||||
|
||||
group :development do
|
||||
|
||||
123
Gemfile.lock
123
Gemfile.lock
@@ -10,29 +10,29 @@ GIT
|
||||
GEM
|
||||
remote: https://rubygems.org/
|
||||
specs:
|
||||
actioncable (8.0.2)
|
||||
actionpack (= 8.0.2)
|
||||
activesupport (= 8.0.2)
|
||||
actioncable (8.0.2.1)
|
||||
actionpack (= 8.0.2.1)
|
||||
activesupport (= 8.0.2.1)
|
||||
nio4r (~> 2.0)
|
||||
websocket-driver (>= 0.6.1)
|
||||
zeitwerk (~> 2.6)
|
||||
actionmailbox (8.0.2)
|
||||
actionpack (= 8.0.2)
|
||||
activejob (= 8.0.2)
|
||||
activerecord (= 8.0.2)
|
||||
activestorage (= 8.0.2)
|
||||
activesupport (= 8.0.2)
|
||||
actionmailbox (8.0.2.1)
|
||||
actionpack (= 8.0.2.1)
|
||||
activejob (= 8.0.2.1)
|
||||
activerecord (= 8.0.2.1)
|
||||
activestorage (= 8.0.2.1)
|
||||
activesupport (= 8.0.2.1)
|
||||
mail (>= 2.8.0)
|
||||
actionmailer (8.0.2)
|
||||
actionpack (= 8.0.2)
|
||||
actionview (= 8.0.2)
|
||||
activejob (= 8.0.2)
|
||||
activesupport (= 8.0.2)
|
||||
actionmailer (8.0.2.1)
|
||||
actionpack (= 8.0.2.1)
|
||||
actionview (= 8.0.2.1)
|
||||
activejob (= 8.0.2.1)
|
||||
activesupport (= 8.0.2.1)
|
||||
mail (>= 2.8.0)
|
||||
rails-dom-testing (~> 2.2)
|
||||
actionpack (8.0.2)
|
||||
actionview (= 8.0.2)
|
||||
activesupport (= 8.0.2)
|
||||
actionpack (8.0.2.1)
|
||||
actionview (= 8.0.2.1)
|
||||
activesupport (= 8.0.2.1)
|
||||
nokogiri (>= 1.8.5)
|
||||
rack (>= 2.2.4)
|
||||
rack-session (>= 1.0.1)
|
||||
@@ -40,15 +40,15 @@ GEM
|
||||
rails-dom-testing (~> 2.2)
|
||||
rails-html-sanitizer (~> 1.6)
|
||||
useragent (~> 0.16)
|
||||
actiontext (8.0.2)
|
||||
actionpack (= 8.0.2)
|
||||
activerecord (= 8.0.2)
|
||||
activestorage (= 8.0.2)
|
||||
activesupport (= 8.0.2)
|
||||
actiontext (8.0.2.1)
|
||||
actionpack (= 8.0.2.1)
|
||||
activerecord (= 8.0.2.1)
|
||||
activestorage (= 8.0.2.1)
|
||||
activesupport (= 8.0.2.1)
|
||||
globalid (>= 0.6.0)
|
||||
nokogiri (>= 1.8.5)
|
||||
actionview (8.0.2)
|
||||
activesupport (= 8.0.2)
|
||||
actionview (8.0.2.1)
|
||||
activesupport (= 8.0.2.1)
|
||||
builder (~> 3.1)
|
||||
erubi (~> 1.11)
|
||||
rails-dom-testing (~> 2.2)
|
||||
@@ -58,22 +58,22 @@ GEM
|
||||
activemodel (>= 4.1)
|
||||
case_transform (>= 0.2)
|
||||
jsonapi-renderer (>= 0.1.1.beta1, < 0.3)
|
||||
activejob (8.0.2)
|
||||
activesupport (= 8.0.2)
|
||||
activejob (8.0.2.1)
|
||||
activesupport (= 8.0.2.1)
|
||||
globalid (>= 0.3.6)
|
||||
activemodel (8.0.2)
|
||||
activesupport (= 8.0.2)
|
||||
activerecord (8.0.2)
|
||||
activemodel (= 8.0.2)
|
||||
activesupport (= 8.0.2)
|
||||
activemodel (8.0.2.1)
|
||||
activesupport (= 8.0.2.1)
|
||||
activerecord (8.0.2.1)
|
||||
activemodel (= 8.0.2.1)
|
||||
activesupport (= 8.0.2.1)
|
||||
timeout (>= 0.4.0)
|
||||
activestorage (8.0.2)
|
||||
actionpack (= 8.0.2)
|
||||
activejob (= 8.0.2)
|
||||
activerecord (= 8.0.2)
|
||||
activesupport (= 8.0.2)
|
||||
activestorage (8.0.2.1)
|
||||
actionpack (= 8.0.2.1)
|
||||
activejob (= 8.0.2.1)
|
||||
activerecord (= 8.0.2.1)
|
||||
activesupport (= 8.0.2.1)
|
||||
marcel (~> 1.0)
|
||||
activesupport (8.0.2)
|
||||
activesupport (8.0.2.1)
|
||||
base64
|
||||
benchmark (>= 0.3)
|
||||
bigdecimal
|
||||
@@ -458,7 +458,7 @@ GEM
|
||||
net-smtp (0.5.1)
|
||||
net-protocol
|
||||
nio4r (2.7.4)
|
||||
nokogiri (1.18.8)
|
||||
nokogiri (1.18.9)
|
||||
mini_portile2 (~> 2.8.2)
|
||||
racc (~> 1.4)
|
||||
oj (3.16.11)
|
||||
@@ -494,7 +494,7 @@ GEM
|
||||
tzinfo
|
||||
validate_url
|
||||
webfinger (~> 2.0)
|
||||
openssl (3.3.0)
|
||||
openssl (3.3.1)
|
||||
openssl-signature_algorithm (1.3.0)
|
||||
openssl (> 2.0)
|
||||
opentelemetry-api (1.5.0)
|
||||
@@ -642,7 +642,7 @@ GEM
|
||||
activesupport (>= 3.0.0)
|
||||
raabro (1.4.0)
|
||||
racc (1.8.1)
|
||||
rack (3.1.16)
|
||||
rack (3.1.18)
|
||||
rack-attack (6.7.0)
|
||||
rack (>= 1.0, < 4)
|
||||
rack-cors (3.0.0)
|
||||
@@ -668,20 +668,20 @@ GEM
|
||||
rack (>= 1.3)
|
||||
rackup (2.2.1)
|
||||
rack (>= 3)
|
||||
rails (8.0.2)
|
||||
actioncable (= 8.0.2)
|
||||
actionmailbox (= 8.0.2)
|
||||
actionmailer (= 8.0.2)
|
||||
actionpack (= 8.0.2)
|
||||
actiontext (= 8.0.2)
|
||||
actionview (= 8.0.2)
|
||||
activejob (= 8.0.2)
|
||||
activemodel (= 8.0.2)
|
||||
activerecord (= 8.0.2)
|
||||
activestorage (= 8.0.2)
|
||||
activesupport (= 8.0.2)
|
||||
rails (8.0.2.1)
|
||||
actioncable (= 8.0.2.1)
|
||||
actionmailbox (= 8.0.2.1)
|
||||
actionmailer (= 8.0.2.1)
|
||||
actionpack (= 8.0.2.1)
|
||||
actiontext (= 8.0.2.1)
|
||||
actionview (= 8.0.2.1)
|
||||
activejob (= 8.0.2.1)
|
||||
activemodel (= 8.0.2.1)
|
||||
activerecord (= 8.0.2.1)
|
||||
activestorage (= 8.0.2.1)
|
||||
activesupport (= 8.0.2.1)
|
||||
bundler (>= 1.15.0)
|
||||
railties (= 8.0.2)
|
||||
railties (= 8.0.2.1)
|
||||
rails-dom-testing (2.2.0)
|
||||
activesupport (>= 5.0.0)
|
||||
minitest
|
||||
@@ -692,9 +692,9 @@ GEM
|
||||
rails-i18n (8.0.1)
|
||||
i18n (>= 0.7, < 2)
|
||||
railties (>= 8.0.0, < 9)
|
||||
railties (8.0.2)
|
||||
actionpack (= 8.0.2)
|
||||
activesupport (= 8.0.2)
|
||||
railties (8.0.2.1)
|
||||
actionpack (= 8.0.2.1)
|
||||
activesupport (= 8.0.2.1)
|
||||
irb (~> 1.13)
|
||||
rackup (>= 1.0.0)
|
||||
rake (>= 12.2)
|
||||
@@ -725,7 +725,7 @@ GEM
|
||||
responders (3.1.1)
|
||||
actionpack (>= 5.2)
|
||||
railties (>= 5.2)
|
||||
rexml (3.4.1)
|
||||
rexml (3.4.4)
|
||||
rotp (6.3.0)
|
||||
rouge (4.5.2)
|
||||
rpam2 (4.0.2)
|
||||
@@ -801,7 +801,7 @@ GEM
|
||||
ruby-prof (1.7.2)
|
||||
base64
|
||||
ruby-progressbar (1.13.0)
|
||||
ruby-saml (1.18.0)
|
||||
ruby-saml (1.18.1)
|
||||
nokogiri (>= 1.13.10)
|
||||
rexml
|
||||
ruby-vips (2.2.4)
|
||||
@@ -869,7 +869,7 @@ GEM
|
||||
terrapin (1.1.0)
|
||||
climate_control
|
||||
test-prof (1.4.4)
|
||||
thor (1.3.2)
|
||||
thor (1.4.0)
|
||||
tilt (2.6.0)
|
||||
timeout (0.4.3)
|
||||
tpm-key_attestation (0.14.1)
|
||||
@@ -899,7 +899,7 @@ GEM
|
||||
unicode-display_width (3.1.4)
|
||||
unicode-emoji (~> 4.0, >= 4.0.4)
|
||||
unicode-emoji (4.0.4)
|
||||
uri (1.0.3)
|
||||
uri (1.0.4)
|
||||
useragent (0.16.11)
|
||||
validate_url (1.0.15)
|
||||
activemodel (>= 3.0.0)
|
||||
@@ -932,7 +932,7 @@ GEM
|
||||
crack (>= 0.3.2)
|
||||
hashdiff (>= 0.4.0, < 2.0.0)
|
||||
webrick (1.9.1)
|
||||
websocket-driver (0.7.7)
|
||||
websocket-driver (0.8.0)
|
||||
base64
|
||||
websocket-extensions (>= 0.1.0)
|
||||
websocket-extensions (0.1.5)
|
||||
@@ -1096,6 +1096,7 @@ DEPENDENCIES
|
||||
webauthn (~> 3.0)
|
||||
webmock (~> 3.18)
|
||||
webpush!
|
||||
websocket-driver (~> 0.8)
|
||||
xorcist (~> 1.1)
|
||||
|
||||
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
|
||||
|
||||
| Version | Supported |
|
||||
| ------- | --------- |
|
||||
| 4.3.x | Yes |
|
||||
| 4.2.x | Yes |
|
||||
| < 4.2 | No |
|
||||
| Version | Supported |
|
||||
| ------- | ---------------- |
|
||||
| 4.4.x | Yes |
|
||||
| 4.3.x | Until 2026-05-06 |
|
||||
| 4.2.x | Until 2026-01-08 |
|
||||
| < 4.2 | No |
|
||||
|
||||
@@ -22,7 +22,7 @@ class ActivityPub::LikesController < ActivityPub::BaseController
|
||||
def set_status
|
||||
@status = @account.statuses.find(params[:status_id])
|
||||
authorize @status, :show?
|
||||
rescue Mastodon::NotPermittedError
|
||||
rescue ActiveRecord::RecordNotFound, Mastodon::NotPermittedError
|
||||
not_found
|
||||
end
|
||||
|
||||
|
||||
@@ -25,7 +25,7 @@ class ActivityPub::RepliesController < ActivityPub::BaseController
|
||||
def set_status
|
||||
@status = @account.statuses.find(params[:status_id])
|
||||
authorize @status, :show?
|
||||
rescue Mastodon::NotPermittedError
|
||||
rescue ActiveRecord::RecordNotFound, Mastodon::NotPermittedError
|
||||
not_found
|
||||
end
|
||||
|
||||
|
||||
@@ -22,7 +22,7 @@ class ActivityPub::SharesController < ActivityPub::BaseController
|
||||
def set_status
|
||||
@status = @account.statuses.find(params[:status_id])
|
||||
authorize @status, :show?
|
||||
rescue Mastodon::NotPermittedError
|
||||
rescue ActiveRecord::RecordNotFound, Mastodon::NotPermittedError
|
||||
not_found
|
||||
end
|
||||
|
||||
|
||||
@@ -14,16 +14,20 @@ module Admin
|
||||
def create
|
||||
authorize @account, :show?
|
||||
|
||||
account_action = Admin::AccountAction.new(resource_params)
|
||||
account_action.target_account = @account
|
||||
account_action.current_account = current_account
|
||||
@account_action = Admin::AccountAction.new(resource_params)
|
||||
@account_action.target_account = @account
|
||||
@account_action.current_account = current_account
|
||||
|
||||
account_action.save!
|
||||
|
||||
if account_action.with_report?
|
||||
redirect_to admin_reports_path, notice: I18n.t('admin.reports.processed_msg', id: resource_params[:report_id])
|
||||
if @account_action.save
|
||||
if @account_action.with_report?
|
||||
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
|
||||
redirect_to admin_account_path(@account.id)
|
||||
@warning_presets = AccountWarningPreset.all
|
||||
|
||||
render :new
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
@@ -9,10 +9,16 @@ module Admin
|
||||
|
||||
@pending_appeals_count = Appeal.pending.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
|
||||
@system_checks = Admin::SystemCheck.perform(current_user)
|
||||
@time_period = (29.days.ago.to_date...Time.now.utc.to_date)
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def pending_tags
|
||||
::Trends::TagFilter.new(status: :pending_review).results
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
@@ -17,7 +17,7 @@ class Api::V1::Polls::VotesController < Api::BaseController
|
||||
def set_poll
|
||||
@poll = Poll.find(params[:poll_id])
|
||||
authorize @poll.status, :show?
|
||||
rescue Mastodon::NotPermittedError
|
||||
rescue ActiveRecord::RecordNotFound, Mastodon::NotPermittedError
|
||||
not_found
|
||||
end
|
||||
|
||||
|
||||
@@ -17,7 +17,7 @@ class Api::V1::PollsController < Api::BaseController
|
||||
def set_poll
|
||||
@poll = Poll.find(params[:id])
|
||||
authorize @poll.status, :show?
|
||||
rescue Mastodon::NotPermittedError
|
||||
rescue ActiveRecord::RecordNotFound, Mastodon::NotPermittedError
|
||||
not_found
|
||||
end
|
||||
|
||||
|
||||
@@ -10,7 +10,7 @@ class Api::V1::Statuses::BaseController < Api::BaseController
|
||||
def set_status
|
||||
@status = Status.find(params[:status_id])
|
||||
authorize @status, :show?
|
||||
rescue Mastodon::NotPermittedError
|
||||
rescue ActiveRecord::RecordNotFound, Mastodon::NotPermittedError
|
||||
not_found
|
||||
end
|
||||
end
|
||||
|
||||
@@ -23,7 +23,7 @@ class Api::V1::Statuses::BookmarksController < Api::V1::Statuses::BaseController
|
||||
bookmark&.destroy!
|
||||
|
||||
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
|
||||
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 } })
|
||||
render json: @status, serializer: REST::StatusSerializer, relationships: relationships
|
||||
rescue Mastodon::NotPermittedError
|
||||
rescue ActiveRecord::RecordNotFound, Mastodon::NotPermittedError
|
||||
not_found
|
||||
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 } })
|
||||
render json: @reblog, serializer: REST::StatusSerializer, relationships: relationships
|
||||
rescue Mastodon::NotPermittedError
|
||||
rescue ActiveRecord::RecordNotFound, Mastodon::NotPermittedError
|
||||
not_found
|
||||
end
|
||||
|
||||
@@ -45,7 +45,7 @@ class Api::V1::Statuses::ReblogsController < Api::V1::Statuses::BaseController
|
||||
def set_reblog
|
||||
@reblog = Status.find(params[:status_id])
|
||||
authorize @reblog, :show?
|
||||
rescue Mastodon::NotPermittedError
|
||||
rescue ActiveRecord::RecordNotFound, Mastodon::NotPermittedError
|
||||
not_found
|
||||
end
|
||||
|
||||
|
||||
@@ -129,7 +129,7 @@ class Api::V1::StatusesController < Api::BaseController
|
||||
def set_status
|
||||
@status = Status.find(params[:id])
|
||||
authorize @status, :show?
|
||||
rescue Mastodon::NotPermittedError
|
||||
rescue ActiveRecord::RecordNotFound, Mastodon::NotPermittedError
|
||||
not_found
|
||||
end
|
||||
|
||||
|
||||
@@ -30,7 +30,7 @@ class Api::Web::EmbedsController < Api::Web::BaseController
|
||||
def set_status
|
||||
@status = Status.find(params[:id])
|
||||
authorize @status, :show?
|
||||
rescue Mastodon::NotPermittedError
|
||||
rescue ActiveRecord::RecordNotFound, Mastodon::NotPermittedError
|
||||
not_found
|
||||
end
|
||||
end
|
||||
|
||||
@@ -21,7 +21,7 @@ class AuthorizeInteractionsController < ApplicationController
|
||||
def set_resource
|
||||
@resource = located_resource
|
||||
authorize(@resource, :show?) if @resource.is_a?(Status)
|
||||
rescue Mastodon::NotPermittedError
|
||||
rescue ActiveRecord::RecordNotFound, Mastodon::NotPermittedError
|
||||
not_found
|
||||
end
|
||||
|
||||
|
||||
@@ -64,6 +64,9 @@ module SignatureVerification
|
||||
return (@signed_request_actor = actor) if signed_request.verified?(actor)
|
||||
|
||||
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
|
||||
fail_with! e.message
|
||||
rescue *Mastodon::HTTP_CONNECTION_ERRORS => e
|
||||
|
||||
@@ -50,6 +50,13 @@ module WebAppControllerConcern
|
||||
return unless current_user&.require_tos_interstitial?
|
||||
|
||||
@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'
|
||||
end
|
||||
|
||||
|
||||
@@ -34,7 +34,7 @@ class MediaController < ApplicationController
|
||||
|
||||
def verify_permitted_status!
|
||||
authorize @media_attachment.status, :show?
|
||||
rescue Mastodon::NotPermittedError
|
||||
rescue ActiveRecord::RecordNotFound, Mastodon::NotPermittedError
|
||||
not_found
|
||||
end
|
||||
|
||||
|
||||
@@ -59,7 +59,7 @@ class StatusesController < ApplicationController
|
||||
def set_status
|
||||
@status = @account.statuses.find(params[:id])
|
||||
authorize @status, :show?
|
||||
rescue Mastodon::NotPermittedError
|
||||
rescue ActiveRecord::RecordNotFound, Mastodon::NotPermittedError
|
||||
not_found
|
||||
end
|
||||
|
||||
|
||||
@@ -66,7 +66,7 @@ module ApplicationHelper
|
||||
|
||||
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)
|
||||
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
|
||||
|
||||
def locale_direction
|
||||
@@ -102,6 +102,16 @@ module ApplicationHelper
|
||||
policy(record).public_send(:"#{action}?")
|
||||
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 = {})
|
||||
safe_join(
|
||||
[
|
||||
|
||||
@@ -27,6 +27,12 @@ module ContextHelper
|
||||
suspended: { 'toot' => 'http://joinmastodon.org/ns#', 'suspended' => 'toot:suspended' },
|
||||
attribution_domains: { 'toot' => 'http://joinmastodon.org/ns#', 'attributionDomains' => { '@id' => 'toot:attributionDomains', '@type' => '@id' } },
|
||||
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: {
|
||||
'gts' => 'https://gotosocial.org/ns#',
|
||||
'interactionPolicy' => { '@id' => 'gts:interactionPolicy', '@type' => '@id' },
|
||||
|
||||
@@ -101,12 +101,17 @@ export const ensureComposeIsVisible = (getState) => {
|
||||
};
|
||||
|
||||
export function setComposeToStatus(status, text, spoiler_text, content_type) {
|
||||
return{
|
||||
type: COMPOSE_SET_STATUS,
|
||||
status,
|
||||
text,
|
||||
spoiler_text,
|
||||
content_type,
|
||||
return (dispatch, getState) => {
|
||||
const maxOptions = getState().server.getIn(['server', 'configuration', 'polls', 'max_options']);
|
||||
|
||||
dispatch({
|
||||
type: COMPOSE_SET_STATUS,
|
||||
status,
|
||||
text,
|
||||
spoiler_text,
|
||||
content_type,
|
||||
maxOptions
|
||||
});
|
||||
};
|
||||
}
|
||||
|
||||
@@ -193,8 +198,9 @@ export function directCompose(account) {
|
||||
|
||||
/**
|
||||
* @param {null | string} overridePrivacy
|
||||
* @param {undefined | Function} successCallback
|
||||
*/
|
||||
export function submitCompose(overridePrivacy = null) {
|
||||
export function submitCompose(overridePrivacy = null, successCallback = undefined) {
|
||||
return function (dispatch, getState) {
|
||||
let status = getState().getIn(['compose', 'text'], '');
|
||||
const media = getState().getIn(['compose', 'media_attachments']);
|
||||
@@ -259,6 +265,9 @@ export function submitCompose(overridePrivacy = null) {
|
||||
|
||||
dispatch(insertIntoTagHistory(response.data.tags, status));
|
||||
dispatch(submitComposeSuccess({ ...response.data }));
|
||||
if (typeof successCallback === 'function') {
|
||||
successCallback(response.data);
|
||||
}
|
||||
|
||||
// To make the app more responsive, immediately push the status
|
||||
// into the columns
|
||||
|
||||
@@ -21,6 +21,15 @@ export function normalizeFilterResult(result) {
|
||||
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) {
|
||||
const normalStatus = { ...status };
|
||||
|
||||
@@ -78,6 +87,11 @@ export function normalizeStatus(status, normalOldStatus, settings) {
|
||||
normalStatus.spoilerHtml = emojify(escapeTextContentForBrowser(spoilerText), emojiMap);
|
||||
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://'))) {
|
||||
normalStatus.url = null;
|
||||
}
|
||||
@@ -117,6 +131,11 @@ export function normalizeStatusTranslation(translation, status) {
|
||||
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;
|
||||
}
|
||||
|
||||
|
||||
@@ -3,7 +3,7 @@ import { browserHistory } from 'flavours/glitch/components/router';
|
||||
import api from '../api';
|
||||
|
||||
import { ensureComposeIsVisible, setComposeToStatus } from './compose';
|
||||
import { importFetchedStatus, importFetchedStatuses, importFetchedAccount } from './importer';
|
||||
import { importFetchedStatus, importFetchedAccount } from './importer';
|
||||
import { fetchContext } from './statuses_typed';
|
||||
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) => {
|
||||
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(fetchStatusSuccess(skipLoading));
|
||||
}).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 {
|
||||
type: STATUS_FETCH_FAIL,
|
||||
id,
|
||||
error,
|
||||
parentQuotePostId,
|
||||
skipLoading,
|
||||
skipAlert: true,
|
||||
};
|
||||
}
|
||||
|
||||
export function redraft(status, raw_text, content_type) {
|
||||
return {
|
||||
type: REDRAFT,
|
||||
status,
|
||||
raw_text,
|
||||
content_type,
|
||||
return (dispatch, getState) => {
|
||||
const maxOptions = getState().server.getIn(['server', 'configuration', 'polls', 'max_options']);
|
||||
|
||||
dispatch({
|
||||
type: REDRAFT,
|
||||
status,
|
||||
raw_text,
|
||||
content_type,
|
||||
maxOptions,
|
||||
});
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
@@ -1,10 +1,11 @@
|
||||
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) =>
|
||||
apiRequestPost<{ reblog: Status }>(`v1/statuses/${statusId}/reblog`, {
|
||||
apiRequestPost<{ reblog: ApiStatusJSON }>(`v1/statuses/${statusId}/reblog`, {
|
||||
visibility,
|
||||
});
|
||||
|
||||
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';
|
||||
|
||||
export const AccountBio: React.FC<{
|
||||
interface AccountBioProps {
|
||||
note: string;
|
||||
className: string;
|
||||
}> = ({ note, className }) => {
|
||||
const handleClick = useLinks();
|
||||
dropdownAccountId?: string;
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
@@ -15,6 +33,28 @@ export const AccountBio: React.FC<{
|
||||
className={`${className} translate`}
|
||||
dangerouslySetInnerHTML={{ __html: note }}
|
||||
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,
|
||||
cloneElement,
|
||||
Children,
|
||||
useId,
|
||||
} from 'react';
|
||||
|
||||
import classNames from 'classnames';
|
||||
@@ -16,6 +17,7 @@ import Overlay from 'react-overlays/Overlay';
|
||||
import type {
|
||||
OffsetValue,
|
||||
UsePopperOptions,
|
||||
Placement,
|
||||
} from 'react-overlays/esm/usePopper';
|
||||
|
||||
import { fetchRelationships } from 'flavours/glitch/actions/accounts';
|
||||
@@ -295,6 +297,11 @@ interface DropdownProps<Item = MenuItem> {
|
||||
title?: string;
|
||||
disabled?: boolean;
|
||||
scrollable?: boolean;
|
||||
placement?: Placement;
|
||||
/**
|
||||
* Prevent the `ScrollableList` with this scrollKey
|
||||
* from being scrolled while the dropdown is open
|
||||
*/
|
||||
scrollKey?: string;
|
||||
status?: ImmutableMap<string, unknown>;
|
||||
forceDropdown?: boolean;
|
||||
@@ -316,6 +323,7 @@ export const Dropdown = <Item = MenuItem,>({
|
||||
title = 'Menu',
|
||||
disabled,
|
||||
scrollable,
|
||||
placement = 'bottom',
|
||||
status,
|
||||
forceDropdown = false,
|
||||
renderItem,
|
||||
@@ -331,16 +339,15 @@ export const Dropdown = <Item = MenuItem,>({
|
||||
);
|
||||
const [currentId] = useState(id++);
|
||||
const open = currentId === openDropdownId;
|
||||
const activeElement = useRef<HTMLElement | null>(null);
|
||||
const targetRef = useRef<HTMLButtonElement | null>(null);
|
||||
const buttonRef = useRef<HTMLButtonElement | null>(null);
|
||||
const menuId = useId();
|
||||
const prefetchAccountId = status
|
||||
? status.getIn(['account', 'id'])
|
||||
: undefined;
|
||||
|
||||
const handleClose = useCallback(() => {
|
||||
if (activeElement.current) {
|
||||
activeElement.current.focus({ preventScroll: true });
|
||||
activeElement.current = null;
|
||||
if (buttonRef.current) {
|
||||
buttonRef.current.focus({ preventScroll: true });
|
||||
}
|
||||
|
||||
dispatch(
|
||||
@@ -375,7 +382,7 @@ export const Dropdown = <Item = MenuItem,>({
|
||||
[handleClose, onItemClick, items],
|
||||
);
|
||||
|
||||
const handleClick = useCallback(
|
||||
const toggleDropdown = useCallback(
|
||||
(e: React.MouseEvent | React.KeyboardEvent) => {
|
||||
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(() => {
|
||||
return () => {
|
||||
if (currentId === openDropdownId) {
|
||||
@@ -465,14 +440,16 @@ export const Dropdown = <Item = MenuItem,>({
|
||||
|
||||
let button: React.ReactElement;
|
||||
|
||||
const buttonProps = {
|
||||
disabled,
|
||||
onClick: toggleDropdown,
|
||||
'aria-expanded': open,
|
||||
'aria-controls': menuId,
|
||||
ref: buttonRef,
|
||||
};
|
||||
|
||||
if (children) {
|
||||
button = cloneElement(Children.only(children), {
|
||||
onClick: handleClick,
|
||||
onMouseDown: handleMouseDown,
|
||||
onKeyDown: handleButtonKeyDown,
|
||||
onKeyPress: handleKeyPress,
|
||||
ref: targetRef,
|
||||
});
|
||||
button = cloneElement(Children.only(children), buttonProps);
|
||||
} else if (icon && iconComponent) {
|
||||
button = (
|
||||
<IconButton
|
||||
@@ -480,12 +457,7 @@ export const Dropdown = <Item = MenuItem,>({
|
||||
iconComponent={iconComponent}
|
||||
title={title}
|
||||
active={open}
|
||||
disabled={disabled}
|
||||
onClick={handleClick}
|
||||
onMouseDown={handleMouseDown}
|
||||
onKeyDown={handleButtonKeyDown}
|
||||
onKeyPress={handleKeyPress}
|
||||
ref={targetRef}
|
||||
{...buttonProps}
|
||||
/>
|
||||
);
|
||||
} else {
|
||||
@@ -499,13 +471,13 @@ export const Dropdown = <Item = MenuItem,>({
|
||||
<Overlay
|
||||
show={open}
|
||||
offset={offset}
|
||||
placement='bottom'
|
||||
placement={placement}
|
||||
flip
|
||||
target={targetRef}
|
||||
target={buttonRef}
|
||||
popperConfig={popperConfig}
|
||||
>
|
||||
{({ props, arrowProps, placement }) => (
|
||||
<div {...props}>
|
||||
<div {...props} id={menuId}>
|
||||
<div className={`dropdown-animation dropdown-menu ${placement}`}>
|
||||
<div
|
||||
className={`dropdown-menu__arrow ${placement}`}
|
||||
|
||||
@@ -20,7 +20,7 @@ import { useDrag } from '@use-gesture/react';
|
||||
import { expandAccountFeaturedTimeline } from '@/flavours/glitch/actions/timelines';
|
||||
import { Icon } from '@/flavours/glitch/components/icon';
|
||||
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 { useAppDispatch, useAppSelector } from '@/flavours/glitch/store';
|
||||
import ChevronLeftIcon from '@/material-icons/400-24px/chevron_left.svg?react';
|
||||
@@ -218,12 +218,7 @@ const FeaturedCarouselItem: React.FC<
|
||||
ref={handleRef}
|
||||
{...props}
|
||||
>
|
||||
<StatusContainer
|
||||
// @ts-expect-error inferred props are wrong
|
||||
id={statusId}
|
||||
contextType='account'
|
||||
withCounters
|
||||
/>
|
||||
<StatusQuoteManager id={statusId} contextType='account' withCounters />
|
||||
</animated.div>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -14,7 +14,6 @@ interface Props {
|
||||
onClick?: React.MouseEventHandler<HTMLButtonElement>;
|
||||
onMouseDown?: React.MouseEventHandler<HTMLButtonElement>;
|
||||
onKeyDown?: React.KeyboardEventHandler<HTMLButtonElement>;
|
||||
onKeyPress?: React.KeyboardEventHandler<HTMLButtonElement>;
|
||||
active?: boolean;
|
||||
expanded?: boolean;
|
||||
style?: React.CSSProperties;
|
||||
@@ -47,7 +46,6 @@ export const IconButton = forwardRef<HTMLButtonElement, Props>(
|
||||
activeStyle,
|
||||
onClick,
|
||||
onKeyDown,
|
||||
onKeyPress,
|
||||
onMouseDown,
|
||||
active = false,
|
||||
disabled = false,
|
||||
@@ -89,16 +87,6 @@ export const IconButton = forwardRef<HTMLButtonElement, Props>(
|
||||
[disabled, onClick],
|
||||
);
|
||||
|
||||
const handleKeyPress: React.KeyboardEventHandler<HTMLButtonElement> =
|
||||
useCallback(
|
||||
(e) => {
|
||||
if (!disabled) {
|
||||
onKeyPress?.(e);
|
||||
}
|
||||
},
|
||||
[disabled, onKeyPress],
|
||||
);
|
||||
|
||||
const handleMouseDown: React.MouseEventHandler<HTMLButtonElement> =
|
||||
useCallback(
|
||||
(e) => {
|
||||
@@ -166,7 +154,6 @@ export const IconButton = forwardRef<HTMLButtonElement, Props>(
|
||||
onClick={handleClick}
|
||||
onMouseDown={handleMouseDown}
|
||||
onKeyDown={handleKeyDown}
|
||||
onKeyPress={handleKeyPress} // eslint-disable-line @typescript-eslint/no-deprecated
|
||||
style={buttonStyle}
|
||||
tabIndex={tabIndex}
|
||||
disabled={disabled}
|
||||
|
||||
@@ -190,7 +190,7 @@ class StatusContent extends PureComponent {
|
||||
link.classList.add('unhandled-link');
|
||||
|
||||
link.setAttribute('target', '_blank');
|
||||
link.setAttribute('rel', 'noopener nofollow noreferrer');
|
||||
link.setAttribute('rel', 'noopener nofollow');
|
||||
|
||||
try {
|
||||
if (tagLinks && isLinkMisleading(link)) {
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { useEffect, useMemo } from 'react';
|
||||
import { useCallback, useEffect, useMemo } from 'react';
|
||||
|
||||
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 { Icon } from 'flavours/glitch/components/icon';
|
||||
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 { RootState } from 'flavours/glitch/store';
|
||||
import { useAppDispatch, useAppSelector } from 'flavours/glitch/store';
|
||||
|
||||
import { revealAccount } from '../actions/accounts_typed';
|
||||
import { fetchStatus } from '../actions/statuses';
|
||||
import { makeGetStatus } from '../selectors';
|
||||
import { getAccountHidden } from '../selectors/accounts';
|
||||
|
||||
const MAX_QUOTE_POSTS_NESTING_LEVEL = 1;
|
||||
|
||||
@@ -37,9 +40,7 @@ const QuoteWrapper: React.FC<{
|
||||
);
|
||||
};
|
||||
|
||||
const NestedQuoteLink: React.FC<{
|
||||
status: Status;
|
||||
}> = ({ status }) => {
|
||||
const NestedQuoteLink: React.FC<{ status: Status }> = ({ status }) => {
|
||||
const accountId = status.get('account') as string;
|
||||
const account = useAppSelector((state) =>
|
||||
accountId ? state.accounts.get(accountId) : undefined,
|
||||
@@ -75,24 +76,74 @@ type GetStatusSelector = (
|
||||
props: { id?: string | null; contextType?: string },
|
||||
) => 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<{
|
||||
quote: QuoteMap;
|
||||
contextType?: string;
|
||||
parentQuotePostId?: string | null;
|
||||
variant?: 'full' | 'link';
|
||||
nestingLevel?: number;
|
||||
}> = ({ quote, contextType, nestingLevel = 1, variant = 'full' }) => {
|
||||
}> = ({
|
||||
quote,
|
||||
contextType,
|
||||
parentQuotePostId,
|
||||
nestingLevel = 1,
|
||||
variant = 'full',
|
||||
}) => {
|
||||
const dispatch = useAppDispatch();
|
||||
const quoteState = useAppSelector((state) =>
|
||||
parentQuotePostId
|
||||
? state.statuses.getIn([parentQuotePostId, 'quote', 'state'])
|
||||
: quote.get('state'),
|
||||
);
|
||||
|
||||
const quotedStatusId = quote.get('quoted_status');
|
||||
const quoteState = quote.get('state');
|
||||
const status = useAppSelector((state) =>
|
||||
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(() => {
|
||||
if (!status && quotedStatusId) {
|
||||
dispatch(fetchStatus(quotedStatusId));
|
||||
if (shouldLoadQuote && 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
|
||||
// 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.'
|
||||
/>
|
||||
);
|
||||
} else if (hiddenAccount && accountId) {
|
||||
quoteError = <LimitedAccountHint accountId={accountId} />;
|
||||
}
|
||||
|
||||
if (quoteError) {
|
||||
@@ -173,6 +226,7 @@ export const QuotedStatus: React.FC<{
|
||||
{canRenderChildQuote && (
|
||||
<QuotedStatus
|
||||
quote={childQuote}
|
||||
parentQuotePostId={quotedStatusId}
|
||||
contextType={contextType}
|
||||
variant={
|
||||
nestingLevel === MAX_QUOTE_POSTS_NESTING_LEVEL ? 'link' : 'full'
|
||||
@@ -209,7 +263,11 @@ export const StatusQuoteManager = (props: StatusQuoteManagerProps) => {
|
||||
return (
|
||||
/* @ts-expect-error Status is not yet typed */
|
||||
<StatusContainer {...props}>
|
||||
<QuotedStatus quote={quote} contextType={props.contextType} />
|
||||
<QuotedStatus
|
||||
quote={quote}
|
||||
parentQuotePostId={status?.get('id') as string}
|
||||
contextType={props.contextType}
|
||||
/>
|
||||
</StatusContainer>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -6,6 +6,7 @@ import classNames from 'classnames';
|
||||
import { Helmet } from 'react-helmet';
|
||||
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 LockIcon from '@/material-icons/400-24px/lock.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 fields = account.fields;
|
||||
const isLocal = !account.acct.includes('@');
|
||||
@@ -901,12 +901,11 @@ export const AccountHeader: React.FC<{
|
||||
<AccountNote accountId={accountId} />
|
||||
)}
|
||||
|
||||
{account.note.length > 0 && account.note !== '<p></p>' && (
|
||||
<div
|
||||
className='account__header__content translate'
|
||||
dangerouslySetInnerHTML={content}
|
||||
/>
|
||||
)}
|
||||
<AccountBio
|
||||
note={account.note_emojified}
|
||||
dropdownAccountId={accountId}
|
||||
className='account__header__content'
|
||||
/>
|
||||
|
||||
<div className='account__header__fields'>
|
||||
<dl>
|
||||
|
||||
@@ -261,7 +261,9 @@ export const AltTextModal = forwardRef<ModalRef, Props & Partial<RestoreProps>>(
|
||||
);
|
||||
const lang = useAppSelector(
|
||||
(state) =>
|
||||
(state.compose as ImmutableMap<string, unknown>).get('lang') as string,
|
||||
(state.compose as ImmutableMap<string, unknown>).get(
|
||||
'language',
|
||||
) as string,
|
||||
);
|
||||
const focusX =
|
||||
(media?.getIn(['meta', 'focus', 'x'], 0) as number | undefined) ?? 0;
|
||||
|
||||
@@ -81,6 +81,7 @@ class ComposeForm extends ImmutablePureComponent {
|
||||
singleColumn: PropTypes.bool,
|
||||
lang: PropTypes.string,
|
||||
maxChars: PropTypes.number,
|
||||
redirectOnSuccess: PropTypes.bool,
|
||||
};
|
||||
|
||||
static defaultProps = {
|
||||
@@ -336,7 +337,7 @@ class ComposeForm extends ImmutablePureComponent {
|
||||
>
|
||||
{intl.formatMessage(
|
||||
this.props.isEditing ?
|
||||
messages.saveChanges :
|
||||
messages.saveChanges :
|
||||
(this.props.isInReply ? messages.reply : messages.publish)
|
||||
)}
|
||||
</Button>
|
||||
|
||||
@@ -47,10 +47,6 @@ const labelForRecentSearch = (search: RecentSearch) => {
|
||||
}
|
||||
};
|
||||
|
||||
const unfocus = () => {
|
||||
document.querySelector('.ui')?.parentElement?.focus();
|
||||
};
|
||||
|
||||
const ClearButton: React.FC<{
|
||||
onClick: () => void;
|
||||
hasValue: boolean;
|
||||
@@ -107,6 +103,11 @@ export const Search: React.FC<{
|
||||
}, [initialValue]);
|
||||
const searchOptions: SearchOption[] = [];
|
||||
|
||||
const unfocus = useCallback(() => {
|
||||
document.querySelector('.ui')?.parentElement?.focus();
|
||||
setExpanded(false);
|
||||
}, []);
|
||||
|
||||
if (searchEnabled) {
|
||||
searchOptions.push(
|
||||
{
|
||||
@@ -282,7 +283,7 @@ export const Search: React.FC<{
|
||||
history.push({ pathname: '/search', search: queryParams.toString() });
|
||||
unfocus();
|
||||
},
|
||||
[dispatch, history],
|
||||
[dispatch, history, unfocus],
|
||||
);
|
||||
|
||||
const handleChange = useCallback(
|
||||
@@ -402,7 +403,7 @@ export const Search: React.FC<{
|
||||
|
||||
setQuickActions(newQuickActions);
|
||||
},
|
||||
[dispatch, history, signedIn, setValue, setQuickActions, submit],
|
||||
[signedIn, dispatch, unfocus, history, submit],
|
||||
);
|
||||
|
||||
const handleClear = useCallback(() => {
|
||||
@@ -410,7 +411,7 @@ export const Search: React.FC<{
|
||||
setQuickActions([]);
|
||||
setSelectedOption(-1);
|
||||
unfocus();
|
||||
}, [setValue, setQuickActions, setSelectedOption]);
|
||||
}, [unfocus]);
|
||||
|
||||
const handleKeyDown = useCallback(
|
||||
(e: React.KeyboardEvent) => {
|
||||
@@ -461,7 +462,7 @@ export const Search: React.FC<{
|
||||
break;
|
||||
}
|
||||
},
|
||||
[navigableOptions, value, selectedOption, setSelectedOption, submit],
|
||||
[unfocus, navigableOptions, selectedOption, submit, value],
|
||||
);
|
||||
|
||||
const handleFocus = useCallback(() => {
|
||||
@@ -481,12 +482,38 @@ export const Search: React.FC<{
|
||||
}, [setExpanded, setSelectedOption, singleColumn]);
|
||||
|
||||
const handleBlur = useCallback(() => {
|
||||
setExpanded(false);
|
||||
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 (
|
||||
<form className={classNames('search', { active: expanded })}>
|
||||
<form ref={formRef} className={classNames('search', { active: expanded })}>
|
||||
<input
|
||||
ref={searchInputRef}
|
||||
className='search__input'
|
||||
@@ -506,7 +533,7 @@ export const Search: React.FC<{
|
||||
|
||||
<ClearButton hasValue={hasValue} onClick={handleClear} />
|
||||
|
||||
<div className='search__popout'>
|
||||
<div className='search__popout' tabIndex={-1}>
|
||||
{!hasValue && (
|
||||
<>
|
||||
<h4>
|
||||
|
||||
@@ -56,7 +56,7 @@ const mapStateToProps = state => ({
|
||||
maxChars: state.getIn(['server', 'server', 'configuration', 'statuses', 'max_characters'], 500),
|
||||
});
|
||||
|
||||
const mapDispatchToProps = (dispatch) => ({
|
||||
const mapDispatchToProps = (dispatch, props) => ({
|
||||
|
||||
onChange (text) {
|
||||
dispatch(changeCompose(text));
|
||||
@@ -69,7 +69,11 @@ const mapDispatchToProps = (dispatch) => ({
|
||||
modalProps: { overridePrivacy },
|
||||
}));
|
||||
} 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 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),
|
||||
to: '/domain_blocks',
|
||||
href: '/filters',
|
||||
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,
|
||||
{
|
||||
href: '/settings/privacy',
|
||||
@@ -80,7 +86,7 @@ export const MoreLink: React.FC = () => {
|
||||
href: '/settings/export',
|
||||
text: intl.formatMessage(messages.importExport),
|
||||
},
|
||||
);
|
||||
];
|
||||
|
||||
if (canManageReports(permissions)) {
|
||||
arr.push(null, {
|
||||
@@ -109,7 +115,7 @@ export const MoreLink: React.FC = () => {
|
||||
}, [intl, dispatch, permissions]);
|
||||
|
||||
return (
|
||||
<Dropdown items={menu}>
|
||||
<Dropdown items={menu} placement='bottom-start'>
|
||||
<button className='column-link column-link--transparent'>
|
||||
<Icon id='' icon={MoreHorizIcon} className='column-link__icon' />
|
||||
|
||||
|
||||
@@ -466,6 +466,7 @@ export const CollapsibleNavigationPanel: React.FC = () => {
|
||||
filterTaps: true,
|
||||
bounds: isLtrDir ? { left: 0 } : { right: 0 },
|
||||
rubberband: true,
|
||||
enabled: openable,
|
||||
},
|
||||
);
|
||||
|
||||
|
||||
@@ -122,98 +122,93 @@ export const PolicyControls: React.FC = () => {
|
||||
value={notificationPolicy.for_not_following}
|
||||
onChange={handleFilterNotFollowing}
|
||||
options={options}
|
||||
>
|
||||
<strong>
|
||||
label={
|
||||
<FormattedMessage
|
||||
id='notifications.policy.filter_not_following_title'
|
||||
defaultMessage="People you don't follow"
|
||||
/>
|
||||
</strong>
|
||||
<span className='hint'>
|
||||
}
|
||||
hint={
|
||||
<FormattedMessage
|
||||
id='notifications.policy.filter_not_following_hint'
|
||||
defaultMessage='Until you manually approve them'
|
||||
/>
|
||||
</span>
|
||||
</SelectWithLabel>
|
||||
}
|
||||
/>
|
||||
|
||||
<SelectWithLabel
|
||||
value={notificationPolicy.for_not_followers}
|
||||
onChange={handleFilterNotFollowers}
|
||||
options={options}
|
||||
>
|
||||
<strong>
|
||||
label={
|
||||
<FormattedMessage
|
||||
id='notifications.policy.filter_not_followers_title'
|
||||
defaultMessage='People not following you'
|
||||
/>
|
||||
</strong>
|
||||
<span className='hint'>
|
||||
}
|
||||
hint={
|
||||
<FormattedMessage
|
||||
id='notifications.policy.filter_not_followers_hint'
|
||||
defaultMessage='Including people who have been following you fewer than {days, plural, one {one day} other {# days}}'
|
||||
values={{ days: 3 }}
|
||||
/>
|
||||
</span>
|
||||
</SelectWithLabel>
|
||||
}
|
||||
/>
|
||||
|
||||
<SelectWithLabel
|
||||
value={notificationPolicy.for_new_accounts}
|
||||
onChange={handleFilterNewAccounts}
|
||||
options={options}
|
||||
>
|
||||
<strong>
|
||||
label={
|
||||
<FormattedMessage
|
||||
id='notifications.policy.filter_new_accounts_title'
|
||||
defaultMessage='New accounts'
|
||||
/>
|
||||
</strong>
|
||||
<span className='hint'>
|
||||
}
|
||||
hint={
|
||||
<FormattedMessage
|
||||
id='notifications.policy.filter_new_accounts.hint'
|
||||
defaultMessage='Created within the past {days, plural, one {one day} other {# days}}'
|
||||
values={{ days: 30 }}
|
||||
/>
|
||||
</span>
|
||||
</SelectWithLabel>
|
||||
}
|
||||
/>
|
||||
|
||||
<SelectWithLabel
|
||||
value={notificationPolicy.for_private_mentions}
|
||||
onChange={handleFilterPrivateMentions}
|
||||
options={options}
|
||||
>
|
||||
<strong>
|
||||
label={
|
||||
<FormattedMessage
|
||||
id='notifications.policy.filter_private_mentions_title'
|
||||
defaultMessage='Unsolicited private mentions'
|
||||
/>
|
||||
</strong>
|
||||
<span className='hint'>
|
||||
}
|
||||
hint={
|
||||
<FormattedMessage
|
||||
id='notifications.policy.filter_private_mentions_hint'
|
||||
defaultMessage="Filtered unless it's in reply to your own mention or if you follow the sender"
|
||||
/>
|
||||
</span>
|
||||
</SelectWithLabel>
|
||||
}
|
||||
/>
|
||||
|
||||
<SelectWithLabel
|
||||
value={notificationPolicy.for_limited_accounts}
|
||||
onChange={handleFilterLimitedAccounts}
|
||||
options={options}
|
||||
>
|
||||
<strong>
|
||||
label={
|
||||
<FormattedMessage
|
||||
id='notifications.policy.filter_limited_accounts_title'
|
||||
defaultMessage='Moderated accounts'
|
||||
/>
|
||||
</strong>
|
||||
<span className='hint'>
|
||||
}
|
||||
hint={
|
||||
<FormattedMessage
|
||||
id='notifications.policy.filter_limited_accounts_hint'
|
||||
defaultMessage='Limited by server moderators'
|
||||
/>
|
||||
</span>
|
||||
</SelectWithLabel>
|
||||
}
|
||||
/>
|
||||
</div>
|
||||
</section>
|
||||
);
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import type { PropsWithChildren } from 'react';
|
||||
import { useCallback, useState, useRef } from 'react';
|
||||
import { useCallback, useState, useRef, useId } from 'react';
|
||||
|
||||
import classNames from 'classnames';
|
||||
|
||||
@@ -16,6 +16,8 @@ interface DropdownProps {
|
||||
options: SelectItem[];
|
||||
disabled?: boolean;
|
||||
onChange: (value: string) => void;
|
||||
'aria-labelledby': string;
|
||||
'aria-describedby'?: string;
|
||||
placement?: Placement;
|
||||
}
|
||||
|
||||
@@ -24,51 +26,33 @@ const Dropdown: React.FC<DropdownProps> = ({
|
||||
options,
|
||||
disabled,
|
||||
onChange,
|
||||
'aria-labelledby': ariaLabelledBy,
|
||||
'aria-describedby': ariaDescribedBy,
|
||||
placement: initialPlacement = 'bottom-end',
|
||||
}) => {
|
||||
const activeElementRef = useRef<Element | null>(null);
|
||||
const containerRef = useRef(null);
|
||||
const containerRef = useRef<HTMLDivElement>(null);
|
||||
const buttonRef = useRef<HTMLButtonElement>(null);
|
||||
const [isOpen, setOpen] = useState<boolean>(false);
|
||||
const [placement, setPlacement] = useState<Placement>(initialPlacement);
|
||||
|
||||
const handleToggle = useCallback(() => {
|
||||
if (
|
||||
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 uniqueId = useId();
|
||||
const menuId = `${uniqueId}-menu`;
|
||||
const buttonLabelId = `${uniqueId}-button`;
|
||||
|
||||
const handleClose = useCallback(() => {
|
||||
if (
|
||||
isOpen &&
|
||||
activeElementRef.current &&
|
||||
activeElementRef.current instanceof HTMLElement
|
||||
)
|
||||
activeElementRef.current.focus({ preventScroll: true });
|
||||
if (isOpen && buttonRef.current) {
|
||||
buttonRef.current.focus({ preventScroll: true });
|
||||
}
|
||||
setOpen(false);
|
||||
}, [isOpen]);
|
||||
|
||||
const handleToggle = useCallback(() => {
|
||||
if (isOpen) {
|
||||
handleClose();
|
||||
} else {
|
||||
setOpen(true);
|
||||
}
|
||||
}, [isOpen, handleClose]);
|
||||
|
||||
const handleOverlayEnter = useCallback(
|
||||
(state: Partial<PopperState>) => {
|
||||
if (state.placement) setPlacement(state.placement);
|
||||
@@ -82,13 +66,18 @@ const Dropdown: React.FC<DropdownProps> = ({
|
||||
<div ref={containerRef}>
|
||||
<button
|
||||
type='button'
|
||||
ref={buttonRef}
|
||||
onClick={handleToggle}
|
||||
onMouseDown={handleMouseDown}
|
||||
onKeyDown={handleKeyDown}
|
||||
disabled={disabled}
|
||||
aria-expanded={isOpen}
|
||||
aria-controls={menuId}
|
||||
aria-labelledby={`${ariaLabelledBy} ${buttonLabelId}`}
|
||||
aria-describedby={ariaDescribedBy}
|
||||
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} />
|
||||
</button>
|
||||
|
||||
@@ -101,7 +90,7 @@ const Dropdown: React.FC<DropdownProps> = ({
|
||||
popperConfig={{ strategy: 'fixed', onFirstUpdate: handleOverlayEnter }}
|
||||
>
|
||||
{({ props, placement }) => (
|
||||
<div {...props}>
|
||||
<div {...props} id={menuId}>
|
||||
<div
|
||||
className={`dropdown-animation privacy-dropdown__dropdown ${placement}`}
|
||||
>
|
||||
@@ -123,6 +112,8 @@ const Dropdown: React.FC<DropdownProps> = ({
|
||||
interface Props {
|
||||
value: string;
|
||||
options: SelectItem[];
|
||||
label: string | React.ReactElement;
|
||||
hint: string | React.ReactElement;
|
||||
disabled?: boolean;
|
||||
onChange: (value: string) => void;
|
||||
}
|
||||
@@ -130,13 +121,26 @@ interface Props {
|
||||
export const SelectWithLabel: React.FC<PropsWithChildren<Props>> = ({
|
||||
value,
|
||||
options,
|
||||
label,
|
||||
hint,
|
||||
disabled,
|
||||
children,
|
||||
onChange,
|
||||
}) => {
|
||||
const uniqueId = useId();
|
||||
const labelId = `${uniqueId}-label`;
|
||||
const descId = `${uniqueId}-desc`;
|
||||
|
||||
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'>
|
||||
<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>
|
||||
@@ -144,6 +148,8 @@ export const SelectWithLabel: React.FC<PropsWithChildren<Props>> = ({
|
||||
value={value}
|
||||
onChange={onChange}
|
||||
disabled={disabled}
|
||||
aria-labelledby={labelId}
|
||||
aria-describedby={descId}
|
||||
options={options}
|
||||
/>
|
||||
</div>
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { useCallback } from 'react';
|
||||
import { useCallback, useMemo } from 'react';
|
||||
|
||||
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 { useIdentity } from 'flavours/glitch/identity_context';
|
||||
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';
|
||||
|
||||
const messages = defineMessages({
|
||||
@@ -50,6 +54,11 @@ const messages = defineMessages({
|
||||
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<{
|
||||
statusId: string;
|
||||
withOpenButton?: boolean;
|
||||
@@ -59,11 +68,9 @@ export const Footer: React.FC<{
|
||||
const intl = useIntl();
|
||||
const history = useHistory();
|
||||
const dispatch = useAppDispatch();
|
||||
const status = useAppSelector((state) => state.statuses.get(statusId));
|
||||
const accountId = status?.get('account') as string | undefined;
|
||||
const account = useAppSelector((state) =>
|
||||
accountId ? state.accounts.get(accountId) : undefined,
|
||||
);
|
||||
const getStatus = useMemo(() => makeGetStatus(), []) as GetStatusSelector;
|
||||
const status = useAppSelector((state) => getStatus(state, { id: statusId }));
|
||||
const account = status?.get('account') as Account | undefined;
|
||||
const askReplyConfirmation = useAppSelector(
|
||||
(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 = () => (
|
||||
<>
|
||||
<ComposeFormContainer autoFocus withoutNavigation />
|
||||
<ComposeFormContainer autoFocus withoutNavigation redirectOnSuccess />
|
||||
<AlertsController />
|
||||
<ModalContainer />
|
||||
<LoadingBarContainer className='loading-bar' />
|
||||
|
||||
@@ -38,7 +38,7 @@ const Embed: React.FC<{ id: string }> = ({ id }) => {
|
||||
const dispatchRenderSignal = useRenderSignal();
|
||||
|
||||
useEffect(() => {
|
||||
dispatch(fetchStatus(id, false, false));
|
||||
dispatch(fetchStatus(id, { alsoFetchContext: false }));
|
||||
}, [dispatch, id]);
|
||||
|
||||
const handleToggleHidden = useCallback(() => {
|
||||
|
||||
@@ -26,18 +26,23 @@ const getHostname = url => {
|
||||
|
||||
const domParser = new DOMParser();
|
||||
|
||||
const addAutoPlay = html => {
|
||||
const handleIframeUrl = (html, url, providerName) => {
|
||||
const document = domParser.parseFromString(html, 'text/html').documentElement;
|
||||
const iframe = document.querySelector('iframe');
|
||||
const startTime = new URL(url).searchParams.get('t')
|
||||
|
||||
if (iframe) {
|
||||
if (iframe.src.indexOf('?') !== -1) {
|
||||
iframe.src += '&';
|
||||
} else {
|
||||
iframe.src += '?';
|
||||
const iframeUrl = new URL(iframe.src)
|
||||
|
||||
iframeUrl.searchParams.set('autoplay', 1)
|
||||
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,
|
||||
// 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 () {
|
||||
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 (
|
||||
<div
|
||||
|
||||
@@ -418,7 +418,11 @@ export const DetailedStatus: React.FC<{
|
||||
{hashtagBar}
|
||||
|
||||
{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 = () => {
|
||||
const { dispatch, statusId } = this.props;
|
||||
dispatch(fetchStatus(statusId, true));
|
||||
dispatch(fetchStatus(statusId, {forceFetch: true}));
|
||||
this.setState({ isSubmitting: false, isSubmitted: true, step: 'submitted' });
|
||||
};
|
||||
|
||||
|
||||
@@ -201,8 +201,6 @@ class MediaModal extends ImmutablePureComponent {
|
||||
preview={image.get('preview_url')}
|
||||
blurhash={image.get('blurhash')}
|
||||
src={image.get('url')}
|
||||
width={image.get('width')}
|
||||
height={image.get('height')}
|
||||
frameRate={image.getIn(['meta', 'original', 'frame_rate'])}
|
||||
startTime={currentTime || 0}
|
||||
startPlaying={autoPlay || false}
|
||||
@@ -218,8 +216,6 @@ class MediaModal extends ImmutablePureComponent {
|
||||
return (
|
||||
<GIFV
|
||||
src={image.get('url')}
|
||||
width={width}
|
||||
height={height}
|
||||
key={image.get('url')}
|
||||
alt={description}
|
||||
lang={lang}
|
||||
|
||||
@@ -92,8 +92,7 @@ const messages = defineMessages({
|
||||
|
||||
const mapStateToProps = state => ({
|
||||
layout: state.getIn(['meta', 'layout']),
|
||||
hasComposingText: state.getIn(['compose', 'text']).trim().length !== 0,
|
||||
hasMediaAttachments: state.getIn(['compose', 'media_attachments']).size > 0,
|
||||
hasComposingContents: state.getIn(['compose', 'text']).trim().length !== 0 || state.getIn(['compose', 'media_attachments']).size > 0 || state.getIn(['compose', 'poll']) !== null,
|
||||
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']),
|
||||
fullWidthColumns: state.getIn(['local_settings', 'fullwidth_columns']),
|
||||
@@ -284,8 +283,7 @@ class UI extends PureComponent {
|
||||
fullWidthColumns: PropTypes.bool,
|
||||
systemFontUi: PropTypes.bool,
|
||||
isComposing: PropTypes.bool,
|
||||
hasComposingText: PropTypes.bool,
|
||||
hasMediaAttachments: PropTypes.bool,
|
||||
hasComposingContents: PropTypes.bool,
|
||||
canUploadMore: PropTypes.bool,
|
||||
intl: PropTypes.object.isRequired,
|
||||
unreadNotifications: PropTypes.number,
|
||||
@@ -304,11 +302,11 @@ class UI extends PureComponent {
|
||||
};
|
||||
|
||||
handleBeforeUnload = e => {
|
||||
const { intl, dispatch, hasComposingText, hasMediaAttachments } = this.props;
|
||||
const { intl, dispatch, hasComposingContents } = this.props;
|
||||
|
||||
dispatch(synchronouslySubmitMarkers());
|
||||
|
||||
if (hasComposingText || hasMediaAttachments) {
|
||||
if (hasComposingContents) {
|
||||
// Setting returnValue to any string causes confirmation dialog.
|
||||
// Many browsers no longer display this text to users,
|
||||
// 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';
|
||||
|
||||
const isMentionClick = (element: HTMLAnchorElement) =>
|
||||
element.classList.contains('mention');
|
||||
element.classList.contains('mention') &&
|
||||
!element.classList.contains('hashtag');
|
||||
|
||||
const isHashtagClick = (element: HTMLAnchorElement) =>
|
||||
element.textContent?.[0] === '#' ||
|
||||
element.previousSibling?.textContent?.endsWith('#');
|
||||
|
||||
export const useLinks = () => {
|
||||
export const useLinks = (skipHashtags?: boolean) => {
|
||||
const history = useHistory();
|
||||
const dispatch = useAppDispatch();
|
||||
|
||||
@@ -61,12 +62,12 @@ export const useLinks = () => {
|
||||
if (isMentionClick(target)) {
|
||||
e.preventDefault();
|
||||
void handleMentionClick(target);
|
||||
} else if (isHashtagClick(target)) {
|
||||
} else if (isHashtagClick(target) && !skipHashtags) {
|
||||
e.preventDefault();
|
||||
handleHashtagClick(target);
|
||||
}
|
||||
},
|
||||
[handleMentionClick, handleHashtagClick],
|
||||
[skipHashtags, handleMentionClick, handleHashtagClick],
|
||||
);
|
||||
|
||||
return handleClick;
|
||||
|
||||
@@ -6,12 +6,8 @@
|
||||
"account.view_full_profile": "عرض الملف الشخصي كاملاً",
|
||||
"boost_modal.missing_description": "هذا المنشور يحتوي على وسائط بلا وصف",
|
||||
"column.favourited_by": "المفضلة من قبل",
|
||||
"column.heading": "متنوعة",
|
||||
"column.reblogged_by": "المرقى من قبل",
|
||||
"column.subheading": "خيارات متنوعة",
|
||||
"column_header.profile": "الملف الشخصي",
|
||||
"column_subheading.lists": "القوائم",
|
||||
"column_subheading.navigation": "التنقل",
|
||||
"community.column_settings.allow_local_only": "إظهار المنشورات المحلية فقط",
|
||||
"compose.attach.doodle": "الرسوم و التخمين",
|
||||
"compose.change_federation": "تغيير اعدادات الفيديرالية",
|
||||
@@ -36,8 +32,6 @@
|
||||
"keyboard_shortcuts.secondary_toot": "لإرسال التبويق باستخدام إعدادات الخصوصية الثانوية",
|
||||
"moved_to_warning": "عُلِّم هذا الحساب بأنه انتقل إلى {moved_to_link}، لذا قد لا يقبل متابعات جديدة.",
|
||||
"navigation_bar.app_settings": "إعدادات التطبيق",
|
||||
"navigation_bar.keyboard_shortcuts": "اختصارات لوحة المفاتيح",
|
||||
"navigation_bar.misc": "متنوع",
|
||||
"settings.always_show_spoilers_field": "تمكين دائما حقل تحذير المحتوى",
|
||||
"settings.close": "إغلاق",
|
||||
"settings.content_warnings": "Content warnings",
|
||||
|
||||
@@ -6,12 +6,8 @@
|
||||
"account.view_full_profile": "Zobrazit celý profil",
|
||||
"boost_modal.missing_description": "Příspěvek obsahuje obrázky bez popisků",
|
||||
"column.favourited_by": "Oblíbeno uživatelem",
|
||||
"column.heading": "Různé",
|
||||
"column.reblogged_by": "Boostnuto uživatelem",
|
||||
"column.subheading": "Různé",
|
||||
"column_header.profile": "Profil",
|
||||
"column_subheading.lists": "Seznamy",
|
||||
"column_subheading.navigation": "Navigace",
|
||||
"community.column_settings.allow_local_only": "Zobrazit pouze místní tooty",
|
||||
"compose.attach.doodle": "Nakreslete něco",
|
||||
"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í",
|
||||
"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.keyboard_shortcuts": "Klávesové zkratky",
|
||||
"navigation_bar.misc": "Různé",
|
||||
"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.close": "Zavřít",
|
||||
|
||||
@@ -6,12 +6,8 @@
|
||||
"account.view_full_profile": "Dangos proffil cyfan",
|
||||
"boost_modal.missing_description": "Mae'r tŵt yma'n cynnwys ychydig gyfryngau heb ddisgrifiad",
|
||||
"column.favourited_by": "Wedi'i hoffi gan",
|
||||
"column.heading": "Misg",
|
||||
"column.reblogged_by": "Wedi'i bŵstio gan",
|
||||
"column.subheading": "Opsiynnau arall",
|
||||
"column_header.profile": "Proffil",
|
||||
"column_subheading.lists": "Rhestri",
|
||||
"column_subheading.navigation": "Llywio",
|
||||
"community.column_settings.allow_local_only": "Dangos tŵtiau lleol yn unig",
|
||||
"compose.content-type.html": "HTML",
|
||||
"compose.content-type.markdown": "Markdown",
|
||||
|
||||
@@ -2,7 +2,6 @@
|
||||
"home.column_settings.advanced": "Avanceret",
|
||||
"home.column_settings.show_direct": "Vis private omtaler",
|
||||
"navigation_bar.app_settings": "Appindstillinger",
|
||||
"navigation_bar.misc": "Diverse",
|
||||
"settings.always_show_spoilers_field": "Vis altid feltet til indholdsadvarsel",
|
||||
"settings.close": "Luk",
|
||||
"settings.content_warnings": "Indholdsadvarsler",
|
||||
|
||||
@@ -6,12 +6,8 @@
|
||||
"account.view_full_profile": "Vollständiges Profil anzeigen",
|
||||
"boost_modal.missing_description": "Dieser Toot enthält Medien ohne Beschreibung",
|
||||
"column.favourited_by": "Favorisiert von",
|
||||
"column.heading": "Sonstiges",
|
||||
"column.reblogged_by": "Geteilt von",
|
||||
"column.subheading": "Sonstige Optionen",
|
||||
"column_header.profile": "Profil",
|
||||
"column_subheading.lists": "Listen",
|
||||
"column_subheading.navigation": "Navigation",
|
||||
"community.column_settings.allow_local_only": "Nur-lokale Toots anzeigen",
|
||||
"compose.attach.doodle": "Male etwas",
|
||||
"compose.change_federation": "Föderationseinstellungen ändern",
|
||||
@@ -45,8 +41,6 @@
|
||||
"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.",
|
||||
"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",
|
||||
"settings.always_show_spoilers_field": "Das Inhaltswarnungs-Feld immer aktivieren",
|
||||
"settings.close": "Schließen",
|
||||
|
||||
@@ -1,4 +1,92 @@
|
||||
{
|
||||
"settings.content_warnings": "Content warnings",
|
||||
"settings.preferences": "Preferences"
|
||||
"about.fork_disclaimer": "Το Glitch-soc είναι ελεύθερο λογισμικό ανοιχτού κώδικα που είναι αντιγραφή από το Mastodon.",
|
||||
"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",
|
||||
"boost_modal.missing_description": "Ĉi tiu afiŝo enhavas plurmedion, ke ne havas priskribon",
|
||||
"column.favourited_by": "Stelumita per",
|
||||
"column.heading": "Diversaj aferoj",
|
||||
"column.reblogged_by": "Diskonigita de",
|
||||
"column.subheading": "Diversaj agordoj",
|
||||
"column_header.profile": "Profilo",
|
||||
"column_subheading.lists": "Listoj",
|
||||
"column_subheading.navigation": "Navigado",
|
||||
"community.column_settings.allow_local_only": "Montri nur-lokajn afiŝojn",
|
||||
"compose.attach.doodle": "Skribu ion",
|
||||
"compose.change_federation": "Ŝanĝi agordojn de federacio",
|
||||
@@ -45,8 +41,6 @@
|
||||
"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.",
|
||||
"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",
|
||||
"settings.always_show_spoilers_field": "Ĉiam ŝaltiĝu la arealo de Enhava Averto",
|
||||
"settings.close": "Fermi",
|
||||
|
||||
@@ -6,12 +6,8 @@
|
||||
"account.view_full_profile": "Ver perfil completo",
|
||||
"boost_modal.missing_description": "Esta publicación contiene medios sin descripción",
|
||||
"column.favourited_by": "Marcado como favorito por",
|
||||
"column.heading": "Misc",
|
||||
"column.reblogged_by": "Impulsado por",
|
||||
"column.subheading": "Opciones misceláneas",
|
||||
"column_header.profile": "Perfil",
|
||||
"column_subheading.lists": "Listas",
|
||||
"column_subheading.navigation": "Navegación",
|
||||
"community.column_settings.allow_local_only": "Mostrar sólo toots locales",
|
||||
"compose.attach.doodle": "Dibujá algo",
|
||||
"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",
|
||||
"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.keyboard_shortcuts": "Atajos de teclado",
|
||||
"navigation_bar.misc": "Misc",
|
||||
"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.close": "Cerrar",
|
||||
|
||||
@@ -6,12 +6,8 @@
|
||||
"account.view_full_profile": "Ver perfil completo",
|
||||
"boost_modal.missing_description": "Esta publicación contiene medios sin descripción",
|
||||
"column.favourited_by": "Marcado como favorito por",
|
||||
"column.heading": "Misc",
|
||||
"column.reblogged_by": "Impulsado por",
|
||||
"column.subheading": "Opciones misceláneas",
|
||||
"column_header.profile": "Perfil",
|
||||
"column_subheading.lists": "Listas",
|
||||
"column_subheading.navigation": "Navegación",
|
||||
"community.column_settings.allow_local_only": "Mostrar sólo toots locales",
|
||||
"compose.attach.doodle": "Dibujar algo",
|
||||
"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",
|
||||
"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.keyboard_shortcuts": "Atajos de teclado",
|
||||
"navigation_bar.misc": "Misc",
|
||||
"settings.always_show_spoilers_field": "Siempre mostrar el campo de advertencia de contenido",
|
||||
"settings.close": "Cerrar",
|
||||
"settings.compose_box_opts": "Cuadro de redacción",
|
||||
|
||||
@@ -6,12 +6,8 @@
|
||||
"account.view_full_profile": "Ver perfil completo",
|
||||
"boost_modal.missing_description": "Esta publicación contiene medios sin descripción",
|
||||
"column.favourited_by": "Marcado como favorito por",
|
||||
"column.heading": "Misc",
|
||||
"column.reblogged_by": "Impulsado por",
|
||||
"column.subheading": "Opciones misceláneas",
|
||||
"column_header.profile": "Perfil",
|
||||
"column_subheading.lists": "Listas",
|
||||
"column_subheading.navigation": "Navegación",
|
||||
"community.column_settings.allow_local_only": "Mostrar toots solo-locales",
|
||||
"compose.attach.doodle": "Dibujar algo",
|
||||
"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",
|
||||
"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.keyboard_shortcuts": "Atajos de teclado",
|
||||
"navigation_bar.misc": "Misc",
|
||||
"settings.always_show_spoilers_field": "Siempre mostrar el campo de advertencia de contenido",
|
||||
"settings.close": "Cerrar",
|
||||
"settings.compose_box_opts": "Cuadro de redacción",
|
||||
|
||||
@@ -8,7 +8,6 @@
|
||||
"compose.content-type.plain": "متن ساده",
|
||||
"home.column_settings.advanced": "پیشرفته",
|
||||
"navigation_bar.app_settings": "تنظیمات کاره",
|
||||
"navigation_bar.keyboard_shortcuts": "میانبرهای صفحهکلید",
|
||||
"settings.close": "بستن",
|
||||
"settings.content_warnings": "هشدارهای محتوا",
|
||||
"settings.media": "رسانه",
|
||||
|
||||
@@ -6,12 +6,8 @@
|
||||
"account.view_full_profile": "Voir le profil complet",
|
||||
"boost_modal.missing_description": "Ce post contient des médias sans description",
|
||||
"column.favourited_by": "Ajouté en favori par",
|
||||
"column.heading": "Divers",
|
||||
"column.reblogged_by": "Partagé par",
|
||||
"column.subheading": "Autres options",
|
||||
"column_header.profile": "Profil",
|
||||
"column_subheading.lists": "Listes",
|
||||
"column_subheading.navigation": "Navigation",
|
||||
"community.column_settings.allow_local_only": "Afficher seulement les posts locaux",
|
||||
"compose.attach.doodle": "Dessinez quelque chose",
|
||||
"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.disable_threaded_mode": "Désactiver 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",
|
||||
"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 :",
|
||||
@@ -42,8 +41,7 @@
|
||||
"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.",
|
||||
"navigation_bar.app_settings": "Paramètres de l'application",
|
||||
"navigation_bar.keyboard_shortcuts": "Raccourcis clavier",
|
||||
"navigation_bar.misc": "Autres",
|
||||
"notifications.column_settings.filter_bar.show_bar": "Afficher la barre de filtre",
|
||||
"settings.always_show_spoilers_field": "Toujours activer le champ de rédaction de l'avertissement de contenu",
|
||||
"settings.close": "Fermer",
|
||||
"settings.compose_box_opts": "Zone de rédaction",
|
||||
@@ -57,6 +55,8 @@
|
||||
"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.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.hicolor_privacy_icons": "Indicateurs de confidentialité en couleurs",
|
||||
"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.wide_view": "Vue élargie (mode ordinateur uniquement)",
|
||||
"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_pictures": "Contient des images attachées",
|
||||
"status.has_preview_card": "Contient une carte de prévisualisation attachée",
|
||||
"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.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.",
|
||||
"account.disclaimer_full": "Les informations ci-dessous peuvent être incomplètes.",
|
||||
"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",
|
||||
"boost_modal.missing_description": "Ce post contient des médias sans description",
|
||||
"column.favourited_by": "Ajouté en favori par",
|
||||
"column.heading": "Divers",
|
||||
"column.reblogged_by": "Partagé par",
|
||||
"column.subheading": "Autres options",
|
||||
"column_header.profile": "Profil",
|
||||
"column_subheading.lists": "Listes",
|
||||
"column_subheading.navigation": "Navigation",
|
||||
"community.column_settings.allow_local_only": "Afficher seulement les posts locaux",
|
||||
"compose.attach.doodle": "Dessinez quelque chose",
|
||||
"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.markdown": "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.disable_threaded_mode": "Désactiver 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",
|
||||
"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 :",
|
||||
@@ -42,8 +41,7 @@
|
||||
"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.",
|
||||
"navigation_bar.app_settings": "Paramètres de l'application",
|
||||
"navigation_bar.keyboard_shortcuts": "Raccourcis clavier",
|
||||
"navigation_bar.misc": "Autres",
|
||||
"notifications.column_settings.filter_bar.show_bar": "Afficher la barre de filtre",
|
||||
"settings.always_show_spoilers_field": "Toujours activer le champ de rédaction de l'avertissement de contenu",
|
||||
"settings.close": "Fermer",
|
||||
"settings.compose_box_opts": "Zone de rédaction",
|
||||
@@ -57,6 +55,8 @@
|
||||
"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.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.hicolor_privacy_icons": "Indicateurs de confidentialité en couleurs",
|
||||
"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_position": "Position du lecteur pop-in :",
|
||||
"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.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",
|
||||
@@ -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.wide_view": "Vue élargie (mode ordinateur uniquement)",
|
||||
"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_pictures": "Contient des images attachées",
|
||||
"status.has_preview_card": "Contient une carte de prévisualisation attachée",
|
||||
"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.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",
|
||||
"boost_modal.missing_description": "Esta publicación contén multimedia sen descrición",
|
||||
"column.favourited_by": "Favorecida por",
|
||||
"column.heading": "Varios",
|
||||
"column.reblogged_by": "Promovida por",
|
||||
"column.subheading": "Opcións diversas",
|
||||
"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",
|
||||
"compose.attach.doodle": "Debuxa algo",
|
||||
"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",
|
||||
"moved_to_warning": "Esta conta moveuse a {moved_to_link}, e non acepta novos seguimentos.",
|
||||
"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",
|
||||
"settings.always_show_spoilers_field": "Activar sempre o campo de Aviso sobre o contido CW",
|
||||
"settings.close": "Pechar",
|
||||
|
||||
@@ -6,12 +6,8 @@
|
||||
"account.view_full_profile": "Tampilkan profil lengkap",
|
||||
"boost_modal.missing_description": "Toot ini berisi beberapa media tanpa deskripsi",
|
||||
"column.favourited_by": "Disukai oleh",
|
||||
"column.heading": "Lainnya",
|
||||
"column.reblogged_by": "Dibagikan oleh",
|
||||
"column.subheading": "Opsi lain-lain",
|
||||
"column_header.profile": "Profil",
|
||||
"column_subheading.lists": "Daftar",
|
||||
"column_subheading.navigation": "Penelusuran",
|
||||
"community.column_settings.allow_local_only": "Tampilkan toot lokal saja",
|
||||
"compose.change_federation": "Ubah pengaturan federasi",
|
||||
"compose.content-type.change": "Ubah opsi pemformatan lanjutan",
|
||||
@@ -38,8 +34,6 @@
|
||||
"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.",
|
||||
"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.close": "Tutup",
|
||||
"settings.compose_box_opts": "Kotak tulis",
|
||||
@@ -49,6 +43,7 @@
|
||||
"settings.content_warnings.regexp": "Ekspresi reguler",
|
||||
"settings.content_warnings_filter": "Peringatan konten yang tidak akan dibuka secara otomatis:",
|
||||
"settings.content_warnings_shared_state": "Tampilkan/sembunyikan konten semua salinan sekaligus",
|
||||
"settings.pop_in_right": "Kanan",
|
||||
"settings.preferences": "Preferences",
|
||||
"settings.status_icons_reply": "Indikator balasan",
|
||||
"settings.status_icons_visibility": "Indikator privasi toot",
|
||||
|
||||
@@ -6,12 +6,8 @@
|
||||
"account.view_full_profile": "正確な情報を見る",
|
||||
"boost_modal.missing_description": "このトゥートには少なくとも1つの画像に説明が付与されていません",
|
||||
"column.favourited_by": "お気に入りしたユーザー",
|
||||
"column.heading": "その他",
|
||||
"column.reblogged_by": "ブーストしたユーザー",
|
||||
"column.subheading": "その他のオプション",
|
||||
"column_header.profile": "プロフィール",
|
||||
"column_subheading.lists": "リスト",
|
||||
"column_subheading.navigation": "ナビゲーション",
|
||||
"community.column_settings.allow_local_only": "ローカル限定投稿を表示する",
|
||||
"compose.content-type.html": "HTML",
|
||||
"compose.content-type.html_meta": "投稿に HTML を使用する",
|
||||
@@ -33,8 +29,6 @@
|
||||
"keyboard_shortcuts.secondary_toot": "セカンダリートゥートの公開範囲でトゥートする",
|
||||
"moved_to_warning": "このアカウント{moved_to_link}に引っ越したため、新しいフォロワーを受け入れていません。",
|
||||
"navigation_bar.app_settings": "アプリ設定",
|
||||
"navigation_bar.keyboard_shortcuts": "キーボードショートカット",
|
||||
"navigation_bar.misc": "その他",
|
||||
"settings.always_show_spoilers_field": "常にコンテンツワーニング設定を表示する(指定がない場合は通常投稿)",
|
||||
"settings.close": "閉じる",
|
||||
"settings.compose_box_opts": "コンポーズボックス設定",
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
{
|
||||
"compose.attach.doodle": "Ssuneɣ-d kra",
|
||||
"navigation_bar.keyboard_shortcuts": "Inezgumen n unasiw",
|
||||
"settings.close": "Mdel",
|
||||
"settings.content_warnings": "Content warnings",
|
||||
"settings.preferences": "Preferences"
|
||||
|
||||
@@ -6,12 +6,8 @@
|
||||
"account.view_full_profile": "전체 프로필 보기",
|
||||
"boost_modal.missing_description": "이 게시물은 설명이 없는 미디어를 포함하고 있습니다",
|
||||
"column.favourited_by": "즐겨찾기 한 사람",
|
||||
"column.heading": "기타",
|
||||
"column.reblogged_by": "부스트 한 사람",
|
||||
"column.subheading": "다양한 옵션",
|
||||
"column_header.profile": "프로필",
|
||||
"column_subheading.lists": "리스트",
|
||||
"column_subheading.navigation": "탐색",
|
||||
"community.column_settings.allow_local_only": "로컬 전용 글 보기",
|
||||
"compose.attach.doodle": "뭔가 그려보세요",
|
||||
"compose.change_federation": "연합 설정 변경",
|
||||
@@ -45,8 +41,6 @@
|
||||
"keyboard_shortcuts.secondary_toot": "보조 프라이버시 설정으로 글 보내기",
|
||||
"moved_to_warning": "이 계정은 {moved_to_link}로 이동한 것으로 표시되었고, 새 팔로우를 받지 않는 것 같습니다.",
|
||||
"navigation_bar.app_settings": "앱 설정",
|
||||
"navigation_bar.keyboard_shortcuts": "키보드 단축기",
|
||||
"navigation_bar.misc": "다양한 옵션들",
|
||||
"notifications.column_settings.filter_bar.show_bar": "필터 막대 표시",
|
||||
"settings.always_show_spoilers_field": "열람주의 항목을 언제나 활성화",
|
||||
"settings.close": "닫기",
|
||||
|
||||
@@ -6,12 +6,8 @@
|
||||
"account.view_full_profile": "Peržiūrėti visą profilį",
|
||||
"boost_modal.missing_description": "Šis įrašas turi šiek tiek medijų be aprašų.",
|
||||
"column.favourited_by": "Pamėgo",
|
||||
"column.heading": "Įvairūs",
|
||||
"column.reblogged_by": "Pasidalino",
|
||||
"column.subheading": "Įvairios parinktys",
|
||||
"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",
|
||||
"compose.attach.doodle": "Nupiešti kažką",
|
||||
"compose.change_federation": "Keisti federacijos nustatymus",
|
||||
@@ -45,8 +41,6 @@
|
||||
"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ų.",
|
||||
"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ą",
|
||||
"settings.always_show_spoilers_field": "Visada įjungti turinio įspėjimo lauką",
|
||||
"settings.close": "Užverti",
|
||||
|
||||
@@ -3,12 +3,8 @@
|
||||
"account.view_full_profile": "Volledig profiel weergeven",
|
||||
"boost_modal.missing_description": "Deze toot bevat media zonder beschrijving",
|
||||
"column.favourited_by": "Favoriet door",
|
||||
"column.heading": "Overige",
|
||||
"column.reblogged_by": "Geboost door",
|
||||
"column.subheading": "Diverse opties",
|
||||
"column_header.profile": "Profiel",
|
||||
"column_subheading.lists": "Lijsten",
|
||||
"column_subheading.navigation": "Navigatie",
|
||||
"community.column_settings.allow_local_only": "Toon alleen lokale toots",
|
||||
"compose.content-type.html": "HTML",
|
||||
"compose.content-type.markdown": "Markdown",
|
||||
|
||||
@@ -6,12 +6,8 @@
|
||||
"account.view_full_profile": "Pokaż pełny profil",
|
||||
"boost_modal.missing_description": "Ten wpis zawiera multimedialne załączniki bez opisu",
|
||||
"column.favourited_by": "Polubiony przez",
|
||||
"column.heading": "Różne",
|
||||
"column.reblogged_by": "Podbity przez",
|
||||
"column.subheading": "Różne opcje",
|
||||
"column_header.profile": "Profil",
|
||||
"column_subheading.lists": "Listy",
|
||||
"column_subheading.navigation": "Nawigacja",
|
||||
"community.column_settings.allow_local_only": "Pokazuj wyłącznie wpisy lokalne",
|
||||
"compose.content-type.html": "HTML",
|
||||
"compose.content-type.markdown": "Markdown",
|
||||
@@ -28,8 +24,6 @@
|
||||
"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.",
|
||||
"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.close": "Zamknij",
|
||||
"settings.compose_box_opts": "Pole edycji",
|
||||
|
||||
@@ -6,12 +6,8 @@
|
||||
"account.view_full_profile": "Ver o perfil completo",
|
||||
"boost_modal.missing_description": "Este toot contém algumas mídias sem descrição",
|
||||
"column.favourited_by": "Favoritado por",
|
||||
"column.heading": "Diversos",
|
||||
"column.reblogged_by": "Inpulsionado por",
|
||||
"column.subheading": "Opções diversas",
|
||||
"column_header.profile": "Perfil",
|
||||
"column_subheading.lists": "Listas",
|
||||
"column_subheading.navigation": "Navegação",
|
||||
"community.column_settings.allow_local_only": "Mostrar os toots apenas locais",
|
||||
"compose.content-type.html": "HTML",
|
||||
"compose.content-type.markdown": "Markdown",
|
||||
@@ -28,8 +24,6 @@
|
||||
"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.",
|
||||
"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.close": "Fechar",
|
||||
"settings.compose_box_opts": "Caixa de composição",
|
||||
|
||||
@@ -6,7 +6,6 @@
|
||||
"account.view_full_profile": "Ver o perfil completo",
|
||||
"boost_modal.missing_description": "Este post contém alguns media sem descrição",
|
||||
"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.",
|
||||
"notifications.column_settings.filter_bar.show_bar": "Mostrar barra de filtros",
|
||||
"settings.always_show_spoilers_field": "Mostrar sempre o campo Aviso de Conteúdo",
|
||||
|
||||
@@ -6,12 +6,8 @@
|
||||
"account.view_full_profile": "Открыть полный профиль",
|
||||
"boost_modal.missing_description": "Этот пост содержит медиафайлы без описания",
|
||||
"column.favourited_by": "Добавили в избранное",
|
||||
"column.heading": "Прочее",
|
||||
"column.reblogged_by": "Продвинули",
|
||||
"column.subheading": "Прочие разделы",
|
||||
"column_header.profile": "Профиль",
|
||||
"column_subheading.lists": "Списки",
|
||||
"column_subheading.navigation": "Навигация",
|
||||
"community.column_settings.allow_local_only": "Показывать нефедерируемые посты",
|
||||
"compose.attach.doodle": "Нарисовать что-нибудь",
|
||||
"compose.change_federation": "Изменить настройки федерации",
|
||||
@@ -39,8 +35,6 @@
|
||||
"keyboard_shortcuts.bookmark": "добавить закладку",
|
||||
"moved_to_warning": "Этот аккаунт переехал на {moved_to_link}, и скорее всего не принимает новых подписчиков.",
|
||||
"navigation_bar.app_settings": "Настройки приложения",
|
||||
"navigation_bar.keyboard_shortcuts": "Сочетания клавиш",
|
||||
"navigation_bar.misc": "Прочее",
|
||||
"notifications.column_settings.filter_bar.show_bar": "Показать панель фильтров",
|
||||
"settings.always_show_spoilers_field": "Всегда ставить предупреждение о содержании",
|
||||
"settings.close": "Закрыть",
|
||||
|
||||
@@ -6,12 +6,8 @@
|
||||
"account.view_full_profile": "View full profile",
|
||||
"boost_modal.missing_description": "This post contains some media withoot description",
|
||||
"column.favourited_by": "Favouritit by",
|
||||
"column.heading": "Misc",
|
||||
"column.reblogged_by": "Boosted by",
|
||||
"column.subheading": "Miscellaneous options",
|
||||
"column_header.profile": "Profile",
|
||||
"column_subheading.lists": "Lists",
|
||||
"column_subheading.navigation": "Navigation",
|
||||
"community.column_settings.allow_local_only": "Show local-only posts",
|
||||
"compose.attach.doodle": "Draw something",
|
||||
"compose.change_federation": "Change federation settins",
|
||||
@@ -45,8 +41,6 @@
|
||||
"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.",
|
||||
"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",
|
||||
"settings.always_show_spoilers_field": "Always enable the Content Warning field",
|
||||
"settings.close": "Close",
|
||||
|
||||
@@ -4,12 +4,8 @@
|
||||
"account.view_full_profile": "Visa full profil",
|
||||
"boost_modal.missing_description": "Denna toot innehåller viss media utan beskrivning",
|
||||
"column.favourited_by": "Favoritmarkerad av",
|
||||
"column.heading": "Övrigt",
|
||||
"column.reblogged_by": "Boostad av",
|
||||
"column.subheading": "Övriga val",
|
||||
"column_header.profile": "Profil",
|
||||
"column_subheading.lists": "Listor",
|
||||
"column_subheading.navigation": "Navigering",
|
||||
"community.column_settings.allow_local_only": "Visa endast lokala toots",
|
||||
"compose.content-type.html": "HTML",
|
||||
"compose.content-type.markdown": "Markdown",
|
||||
|
||||
@@ -1,8 +1,5 @@
|
||||
{
|
||||
"about.fork_disclaimer": ".",
|
||||
"account.disclaimer_full": ".",
|
||||
"account.follows": "",
|
||||
"community.column_settings.allow_local_only": "",
|
||||
"account.follows": "ติดตาม",
|
||||
"settings.content_warnings": "Content warnings",
|
||||
"settings.preferences": "Preferences"
|
||||
}
|
||||
|
||||
@@ -5,12 +5,8 @@
|
||||
"account.view_full_profile": "Tam görünüm",
|
||||
"boost_modal.missing_description": "Bu gönderi açıklaması olmayan medya içerir",
|
||||
"column.favourited_by": "Tarafından favorilere eklendi",
|
||||
"column.heading": "Diğer",
|
||||
"column.reblogged_by": "Tarafından yükseltildi",
|
||||
"column.subheading": "Diğer seçenekler",
|
||||
"column_header.profile": "Profil",
|
||||
"column_subheading.lists": "Listeler",
|
||||
"column_subheading.navigation": "Gezinme",
|
||||
"community.column_settings.allow_local_only": "Sadece yerel gönderileri göster",
|
||||
"compose.content-type.html": "HTML",
|
||||
"compose.content-type.markdown": "Markdown modu",
|
||||
|
||||
@@ -6,12 +6,8 @@
|
||||
"account.view_full_profile": "Переглянути повний профіль",
|
||||
"boost_modal.missing_description": "Цей дмух містить деякі медіа без опису",
|
||||
"column.favourited_by": "Уподобані",
|
||||
"column.heading": "Різне",
|
||||
"column.reblogged_by": "Поширено",
|
||||
"column.subheading": "Інші параметри",
|
||||
"column_header.profile": "Профіль",
|
||||
"column_subheading.lists": "Списки",
|
||||
"column_subheading.navigation": "Навігація",
|
||||
"community.column_settings.allow_local_only": "Показувати тільки локальні дмухи",
|
||||
"compose.content-type.html": "HTML",
|
||||
"compose.content-type.markdown": "Markdown",
|
||||
@@ -29,8 +25,6 @@
|
||||
"keyboard_shortcuts.secondary_toot": "надсилати повідомлення з використанням вторинних налаштувань конфіденційності",
|
||||
"moved_to_warning": "Цей обліковий запис позначено як переміщений до {moved_to_link} і тому не може прийняти нових підписок.",
|
||||
"navigation_bar.app_settings": "Налаштування програми",
|
||||
"navigation_bar.keyboard_shortcuts": "Комбінації клавіш",
|
||||
"navigation_bar.misc": "Різне",
|
||||
"settings.always_show_spoilers_field": "Завжди вмикати попередження про вміст",
|
||||
"settings.close": "Закрити",
|
||||
"settings.compose_box_opts": "Compose box",
|
||||
|
||||
@@ -6,12 +6,8 @@
|
||||
"account.view_full_profile": "查看完整资料",
|
||||
"boost_modal.missing_description": "这条嘟文未包含媒体描述",
|
||||
"column.favourited_by": "点赞",
|
||||
"column.heading": "杂项",
|
||||
"column.reblogged_by": "转嘟",
|
||||
"column.subheading": "其他选项",
|
||||
"column_header.profile": "资料卡",
|
||||
"column_subheading.lists": "列表",
|
||||
"column_subheading.navigation": "导航",
|
||||
"community.column_settings.allow_local_only": "只显示仅本站可见的嘟文",
|
||||
"compose.attach.doodle": "涂鸦",
|
||||
"compose.change_federation": "更改联动设置",
|
||||
@@ -45,8 +41,6 @@
|
||||
"keyboard_shortcuts.secondary_toot": "使用另一隐私设置发送嘟文",
|
||||
"moved_to_warning": "此账户已被标记为移至 {moved_to_link},并且似乎没有收到新粉丝。",
|
||||
"navigation_bar.app_settings": "应用设置",
|
||||
"navigation_bar.keyboard_shortcuts": "键盘快捷键",
|
||||
"navigation_bar.misc": "杂项",
|
||||
"notifications.column_settings.filter_bar.show_bar": "显示筛选栏",
|
||||
"settings.always_show_spoilers_field": "始终显示内容警告框",
|
||||
"settings.close": "关闭",
|
||||
|
||||
@@ -6,12 +6,8 @@
|
||||
"account.view_full_profile": "查看完整個人資料",
|
||||
"boost_modal.missing_description": "此貼文包含未加說明的媒體檔案",
|
||||
"column.favourited_by": "誰按了最愛",
|
||||
"column.heading": "雜項",
|
||||
"column.reblogged_by": "被誰轉貼",
|
||||
"column.subheading": "其他選項",
|
||||
"column_header.profile": "個人檔案",
|
||||
"column_subheading.lists": "列表",
|
||||
"column_subheading.navigation": "導覽",
|
||||
"community.column_settings.allow_local_only": "顯示僅限本地的貼文",
|
||||
"compose.attach.doodle": "塗鴉",
|
||||
"compose.change_federation": "變更聯邦設定",
|
||||
@@ -24,6 +20,9 @@
|
||||
"compose.content-type.plain_meta": "不使用進階格式撰寫",
|
||||
"compose.disable_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": "不要再顯示確認訊息",
|
||||
"confirmations.deprecated_settings.confirm": "使用 Mastodon 偏好",
|
||||
"confirmations.deprecated_settings.message": "您正在使用的某些特定於 glitch-soc 設備的 {app_settings} 已被 Mastodon {preferences} 所取代,並將被覆蓋:",
|
||||
@@ -42,8 +41,6 @@
|
||||
"keyboard_shortcuts.secondary_toot": "使用次要隱私設定來發布貼文",
|
||||
"moved_to_warning": "此帳戶已標記為移至 {moved_to_link},因此可能不接受新的追隨者。",
|
||||
"navigation_bar.app_settings": "應用程式設定",
|
||||
"navigation_bar.keyboard_shortcuts": "鍵盤快速鍵",
|
||||
"navigation_bar.misc": "雜項",
|
||||
"notifications.column_settings.filter_bar.show_bar": "顯示過濾器",
|
||||
"settings.always_show_spoilers_field": "永遠啟用內容警告欄位",
|
||||
"settings.close": "關閉",
|
||||
@@ -58,6 +55,8 @@
|
||||
"settings.content_warnings_unfold_opts": "自動展開選項",
|
||||
"settings.deprecated_setting": "此設定現在已由 Mastodon 的 {settings_page_link} 控制。",
|
||||
"settings.enable_content_warnings_auto_unfold": "自動展開內容警告",
|
||||
"settings.fullwidth_view": "將欄位延展至全寬(僅限桌面模式)",
|
||||
"settings.fullwidth_view_hint": "將欄位延展以填滿所有可用空間。",
|
||||
"settings.general": "一般設定",
|
||||
"settings.hicolor_privacy_icons": "隱私圖示使用對比色",
|
||||
"settings.hicolor_privacy_icons.hint": "用明亮且易於區分的顏色顯示隱私圖示",
|
||||
|
||||
@@ -126,6 +126,9 @@ export function createAccountFromServerJSON(serverJSON: ApiAccountJSON) {
|
||||
? accountJSON.username
|
||||
: accountJSON.display_name;
|
||||
|
||||
const accountNote =
|
||||
accountJSON.note && accountJSON.note !== '<p></p>' ? accountJSON.note : '';
|
||||
|
||||
return AccountFactory({
|
||||
...accountJSON,
|
||||
moved: moved?.id,
|
||||
@@ -142,8 +145,8 @@ export function createAccountFromServerJSON(serverJSON: ApiAccountJSON) {
|
||||
escapeTextContentForBrowser(displayName),
|
||||
emojiMap,
|
||||
),
|
||||
note_emojified: emojify(accountJSON.note, emojiMap),
|
||||
note_plain: unescapeHTML(accountJSON.note),
|
||||
note_emojified: emojify(accountNote, emojiMap),
|
||||
note_plain: unescapeHTML(accountNote),
|
||||
url:
|
||||
accountJSON.url.startsWith('http://') ||
|
||||
accountJSON.url.startsWith('https://')
|
||||
|
||||
@@ -619,8 +619,13 @@ export const composeReducer = (state = initialState, action) => {
|
||||
}
|
||||
|
||||
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({
|
||||
options: ImmutableList(action.status.get('poll').options.map(x => x.title)),
|
||||
options: options,
|
||||
multiple: action.status.get('poll').multiple,
|
||||
expires_in: expiresInFromExpiresAt(action.status.get('poll').expires_at),
|
||||
}));
|
||||
@@ -650,8 +655,13 @@ export const composeReducer = (state = initialState, action) => {
|
||||
}
|
||||
|
||||
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({
|
||||
options: ImmutableList(action.status.get('poll').options.map(x => x.title)),
|
||||
options: options,
|
||||
multiple: action.status.get('poll').multiple,
|
||||
expires_in: expiresInFromExpiresAt(action.status.get('poll').expires_at),
|
||||
}));
|
||||
|
||||
@@ -10,7 +10,7 @@ const initialState = ImmutableMap({
|
||||
stretch : true,
|
||||
side_arm : 'none',
|
||||
side_arm_reply_mode : 'keep',
|
||||
show_reply_count : false,
|
||||
show_reply_count : true,
|
||||
always_show_spoilers_field: false,
|
||||
confirm_boost_missing_media_description: false,
|
||||
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>} */
|
||||
const initialState = ImmutableMap();
|
||||
@@ -73,8 +77,14 @@ export default function statuses(state = initialState, action) {
|
||||
switch(action.type) {
|
||||
case STATUS_FETCH_REQUEST:
|
||||
return state.setIn([action.id, 'isLoading'], true);
|
||||
case STATUS_FETCH_FAIL:
|
||||
return state.delete(action.id);
|
||||
case STATUS_FETCH_FAIL: {
|
||||
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:
|
||||
return importStatus(state, action.status);
|
||||
case STATUSES_IMPORT:
|
||||
|
||||
@@ -1893,7 +1893,7 @@ a.sparkline {
|
||||
font-size: 15px;
|
||||
line-height: 22px;
|
||||
|
||||
li {
|
||||
> li {
|
||||
counter-increment: step 1;
|
||||
padding-inline-start: 2.5rem;
|
||||
padding-bottom: 8px;
|
||||
|
||||
@@ -992,6 +992,7 @@ body > [data-popper-placement] {
|
||||
line-height: 20px;
|
||||
letter-spacing: 0.1px;
|
||||
color: $highlight-text-color;
|
||||
background-color: var(--input-background-color);
|
||||
white-space: nowrap;
|
||||
text-overflow: ellipsis;
|
||||
overflow: hidden;
|
||||
@@ -1922,7 +1923,10 @@ body > [data-popper-placement] {
|
||||
}
|
||||
|
||||
.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;
|
||||
margin-block-start: 16px;
|
||||
@@ -1933,7 +1937,7 @@ body > [data-popper-placement] {
|
||||
border: var(--nested-card-border);
|
||||
|
||||
@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__datetime,
|
||||
.detailed-status__application,
|
||||
.detailed-status__link,
|
||||
.account__display-name {
|
||||
text-decoration: none;
|
||||
}
|
||||
@@ -2426,7 +2431,8 @@ a.account__display-name {
|
||||
}
|
||||
|
||||
.detailed-status__application,
|
||||
.detailed-status__datetime {
|
||||
.detailed-status__datetime,
|
||||
.detailed-status__link {
|
||||
color: inherit;
|
||||
}
|
||||
|
||||
@@ -2612,8 +2618,9 @@ a.account__display-name {
|
||||
}
|
||||
|
||||
.status__relative-time,
|
||||
.detailed-status__datetime {
|
||||
&:hover {
|
||||
.detailed-status__datetime,
|
||||
.detailed-status__link {
|
||||
&:is(a):hover {
|
||||
text-decoration: underline;
|
||||
}
|
||||
}
|
||||
@@ -2913,7 +2920,6 @@ a.account__display-name {
|
||||
&__pane {
|
||||
height: 100%;
|
||||
overflow: hidden;
|
||||
pointer-events: none;
|
||||
display: flex;
|
||||
justify-content: flex-end;
|
||||
min-width: 285px;
|
||||
@@ -2925,7 +2931,6 @@ a.account__display-name {
|
||||
&__inner {
|
||||
position: fixed;
|
||||
width: 285px;
|
||||
pointer-events: auto;
|
||||
height: 100%;
|
||||
}
|
||||
}
|
||||
@@ -3939,16 +3944,18 @@ a.account__display-name {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
width: 100%;
|
||||
padding: 12px;
|
||||
font-size: 16px;
|
||||
font-weight: 400;
|
||||
padding: 12px;
|
||||
text-decoration: none;
|
||||
overflow: hidden;
|
||||
white-space: nowrap;
|
||||
border: 0;
|
||||
background: transparent;
|
||||
color: $secondary-text-color;
|
||||
background: transparent;
|
||||
border: 0;
|
||||
border-left: 4px solid transparent;
|
||||
box-sizing: border-box;
|
||||
|
||||
&:hover,
|
||||
&:focus,
|
||||
@@ -11130,21 +11137,23 @@ noscript {
|
||||
}
|
||||
}
|
||||
|
||||
.status {
|
||||
.status:not(.status--is-quote) {
|
||||
border: 0;
|
||||
padding: 0;
|
||||
|
||||
&__avatar {
|
||||
width: 40px;
|
||||
height: 40px;
|
||||
}
|
||||
}
|
||||
|
||||
.status__wrapper-direct {
|
||||
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__action-bar,
|
||||
@@ -11158,16 +11167,16 @@ noscript {
|
||||
.hashtag-bar,
|
||||
.content-warning,
|
||||
.filter-warning {
|
||||
margin-inline-start: $icon-margin;
|
||||
width: calc(100% - $icon-margin);
|
||||
margin-inline-start: var(--status-gutter-width);
|
||||
width: calc(100% - var(--status-gutter-width));
|
||||
}
|
||||
|
||||
.more-from-author {
|
||||
width: calc(100% - $icon-margin + 2px);
|
||||
width: calc(100% - var(--status-gutter-width) + 2px);
|
||||
}
|
||||
|
||||
.status__content__read-more-button {
|
||||
margin-inline-start: $icon-margin;
|
||||
margin-inline-start: var(--status-gutter-width);
|
||||
}
|
||||
|
||||
.notification__report {
|
||||
|
||||
@@ -589,11 +589,14 @@ code {
|
||||
}
|
||||
|
||||
.stacked-actions {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 10px;
|
||||
margin-top: 30px;
|
||||
margin-bottom: 15px;
|
||||
}
|
||||
|
||||
button:not(.button, .link-button, .help-button) {
|
||||
.btn {
|
||||
display: block;
|
||||
width: 100%;
|
||||
border: 0;
|
||||
@@ -610,8 +613,6 @@ code {
|
||||
cursor: pointer;
|
||||
font-weight: 500;
|
||||
outline: 0;
|
||||
margin-bottom: 10px;
|
||||
margin-inline-end: 10px;
|
||||
|
||||
&:last-child {
|
||||
margin-inline-end: 0;
|
||||
|
||||
@@ -101,7 +101,7 @@
|
||||
}
|
||||
|
||||
// Change the background colors of statuses
|
||||
.focusable:focus {
|
||||
.focusable:focus-visible {
|
||||
background: lighten($white, 4%);
|
||||
}
|
||||
|
||||
|
||||
@@ -107,7 +107,8 @@
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
&.editable {
|
||||
&.editable,
|
||||
&.disabled {
|
||||
align-items: center;
|
||||
overflow: visible;
|
||||
}
|
||||
@@ -165,7 +166,8 @@
|
||||
}
|
||||
}
|
||||
|
||||
&__option.editable &__input {
|
||||
&__option.editable &__input,
|
||||
&__option.disabled &__input {
|
||||
&:active,
|
||||
&:focus,
|
||||
&:hover {
|
||||
|
||||
@@ -96,12 +96,17 @@ export const ensureComposeIsVisible = (getState) => {
|
||||
};
|
||||
|
||||
export function setComposeToStatus(status, text, spoiler_text) {
|
||||
return{
|
||||
type: COMPOSE_SET_STATUS,
|
||||
status,
|
||||
text,
|
||||
spoiler_text,
|
||||
};
|
||||
return (dispatch, getState) => {
|
||||
const maxOptions = getState().server.getIn(['server', 'configuration', 'polls', 'max_options']);
|
||||
|
||||
dispatch({
|
||||
type: COMPOSE_SET_STATUS,
|
||||
status,
|
||||
text,
|
||||
spoiler_text,
|
||||
maxOptions,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
export function changeCompose(text) {
|
||||
@@ -183,7 +188,7 @@ export function directCompose(account) {
|
||||
};
|
||||
}
|
||||
|
||||
export function submitCompose() {
|
||||
export function submitCompose(successCallback) {
|
||||
return function (dispatch, getState) {
|
||||
const status = getState().getIn(['compose', 'text'], '');
|
||||
const media = getState().getIn(['compose', 'media_attachments']);
|
||||
@@ -239,6 +244,9 @@ export function submitCompose() {
|
||||
|
||||
dispatch(insertIntoTagHistory(response.data.tags, status));
|
||||
dispatch(submitComposeSuccess({ ...response.data }));
|
||||
if (typeof successCallback === 'function') {
|
||||
successCallback(response.data);
|
||||
}
|
||||
|
||||
// To make the app more responsive, immediately push the status
|
||||
// into the columns
|
||||
|
||||
@@ -21,6 +21,15 @@ export function normalizeFilterResult(result) {
|
||||
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) {
|
||||
const normalStatus = { ...status };
|
||||
|
||||
@@ -72,7 +81,7 @@ export function normalizeStatus(status, normalOldStatus) {
|
||||
} else {
|
||||
// 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.
|
||||
if (normalStatus.spoiler_text && !normalStatus.content) {
|
||||
if (normalStatus.spoiler_text && !normalStatus.content && !normalStatus.quote) {
|
||||
normalStatus.content = normalStatus.spoiler_text;
|
||||
normalStatus.spoiler_text = '';
|
||||
}
|
||||
@@ -86,6 +95,11 @@ export function normalizeStatus(status, normalOldStatus) {
|
||||
normalStatus.spoilerHtml = emojify(escapeTextContentForBrowser(spoilerText), emojiMap);
|
||||
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://'))) {
|
||||
normalStatus.url = null;
|
||||
}
|
||||
@@ -125,6 +139,11 @@ export function normalizeStatusTranslation(translation, status) {
|
||||
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;
|
||||
}
|
||||
|
||||
|
||||
@@ -3,7 +3,7 @@ import { browserHistory } from 'mastodon/components/router';
|
||||
import api from '../api';
|
||||
|
||||
import { ensureComposeIsVisible, setComposeToStatus } from './compose';
|
||||
import { importFetchedStatus, importFetchedStatuses, importFetchedAccount } from './importer';
|
||||
import { importFetchedStatus, importFetchedAccount } from './importer';
|
||||
import { fetchContext } from './statuses_typed';
|
||||
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) => {
|
||||
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(fetchStatusSuccess(skipLoading));
|
||||
}).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 {
|
||||
type: STATUS_FETCH_FAIL,
|
||||
id,
|
||||
error,
|
||||
parentQuotePostId,
|
||||
skipLoading,
|
||||
skipAlert: true,
|
||||
};
|
||||
}
|
||||
|
||||
export function redraft(status, raw_text) {
|
||||
return {
|
||||
type: REDRAFT,
|
||||
status,
|
||||
raw_text,
|
||||
return (dispatch, getState) => {
|
||||
const maxOptions = getState().server.getIn(['server', 'configuration', 'polls', 'max_options']);
|
||||
|
||||
dispatch({
|
||||
type: REDRAFT,
|
||||
status,
|
||||
raw_text,
|
||||
maxOptions,
|
||||
});
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
@@ -1,10 +1,11 @@
|
||||
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) =>
|
||||
apiRequestPost<{ reblog: Status }>(`v1/statuses/${statusId}/reblog`, {
|
||||
apiRequestPost<{ reblog: ApiStatusJSON }>(`v1/statuses/${statusId}/reblog`, {
|
||||
visibility,
|
||||
});
|
||||
|
||||
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[];
|
||||
statuses_count: number;
|
||||
uri: string;
|
||||
url: string;
|
||||
url?: string;
|
||||
username: string;
|
||||
moved?: ApiAccountJSON;
|
||||
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