Merge pull request #888 from ThibG/glitch-soc/merge-upstream
Merge upstream changes
This commit is contained in:
commit
8f3fbf4e3b
69 changed files with 1569 additions and 511 deletions
460
CHANGELOG.md
460
CHANGELOG.md
|
@ -6,301 +6,325 @@ All notable changes to this project will be documented in this file.
|
||||||
## [Unreleased]
|
## [Unreleased]
|
||||||
### Added
|
### Added
|
||||||
|
|
||||||
- Add link for adding a user to a list from their profile (#9062)
|
- Add link for adding a user to a list from their profile ([namelessGonbai](https://github.com/tootsuite/mastodon/pull/9062))
|
||||||
- Add joining several hashtags in a single column (#8904)
|
- Add joining several hashtags in a single column ([gdpelican](https://github.com/tootsuite/mastodon/pull/8904))
|
||||||
- Add volume sliders for videos (#9366)
|
- Add volume sliders for videos ([sumdog](https://github.com/tootsuite/mastodon/pull/9366))
|
||||||
- Add a tooltip explaining what a locked account is (#9403)
|
- Add a tooltip explaining what a locked account is ([pawelngei](https://github.com/tootsuite/mastodon/pull/9403))
|
||||||
- Add preloaded cache for common JSON-LD contexts (#9412)
|
- Add preloaded cache for common JSON-LD contexts ([ThibG](https://github.com/tootsuite/mastodon/pull/9412))
|
||||||
- Add profile directory (#9427)
|
- Add profile directory ([Gargron](https://github.com/tootsuite/mastodon/pull/9427))
|
||||||
- Add setting to not group reblogs in home feed (#9248)
|
- Add setting to not group reblogs in home feed ([ThibG](https://github.com/tootsuite/mastodon/pull/9248))
|
||||||
- Add admin ability to remove a user's header image (#9495)
|
- Add admin ability to remove a user's header image ([ThibG](https://github.com/tootsuite/mastodon/pull/9495))
|
||||||
- Add account hashtags to ActivityPub actor JSON (#9450)
|
- Add account hashtags to ActivityPub actor JSON ([Gargron](https://github.com/tootsuite/mastodon/pull/9450))
|
||||||
- Add error message for avatar image that's too large (#9518)
|
- Add error message for avatar image that's too large ([sumdog](https://github.com/tootsuite/mastodon/pull/9518))
|
||||||
- Add notification quick-filter bar (#9399)
|
- Add notification quick-filter bar ([pawelngei](https://github.com/tootsuite/mastodon/pull/9399))
|
||||||
- Add new first-time tutorial (#9531)
|
- Add new first-time tutorial ([Gargron](https://github.com/tootsuite/mastodon/pull/9531))
|
||||||
- Add moderation warnings (#9519)
|
- Add moderation warnings ([Gargron](https://github.com/tootsuite/mastodon/pull/9519))
|
||||||
- Add emoji codepoint mappings for v11.0 (#9618)
|
- Add emoji codepoint mappings for v11.0 ([Gargron](https://github.com/tootsuite/mastodon/pull/9618))
|
||||||
- Add REST API for creating an account (#9572)
|
- Add REST API for creating an account ([Gargron](https://github.com/tootsuite/mastodon/pull/9572))
|
||||||
- Add support for Malayalam in language filter (#9624)
|
- Add support for Malayalam in language filter ([tachyons](https://github.com/tootsuite/mastodon/pull/9624))
|
||||||
- Add exclude_reblogs option to account statuses API (#9640)
|
- Add exclude_reblogs option to account statuses API ([Gargron](https://github.com/tootsuite/mastodon/pull/9640))
|
||||||
- Add local followers page to admin account UI (#9610)
|
- Add local followers page to admin account UI ([chr-1x](https://github.com/tootsuite/mastodon/pull/9610))
|
||||||
- Add healthcheck commands to docker-compose.yml (#9143)
|
- Add healthcheck commands to docker-compose.yml ([BenLubar](https://github.com/tootsuite/mastodon/pull/9143))
|
||||||
- Add handler for Move activity to migrate followers (#9629)
|
- Add handler for Move activity to migrate followers ([Gargron](https://github.com/tootsuite/mastodon/pull/9629))
|
||||||
- Add CSV export for lists and domain blocks (#9677)
|
- Add CSV export for lists and domain blocks ([Gargron](https://github.com/tootsuite/mastodon/pull/9677))
|
||||||
- Add `tootctl accounts follow ACCT` (#9414)
|
- Add `tootctl accounts follow ACCT` ([Gargron](https://github.com/tootsuite/mastodon/pull/9414))
|
||||||
- Add scheduled statuses (#9706)
|
- Add scheduled statuses ([Gargron](https://github.com/tootsuite/mastodon/pull/9706))
|
||||||
- Add immutable caching for S3 objects (#9722)
|
- Add immutable caching for S3 objects ([nolanlawson](https://github.com/tootsuite/mastodon/pull/9722))
|
||||||
- Add cache to custom emojis API (#9732)
|
- Add cache to custom emojis API ([Gargron](https://github.com/tootsuite/mastodon/pull/9732))
|
||||||
- Add preview cards to non-detailed statuses on public pages (#9714)
|
- Add preview cards to non-detailed statuses on public pages ([Gargron](https://github.com/tootsuite/mastodon/pull/9714))
|
||||||
- Add `mod` and `moderator` to list of default reserved usernames (#9713)
|
- Add `mod` and `moderator` to list of default reserved usernames ([Gargron](https://github.com/tootsuite/mastodon/pull/9713))
|
||||||
- Add quick links to the admin interface in the web UI (#8545)
|
- Add quick links to the admin interface in the web UI ([ThibG](https://github.com/tootsuite/mastodon/pull/8545))
|
||||||
|
- Add `tootctl domains crawl` ([Gargron](https://github.com/tootsuite/mastodon/pull/9809))
|
||||||
|
- Add attachment list fallback to public pages ([ThibG](https://github.com/tootsuite/mastodon/pull/9780))
|
||||||
|
- Add `tootctl --version` ([Gargron](https://github.com/tootsuite/mastodon/pull/9835))
|
||||||
|
- Add information about how to opt-in to the directory on the directory ([Gargron](https://github.com/tootsuite/mastodon/pull/9834))
|
||||||
|
- Add timeouts for S3 ([Gargron](https://github.com/tootsuite/mastodon/pull/9842))
|
||||||
|
- Add support for non-public reblogs from ActivityPub ([Gargron](https://github.com/tootsuite/mastodon/pull/9841))
|
||||||
|
- Add sending of `Reject` activity when sending a `Block` activity ([ThibG](https://github.com/tootsuite/mastodon/pull/9811))
|
||||||
|
|
||||||
### Changed
|
### Changed
|
||||||
|
|
||||||
- Temporarily pause timeline if mouse moved recently (#9200)
|
- Temporarily pause timeline if mouse moved recently ([lmorchard](https://github.com/tootsuite/mastodon/pull/9200))
|
||||||
- Change the password form order (#9267)
|
- Change the password form order ([mayaeh](https://github.com/tootsuite/mastodon/pull/9267))
|
||||||
- Redesign admin UI for accounts (#9340, #9643)
|
- Redesign admin UI for accounts ([Gargron](https://github.com/tootsuite/mastodon/pull/9340), [Gargron](https://github.com/tootsuite/mastodon/pull/9643))
|
||||||
- Redesign admin UI for instances/domain blocks (#9645)
|
- Redesign admin UI for instances/domain blocks ([Gargron](https://github.com/tootsuite/mastodon/pull/9645))
|
||||||
- Swap avatar and header input fields in profile page (#9271)
|
- Swap avatar and header input fields in profile page ([ThibG](https://github.com/tootsuite/mastodon/pull/9271))
|
||||||
- When posting in mobile mode, go back to previous history location (#9502)
|
- When posting in mobile mode, go back to previous history location ([ThibG](https://github.com/tootsuite/mastodon/pull/9502))
|
||||||
- Split out is_changing_upload from is_submitting (#9536)
|
- Split out is_changing_upload from is_submitting ([ThibG](https://github.com/tootsuite/mastodon/pull/9536))
|
||||||
- Back to the getting-started when pins the timeline. (#9561)
|
- Back to the getting-started when pins the timeline. ([kedamaDQ](https://github.com/tootsuite/mastodon/pull/9561))
|
||||||
- Allow unauthenticated REST API access to GET /api/v1/accounts/:id/statuses (#9573)
|
- Allow unauthenticated REST API access to GET /api/v1/accounts/:id/statuses ([Gargron](https://github.com/tootsuite/mastodon/pull/9573))
|
||||||
- Limit maximum visibility of local silenced users to unlisted (#9583)
|
- Limit maximum visibility of local silenced users to unlisted ([ThibG](https://github.com/tootsuite/mastodon/pull/9583))
|
||||||
- Change API error message for unconfirmed accounts (#9625)
|
- Change API error message for unconfirmed accounts ([noellabo](https://github.com/tootsuite/mastodon/pull/9625))
|
||||||
- Change the icon to "reply-all" when it's a reply to other accounts (#9378)
|
- Change the icon to "reply-all" when it's a reply to other accounts ([mayaeh](https://github.com/tootsuite/mastodon/pull/9378))
|
||||||
- Do not ignore federated reports targetting already-reported accounts (#9534)
|
- Do not ignore federated reports targetting already-reported accounts ([ThibG](https://github.com/tootsuite/mastodon/pull/9534))
|
||||||
- Upgrade default Ruby version to 2.6.0 (#9688)
|
- Upgrade default Ruby version to 2.6.0 ([Gargron](https://github.com/tootsuite/mastodon/pull/9688))
|
||||||
- Change e-mail digest frequency (#9689)
|
- Change e-mail digest frequency ([Gargron](https://github.com/tootsuite/mastodon/pull/9689))
|
||||||
- Change Docker images for Tor support in docker-compose.yml (#9438)
|
- Change Docker images for Tor support in docker-compose.yml ([Sir-Boops](https://github.com/tootsuite/mastodon/pull/9438))
|
||||||
- Display fallback link card thumbnail when none is given (#9715)
|
- Display fallback link card thumbnail when none is given ([Gargron](https://github.com/tootsuite/mastodon/pull/9715))
|
||||||
- Change account bio length validation to ignore mention domains and URLs (#9717)
|
- Change account bio length validation to ignore mention domains and URLs ([Gargron](https://github.com/tootsuite/mastodon/pull/9717))
|
||||||
- Use configured contact user for "anonymous" federation activities (#9661)
|
- Use configured contact user for "anonymous" federation activities ([yukimochi](https://github.com/tootsuite/mastodon/pull/9661))
|
||||||
- Change remote interaction dialog to use specific actions instead of generic "interact" (#9743)
|
- Change remote interaction dialog to use specific actions instead of generic "interact" ([Gargron](https://github.com/tootsuite/mastodon/pull/9743))
|
||||||
- Always re-fetch public key when signature verification fails to support blind key rotation (#9667)
|
- Always re-fetch public key when signature verification fails to support blind key rotation ([ThibG](https://github.com/tootsuite/mastodon/pull/9667))
|
||||||
- Make replies to boosts impossible, connect reply to original status instead (#9129)
|
- Make replies to boosts impossible, connect reply to original status instead ([valerauko](https://github.com/tootsuite/mastodon/pull/9129))
|
||||||
- Change e-mail MX validation to check both A and MX records against blacklist (#9489)
|
- Change e-mail MX validation to check both A and MX records against blacklist ([Gargron](https://github.com/tootsuite/mastodon/pull/9489))
|
||||||
|
- Hide floating action button on search and getting started pages ([tmm576](https://github.com/tootsuite/mastodon/pull/9826))
|
||||||
|
- Redesign public hashtag page to use a masonry layout ([Gargron](https://github.com/tootsuite/mastodon/pull/9822))
|
||||||
|
- Use `summary` as summary instead of content warning for converted ActivityPub objects ([Gargron](https://github.com/tootsuite/mastodon/pull/9823))
|
||||||
|
- Display a double reply arrow on public pages for toots that are replies ([ThibG](https://github.com/tootsuite/mastodon/pull/9808))
|
||||||
|
- Change admin UI right panel size to be wider ([Kjwon15](https://github.com/tootsuite/mastodon/pull/9768))
|
||||||
|
|
||||||
### Removed
|
### Removed
|
||||||
|
|
||||||
- Remove links to bridge.joinmastodon.org (non-functional) (#9608)
|
- Remove links to bridge.joinmastodon.org (non-functional) ([Gargron](https://github.com/tootsuite/mastodon/pull/9608))
|
||||||
- Remove LD-Signatures from activities that do not need them (#9659)
|
- Remove LD-Signatures from activities that do not need them ([ThibG](https://github.com/tootsuite/mastodon/pull/9659))
|
||||||
|
|
||||||
### Fixed
|
### Fixed
|
||||||
|
|
||||||
- Remove unused computation of reblog references from updateTimeline (#9244)
|
- Remove unused computation of reblog references from updateTimeline ([ThibG](https://github.com/tootsuite/mastodon/pull/9244))
|
||||||
- Fix loaded embeds resetting if a status arrives from API again (#9270)
|
- Fix loaded embeds resetting if a status arrives from API again ([ThibG](https://github.com/tootsuite/mastodon/pull/9270))
|
||||||
- Fix race condition causing shallow status with only a "favourited" attribute (#9272)
|
- Fix race condition causing shallow status with only a "favourited" attribute ([ThibG](https://github.com/tootsuite/mastodon/pull/9272))
|
||||||
- Remove intermediary arrays when creating hash maps from results (#9291)
|
- Remove intermediary arrays when creating hash maps from results ([Gargron](https://github.com/tootsuite/mastodon/pull/9291))
|
||||||
- Extract counters from accounts table to account_stats table to improve performance (#9295)
|
- Extract counters from accounts table to account_stats table to improve performance ([Gargron](https://github.com/tootsuite/mastodon/pull/9295))
|
||||||
- Change identities id column to a bigint (#9371)
|
- Change identities id column to a bigint ([Gargron](https://github.com/tootsuite/mastodon/pull/9371))
|
||||||
- Fix conversations API pagination (#9407)
|
- Fix conversations API pagination ([ThibG](https://github.com/tootsuite/mastodon/pull/9407))
|
||||||
- Improve account suspension speed and completeness (#9290)
|
- Improve account suspension speed and completeness ([Gargron](https://github.com/tootsuite/mastodon/pull/9290))
|
||||||
- Fix thread depth computation in statuses_controller (#9426)
|
- Fix thread depth computation in statuses_controller ([ThibG](https://github.com/tootsuite/mastodon/pull/9426))
|
||||||
- Fix database deadlocks by moving account stats update outside transaction (#9437)
|
- Fix database deadlocks by moving account stats update outside transaction ([ThibG](https://github.com/tootsuite/mastodon/pull/9437))
|
||||||
- Escape HTML in profile name preview in profile settings (#9446)
|
- Escape HTML in profile name preview in profile settings ([pawelngei](https://github.com/tootsuite/mastodon/pull/9446))
|
||||||
- Use same CORS policy for /@:username and /users/:username (#9485)
|
- Use same CORS policy for /@:username and /users/:username ([ThibG](https://github.com/tootsuite/mastodon/pull/9485))
|
||||||
- Make custom emoji domains case insensitive (#9474)
|
- Make custom emoji domains case insensitive ([Esteth](https://github.com/tootsuite/mastodon/pull/9474))
|
||||||
- Various fixes to scrollable lists and media gallery (#9501)
|
- Various fixes to scrollable lists and media gallery ([ThibG](https://github.com/tootsuite/mastodon/pull/9501))
|
||||||
- Fix bootsnap cache directory being declared relatively (#9511)
|
- Fix bootsnap cache directory being declared relatively ([Gargron](https://github.com/tootsuite/mastodon/pull/9511))
|
||||||
- Fix timeline pagination in the web UI (#9516)
|
- Fix timeline pagination in the web UI ([ThibG](https://github.com/tootsuite/mastodon/pull/9516))
|
||||||
- Fix padding on dropdown elements in preferences (#9517)
|
- Fix padding on dropdown elements in preferences ([ThibG](https://github.com/tootsuite/mastodon/pull/9517))
|
||||||
- Make avatar and headers respect GIF autoplay settings (#9515)
|
- Make avatar and headers respect GIF autoplay settings ([ThibG](https://github.com/tootsuite/mastodon/pull/9515))
|
||||||
- Do no retry Web Push workers if the server returns a 4xx response (#9434)
|
- Do no retry Web Push workers if the server returns a 4xx response ([Gargron](https://github.com/tootsuite/mastodon/pull/9434))
|
||||||
- Minor scrollable list fixes (#9551)
|
- Minor scrollable list fixes ([ThibG](https://github.com/tootsuite/mastodon/pull/9551))
|
||||||
- Ignore low-confidence CharlockHolmes guesses when parsing link cards (#9510)
|
- Ignore low-confidence CharlockHolmes guesses when parsing link cards ([ThibG](https://github.com/tootsuite/mastodon/pull/9510))
|
||||||
- Fix `tootctl accounts rotate` not updating public keys (#9556)
|
- Fix `tootctl accounts rotate` not updating public keys ([Gargron](https://github.com/tootsuite/mastodon/pull/9556))
|
||||||
- Fix CSP / X-Frame-Options for media players (#9558)
|
- Fix CSP / X-Frame-Options for media players ([jomo](https://github.com/tootsuite/mastodon/pull/9558))
|
||||||
- Fix unnecessary loadMore calls when the end of a timeline has been reached (#9581)
|
- Fix unnecessary loadMore calls when the end of a timeline has been reached ([ThibG](https://github.com/tootsuite/mastodon/pull/9581))
|
||||||
- Skip mailer job retries when a record no longer exists (#9590)
|
- Skip mailer job retries when a record no longer exists ([Gargron](https://github.com/tootsuite/mastodon/pull/9590))
|
||||||
- Fix composer not getting focus after reply confirmation dialog (#9602)
|
- Fix composer not getting focus after reply confirmation dialog ([ThibG](https://github.com/tootsuite/mastodon/pull/9602))
|
||||||
- Fix signature verification stoplight triggering on non-timeout errors (#9617)
|
- Fix signature verification stoplight triggering on non-timeout errors ([Gargron](https://github.com/tootsuite/mastodon/pull/9617))
|
||||||
- Fix ThreadResolveWorker getting queued with invalid URLs (#9628)
|
- Fix ThreadResolveWorker getting queued with invalid URLs ([Gargron](https://github.com/tootsuite/mastodon/pull/9628))
|
||||||
- Fix crash when clearing uninitialized timeline (#9662)
|
- Fix crash when clearing uninitialized timeline ([ThibG](https://github.com/tootsuite/mastodon/pull/9662))
|
||||||
- Avoid duplicate work by merging ReplyDistributionWorker into DistributionWorker (#9660)
|
- Avoid duplicate work by merging ReplyDistributionWorker into DistributionWorker ([ThibG](https://github.com/tootsuite/mastodon/pull/9660))
|
||||||
- Skip full text search if it fails, instead of erroring out completely (#9654)
|
- Skip full text search if it fails, instead of erroring out completely ([Kjwon15](https://github.com/tootsuite/mastodon/pull/9654))
|
||||||
- Fix profile metadata links not verifying correctly sometimes (#9673)
|
- Fix profile metadata links not verifying correctly sometimes ([shrft](https://github.com/tootsuite/mastodon/pull/9673))
|
||||||
- Ensure blocked user unfollows blocker if Block/Undo-Block activities are processed out of order (#9687)
|
- Ensure blocked user unfollows blocker if Block/Undo-Block activities are processed out of order ([ThibG](https://github.com/tootsuite/mastodon/pull/9687))
|
||||||
- Fix unreadable text color in report modal for some statuses (#9716)
|
- Fix unreadable text color in report modal for some statuses ([Gargron](https://github.com/tootsuite/mastodon/pull/9716))
|
||||||
- Stop GIFV timeline preview explicitly when it's opened in modal (#9749)
|
- Stop GIFV timeline preview explicitly when it's opened in modal ([kedamaDQ](https://github.com/tootsuite/mastodon/pull/9749))
|
||||||
|
- Fix scrollbar width compensation ([ThibG](https://github.com/tootsuite/mastodon/pull/9824))
|
||||||
|
- Fix race conditions when processing deleted toots ([ThibG](https://github.com/tootsuite/mastodon/pull/9815))
|
||||||
|
- Fix SSO issues on WebKit browsers by disabling Same-Site cookie again ([moritzheiber](https://github.com/tootsuite/mastodon/pull/9819))
|
||||||
|
- Fix empty OEmbed error ([renatolond](https://github.com/tootsuite/mastodon/pull/9807))
|
||||||
|
- Fix drag & drop modal not disappearing sometimes ([hinaloe](https://github.com/tootsuite/mastodon/pull/9797))
|
||||||
|
- Fix statuses with content warnings being displayed in web push notifications sometimes ([ThibG](https://github.com/tootsuite/mastodon/pull/9778))
|
||||||
|
- Fix scroll-to-detailed status not working on public pages ([ThibG](https://github.com/tootsuite/mastodon/pull/9773))
|
||||||
|
- Fix media modal loading indicator ([ThibG](https://github.com/tootsuite/mastodon/pull/9771))
|
||||||
|
- Fix hashtag search results not having a permalink fallback in web UI ([ThibG](https://github.com/tootsuite/mastodon/pull/9810))
|
||||||
|
- Fix slightly cropped font on settings page dropdowns when using system font ([ariasuni](https://github.com/tootsuite/mastodon/pull/9839))
|
||||||
|
- Fix not being able to drag & drop text into forms ([tmm576](https://github.com/tootsuite/mastodon/pull/9840))
|
||||||
|
|
||||||
### Security
|
### Security
|
||||||
|
|
||||||
- Sanitize and sandbox toot embeds in web UI (#9552)
|
- Sanitize and sandbox toot embeds in web UI ([ThibG](https://github.com/tootsuite/mastodon/pull/9552))
|
||||||
|
- Add tombstones for remote statuses to prevent replay attacks ([ThibG](https://github.com/tootsuite/mastodon/pull/9830))
|
||||||
|
|
||||||
## [2.6.5] - 2018-12-01
|
## [2.6.5] - 2018-12-01
|
||||||
### Changed
|
### Changed
|
||||||
|
|
||||||
- Change lists to display replies to others on the list and list owner (#9324)
|
- Change lists to display replies to others on the list and list owner ([ThibG](https://github.com/tootsuite/mastodon/pull/9324))
|
||||||
|
|
||||||
### Fixed
|
### Fixed
|
||||||
|
|
||||||
- Fix failures caused by commonly-used JSON-LD contexts being unavailable (#9412)
|
- Fix failures caused by commonly-used JSON-LD contexts being unavailable ([ThibG](https://github.com/tootsuite/mastodon/pull/9412))
|
||||||
|
|
||||||
## [2.6.4] - 2018-11-30
|
## [2.6.4] - 2018-11-30
|
||||||
### Fixed
|
### Fixed
|
||||||
|
|
||||||
- Fix yarn dependencies not installing due to yanked event-stream package (#9401)
|
- Fix yarn dependencies not installing due to yanked event-stream package ([Gargron](https://github.com/tootsuite/mastodon/pull/9401))
|
||||||
|
|
||||||
## [2.6.3] - 2018-11-30
|
## [2.6.3] - 2018-11-30
|
||||||
### Added
|
### Added
|
||||||
|
|
||||||
- Add hyphen to characters allowed in remote usernames (#9345)
|
- Add hyphen to characters allowed in remote usernames ([ThibG](https://github.com/tootsuite/mastodon/pull/9345))
|
||||||
|
|
||||||
### Changed
|
### Changed
|
||||||
|
|
||||||
- Change server user count to exclude suspended accounts (#9380)
|
- Change server user count to exclude suspended accounts ([Gargron](https://github.com/tootsuite/mastodon/pull/9380))
|
||||||
|
|
||||||
### Fixed
|
### Fixed
|
||||||
|
|
||||||
- Fix ffmpeg processing sometimes stalling due to overfilled stdout buffer (#9368)
|
- Fix ffmpeg processing sometimes stalling due to overfilled stdout buffer ([hugogameiro](https://github.com/tootsuite/mastodon/pull/9368))
|
||||||
- Fix missing DNS records raising the wrong kind of exception (#9379)
|
- Fix missing DNS records raising the wrong kind of exception ([Gargron](https://github.com/tootsuite/mastodon/pull/9379))
|
||||||
- Fix already queued deliveries still trying to reach inboxes marked as unavailable (#9358)
|
- Fix already queued deliveries still trying to reach inboxes marked as unavailable ([Gargron](https://github.com/tootsuite/mastodon/pull/9358))
|
||||||
|
|
||||||
### Security
|
### Security
|
||||||
|
|
||||||
- Fix TLS handshake timeout not being enforced (#9381)
|
- Fix TLS handshake timeout not being enforced ([Gargron](https://github.com/tootsuite/mastodon/pull/9381))
|
||||||
|
|
||||||
## [2.6.2] - 2018-11-23
|
## [2.6.2] - 2018-11-23
|
||||||
### Added
|
### Added
|
||||||
|
|
||||||
- Add Page to whitelisted ActivityPub types (#9188)
|
- Add Page to whitelisted ActivityPub types ([mbajur](https://github.com/tootsuite/mastodon/pull/9188))
|
||||||
- Add 20px to column width in web UI (#9227)
|
- Add 20px to column width in web UI ([Gargron](https://github.com/tootsuite/mastodon/pull/9227))
|
||||||
- Add amount of freed disk space in `tootctl media remove` (#9229, #9239, #9288)
|
- Add amount of freed disk space in `tootctl media remove` ([Gargron](https://github.com/tootsuite/mastodon/pull/9229), [Gargron](https://github.com/tootsuite/mastodon/pull/9239), [mayaeh](https://github.com/tootsuite/mastodon/pull/9288))
|
||||||
- Add "Show thread" link to self-replies (#9228)
|
- Add "Show thread" link to self-replies ([Gargron](https://github.com/tootsuite/mastodon/pull/9228))
|
||||||
|
|
||||||
### Changed
|
### Changed
|
||||||
|
|
||||||
- Change order of Atom and RSS links so Atom is first (#9302)
|
- Change order of Atom and RSS links so Atom is first ([Alkarex](https://github.com/tootsuite/mastodon/pull/9302))
|
||||||
- Change Nginx configuration for Nanobox apps (#9310)
|
- Change Nginx configuration for Nanobox apps ([danhunsaker](https://github.com/tootsuite/mastodon/pull/9310))
|
||||||
- Change the follow action to appear instant in web UI (#9220)
|
- Change the follow action to appear instant in web UI ([Gargron](https://github.com/tootsuite/mastodon/pull/9220))
|
||||||
- Change how the ActiveRecord connection is instantiated in on_worker_boot (#9238)
|
- Change how the ActiveRecord connection is instantiated in on_worker_boot ([Gargron](https://github.com/tootsuite/mastodon/pull/9238))
|
||||||
- Change `tootctl accounts cull` to always touch accounts so they can be skipped (#9293)
|
- Change `tootctl accounts cull` to always touch accounts so they can be skipped ([renatolond](https://github.com/tootsuite/mastodon/pull/9293))
|
||||||
- Change mime type comparison to ignore JSON-LD profile (#9179)
|
- Change mime type comparison to ignore JSON-LD profile ([valerauko](https://github.com/tootsuite/mastodon/pull/9179))
|
||||||
|
|
||||||
### Fixed
|
### Fixed
|
||||||
|
|
||||||
- Fix web UI crash when conversation has no last status (#9207)
|
- Fix web UI crash when conversation has no last status ([sammy8806](https://github.com/tootsuite/mastodon/pull/9207))
|
||||||
- Fix follow limit validator reporting lower number past threshold (#9230)
|
- Fix follow limit validator reporting lower number past threshold ([Gargron](https://github.com/tootsuite/mastodon/pull/9230))
|
||||||
- Fix form validation flash message color and input borders (#9235)
|
- Fix form validation flash message color and input borders ([Gargron](https://github.com/tootsuite/mastodon/pull/9235))
|
||||||
- Fix invalid twitter:player cards being displayed (#9254)
|
- Fix invalid twitter:player cards being displayed ([ThibG](https://github.com/tootsuite/mastodon/pull/9254))
|
||||||
- Fix emoji update date being processed incorrectly (#9255)
|
- Fix emoji update date being processed incorrectly ([ThibG](https://github.com/tootsuite/mastodon/pull/9255))
|
||||||
- Fix playing embed resetting if status is reloaded in web UI (#9270, #9275)
|
- Fix playing embed resetting if status is reloaded in web UI ([ThibG](https://github.com/tootsuite/mastodon/pull/9270), [Gargron](https://github.com/tootsuite/mastodon/pull/9275))
|
||||||
- Fix web UI crash when favouriting a deleted status (#9272)
|
- Fix web UI crash when favouriting a deleted status ([ThibG](https://github.com/tootsuite/mastodon/pull/9272))
|
||||||
- Fix intermediary arrays being created for hash maps (#9291)
|
- Fix intermediary arrays being created for hash maps ([Gargron](https://github.com/tootsuite/mastodon/pull/9291))
|
||||||
- Fix filter ID not being a string in REST API (#9303)
|
- Fix filter ID not being a string in REST API ([Gargron](https://github.com/tootsuite/mastodon/pull/9303))
|
||||||
|
|
||||||
### Security
|
### Security
|
||||||
|
|
||||||
- Fix multiple remote account deletions being able to deadlock the database (#9292)
|
- Fix multiple remote account deletions being able to deadlock the database ([Gargron](https://github.com/tootsuite/mastodon/pull/9292))
|
||||||
- Fix HTTP connection timeout of 10s not being enforced (#9329)
|
- Fix HTTP connection timeout of 10s not being enforced ([Gargron](https://github.com/tootsuite/mastodon/pull/9329))
|
||||||
|
|
||||||
## [2.6.1] - 2018-10-30
|
## [2.6.1] - 2018-10-30
|
||||||
### Fixed
|
### Fixed
|
||||||
|
|
||||||
- Fix resolving resources by URL not working due to a regression in #9132 (#9171)
|
- Fix resolving resources by URL not working due to a regression in [valerauko](https://github.com/tootsuite/mastodon/pull/9132) ([Gargron](https://github.com/tootsuite/mastodon/pull/9171))
|
||||||
- Fix reducer error in web UI when a conversation has no last status (#9173)
|
- Fix reducer error in web UI when a conversation has no last status ([Gargron](https://github.com/tootsuite/mastodon/pull/9173))
|
||||||
|
|
||||||
## [2.6.0] - 2018-10-30
|
## [2.6.0] - 2018-10-30
|
||||||
### Added
|
### Added
|
||||||
|
|
||||||
- Add link ownership verification (#8703)
|
- Add link ownership verification ([Gargron](https://github.com/tootsuite/mastodon/pull/8703))
|
||||||
- Add conversations API (#8832)
|
- Add conversations API ([Gargron](https://github.com/tootsuite/mastodon/pull/8832))
|
||||||
- Add limit for the number of people that can be followed from one account (#8807)
|
- Add limit for the number of people that can be followed from one account ([Gargron](https://github.com/tootsuite/mastodon/pull/8807))
|
||||||
- Add admin setting to customize mascot (#8766)
|
- Add admin setting to customize mascot ([ashleyhull-versent](https://github.com/tootsuite/mastodon/pull/8766))
|
||||||
- Add support for more granular ActivityPub audiences from other software, i.e. circles (#8950, #9093, #9150)
|
- Add support for more granular ActivityPub audiences from other software, i.e. circles ([Gargron](https://github.com/tootsuite/mastodon/pull/8950), [Gargron](https://github.com/tootsuite/mastodon/pull/9093), [Gargron](https://github.com/tootsuite/mastodon/pull/9150))
|
||||||
- Add option to block all reports from a domain (#8830)
|
- Add option to block all reports from a domain ([Gargron](https://github.com/tootsuite/mastodon/pull/8830))
|
||||||
- Add user preference to always expand toots marked with content warnings (#8762)
|
- Add user preference to always expand toots marked with content warnings ([webroo](https://github.com/tootsuite/mastodon/pull/8762))
|
||||||
- Add user preference to always hide all media (#8569)
|
- Add user preference to always hide all media ([fvh-P](https://github.com/tootsuite/mastodon/pull/8569))
|
||||||
- Add `force_login` param to OAuth authorize page (#8655)
|
- Add `force_login` param to OAuth authorize page ([Gargron](https://github.com/tootsuite/mastodon/pull/8655))
|
||||||
- Add `tootctl accounts backup` (#8642, #8811)
|
- Add `tootctl accounts backup` ([Gargron](https://github.com/tootsuite/mastodon/pull/8642), [Gargron](https://github.com/tootsuite/mastodon/pull/8811))
|
||||||
- Add `tootctl accounts create` (#8642, #8811)
|
- Add `tootctl accounts create` ([Gargron](https://github.com/tootsuite/mastodon/pull/8642), [Gargron](https://github.com/tootsuite/mastodon/pull/8811))
|
||||||
- Add `tootctl accounts cull` (#8642, #8811)
|
- Add `tootctl accounts cull` ([Gargron](https://github.com/tootsuite/mastodon/pull/8642), [Gargron](https://github.com/tootsuite/mastodon/pull/8811))
|
||||||
- Add `tootctl accounts delete` (#8642, #8811)
|
- Add `tootctl accounts delete` ([Gargron](https://github.com/tootsuite/mastodon/pull/8642), [Gargron](https://github.com/tootsuite/mastodon/pull/8811))
|
||||||
- Add `tootctl accounts modify` (#8642, #8811)
|
- Add `tootctl accounts modify` ([Gargron](https://github.com/tootsuite/mastodon/pull/8642), [Gargron](https://github.com/tootsuite/mastodon/pull/8811))
|
||||||
- Add `tootctl accounts refresh` (#8642, #8811)
|
- Add `tootctl accounts refresh` ([Gargron](https://github.com/tootsuite/mastodon/pull/8642), [Gargron](https://github.com/tootsuite/mastodon/pull/8811))
|
||||||
- Add `tootctl feeds build` (#8642, #8811)
|
- Add `tootctl feeds build` ([Gargron](https://github.com/tootsuite/mastodon/pull/8642), [Gargron](https://github.com/tootsuite/mastodon/pull/8811))
|
||||||
- Add `tootctl feeds clear` (#8642, #8811)
|
- Add `tootctl feeds clear` ([Gargron](https://github.com/tootsuite/mastodon/pull/8642), [Gargron](https://github.com/tootsuite/mastodon/pull/8811))
|
||||||
- Add `tootctl settings registrations open` (#8642, #8811)
|
- Add `tootctl settings registrations open` ([Gargron](https://github.com/tootsuite/mastodon/pull/8642), [Gargron](https://github.com/tootsuite/mastodon/pull/8811))
|
||||||
- Add `tootctl settings registrations close` (#8642, #8811)
|
- Add `tootctl settings registrations close` ([Gargron](https://github.com/tootsuite/mastodon/pull/8642), [Gargron](https://github.com/tootsuite/mastodon/pull/8811))
|
||||||
- Add `min_id` param to REST API to support backwards pagination (#8736)
|
- Add `min_id` param to REST API to support backwards pagination ([Gargron](https://github.com/tootsuite/mastodon/pull/8736))
|
||||||
- Add a confirmation dialog when hitting reply and the compose box isn't empty (#8893)
|
- Add a confirmation dialog when hitting reply and the compose box isn't empty ([ThibG](https://github.com/tootsuite/mastodon/pull/8893))
|
||||||
- Add PostgreSQL disk space growth tracking in PGHero (#8906)
|
- Add PostgreSQL disk space growth tracking in PGHero ([Gargron](https://github.com/tootsuite/mastodon/pull/8906))
|
||||||
- Add button for disabling local account to report quick actions bar (#9024)
|
- Add button for disabling local account to report quick actions bar ([Gargron](https://github.com/tootsuite/mastodon/pull/9024))
|
||||||
- Add Czech language (#8594)
|
- Add Czech language ([Aditoo17](https://github.com/tootsuite/mastodon/pull/8594))
|
||||||
- Add `same-site` (`lax`) attribute to cookies (#8626)
|
- Add `same-site` (`lax`) attribute to cookies ([sorin-davidoi](https://github.com/tootsuite/mastodon/pull/8626))
|
||||||
- Add support for styled scrollbars in Firefox Nightly (#8653)
|
- Add support for styled scrollbars in Firefox Nightly ([sorin-davidoi](https://github.com/tootsuite/mastodon/pull/8653))
|
||||||
- Add highlight to the active tab in web UI profiles (#8673)
|
- Add highlight to the active tab in web UI profiles ([rhoio](https://github.com/tootsuite/mastodon/pull/8673))
|
||||||
- Add auto-focus for comment textarea in report modal (#8689)
|
- Add auto-focus for comment textarea in report modal ([ThibG](https://github.com/tootsuite/mastodon/pull/8689))
|
||||||
- Add auto-focus for emoji picker's search field (#8688)
|
- Add auto-focus for emoji picker's search field ([ThibG](https://github.com/tootsuite/mastodon/pull/8688))
|
||||||
- Add nginx and systemd templates to `dist/` directory (#8770)
|
- Add nginx and systemd templates to `dist/` directory ([Gargron](https://github.com/tootsuite/mastodon/pull/8770))
|
||||||
- Add support for `/.well-known/change-password` (#8828)
|
- Add support for `/.well-known/change-password` ([Gargron](https://github.com/tootsuite/mastodon/pull/8828))
|
||||||
- Add option to override FFMPEG binary path (#8855)
|
- Add option to override FFMPEG binary path ([sascha-sl](https://github.com/tootsuite/mastodon/pull/8855))
|
||||||
- Add `dns-prefetch` tag when using different host for assets or uploads (#8942)
|
- Add `dns-prefetch` tag when using different host for assets or uploads ([Gargron](https://github.com/tootsuite/mastodon/pull/8942))
|
||||||
- Add `description` meta tag (#8941)
|
- Add `description` meta tag ([Gargron](https://github.com/tootsuite/mastodon/pull/8941))
|
||||||
- Add `Content-Security-Policy` header (#8957)
|
- Add `Content-Security-Policy` header ([ThibG](https://github.com/tootsuite/mastodon/pull/8957))
|
||||||
- Add cache for the instance info API (#8765)
|
- Add cache for the instance info API ([ykzts](https://github.com/tootsuite/mastodon/pull/8765))
|
||||||
- Add suggested follows to search screen in mobile layout (#9010)
|
- Add suggested follows to search screen in mobile layout ([Gargron](https://github.com/tootsuite/mastodon/pull/9010))
|
||||||
- Add CORS header to `/.well-known/*` routes (#9083)
|
- Add CORS header to `/.well-known/*` routes ([BenLubar](https://github.com/tootsuite/mastodon/pull/9083))
|
||||||
- Add `card` attribute to statuses returned from REST API (#9120)
|
- Add `card` attribute to statuses returned from REST API ([Gargron](https://github.com/tootsuite/mastodon/pull/9120))
|
||||||
- Add in-stream link preview (#9120)
|
- Add in-stream link preview ([Gargron](https://github.com/tootsuite/mastodon/pull/9120))
|
||||||
- Add support for ActivityPub `Page` objects (#9121)
|
- Add support for ActivityPub `Page` objects ([mbajur](https://github.com/tootsuite/mastodon/pull/9121))
|
||||||
|
|
||||||
### Changed
|
### Changed
|
||||||
|
|
||||||
- Change forms design (#8703)
|
- Change forms design ([Gargron](https://github.com/tootsuite/mastodon/pull/8703))
|
||||||
- Change reports overview to group by target account (#8674)
|
- Change reports overview to group by target account ([Gargron](https://github.com/tootsuite/mastodon/pull/8674))
|
||||||
- Change web UI to show "read more" link on overly long in-stream statuses (#8205)
|
- Change web UI to show "read more" link on overly long in-stream statuses ([lanodan](https://github.com/tootsuite/mastodon/pull/8205))
|
||||||
- Change design of direct messages column (#8832, #9022)
|
- Change design of direct messages column ([Gargron](https://github.com/tootsuite/mastodon/pull/8832), [Gargron](https://github.com/tootsuite/mastodon/pull/9022))
|
||||||
- Change home timelines to exclude DMs (#8940)
|
- Change home timelines to exclude DMs ([Gargron](https://github.com/tootsuite/mastodon/pull/8940))
|
||||||
- Change list timelines to exclude all replies (#8683)
|
- Change list timelines to exclude all replies ([cbayerlein](https://github.com/tootsuite/mastodon/pull/8683))
|
||||||
- Change admin accounts UI default sort to most recent (#8813)
|
- Change admin accounts UI default sort to most recent ([Gargron](https://github.com/tootsuite/mastodon/pull/8813))
|
||||||
- Change documentation URL in the UI (#8898)
|
- Change documentation URL in the UI ([Gargron](https://github.com/tootsuite/mastodon/pull/8898))
|
||||||
- Change style of success and failure messages (#8973)
|
- Change style of success and failure messages ([Gargron](https://github.com/tootsuite/mastodon/pull/8973))
|
||||||
- Change DM filtering to always allow DMs from staff (#8993)
|
- Change DM filtering to always allow DMs from staff ([qguv](https://github.com/tootsuite/mastodon/pull/8993))
|
||||||
- Change recommended Ruby version to 2.5.3 (#9003)
|
- Change recommended Ruby version to 2.5.3 ([zunda](https://github.com/tootsuite/mastodon/pull/9003))
|
||||||
- Change docker-compose default to persist volumes in current directory (#9055)
|
- Change docker-compose default to persist volumes in current directory ([Gargron](https://github.com/tootsuite/mastodon/pull/9055))
|
||||||
- Change character counters on edit profile page to input length limit (#9100)
|
- Change character counters on edit profile page to input length limit ([Gargron](https://github.com/tootsuite/mastodon/pull/9100))
|
||||||
- Change notification filtering to always let through messages from staff (#9152)
|
- Change notification filtering to always let through messages from staff ([Gargron](https://github.com/tootsuite/mastodon/pull/9152))
|
||||||
- Change "hide boosts from user" function also hiding notifications about boosts (#9147)
|
- Change "hide boosts from user" function also hiding notifications about boosts ([ThibG](https://github.com/tootsuite/mastodon/pull/9147))
|
||||||
- Change CSS `detailed-status__wrapper` class actually wrap the detailed status (#8547)
|
- Change CSS `detailed-status__wrapper` class actually wrap the detailed status ([trwnh](https://github.com/tootsuite/mastodon/pull/8547))
|
||||||
|
|
||||||
### Deprecated
|
### Deprecated
|
||||||
|
|
||||||
- `GET /api/v1/timelines/direct` → `GET /api/v1/conversations` (#8832)
|
- `GET /api/v1/timelines/direct` → `GET /api/v1/conversations` ([Gargron](https://github.com/tootsuite/mastodon/pull/8832))
|
||||||
- `POST /api/v1/notifications/dismiss` → `POST /api/v1/notifications/:id/dismiss` (#8905)
|
- `POST /api/v1/notifications/dismiss` → `POST /api/v1/notifications/:id/dismiss` ([Gargron](https://github.com/tootsuite/mastodon/pull/8905))
|
||||||
- `GET /api/v1/statuses/:id/card` → `card` attributed included in status (#9120)
|
- `GET /api/v1/statuses/:id/card` → `card` attributed included in status ([Gargron](https://github.com/tootsuite/mastodon/pull/9120))
|
||||||
|
|
||||||
### Removed
|
### Removed
|
||||||
|
|
||||||
- Remove "on this device" label in column push settings (#8704)
|
- Remove "on this device" label in column push settings ([rhoio](https://github.com/tootsuite/mastodon/pull/8704))
|
||||||
- Remove rake tasks in favour of tootctl commands (#8675)
|
- Remove rake tasks in favour of tootctl commands ([Gargron](https://github.com/tootsuite/mastodon/pull/8675))
|
||||||
|
|
||||||
### Fixed
|
### Fixed
|
||||||
|
|
||||||
- Fix remote statuses using instance's default locale if no language given (#8861)
|
- Fix remote statuses using instance's default locale if no language given ([Kjwon15](https://github.com/tootsuite/mastodon/pull/8861))
|
||||||
- Fix streaming API not exiting when port or socket is unavailable (#9023)
|
- Fix streaming API not exiting when port or socket is unavailable ([Gargron](https://github.com/tootsuite/mastodon/pull/9023))
|
||||||
- Fix network calls being performed in database transaction in ActivityPub handler (#8951)
|
- Fix network calls being performed in database transaction in ActivityPub handler ([Gargron](https://github.com/tootsuite/mastodon/pull/8951))
|
||||||
- Fix dropdown arrow position (#8637)
|
- Fix dropdown arrow position ([ThibG](https://github.com/tootsuite/mastodon/pull/8637))
|
||||||
- Fix first element of dropdowns being focused even if not using keyboard (#8679)
|
- Fix first element of dropdowns being focused even if not using keyboard ([ThibG](https://github.com/tootsuite/mastodon/pull/8679))
|
||||||
- Fix tootctl requiring `bundle exec` invocation (#8619)
|
- Fix tootctl requiring `bundle exec` invocation ([abcang](https://github.com/tootsuite/mastodon/pull/8619))
|
||||||
- Fix public pages not using animation preference for avatars (#8614)
|
- Fix public pages not using animation preference for avatars ([renatolond](https://github.com/tootsuite/mastodon/pull/8614))
|
||||||
- Fix OEmbed/OpenGraph cards not understanding relative URLs (#8669)
|
- Fix OEmbed/OpenGraph cards not understanding relative URLs ([ThibG](https://github.com/tootsuite/mastodon/pull/8669))
|
||||||
- Fix some dark emojis not having a white outline (#8597)
|
- Fix some dark emojis not having a white outline ([ThibG](https://github.com/tootsuite/mastodon/pull/8597))
|
||||||
- Fix media description not being displayed in various media modals (#8678)
|
- Fix media description not being displayed in various media modals ([ThibG](https://github.com/tootsuite/mastodon/pull/8678))
|
||||||
- Fix generated URLs of desktop notifications missing base URL (#8758)
|
- Fix generated URLs of desktop notifications missing base URL ([GenbuHase](https://github.com/tootsuite/mastodon/pull/8758))
|
||||||
- Fix RTL styles (#8764, #8767, #8823, #8897, #9005, #9007, #9018, #9021, #9145, #9146)
|
- Fix RTL styles ([mabkenar](https://github.com/tootsuite/mastodon/pull/8764), [mabkenar](https://github.com/tootsuite/mastodon/pull/8767), [mabkenar](https://github.com/tootsuite/mastodon/pull/8823), [mabkenar](https://github.com/tootsuite/mastodon/pull/8897), [mabkenar](https://github.com/tootsuite/mastodon/pull/9005), [mabkenar](https://github.com/tootsuite/mastodon/pull/9007), [mabkenar](https://github.com/tootsuite/mastodon/pull/9018), [mabkenar](https://github.com/tootsuite/mastodon/pull/9021), [mabkenar](https://github.com/tootsuite/mastodon/pull/9145), [mabkenar](https://github.com/tootsuite/mastodon/pull/9146))
|
||||||
- Fix crash in streaming API when tag param missing (#8955)
|
- Fix crash in streaming API when tag param missing ([Gargron](https://github.com/tootsuite/mastodon/pull/8955))
|
||||||
- Fix hotkeys not working when no element is focused (#8998)
|
- Fix hotkeys not working when no element is focused ([ThibG](https://github.com/tootsuite/mastodon/pull/8998))
|
||||||
- Fix some hotkeys not working on detailed status view (#9006)
|
- Fix some hotkeys not working on detailed status view ([ThibG](https://github.com/tootsuite/mastodon/pull/9006))
|
||||||
- Fix og:url on status pages (#9047)
|
- Fix og:url on status pages ([ThibG](https://github.com/tootsuite/mastodon/pull/9047))
|
||||||
- Fix upload option buttons only being visible on hover (#9074)
|
- Fix upload option buttons only being visible on hover ([Gargron](https://github.com/tootsuite/mastodon/pull/9074))
|
||||||
- Fix tootctl not returning exit code 1 on wrong arguments (#9094)
|
- Fix tootctl not returning exit code 1 on wrong arguments ([sascha-sl](https://github.com/tootsuite/mastodon/pull/9094))
|
||||||
- Fix preview cards for appearing for profiles mentioned in toot (#6934, #9158)
|
- Fix preview cards for appearing for profiles mentioned in toot ([ThibG](https://github.com/tootsuite/mastodon/pull/6934), [ThibG](https://github.com/tootsuite/mastodon/pull/9158))
|
||||||
- Fix local accounts sometimes being duplicated as faux-remote (#9109)
|
- Fix local accounts sometimes being duplicated as faux-remote ([Gargron](https://github.com/tootsuite/mastodon/pull/9109))
|
||||||
- Fix emoji search when the shortcode has multiple separators (#9124)
|
- Fix emoji search when the shortcode has multiple separators ([ThibG](https://github.com/tootsuite/mastodon/pull/9124))
|
||||||
- Fix dropdowns sometimes being partially obscured by other elements (#9126)
|
- Fix dropdowns sometimes being partially obscured by other elements ([kedamaDQ](https://github.com/tootsuite/mastodon/pull/9126))
|
||||||
- Fix cache not updating when reply/boost/favourite counters or media sensitivity update (#9119)
|
- Fix cache not updating when reply/boost/favourite counters or media sensitivity update ([Gargron](https://github.com/tootsuite/mastodon/pull/9119))
|
||||||
- Fix empty display name precedence over username in web UI (#9163)
|
- Fix empty display name precedence over username in web UI ([Gargron](https://github.com/tootsuite/mastodon/pull/9163))
|
||||||
- Fix td instead of th in sessions table header (#9162)
|
- Fix td instead of th in sessions table header ([Gargron](https://github.com/tootsuite/mastodon/pull/9162))
|
||||||
- Fix handling of content types with profile (#9132)
|
- Fix handling of content types with profile ([valerauko](https://github.com/tootsuite/mastodon/pull/9132))
|
||||||
|
|
||||||
## [2.5.2] - 2018-10-12
|
## [2.5.2] - 2018-10-12
|
||||||
### Security
|
### Security
|
||||||
|
|
||||||
- Fix XSS vulnerability (#8959)
|
- Fix XSS vulnerability ([Gargron](https://github.com/tootsuite/mastodon/pull/8959))
|
||||||
|
|
||||||
## [2.5.1] - 2018-10-07
|
## [2.5.1] - 2018-10-07
|
||||||
### Fixed
|
### Fixed
|
||||||
|
|
||||||
- Fix database migrations for PostgreSQL below 9.5 (#8903)
|
- Fix database migrations for PostgreSQL below 9.5 ([Gargron](https://github.com/tootsuite/mastodon/pull/8903))
|
||||||
- Fix class autoloading issue in ActivityPub Create handler (#8820)
|
- Fix class autoloading issue in ActivityPub Create handler ([Gargron](https://github.com/tootsuite/mastodon/pull/8820))
|
||||||
- Fix cache statistics not being sent via statsd when statsd enabled (#8831)
|
- Fix cache statistics not being sent via statsd when statsd enabled ([ykzts](https://github.com/tootsuite/mastodon/pull/8831))
|
||||||
- Bump puma from 3.11.4 to 3.12.0 (#8883)
|
- Bump puma from 3.11.4 to 3.12.0 ([dependabot[bot]](https://github.com/tootsuite/mastodon/pull/8883))
|
||||||
|
|
||||||
### Security
|
### Security
|
||||||
|
|
||||||
- Fix some local images not having their EXIF metadata stripped on upload (#8714)
|
- Fix some local images not having their EXIF metadata stripped on upload ([ThibG](https://github.com/tootsuite/mastodon/pull/8714))
|
||||||
- Fix being able to enable a disabled relay via ActivityPub Accept handler (#8864)
|
- Fix being able to enable a disabled relay via ActivityPub Accept handler ([ThibG](https://github.com/tootsuite/mastodon/pull/8864))
|
||||||
- Bump nokogiri from 1.8.4 to 1.8.5 (#8881)
|
- Bump nokogiri from 1.8.4 to 1.8.5 ([dependabot[bot]](https://github.com/tootsuite/mastodon/pull/8881))
|
||||||
- Fix being able to report statuses not belonging to the reported account (#8916)
|
- Fix being able to report statuses not belonging to the reported account ([ThibG](https://github.com/tootsuite/mastodon/pull/8916))
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
FROM node:8.14.0-alpine as node
|
FROM node:8.15-alpine as node
|
||||||
FROM ruby:2.4.5-alpine3.8
|
FROM ruby:2.6-alpine3.8
|
||||||
|
|
||||||
LABEL maintainer="https://github.com/tootsuite/mastodon" \
|
LABEL maintainer="https://github.com/tootsuite/mastodon" \
|
||||||
description="Your self-hosted, globally interconnected microblogging community"
|
description="Your self-hosted, globally interconnected microblogging community"
|
||||||
|
|
6
Gemfile
6
Gemfile
|
@ -90,7 +90,7 @@ gem 'tzinfo-data', '~> 1.2018'
|
||||||
gem 'webpacker', '~> 3.5'
|
gem 'webpacker', '~> 3.5'
|
||||||
gem 'webpush'
|
gem 'webpush'
|
||||||
|
|
||||||
gem 'json-ld', '~> 2.2'
|
gem 'json-ld', '~> 3.0'
|
||||||
gem 'json-ld-preloaded', '~> 3.0'
|
gem 'json-ld-preloaded', '~> 3.0'
|
||||||
gem 'rdf-normalize', '~> 0.3'
|
gem 'rdf-normalize', '~> 0.3'
|
||||||
|
|
||||||
|
@ -128,8 +128,8 @@ group :development do
|
||||||
gem 'letter_opener', '~> 1.7'
|
gem 'letter_opener', '~> 1.7'
|
||||||
gem 'letter_opener_web', '~> 1.3'
|
gem 'letter_opener_web', '~> 1.3'
|
||||||
gem 'memory_profiler'
|
gem 'memory_profiler'
|
||||||
gem 'rubocop', '~> 0.62', require: false
|
gem 'rubocop', '~> 0.63', require: false
|
||||||
gem 'brakeman', '~> 4.3', require: false
|
gem 'brakeman', '~> 4.4', require: false
|
||||||
gem 'bundler-audit', '~> 0.6', require: false
|
gem 'bundler-audit', '~> 0.6', require: false
|
||||||
gem 'scss_lint', '~> 0.57', require: false
|
gem 'scss_lint', '~> 0.57', require: false
|
||||||
|
|
||||||
|
|
24
Gemfile.lock
24
Gemfile.lock
|
@ -100,14 +100,14 @@ GEM
|
||||||
debug_inspector (>= 0.0.1)
|
debug_inspector (>= 0.0.1)
|
||||||
bootsnap (1.3.2)
|
bootsnap (1.3.2)
|
||||||
msgpack (~> 1.0)
|
msgpack (~> 1.0)
|
||||||
brakeman (4.3.1)
|
brakeman (4.4.0)
|
||||||
browser (2.5.3)
|
browser (2.5.3)
|
||||||
builder (3.2.3)
|
builder (3.2.3)
|
||||||
bullet (5.9.0)
|
bullet (5.9.0)
|
||||||
activesupport (>= 3.0.0)
|
activesupport (>= 3.0.0)
|
||||||
uniform_notifier (~> 1.11)
|
uniform_notifier (~> 1.11)
|
||||||
bundler-audit (0.6.0)
|
bundler-audit (0.6.1)
|
||||||
bundler (~> 1.2)
|
bundler (>= 1.2.0, < 3)
|
||||||
thor (~> 0.18)
|
thor (~> 0.18)
|
||||||
byebug (10.0.2)
|
byebug (10.0.2)
|
||||||
capistrano (3.11.0)
|
capistrano (3.11.0)
|
||||||
|
@ -286,10 +286,10 @@ GEM
|
||||||
idn-ruby (0.1.0)
|
idn-ruby (0.1.0)
|
||||||
ipaddress (0.8.3)
|
ipaddress (0.8.3)
|
||||||
iso-639 (0.2.8)
|
iso-639 (0.2.8)
|
||||||
jaro_winkler (1.5.1)
|
jaro_winkler (1.5.2)
|
||||||
jmespath (1.4.0)
|
jmespath (1.4.0)
|
||||||
json (2.1.0)
|
json (2.1.0)
|
||||||
json-ld (2.2.1)
|
json-ld (3.0.2)
|
||||||
multi_json (~> 1.12)
|
multi_json (~> 1.12)
|
||||||
rdf (>= 2.2.8, < 4.0)
|
rdf (>= 2.2.8, < 4.0)
|
||||||
json-ld-preloaded (3.0.0)
|
json-ld-preloaded (3.0.0)
|
||||||
|
@ -365,7 +365,7 @@ GEM
|
||||||
concurrent-ruby (~> 1.0, >= 1.0.2)
|
concurrent-ruby (~> 1.0, >= 1.0.2)
|
||||||
sidekiq (>= 3.5)
|
sidekiq (>= 3.5)
|
||||||
statsd-ruby (~> 1.4, >= 1.4.0)
|
statsd-ruby (~> 1.4, >= 1.4.0)
|
||||||
oj (3.7.6)
|
oj (3.7.7)
|
||||||
omniauth (1.9.0)
|
omniauth (1.9.0)
|
||||||
hashie (>= 3.4.6, < 3.7.0)
|
hashie (>= 3.4.6, < 3.7.0)
|
||||||
rack (>= 1.6.2, < 3)
|
rack (>= 1.6.2, < 3)
|
||||||
|
@ -394,7 +394,7 @@ GEM
|
||||||
parallel (1.12.1)
|
parallel (1.12.1)
|
||||||
parallel_tests (2.27.1)
|
parallel_tests (2.27.1)
|
||||||
parallel
|
parallel
|
||||||
parser (2.5.3.0)
|
parser (2.6.0.0)
|
||||||
ast (~> 2.4.0)
|
ast (~> 2.4.0)
|
||||||
pastel (0.7.2)
|
pastel (0.7.2)
|
||||||
equatable (~> 0.5.0)
|
equatable (~> 0.5.0)
|
||||||
|
@ -473,7 +473,7 @@ GEM
|
||||||
rb-fsevent (0.10.3)
|
rb-fsevent (0.10.3)
|
||||||
rb-inotify (0.9.10)
|
rb-inotify (0.9.10)
|
||||||
ffi (>= 0.5.0, < 2)
|
ffi (>= 0.5.0, < 2)
|
||||||
rdf (3.0.7)
|
rdf (3.0.9)
|
||||||
hamster (~> 3.0)
|
hamster (~> 3.0)
|
||||||
link_header (~> 0.0, >= 0.0.8)
|
link_header (~> 0.0, >= 0.0.8)
|
||||||
rdf-normalize (0.3.3)
|
rdf-normalize (0.3.3)
|
||||||
|
@ -527,7 +527,7 @@ GEM
|
||||||
rspec-core (~> 3.0, >= 3.0.0)
|
rspec-core (~> 3.0, >= 3.0.0)
|
||||||
sidekiq (>= 2.4.0)
|
sidekiq (>= 2.4.0)
|
||||||
rspec-support (3.8.0)
|
rspec-support (3.8.0)
|
||||||
rubocop (0.62.0)
|
rubocop (0.63.0)
|
||||||
jaro_winkler (~> 1.5.1)
|
jaro_winkler (~> 1.5.1)
|
||||||
parallel (~> 1.10)
|
parallel (~> 1.10)
|
||||||
parser (>= 2.5, != 2.5.1.1)
|
parser (>= 2.5, != 2.5.1.1)
|
||||||
|
@ -663,7 +663,7 @@ DEPENDENCIES
|
||||||
better_errors (~> 2.5)
|
better_errors (~> 2.5)
|
||||||
binding_of_caller (~> 0.7)
|
binding_of_caller (~> 0.7)
|
||||||
bootsnap (~> 1.3)
|
bootsnap (~> 1.3)
|
||||||
brakeman (~> 4.3)
|
brakeman (~> 4.4)
|
||||||
browser
|
browser
|
||||||
bullet (~> 5.9)
|
bullet (~> 5.9)
|
||||||
bundler-audit (~> 0.6)
|
bundler-audit (~> 0.6)
|
||||||
|
@ -702,7 +702,7 @@ DEPENDENCIES
|
||||||
i18n-tasks (~> 0.9)
|
i18n-tasks (~> 0.9)
|
||||||
idn-ruby
|
idn-ruby
|
||||||
iso-639
|
iso-639
|
||||||
json-ld (~> 2.2)
|
json-ld (~> 3.0)
|
||||||
json-ld-preloaded (~> 3.0)
|
json-ld-preloaded (~> 3.0)
|
||||||
kaminari (~> 1.1)
|
kaminari (~> 1.1)
|
||||||
letter_opener (~> 1.7)
|
letter_opener (~> 1.7)
|
||||||
|
@ -749,7 +749,7 @@ DEPENDENCIES
|
||||||
rqrcode (~> 0.10)
|
rqrcode (~> 0.10)
|
||||||
rspec-rails (~> 3.8)
|
rspec-rails (~> 3.8)
|
||||||
rspec-sidekiq (~> 3.0)
|
rspec-sidekiq (~> 3.0)
|
||||||
rubocop (~> 0.62)
|
rubocop (~> 0.63)
|
||||||
sanitize (~> 5.0)
|
sanitize (~> 5.0)
|
||||||
scss_lint (~> 0.57)
|
scss_lint (~> 0.57)
|
||||||
sidekiq (~> 5.2)
|
sidekiq (~> 5.2)
|
||||||
|
|
|
@ -25,7 +25,7 @@ class Api::V1::Statuses::RebloggedByAccountsController < Api::BaseController
|
||||||
end
|
end
|
||||||
|
|
||||||
def paginated_statuses
|
def paginated_statuses
|
||||||
Status.where(reblog_of_id: @status.id).paginate_by_max_id(
|
Status.where(reblog_of_id: @status.id).where(visibility: [:public, :unlisted]).paginate_by_max_id(
|
||||||
limit_param(DEFAULT_ACCOUNTS_LIMIT),
|
limit_param(DEFAULT_ACCOUNTS_LIMIT),
|
||||||
params[:max_id],
|
params[:max_id],
|
||||||
params[:since_id]
|
params[:since_id]
|
||||||
|
|
|
@ -3,6 +3,8 @@
|
||||||
class TagsController < ApplicationController
|
class TagsController < ApplicationController
|
||||||
PAGE_SIZE = 20
|
PAGE_SIZE = 20
|
||||||
|
|
||||||
|
layout 'public'
|
||||||
|
|
||||||
before_action :set_body_classes
|
before_action :set_body_classes
|
||||||
before_action :set_instance_presenter
|
before_action :set_instance_presenter
|
||||||
|
|
||||||
|
|
|
@ -9,15 +9,23 @@ export default function DisplayName ({
|
||||||
account,
|
account,
|
||||||
className,
|
className,
|
||||||
inline,
|
inline,
|
||||||
|
localDomain,
|
||||||
}) {
|
}) {
|
||||||
const computedClass = classNames('display-name', { inline }, className);
|
const computedClass = classNames('display-name', { inline }, className);
|
||||||
|
|
||||||
|
if (!account) return null;
|
||||||
|
|
||||||
|
let acct = account.get('acct');
|
||||||
|
if (acct.indexOf('@') === -1 && localDomain) {
|
||||||
|
acct = `${acct}@${localDomain}`;
|
||||||
|
}
|
||||||
|
|
||||||
// The result.
|
// The result.
|
||||||
return account ? (
|
return account ? (
|
||||||
<span className={computedClass}>
|
<span className={computedClass}>
|
||||||
<bdi><strong className='display-name__html' dangerouslySetInnerHTML={{ __html: account.get('display_name_html') }} /></bdi>
|
<bdi><strong className='display-name__html' dangerouslySetInnerHTML={{ __html: account.get('display_name_html') }} /></bdi>
|
||||||
{inline ? ' ' : null}
|
{inline ? ' ' : null}
|
||||||
<span className='display-name__account'>@{account.get('acct')}</span>
|
<span className='display-name__account'>@{acct}</span>
|
||||||
</span>
|
</span>
|
||||||
) : null;
|
) : null;
|
||||||
}
|
}
|
||||||
|
@ -27,4 +35,5 @@ DisplayName.propTypes = {
|
||||||
account: ImmutablePropTypes.map,
|
account: ImmutablePropTypes.map,
|
||||||
className: PropTypes.string,
|
className: PropTypes.string,
|
||||||
inline: PropTypes.bool,
|
inline: PropTypes.bool,
|
||||||
|
localDomain: PropTypes.string,
|
||||||
};
|
};
|
||||||
|
|
|
@ -1,28 +1,32 @@
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
import { connect } from 'react-redux';
|
import { connect } from 'react-redux';
|
||||||
import PropTypes from 'prop-types';
|
import PropTypes from 'prop-types';
|
||||||
import StatusListContainer from 'flavours/glitch/features/ui/containers/status_list_container';
|
import ImmutablePropTypes from 'react-immutable-proptypes';
|
||||||
import { expandHashtagTimeline } from 'flavours/glitch/actions/timelines';
|
import { expandHashtagTimeline } from 'flavours/glitch/actions/timelines';
|
||||||
import Column from 'flavours/glitch/components/column';
|
|
||||||
import ColumnHeader from 'flavours/glitch/components/column_header';
|
|
||||||
import { connectHashtagStream } from 'flavours/glitch/actions/streaming';
|
import { connectHashtagStream } from 'flavours/glitch/actions/streaming';
|
||||||
|
import Masonry from 'react-masonry-infinite';
|
||||||
|
import { List as ImmutableList } from 'immutable';
|
||||||
|
import DetailedStatusContainer from 'flavours/glitch/features/status/containers/detailed_status_container';
|
||||||
|
import { debounce } from 'lodash';
|
||||||
|
import LoadingIndicator from 'flavours/glitch/components/loading_indicator';
|
||||||
|
|
||||||
@connect()
|
const mapStateToProps = (state, { hashtag }) => ({
|
||||||
export default class HashtagTimeline extends React.PureComponent {
|
statusIds: state.getIn(['timelines', `hashtag:${hashtag}`, 'items'], ImmutableList()),
|
||||||
|
isLoading: state.getIn(['timelines', `hashtag:${hashtag}`, 'isLoading'], false),
|
||||||
|
hasMore: state.getIn(['timelines', `hashtag:${hashtag}`, 'hasMore'], false),
|
||||||
|
});
|
||||||
|
|
||||||
|
export default @connect(mapStateToProps)
|
||||||
|
class HashtagTimeline extends React.PureComponent {
|
||||||
|
|
||||||
static propTypes = {
|
static propTypes = {
|
||||||
dispatch: PropTypes.func.isRequired,
|
dispatch: PropTypes.func.isRequired,
|
||||||
|
statusIds: ImmutablePropTypes.list.isRequired,
|
||||||
|
isLoading: PropTypes.bool.isRequired,
|
||||||
|
hasMore: PropTypes.bool.isRequired,
|
||||||
hashtag: PropTypes.string.isRequired,
|
hashtag: PropTypes.string.isRequired,
|
||||||
};
|
};
|
||||||
|
|
||||||
handleHeaderClick = () => {
|
|
||||||
this.column.scrollTop();
|
|
||||||
}
|
|
||||||
|
|
||||||
setRef = c => {
|
|
||||||
this.column = c;
|
|
||||||
}
|
|
||||||
|
|
||||||
componentDidMount () {
|
componentDidMount () {
|
||||||
const { dispatch, hashtag } = this.props;
|
const { dispatch, hashtag } = this.props;
|
||||||
|
|
||||||
|
@ -37,28 +41,52 @@ export default class HashtagTimeline extends React.PureComponent {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
handleLoadMore = maxId => {
|
handleLoadMore = () => {
|
||||||
this.props.dispatch(expandHashtagTimeline(this.props.hashtag, { maxId }));
|
const maxId = this.props.statusIds.last();
|
||||||
|
|
||||||
|
if (maxId) {
|
||||||
|
this.props.dispatch(expandHashtagTimeline(this.props.hashtag, { maxId }));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
setRef = c => {
|
||||||
|
this.masonry = c;
|
||||||
|
}
|
||||||
|
|
||||||
|
handleHeightChange = debounce(() => {
|
||||||
|
if (!this.masonry) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
this.masonry.forcePack();
|
||||||
|
}, 50)
|
||||||
|
|
||||||
render () {
|
render () {
|
||||||
const { hashtag } = this.props;
|
const { statusIds, hasMore, isLoading } = this.props;
|
||||||
|
|
||||||
|
const sizes = [
|
||||||
|
{ columns: 1, gutter: 0 },
|
||||||
|
{ mq: '415px', columns: 1, gutter: 10 },
|
||||||
|
{ mq: '640px', columns: 2, gutter: 10 },
|
||||||
|
{ mq: '960px', columns: 3, gutter: 10 },
|
||||||
|
{ mq: '1255px', columns: 3, gutter: 10 },
|
||||||
|
];
|
||||||
|
|
||||||
|
const loader = (isLoading && statusIds.isEmpty()) ? <LoadingIndicator key={0} /> : undefined;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Column ref={this.setRef}>
|
<Masonry ref={this.setRef} className='statuses-grid' hasMore={hasMore} loadMore={this.handleLoadMore} sizes={sizes} loader={loader}>
|
||||||
<ColumnHeader
|
{statusIds.map(statusId => (
|
||||||
icon='hashtag'
|
<div className='statuses-grid__item' key={statusId}>
|
||||||
title={hashtag}
|
<DetailedStatusContainer
|
||||||
onClick={this.handleHeaderClick}
|
id={statusId}
|
||||||
/>
|
compact
|
||||||
|
measureHeight
|
||||||
<StatusListContainer
|
onHeightChange={this.handleHeightChange}
|
||||||
trackScroll={false}
|
/>
|
||||||
scrollKey='standalone_hashtag_timeline'
|
</div>
|
||||||
timelineId={`hashtag:${hashtag}`}
|
)).toArray()}
|
||||||
onLoadMore={this.handleLoadMore}
|
</Masonry>
|
||||||
/>
|
|
||||||
</Column>
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -12,6 +12,8 @@ import Card from './card';
|
||||||
import ImmutablePureComponent from 'react-immutable-pure-component';
|
import ImmutablePureComponent from 'react-immutable-pure-component';
|
||||||
import Video from 'flavours/glitch/features/video';
|
import Video from 'flavours/glitch/features/video';
|
||||||
import VisibilityIcon from 'flavours/glitch/components/status_visibility_icon';
|
import VisibilityIcon from 'flavours/glitch/components/status_visibility_icon';
|
||||||
|
import scheduleIdleTask from 'flavours/glitch/util/schedule_idle_task';
|
||||||
|
import classNames from 'classnames';
|
||||||
|
|
||||||
export default class DetailedStatus extends ImmutablePureComponent {
|
export default class DetailedStatus extends ImmutablePureComponent {
|
||||||
|
|
||||||
|
@ -26,10 +28,18 @@ export default class DetailedStatus extends ImmutablePureComponent {
|
||||||
onOpenVideo: PropTypes.func.isRequired,
|
onOpenVideo: PropTypes.func.isRequired,
|
||||||
onToggleHidden: PropTypes.func.isRequired,
|
onToggleHidden: PropTypes.func.isRequired,
|
||||||
expanded: PropTypes.bool,
|
expanded: PropTypes.bool,
|
||||||
|
measureHeight: PropTypes.bool,
|
||||||
|
onHeightChange: PropTypes.func,
|
||||||
|
domain: PropTypes.string.isRequired,
|
||||||
|
compact: PropTypes.bool,
|
||||||
|
};
|
||||||
|
|
||||||
|
state = {
|
||||||
|
height: null,
|
||||||
};
|
};
|
||||||
|
|
||||||
handleAccountClick = (e) => {
|
handleAccountClick = (e) => {
|
||||||
if (e.button === 0 && !(e.ctrlKey || e.altKey || e.metaKey)) {
|
if (e.button === 0 && !(e.ctrlKey || e.altKey || e.metaKey) && this.context.router) {
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
this.context.router.history.push(`/accounts/${this.props.status.getIn(['account', 'id'])}`);
|
this.context.router.history.push(`/accounts/${this.props.status.getIn(['account', 'id'])}`);
|
||||||
}
|
}
|
||||||
|
@ -38,7 +48,7 @@ export default class DetailedStatus extends ImmutablePureComponent {
|
||||||
}
|
}
|
||||||
|
|
||||||
parseClick = (e, destination) => {
|
parseClick = (e, destination) => {
|
||||||
if (e.button === 0 && !(e.ctrlKey || e.altKey || e.metaKey)) {
|
if (e.button === 0 && !(e.ctrlKey || e.altKey || e.metaKey) && this.context.router) {
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
this.context.router.history.push(destination);
|
this.context.router.history.push(destination);
|
||||||
}
|
}
|
||||||
|
@ -50,15 +60,59 @@ export default class DetailedStatus extends ImmutablePureComponent {
|
||||||
this.props.onOpenVideo(media, startTime);
|
this.props.onOpenVideo(media, startTime);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
_measureHeight (heightJustChanged) {
|
||||||
|
if (this.props.measureHeight && this.node) {
|
||||||
|
scheduleIdleTask(() => this.node && this.setState({ height: Math.ceil(this.node.scrollHeight) + 1 }));
|
||||||
|
|
||||||
|
if (this.props.onHeightChange && heightJustChanged) {
|
||||||
|
this.props.onHeightChange();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
setRef = c => {
|
||||||
|
this.node = c;
|
||||||
|
this._measureHeight();
|
||||||
|
}
|
||||||
|
|
||||||
|
componentDidUpdate (prevProps, prevState) {
|
||||||
|
this._measureHeight(prevState.height !== this.state.height);
|
||||||
|
}
|
||||||
|
|
||||||
|
handleModalLink = e => {
|
||||||
|
e.preventDefault();
|
||||||
|
|
||||||
|
let href;
|
||||||
|
|
||||||
|
if (e.target.nodeName !== 'A') {
|
||||||
|
href = e.target.parentNode.href;
|
||||||
|
} else {
|
||||||
|
href = e.target.href;
|
||||||
|
}
|
||||||
|
|
||||||
|
window.open(href, 'mastodon-intent', 'width=445,height=600,resizable=no,menubar=no,status=no,scrollbars=yes');
|
||||||
|
}
|
||||||
|
|
||||||
render () {
|
render () {
|
||||||
const status = this.props.status.get('reblog') ? this.props.status.get('reblog') : this.props.status;
|
const status = this.props.status.get('reblog') ? this.props.status.get('reblog') : this.props.status;
|
||||||
const { expanded, onToggleHidden, settings } = this.props;
|
const { expanded, onToggleHidden, settings } = this.props;
|
||||||
|
const outerStyle = { boxSizing: 'border-box' };
|
||||||
|
const { compact } = this.props;
|
||||||
|
|
||||||
|
if (!status) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
let media = '';
|
let media = '';
|
||||||
let mediaIcon = null;
|
let mediaIcon = null;
|
||||||
let applicationLink = '';
|
let applicationLink = '';
|
||||||
let reblogLink = '';
|
let reblogLink = '';
|
||||||
let reblogIcon = 'retweet';
|
let reblogIcon = 'retweet';
|
||||||
|
let favouriteLink = '';
|
||||||
|
|
||||||
|
if (this.props.measureHeight) {
|
||||||
|
outerStyle.height = `${this.state.height}px`;
|
||||||
|
}
|
||||||
|
|
||||||
if (status.get('media_attachments').size > 0) {
|
if (status.get('media_attachments').size > 0) {
|
||||||
if (status.get('media_attachments').some(item => item.get('type') === 'unknown')) {
|
if (status.get('media_attachments').some(item => item.get('type') === 'unknown')) {
|
||||||
|
@ -108,41 +162,69 @@ export default class DetailedStatus extends ImmutablePureComponent {
|
||||||
|
|
||||||
if (status.get('visibility') === 'private') {
|
if (status.get('visibility') === 'private') {
|
||||||
reblogLink = <i className={`fa fa-${reblogIcon}`} />;
|
reblogLink = <i className={`fa fa-${reblogIcon}`} />;
|
||||||
|
} else if (this.context.router) {
|
||||||
|
reblogLink = (
|
||||||
|
<Link to={`/statuses/${status.get('id')}/reblogs`} className='detailed-status__link'>
|
||||||
|
<i className={`fa fa-${reblogIcon}`} />
|
||||||
|
<span className='detailed-status__reblogs'>
|
||||||
|
<FormattedNumber value={status.get('reblogs_count')} />
|
||||||
|
</span>
|
||||||
|
</Link>
|
||||||
|
);
|
||||||
} else {
|
} else {
|
||||||
reblogLink = (<Link to={`/statuses/${status.get('id')}/reblogs`} className='detailed-status__link'>
|
reblogLink = (
|
||||||
<i className={`fa fa-${reblogIcon}`} />
|
<a href={`/interact/${status.get('id')}?type=reblog`} className='detailed-status__link' onClick={this.handleModalLink}>
|
||||||
<span className='detailed-status__reblogs'>
|
<i className={`fa fa-${reblogIcon}`} />
|
||||||
<FormattedNumber value={status.get('reblogs_count')} />
|
<span className='detailed-status__reblogs'>
|
||||||
</span>
|
<FormattedNumber value={status.get('reblogs_count')} />
|
||||||
</Link>);
|
</span>
|
||||||
|
</a>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (this.context.router) {
|
||||||
|
favouriteLink = (
|
||||||
|
<Link to={`/statuses/${status.get('id')}/favourites`} className='detailed-status__link'>
|
||||||
|
<i className='fa fa-star' />
|
||||||
|
<span className='detailed-status__favorites'>
|
||||||
|
<FormattedNumber value={status.get('favourites_count')} />
|
||||||
|
</span>
|
||||||
|
</Link>
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
favouriteLink = (
|
||||||
|
<a href={`/interact/${status.get('id')}?type=favourite`} className='detailed-status__link' onClick={this.handleModalLink}>
|
||||||
|
<i className='fa fa-star' />
|
||||||
|
<span className='detailed-status__favorites'>
|
||||||
|
<FormattedNumber value={status.get('favourites_count')} />
|
||||||
|
</span>
|
||||||
|
</a>
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className='detailed-status' data-status-by={status.getIn(['account', 'acct'])}>
|
<div style={outerStyle}>
|
||||||
<a href={status.getIn(['account', 'url'])} onClick={this.handleAccountClick} className='detailed-status__display-name'>
|
<div ref={this.setRef} className={classNames('detailed-status', { compact })} data-status-by={status.getIn(['account', 'acct'])}>
|
||||||
<div className='detailed-status__display-avatar'><Avatar account={status.get('account')} size={48} /></div>
|
<a href={status.getIn(['account', 'url'])} onClick={this.handleAccountClick} className='detailed-status__display-name'>
|
||||||
<DisplayName account={status.get('account')} />
|
<div className='detailed-status__display-avatar'><Avatar account={status.get('account')} size={48} /></div>
|
||||||
</a>
|
<DisplayName account={status.get('account')} localDomain={this.props.domain} />
|
||||||
|
</a>
|
||||||
|
|
||||||
<StatusContent
|
<StatusContent
|
||||||
status={status}
|
status={status}
|
||||||
media={media}
|
media={media}
|
||||||
mediaIcon={mediaIcon}
|
mediaIcon={mediaIcon}
|
||||||
expanded={expanded}
|
expanded={expanded}
|
||||||
collapsed={false}
|
collapsed={false}
|
||||||
onExpandedToggle={onToggleHidden}
|
onExpandedToggle={onToggleHidden}
|
||||||
parseClick={this.parseClick}
|
parseClick={this.parseClick}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<div className='detailed-status__meta'>
|
<div className='detailed-status__meta'>
|
||||||
<a className='detailed-status__datetime' href={status.get('url')} target='_blank' rel='noopener'>
|
<a className='detailed-status__datetime' href={status.get('url')} target='_blank' rel='noopener'>
|
||||||
<FormattedDate value={new Date(status.get('created_at'))} hour12={false} year='numeric' month='short' day='2-digit' hour='2-digit' minute='2-digit' />
|
<FormattedDate value={new Date(status.get('created_at'))} hour12={false} year='numeric' month='short' day='2-digit' hour='2-digit' minute='2-digit' />
|
||||||
</a>{applicationLink} · {reblogLink} · <Link to={`/statuses/${status.get('id')}/favourites`} className='detailed-status__link'>
|
</a>{applicationLink} · {reblogLink} · {favouriteLink} · <VisibilityIcon visibility={status.get('visibility')} />
|
||||||
<i className='fa fa-star' />
|
</div>
|
||||||
<span className='detailed-status__favorites'>
|
|
||||||
<FormattedNumber value={status.get('favourites_count')} />
|
|
||||||
</span>
|
|
||||||
</Link> · <VisibilityIcon visibility={status.get('visibility')} />
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
|
|
@ -0,0 +1,173 @@
|
||||||
|
import React from 'react';
|
||||||
|
import { connect } from 'react-redux';
|
||||||
|
import DetailedStatus from '../components/detailed_status';
|
||||||
|
import { makeGetStatus } from 'flavours/glitch/selectors';
|
||||||
|
import {
|
||||||
|
replyCompose,
|
||||||
|
mentionCompose,
|
||||||
|
directCompose,
|
||||||
|
} from 'flavours/glitch/actions/compose';
|
||||||
|
import {
|
||||||
|
reblog,
|
||||||
|
favourite,
|
||||||
|
unreblog,
|
||||||
|
unfavourite,
|
||||||
|
pin,
|
||||||
|
unpin,
|
||||||
|
} from 'flavours/glitch/actions/interactions';
|
||||||
|
import { blockAccount } from 'flavours/glitch/actions/accounts';
|
||||||
|
import {
|
||||||
|
muteStatus,
|
||||||
|
unmuteStatus,
|
||||||
|
deleteStatus,
|
||||||
|
hideStatus,
|
||||||
|
revealStatus,
|
||||||
|
} from 'flavours/glitch/actions/statuses';
|
||||||
|
import { initMuteModal } from 'flavours/glitch/actions/mutes';
|
||||||
|
import { initReport } from 'flavours/glitch/actions/reports';
|
||||||
|
import { openModal } from 'flavours/glitch/actions/modal';
|
||||||
|
import { defineMessages, injectIntl, FormattedMessage } from 'react-intl';
|
||||||
|
import { boostModal, deleteModal } from 'flavours/glitch/util/initial_state';
|
||||||
|
import { showAlertForError } from 'flavours/glitch/actions/alerts';
|
||||||
|
|
||||||
|
const messages = defineMessages({
|
||||||
|
deleteConfirm: { id: 'confirmations.delete.confirm', defaultMessage: 'Delete' },
|
||||||
|
deleteMessage: { id: 'confirmations.delete.message', defaultMessage: 'Are you sure you want to delete this status?' },
|
||||||
|
redraftConfirm: { id: 'confirmations.redraft.confirm', defaultMessage: 'Delete & redraft' },
|
||||||
|
redraftMessage: { id: 'confirmations.redraft.message', defaultMessage: 'Are you sure you want to delete this status and re-draft it? Favourites and boosts will be lost, and replies to the original post will be orphaned.' },
|
||||||
|
blockConfirm: { id: 'confirmations.block.confirm', defaultMessage: 'Block' },
|
||||||
|
replyConfirm: { id: 'confirmations.reply.confirm', defaultMessage: 'Reply' },
|
||||||
|
replyMessage: { id: 'confirmations.reply.message', defaultMessage: 'Replying now will overwrite the message you are currently composing. Are you sure you want to proceed?' },
|
||||||
|
});
|
||||||
|
|
||||||
|
const makeMapStateToProps = () => {
|
||||||
|
const getStatus = makeGetStatus();
|
||||||
|
|
||||||
|
const mapStateToProps = (state, props) => ({
|
||||||
|
status: getStatus(state, props),
|
||||||
|
domain: state.getIn(['meta', 'domain']),
|
||||||
|
settings: state.get('local_settings'),
|
||||||
|
});
|
||||||
|
|
||||||
|
return mapStateToProps;
|
||||||
|
};
|
||||||
|
|
||||||
|
const mapDispatchToProps = (dispatch, { intl }) => ({
|
||||||
|
|
||||||
|
onReply (status, router) {
|
||||||
|
dispatch((_, getState) => {
|
||||||
|
let state = getState();
|
||||||
|
if (state.getIn(['compose', 'text']).trim().length !== 0) {
|
||||||
|
dispatch(openModal('CONFIRM', {
|
||||||
|
message: intl.formatMessage(messages.replyMessage),
|
||||||
|
confirm: intl.formatMessage(messages.replyConfirm),
|
||||||
|
onConfirm: () => dispatch(replyCompose(status, router)),
|
||||||
|
}));
|
||||||
|
} else {
|
||||||
|
dispatch(replyCompose(status, router));
|
||||||
|
}
|
||||||
|
});
|
||||||
|
},
|
||||||
|
|
||||||
|
onModalReblog (status) {
|
||||||
|
dispatch(reblog(status));
|
||||||
|
},
|
||||||
|
|
||||||
|
onReblog (status, e) {
|
||||||
|
if (status.get('reblogged')) {
|
||||||
|
dispatch(unreblog(status));
|
||||||
|
} else {
|
||||||
|
if (e.shiftKey || !boostModal) {
|
||||||
|
this.onModalReblog(status);
|
||||||
|
} else {
|
||||||
|
dispatch(openModal('BOOST', { status, onReblog: this.onModalReblog }));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
onFavourite (status) {
|
||||||
|
if (status.get('favourited')) {
|
||||||
|
dispatch(unfavourite(status));
|
||||||
|
} else {
|
||||||
|
dispatch(favourite(status));
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
onPin (status) {
|
||||||
|
if (status.get('pinned')) {
|
||||||
|
dispatch(unpin(status));
|
||||||
|
} else {
|
||||||
|
dispatch(pin(status));
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
onEmbed (status) {
|
||||||
|
dispatch(openModal('EMBED', {
|
||||||
|
url: status.get('url'),
|
||||||
|
onError: error => dispatch(showAlertForError(error)),
|
||||||
|
}));
|
||||||
|
},
|
||||||
|
|
||||||
|
onDelete (status, history, withRedraft = false) {
|
||||||
|
if (!deleteModal) {
|
||||||
|
dispatch(deleteStatus(status.get('id'), history, withRedraft));
|
||||||
|
} else {
|
||||||
|
dispatch(openModal('CONFIRM', {
|
||||||
|
message: intl.formatMessage(withRedraft ? messages.redraftMessage : messages.deleteMessage),
|
||||||
|
confirm: intl.formatMessage(withRedraft ? messages.redraftConfirm : messages.deleteConfirm),
|
||||||
|
onConfirm: () => dispatch(deleteStatus(status.get('id'), history, withRedraft)),
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
onDirect (account, router) {
|
||||||
|
dispatch(directCompose(account, router));
|
||||||
|
},
|
||||||
|
|
||||||
|
onMention (account, router) {
|
||||||
|
dispatch(mentionCompose(account, router));
|
||||||
|
},
|
||||||
|
|
||||||
|
onOpenMedia (media, index) {
|
||||||
|
dispatch(openModal('MEDIA', { media, index }));
|
||||||
|
},
|
||||||
|
|
||||||
|
onOpenVideo (media, time) {
|
||||||
|
dispatch(openModal('VIDEO', { media, time }));
|
||||||
|
},
|
||||||
|
|
||||||
|
onBlock (account) {
|
||||||
|
dispatch(openModal('CONFIRM', {
|
||||||
|
message: <FormattedMessage id='confirmations.block.message' defaultMessage='Are you sure you want to block {name}?' values={{ name: <strong>@{account.get('acct')}</strong> }} />,
|
||||||
|
confirm: intl.formatMessage(messages.blockConfirm),
|
||||||
|
onConfirm: () => dispatch(blockAccount(account.get('id'))),
|
||||||
|
}));
|
||||||
|
},
|
||||||
|
|
||||||
|
onReport (status) {
|
||||||
|
dispatch(initReport(status.get('account'), status));
|
||||||
|
},
|
||||||
|
|
||||||
|
onMute (account) {
|
||||||
|
dispatch(initMuteModal(account));
|
||||||
|
},
|
||||||
|
|
||||||
|
onMuteConversation (status) {
|
||||||
|
if (status.get('muted')) {
|
||||||
|
dispatch(unmuteStatus(status.get('id')));
|
||||||
|
} else {
|
||||||
|
dispatch(muteStatus(status.get('id')));
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
onToggleHidden (status) {
|
||||||
|
if (status.get('hidden')) {
|
||||||
|
dispatch(revealStatus(status.get('id')));
|
||||||
|
} else {
|
||||||
|
dispatch(hideStatus(status.get('id')));
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
});
|
||||||
|
|
||||||
|
export default injectIntl(connect(makeMapStateToProps, mapDispatchToProps)(DetailedStatus));
|
|
@ -100,6 +100,7 @@ const makeMapStateToProps = () => {
|
||||||
descendantsIds,
|
descendantsIds,
|
||||||
settings: state.get('local_settings'),
|
settings: state.get('local_settings'),
|
||||||
askReplyConfirmation: state.getIn(['local_settings', 'confirm_before_clearing_draft']) && state.getIn(['compose', 'text']).trim().length !== 0,
|
askReplyConfirmation: state.getIn(['local_settings', 'confirm_before_clearing_draft']) && state.getIn(['compose', 'text']).trim().length !== 0,
|
||||||
|
domain: state.getIn(['meta', 'domain']),
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -123,6 +124,7 @@ export default class Status extends ImmutablePureComponent {
|
||||||
descendantsIds: ImmutablePropTypes.list,
|
descendantsIds: ImmutablePropTypes.list,
|
||||||
intl: PropTypes.object.isRequired,
|
intl: PropTypes.object.isRequired,
|
||||||
askReplyConfirmation: PropTypes.bool,
|
askReplyConfirmation: PropTypes.bool,
|
||||||
|
domain: PropTypes.string.isRequired,
|
||||||
};
|
};
|
||||||
|
|
||||||
state = {
|
state = {
|
||||||
|
@ -417,7 +419,7 @@ export default class Status extends ImmutablePureComponent {
|
||||||
render () {
|
render () {
|
||||||
let ancestors, descendants;
|
let ancestors, descendants;
|
||||||
const { setExpansion } = this;
|
const { setExpansion } = this;
|
||||||
const { status, settings, ancestorsIds, descendantsIds, intl } = this.props;
|
const { status, settings, ancestorsIds, descendantsIds, intl, domain } = this.props;
|
||||||
const { fullscreen, isExpanded } = this.state;
|
const { fullscreen, isExpanded } = this.state;
|
||||||
|
|
||||||
if (status === null) {
|
if (status === null) {
|
||||||
|
@ -470,6 +472,7 @@ export default class Status extends ImmutablePureComponent {
|
||||||
onOpenMedia={this.handleOpenMedia}
|
onOpenMedia={this.handleOpenMedia}
|
||||||
expanded={isExpanded}
|
expanded={isExpanded}
|
||||||
onToggleHidden={this.handleExpandedToggle}
|
onToggleHidden={this.handleExpandedToggle}
|
||||||
|
domain={domain}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<ActionBar
|
<ActionBar
|
||||||
|
|
|
@ -30,7 +30,7 @@ const componentMap = {
|
||||||
'LIST': ListTimeline,
|
'LIST': ListTimeline,
|
||||||
};
|
};
|
||||||
|
|
||||||
const shouldHideFAB = path => path.match(/^\/statuses\//);
|
const shouldHideFAB = path => path.match(/^\/statuses\/|^\/search|^\/getting-started/);
|
||||||
|
|
||||||
const messages = defineMessages({
|
const messages = defineMessages({
|
||||||
publish: { id: 'compose_form.publish', defaultMessage: 'Toot' },
|
publish: { id: 'compose_form.publish', defaultMessage: 'Toot' },
|
||||||
|
|
|
@ -166,6 +166,7 @@ export default class UI extends React.Component {
|
||||||
}
|
}
|
||||||
|
|
||||||
handleDragOver = (e) => {
|
handleDragOver = (e) => {
|
||||||
|
if (this.dataTransferIsText(e.dataTransfer)) return false;
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
e.stopPropagation();
|
e.stopPropagation();
|
||||||
|
|
||||||
|
@ -179,6 +180,7 @@ export default class UI extends React.Component {
|
||||||
}
|
}
|
||||||
|
|
||||||
handleDrop = (e) => {
|
handleDrop = (e) => {
|
||||||
|
if (this.dataTransferIsText(e.dataTransfer)) return;
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
|
|
||||||
this.setState({ draggingOver: false });
|
this.setState({ draggingOver: false });
|
||||||
|
@ -202,6 +204,10 @@ export default class UI extends React.Component {
|
||||||
this.setState({ draggingOver: false });
|
this.setState({ draggingOver: false });
|
||||||
}
|
}
|
||||||
|
|
||||||
|
dataTransferIsText = (dataTransfer) => {
|
||||||
|
return (dataTransfer && Array.from(dataTransfer.types).includes('text/plain') && dataTransfer.items.length === 1);
|
||||||
|
}
|
||||||
|
|
||||||
closeUploadModal = () => {
|
closeUploadModal = () => {
|
||||||
this.setState({ draggingOver: false });
|
this.setState({ draggingOver: false });
|
||||||
}
|
}
|
||||||
|
|
|
@ -86,6 +86,14 @@ function main() {
|
||||||
if (parallaxComponents.length > 0 ) {
|
if (parallaxComponents.length > 0 ) {
|
||||||
new Rellax('.parallax', { speed: -1 });
|
new Rellax('.parallax', { speed: -1 });
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (document.body.classList.contains('with-modals')) {
|
||||||
|
const scrollbarWidth = window.innerWidth - document.documentElement.clientWidth;
|
||||||
|
const scrollbarWidthStyle = document.createElement('style');
|
||||||
|
scrollbarWidthStyle.id = 'scrollbar-width';
|
||||||
|
document.head.appendChild(scrollbarWidthStyle);
|
||||||
|
scrollbarWidthStyle.sheet.insertRule(`body.with-modals--active { margin-right: ${scrollbarWidth}px; }`, 0);
|
||||||
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -366,7 +366,7 @@ $small-breakpoint: 960px;
|
||||||
|
|
||||||
@media screen and (max-width: $column-breakpoint) {
|
@media screen and (max-width: $column-breakpoint) {
|
||||||
.grid {
|
.grid {
|
||||||
grid-template-columns: auto;
|
grid-template-columns: 100%;
|
||||||
|
|
||||||
.column-0 {
|
.column-0 {
|
||||||
display: block;
|
display: block;
|
||||||
|
|
|
@ -297,7 +297,7 @@
|
||||||
color: $primary-text-color;
|
color: $primary-text-color;
|
||||||
}
|
}
|
||||||
|
|
||||||
@media screen and (max-width: $no-gap-breakpoint) {
|
@media screen and (max-width: 550px) {
|
||||||
&.optional {
|
&.optional {
|
||||||
display: none;
|
display: none;
|
||||||
}
|
}
|
||||||
|
|
|
@ -419,7 +419,7 @@ code {
|
||||||
background: darken($ui-base-color, 10%) url("data:image/svg+xml;utf8,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 14.933 18.467' height='19.698' width='15.929'><path d='M3.467 14.967l-3.393-3.5H14.86l-3.392 3.5c-1.866 1.925-3.666 3.5-4 3.5-.335 0-2.135-1.575-4-3.5zm.266-11.234L7.467 0 11.2 3.733l3.733 3.734H0l3.733-3.734z' fill='#{hex-color(lighten($ui-base-color, 12%))}'/></svg>") no-repeat right 8px center / auto 16px;
|
background: darken($ui-base-color, 10%) url("data:image/svg+xml;utf8,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 14.933 18.467' height='19.698' width='15.929'><path d='M3.467 14.967l-3.393-3.5H14.86l-3.392 3.5c-1.866 1.925-3.666 3.5-4 3.5-.335 0-2.135-1.575-4-3.5zm.266-11.234L7.467 0 11.2 3.733l3.733 3.734H0l3.733-3.734z' fill='#{hex-color(lighten($ui-base-color, 12%))}'/></svg>") no-repeat right 8px center / auto 16px;
|
||||||
border: 1px solid darken($ui-base-color, 14%);
|
border: 1px solid darken($ui-base-color, 14%);
|
||||||
border-radius: 4px;
|
border-radius: 4px;
|
||||||
padding: 10px;
|
padding-left: 10px;
|
||||||
padding-right: 30px;
|
padding-right: 30px;
|
||||||
height: 41px;
|
height: 41px;
|
||||||
}
|
}
|
||||||
|
|
|
@ -425,3 +425,93 @@
|
||||||
border-radius: 0;
|
border-radius: 0;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
$maximum-width: 1235px;
|
||||||
|
$fluid-breakpoint: $maximum-width + 20px;
|
||||||
|
|
||||||
|
.statuses-grid {
|
||||||
|
min-height: 600px;
|
||||||
|
|
||||||
|
@media screen and (max-width: 640px) {
|
||||||
|
width: 100% !important; // Masonry layout is unnecessary at this width
|
||||||
|
}
|
||||||
|
|
||||||
|
&__item {
|
||||||
|
width: (960px - 20px) / 3;
|
||||||
|
|
||||||
|
@media screen and (max-width: $fluid-breakpoint) {
|
||||||
|
width: (940px - 20px) / 3;
|
||||||
|
}
|
||||||
|
|
||||||
|
@media screen and (max-width: 640px) {
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
@media screen and (max-width: $no-gap-breakpoint) {
|
||||||
|
width: 100vw;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.detailed-status {
|
||||||
|
border-radius: 4px;
|
||||||
|
|
||||||
|
@media screen and (max-width: $no-gap-breakpoint) {
|
||||||
|
border-top: 1px solid lighten($ui-base-color, 16%);
|
||||||
|
}
|
||||||
|
|
||||||
|
&.compact {
|
||||||
|
.detailed-status__meta {
|
||||||
|
margin-top: 15px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.status__content {
|
||||||
|
font-size: 15px;
|
||||||
|
line-height: 20px;
|
||||||
|
|
||||||
|
.emojione {
|
||||||
|
width: 20px;
|
||||||
|
height: 20px;
|
||||||
|
margin: -3px 0 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.status__content__spoiler-link {
|
||||||
|
line-height: 20px;
|
||||||
|
margin: 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.media-gallery,
|
||||||
|
.status-card,
|
||||||
|
.video-player {
|
||||||
|
margin-top: 15px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.notice-widget {
|
||||||
|
margin-bottom: 10px;
|
||||||
|
color: $darker-text-color;
|
||||||
|
|
||||||
|
p {
|
||||||
|
margin-bottom: 10px;
|
||||||
|
|
||||||
|
&:last-child {
|
||||||
|
margin-bottom: 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
a {
|
||||||
|
font-size: 14px;
|
||||||
|
line-height: 20px;
|
||||||
|
text-decoration: none;
|
||||||
|
font-weight: 500;
|
||||||
|
color: $ui-highlight-color;
|
||||||
|
|
||||||
|
&:hover,
|
||||||
|
&:focus,
|
||||||
|
&:active {
|
||||||
|
text-decoration: underline;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -4,5 +4,9 @@ export function start() {
|
||||||
require('font-awesome/css/font-awesome.css');
|
require('font-awesome/css/font-awesome.css');
|
||||||
require.context('../images/', true);
|
require.context('../images/', true);
|
||||||
|
|
||||||
Rails.start();
|
try {
|
||||||
|
Rails.start();
|
||||||
|
} catch (e) {
|
||||||
|
// If called twice
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
|
@ -1,15 +1,17 @@
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
import ImmutablePropTypes from 'react-immutable-proptypes';
|
import ImmutablePropTypes from 'react-immutable-proptypes';
|
||||||
|
import PropTypes from 'prop-types';
|
||||||
|
|
||||||
export default class DisplayName extends React.PureComponent {
|
export default class DisplayName extends React.PureComponent {
|
||||||
|
|
||||||
static propTypes = {
|
static propTypes = {
|
||||||
account: ImmutablePropTypes.map.isRequired,
|
account: ImmutablePropTypes.map.isRequired,
|
||||||
others: ImmutablePropTypes.list,
|
others: ImmutablePropTypes.list,
|
||||||
|
localDomain: PropTypes.string,
|
||||||
};
|
};
|
||||||
|
|
||||||
render () {
|
render () {
|
||||||
const { account, others } = this.props;
|
const { account, others, localDomain } = this.props;
|
||||||
const displayNameHtml = { __html: account.get('display_name_html') };
|
const displayNameHtml = { __html: account.get('display_name_html') };
|
||||||
|
|
||||||
let suffix;
|
let suffix;
|
||||||
|
@ -17,7 +19,13 @@ export default class DisplayName extends React.PureComponent {
|
||||||
if (others && others.size > 1) {
|
if (others && others.size > 1) {
|
||||||
suffix = `+${others.size}`;
|
suffix = `+${others.size}`;
|
||||||
} else {
|
} else {
|
||||||
suffix = <span className='display-name__account'>@{account.get('acct')}</span>;
|
let acct = account.get('acct');
|
||||||
|
|
||||||
|
if (acct.indexOf('@') === -1 && localDomain) {
|
||||||
|
acct = `${acct}@${localDomain}`;
|
||||||
|
}
|
||||||
|
|
||||||
|
suffix = <span className='display-name__account'>@{acct}</span>;
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
|
|
@ -77,7 +77,7 @@ class Status extends ImmutablePureComponent {
|
||||||
'account',
|
'account',
|
||||||
'muted',
|
'muted',
|
||||||
'hidden',
|
'hidden',
|
||||||
]
|
];
|
||||||
|
|
||||||
handleClick = () => {
|
handleClick = () => {
|
||||||
if (this.props.onClick) {
|
if (this.props.onClick) {
|
||||||
|
|
|
@ -1,28 +1,32 @@
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
import { connect } from 'react-redux';
|
import { connect } from 'react-redux';
|
||||||
import PropTypes from 'prop-types';
|
import PropTypes from 'prop-types';
|
||||||
import StatusListContainer from '../../ui/containers/status_list_container';
|
import ImmutablePropTypes from 'react-immutable-proptypes';
|
||||||
import { expandHashtagTimeline } from '../../../actions/timelines';
|
import { expandHashtagTimeline } from '../../../actions/timelines';
|
||||||
import Column from '../../../components/column';
|
|
||||||
import ColumnHeader from '../../../components/column_header';
|
|
||||||
import { connectHashtagStream } from '../../../actions/streaming';
|
import { connectHashtagStream } from '../../../actions/streaming';
|
||||||
|
import Masonry from 'react-masonry-infinite';
|
||||||
|
import { List as ImmutableList } from 'immutable';
|
||||||
|
import DetailedStatusContainer from '../../status/containers/detailed_status_container';
|
||||||
|
import { debounce } from 'lodash';
|
||||||
|
import LoadingIndicator from '../../../components/loading_indicator';
|
||||||
|
|
||||||
export default @connect()
|
const mapStateToProps = (state, { hashtag }) => ({
|
||||||
|
statusIds: state.getIn(['timelines', `hashtag:${hashtag}`, 'items'], ImmutableList()),
|
||||||
|
isLoading: state.getIn(['timelines', `hashtag:${hashtag}`, 'isLoading'], false),
|
||||||
|
hasMore: state.getIn(['timelines', `hashtag:${hashtag}`, 'hasMore'], false),
|
||||||
|
});
|
||||||
|
|
||||||
|
export default @connect(mapStateToProps)
|
||||||
class HashtagTimeline extends React.PureComponent {
|
class HashtagTimeline extends React.PureComponent {
|
||||||
|
|
||||||
static propTypes = {
|
static propTypes = {
|
||||||
dispatch: PropTypes.func.isRequired,
|
dispatch: PropTypes.func.isRequired,
|
||||||
|
statusIds: ImmutablePropTypes.list.isRequired,
|
||||||
|
isLoading: PropTypes.bool.isRequired,
|
||||||
|
hasMore: PropTypes.bool.isRequired,
|
||||||
hashtag: PropTypes.string.isRequired,
|
hashtag: PropTypes.string.isRequired,
|
||||||
};
|
};
|
||||||
|
|
||||||
handleHeaderClick = () => {
|
|
||||||
this.column.scrollTop();
|
|
||||||
}
|
|
||||||
|
|
||||||
setRef = c => {
|
|
||||||
this.column = c;
|
|
||||||
}
|
|
||||||
|
|
||||||
componentDidMount () {
|
componentDidMount () {
|
||||||
const { dispatch, hashtag } = this.props;
|
const { dispatch, hashtag } = this.props;
|
||||||
|
|
||||||
|
@ -37,28 +41,52 @@ class HashtagTimeline extends React.PureComponent {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
handleLoadMore = maxId => {
|
handleLoadMore = () => {
|
||||||
this.props.dispatch(expandHashtagTimeline(this.props.hashtag, { maxId }));
|
const maxId = this.props.statusIds.last();
|
||||||
|
|
||||||
|
if (maxId) {
|
||||||
|
this.props.dispatch(expandHashtagTimeline(this.props.hashtag, { maxId }));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
setRef = c => {
|
||||||
|
this.masonry = c;
|
||||||
|
}
|
||||||
|
|
||||||
|
handleHeightChange = debounce(() => {
|
||||||
|
if (!this.masonry) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
this.masonry.forcePack();
|
||||||
|
}, 50)
|
||||||
|
|
||||||
render () {
|
render () {
|
||||||
const { hashtag } = this.props;
|
const { statusIds, hasMore, isLoading } = this.props;
|
||||||
|
|
||||||
|
const sizes = [
|
||||||
|
{ columns: 1, gutter: 0 },
|
||||||
|
{ mq: '415px', columns: 1, gutter: 10 },
|
||||||
|
{ mq: '640px', columns: 2, gutter: 10 },
|
||||||
|
{ mq: '960px', columns: 3, gutter: 10 },
|
||||||
|
{ mq: '1255px', columns: 3, gutter: 10 },
|
||||||
|
];
|
||||||
|
|
||||||
|
const loader = (isLoading && statusIds.isEmpty()) ? <LoadingIndicator key={0} /> : undefined;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Column ref={this.setRef}>
|
<Masonry ref={this.setRef} className='statuses-grid' hasMore={hasMore} loadMore={this.handleLoadMore} sizes={sizes} loader={loader}>
|
||||||
<ColumnHeader
|
{statusIds.map(statusId => (
|
||||||
icon='hashtag'
|
<div className='statuses-grid__item' key={statusId}>
|
||||||
title={hashtag}
|
<DetailedStatusContainer
|
||||||
onClick={this.handleHeaderClick}
|
id={statusId}
|
||||||
/>
|
compact
|
||||||
|
measureHeight
|
||||||
<StatusListContainer
|
onHeightChange={this.handleHeightChange}
|
||||||
trackScroll={false}
|
/>
|
||||||
scrollKey='standalone_hashtag_timeline'
|
</div>
|
||||||
timelineId={`hashtag:${hashtag}`}
|
)).toArray()}
|
||||||
onLoadMore={this.handleLoadMore}
|
</Masonry>
|
||||||
/>
|
|
||||||
</Column>
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -11,6 +11,8 @@ import { FormattedDate, FormattedNumber } from 'react-intl';
|
||||||
import Card from './card';
|
import Card from './card';
|
||||||
import ImmutablePureComponent from 'react-immutable-pure-component';
|
import ImmutablePureComponent from 'react-immutable-pure-component';
|
||||||
import Video from '../../video';
|
import Video from '../../video';
|
||||||
|
import scheduleIdleTask from '../../ui/util/schedule_idle_task';
|
||||||
|
import classNames from 'classnames';
|
||||||
|
|
||||||
export default class DetailedStatus extends ImmutablePureComponent {
|
export default class DetailedStatus extends ImmutablePureComponent {
|
||||||
|
|
||||||
|
@ -23,10 +25,18 @@ export default class DetailedStatus extends ImmutablePureComponent {
|
||||||
onOpenMedia: PropTypes.func.isRequired,
|
onOpenMedia: PropTypes.func.isRequired,
|
||||||
onOpenVideo: PropTypes.func.isRequired,
|
onOpenVideo: PropTypes.func.isRequired,
|
||||||
onToggleHidden: PropTypes.func.isRequired,
|
onToggleHidden: PropTypes.func.isRequired,
|
||||||
|
measureHeight: PropTypes.bool,
|
||||||
|
onHeightChange: PropTypes.func,
|
||||||
|
domain: PropTypes.string.isRequired,
|
||||||
|
compact: PropTypes.bool,
|
||||||
|
};
|
||||||
|
|
||||||
|
state = {
|
||||||
|
height: null,
|
||||||
};
|
};
|
||||||
|
|
||||||
handleAccountClick = (e) => {
|
handleAccountClick = (e) => {
|
||||||
if (e.button === 0 && !(e.ctrlKey || e.metaKey)) {
|
if (e.button === 0 && !(e.ctrlKey || e.metaKey) && this.context.router) {
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
this.context.router.history.push(`/accounts/${this.props.status.getIn(['account', 'id'])}`);
|
this.context.router.history.push(`/accounts/${this.props.status.getIn(['account', 'id'])}`);
|
||||||
}
|
}
|
||||||
|
@ -42,13 +52,57 @@ export default class DetailedStatus extends ImmutablePureComponent {
|
||||||
this.props.onToggleHidden(this.props.status);
|
this.props.onToggleHidden(this.props.status);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
_measureHeight (heightJustChanged) {
|
||||||
|
if (this.props.measureHeight && this.node) {
|
||||||
|
scheduleIdleTask(() => this.node && this.setState({ height: Math.ceil(this.node.scrollHeight) + 1 }));
|
||||||
|
|
||||||
|
if (this.props.onHeightChange && heightJustChanged) {
|
||||||
|
this.props.onHeightChange();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
setRef = c => {
|
||||||
|
this.node = c;
|
||||||
|
this._measureHeight();
|
||||||
|
}
|
||||||
|
|
||||||
|
componentDidUpdate (prevProps, prevState) {
|
||||||
|
this._measureHeight(prevState.height !== this.state.height);
|
||||||
|
}
|
||||||
|
|
||||||
|
handleModalLink = e => {
|
||||||
|
e.preventDefault();
|
||||||
|
|
||||||
|
let href;
|
||||||
|
|
||||||
|
if (e.target.nodeName !== 'A') {
|
||||||
|
href = e.target.parentNode.href;
|
||||||
|
} else {
|
||||||
|
href = e.target.href;
|
||||||
|
}
|
||||||
|
|
||||||
|
window.open(href, 'mastodon-intent', 'width=445,height=600,resizable=no,menubar=no,status=no,scrollbars=yes');
|
||||||
|
}
|
||||||
|
|
||||||
render () {
|
render () {
|
||||||
const status = this.props.status.get('reblog') ? this.props.status.get('reblog') : this.props.status;
|
const status = this.props.status.get('reblog') ? this.props.status.get('reblog') : this.props.status;
|
||||||
|
const outerStyle = { boxSizing: 'border-box' };
|
||||||
|
const { compact } = this.props;
|
||||||
|
|
||||||
|
if (!status) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
let media = '';
|
let media = '';
|
||||||
let applicationLink = '';
|
let applicationLink = '';
|
||||||
let reblogLink = '';
|
let reblogLink = '';
|
||||||
let reblogIcon = 'retweet';
|
let reblogIcon = 'retweet';
|
||||||
|
let favouriteLink = '';
|
||||||
|
|
||||||
|
if (this.props.measureHeight) {
|
||||||
|
outerStyle.height = `${this.state.height}px`;
|
||||||
|
}
|
||||||
|
|
||||||
if (status.get('media_attachments').size > 0) {
|
if (status.get('media_attachments').size > 0) {
|
||||||
if (status.get('media_attachments').some(item => item.get('type') === 'unknown')) {
|
if (status.get('media_attachments').some(item => item.get('type') === 'unknown')) {
|
||||||
|
@ -95,35 +149,63 @@ export default class DetailedStatus extends ImmutablePureComponent {
|
||||||
|
|
||||||
if (status.get('visibility') === 'private') {
|
if (status.get('visibility') === 'private') {
|
||||||
reblogLink = <i className={`fa fa-${reblogIcon}`} />;
|
reblogLink = <i className={`fa fa-${reblogIcon}`} />;
|
||||||
|
} else if (this.context.router) {
|
||||||
|
reblogLink = (
|
||||||
|
<Link to={`/statuses/${status.get('id')}/reblogs`} className='detailed-status__link'>
|
||||||
|
<i className={`fa fa-${reblogIcon}`} />
|
||||||
|
<span className='detailed-status__reblogs'>
|
||||||
|
<FormattedNumber value={status.get('reblogs_count')} />
|
||||||
|
</span>
|
||||||
|
</Link>
|
||||||
|
);
|
||||||
} else {
|
} else {
|
||||||
reblogLink = (<Link to={`/statuses/${status.get('id')}/reblogs`} className='detailed-status__link'>
|
reblogLink = (
|
||||||
<i className={`fa fa-${reblogIcon}`} />
|
<a href={`/interact/${status.get('id')}?type=reblog`} className='detailed-status__link' onClick={this.handleModalLink}>
|
||||||
<span className='detailed-status__reblogs'>
|
<i className={`fa fa-${reblogIcon}`} />
|
||||||
<FormattedNumber value={status.get('reblogs_count')} />
|
<span className='detailed-status__reblogs'>
|
||||||
</span>
|
<FormattedNumber value={status.get('reblogs_count')} />
|
||||||
</Link>);
|
</span>
|
||||||
|
</a>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (this.context.router) {
|
||||||
|
favouriteLink = (
|
||||||
|
<Link to={`/statuses/${status.get('id')}/favourites`} className='detailed-status__link'>
|
||||||
|
<i className='fa fa-star' />
|
||||||
|
<span className='detailed-status__favorites'>
|
||||||
|
<FormattedNumber value={status.get('favourites_count')} />
|
||||||
|
</span>
|
||||||
|
</Link>
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
favouriteLink = (
|
||||||
|
<a href={`/interact/${status.get('id')}?type=favourite`} className='detailed-status__link' onClick={this.handleModalLink}>
|
||||||
|
<i className='fa fa-star' />
|
||||||
|
<span className='detailed-status__favorites'>
|
||||||
|
<FormattedNumber value={status.get('favourites_count')} />
|
||||||
|
</span>
|
||||||
|
</a>
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className='detailed-status'>
|
<div style={outerStyle}>
|
||||||
<a href={status.getIn(['account', 'url'])} onClick={this.handleAccountClick} className='detailed-status__display-name'>
|
<div ref={this.setRef} className={classNames('detailed-status', { compact })}>
|
||||||
<div className='detailed-status__display-avatar'><Avatar account={status.get('account')} size={48} /></div>
|
<a href={status.getIn(['account', 'url'])} onClick={this.handleAccountClick} className='detailed-status__display-name'>
|
||||||
<DisplayName account={status.get('account')} />
|
<div className='detailed-status__display-avatar'><Avatar account={status.get('account')} size={48} /></div>
|
||||||
</a>
|
<DisplayName account={status.get('account')} localDomain={this.props.domain} />
|
||||||
|
</a>
|
||||||
|
|
||||||
<StatusContent status={status} expanded={!status.get('hidden')} onExpandedToggle={this.handleExpandedToggle} />
|
<StatusContent status={status} expanded={!status.get('hidden')} onExpandedToggle={this.handleExpandedToggle} />
|
||||||
|
|
||||||
{media}
|
{media}
|
||||||
|
|
||||||
<div className='detailed-status__meta'>
|
<div className='detailed-status__meta'>
|
||||||
<a className='detailed-status__datetime' href={status.get('url')} target='_blank' rel='noopener'>
|
<a className='detailed-status__datetime' href={status.get('url')} target='_blank' rel='noopener'>
|
||||||
<FormattedDate value={new Date(status.get('created_at'))} hour12={false} year='numeric' month='short' day='2-digit' hour='2-digit' minute='2-digit' />
|
<FormattedDate value={new Date(status.get('created_at'))} hour12={false} year='numeric' month='short' day='2-digit' hour='2-digit' minute='2-digit' />
|
||||||
</a>{applicationLink} · {reblogLink} · <Link to={`/statuses/${status.get('id')}/favourites`} className='detailed-status__link'>
|
</a>{applicationLink} · {reblogLink} · {favouriteLink}
|
||||||
<i className='fa fa-star' />
|
</div>
|
||||||
<span className='detailed-status__favorites'>
|
|
||||||
<FormattedNumber value={status.get('favourites_count')} />
|
|
||||||
</span>
|
|
||||||
</Link>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
|
|
@ -0,0 +1,172 @@
|
||||||
|
import React from 'react';
|
||||||
|
import { connect } from 'react-redux';
|
||||||
|
import DetailedStatus from '../components/detailed_status';
|
||||||
|
import { makeGetStatus } from '../../../selectors';
|
||||||
|
import {
|
||||||
|
replyCompose,
|
||||||
|
mentionCompose,
|
||||||
|
directCompose,
|
||||||
|
} from '../../../actions/compose';
|
||||||
|
import {
|
||||||
|
reblog,
|
||||||
|
favourite,
|
||||||
|
unreblog,
|
||||||
|
unfavourite,
|
||||||
|
pin,
|
||||||
|
unpin,
|
||||||
|
} from '../../../actions/interactions';
|
||||||
|
import { blockAccount } from '../../../actions/accounts';
|
||||||
|
import {
|
||||||
|
muteStatus,
|
||||||
|
unmuteStatus,
|
||||||
|
deleteStatus,
|
||||||
|
hideStatus,
|
||||||
|
revealStatus,
|
||||||
|
} from '../../../actions/statuses';
|
||||||
|
import { initMuteModal } from '../../../actions/mutes';
|
||||||
|
import { initReport } from '../../../actions/reports';
|
||||||
|
import { openModal } from '../../../actions/modal';
|
||||||
|
import { defineMessages, injectIntl, FormattedMessage } from 'react-intl';
|
||||||
|
import { boostModal, deleteModal } from '../../../initial_state';
|
||||||
|
import { showAlertForError } from '../../../actions/alerts';
|
||||||
|
|
||||||
|
const messages = defineMessages({
|
||||||
|
deleteConfirm: { id: 'confirmations.delete.confirm', defaultMessage: 'Delete' },
|
||||||
|
deleteMessage: { id: 'confirmations.delete.message', defaultMessage: 'Are you sure you want to delete this status?' },
|
||||||
|
redraftConfirm: { id: 'confirmations.redraft.confirm', defaultMessage: 'Delete & redraft' },
|
||||||
|
redraftMessage: { id: 'confirmations.redraft.message', defaultMessage: 'Are you sure you want to delete this status and re-draft it? Favourites and boosts will be lost, and replies to the original post will be orphaned.' },
|
||||||
|
blockConfirm: { id: 'confirmations.block.confirm', defaultMessage: 'Block' },
|
||||||
|
replyConfirm: { id: 'confirmations.reply.confirm', defaultMessage: 'Reply' },
|
||||||
|
replyMessage: { id: 'confirmations.reply.message', defaultMessage: 'Replying now will overwrite the message you are currently composing. Are you sure you want to proceed?' },
|
||||||
|
});
|
||||||
|
|
||||||
|
const makeMapStateToProps = () => {
|
||||||
|
const getStatus = makeGetStatus();
|
||||||
|
|
||||||
|
const mapStateToProps = (state, props) => ({
|
||||||
|
status: getStatus(state, props),
|
||||||
|
domain: state.getIn(['meta', 'domain']),
|
||||||
|
});
|
||||||
|
|
||||||
|
return mapStateToProps;
|
||||||
|
};
|
||||||
|
|
||||||
|
const mapDispatchToProps = (dispatch, { intl }) => ({
|
||||||
|
|
||||||
|
onReply (status, router) {
|
||||||
|
dispatch((_, getState) => {
|
||||||
|
let state = getState();
|
||||||
|
if (state.getIn(['compose', 'text']).trim().length !== 0) {
|
||||||
|
dispatch(openModal('CONFIRM', {
|
||||||
|
message: intl.formatMessage(messages.replyMessage),
|
||||||
|
confirm: intl.formatMessage(messages.replyConfirm),
|
||||||
|
onConfirm: () => dispatch(replyCompose(status, router)),
|
||||||
|
}));
|
||||||
|
} else {
|
||||||
|
dispatch(replyCompose(status, router));
|
||||||
|
}
|
||||||
|
});
|
||||||
|
},
|
||||||
|
|
||||||
|
onModalReblog (status) {
|
||||||
|
dispatch(reblog(status));
|
||||||
|
},
|
||||||
|
|
||||||
|
onReblog (status, e) {
|
||||||
|
if (status.get('reblogged')) {
|
||||||
|
dispatch(unreblog(status));
|
||||||
|
} else {
|
||||||
|
if (e.shiftKey || !boostModal) {
|
||||||
|
this.onModalReblog(status);
|
||||||
|
} else {
|
||||||
|
dispatch(openModal('BOOST', { status, onReblog: this.onModalReblog }));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
onFavourite (status) {
|
||||||
|
if (status.get('favourited')) {
|
||||||
|
dispatch(unfavourite(status));
|
||||||
|
} else {
|
||||||
|
dispatch(favourite(status));
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
onPin (status) {
|
||||||
|
if (status.get('pinned')) {
|
||||||
|
dispatch(unpin(status));
|
||||||
|
} else {
|
||||||
|
dispatch(pin(status));
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
onEmbed (status) {
|
||||||
|
dispatch(openModal('EMBED', {
|
||||||
|
url: status.get('url'),
|
||||||
|
onError: error => dispatch(showAlertForError(error)),
|
||||||
|
}));
|
||||||
|
},
|
||||||
|
|
||||||
|
onDelete (status, history, withRedraft = false) {
|
||||||
|
if (!deleteModal) {
|
||||||
|
dispatch(deleteStatus(status.get('id'), history, withRedraft));
|
||||||
|
} else {
|
||||||
|
dispatch(openModal('CONFIRM', {
|
||||||
|
message: intl.formatMessage(withRedraft ? messages.redraftMessage : messages.deleteMessage),
|
||||||
|
confirm: intl.formatMessage(withRedraft ? messages.redraftConfirm : messages.deleteConfirm),
|
||||||
|
onConfirm: () => dispatch(deleteStatus(status.get('id'), history, withRedraft)),
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
onDirect (account, router) {
|
||||||
|
dispatch(directCompose(account, router));
|
||||||
|
},
|
||||||
|
|
||||||
|
onMention (account, router) {
|
||||||
|
dispatch(mentionCompose(account, router));
|
||||||
|
},
|
||||||
|
|
||||||
|
onOpenMedia (media, index) {
|
||||||
|
dispatch(openModal('MEDIA', { media, index }));
|
||||||
|
},
|
||||||
|
|
||||||
|
onOpenVideo (media, time) {
|
||||||
|
dispatch(openModal('VIDEO', { media, time }));
|
||||||
|
},
|
||||||
|
|
||||||
|
onBlock (account) {
|
||||||
|
dispatch(openModal('CONFIRM', {
|
||||||
|
message: <FormattedMessage id='confirmations.block.message' defaultMessage='Are you sure you want to block {name}?' values={{ name: <strong>@{account.get('acct')}</strong> }} />,
|
||||||
|
confirm: intl.formatMessage(messages.blockConfirm),
|
||||||
|
onConfirm: () => dispatch(blockAccount(account.get('id'))),
|
||||||
|
}));
|
||||||
|
},
|
||||||
|
|
||||||
|
onReport (status) {
|
||||||
|
dispatch(initReport(status.get('account'), status));
|
||||||
|
},
|
||||||
|
|
||||||
|
onMute (account) {
|
||||||
|
dispatch(initMuteModal(account));
|
||||||
|
},
|
||||||
|
|
||||||
|
onMuteConversation (status) {
|
||||||
|
if (status.get('muted')) {
|
||||||
|
dispatch(unmuteStatus(status.get('id')));
|
||||||
|
} else {
|
||||||
|
dispatch(muteStatus(status.get('id')));
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
onToggleHidden (status) {
|
||||||
|
if (status.get('hidden')) {
|
||||||
|
dispatch(revealStatus(status.get('id')));
|
||||||
|
} else {
|
||||||
|
dispatch(hideStatus(status.get('id')));
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
});
|
||||||
|
|
||||||
|
export default injectIntl(connect(makeMapStateToProps, mapDispatchToProps)(DetailedStatus));
|
|
@ -101,6 +101,7 @@ const makeMapStateToProps = () => {
|
||||||
ancestorsIds,
|
ancestorsIds,
|
||||||
descendantsIds,
|
descendantsIds,
|
||||||
askReplyConfirmation: state.getIn(['compose', 'text']).trim().length !== 0,
|
askReplyConfirmation: state.getIn(['compose', 'text']).trim().length !== 0,
|
||||||
|
domain: state.getIn(['meta', 'domain']),
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -123,6 +124,7 @@ class Status extends ImmutablePureComponent {
|
||||||
descendantsIds: ImmutablePropTypes.list,
|
descendantsIds: ImmutablePropTypes.list,
|
||||||
intl: PropTypes.object.isRequired,
|
intl: PropTypes.object.isRequired,
|
||||||
askReplyConfirmation: PropTypes.bool,
|
askReplyConfirmation: PropTypes.bool,
|
||||||
|
domain: PropTypes.string.isRequired,
|
||||||
};
|
};
|
||||||
|
|
||||||
state = {
|
state = {
|
||||||
|
@ -387,7 +389,7 @@ class Status extends ImmutablePureComponent {
|
||||||
|
|
||||||
render () {
|
render () {
|
||||||
let ancestors, descendants;
|
let ancestors, descendants;
|
||||||
const { shouldUpdateScroll, status, ancestorsIds, descendantsIds, intl } = this.props;
|
const { shouldUpdateScroll, status, ancestorsIds, descendantsIds, intl, domain } = this.props;
|
||||||
const { fullscreen } = this.state;
|
const { fullscreen } = this.state;
|
||||||
|
|
||||||
if (status === null) {
|
if (status === null) {
|
||||||
|
@ -438,6 +440,7 @@ class Status extends ImmutablePureComponent {
|
||||||
onOpenVideo={this.handleOpenVideo}
|
onOpenVideo={this.handleOpenVideo}
|
||||||
onOpenMedia={this.handleOpenMedia}
|
onOpenMedia={this.handleOpenMedia}
|
||||||
onToggleHidden={this.handleToggleHidden}
|
onToggleHidden={this.handleToggleHidden}
|
||||||
|
domain={domain}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<ActionBar
|
<ActionBar
|
||||||
|
|
|
@ -33,7 +33,7 @@ const messages = defineMessages({
|
||||||
publish: { id: 'compose_form.publish', defaultMessage: 'Toot' },
|
publish: { id: 'compose_form.publish', defaultMessage: 'Toot' },
|
||||||
});
|
});
|
||||||
|
|
||||||
const shouldHideFAB = path => path.match(/^\/statuses\//);
|
const shouldHideFAB = path => path.match(/^\/statuses\/|^\/search|^\/getting-started/);
|
||||||
|
|
||||||
export default @(component => injectIntl(component, { withRef: true }))
|
export default @(component => injectIntl(component, { withRef: true }))
|
||||||
class ColumnsArea extends ImmutablePureComponent {
|
class ColumnsArea extends ImmutablePureComponent {
|
||||||
|
|
|
@ -243,6 +243,7 @@ class UI extends React.PureComponent {
|
||||||
}
|
}
|
||||||
|
|
||||||
handleDragOver = (e) => {
|
handleDragOver = (e) => {
|
||||||
|
if (this.dataTransferIsText(e.dataTransfer)) return false;
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
e.stopPropagation();
|
e.stopPropagation();
|
||||||
|
|
||||||
|
@ -256,6 +257,7 @@ class UI extends React.PureComponent {
|
||||||
}
|
}
|
||||||
|
|
||||||
handleDrop = (e) => {
|
handleDrop = (e) => {
|
||||||
|
if (this.dataTransferIsText(e.dataTransfer)) return;
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
|
|
||||||
this.setState({ draggingOver: false });
|
this.setState({ draggingOver: false });
|
||||||
|
@ -279,6 +281,10 @@ class UI extends React.PureComponent {
|
||||||
this.setState({ draggingOver: false });
|
this.setState({ draggingOver: false });
|
||||||
}
|
}
|
||||||
|
|
||||||
|
dataTransferIsText = (dataTransfer) => {
|
||||||
|
return (dataTransfer && Array.from(dataTransfer.types).includes('text/plain') && dataTransfer.items.length === 1);
|
||||||
|
}
|
||||||
|
|
||||||
closeUploadModal = () => {
|
closeUploadModal = () => {
|
||||||
this.setState({ draggingOver: false });
|
this.setState({ draggingOver: false });
|
||||||
}
|
}
|
||||||
|
|
|
@ -8,7 +8,7 @@
|
||||||
"account.disclaimer_full": "La informació següent pot reflectir incompleta el perfil de l'usuari.",
|
"account.disclaimer_full": "La informació següent pot reflectir incompleta el perfil de l'usuari.",
|
||||||
"account.domain_blocked": "Domini ocult",
|
"account.domain_blocked": "Domini ocult",
|
||||||
"account.edit_profile": "Editar el perfil",
|
"account.edit_profile": "Editar el perfil",
|
||||||
"account.endorse": "Característica del perfil",
|
"account.endorse": "Recomanar en el teu perfil",
|
||||||
"account.follow": "Segueix",
|
"account.follow": "Segueix",
|
||||||
"account.followers": "Seguidors",
|
"account.followers": "Seguidors",
|
||||||
"account.followers.empty": "Encara ningú no segueix aquest usuari.",
|
"account.followers.empty": "Encara ningú no segueix aquest usuari.",
|
||||||
|
|
|
@ -84,7 +84,7 @@
|
||||||
"confirmations.block.confirm": "Blocio",
|
"confirmations.block.confirm": "Blocio",
|
||||||
"confirmations.block.message": "Ydych chi'n sicr eich bod eisiau blocio {name}?",
|
"confirmations.block.message": "Ydych chi'n sicr eich bod eisiau blocio {name}?",
|
||||||
"confirmations.delete.confirm": "Dileu",
|
"confirmations.delete.confirm": "Dileu",
|
||||||
"confirmations.delete.message": "Ydych chi'n sicr eich bod eisiau dileu y statws hwn?",
|
"confirmations.delete.message": "Ydych chi'n sicr eich bod eisiau dileu y tŵt hwn?",
|
||||||
"confirmations.delete_list.confirm": "Dileu",
|
"confirmations.delete_list.confirm": "Dileu",
|
||||||
"confirmations.delete_list.message": "Ydych chi'n sicr eich bod eisiau dileu y rhestr hwn am byth?",
|
"confirmations.delete_list.message": "Ydych chi'n sicr eich bod eisiau dileu y rhestr hwn am byth?",
|
||||||
"confirmations.domain_block.confirm": "Cuddio parth cyfan",
|
"confirmations.domain_block.confirm": "Cuddio parth cyfan",
|
||||||
|
@ -92,12 +92,12 @@
|
||||||
"confirmations.mute.confirm": "Tawelu",
|
"confirmations.mute.confirm": "Tawelu",
|
||||||
"confirmations.mute.message": "Ydych chi'n sicr eich bod am ddistewi {name}?",
|
"confirmations.mute.message": "Ydych chi'n sicr eich bod am ddistewi {name}?",
|
||||||
"confirmations.redraft.confirm": "Dileu & ailddrafftio",
|
"confirmations.redraft.confirm": "Dileu & ailddrafftio",
|
||||||
"confirmations.redraft.message": "Ydych chi'n siwr eich bod eisiau dileu y statws hwn a'i ailddrafftio? Bydd ffefrynnau a bwstiau'n cael ei colli, a bydd ymatebion i'r statws gwreiddiol yn cael eu hamddifadu.",
|
"confirmations.redraft.message": "Ydych chi'n siwr eich bod eisiau dileu y tŵt hwn a'i ailddrafftio? Bydd ffefrynnau a bwstiau'n cael ei colli, a bydd ymatebion i'r tŵt gwreiddiol yn cael eu hamddifadu.",
|
||||||
"confirmations.reply.confirm": "Ateb",
|
"confirmations.reply.confirm": "Ateb",
|
||||||
"confirmations.reply.message": "Bydd ateb nawr yn cymryd lle y neges yr ydych yn cyfansoddi ar hyn o bryd. Ydych chi'n sicr yr ydych am barhau?",
|
"confirmations.reply.message": "Bydd ateb nawr yn cymryd lle y neges yr ydych yn cyfansoddi ar hyn o bryd. Ydych chi'n sicr yr ydych am barhau?",
|
||||||
"confirmations.unfollow.confirm": "Dad-ddilynwch",
|
"confirmations.unfollow.confirm": "Dad-ddilynwch",
|
||||||
"confirmations.unfollow.message": "Ydych chi'n sicr eich bod am ddad-ddilyn {name}?",
|
"confirmations.unfollow.message": "Ydych chi'n sicr eich bod am ddad-ddilyn {name}?",
|
||||||
"embed.instructions": "Mewnblannwch y statws hwn ar eich gwefan drwy gopïo'r côd isod.",
|
"embed.instructions": "Mewnblannwch y tŵt hwn ar eich gwefan drwy gopïo'r côd isod.",
|
||||||
"embed.preview": "Dyma sut olwg fydd arno:",
|
"embed.preview": "Dyma sut olwg fydd arno:",
|
||||||
"emoji_button.activity": "Gweithgarwch",
|
"emoji_button.activity": "Gweithgarwch",
|
||||||
"emoji_button.custom": "Unigryw",
|
"emoji_button.custom": "Unigryw",
|
||||||
|
@ -169,12 +169,12 @@
|
||||||
"keyboard_shortcuts.back": "i lywio nôl",
|
"keyboard_shortcuts.back": "i lywio nôl",
|
||||||
"keyboard_shortcuts.blocked": "i agor rhestr defnyddwyr a flociwyd",
|
"keyboard_shortcuts.blocked": "i agor rhestr defnyddwyr a flociwyd",
|
||||||
"keyboard_shortcuts.boost": "i fŵstio",
|
"keyboard_shortcuts.boost": "i fŵstio",
|
||||||
"keyboard_shortcuts.column": "i ffocysu statws yn un o'r colofnau",
|
"keyboard_shortcuts.column": "i ffocysu tŵt yn un o'r colofnau",
|
||||||
"keyboard_shortcuts.compose": "i ffocysu yr ardal cyfansoddi testun",
|
"keyboard_shortcuts.compose": "i ffocysu yr ardal cyfansoddi testun",
|
||||||
"keyboard_shortcuts.description": "Disgrifiad",
|
"keyboard_shortcuts.description": "Disgrifiad",
|
||||||
"keyboard_shortcuts.direct": "i agor colofn negeseuon preifat",
|
"keyboard_shortcuts.direct": "i agor colofn negeseuon preifat",
|
||||||
"keyboard_shortcuts.down": "i symud lawr yn y rhestr",
|
"keyboard_shortcuts.down": "i symud lawr yn y rhestr",
|
||||||
"keyboard_shortcuts.enter": "i agor statws",
|
"keyboard_shortcuts.enter": "i agor tŵt",
|
||||||
"keyboard_shortcuts.favourite": "i hoffi",
|
"keyboard_shortcuts.favourite": "i hoffi",
|
||||||
"keyboard_shortcuts.favourites": "i agor rhestr hoffi",
|
"keyboard_shortcuts.favourites": "i agor rhestr hoffi",
|
||||||
"keyboard_shortcuts.federated": "i agor ffrwd y ffederasiwn",
|
"keyboard_shortcuts.federated": "i agor ffrwd y ffederasiwn",
|
||||||
|
@ -234,10 +234,10 @@
|
||||||
"navigation_bar.preferences": "Dewisiadau",
|
"navigation_bar.preferences": "Dewisiadau",
|
||||||
"navigation_bar.public_timeline": "Ffrwd y ffederasiwn",
|
"navigation_bar.public_timeline": "Ffrwd y ffederasiwn",
|
||||||
"navigation_bar.security": "Diogelwch",
|
"navigation_bar.security": "Diogelwch",
|
||||||
"notification.favourite": "hoffodd {name} eich statws",
|
"notification.favourite": "hoffodd {name} eich tŵt",
|
||||||
"notification.follow": "dilynodd {name} chi",
|
"notification.follow": "dilynodd {name} chi",
|
||||||
"notification.mention": "Soniodd {name} amdanoch chi",
|
"notification.mention": "Soniodd {name} amdanoch chi",
|
||||||
"notification.reblog": "{name} boosted your status",
|
"notification.reblog": "Hysbysebodd {name} eich tŵt",
|
||||||
"notifications.clear": "Clirio hysbysiadau",
|
"notifications.clear": "Clirio hysbysiadau",
|
||||||
"notifications.clear_confirmation": "Ydych chi'n sicr eich bod am glirio'ch holl hysbysiadau am byth?",
|
"notifications.clear_confirmation": "Ydych chi'n sicr eich bod am glirio'ch holl hysbysiadau am byth?",
|
||||||
"notifications.column_settings.alert": "Hysbysiadau bwrdd gwaith",
|
"notifications.column_settings.alert": "Hysbysiadau bwrdd gwaith",
|
||||||
|
@ -257,7 +257,7 @@
|
||||||
"notifications.filter.follows": "Yn dilyn",
|
"notifications.filter.follows": "Yn dilyn",
|
||||||
"notifications.filter.mentions": "Mentions",
|
"notifications.filter.mentions": "Mentions",
|
||||||
"notifications.group": "{count} o hysbysiadau",
|
"notifications.group": "{count} o hysbysiadau",
|
||||||
"privacy.change": "Addasu preifatrwdd y statws",
|
"privacy.change": "Addasu preifatrwdd y tŵt",
|
||||||
"privacy.direct.long": "Cyhoeddi i'r defnyddwyr sy'n cael eu crybwyll yn unig",
|
"privacy.direct.long": "Cyhoeddi i'r defnyddwyr sy'n cael eu crybwyll yn unig",
|
||||||
"privacy.direct.short": "Uniongyrchol",
|
"privacy.direct.short": "Uniongyrchol",
|
||||||
"privacy.private.long": "Cyhoeddi i ddilynwyr yn unig",
|
"privacy.private.long": "Cyhoeddi i ddilynwyr yn unig",
|
||||||
|
@ -284,7 +284,7 @@
|
||||||
"search_popout.search_format": "Fformat chwilio uwch",
|
"search_popout.search_format": "Fformat chwilio uwch",
|
||||||
"search_popout.tips.full_text": "Mae testun syml yn dychwelyd tŵtiau yr ydych wedi ysgrifennu, hoffi, wedi'u bŵstio, neu wedi'ch crybwyll ynddynt, ynghyd a chyfateb a enwau defnyddwyr, enwau arddangos ac hashnodau.",
|
"search_popout.tips.full_text": "Mae testun syml yn dychwelyd tŵtiau yr ydych wedi ysgrifennu, hoffi, wedi'u bŵstio, neu wedi'ch crybwyll ynddynt, ynghyd a chyfateb a enwau defnyddwyr, enwau arddangos ac hashnodau.",
|
||||||
"search_popout.tips.hashtag": "hashnod",
|
"search_popout.tips.hashtag": "hashnod",
|
||||||
"search_popout.tips.status": "statws",
|
"search_popout.tips.status": "tŵt",
|
||||||
"search_popout.tips.text": "Mae testun syml yn dychwelyd enwau arddangos, enwau defnyddwyr a hashnodau sy'n cyfateb",
|
"search_popout.tips.text": "Mae testun syml yn dychwelyd enwau arddangos, enwau defnyddwyr a hashnodau sy'n cyfateb",
|
||||||
"search_popout.tips.user": "defnyddiwr",
|
"search_popout.tips.user": "defnyddiwr",
|
||||||
"search_results.accounts": "Pobl",
|
"search_results.accounts": "Pobl",
|
||||||
|
@ -293,7 +293,7 @@
|
||||||
"search_results.total": "{count, number} {count, plural, one {result} other {results}}",
|
"search_results.total": "{count, number} {count, plural, one {result} other {results}}",
|
||||||
"standalone.public_title": "Golwg tu fewn...",
|
"standalone.public_title": "Golwg tu fewn...",
|
||||||
"status.admin_account": "Open moderation interface for @{name}",
|
"status.admin_account": "Open moderation interface for @{name}",
|
||||||
"status.admin_status": "Open this status in the moderation interface",
|
"status.admin_status": "Open this tŵt in the moderation interface",
|
||||||
"status.block": "Blocio @{name}",
|
"status.block": "Blocio @{name}",
|
||||||
"status.cancel_reblog_private": "Dadfŵstio",
|
"status.cancel_reblog_private": "Dadfŵstio",
|
||||||
"status.cannot_reblog": "Ni ellir sbarduno'r tŵt hwn",
|
"status.cannot_reblog": "Ni ellir sbarduno'r tŵt hwn",
|
||||||
|
@ -309,7 +309,7 @@
|
||||||
"status.more": "Mwy",
|
"status.more": "Mwy",
|
||||||
"status.mute": "Tawelu @{name}",
|
"status.mute": "Tawelu @{name}",
|
||||||
"status.mute_conversation": "Tawelu sgwrs",
|
"status.mute_conversation": "Tawelu sgwrs",
|
||||||
"status.open": "Ehangu'r statws hwn",
|
"status.open": "Ehangu'r tŵt hwn",
|
||||||
"status.pin": "Pinio ar y proffil",
|
"status.pin": "Pinio ar y proffil",
|
||||||
"status.pinned": "Pinio tŵt",
|
"status.pinned": "Pinio tŵt",
|
||||||
"status.read_more": "Darllen mwy",
|
"status.read_more": "Darllen mwy",
|
||||||
|
|
|
@ -1950,6 +1950,43 @@
|
||||||
],
|
],
|
||||||
"path": "app/javascript/mastodon/features/status/components/action_bar.json"
|
"path": "app/javascript/mastodon/features/status/components/action_bar.json"
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"descriptors": [
|
||||||
|
{
|
||||||
|
"defaultMessage": "Delete",
|
||||||
|
"id": "confirmations.delete.confirm"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"defaultMessage": "Are you sure you want to delete this status?",
|
||||||
|
"id": "confirmations.delete.message"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"defaultMessage": "Delete & redraft",
|
||||||
|
"id": "confirmations.redraft.confirm"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"defaultMessage": "Are you sure you want to delete this status and re-draft it? Favourites and boosts will be lost, and replies to the original post will be orphaned.",
|
||||||
|
"id": "confirmations.redraft.message"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"defaultMessage": "Block",
|
||||||
|
"id": "confirmations.block.confirm"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"defaultMessage": "Reply",
|
||||||
|
"id": "confirmations.reply.confirm"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"defaultMessage": "Replying now will overwrite the message you are currently composing. Are you sure you want to proceed?",
|
||||||
|
"id": "confirmations.reply.message"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"defaultMessage": "Are you sure you want to block {name}?",
|
||||||
|
"id": "confirmations.block.message"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"path": "app/javascript/mastodon/features/status/containers/detailed_status_container.json"
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"descriptors": [
|
"descriptors": [
|
||||||
{
|
{
|
||||||
|
|
|
@ -132,7 +132,7 @@
|
||||||
"follow_request.authorize": "Autorizează",
|
"follow_request.authorize": "Autorizează",
|
||||||
"follow_request.reject": "Respinge",
|
"follow_request.reject": "Respinge",
|
||||||
"getting_started.developers": "Dezvoltatori",
|
"getting_started.developers": "Dezvoltatori",
|
||||||
"getting_started.directory": "Directorul profilului",
|
"getting_started.directory": "Explorează",
|
||||||
"getting_started.documentation": "Documentație",
|
"getting_started.documentation": "Documentație",
|
||||||
"getting_started.heading": "Începe",
|
"getting_started.heading": "Începe",
|
||||||
"getting_started.invite": "Invită prieteni",
|
"getting_started.invite": "Invită prieteni",
|
||||||
|
|
|
@ -70,7 +70,7 @@
|
||||||
"compose_form.direct_message_warning": "Tento príspevok bude videný výhradne iba spomenutými užívateľmi. Ber ale na vedomie že správci tvojej a všetkých iných zahrnutých instancií majú možnosť skontrolovať túto správu.",
|
"compose_form.direct_message_warning": "Tento príspevok bude videný výhradne iba spomenutými užívateľmi. Ber ale na vedomie že správci tvojej a všetkých iných zahrnutých instancií majú možnosť skontrolovať túto správu.",
|
||||||
"compose_form.direct_message_warning_learn_more": "Zistiť viac",
|
"compose_form.direct_message_warning_learn_more": "Zistiť viac",
|
||||||
"compose_form.hashtag_warning": "Tento toot nebude zobrazený pod žiadným haštagom lebo nieje listovaný. Iba verejné tooty môžu byť nájdené podľa haštagu.",
|
"compose_form.hashtag_warning": "Tento toot nebude zobrazený pod žiadným haštagom lebo nieje listovaný. Iba verejné tooty môžu byť nájdené podľa haštagu.",
|
||||||
"compose_form.lock_disclaimer": "Váš účet nie je zamknutý. Ktokoľvek ťa môže nasledovať a vidieť tvoje správy pre sledujúcich.",
|
"compose_form.lock_disclaimer": "Váš účet nie je {locked}. Ktokoľvek ťa môže nasledovať a vidieť tvoje správy pre sledujúcich.",
|
||||||
"compose_form.lock_disclaimer.lock": "zamknutý",
|
"compose_form.lock_disclaimer.lock": "zamknutý",
|
||||||
"compose_form.placeholder": "Čo máš na mysli?",
|
"compose_form.placeholder": "Čo máš na mysli?",
|
||||||
"compose_form.publish": "Pošli",
|
"compose_form.publish": "Pošli",
|
||||||
|
|
|
@ -91,6 +91,14 @@ function main() {
|
||||||
if (parallaxComponents.length > 0 ) {
|
if (parallaxComponents.length > 0 ) {
|
||||||
new Rellax('.parallax', { speed: -1 });
|
new Rellax('.parallax', { speed: -1 });
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (document.body.classList.contains('with-modals')) {
|
||||||
|
const scrollbarWidth = window.innerWidth - document.documentElement.clientWidth;
|
||||||
|
const scrollbarWidthStyle = document.createElement('style');
|
||||||
|
scrollbarWidthStyle.id = 'scrollbar-width';
|
||||||
|
document.head.appendChild(scrollbarWidthStyle);
|
||||||
|
scrollbarWidthStyle.sheet.insertRule(`body.with-modals--active { margin-right: ${scrollbarWidth}px; }`, 0);
|
||||||
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -364,7 +364,7 @@ $small-breakpoint: 960px;
|
||||||
|
|
||||||
@media screen and (max-width: $column-breakpoint) {
|
@media screen and (max-width: $column-breakpoint) {
|
||||||
.grid {
|
.grid {
|
||||||
grid-template-columns: auto;
|
grid-template-columns: 100%;
|
||||||
|
|
||||||
.column-0 {
|
.column-0 {
|
||||||
display: block;
|
display: block;
|
||||||
|
|
|
@ -295,7 +295,7 @@
|
||||||
color: $primary-text-color;
|
color: $primary-text-color;
|
||||||
}
|
}
|
||||||
|
|
||||||
@media screen and (max-width: $no-gap-breakpoint) {
|
@media screen and (max-width: 550px) {
|
||||||
&.optional {
|
&.optional {
|
||||||
display: none;
|
display: none;
|
||||||
}
|
}
|
||||||
|
|
|
@ -419,7 +419,7 @@ code {
|
||||||
background: darken($ui-base-color, 10%) url("data:image/svg+xml;utf8,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 14.933 18.467' height='19.698' width='15.929'><path d='M3.467 14.967l-3.393-3.5H14.86l-3.392 3.5c-1.866 1.925-3.666 3.5-4 3.5-.335 0-2.135-1.575-4-3.5zm.266-11.234L7.467 0 11.2 3.733l3.733 3.734H0l3.733-3.734z' fill='#{hex-color(lighten($ui-base-color, 12%))}'/></svg>") no-repeat right 8px center / auto 16px;
|
background: darken($ui-base-color, 10%) url("data:image/svg+xml;utf8,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 14.933 18.467' height='19.698' width='15.929'><path d='M3.467 14.967l-3.393-3.5H14.86l-3.392 3.5c-1.866 1.925-3.666 3.5-4 3.5-.335 0-2.135-1.575-4-3.5zm.266-11.234L7.467 0 11.2 3.733l3.733 3.734H0l3.733-3.734z' fill='#{hex-color(lighten($ui-base-color, 12%))}'/></svg>") no-repeat right 8px center / auto 16px;
|
||||||
border: 1px solid darken($ui-base-color, 14%);
|
border: 1px solid darken($ui-base-color, 14%);
|
||||||
border-radius: 4px;
|
border-radius: 4px;
|
||||||
padding: 10px;
|
padding-left: 10px;
|
||||||
padding-right: 30px;
|
padding-right: 30px;
|
||||||
height: 41px;
|
height: 41px;
|
||||||
}
|
}
|
||||||
|
|
|
@ -425,3 +425,93 @@
|
||||||
border-radius: 0;
|
border-radius: 0;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
$maximum-width: 1235px;
|
||||||
|
$fluid-breakpoint: $maximum-width + 20px;
|
||||||
|
|
||||||
|
.statuses-grid {
|
||||||
|
min-height: 600px;
|
||||||
|
|
||||||
|
@media screen and (max-width: 640px) {
|
||||||
|
width: 100% !important; // Masonry layout is unnecessary at this width
|
||||||
|
}
|
||||||
|
|
||||||
|
&__item {
|
||||||
|
width: (960px - 20px) / 3;
|
||||||
|
|
||||||
|
@media screen and (max-width: $fluid-breakpoint) {
|
||||||
|
width: (940px - 20px) / 3;
|
||||||
|
}
|
||||||
|
|
||||||
|
@media screen and (max-width: 640px) {
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
@media screen and (max-width: $no-gap-breakpoint) {
|
||||||
|
width: 100vw;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.detailed-status {
|
||||||
|
border-radius: 4px;
|
||||||
|
|
||||||
|
@media screen and (max-width: $no-gap-breakpoint) {
|
||||||
|
border-top: 1px solid lighten($ui-base-color, 16%);
|
||||||
|
}
|
||||||
|
|
||||||
|
&.compact {
|
||||||
|
.detailed-status__meta {
|
||||||
|
margin-top: 15px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.status__content {
|
||||||
|
font-size: 15px;
|
||||||
|
line-height: 20px;
|
||||||
|
|
||||||
|
.emojione {
|
||||||
|
width: 20px;
|
||||||
|
height: 20px;
|
||||||
|
margin: -3px 0 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.status__content__spoiler-link {
|
||||||
|
line-height: 20px;
|
||||||
|
margin: 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.media-gallery,
|
||||||
|
.status-card,
|
||||||
|
.video-player {
|
||||||
|
margin-top: 15px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.notice-widget {
|
||||||
|
margin-bottom: 10px;
|
||||||
|
color: $darker-text-color;
|
||||||
|
|
||||||
|
p {
|
||||||
|
margin-bottom: 10px;
|
||||||
|
|
||||||
|
&:last-child {
|
||||||
|
margin-bottom: 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
a {
|
||||||
|
font-size: 14px;
|
||||||
|
line-height: 20px;
|
||||||
|
text-decoration: none;
|
||||||
|
font-weight: 500;
|
||||||
|
color: $ui-highlight-color;
|
||||||
|
|
||||||
|
&:hover,
|
||||||
|
&:focus,
|
||||||
|
&:active {
|
||||||
|
text-decoration: underline;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -17,7 +17,7 @@ class ActivityPub::Activity::Announce < ActivityPub::Activity
|
||||||
uri: @json['id'],
|
uri: @json['id'],
|
||||||
created_at: @json['published'],
|
created_at: @json['published'],
|
||||||
override_timestamps: @options[:override_timestamps],
|
override_timestamps: @options[:override_timestamps],
|
||||||
visibility: original_status.visibility
|
visibility: visibility_from_audience
|
||||||
)
|
)
|
||||||
|
|
||||||
distribute(status)
|
distribute(status)
|
||||||
|
@ -26,6 +26,18 @@ class ActivityPub::Activity::Announce < ActivityPub::Activity
|
||||||
|
|
||||||
private
|
private
|
||||||
|
|
||||||
|
def visibility_from_audience
|
||||||
|
if equals_or_includes?(@json['to'], ActivityPub::TagManager::COLLECTIONS[:public])
|
||||||
|
:public
|
||||||
|
elsif equals_or_includes?(@json['cc'], ActivityPub::TagManager::COLLECTIONS[:public])
|
||||||
|
:unlisted
|
||||||
|
elsif equals_or_includes?(@json['to'], @account.followers_url)
|
||||||
|
:private
|
||||||
|
else
|
||||||
|
:direct
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
def announceable?(status)
|
def announceable?(status)
|
||||||
status.account_id == @account.id || status.public_visibility? || status.unlisted_visibility?
|
status.account_id == @account.id || status.public_visibility? || status.unlisted_visibility?
|
||||||
end
|
end
|
||||||
|
|
|
@ -5,10 +5,13 @@ class ActivityPub::Activity::Create < ActivityPub::Activity
|
||||||
CONVERTED_TYPES = %w(Image Video Article Page).freeze
|
CONVERTED_TYPES = %w(Image Video Article Page).freeze
|
||||||
|
|
||||||
def perform
|
def perform
|
||||||
return if delete_arrived_first?(object_uri) || unsupported_object_type? || invalid_origin?(@object['id'])
|
return if unsupported_object_type? || invalid_origin?(@object['id'])
|
||||||
|
return if Tombstone.exists?(uri: @object['id'])
|
||||||
|
|
||||||
RedisLock.acquire(lock_options) do |lock|
|
RedisLock.acquire(lock_options) do |lock|
|
||||||
if lock.acquired?
|
if lock.acquired?
|
||||||
|
return if delete_arrived_first?(object_uri)
|
||||||
|
|
||||||
@status = find_existing_status
|
@status = find_existing_status
|
||||||
|
|
||||||
if @status.nil?
|
if @status.nil?
|
||||||
|
@ -59,7 +62,7 @@ class ActivityPub::Activity::Create < ActivityPub::Activity
|
||||||
account: @account,
|
account: @account,
|
||||||
text: text_from_content || '',
|
text: text_from_content || '',
|
||||||
language: detected_language,
|
language: detected_language,
|
||||||
spoiler_text: text_from_summary || '',
|
spoiler_text: converted_object_type? ? '' : (text_from_summary || ''),
|
||||||
created_at: @object['published'],
|
created_at: @object['published'],
|
||||||
override_timestamps: @options[:override_timestamps],
|
override_timestamps: @options[:override_timestamps],
|
||||||
reply: @object['inReplyTo'].present?,
|
reply: @object['inReplyTo'].present?,
|
||||||
|
@ -254,7 +257,7 @@ class ActivityPub::Activity::Create < ActivityPub::Activity
|
||||||
end
|
end
|
||||||
|
|
||||||
def text_from_content
|
def text_from_content
|
||||||
return Formatter.instance.linkify([text_from_name, object_url || @object['id']].join(' ')) if converted_object_type?
|
return Formatter.instance.linkify([[text_from_name, text_from_summary.presence].compact.join("\n\n"), object_url || @object['id']].join(' ')) if converted_object_type?
|
||||||
|
|
||||||
if @object['content'].present?
|
if @object['content'].present?
|
||||||
@object['content']
|
@object['content']
|
||||||
|
|
|
@ -21,11 +21,14 @@ class ActivityPub::Activity::Delete < ActivityPub::Activity
|
||||||
def delete_note
|
def delete_note
|
||||||
return if object_uri.nil?
|
return if object_uri.nil?
|
||||||
|
|
||||||
|
unless invalid_origin?(object_uri)
|
||||||
|
RedisLock.acquire(lock_options) { |_lock| delete_later!(object_uri) }
|
||||||
|
Tombstone.find_or_create_by(uri: object_uri, account: @account)
|
||||||
|
end
|
||||||
|
|
||||||
@status = Status.find_by(uri: object_uri, account: @account)
|
@status = Status.find_by(uri: object_uri, account: @account)
|
||||||
@status ||= Status.find_by(uri: @object['atomUri'], account: @account) if @object.is_a?(Hash) && @object['atomUri'].present?
|
@status ||= Status.find_by(uri: @object['atomUri'], account: @account) if @object.is_a?(Hash) && @object['atomUri'].present?
|
||||||
|
|
||||||
delete_later!(object_uri)
|
|
||||||
|
|
||||||
return if @status.nil?
|
return if @status.nil?
|
||||||
|
|
||||||
if @status.public_visibility? || @status.unlisted_visibility?
|
if @status.public_visibility? || @status.unlisted_visibility?
|
||||||
|
@ -68,4 +71,17 @@ class ActivityPub::Activity::Delete < ActivityPub::Activity
|
||||||
def payload
|
def payload
|
||||||
@payload ||= Oj.dump(@json)
|
@payload ||= Oj.dump(@json)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def lock_options
|
||||||
|
{ redis: Redis.current, key: "create:#{object_uri}" }
|
||||||
|
end
|
||||||
|
|
||||||
|
def invalid_origin?(url)
|
||||||
|
return true if unsupported_uri_scheme?(url)
|
||||||
|
|
||||||
|
needle = Addressable::URI.parse(url).host
|
||||||
|
haystack = Addressable::URI.parse(@account.uri).host
|
||||||
|
|
||||||
|
!haystack.casecmp(needle).zero?
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
|
@ -504,7 +504,7 @@ class Status < ApplicationRecord
|
||||||
return if direct_visibility?
|
return if direct_visibility?
|
||||||
|
|
||||||
account&.increment_count!(:statuses_count)
|
account&.increment_count!(:statuses_count)
|
||||||
reblog&.increment_count!(:reblogs_count) if reblog?
|
reblog&.increment_count!(:reblogs_count) if reblog? && (public_visibility? || unlisted_visibility?)
|
||||||
thread&.increment_count!(:replies_count) if in_reply_to_id.present? && (public_visibility? || unlisted_visibility?)
|
thread&.increment_count!(:replies_count) if in_reply_to_id.present? && (public_visibility? || unlisted_visibility?)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
@ -512,7 +512,7 @@ class Status < ApplicationRecord
|
||||||
return if direct_visibility? || marked_for_mass_destruction?
|
return if direct_visibility? || marked_for_mass_destruction?
|
||||||
|
|
||||||
account&.decrement_count!(:statuses_count)
|
account&.decrement_count!(:statuses_count)
|
||||||
reblog&.decrement_count!(:reblogs_count) if reblog?
|
reblog&.decrement_count!(:reblogs_count) if reblog? && (public_visibility? || unlisted_visibility?)
|
||||||
thread&.decrement_count!(:replies_count) if in_reply_to_id.present? && (public_visibility? || unlisted_visibility?)
|
thread&.decrement_count!(:replies_count) if in_reply_to_id.present? && (public_visibility? || unlisted_visibility?)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
16
app/models/tombstone.rb
Normal file
16
app/models/tombstone.rb
Normal file
|
@ -0,0 +1,16 @@
|
||||||
|
# frozen_string_literal: true
|
||||||
|
|
||||||
|
# == Schema Information
|
||||||
|
#
|
||||||
|
# Table name: tombstones
|
||||||
|
#
|
||||||
|
# id :bigint(8) not null, primary key
|
||||||
|
# account_id :bigint(8)
|
||||||
|
# uri :string not null
|
||||||
|
# created_at :datetime not null
|
||||||
|
# updated_at :datetime not null
|
||||||
|
#
|
||||||
|
|
||||||
|
class Tombstone < ApplicationRecord
|
||||||
|
belongs_to :account
|
||||||
|
end
|
|
@ -362,7 +362,8 @@ class User < ApplicationRecord
|
||||||
end
|
end
|
||||||
|
|
||||||
def regenerate_feed!
|
def regenerate_feed!
|
||||||
Redis.current.setnx("account:#{account_id}:regeneration", true) && Redis.current.expire("account:#{account_id}:regeneration", 1.day.seconds)
|
return unless Redis.current.setnx("account:#{account_id}:regeneration", true)
|
||||||
|
Redis.current.expire("account:#{account_id}:regeneration", 1.day.seconds)
|
||||||
RegenerationWorker.perform_async(account_id)
|
RegenerationWorker.perform_async(account_id)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
|
@ -33,6 +33,8 @@ class ActivityPub::ProcessAccountService < BaseService
|
||||||
|
|
||||||
after_protocol_change! if protocol_changed?
|
after_protocol_change! if protocol_changed?
|
||||||
after_key_change! if key_changed? && !@options[:signed_with_known_key]
|
after_key_change! if key_changed? && !@options[:signed_with_known_key]
|
||||||
|
clear_tombstones! if key_changed?
|
||||||
|
|
||||||
unless @options[:only_key]
|
unless @options[:only_key]
|
||||||
check_featured_collection! if @account.featured_collection_url.present?
|
check_featured_collection! if @account.featured_collection_url.present?
|
||||||
check_links! unless @account.fields.empty?
|
check_links! unless @account.fields.empty?
|
||||||
|
@ -209,6 +211,10 @@ class ActivityPub::ProcessAccountService < BaseService
|
||||||
!@old_public_key.nil? && @old_public_key != @account.public_key
|
!@old_public_key.nil? && @old_public_key != @account.public_key
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def clear_tombstones!
|
||||||
|
Tombstone.delete_all(account_id: @account.id)
|
||||||
|
end
|
||||||
|
|
||||||
def protocol_changed?
|
def protocol_changed?
|
||||||
!@old_protocol.nil? && @old_protocol != @account.protocol
|
!@old_protocol.nil? && @old_protocol != @account.protocol
|
||||||
end
|
end
|
||||||
|
|
|
@ -3,6 +3,7 @@
|
||||||
class PrecomputeFeedService < BaseService
|
class PrecomputeFeedService < BaseService
|
||||||
def call(account)
|
def call(account)
|
||||||
FeedManager.instance.populate_feed(account)
|
FeedManager.instance.populate_feed(account)
|
||||||
|
ensure
|
||||||
Redis.current.del("account:#{account.id}:regeneration")
|
Redis.current.del("account:#{account.id}:regeneration")
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
|
@ -20,6 +20,7 @@ class UnfollowService < BaseService
|
||||||
|
|
||||||
follow.destroy!
|
follow.destroy!
|
||||||
create_notification(follow) unless @target_account.local?
|
create_notification(follow) unless @target_account.local?
|
||||||
|
create_reject_notification(follow) if @target_account.local? && !@source_account.local?
|
||||||
UnmergeWorker.perform_async(@target_account.id, @source_account.id)
|
UnmergeWorker.perform_async(@target_account.id, @source_account.id)
|
||||||
follow
|
follow
|
||||||
end
|
end
|
||||||
|
@ -42,6 +43,12 @@ class UnfollowService < BaseService
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def create_reject_notification(follow)
|
||||||
|
# Rejecting an already-existing follow request
|
||||||
|
return unless follow.account.activitypub?
|
||||||
|
ActivityPub::DeliveryWorker.perform_async(build_reject_json(follow), follow.target_account_id, follow.account.inbox_url)
|
||||||
|
end
|
||||||
|
|
||||||
def build_json(follow)
|
def build_json(follow)
|
||||||
ActiveModelSerializers::SerializableResource.new(
|
ActiveModelSerializers::SerializableResource.new(
|
||||||
follow,
|
follow,
|
||||||
|
@ -50,6 +57,14 @@ class UnfollowService < BaseService
|
||||||
).to_json
|
).to_json
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def build_reject_json(follow)
|
||||||
|
ActiveModelSerializers::SerializableResource.new(
|
||||||
|
follow,
|
||||||
|
serializer: ActivityPub::RejectFollowSerializer,
|
||||||
|
adapter: ActivityPub::Adapter
|
||||||
|
).to_json
|
||||||
|
end
|
||||||
|
|
||||||
def build_xml(follow)
|
def build_xml(follow)
|
||||||
OStatus::AtomSerializer.render(OStatus::AtomSerializer.new.unfollow_salmon(follow))
|
OStatus::AtomSerializer.render(OStatus::AtomSerializer.new.unfollow_salmon(follow))
|
||||||
end
|
end
|
||||||
|
|
|
@ -41,8 +41,22 @@
|
||||||
= paginate @accounts
|
= paginate @accounts
|
||||||
|
|
||||||
.column-1
|
.column-1
|
||||||
- if @tags.empty?
|
- if user_signed_in?
|
||||||
.nothing-here.nothing-here--flexible
|
.box-widget.notice-widget
|
||||||
|
- if current_account.discoverable?
|
||||||
|
- if current_account.followers_count < Account::MIN_FOLLOWERS_DISCOVERY
|
||||||
|
%p= t('directories.enabled_but_waiting', min_followers: Account::MIN_FOLLOWERS_DISCOVERY)
|
||||||
|
- else
|
||||||
|
%p= t('directories.enabled')
|
||||||
|
- else
|
||||||
|
%p= t('directories.how_to_enable')
|
||||||
|
|
||||||
|
= link_to settings_profile_path do
|
||||||
|
= t('settings.edit_profile')
|
||||||
|
= fa_icon 'chevron-right fw'
|
||||||
|
|
||||||
|
- if @tags.empty? && !user_signed_in?
|
||||||
|
.nothing-here
|
||||||
- else
|
- else
|
||||||
- @tags.each do |tag|
|
- @tags.each do |tag|
|
||||||
.directory__tag{ class: tag.id == @tag&.id ? 'active' : nil }
|
.directory__tag{ class: tag.id == @tag&.id ? 'active' : nil }
|
||||||
|
|
|
@ -1,25 +0,0 @@
|
||||||
.features-list
|
|
||||||
.features-list__row
|
|
||||||
.text
|
|
||||||
%h6= t 'about.features.real_conversation_title'
|
|
||||||
= t 'about.features.real_conversation_body'
|
|
||||||
.visual
|
|
||||||
= fa_icon 'fw comments'
|
|
||||||
.features-list__row
|
|
||||||
.text
|
|
||||||
%h6= t 'about.features.not_a_product_title'
|
|
||||||
= t 'about.features.not_a_product_body'
|
|
||||||
.visual
|
|
||||||
= fa_icon 'fw users'
|
|
||||||
.features-list__row
|
|
||||||
.text
|
|
||||||
%h6= t 'about.features.within_reach_title'
|
|
||||||
= t 'about.features.within_reach_body'
|
|
||||||
.visual
|
|
||||||
= fa_icon 'fw mobile'
|
|
||||||
.features-list__row
|
|
||||||
.text
|
|
||||||
%h6= t 'about.features.humane_approach_title'
|
|
||||||
= t 'about.features.humane_approach_body'
|
|
||||||
.visual
|
|
||||||
= fa_icon 'fw leaf'
|
|
|
@ -7,33 +7,9 @@
|
||||||
%script#initial-state{ type: 'application/json' }!= json_escape(@initial_state_json)
|
%script#initial-state{ type: 'application/json' }!= json_escape(@initial_state_json)
|
||||||
= render 'og'
|
= render 'og'
|
||||||
|
|
||||||
.landing-page.tag-page.alternative
|
.page-header
|
||||||
.features
|
%h1= "##{@tag.name}"
|
||||||
.container
|
%p= t('about.about_hashtag_html', hashtag: @tag.name)
|
||||||
.grid
|
|
||||||
.column-1
|
|
||||||
#mastodon-timeline{ data: { props: Oj.dump(default_props.merge(hashtag: @tag.name)) } }
|
|
||||||
|
|
||||||
.column-2
|
|
||||||
.about-mastodon
|
|
||||||
.about-hashtag.landing-page__information
|
|
||||||
.brand
|
|
||||||
= link_to root_url do
|
|
||||||
= image_tag asset_pack_path('logo_full.svg'), alt: 'Mastodon'
|
|
||||||
|
|
||||||
%p= t 'about.about_hashtag_html', hashtag: @tag.name
|
|
||||||
|
|
||||||
.cta
|
|
||||||
- if user_signed_in?
|
|
||||||
= link_to t('settings.back'), root_path, class: 'button button-secondary'
|
|
||||||
- else
|
|
||||||
= link_to t('auth.login'), new_user_session_path, class: 'button button-secondary'
|
|
||||||
= link_to t('about.learn_more'), about_path, class: 'button button-alternative'
|
|
||||||
|
|
||||||
.landing-page__features.landing-page__information
|
|
||||||
%h3= t 'about.what_is_mastodon'
|
|
||||||
%p= t 'about.about_mastodon_html'
|
|
||||||
|
|
||||||
= render 'features'
|
|
||||||
|
|
||||||
|
#mastodon-timeline{ data: { props: Oj.dump(default_props.merge(hashtag: @tag.name)) }}
|
||||||
#modal-container
|
#modal-container
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
# frozen_string_literal: true
|
# frozen_string_literal: true
|
||||||
|
|
||||||
lock '3.10.2'
|
lock '3.11.0'
|
||||||
|
|
||||||
set :repo_url, ENV.fetch('REPO', 'https://github.com/tootsuite/mastodon.git')
|
set :repo_url, ENV.fetch('REPO', 'https://github.com/tootsuite/mastodon.git')
|
||||||
set :branch, ENV.fetch('BRANCH', 'master')
|
set :branch, ENV.fetch('BRANCH', 'master')
|
||||||
|
|
|
@ -36,6 +36,9 @@ if ENV['S3_ENABLED'] == 'true'
|
||||||
},
|
},
|
||||||
s3_options: {
|
s3_options: {
|
||||||
signature_version: ENV.fetch('S3_SIGNATURE_VERSION') { 'v4' },
|
signature_version: ENV.fetch('S3_SIGNATURE_VERSION') { 'v4' },
|
||||||
|
http_open_timeout: 5,
|
||||||
|
http_read_timeout: 5,
|
||||||
|
http_idle_timeout: 5,
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
|
@ -2,7 +2,7 @@
|
||||||
cs:
|
cs:
|
||||||
about:
|
about:
|
||||||
about_hashtag_html: Tohle jsou veřejné tooty označené hashtagem <strong>#%{hashtag}</strong>. Pokud máte účet kdekoliv na fediverse, můžete s nimi interagovat.
|
about_hashtag_html: Tohle jsou veřejné tooty označené hashtagem <strong>#%{hashtag}</strong>. Pokud máte účet kdekoliv na fediverse, můžete s nimi interagovat.
|
||||||
about_mastodon_html: Mastodon je sociální síť založená na otevřených webových protokolech a svobodném, otevřeném softwaru. Je decentrovalizovaná jako e-mail.
|
about_mastodon_html: Mastodon je sociální síť založená na otevřených webových protokolech a svobodném, otevřeném softwaru. Je decentralizovaná jako e-mail.
|
||||||
about_this: O této instanci
|
about_this: O této instanci
|
||||||
administered_by: 'Instanci spravuje:'
|
administered_by: 'Instanci spravuje:'
|
||||||
api: API
|
api: API
|
||||||
|
@ -310,7 +310,7 @@ cs:
|
||||||
instances:
|
instances:
|
||||||
delivery_available: Doručení je k dispozici
|
delivery_available: Doručení je k dispozici
|
||||||
known_accounts:
|
known_accounts:
|
||||||
few: "%{count} známých účtů"
|
few: "%{count} známé účty"
|
||||||
one: "%{count} známý účet"
|
one: "%{count} známý účet"
|
||||||
other: "%{count} známých účtů"
|
other: "%{count} známých účtů"
|
||||||
moderation:
|
moderation:
|
||||||
|
@ -686,7 +686,7 @@ cs:
|
||||||
body: Zde najdete stručný souhrn zpráv, které jste zmeškal/a od vaší poslední návštěvy %{since}
|
body: Zde najdete stručný souhrn zpráv, které jste zmeškal/a od vaší poslední návštěvy %{since}
|
||||||
mention: "%{name} vás zmínil/a v:"
|
mention: "%{name} vás zmínil/a v:"
|
||||||
new_followers_summary:
|
new_followers_summary:
|
||||||
few: Navíc jste získal/a %{count} nové sledovatele, zatímco jste byl/a pryč! Hurá!
|
few: Navíc jste získal/a %{count} nové sledovatele, zatímco jste byl/a pryč! Skvělé!
|
||||||
one: Navíc jste získal/a jednoho nového sledovatele, zatímco jste byl/a pryč! Hurá!
|
one: Navíc jste získal/a jednoho nového sledovatele, zatímco jste byl/a pryč! Hurá!
|
||||||
other: Navíc jste získal/a %{count} nových sledovatelů, zatímco jste byl/a pryč! Úžasné!
|
other: Navíc jste získal/a %{count} nových sledovatelů, zatímco jste byl/a pryč! Úžasné!
|
||||||
subject:
|
subject:
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
---
|
---
|
||||||
de:
|
de:
|
||||||
about:
|
about:
|
||||||
about_hashtag_html: Dies sind öffentliche Beiträge, die mit <strong>#%{hashtag}</strong> getaggt wurden. Wenn du ein Konto irgendwo im Fediversum besitzt, kannst du mit ihnen interagieren.
|
about_hashtag_html: Dies sind öffentliche Beiträge, die mit <strong>#%{hashtag}</strong> getaggt wurden. Wenn du irgendwo im Fediversum ein Konto besitzt, kannst du mit ihnen interagieren.
|
||||||
about_mastodon_html: Mastodon ist ein soziales Netzwerk. Es basiert auf offenen Web-Protokollen und freier, quelloffener Software. Es ist dezentral (so wie E-Mail!).
|
about_mastodon_html: Mastodon ist ein soziales Netzwerk. Es basiert auf offenen Web-Protokollen und freier, quelloffener Software. Es ist dezentral (so wie E-Mail!).
|
||||||
about_this: Über diese Instanz
|
about_this: Über diese Instanz
|
||||||
administered_by: 'Administriert von:'
|
administered_by: 'Administriert von:'
|
||||||
|
@ -22,7 +22,7 @@ de:
|
||||||
not_a_product_title: Du bist ein Mensch und keine Ware
|
not_a_product_title: Du bist ein Mensch und keine Ware
|
||||||
real_conversation_body: Mit 500 Zeichen pro Beitrag und Features wie Inhalts- und Bilderwarnungen kannst du dich so ausdrücken, wie du es möchtest.
|
real_conversation_body: Mit 500 Zeichen pro Beitrag und Features wie Inhalts- und Bilderwarnungen kannst du dich so ausdrücken, wie du es möchtest.
|
||||||
real_conversation_title: Geschaffen für echte Gespräche
|
real_conversation_title: Geschaffen für echte Gespräche
|
||||||
within_reach_body: Verschiedene Apps für iOS, Android und andere Plattformen erlauben dir, dank unseres blühenden API-Ökosystems, dich von überall auf dem Laufenden zu halten.
|
within_reach_body: Verschiedene Apps für iOS, Android und andere Plattformen erlauben es dir, dank unseres blühenden API-Ökosystems, dich von überall auf dem Laufenden zu halten.
|
||||||
within_reach_title: Immer für dich da
|
within_reach_title: Immer für dich da
|
||||||
generic_description: "%{domain} ist ein Server im Netzwerk"
|
generic_description: "%{domain} ist ein Server im Netzwerk"
|
||||||
hosted_on: Mastodon, beherbergt auf %{domain}
|
hosted_on: Mastodon, beherbergt auf %{domain}
|
||||||
|
@ -44,8 +44,8 @@ de:
|
||||||
choices_html: "%{name} empfiehlt:"
|
choices_html: "%{name} empfiehlt:"
|
||||||
follow: Folgen
|
follow: Folgen
|
||||||
followers:
|
followers:
|
||||||
one: Folgende
|
one: Folgender
|
||||||
other: Follower
|
other: Folgende
|
||||||
following: Folgt
|
following: Folgt
|
||||||
joined: Beigetreten am %{date}
|
joined: Beigetreten am %{date}
|
||||||
last_active: zuletzt aktiv
|
last_active: zuletzt aktiv
|
||||||
|
@ -81,7 +81,7 @@ de:
|
||||||
accounts:
|
accounts:
|
||||||
are_you_sure: Bist du sicher?
|
are_you_sure: Bist du sicher?
|
||||||
avatar: Profilbild
|
avatar: Profilbild
|
||||||
by_domain: Domäne
|
by_domain: Domain
|
||||||
change_email:
|
change_email:
|
||||||
changed_msg: E-Mail-Adresse des Kontos erfolgreich geändert!
|
changed_msg: E-Mail-Adresse des Kontos erfolgreich geändert!
|
||||||
current_email: Aktuelle E-Mail-Adresse
|
current_email: Aktuelle E-Mail-Adresse
|
||||||
|
@ -105,7 +105,7 @@ de:
|
||||||
enable: Freischalten
|
enable: Freischalten
|
||||||
enabled: Freigegeben
|
enabled: Freigegeben
|
||||||
feed_url: Feed-URL
|
feed_url: Feed-URL
|
||||||
followers: Folger
|
followers: Folgende
|
||||||
followers_local: "(%{local} lokal)"
|
followers_local: "(%{local} lokal)"
|
||||||
followers_url: URL des Folgenden
|
followers_url: URL des Folgenden
|
||||||
follows: Folgt
|
follows: Folgt
|
||||||
|
@ -194,17 +194,17 @@ de:
|
||||||
disable_user: "%{name} hat den Login für Benutzer:in %{target} deaktiviert"
|
disable_user: "%{name} hat den Login für Benutzer:in %{target} deaktiviert"
|
||||||
enable_custom_emoji: "%{name} hat das %{target} Emoji aktiviert"
|
enable_custom_emoji: "%{name} hat das %{target} Emoji aktiviert"
|
||||||
enable_user: "%{name} hat die Anmeldung für di:en Benutzer:in %{target} aktiviert"
|
enable_user: "%{name} hat die Anmeldung für di:en Benutzer:in %{target} aktiviert"
|
||||||
memorialize_account: "%{name} hat %{target}s Profil in eine Gedenkseite umgewandelt"
|
memorialize_account: "%{name} hat %{target}s Konto in eine Gedenkseite umgewandelt"
|
||||||
promote_user: "%{name} hat %{target} befördert"
|
promote_user: "%{name} hat %{target} befördert"
|
||||||
remove_avatar_user: "%{name} hat das Profilbild von %{target} entfernt"
|
remove_avatar_user: "%{name} hat das Profilbild von %{target} entfernt"
|
||||||
reopen_report: "%{name} hat die Meldung %{target} wieder geöffnet"
|
reopen_report: "%{name} hat die Meldung %{target} wieder geöffnet"
|
||||||
reset_password_user: "%{name} hat das Passwort für di:en Benutzer:in %{target} zurückgesetzt"
|
reset_password_user: "%{name} hat das Passwort für di:en Benutzer:in %{target} zurückgesetzt"
|
||||||
resolve_report: "%{name} hat die Meldung %{target} bearbeitet"
|
resolve_report: "%{name} hat die Meldung %{target} bearbeitet"
|
||||||
silence_account: "%{name} hat %{target}s Account stummgeschaltet"
|
silence_account: "%{name} hat %{target}s Konto stummgeschaltet"
|
||||||
suspend_account: "%{name} hat %{target}s Account gesperrt"
|
suspend_account: "%{name} hat %{target}s Konto gesperrt"
|
||||||
unassigned_report: "%{name} hat die Zuweisung der Meldung %{target} entfernt"
|
unassigned_report: "%{name} hat die Zuweisung der Meldung %{target} entfernt"
|
||||||
unsilence_account: "%{name} hat die Stummschaltung von %{target}s Account aufgehoben"
|
unsilence_account: "%{name} hat die Stummschaltung von %{target}s Konto aufgehoben"
|
||||||
unsuspend_account: "%{name} hat die Sperrung von %{target}s Account aufgehoben"
|
unsuspend_account: "%{name} hat die Sperrung von %{target}s Konto aufgehoben"
|
||||||
update_custom_emoji: "%{name} hat das %{target} Emoji aktualisiert"
|
update_custom_emoji: "%{name} hat das %{target} Emoji aktualisiert"
|
||||||
update_status: "%{name} hat den Status von %{target} aktualisiert"
|
update_status: "%{name} hat den Status von %{target} aktualisiert"
|
||||||
deleted_status: "(gelöschter Beitrag)"
|
deleted_status: "(gelöschter Beitrag)"
|
||||||
|
@ -300,12 +300,12 @@ de:
|
||||||
title: Neue E-Mail-Domain-Blockade
|
title: Neue E-Mail-Domain-Blockade
|
||||||
title: E-Mail-Domain-Blockade
|
title: E-Mail-Domain-Blockade
|
||||||
followers:
|
followers:
|
||||||
back_to_account: Zurück zum Account
|
back_to_account: Zurück zum Konto
|
||||||
title: "%{acct}'s Follower"
|
title: "%{acct}'s Follower"
|
||||||
instances:
|
instances:
|
||||||
delivery_available: Zustellung ist verfügbar
|
delivery_available: Zustellung ist verfügbar
|
||||||
known_accounts:
|
known_accounts:
|
||||||
one: "%{count} bekannter Account"
|
one: "%{count} bekanntes Konto"
|
||||||
other: "%{count} bekannte Accounts"
|
other: "%{count} bekannte Accounts"
|
||||||
moderation:
|
moderation:
|
||||||
all: Alle
|
all: Alle
|
||||||
|
@ -506,8 +506,8 @@ de:
|
||||||
invalid_reset_password_token: Das Token zum Zurücksetzen des Passworts ist ungültig oder abgelaufen. Bitte fordere ein neues an.
|
invalid_reset_password_token: Das Token zum Zurücksetzen des Passworts ist ungültig oder abgelaufen. Bitte fordere ein neues an.
|
||||||
login: Anmelden
|
login: Anmelden
|
||||||
logout: Abmelden
|
logout: Abmelden
|
||||||
migrate_account: Ziehe zu einem anderen Account um
|
migrate_account: Ziehe zu einem anderen Konto um
|
||||||
migrate_account_html: Wenn du es wünschst diesen Account zu einem anderen umzuziehen, dann kannst du <a href="%{path}">es hier einstellen</a>.
|
migrate_account_html: Wenn du wünschst, dieses Konto zu einem anderen umzuziehen, kannst du <a href="%{path}">dies hier einstellen</a>.
|
||||||
or: oder
|
or: oder
|
||||||
or_log_in_with: Oder anmelden mit
|
or_log_in_with: Oder anmelden mit
|
||||||
providers:
|
providers:
|
||||||
|
@ -521,7 +521,7 @@ de:
|
||||||
set_new_password: Neues Passwort setzen
|
set_new_password: Neues Passwort setzen
|
||||||
authorize_follow:
|
authorize_follow:
|
||||||
already_following: Du folgst diesem Konto bereits
|
already_following: Du folgst diesem Konto bereits
|
||||||
error: Das Profil konnte nicht geladen werden
|
error: Das Remote-Konto konnte nicht geladen werden
|
||||||
follow: Folgen
|
follow: Folgen
|
||||||
follow_request: 'Du hast eine Folgeanfrage gesendet an:'
|
follow_request: 'Du hast eine Folgeanfrage gesendet an:'
|
||||||
following: 'Erfolg! Du folgst nun:'
|
following: 'Erfolg! Du folgst nun:'
|
||||||
|
@ -655,7 +655,7 @@ de:
|
||||||
table:
|
table:
|
||||||
expires_at: Läuft ab
|
expires_at: Läuft ab
|
||||||
uses: Verwendungen
|
uses: Verwendungen
|
||||||
title: Leute Einladen
|
title: Leute einladen
|
||||||
lists:
|
lists:
|
||||||
errors:
|
errors:
|
||||||
limit: Du hast die maximale Anzahl an Listen erreicht
|
limit: Du hast die maximale Anzahl an Listen erreicht
|
||||||
|
@ -664,10 +664,10 @@ de:
|
||||||
images_and_video: Es kann kein Video an einen Beitrag, der bereits Bilder enthält, angehängt werden
|
images_and_video: Es kann kein Video an einen Beitrag, der bereits Bilder enthält, angehängt werden
|
||||||
too_many: Es können nicht mehr als 4 Bilder angehängt werden
|
too_many: Es können nicht mehr als 4 Bilder angehängt werden
|
||||||
migrations:
|
migrations:
|
||||||
acct: benutzername@domain des neuen Accounts
|
acct: benutzername@domain des neuen Kontos
|
||||||
currently_redirecting: 'Deine Profilweiterleitung wurde gesetzt auf:'
|
currently_redirecting: 'Deine Profilweiterleitung wurde gesetzt auf:'
|
||||||
proceed: Speichern
|
proceed: Speichern
|
||||||
updated_msg: Deine Account-Migrationseinstellungen wurden erfolgreich aktualisiert!
|
updated_msg: Deine Konto-Migrationseinstellungen wurden erfolgreich aktualisiert!
|
||||||
moderation:
|
moderation:
|
||||||
title: Moderation
|
title: Moderation
|
||||||
notification_mailer:
|
notification_mailer:
|
||||||
|
@ -729,7 +729,7 @@ de:
|
||||||
remote_follow:
|
remote_follow:
|
||||||
acct: Profilname@Domain, von wo aus du dieser Person folgen möchtest
|
acct: Profilname@Domain, von wo aus du dieser Person folgen möchtest
|
||||||
missing_resource: Die erforderliche Weiterleitungs-URL für dein Konto konnte nicht gefunden werden
|
missing_resource: Die erforderliche Weiterleitungs-URL für dein Konto konnte nicht gefunden werden
|
||||||
no_account_html: Noch keinen Account? Du kannst dich <a href='%{sign_up_path}' target='_blank'>hier anmelden</a>
|
no_account_html: Noch kein Konto? Du kannst dich <a href='%{sign_up_path}' target='_blank'>hier anmelden</a>
|
||||||
proceed: Weiter
|
proceed: Weiter
|
||||||
prompt: 'Du wirst dieser Person folgen:'
|
prompt: 'Du wirst dieser Person folgen:'
|
||||||
reason_html: "<strong>Warum ist dieser Schritt erforderlich?</strong><code>%{instance}</code> ist möglicherweise nicht der Server auf dem du registriert bist, also müssen wir dich erst auf deinen Heimserver weiterleiten."
|
reason_html: "<strong>Warum ist dieser Schritt erforderlich?</strong><code>%{instance}</code> ist möglicherweise nicht der Server auf dem du registriert bist, also müssen wir dich erst auf deinen Heimserver weiterleiten."
|
||||||
|
@ -774,7 +774,7 @@ de:
|
||||||
weibo: Weibo
|
weibo: Weibo
|
||||||
current_session: Aktuelle Sitzung
|
current_session: Aktuelle Sitzung
|
||||||
description: "%{browser} auf %{platform}"
|
description: "%{browser} auf %{platform}"
|
||||||
explanation: Dies sind die Webbrowser, die derzeit in dein Mastodon-Konto eingeloggt sind.
|
explanation: Dies sind die Webbrowser, die derzeit in deinem Mastodon-Konto eingeloggt sind.
|
||||||
ip: IP-Adresse
|
ip: IP-Adresse
|
||||||
platforms:
|
platforms:
|
||||||
adobe_air: Adobe Air
|
adobe_air: Adobe Air
|
||||||
|
@ -801,7 +801,7 @@ de:
|
||||||
export: Datenexport
|
export: Datenexport
|
||||||
followers: Autorisierte Folgende
|
followers: Autorisierte Folgende
|
||||||
import: Datenimport
|
import: Datenimport
|
||||||
migrate: Account-Umzug
|
migrate: Konto-Umzug
|
||||||
notifications: Benachrichtigungen
|
notifications: Benachrichtigungen
|
||||||
preferences: Einstellungen
|
preferences: Einstellungen
|
||||||
settings: Einstellungen
|
settings: Einstellungen
|
||||||
|
@ -842,7 +842,7 @@ de:
|
||||||
stream_entries:
|
stream_entries:
|
||||||
pinned: Angehefteter Beitrag
|
pinned: Angehefteter Beitrag
|
||||||
reblogged: teilte
|
reblogged: teilte
|
||||||
sensitive_content: Heikle Inhalte
|
sensitive_content: Sensible Inhalte
|
||||||
terms:
|
terms:
|
||||||
body_html: |
|
body_html: |
|
||||||
<h2>Datenschutzerklärung</h2>
|
<h2>Datenschutzerklärung</h2>
|
||||||
|
@ -948,33 +948,33 @@ de:
|
||||||
manual_instructions: 'Wenn du den QR-Code nicht einlesen kannst und ihn manuell eingeben musst, ist hier das Klartext-Geheimnis:'
|
manual_instructions: 'Wenn du den QR-Code nicht einlesen kannst und ihn manuell eingeben musst, ist hier das Klartext-Geheimnis:'
|
||||||
recovery_codes: Wiederherstellungs-Codes sichern
|
recovery_codes: Wiederherstellungs-Codes sichern
|
||||||
recovery_codes_regenerated: Wiederherstellungscodes erfolgreich neu generiert
|
recovery_codes_regenerated: Wiederherstellungscodes erfolgreich neu generiert
|
||||||
recovery_instructions_html: Wenn du den Zugang zu deinem Telefon verlieren solltest, kannst du einen untenstehenden Wiederherstellungscodes benutzen, um wieder auf dein Konto zugreifen zu können. <strong>Bewahre die Wiederherstellungscodes gut auf.</strong> Du könntest sie beispielsweise ausdrucken und bei deinen restlichen wichtigen Dokumenten aufbewahren.
|
recovery_instructions_html: Wenn du den Zugang zu deinem Telefon verlieren solltest, kannst du einen untenstehenden Wiederherstellungscode benutzen, um wieder auf dein Konto zugreifen zu können. <strong>Bewahre die Wiederherstellungscodes gut auf.</strong> Du könntest sie beispielsweise ausdrucken und bei deinen restlichen wichtigen Dokumenten aufbewahren.
|
||||||
setup: Einrichten
|
setup: Einrichten
|
||||||
wrong_code: Der eingegebene Code war ungültig! Stimmen Serverzeit und Gerätezeit?
|
wrong_code: Der eingegebene Code war ungültig! Stimmen Serverzeit und Gerätezeit?
|
||||||
user_mailer:
|
user_mailer:
|
||||||
backup_ready:
|
backup_ready:
|
||||||
explanation: Du hast ein vollständiges Backup von deinem Mastodon-Account angefragt. Es kann jetzt heruntergeladen werden!
|
explanation: Du hast ein vollständiges Backup von deinem Mastodon-Konto angefragt. Es kann jetzt heruntergeladen werden!
|
||||||
subject: Dein Archiv ist bereit zum Download
|
subject: Dein Archiv ist bereit zum Download
|
||||||
title: Archiv-Download
|
title: Archiv-Download
|
||||||
warning:
|
warning:
|
||||||
explanation:
|
explanation:
|
||||||
disable: Solange dein Account eingefroren ist sind deine Benutzerdaten intakt, aber du kannst nichts tun bis dein Account entsperrt wurde.
|
disable: Solange dein Konto eingefroren ist, sind deine Benutzerdaten intakt; aber du kannst nichts tun, bis dein Konto entsperrt wurde.
|
||||||
silence: Solange dein Account limitiert ist können nur Leute, die dir bereits folgen deine Beiträge auf dem Server sehen und es könnte sein, dass du von verschiedenen öffentlichen Listungen ausgeschlossen wirst. Andererseits können andere dir manuell folgen.
|
silence: Solange dein Konto limitiert ist, können nur die Leute, die dir bereits folgen, deine Beiträge auf dem Server sehen und es könnte sein, dass du von verschiedenen öffentlichen Listungen ausgeschlossen wirst. Andererseits können andere dir manuell folgen.
|
||||||
suspend: Dein Account wurde gesperrt und alle deine Beiträge und hochgeladenen Medien wurden unwiderruflich vom Server und anderen Servern wo du Follower hattest gelöscht.
|
suspend: Dein Konto wurde gesperrt und alle deine Beiträge und hochgeladenen Medien wurden unwiderruflich vom Server und anderen Servern, bei denen du Folgende hattest, gelöscht.
|
||||||
review_server_policies: Serverrichtlinien ansehen
|
review_server_policies: Serverrichtlinien ansehen
|
||||||
subject:
|
subject:
|
||||||
disable: Dein Account %{acct} wurde eingefroren
|
disable: Dein Konto %{acct} wurde eingefroren
|
||||||
none: Warnung für %{acct}
|
none: Warnung für %{acct}
|
||||||
silence: Dein Account %{acct} wurde limitiert
|
silence: Dein Konto %{acct} wurde limitiert
|
||||||
suspend: Dein Account %{acct} wurde gesperrt
|
suspend: Dein Konto %{acct} wurde gesperrt
|
||||||
title:
|
title:
|
||||||
disable: Account eingefroren
|
disable: Konto eingefroren
|
||||||
none: Warnung
|
none: Warnung
|
||||||
silence: Account limitiert
|
silence: Konto limitiert
|
||||||
suspend: Account gesperrt
|
suspend: Konto gesperrt
|
||||||
welcome:
|
welcome:
|
||||||
edit_profile_action: Profil einstellen
|
edit_profile_action: Profil einstellen
|
||||||
edit_profile_step: Du kannst dein Profil anpassen, indem du einen Avatar oder ein Titelbild hochlädst oder deinen Anzeigenamen änderst und mehr. Wenn du deine Follower vorher überprüfen möchtest, bevor sie dir folgen können, dann kannst du dein Profil sperren.
|
edit_profile_step: Du kannst dein Profil anpassen, indem du einen Avatar oder ein Titelbild hochlädst oder deinen Anzeigenamen änderst und mehr. Wenn du deine Folgenden vorher überprüfen möchtest, bevor sie dir folgen können, dann kannst du dein Profil sperren.
|
||||||
explanation: Hier sind ein paar Tipps, um loszulegen
|
explanation: Hier sind ein paar Tipps, um loszulegen
|
||||||
final_action: Fang an zu posten
|
final_action: Fang an zu posten
|
||||||
final_step: 'Fang an zu posten! Selbst ohne Follower werden deine öffentlichen Beitrage von anderen gesehen, zum Beispiel auf der lokalen Zeitleiste oder in Hashtags. Vielleicht möchtest du dich vorstellen mit dem #introductions-Hashtag.'
|
final_step: 'Fang an zu posten! Selbst ohne Follower werden deine öffentlichen Beitrage von anderen gesehen, zum Beispiel auf der lokalen Zeitleiste oder in Hashtags. Vielleicht möchtest du dich vorstellen mit dem #introductions-Hashtag.'
|
||||||
|
@ -997,5 +997,5 @@ de:
|
||||||
seamless_external_login: Du bist angemeldet über einen Drittanbieter-Dienst, weswegen Passwort- und E-Maileinstellungen nicht verfügbar sind.
|
seamless_external_login: Du bist angemeldet über einen Drittanbieter-Dienst, weswegen Passwort- und E-Maileinstellungen nicht verfügbar sind.
|
||||||
signed_in_as: 'Angemeldet als:'
|
signed_in_as: 'Angemeldet als:'
|
||||||
verification:
|
verification:
|
||||||
explanation_html: 'Du kannst <strong>bestätigen, dass die Links in deinen Profil-Metadaten dir gehören</strong>. Dafür muss die verlinkte Website einen Link zurück auf dein Mastodon-Profil enthalten. Dieser Link <strong>muss</strong> ein <code>rel="me"</code>-Attribut enthalten. Der Linktext is dabei egal. Hier ist ein Beispiel:'
|
explanation_html: 'Du kannst <strong>bestätigen, dass die Links in deinen Profil-Metadaten dir gehören</strong>. Dafür muss die verlinkte Website einen Link zurück auf dein Mastodon-Profil enthalten. Dieser Link <strong>muss</strong> ein <code>rel="me"</code>-Attribut enthalten. Der Linktext ist dabei egal. Hier ist ein Beispiel:'
|
||||||
verification: Verifizierung
|
verification: Verifizierung
|
||||||
|
|
|
@ -17,11 +17,33 @@ sr:
|
||||||
unconfirmed: Пре наставка морате потврдити свој налог.
|
unconfirmed: Пре наставка морате потврдити свој налог.
|
||||||
mailer:
|
mailer:
|
||||||
confirmation_instructions:
|
confirmation_instructions:
|
||||||
|
action: Потврдите адресу е-поште
|
||||||
|
action_with_app: Потврди и врати се на %{app}
|
||||||
|
explanation: Направили сте налог на %{host} са адресом ове е-поште. На један клик сте удаљени од активирања. Ако ово нисте ви, молимо игноришите ову е-пошту.
|
||||||
|
extra_html: Молимо да такође проверите <a href="%{terms_path}"> правила ове инстанце и <a href="%{policy_path}"> наше услове коришћења.
|
||||||
subject: 'Мастодонт: Упутство за потврду корисничког налога на инстанци %{instance}'
|
subject: 'Мастодонт: Упутство за потврду корисничког налога на инстанци %{instance}'
|
||||||
|
title: Потврдите адресу е-поште
|
||||||
|
email_changed:
|
||||||
|
explanation: 'Адреса ове е-поште за ваш налог ће бити промењена у:'
|
||||||
|
extra: Ако нисте променили вашу е-пошту, сасвим је могуће да је неко други добио приступ вашем налогу. Молимо промените лозинку одмах или контактирајте администратора инстанце ако сте закључани изван вашег налога.
|
||||||
|
subject: 'Мастодон: Е-пошта промењена'
|
||||||
|
title: Нова адреса е-поште
|
||||||
password_change:
|
password_change:
|
||||||
|
explanation: Лозинка вашег налога је промењена.
|
||||||
|
extra: Ако нисте променили вашу е-пошту, сасвим је могуће да је неко други добио приступ вашем налогу. Молимо промените лозинку одмах или контактирајте администратора инстанце ако сте закључани изван вашег налога.
|
||||||
subject: 'Мастодонт: Лозинка промењена'
|
subject: 'Мастодонт: Лозинка промењена'
|
||||||
|
title: Лозинка промењена
|
||||||
|
reconfirmation_instructions:
|
||||||
|
explanation: Потврдите нову адресу да бисте променили е-пошту.
|
||||||
|
extra: Ако ова промена није иницирана са ваше стране, молимо игноришите ову е-пошту. Адреса е-пошта за овај Мастодон налог неће бити промењена док не приступите повезници/линку изнад.
|
||||||
|
subject: 'Мастодон: Потврдите е-пошту за %{instance}'
|
||||||
|
title: Потврдите адресу е-поште
|
||||||
reset_password_instructions:
|
reset_password_instructions:
|
||||||
|
action: Лозинка промењена
|
||||||
|
explanation: Затражили сте нову лозинку за ваш налог.
|
||||||
|
extra: Ако нисте затражили ово, молимо игноришите ову е-пошту. Ваша лозинка неће бити промењена док не приступите повезници/линку изнад и не направите нову.
|
||||||
subject: 'Мастодонт: Упутство за ресетовање лозинке'
|
subject: 'Мастодонт: Упутство за ресетовање лозинке'
|
||||||
|
title: Лозинка ресетована
|
||||||
unlock_instructions:
|
unlock_instructions:
|
||||||
subject: 'Мастодонт: Упутство за откључавање корисничког налога'
|
subject: 'Мастодонт: Упутство за откључавање корисничког налога'
|
||||||
omniauth_callbacks:
|
omniauth_callbacks:
|
||||||
|
|
|
@ -72,7 +72,7 @@ cs:
|
||||||
index:
|
index:
|
||||||
application: Aplikace
|
application: Aplikace
|
||||||
created_at: Autorizováno
|
created_at: Autorizováno
|
||||||
date_format: "%d.%m.%Y %H:%M:%S"
|
date_format: "%d. %m. %Y %H:%M:%S"
|
||||||
scopes: Rozsahy
|
scopes: Rozsahy
|
||||||
title: Vaše autorizované aplikace
|
title: Vaše autorizované aplikace
|
||||||
errors:
|
errors:
|
||||||
|
|
|
@ -556,8 +556,11 @@ en:
|
||||||
warning_title: Disseminated content availability
|
warning_title: Disseminated content availability
|
||||||
directories:
|
directories:
|
||||||
directory: Profile directory
|
directory: Profile directory
|
||||||
|
enabled: You are currently listed in the directory.
|
||||||
|
enabled_but_waiting: You have opted-in to be listed in the directory, but you do not have the minimum number of followers (%{min_followers}) to be listed yet.
|
||||||
explanation: Discover users based on their interests
|
explanation: Discover users based on their interests
|
||||||
explore_mastodon: Explore %{title}
|
explore_mastodon: Explore %{title}
|
||||||
|
how_to_enable: You are not currently opted-in to the directory. You can opt-in below. Use hashtags in your bio text to be listed under specific hashtags!
|
||||||
people:
|
people:
|
||||||
one: "%{count} person"
|
one: "%{count} person"
|
||||||
other: "%{count} people"
|
other: "%{count} people"
|
||||||
|
|
|
@ -1,6 +1,8 @@
|
||||||
---
|
---
|
||||||
ro:
|
ro:
|
||||||
about:
|
about:
|
||||||
|
features:
|
||||||
|
not_a_product_title: Ești o persoană, nu un produs
|
||||||
hosted_on: Mastodon găzduit de %{domain}
|
hosted_on: Mastodon găzduit de %{domain}
|
||||||
accounts:
|
accounts:
|
||||||
posts:
|
posts:
|
||||||
|
@ -64,7 +66,7 @@ ro:
|
||||||
success_msg: Contul tău a fost șterg. Nu mai poate fi recuperat :D
|
success_msg: Contul tău a fost șterg. Nu mai poate fi recuperat :D
|
||||||
warning_html: Doar ștergerea conținutului de pe acest server este garantată. Conținutul tău care a fost redistribuit în alte instațe e posibil să lase urme. Serverele deconecate sau care nu mai sunt abonate la actualizările contului tău nu își vor mai actualiza baza de date.
|
warning_html: Doar ștergerea conținutului de pe acest server este garantată. Conținutul tău care a fost redistribuit în alte instațe e posibil să lase urme. Serverele deconecate sau care nu mai sunt abonate la actualizările contului tău nu își vor mai actualiza baza de date.
|
||||||
directories:
|
directories:
|
||||||
explanation: Descoperă utilizatori în funcție de interesele lor
|
explanation: Descoperă oameni și companii în funcție de interesele lor
|
||||||
explore_mastodon: Explorează %{title}
|
explore_mastodon: Explorează %{title}
|
||||||
people:
|
people:
|
||||||
few: "%{count} persoană"
|
few: "%{count} persoană"
|
||||||
|
|
|
@ -10,14 +10,14 @@ de:
|
||||||
type_html: Wähle aus, was du mit <strong>%{acct}</strong> machen möchtest
|
type_html: Wähle aus, was du mit <strong>%{acct}</strong> machen möchtest
|
||||||
warning_preset_id: Optional. Du kannst immer noch eigenen Text an das Ende der Vorlage hinzufügen
|
warning_preset_id: Optional. Du kannst immer noch eigenen Text an das Ende der Vorlage hinzufügen
|
||||||
defaults:
|
defaults:
|
||||||
autofollow: Leute die sich über deine Einladung registrieren werden dir automatisch folgen
|
autofollow: Leute, die sich über deine Einladung registrieren, werden dir automatisch folgen
|
||||||
avatar: PNG, GIF oder JPG. Maximal %{size}. Wird auf %{dimensions} px herunterskaliert
|
avatar: PNG, GIF oder JPG. Maximal %{size}. Wird auf %{dimensions} px herunterskaliert
|
||||||
bot: Dieses Konto führt lediglich automatisierte Aktionen durch und wird möglicherweise nicht überwacht
|
bot: Dieses Konto führt lediglich automatisierte Aktionen durch und wird möglicherweise nicht überwacht
|
||||||
context: Ein oder mehrere Aspekte, wo der Filter greifen soll
|
context: Ein oder mehrere Aspekte, wo der Filter greifen soll
|
||||||
digest: Wenn du lange Zeit inaktiv bist, wird dir eine Zusammenfassung von Erwähnungen in deiner Abwesenheit zugeschickt
|
digest: Wenn du lange Zeit inaktiv bist, wird dir eine Zusammenfassung von Erwähnungen in deiner Abwesenheit zugeschickt
|
||||||
discoverable_html: Das <a href="%{path}" target="_blank">Verzeichnis</a> lässt dich neue Benutzerkonten finden basierend auf Interessen und Aktivitäten. Dies benötigt mindestens %{min_followers} Follower
|
discoverable_html: Das <a href="%{path}" target="_blank">Verzeichnis</a> lässt dich basierend auf Interessen und Aktivitäten neue Benutzerkonten finden. Dies benötigt mindestens %{min_followers} Follower
|
||||||
email: Du wirst ein Bestätigungs-E-Mail erhalten
|
email: Du wirst eine Bestätigungs-E-Mail erhalten
|
||||||
fields: Du kannst bis zu 4 Elemente als Tabelle dargestellt auf deinem Profil anzeigen lassen
|
fields: Du kannst bis zu 4 Elemente auf deinem Profil anzeigen lassen, die als Tabelle dargestellt werden
|
||||||
header: PNG, GIF oder JPG. Maximal %{size}. Wird auf %{dimensions} px herunterskaliert
|
header: PNG, GIF oder JPG. Maximal %{size}. Wird auf %{dimensions} px herunterskaliert
|
||||||
inbox_url: Kopiere die URL von der Startseite des gewünschten Relays
|
inbox_url: Kopiere die URL von der Startseite des gewünschten Relays
|
||||||
irreversible: Gefilterte Beiträge werden unwiderruflich gefiltert, selbst wenn der Filter später entfernt wurde
|
irreversible: Gefilterte Beiträge werden unwiderruflich gefiltert, selbst wenn der Filter später entfernt wurde
|
||||||
|
@ -31,11 +31,11 @@ de:
|
||||||
setting_display_media_default: Verstecke Medien, die als sensibel markiert sind
|
setting_display_media_default: Verstecke Medien, die als sensibel markiert sind
|
||||||
setting_display_media_hide_all: Alle Medien immer verstecken
|
setting_display_media_hide_all: Alle Medien immer verstecken
|
||||||
setting_display_media_show_all: Medien, die als sensibel markiert sind, immer anzeigen
|
setting_display_media_show_all: Medien, die als sensibel markiert sind, immer anzeigen
|
||||||
setting_hide_network: Wem du folgst und wer dir folgt wird in deinem Profil nicht angezeigt
|
setting_hide_network: Wem du folgst und wer dir folgt, wird in deinem Profil nicht angezeigt
|
||||||
setting_noindex: Betrifft dein öffentliches Profil und deine Beiträge
|
setting_noindex: Betrifft dein öffentliches Profil und deine Beiträge
|
||||||
setting_theme: Wirkt sich darauf aus, wie Mastodon aussieht, egal auf welchem Gerät du eingeloggt bist.
|
setting_theme: Wirkt sich darauf aus, wie Mastodon aussieht, egal auf welchem Gerät du eingeloggt bist.
|
||||||
username: Dein Benutzer:innen-Name wird auf %{domain} nur einmal vorkommen
|
username: Dein Benutzer:innen-Name wird auf %{domain} nur einmal vorkommen
|
||||||
whole_word: Wenn das Schlüsselwort oder -phrase nur Buchstaben und Zahlen enthält, wird es nur angewendet werden, wenn es dem ganzen Wort entspricht
|
whole_word: Wenn das Schlagwort oder die Phrase nur Buchstaben und Zahlen enthält, wird es nur angewendet, wenn es dem ganzen Wort entspricht
|
||||||
imports:
|
imports:
|
||||||
data: CSV-Datei, die aus einer anderen Mastodon-Instanz exportiert wurde
|
data: CSV-Datei, die aus einer anderen Mastodon-Instanz exportiert wurde
|
||||||
sessions:
|
sessions:
|
||||||
|
@ -60,7 +60,7 @@ de:
|
||||||
suspend: Deaktivieren und unwiderruflich Benutzerdaten löschen
|
suspend: Deaktivieren und unwiderruflich Benutzerdaten löschen
|
||||||
warning_preset_id: Benutze eine Warnungsvorlage
|
warning_preset_id: Benutze eine Warnungsvorlage
|
||||||
defaults:
|
defaults:
|
||||||
autofollow: Einladen, um deinen Account zu folgen
|
autofollow: Einladen, um deinem Account zu folgen
|
||||||
avatar: Profilbild
|
avatar: Profilbild
|
||||||
bot: Dieser Benutzer ist ein Bot
|
bot: Dieser Benutzer ist ein Bot
|
||||||
chosen_languages: Sprachen filtern
|
chosen_languages: Sprachen filtern
|
||||||
|
@ -76,7 +76,7 @@ de:
|
||||||
fields: Profil-Metadaten
|
fields: Profil-Metadaten
|
||||||
header: Kopfbild
|
header: Kopfbild
|
||||||
inbox_url: Inbox-URL des Relays
|
inbox_url: Inbox-URL des Relays
|
||||||
irreversible: Fallen lassen anstatt es zu verstecken
|
irreversible: Verwerfen statt verstecken
|
||||||
locale: Sprache der Benutzeroberfläche
|
locale: Sprache der Benutzeroberfläche
|
||||||
locked: Gesperrtes Profil
|
locked: Gesperrtes Profil
|
||||||
max_uses: Maximale Verwendungen
|
max_uses: Maximale Verwendungen
|
||||||
|
@ -90,28 +90,28 @@ de:
|
||||||
setting_boost_modal: Bestätigungsdialog anzeigen, bevor ein Beitrag geteilt wird
|
setting_boost_modal: Bestätigungsdialog anzeigen, bevor ein Beitrag geteilt wird
|
||||||
setting_default_language: Beitragssprache
|
setting_default_language: Beitragssprache
|
||||||
setting_default_privacy: Beitragssichtbarkeit
|
setting_default_privacy: Beitragssichtbarkeit
|
||||||
setting_default_sensitive: Medien immer als heikel markieren
|
setting_default_sensitive: Medien immer als sensibel markieren
|
||||||
setting_delete_modal: Bestätigungsdialog anzeigen, bevor ein Beitrag gelöscht wird
|
setting_delete_modal: Bestätigungsdialog anzeigen, bevor ein Beitrag gelöscht wird
|
||||||
setting_display_media: Medien-Anzeige
|
setting_display_media: Medien-Anzeige
|
||||||
setting_display_media_default: Standard
|
setting_display_media_default: Standard
|
||||||
setting_display_media_hide_all: Alle verstecken
|
setting_display_media_hide_all: Alle verstecken
|
||||||
setting_display_media_show_all: Alle anzeigen
|
setting_display_media_show_all: Alle anzeigen
|
||||||
setting_expand_spoilers: Toots mit Inhaltswarnungen immer ausklappen
|
setting_expand_spoilers: Beiträge mit Inhaltswarnungen immer ausklappen
|
||||||
setting_hide_network: Blende dein Netzwerk aus
|
setting_hide_network: Blende dein Netzwerk aus
|
||||||
setting_noindex: Suchmaschinen-Indexierung verhindern
|
setting_noindex: Suchmaschinen-Indexierung verhindern
|
||||||
setting_reduce_motion: Bewegung in Animationen verringern
|
setting_reduce_motion: Bewegung in Animationen verringern
|
||||||
setting_system_font_ui: Standardschriftart des Systems verwenden
|
setting_system_font_ui: Standardschriftart des Systems verwenden
|
||||||
setting_theme: Theme der Website
|
setting_theme: Theme der Website
|
||||||
setting_unfollow_modal: Bestätigungsdialog anzeigen, bevor jemand entfolgt wird
|
setting_unfollow_modal: Bestätigungsdialog anzeigen, bevor jemandem entfolgt wird
|
||||||
severity: Schweregrad
|
severity: Schweregrad
|
||||||
type: Importtyp
|
type: Importtyp
|
||||||
username: Profilname
|
username: Profilname
|
||||||
username_or_email: Profilname oder Email
|
username_or_email: Profilname oder E-Mail
|
||||||
whole_word: Ganzes Wort
|
whole_word: Ganzes Wort
|
||||||
interactions:
|
interactions:
|
||||||
must_be_follower: Benachrichtigungen von Nicht-Folgenden blockieren
|
must_be_follower: Benachrichtigungen von Nicht-Folgenden blockieren
|
||||||
must_be_following: Benachrichtigungen von Profilen blockieren, denen ich nicht folge
|
must_be_following: Benachrichtigungen von Profilen blockieren, denen ich nicht folge
|
||||||
must_be_following_dm: Private Nachrichten von Profilen denen ich nicht folge blockieren
|
must_be_following_dm: Private Nachrichten von Profilen, denen ich nicht folge, blockieren
|
||||||
notification_emails:
|
notification_emails:
|
||||||
digest: Schicke Übersichts-E-Mails
|
digest: Schicke Übersichts-E-Mails
|
||||||
favourite: E-Mail senden, wenn jemand meinen Beitrag favorisiert
|
favourite: E-Mail senden, wenn jemand meinen Beitrag favorisiert
|
||||||
|
|
|
@ -2,12 +2,21 @@
|
||||||
sr:
|
sr:
|
||||||
simple_form:
|
simple_form:
|
||||||
hints:
|
hints:
|
||||||
|
account_warning_preset:
|
||||||
|
text: Можете користити синтаксу труба, као што су нпр. УРЛ-ова, тарабе и помињања
|
||||||
|
admin_account_action:
|
||||||
|
send_email_notification: Корисник ће добити објашњење тога шта му се десило са налога
|
||||||
|
text_html: Опционално. Можете користити синтаксу труба. Можете <a href="%{path}">додати упозоравајућа преподешавање</a> да сачувате време
|
||||||
|
type_html: Изаберите шта да радите са <strong>%{acct}</strong>
|
||||||
|
warning_preset_id: Опционално. Можете и даље додати прилагођени текст на крај пресета
|
||||||
defaults:
|
defaults:
|
||||||
autofollow: Особе које се пријаве кроз позивнице ће вас аутоматски запратити
|
autofollow: Особе које се пријаве кроз позивнице ће вас аутоматски запратити
|
||||||
avatar: PNG, GIF или JPG. Највише %{size}. Биће смањена на %{dimensions}px
|
avatar: PNG, GIF или JPG. Највише %{size}. Биће смањена на %{dimensions}px
|
||||||
bot: Овај налог углавном врши аутоматизоване радње и можда се не надгледа
|
bot: Овај налог углавном врши аутоматизоване радње и можда се не надгледа
|
||||||
context: Један или више контекста у којима треба да се примени филтер
|
context: Један или више контекста у којима треба да се примени филтер
|
||||||
digest: Послато после дужег периода неактивности са прегледом свих битних ствари које сте добили док сте били одсутни
|
digest: Послато после дужег периода неактивности са прегледом свих битних ствари које сте добили док сте били одсутни
|
||||||
|
discoverable_html: <a href="%{path}" target="_blank">Директоријум</a> омогућава људима да пронађу налоге засноване на интересима и активности. Захтева бар %{min_followers} пратиоца
|
||||||
|
email: Биће вам послата е-пошта са потврдом
|
||||||
fields: Можете имати до 4 ставке приказане као табела на вашем профилу
|
fields: Можете имати до 4 ставке приказане као табела на вашем профилу
|
||||||
header: PNG, GIF или JPG. Највише %{size}. Биће смањена на %{dimensions}px
|
header: PNG, GIF или JPG. Највише %{size}. Биће смањена на %{dimensions}px
|
||||||
inbox_url: Копирајте URL са насловне стране релеја који желите користити
|
inbox_url: Копирајте URL са насловне стране релеја који желите користити
|
||||||
|
@ -17,6 +26,7 @@ sr:
|
||||||
password: Користите најмање 8 знакова
|
password: Користите најмање 8 знакова
|
||||||
phrase: Биће упарена без обзира на велико или мало слово у тексту или упозорења о садржају трубе
|
phrase: Биће упарена без обзира на велико или мало слово у тексту или упозорења о садржају трубе
|
||||||
scopes: Којим API-јима ће апликација дозволити приступ. Ако изаберете опсег највишег нивоа, не морате одабрати појединачне.
|
scopes: Којим API-јима ће апликација дозволити приступ. Ако изаберете опсег највишег нивоа, не морате одабрати појединачне.
|
||||||
|
setting_aggregate_reblogs: Не показуј нова дељења за трубе које су недавно подељене (утиче само на недавно примљена дељења)
|
||||||
setting_default_language: Језик ваших труба може бити аутоматски откривен, али није увек прецизан
|
setting_default_language: Језик ваших труба може бити аутоматски откривен, али није увек прецизан
|
||||||
setting_hide_network: Кога пратите и ко вас прати неће бити приказано на вашем профилу
|
setting_hide_network: Кога пратите и ко вас прати неће бити приказано на вашем профилу
|
||||||
setting_noindex: Утиче на Ваш јавни профил и статусне стране
|
setting_noindex: Утиче на Ваш јавни профил и статусне стране
|
||||||
|
@ -33,6 +43,10 @@ sr:
|
||||||
fields:
|
fields:
|
||||||
name: Етикета
|
name: Етикета
|
||||||
value: Садржај
|
value: Садржај
|
||||||
|
account_warning_preset:
|
||||||
|
text: Текст пресета
|
||||||
|
admin_account_action:
|
||||||
|
warning_preset_id: Користи упозоравајући пресет
|
||||||
defaults:
|
defaults:
|
||||||
autofollow: Позовите да прати ваш налог
|
autofollow: Позовите да прати ваш налог
|
||||||
avatar: Аватар
|
avatar: Аватар
|
||||||
|
|
12
db/migrate/20190117114553_create_tombstones.rb
Normal file
12
db/migrate/20190117114553_create_tombstones.rb
Normal file
|
@ -0,0 +1,12 @@
|
||||||
|
class CreateTombstones < ActiveRecord::Migration[5.2]
|
||||||
|
def change
|
||||||
|
create_table :tombstones do |t|
|
||||||
|
t.belongs_to :account, foreign_key: { on_delete: :cascade }
|
||||||
|
t.string :uri, null: false
|
||||||
|
|
||||||
|
t.timestamps
|
||||||
|
end
|
||||||
|
|
||||||
|
add_index :tombstones, :uri
|
||||||
|
end
|
||||||
|
end
|
12
db/schema.rb
12
db/schema.rb
|
@ -10,7 +10,7 @@
|
||||||
#
|
#
|
||||||
# It's strongly recommended that you check this file into your version control system.
|
# It's strongly recommended that you check this file into your version control system.
|
||||||
|
|
||||||
ActiveRecord::Schema.define(version: 2019_01_03_124754) do
|
ActiveRecord::Schema.define(version: 2019_01_17_114553) do
|
||||||
|
|
||||||
# These are extensions that must be enabled in order to support this database
|
# These are extensions that must be enabled in order to support this database
|
||||||
enable_extension "plpgsql"
|
enable_extension "plpgsql"
|
||||||
|
@ -627,6 +627,15 @@ ActiveRecord::Schema.define(version: 2019_01_03_124754) do
|
||||||
t.index ["name"], name: "index_tags_on_name", unique: true
|
t.index ["name"], name: "index_tags_on_name", unique: true
|
||||||
end
|
end
|
||||||
|
|
||||||
|
create_table "tombstones", force: :cascade do |t|
|
||||||
|
t.bigint "account_id"
|
||||||
|
t.string "uri", null: false
|
||||||
|
t.datetime "created_at", null: false
|
||||||
|
t.datetime "updated_at", null: false
|
||||||
|
t.index ["account_id"], name: "index_tombstones_on_account_id"
|
||||||
|
t.index ["uri"], name: "index_tombstones_on_uri"
|
||||||
|
end
|
||||||
|
|
||||||
create_table "users", force: :cascade do |t|
|
create_table "users", force: :cascade do |t|
|
||||||
t.string "email", default: "", null: false
|
t.string "email", default: "", null: false
|
||||||
t.datetime "created_at", null: false
|
t.datetime "created_at", null: false
|
||||||
|
@ -757,6 +766,7 @@ ActiveRecord::Schema.define(version: 2019_01_03_124754) do
|
||||||
add_foreign_key "statuses_tags", "tags", name: "fk_3081861e21", on_delete: :cascade
|
add_foreign_key "statuses_tags", "tags", name: "fk_3081861e21", on_delete: :cascade
|
||||||
add_foreign_key "stream_entries", "accounts", name: "fk_5659b17554", on_delete: :cascade
|
add_foreign_key "stream_entries", "accounts", name: "fk_5659b17554", on_delete: :cascade
|
||||||
add_foreign_key "subscriptions", "accounts", name: "fk_9847d1cbb5", on_delete: :cascade
|
add_foreign_key "subscriptions", "accounts", name: "fk_9847d1cbb5", on_delete: :cascade
|
||||||
|
add_foreign_key "tombstones", "accounts", on_delete: :cascade
|
||||||
add_foreign_key "users", "accounts", name: "fk_50500f500d", on_delete: :cascade
|
add_foreign_key "users", "accounts", name: "fk_50500f500d", on_delete: :cascade
|
||||||
add_foreign_key "users", "invites", on_delete: :nullify
|
add_foreign_key "users", "invites", on_delete: :nullify
|
||||||
add_foreign_key "users", "oauth_applications", column: "created_by_application_id", on_delete: :nullify
|
add_foreign_key "users", "oauth_applications", column: "created_by_application_id", on_delete: :nullify
|
||||||
|
|
|
@ -7,6 +7,7 @@ require_relative 'mastodon/accounts_cli'
|
||||||
require_relative 'mastodon/feeds_cli'
|
require_relative 'mastodon/feeds_cli'
|
||||||
require_relative 'mastodon/settings_cli'
|
require_relative 'mastodon/settings_cli'
|
||||||
require_relative 'mastodon/domains_cli'
|
require_relative 'mastodon/domains_cli'
|
||||||
|
require_relative 'mastodon/version'
|
||||||
|
|
||||||
module Mastodon
|
module Mastodon
|
||||||
class CLI < Thor
|
class CLI < Thor
|
||||||
|
@ -31,5 +32,12 @@ module Mastodon
|
||||||
|
|
||||||
desc 'domains SUBCOMMAND ...ARGS', 'Manage account domains'
|
desc 'domains SUBCOMMAND ...ARGS', 'Manage account domains'
|
||||||
subcommand 'domains', Mastodon::DomainsCLI
|
subcommand 'domains', Mastodon::DomainsCLI
|
||||||
|
|
||||||
|
map %w(--version -v) => :version
|
||||||
|
|
||||||
|
desc 'version', 'Show version'
|
||||||
|
def version
|
||||||
|
say(Mastodon::Version.to_s)
|
||||||
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
|
@ -140,15 +140,8 @@ module Mastodon
|
||||||
end
|
end
|
||||||
|
|
||||||
def stats_to_json(stats)
|
def stats_to_json(stats)
|
||||||
totals.each_key do |domain|
|
stats.compact!
|
||||||
if totals[domain].is_a?(Hash)
|
say(Oj.dump(stats))
|
||||||
totals[domain]['activity'] = stats[domain]
|
|
||||||
else
|
|
||||||
totals.delete(domain)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
say(Oj.dump(totals))
|
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
|
@ -21,7 +21,7 @@ module Mastodon
|
||||||
end
|
end
|
||||||
|
|
||||||
def flags
|
def flags
|
||||||
'rc1'
|
'rc3'
|
||||||
end
|
end
|
||||||
|
|
||||||
def to_a
|
def to_a
|
||||||
|
|
|
@ -1,9 +1,9 @@
|
||||||
# frozen_string_literal: true
|
# frozen_string_literal: true
|
||||||
|
|
||||||
namespace :repo do
|
namespace :repo do
|
||||||
desc 'Generate the authors.md file'
|
desc 'Generate the AUTHORS.md file'
|
||||||
task :authors do
|
task :authors do
|
||||||
file = File.open('AUTHORS.md', 'w')
|
file = File.open(Rails.root.join('AUTHORS.md'), 'w')
|
||||||
file << <<~HEADER
|
file << <<~HEADER
|
||||||
Mastodon is available on [GitHub](https://github.com/tootsuite/mastodon)
|
Mastodon is available on [GitHub](https://github.com/tootsuite/mastodon)
|
||||||
and provided thanks to the work of the following contributors:
|
and provided thanks to the work of the following contributors:
|
||||||
|
@ -27,4 +27,50 @@ namespace :repo do
|
||||||
This document is provided for informational purposes only. Since it is only updated once per release, the version you are looking at may be currently out of date. To see the full list of contributors, consider looking at the [git history](https://github.com/tootsuite/mastodon/graphs/contributors) instead.
|
This document is provided for informational purposes only. Since it is only updated once per release, the version you are looking at may be currently out of date. To see the full list of contributors, consider looking at the [git history](https://github.com/tootsuite/mastodon/graphs/contributors) instead.
|
||||||
FOOTER
|
FOOTER
|
||||||
end
|
end
|
||||||
|
|
||||||
|
desc 'Replace pull requests with authors in the CHANGELOG.md file'
|
||||||
|
task :changelog do
|
||||||
|
path = Rails.root.join('CHANGELOG.md')
|
||||||
|
tmp = Tempfile.new
|
||||||
|
|
||||||
|
HttpLog.config.compact_log = true
|
||||||
|
|
||||||
|
begin
|
||||||
|
File.open(path, 'r') do |file|
|
||||||
|
file.each_line do |line|
|
||||||
|
if line.start_with?('-')
|
||||||
|
new_line = line.gsub(/#([[:digit:]]+)*/) do |pull_request_reference|
|
||||||
|
pull_request_number = pull_request_reference[1..-1]
|
||||||
|
response = nil
|
||||||
|
|
||||||
|
loop do
|
||||||
|
response = HTTP.headers('Authorization' => "token #{ENV['GITHUB_API_TOKEN']}").get("https://api.github.com/repos/tootsuite/mastodon/pulls/#{pull_request_number}")
|
||||||
|
|
||||||
|
if response.code == 403
|
||||||
|
sleep_for = (response.headers['X-RateLimit-Reset'].to_i - Time.now.to_i).abs
|
||||||
|
puts "Sleeping for #{sleep_for} seconds to get over rate limit"
|
||||||
|
sleep sleep_for
|
||||||
|
else
|
||||||
|
break
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
pull_request = Oj.load(response.to_s)
|
||||||
|
"[#{pull_request['user']['login']}](#{pull_request['html_url']})"
|
||||||
|
end
|
||||||
|
|
||||||
|
tmp.puts new_line
|
||||||
|
else
|
||||||
|
tmp.puts line
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
tmp.close
|
||||||
|
FileUtils.mv(tmp.path, path)
|
||||||
|
ensure
|
||||||
|
tmp.close
|
||||||
|
tmp.unlink
|
||||||
|
end
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
|
@ -100,6 +100,7 @@
|
||||||
"react-immutable-proptypes": "^2.1.0",
|
"react-immutable-proptypes": "^2.1.0",
|
||||||
"react-immutable-pure-component": "^1.1.1",
|
"react-immutable-pure-component": "^1.1.1",
|
||||||
"react-intl": "^2.7.2",
|
"react-intl": "^2.7.2",
|
||||||
|
"react-masonry-infinite": "^1.2.2",
|
||||||
"react-motion": "^0.5.2",
|
"react-motion": "^0.5.2",
|
||||||
"react-notification": "^6.8.4",
|
"react-notification": "^6.8.4",
|
||||||
"react-overlays": "^0.8.3",
|
"react-overlays": "^0.8.3",
|
||||||
|
|
|
@ -17,7 +17,7 @@ RSpec.describe TagsController, type: :controller do
|
||||||
|
|
||||||
it 'renders application layout' do
|
it 'renders application layout' do
|
||||||
get :show, params: { id: 'test', max_id: late.id }
|
get :show, params: { id: 'test', max_id: late.id }
|
||||||
expect(response).to render_template layout: 'application'
|
expect(response).to render_template layout: 'public'
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
|
@ -56,4 +56,22 @@ RSpec.describe UnfollowService, type: :service do
|
||||||
expect(a_request(:post, 'http://example.com/inbox')).to have_been_made.once
|
expect(a_request(:post, 'http://example.com/inbox')).to have_been_made.once
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
describe 'remote ActivityPub (reverse)' do
|
||||||
|
let(:bob) { Fabricate(:user, email: 'bob@example.com', account: Fabricate(:account, username: 'bob', protocol: :activitypub, domain: 'example.com', inbox_url: 'http://example.com/inbox')).account }
|
||||||
|
|
||||||
|
before do
|
||||||
|
bob.follow!(sender)
|
||||||
|
stub_request(:post, 'http://example.com/inbox').to_return(status: 200)
|
||||||
|
subject.call(bob, sender)
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'destroys the following relation' do
|
||||||
|
expect(bob.following?(sender)).to be false
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'sends a reject activity' do
|
||||||
|
expect(a_request(:post, 'http://example.com/inbox')).to have_been_made.once
|
||||||
|
end
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
28
yarn.lock
28
yarn.lock
|
@ -1685,6 +1685,13 @@ braces@^2.3.0, braces@^2.3.1:
|
||||||
split-string "^3.0.2"
|
split-string "^3.0.2"
|
||||||
to-regex "^3.0.1"
|
to-regex "^3.0.1"
|
||||||
|
|
||||||
|
bricks.js@^1.7.0:
|
||||||
|
version "1.8.0"
|
||||||
|
resolved "https://registry.yarnpkg.com/bricks.js/-/bricks.js-1.8.0.tgz#8fdeb3c0226af251f4d5727a7df7f9ac0092b4b2"
|
||||||
|
integrity sha1-j96zwCJq8lH01XJ6fff5rACStLI=
|
||||||
|
dependencies:
|
||||||
|
knot.js "^1.1.5"
|
||||||
|
|
||||||
brorand@^1.0.1:
|
brorand@^1.0.1:
|
||||||
version "1.1.0"
|
version "1.1.0"
|
||||||
resolved "https://registry.yarnpkg.com/brorand/-/brorand-1.1.0.tgz#12c25efe40a45e3c323eb8675a0a0ce57b22371f"
|
resolved "https://registry.yarnpkg.com/brorand/-/brorand-1.1.0.tgz#12c25efe40a45e3c323eb8675a0a0ce57b22371f"
|
||||||
|
@ -5536,6 +5543,11 @@ kleur@^2.0.1:
|
||||||
resolved "https://registry.yarnpkg.com/kleur/-/kleur-2.0.2.tgz#b704f4944d95e255d038f0cb05fb8a602c55a300"
|
resolved "https://registry.yarnpkg.com/kleur/-/kleur-2.0.2.tgz#b704f4944d95e255d038f0cb05fb8a602c55a300"
|
||||||
integrity sha512-77XF9iTllATmG9lSlIv0qdQ2BQ/h9t0bJllHlbvsQ0zUWfU7Yi0S8L5JXzPZgkefIiajLmBJJ4BsMJmqcf7oxQ==
|
integrity sha512-77XF9iTllATmG9lSlIv0qdQ2BQ/h9t0bJllHlbvsQ0zUWfU7Yi0S8L5JXzPZgkefIiajLmBJJ4BsMJmqcf7oxQ==
|
||||||
|
|
||||||
|
knot.js@^1.1.5:
|
||||||
|
version "1.1.5"
|
||||||
|
resolved "https://registry.yarnpkg.com/knot.js/-/knot.js-1.1.5.tgz#28e72522f703f50fe98812fde224dd72728fef5d"
|
||||||
|
integrity sha1-KOclIvcD9Q/piBL94iTdcnKP710=
|
||||||
|
|
||||||
lcid@^1.0.0:
|
lcid@^1.0.0:
|
||||||
version "1.0.0"
|
version "1.0.0"
|
||||||
resolved "https://registry.yarnpkg.com/lcid/-/lcid-1.0.0.tgz#308accafa0bc483a3867b4b6f2b9506251d1b835"
|
resolved "https://registry.yarnpkg.com/lcid/-/lcid-1.0.0.tgz#308accafa0bc483a3867b4b6f2b9506251d1b835"
|
||||||
|
@ -7566,6 +7578,13 @@ react-immutable-pure-component@^1.1.1:
|
||||||
optionalDependencies:
|
optionalDependencies:
|
||||||
"@types/react" "16.4.6"
|
"@types/react" "16.4.6"
|
||||||
|
|
||||||
|
react-infinite-scroller@^1.0.12:
|
||||||
|
version "1.2.4"
|
||||||
|
resolved "https://registry.yarnpkg.com/react-infinite-scroller/-/react-infinite-scroller-1.2.4.tgz#f67eaec4940a4ce6417bebdd6e3433bfc38826e9"
|
||||||
|
integrity sha512-/oOa0QhZjXPqaD6sictN2edFMsd3kkMiE19Vcz5JDgHpzEJVqYcmq+V3mkwO88087kvKGe1URNksHEOt839Ubw==
|
||||||
|
dependencies:
|
||||||
|
prop-types "^15.5.8"
|
||||||
|
|
||||||
react-input-autosize@^2.2.1:
|
react-input-autosize@^2.2.1:
|
||||||
version "2.2.1"
|
version "2.2.1"
|
||||||
resolved "https://registry.yarnpkg.com/react-input-autosize/-/react-input-autosize-2.2.1.tgz#ec428fa15b1592994fb5f9aa15bb1eb6baf420f8"
|
resolved "https://registry.yarnpkg.com/react-input-autosize/-/react-input-autosize-2.2.1.tgz#ec428fa15b1592994fb5f9aa15bb1eb6baf420f8"
|
||||||
|
@ -7604,6 +7623,15 @@ react-lifecycles-compat@^3.0.2, react-lifecycles-compat@^3.0.4:
|
||||||
resolved "https://registry.yarnpkg.com/react-lifecycles-compat/-/react-lifecycles-compat-3.0.4.tgz#4f1a273afdfc8f3488a8c516bfda78f872352362"
|
resolved "https://registry.yarnpkg.com/react-lifecycles-compat/-/react-lifecycles-compat-3.0.4.tgz#4f1a273afdfc8f3488a8c516bfda78f872352362"
|
||||||
integrity sha512-fBASbA6LnOU9dOU2eW7aQ8xmYBSXUIWr+UmF9b1efZBazGNO+rcXT/icdKnYm2pTwcRylVUYwW7H1PHfLekVzA==
|
integrity sha512-fBASbA6LnOU9dOU2eW7aQ8xmYBSXUIWr+UmF9b1efZBazGNO+rcXT/icdKnYm2pTwcRylVUYwW7H1PHfLekVzA==
|
||||||
|
|
||||||
|
react-masonry-infinite@^1.2.2:
|
||||||
|
version "1.2.2"
|
||||||
|
resolved "https://registry.yarnpkg.com/react-masonry-infinite/-/react-masonry-infinite-1.2.2.tgz#20c1386f9ccdda9747527c8f42bc2c02dd2e7951"
|
||||||
|
integrity sha1-IME4b5zN2pdHUnyPQrwsAt0ueVE=
|
||||||
|
dependencies:
|
||||||
|
bricks.js "^1.7.0"
|
||||||
|
prop-types "^15.5.10"
|
||||||
|
react-infinite-scroller "^1.0.12"
|
||||||
|
|
||||||
react-motion@^0.5.2:
|
react-motion@^0.5.2:
|
||||||
version "0.5.2"
|
version "0.5.2"
|
||||||
resolved "https://registry.yarnpkg.com/react-motion/-/react-motion-0.5.2.tgz#0dd3a69e411316567927917c6626551ba0607316"
|
resolved "https://registry.yarnpkg.com/react-motion/-/react-motion-0.5.2.tgz#0dd3a69e411316567927917c6626551ba0607316"
|
||||||
|
|
Loading…
Reference in a new issue