Compare commits

...

135 commits

Author SHA1 Message Date
Izalia Mae 088fd46b67 Merge branch 'main' of https://git.barkshark.xyz/mirror/mastodon 2023-01-26 05:40:01 -05:00
Claire 7acf26e777
Merge pull request #2094 from ClearlyClaire/glitch-soc/merge-upstream
Merge upstream changes
2023-01-25 21:58:31 +01:00
Claire 2093436349 [Glitch] Fix styling of featured tags in light theme
Port e5ae75bf6a to glitch-soc

Signed-off-by: Claire <claire.github-309c@sitedethib.com>
2023-01-25 17:27:23 +01:00
Claire 0f4637981b Merge branch 'main' into glitch-soc/merge-upstream 2023-01-25 17:26:38 +01:00
Claire e5ae75bf6a
Fix styling of featured tags in light theme (#23252)
* Fix styling of featured tags in light theme

Fixes #23251

* Remove broken highlighting on /settings/featured_tags
2023-01-25 16:28:29 +01:00
Claire 2f112432e6
Bump version to 4.1.0rc2 (#23220) 2023-01-25 16:20:54 +01:00
Claire ffea668076 [Glitch] Fix missing filtering on some notification types
Port 98779535fe to glitch-soc

Signed-off-by: Claire <claire.github-309c@sitedethib.com>
2023-01-24 20:43:19 +01:00
Claire 853d0f28c5 [Glitch] Add lang attribute to compose textarea and CW field
Port 83a8efa9ca to glitch-soc

Signed-off-by: Claire <claire.github-309c@sitedethib.com>
2023-01-24 20:40:48 +01:00
Mina Her 78b822c61d [Glitch] Make <Audio> to handle volume change
Port 3cf60ba267 to glitch-soc

Signed-off-by: Claire <claire.github-309c@sitedethib.com>
2023-01-24 20:37:37 +01:00
Claire cf3ad10e75 [Glitch] Fix upload area display in single-column mode
Port 54e798a5a0 to glitch-soc

Signed-off-by: Claire <claire.github-309c@sitedethib.com>
2023-01-24 20:36:59 +01:00
Claire 3074338d79 Merge branch 'main' into glitch-soc/merge-upstream 2023-01-24 20:32:31 +01:00
Claire a5a00d7f7a
Fix email with empty domain name labels passing validation (#23246)
* Fix email with empty domain name labels passing validation

`EmailMxValidator` would allow empty labels because `Resolv::DNS` is
particularly lenient about them, but the email would be invalid and
unusable.

* Add tests
2023-01-24 20:18:41 +01:00
Claire dd58db64d8
Change email address input to be disabled for logged-in users when requesting a new confirmation e-mail (#23247)
Fixes #23093
2023-01-24 20:18:25 +01:00
Claire 6883fddb19
Fix account activation being triggered before email confirmation (#23245)
* Add tests

* Fix account activation being triggered before email confirmation

Fixes #23098
2023-01-24 19:40:21 +01:00
Claire 4725191d3c
Fix moderation audit log items for warnings having incorrect links (#23242) 2023-01-24 18:50:13 +01:00
Claire 83a8efa9ca
Add lang attribute to compose textarea and CW field (#23240)
Fixes #19858
2023-01-24 18:49:21 +01:00
dependabot[bot] dcdf081c6f
Bump @babel/runtime from 7.20.7 to 7.20.13 (#23226)
Bumps [@babel/runtime](https://github.com/babel/babel/tree/HEAD/packages/babel-runtime) from 7.20.7 to 7.20.13.
- [Release notes](https://github.com/babel/babel/releases)
- [Changelog](https://github.com/babel/babel/blob/main/CHANGELOG.md)
- [Commits](https://github.com/babel/babel/commits/v7.20.13/packages/babel-runtime)

---
updated-dependencies:
- dependency-name: "@babel/runtime"
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-01-24 17:52:06 +01:00
Mina Her 3cf60ba267
Make <Audio> to handle volume change (#23187) 2023-01-24 16:24:46 +01:00
dependabot[bot] 624d7ae51d
Bump @babel/plugin-proposal-decorators from 7.20.7 to 7.20.13 (#23235)
Bumps [@babel/plugin-proposal-decorators](https://github.com/babel/babel/tree/HEAD/packages/babel-plugin-proposal-decorators) from 7.20.7 to 7.20.13.
- [Release notes](https://github.com/babel/babel/releases)
- [Changelog](https://github.com/babel/babel/blob/main/CHANGELOG.md)
- [Commits](https://github.com/babel/babel/commits/v7.20.13/packages/babel-plugin-proposal-decorators)

---
updated-dependencies:
- dependency-name: "@babel/plugin-proposal-decorators"
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-01-24 12:43:40 +01:00
dependabot[bot] 6319845141
Bump jsdom from 21.0.0 to 21.1.0 (#23227)
Bumps [jsdom](https://github.com/jsdom/jsdom) from 21.0.0 to 21.1.0.
- [Release notes](https://github.com/jsdom/jsdom/releases)
- [Changelog](https://github.com/jsdom/jsdom/blob/master/Changelog.md)
- [Commits](https://github.com/jsdom/jsdom/compare/21.0.0...21.1.0)

---
updated-dependencies:
- dependency-name: jsdom
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-01-24 12:42:44 +01:00
dependabot[bot] 3579c9a842
Bump rimraf from 4.0.7 to 4.1.1 (#23225)
Bumps [rimraf](https://github.com/isaacs/rimraf) from 4.0.7 to 4.1.1.
- [Release notes](https://github.com/isaacs/rimraf/releases)
- [Changelog](https://github.com/isaacs/rimraf/blob/main/CHANGELOG.md)
- [Commits](https://github.com/isaacs/rimraf/compare/v4.0.7...v4.1.1)

---
updated-dependencies:
- dependency-name: rimraf
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-01-24 10:01:29 +01:00
dependabot[bot] 24f446d70b
Bump eslint-plugin-react from 7.31.11 to 7.32.1 (#23231)
Bumps [eslint-plugin-react](https://github.com/jsx-eslint/eslint-plugin-react) from 7.31.11 to 7.32.1.
- [Release notes](https://github.com/jsx-eslint/eslint-plugin-react/releases)
- [Changelog](https://github.com/jsx-eslint/eslint-plugin-react/blob/master/CHANGELOG.md)
- [Commits](https://github.com/jsx-eslint/eslint-plugin-react/compare/v7.31.11...v7.32.1)

---
updated-dependencies:
- dependency-name: eslint-plugin-react
  dependency-type: direct:development
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-01-24 09:49:28 +01:00
dependabot[bot] 7424dd0010
Bump axios from 1.2.2 to 1.2.3 (#23232)
Bumps [axios](https://github.com/axios/axios) from 1.2.2 to 1.2.3.
- [Release notes](https://github.com/axios/axios/releases)
- [Changelog](https://github.com/axios/axios/blob/v1.x/CHANGELOG.md)
- [Commits](https://github.com/axios/axios/compare/1.2.2...v1.2.3)

---
updated-dependencies:
- dependency-name: axios
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-01-24 09:48:48 +01:00
dependabot[bot] 637a7c78e6
Bump utf-8-validate from 6.0.0 to 6.0.1 (#23233)
Bumps [utf-8-validate](https://github.com/websockets/utf-8-validate) from 6.0.0 to 6.0.1.
- [Release notes](https://github.com/websockets/utf-8-validate/releases)
- [Commits](https://github.com/websockets/utf-8-validate/compare/v6.0.0...v6.0.1)

---
updated-dependencies:
- dependency-name: utf-8-validate
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-01-24 09:48:27 +01:00
dependabot[bot] aed9d4f567
Bump hadolint/hadolint-action from 3.0.0 to 3.1.0 (#23234)
Bumps [hadolint/hadolint-action](https://github.com/hadolint/hadolint-action) from 3.0.0 to 3.1.0.
- [Release notes](https://github.com/hadolint/hadolint-action/releases)
- [Changelog](https://github.com/hadolint/hadolint-action/blob/master/.releaserc)
- [Commits](https://github.com/hadolint/hadolint-action/compare/v3.0.0...v3.1.0)

---
updated-dependencies:
- dependency-name: hadolint/hadolint-action
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-01-24 09:48:05 +01:00
dependabot[bot] 23a2451576
Bump concurrent-ruby from 1.1.10 to 1.2.0 (#23236)
Bumps [concurrent-ruby](https://github.com/ruby-concurrency/concurrent-ruby) from 1.1.10 to 1.2.0.
- [Release notes](https://github.com/ruby-concurrency/concurrent-ruby/releases)
- [Changelog](https://github.com/ruby-concurrency/concurrent-ruby/blob/master/CHANGELOG.md)
- [Commits](https://github.com/ruby-concurrency/concurrent-ruby/compare/v1.1.10...v1.2.0)

---
updated-dependencies:
- dependency-name: concurrent-ruby
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-01-24 09:46:26 +01:00
dependabot[bot] 95fb53c53e
Bump rubocop from 1.43.0 to 1.44.0 (#23213)
Bumps [rubocop](https://github.com/rubocop/rubocop) from 1.43.0 to 1.44.0.
- [Release notes](https://github.com/rubocop/rubocop/releases)
- [Changelog](https://github.com/rubocop/rubocop/blob/master/CHANGELOG.md)
- [Commits](https://github.com/rubocop/rubocop/compare/v1.43.0...v1.44.0)

---
updated-dependencies:
- dependency-name: rubocop
  dependency-type: direct:development
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-01-23 18:03:20 +01:00
Claire 54e798a5a0
Fix upload area display in single-column mode (#23217) 2023-01-23 17:42:58 +01:00
Eugen Rochko 958955cda4
New Crowdin updates (#23150)
* New translations en.json (Romanian)

* New translations en.json (French)

* New translations en.json (Spanish)

* New translations en.json (Afrikaans)

* New translations en.json (Arabic)

* New translations en.json (Belarusian)

* New translations en.json (Bulgarian)

* New translations en.json (Catalan)

* New translations en.json (Czech)

* New translations en.json (Danish)

* New translations en.json (German)

* New translations en.json (Greek)

* New translations en.json (Frisian)

* New translations en.json (Basque)

* New translations en.json (Finnish)

* New translations en.json (Irish)

* New translations en.json (Hebrew)

* New translations en.json (Hungarian)

* New translations en.json (Armenian)

* New translations en.json (Italian)

* New translations en.json (Japanese)

* New translations en.json (Georgian)

* New translations en.json (Korean)

* New translations en.json (Lithuanian)

* New translations en.json (Macedonian)

* New translations en.json (Dutch)

* New translations en.json (Norwegian)

* New translations en.json (Punjabi)

* New translations en.json (Polish)

* New translations en.json (Portuguese)

* New translations en.json (Russian)

* New translations en.json (Slovak)

* New translations en.json (Slovenian)

* New translations en.json (Albanian)

* New translations en.json (Serbian (Cyrillic))

* New translations en.json (Swedish)

* New translations en.json (Turkish)

* New translations en.json (Ukrainian)

* New translations en.json (Chinese Simplified)

* New translations en.json (Chinese Traditional)

* New translations en.json (Urdu (Pakistan))

* New translations en.json (Vietnamese)

* New translations en.json (Galician)

* New translations en.json (Icelandic)

* New translations en.json (Portuguese, Brazilian)

* New translations en.json (Indonesian)

* New translations en.json (Persian)

* New translations en.json (Tamil)

* New translations en.json (Spanish, Argentina)

* New translations en.json (Spanish, Mexico)

* New translations en.json (Bengali)

* New translations en.json (Marathi)

* New translations en.json (Thai)

* New translations en.json (Croatian)

* New translations en.json (Norwegian Nynorsk)

* New translations en.json (Kazakh)

* New translations en.json (Estonian)

* New translations en.json (Latvian)

* New translations en.json (Hindi)

* New translations en.json (Malay)

* New translations en.json (Telugu)

* New translations en.json (English, United Kingdom)

* New translations en.json (Burmese)

* New translations en.json (Welsh)

* New translations en.json (Faroese)

* New translations en.json (Esperanto)

* New translations en.json (Uyghur)

* New translations en.json (Chinese Traditional, Hong Kong)

* New translations en.json (Tatar)

* New translations en.json (Malayalam)

* New translations en.json (Breton)

* New translations en.json (Latin)

* New translations en.json (Bosnian)

* New translations en.json (French, Quebec)

* New translations en.json (Sinhala)

* New translations en.json (Cornish)

* New translations en.json (Kannada)

* New translations en.json (Scottish Gaelic)

* New translations en.json (Asturian)

* New translations en.json (Aragonese)

* New translations en.json (Occitan)

* New translations en.json (Serbian (Latin))

* New translations en.json (Kurmanji (Kurdish))

* New translations en.json (Sorani (Kurdish))

* New translations en.json (Scots)

* New translations en.json (Igbo)

* New translations en.json (Corsican)

* New translations en.json (Sardinian)

* New translations en.json (Sanskrit)

* New translations en.json (Kabyle)

* New translations en.json (Ido)

* New translations en.json (Taigi)

* New translations en.json (Silesian)

* New translations en.json (Standard Moroccan Tamazight)

* New translations doorkeeper.en.yml (Welsh)

* New translations en.yml (Portuguese, Brazilian)

* New translations en.yml (Welsh)

* New translations simple_form.en.yml (Welsh)

* New translations activerecord.en.yml (Welsh)

* New translations devise.en.yml (Welsh)

* New translations en.json (Spanish)

* New translations en.json (German)

* New translations en.json (Frisian)

* New translations en.json (Hebrew)

* New translations en.json (Italian)

* New translations en.json (Polish)

* New translations en.json (Slovak)

* New translations en.json (Swedish)

* New translations en.json (Turkish)

* New translations en.json (Ukrainian)

* New translations en.json (Spanish, Mexico)

* New translations en.json (Faroese)

* New translations en.yml (Polish)

* New translations simple_form.en.yml (Frisian)

* New translations simple_form.en.yml (Spanish, Mexico)

* New translations en.json (Slovenian)

* New translations en.yml (Slovak)

* New translations en.yml (Slovenian)

* New translations en.json (Danish)

* New translations en.json (Chinese Traditional)

* New translations en.json (Serbian (Cyrillic))

* New translations en.json (Korean)

* New translations en.json (Japanese)

* New translations en.json (Korean)

* New translations en.yml (Japanese)

* New translations doorkeeper.en.yml (Japanese)

* New translations simple_form.en.yml (Japanese)

* New translations en.json (Hindi)

* New translations en.json (Galician)

* New translations simple_form.en.yml (Galician)

* New translations en.yml (Galician)

* New translations doorkeeper.en.yml (Galician)

* New translations en.json (Vietnamese)

* New translations en.yml (Vietnamese)

* New translations simple_form.en.yml (Vietnamese)

* New translations en.json (Estonian)

* New translations en.json (Hebrew)

* New translations doorkeeper.en.yml (Belarusian)

* New translations en.json (English, United Kingdom)

* New translations simple_form.en.yml (English, United Kingdom)

* New translations en.json (Portuguese)

* New translations en.yml (Slovak)

* New translations en.json (Hungarian)

* New translations en.json (Korean)

* New translations en.yml (Korean)

* New translations en.json (French)

* New translations en.json (Slovak)

* New translations en.yml (French)

* New translations simple_form.en.yml (French)

* New translations doorkeeper.en.yml (French)

* New translations en.json (Slovak)

* New translations en.yml (Esperanto)

* New translations en.yml (French)

* New translations simple_form.en.yml (French)

* New translations doorkeeper.en.yml (French)

* New translations en.json (Norwegian)

* New translations en.json (Thai)

* New translations en.json (Basque)

* New translations en.json (Tatar)

* New translations en.json (Estonian)

* New translations en.json (Finnish)

* New translations en.json (Estonian)

* New translations en.yml (Finnish)

* New translations simple_form.en.yml (Finnish)

* New translations en.json (Basque)

* New translations en.json (Basque)

* New translations doorkeeper.en.yml (Japanese)

* New translations en.yml (Basque)

* New translations en.json (Arabic)

* New translations en.json (Slovak)

* New translations en.json (Kabyle)

* New translations doorkeeper.en.yml (Arabic)

* New translations en.yml (Arabic)

* New translations simple_form.en.yml (Arabic)

* New translations en.yml (Kabyle)

* New translations en.json (German)

* New translations en.json (Japanese)

* New translations en.json (German)

* New translations en.json (Tatar)

* New translations en.json (Occitan)

* New translations en.yml (Occitan)

* New translations doorkeeper.en.yml (Occitan)

* New translations simple_form.en.yml (Occitan)

* New translations en.json (Tatar)

* New translations en.json (Esperanto)

* New translations en.yml (Esperanto)

* New translations en.yml (Esperanto)

* New translations en.json (Esperanto)

* New translations en.json (Esperanto)

* New translations en.yml (Esperanto)

* New translations simple_form.en.yml (Esperanto)

* New translations en.json (Estonian)

* New translations doorkeeper.en.yml (Estonian)

* New translations en.json (Estonian)

* New translations en.json (Esperanto)

* New translations en.yml (Esperanto)

* New translations doorkeeper.en.yml (Esperanto)

* New translations en.json (Esperanto)

* New translations en.yml (Esperanto)

* New translations simple_form.en.yml (English, United Kingdom)

* New translations en.json (English, United Kingdom)

* New translations en.json (Portuguese, Brazilian)

* New translations en.json (Portuguese, Brazilian)

* New translations doorkeeper.en.yml (Portuguese, Brazilian)

* New translations en.yml (Portuguese, Brazilian)

* New translations simple_form.en.yml (Portuguese, Brazilian)

* New translations en.json (Serbian (Latin))

* New translations en.json (Croatian)

* New translations en.json (Portuguese)

* New translations en.json (Esperanto)

* New translations en.yml (Esperanto)

* New translations en.json (Bulgarian)

* New translations en.json (Esperanto)

* New translations en.yml (Bulgarian)

* New translations simple_form.en.yml (Bulgarian)

* New translations en.yml (Galician)

* New translations en.json (Bulgarian)

* New translations en.json (Bulgarian)

* New translations en.yml (Japanese)

* New translations en.json (Bulgarian)

* New translations en.yml (Japanese)

* New translations devise.en.yml (Bulgarian)

* New translations activerecord.en.yml (Bulgarian)

* New translations devise.en.yml (Bulgarian)

* New translations doorkeeper.en.yml (Bulgarian)

* Normalize

Co-authored-by: Yamagishi Kazutoshi <ykzts@desire.sh>
2023-01-24 00:37:35 +09:00
Claire 98779535fe
Fix missing filtering on some notification types (#23211)
* Fix missing warning-type filtering on some notification types

* Fix missing hide-type filtering on some notification types
2023-01-23 13:21:50 +01:00
dependabot[bot] 77c2ea1f0f
Bump rubocop-rspec from 2.18.0 to 2.18.1 (#23203)
Bumps [rubocop-rspec](https://github.com/rubocop/rubocop-rspec) from 2.18.0 to 2.18.1.
- [Release notes](https://github.com/rubocop/rubocop-rspec/releases)
- [Changelog](https://github.com/rubocop/rubocop-rspec/blob/master/CHANGELOG.md)
- [Commits](https://github.com/rubocop/rubocop-rspec/compare/v2.18.0...v2.18.1)

---
updated-dependencies:
- dependency-name: rubocop-rspec
  dependency-type: direct:development
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-01-23 13:14:11 +01:00
Markus Unterwaditzer f2a6e71bb6
Suppress AddressFamilyError in link verification (#23204)
* Suppress AddressFamilyError

* clarify comment
2023-01-23 13:05:54 +01:00
Claire 448be26b34
Add missing policy attribute to WebPushSubscriptionSerializer (#23210)
* Add missing `policy` attribute to `WebPushSubscriptionSerializer`

Fixes #23145

* Add tests
2023-01-23 13:05:30 +01:00
Kaspar V 9b795a25cd
fix(pghero): update because CVE-2023-22626 (#23190)
There is a vulnerability
[CVE-2023-22626](https://github.com/advisories/GHSA-vf99-xw26-86g5)

```
Name: pghero
Version: 2.8.3
CVE: CVE-2023-22626
GHSA: GHSA-vf99-xw26-86g5
Criticality: High
URL: https://github.com/ankane/pghero/issues/439
Title: Information Disclosure Through EXPLAIN Feature
Solution: upgrade to '>= 3.1.0'
```
2023-01-22 23:09:02 +01:00
Claire 368d6fe54f
Merge pull request #2092 from ClearlyClaire/glitch-soc/merge-upstream
Merge upstream changes
2023-01-21 21:28:13 +01:00
Claire 3c76f1f6c2 Merge branch 'main' into glitch-soc/merge-upstream 2023-01-21 19:42:58 +01:00
Claire 628dcbb732
Revert "Remove LDSignature on actor Delete activities (#21466)" (#23185)
This reverts commit f4f2b062ec.
2023-01-21 15:33:21 +01:00
Eugen Rochko a4090ab646 [Glitch] Fix wrong text color on some buttons in light theme in web UI
Port 13e9d91ba7 to glitch-soc

Signed-off-by: Claire <claire.github-309c@sitedethib.com>
2023-01-21 15:08:51 +01:00
Eugen Rochko 0d20b38da7 [Glitch] Fix wrong padding in RTL layout in web UI
Port part of 4894deca7e to glitch-soc

Signed-off-by: Claire <claire.github-309c@sitedethib.com>
2023-01-21 15:07:52 +01:00
Claire 3fd3e88b25 Merge branch 'main' into glitch-soc/merge-upstream 2023-01-21 14:58:15 +01:00
Jeong Arm a1abda39dd
Fix Account Strike causing PG not null validation error (#23178) 2023-01-21 10:22:22 +01:00
Claire 8180f7ba19
Bump version to 4.1.0rc1 (#23112) 2023-01-20 14:19:12 +01:00
Vyr Cossont c4a5e0ca0e
Advertise supported MIME types for statuses (#2090) 2023-01-20 13:57:32 +01:00
Eugen Rochko 13e9d91ba7
Fix wrong text color on some buttons in light theme in web UI (#23156) 2023-01-19 15:51:27 +01:00
Eugen Rochko 4894deca7e
Fix wrong padding in RTL layout in web UI (#23157) 2023-01-19 15:51:14 +01:00
Claire 26c2b401a5
Fix missing admin CSS in glitch-soc flavor (#2088)
Port SCSS changes from 43f56f1291

Signed-off-by: Claire <claire.github-309c@sitedethib.com>

Signed-off-by: Claire <claire.github-309c@sitedethib.com>
Co-authored-by: Eugen Rochko <eugen@zeonfederated.com>
2023-01-18 18:54:04 +01:00
Claire 01405bc6f8
Merge pull request #2087 from ClearlyClaire/glitch-soc/merge-upstream
Merge upstream changes
2023-01-18 18:41:24 +01:00
Eugen Rochko a3f176423f
New Crowdin updates (#23052)
* New translations simple_form.en.yml (Hebrew)

* New translations simple_form.en.yml (Italian)

* New translations en.json (German)

* New translations en.yml (Estonian)

* New translations simple_form.en.yml (Estonian)

* New translations en.yml (Spanish, Argentina)

* New translations simple_form.en.yml (Spanish, Argentina)

* New translations simple_form.en.yml (Ukrainian)

* New translations doorkeeper.en.yml (Slovenian)

* New translations doorkeeper.en.yml (Chinese Traditional)

* New translations en.yml (Chinese Traditional)

* New translations en.yml (Slovenian)

* New translations simple_form.en.yml (Slovenian)

* New translations en.yml (Ukrainian)

* New translations simple_form.en.yml (Chinese Traditional)

* New translations en.json (Portuguese)

* New translations simple_form.en.yml (German)

* New translations simple_form.en.yml (Hebrew)

* New translations simple_form.en.yml (Slovenian)

* New translations en.json (Esperanto)

* New translations en.json (Esperanto)

* New translations en.yml (Esperanto)

* New translations simple_form.en.yml (Esperanto)

* New translations doorkeeper.en.yml (Esperanto)

* New translations en.yml (Thai)

* New translations simple_form.en.yml (Thai)

* New translations en.json (Thai)

* New translations doorkeeper.en.yml (Turkish)

* New translations doorkeeper.en.yml (Thai)

* New translations en.yml (Czech)

* New translations simple_form.en.yml (Czech)

* New translations en.yml (Turkish)

* New translations simple_form.en.yml (Turkish)

* New translations en.yml (Thai)

* New translations simple_form.en.yml (Thai)

* New translations en.yml (Latvian)

* New translations simple_form.en.yml (Latvian)

* New translations simple_form.en.yml (Galician)

* New translations en.yml (Galician)

* New translations en.json (Portuguese)

* New translations en.yml (Spanish, Mexico)

* New translations en.yml (Catalan)

* New translations en.yml (German)

* New translations en.yml (Finnish)

* New translations en.yml (Polish)

* New translations en.yml (Italian)

* New translations en.yml (Ukrainian)

* New translations en.yml (Spanish, Argentina)

* New translations en.json (Aragonese)

* New translations doorkeeper.en.yml (Aragonese)

* New translations en.yml (Aragonese)

* New translations simple_form.en.yml (Aragonese)

* New translations en.yml (German)

* New translations en.yml (Hebrew)

* New translations simple_form.en.yml (German)

* New translations en.yml (Slovak)

* New translations en.yml (Chinese Traditional)

* New translations en.yml (Korean)

* New translations simple_form.en.yml (Korean)

* New translations devise.en.yml (German)

* New translations en.yml (German)

* New translations en.yml (Slovak)

* New translations doorkeeper.en.yml (Slovak)

* New translations en.yml (Spanish)

* New translations en.yml (Czech)

* New translations simple_form.en.yml (Slovak)

* New translations en.yml (Slovenian)

* New translations en.yml (Albanian)

* New translations en.yml (Catalan)

* New translations en.yml (German)

* New translations simple_form.en.yml (Catalan)

* New translations en.yml (Portuguese)

* New translations en.yml (Galician)

* New translations en.yml (Chinese Simplified)

* New translations doorkeeper.en.yml (Russian)

* New translations simple_form.en.yml (Russian)

* New translations en.yml (Ukrainian)

* New translations simple_form.en.yml (Chinese Simplified)

* New translations en.yml (Faroese)

* New translations simple_form.en.yml (Faroese)

* New translations en.yml (Hungarian)

* New translations en.yml (Danish)

* New translations simple_form.en.yml (Danish)

* New translations en.yml (Chinese Traditional)

* New translations en.yml (Danish)

* New translations en.yml (Catalan)

* New translations simple_form.en.yml (Catalan)

* New translations doorkeeper.en.yml (Spanish, Mexico)

* New translations en.yml (Spanish, Mexico)

* New translations en.yml (Thai)

* New translations en.yml (Finnish)

* New translations en.yml (Latvian)

* New translations en.yml (Portuguese)

* New translations en.yml (Turkish)

* New translations simple_form.en.yml (Hungarian)

* New translations simple_form.en.yml (Hungarian)

* New translations en.json (Kazakh)

* New translations en.yml (Frisian)

* New translations simple_form.en.yml (Frisian)

* New translations en.yml (Norwegian Nynorsk)

* New translations en.yml (Polish)

* New translations simple_form.en.yml (Polish)

* New translations en.json (Irish)

* New translations en.json (Irish)

* New translations en.yml (Irish)

* New translations en.yml (Irish)

* New translations en.json (Irish)

* New translations en.yml (Irish)

* New translations doorkeeper.en.yml (Irish)

* New translations simple_form.en.yml (Irish)

* New translations devise.en.yml (Irish)

* New translations en.json (Persian)

* New translations en.json (Welsh)

* New translations en.yml (Welsh)

* New translations en.json (Welsh)

* New translations doorkeeper.en.yml (English, United Kingdom)

* New translations simple_form.en.yml (Welsh)

* New translations en.json (Spanish, Argentina)

* New translations en.json (English, United Kingdom)

* New translations simple_form.en.yml (Catalan)

* New translations simple_form.en.yml (English, United Kingdom)

* New translations simple_form.en.yml (German)

* New translations en.yml (Bulgarian)

* New translations doorkeeper.en.yml (Bulgarian)

* New translations simple_form.en.yml (Bulgarian)

* New translations doorkeeper.en.yml (Icelandic)

* New translations en.yml (Icelandic)

* New translations simple_form.en.yml (Icelandic)

* New translations activerecord.en.yml (Icelandic)

* New translations devise.en.yml (Icelandic)

* New translations en.json (Latin)

* New translations en.yml (Slovak)

* New translations en.yml (Slovak)

* New translations en.yml (Belarusian)

* New translations en.json (Estonian)

* New translations en.yml (Belarusian)

* New translations en.json (Albanian)

* New translations doorkeeper.en.yml (Albanian)

* New translations en.yml (Belarusian)

* New translations en.yml (Albanian)

* New translations simple_form.en.yml (Albanian)

* New translations activerecord.en.yml (Albanian)

* New translations devise.en.yml (Albanian)

* New translations en.json (Asturian)

* New translations en.json (Hindi)

* New translations en.yml (Dutch)

* New translations en.yml (Swedish)

* New translations en.yml (Estonian)

* New translations en.yml (Estonian)

* New translations en.json (Latvian)

* New translations en.yml (Latvian)

* New translations en.yml (Slovak)

* Normalize

* New translations en.json (Romanian)

* New translations en.json (French)

* New translations en.json (Spanish)

* New translations en.json (Afrikaans)

* New translations en.json (Arabic)

* New translations en.json (Belarusian)

* New translations en.json (Bulgarian)

* New translations en.json (Catalan)

* New translations en.json (Czech)

* New translations en.json (Danish)

* New translations en.json (German)

* New translations en.json (Greek)

* New translations en.json (Frisian)

* New translations en.json (Basque)

* New translations en.json (Finnish)

* New translations en.json (Irish)

* New translations en.json (Hebrew)

* New translations en.json (Hungarian)

* New translations en.json (Armenian)

* New translations en.json (Italian)

* New translations en.json (Japanese)

* New translations en.json (Georgian)

* New translations en.json (Korean)

* New translations en.json (Lithuanian)

* New translations en.json (Macedonian)

* New translations en.json (Dutch)

* New translations en.json (Norwegian)

* New translations en.json (Punjabi)

* New translations en.json (Polish)

* New translations en.json (Portuguese)

* New translations en.json (Russian)

* New translations en.json (Slovak)

* New translations en.json (Slovenian)

* New translations en.json (Albanian)

* New translations en.json (Serbian (Cyrillic))

* New translations en.json (Swedish)

* New translations en.json (Turkish)

* New translations en.json (Ukrainian)

* New translations en.json (Chinese Simplified)

* New translations en.json (Chinese Traditional)

* New translations en.json (Urdu (Pakistan))

* New translations en.json (Vietnamese)

* New translations en.json (Galician)

* New translations en.json (Icelandic)

* New translations en.json (Portuguese, Brazilian)

* New translations en.json (Indonesian)

* New translations en.json (Persian)

* New translations en.json (Tamil)

* New translations en.json (Spanish, Argentina)

* New translations en.json (Spanish, Mexico)

* New translations en.json (Bengali)

* New translations en.json (Marathi)

* New translations en.json (Thai)

* New translations en.json (Croatian)

* New translations en.json (Norwegian Nynorsk)

* New translations en.json (Kazakh)

* New translations en.json (Estonian)

* New translations en.json (Latvian)

* New translations en.json (Hindi)

* New translations en.json (Malay)

* New translations en.json (Telugu)

* New translations en.json (English, United Kingdom)

* New translations en.json (Burmese)

* New translations en.json (Welsh)

* New translations en.json (Faroese)

* New translations en.json (Esperanto)

* New translations en.json (Uyghur)

* New translations en.json (Chinese Traditional, Hong Kong)

* New translations en.json (Tatar)

* New translations en.json (Malayalam)

* New translations en.json (Breton)

* New translations en.json (Latin)

* New translations en.json (Bosnian)

* New translations en.json (French, Quebec)

* New translations en.json (Sinhala)

* New translations en.json (Cornish)

* New translations en.json (Kannada)

* New translations en.json (Scottish Gaelic)

* New translations en.json (Asturian)

* New translations en.json (Aragonese)

* New translations en.json (Occitan)

* New translations en.json (Serbian (Latin))

* New translations en.json (Kurmanji (Kurdish))

* New translations en.json (Sorani (Kurdish))

* New translations en.json (Scots)

* New translations en.json (Igbo)

* New translations en.json (Corsican)

* New translations en.json (Sardinian)

* New translations en.json (Sanskrit)

* New translations en.json (Kabyle)

* New translations en.json (Ido)

* New translations en.json (Taigi)

* New translations en.json (Silesian)

* New translations en.json (Standard Moroccan Tamazight)

* Normalize

Co-authored-by: Yamagishi Kazutoshi <ykzts@desire.sh>
2023-01-19 02:31:13 +09:00
Claire 3f74235ac5 [Glitch] Fix confusing wording in the sign in banner
Port 3588fbc766 to glitch-soc

Signed-off-by: Claire <claire.github-309c@sitedethib.com>
2023-01-18 17:59:06 +01:00
Connor Shea 00cc1536f2 [Glitch] Add listing of followed hashtags
Port 30e895299c to glitch-soc

Co-authored-by: Claire <claire.github-309c@sitedethib.com>
Signed-off-by: Claire <claire.github-309c@sitedethib.com>
2023-01-18 17:58:18 +01:00
Claire 55e368c02f [Glitch] Add option to make the landing page be /about even when trends are enabled
Port 3970a6f433 to glitch-soc

Signed-off-by: Claire <claire.github-309c@sitedethib.com>
2023-01-18 17:54:37 +01:00
Claire b5c6a116a7 [Glitch] Add support for editing media description and focus point of already-posted statuses
Port 4b92e59f4f to glitch-soc

Signed-off-by: Claire <claire.github-309c@sitedethib.com>
2023-01-18 17:52:45 +01:00
Claire 9b4afb320a [Glitch] Change account moderation notes to make links clickable
Port 9b3e22c40d to glitch-soc

Signed-off-by: Claire <claire.github-309c@sitedethib.com>
2023-01-18 17:49:29 +01:00
Peter Simonsson 9205b4e32f [Glitch] Add checkmark symbol to checkbox
Port 7e6ffa085f to glitch-soc

Signed-off-by: Claire <claire.github-309c@sitedethib.com>
2023-01-18 17:48:48 +01:00
JT Olio a5fd2fe1cb
Add Storj DCS to cloud object storage options (#21929)
* Add Storj DCS to cloud object storage options

More explanation here: https://forum.storj.io/t/object-storage-provider-for-mastodon-instance/11464/37

* more help for which command to use
2023-01-18 17:47:49 +01:00
Jeong Arm c87b1a20c7 [Glitch] Make visible change for new post notification setting icon
Port 1b2ef60cec to glitch-soc

Signed-off-by: Claire <claire.github-309c@sitedethib.com>
2023-01-18 17:45:27 +01:00
Claire 473fed2cdf [Glitch] Fix /api/v1/admin/trends/tags using wrong serializer
Port b034dc42be to glitch-soc

Signed-off-by: Claire <claire.github-309c@sitedethib.com>
2023-01-18 17:44:45 +01:00
Claire 60abcb3c4c Merge branch 'main' into glitch-soc/merge-upstream
Conflicts:
- `config/i18n-tasks.yml`:
  Upstream added new ignored strings, glitch-soc has extra ignored strings
  because of the theming system.
  Added upstream's changes.
2023-01-18 17:38:11 +01:00
Claire 3588fbc766
Fix confusing wording in the sign in banner (#22490)
* Fix confusing wording in the sign in banner

* Split into two sentences
2023-01-18 17:15:23 +01:00
Claire cb4e28f405
Add tootctl domains purge options to select subdomains and keep domain blocks (#22063)
* Add --include-subdomains option to tootctl domains purge

* Add support for '*.' subdomain wildcard patterns in `tootctl domains purge`

* Fix custom emojis deletion not following subdomain and URI options

* Change `tootctl domains purge` to not purge domain blocks unless --purge-domain-blocks is passed

* Refactor `tootctl domains purge`

* Add feedback on deleted domain blocks
2023-01-18 16:50:50 +01:00
Claire 68dcbcb7bf
Add more specific error messages to HTTP signature verification (#21617)
* Return specific error on failure to parse Date header

* Add error message when preferredUsername is not set

* Change error report to be JSON and include more details

* Change error report to differentiate unknown account and failed refresh

* Add tests
2023-01-18 16:47:56 +01:00
Connor Shea 30e895299c
Add listing of followed hashtags (#21773)
* Add followed_tags route.

This at least gets us to the point where the page can actually be
rendered, although it doesn't display any hashtags (yet?).

Attempting to implement #20763.

* Fix minor issues.

* I've got the followed tags data partially working

But the Hashtag component errors for some reason. Something about the
value of the history attribute being invalid.

* Fix a mistake in the code

* Minor change.

* Get the followed hashtags list fully working.

Still need to add the Follow/Unfollow buttons, though.

* Resolve JS linter issues.

* Add pagination logic to followed tags list view.

However, it currently loads further pages immediately on page load, so
that's not ideal. Need to figure that one out.

* Appease the linter.

* Apply suggestions from code review

Co-authored-by: Claire <claire.github-309c@sitedethib.com>

* Fixes and resolve some other feedback.

* Use set/update instead of setIn/updateIn.

Co-authored-by: Claire <claire.github-309c@sitedethib.com>
2023-01-18 16:44:33 +01:00
Claire 3970a6f433
Add option to make the landing page be /about even when trends are enabled (#20808)
* Add option to make the landing page be /about even when trends are enabled

* Restablish /explore as landing page by default
2023-01-18 16:43:58 +01:00
Claire 343e1fe8e9
Add confirmation screen when handling reports (#22375)
* Add confirmation screen on moderation actions

* Add flash notice when a report has been processed

* Refactor tests

* Add tests
2023-01-18 16:40:09 +01:00
Claire 4b92e59f4f
Add support for editing media description and focus point of already-posted statuses (#20878)
* Add backend support for editing media attachments of existing posts

* Allow editing media attachments of already-posted toots

* Add tests
2023-01-18 16:33:55 +01:00
Claire d1387579b9
Fix situations in which instance actor can be set to a Mastodon-incompatible name (#22307)
* Validate internal actor

* Use “internal.actor” by default for the server actor username

* Fix instance actor username on the fly if it includes ':'

* Change actor name from internal.actor to mastodon.internal
2023-01-18 16:33:03 +01:00
Claire 9b3e22c40d
Change account moderation notes to make links clickable (#22553)
* Change account moderation notes to make links clickable

Fixes #22539

* Fix styling of account moderation note links
2023-01-18 16:32:23 +01:00
Peter Simonsson 7e6ffa085f
Add checkmark symbol to checkbox (#22795) 2023-01-18 16:30:46 +01:00
Jeong Arm 1b2ef60cec
Make visible change for new post notification setting icon (#22541) 2023-01-18 16:29:07 +01:00
Claire b034dc42be
Fix /api/v1/admin/trends/tags using wrong serializer (#18943)
* Fix /api/v1/admin/trends/tags using wrong serializer

Fix regression from #18641

* Only use `REST::Admin::TagSerializer` when the user can `manage_taxonomies`

* Fix admin trending hashtag component to not link if `id` is unknown
2023-01-18 16:28:18 +01:00
Claire 0405be69d2
Fix REST API serializer for Account not including moved when the moved account has itself moved (#22483)
Instead of cutting immediately, cut after one recursion.
2023-01-18 16:25:31 +01:00
Claire d4f590d6bb
Fix scheduled_at input not using datetime-local when editing announcements (#21896) 2023-01-18 16:23:39 +01:00
Claire 41517a4845
Fix spurious admin dashboard warning when using ElasticSearch 7.x (#23064)
Some 7.x ElasticSearch versions support some 6.x nodes, thus the version check
is inadequate. I am not sure there is a good way to check if a server
implements all the 7.x APIs, so check server version and minimum wire version
instead.
2023-01-18 16:21:48 +01:00
Claire fcc4c9b34a
Change domain block CSV parsing to be more robust and handle more lists (#21470)
* Change domain block CSV parsing to be more robust and handle more lists

* Add some tests

* Improve domain block import validation and reporting
2023-01-18 16:20:52 +01:00
Claire 472fd4307f
New Crowdin updates (#2069)
* New translations en.yml (Portuguese, Brazilian)
[ci skip]

* New translations en.yml (Chinese Traditional, Hong Kong)
[ci skip]

* New translations en.yml (Serbian (Latin))
[ci skip]

* New translations en.yml (Kurmanji (Kurdish))
[ci skip]

* New translations en.yml (Sorani (Kurdish))
[ci skip]

* New translations simple_form.en.yml (Portuguese, Brazilian)
[ci skip]

* New translations simple_form.en.yml (Chinese Traditional, Hong Kong)
[ci skip]

* New translations simple_form.en.yml (Serbian (Latin))
[ci skip]

* New translations simple_form.en.yml (Kurmanji (Kurdish))
[ci skip]

* New translations simple_form.en.yml (Sorani (Kurdish))
[ci skip]

* New translations en.yml (French)
[ci skip]

* New translations en.yml (French, Quebec)
[ci skip]

* Fix pt-BR key
2023-01-18 15:50:50 +01:00
Claire c16aadf718
Merge pull request #2086 from ClearlyClaire/glitch-soc/merge
Merge upstream changes
2023-01-18 15:33:16 +01:00
Claire 6ae97bba25 Merge branch 'main' into glitch-soc/merge 2023-01-18 11:57:01 +01:00
dependabot[bot] 302fcb9788
Bump rails from 6.1.7 to 6.1.7.1 (#23144)
Bumps [rails](https://github.com/rails/rails) from 6.1.7 to 6.1.7.1.
- [Release notes](https://github.com/rails/rails/releases)
- [Commits](https://github.com/rails/rails/compare/v6.1.7...v6.1.7.1)

---
updated-dependencies:
- dependency-name: rails
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-01-18 11:40:21 +01:00
dependabot[bot] 9b32ca583e
Bump ox from 2.14.12 to 2.14.13 (#23143)
Bumps [ox](https://github.com/ohler55/ox) from 2.14.12 to 2.14.13.
- [Release notes](https://github.com/ohler55/ox/releases)
- [Changelog](https://github.com/ohler55/ox/blob/develop/CHANGELOG.md)
- [Commits](https://github.com/ohler55/ox/compare/v2.14.12...v2.14.13)

---
updated-dependencies:
- dependency-name: ox
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-01-18 11:31:55 +01:00
dependabot[bot] c6cda209d5
Bump rack from 2.2.5 to 2.2.6.2 (#23142)
Bumps [rack](https://github.com/rack/rack) from 2.2.5 to 2.2.6.2.
- [Release notes](https://github.com/rack/rack/releases)
- [Changelog](https://github.com/rack/rack/blob/main/CHANGELOG.md)
- [Commits](https://github.com/rack/rack/compare/v2.2.5...v2.2.6.2)

---
updated-dependencies:
- dependency-name: rack
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-01-18 11:31:39 +01:00
dependabot[bot] 8276274bf6
Bump rubocop-rspec from 2.16.0 to 2.18.0 (#23122)
Bumps [rubocop-rspec](https://github.com/rubocop/rubocop-rspec) from 2.16.0 to 2.18.0.
- [Release notes](https://github.com/rubocop/rubocop-rspec/releases)
- [Changelog](https://github.com/rubocop/rubocop-rspec/blob/master/CHANGELOG.md)
- [Commits](https://github.com/rubocop/rubocop-rspec/compare/v2.16.0...v2.18.0)

---
updated-dependencies:
- dependency-name: rubocop-rspec
  dependency-type: direct:development
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-01-18 11:14:12 +01:00
dependabot[bot] 23fcf7869e
Bump rubocop from 1.42.0 to 1.43.0 (#23119)
Bumps [rubocop](https://github.com/rubocop/rubocop) from 1.42.0 to 1.43.0.
- [Release notes](https://github.com/rubocop/rubocop/releases)
- [Changelog](https://github.com/rubocop/rubocop/blob/master/CHANGELOG.md)
- [Commits](https://github.com/rubocop/rubocop/compare/v1.42.0...v1.43.0)

---
updated-dependencies:
- dependency-name: rubocop
  dependency-type: direct:development
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-01-18 11:13:31 +01:00
dependabot[bot] d047e93f47
Bump nokogiri from 1.13.10 to 1.14.0 (#23128)
Bumps [nokogiri](https://github.com/sparklemotion/nokogiri) from 1.13.10 to 1.14.0.
- [Release notes](https://github.com/sparklemotion/nokogiri/releases)
- [Changelog](https://github.com/sparklemotion/nokogiri/blob/main/CHANGELOG.md)
- [Commits](https://github.com/sparklemotion/nokogiri/compare/v1.13.10...v1.14.0)

---
updated-dependencies:
- dependency-name: nokogiri
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-01-18 10:54:19 +01:00
dependabot[bot] 0512780e0d
Bump rimraf from 3.0.2 to 4.0.7 (#23118)
Bumps [rimraf](https://github.com/isaacs/rimraf) from 3.0.2 to 4.0.7.
- [Release notes](https://github.com/isaacs/rimraf/releases)
- [Changelog](https://github.com/isaacs/rimraf/blob/main/CHANGELOG.md)
- [Commits](https://github.com/isaacs/rimraf/compare/v3.0.2...v4.0.7)

---
updated-dependencies:
- dependency-name: rimraf
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-01-18 10:53:12 +01:00
dependabot[bot] 6a9c74a7af
Bump prettier from 2.8.2 to 2.8.3 (#23123)
Bumps [prettier](https://github.com/prettier/prettier) from 2.8.2 to 2.8.3.
- [Release notes](https://github.com/prettier/prettier/releases)
- [Changelog](https://github.com/prettier/prettier/blob/main/CHANGELOG.md)
- [Commits](https://github.com/prettier/prettier/compare/2.8.2...2.8.3)

---
updated-dependencies:
- dependency-name: prettier
  dependency-type: direct:development
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-01-18 10:52:50 +01:00
dependabot[bot] cfb9450d20
Bump glob from 8.0.3 to 8.1.0 (#23125)
Bumps [glob](https://github.com/isaacs/node-glob) from 8.0.3 to 8.1.0.
- [Release notes](https://github.com/isaacs/node-glob/releases)
- [Changelog](https://github.com/isaacs/node-glob/blob/main/changelog.md)
- [Commits](https://github.com/isaacs/node-glob/compare/v8.0.3...v8.1.0)

---
updated-dependencies:
- dependency-name: glob
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-01-18 10:52:35 +01:00
dependabot[bot] 1554e0e66a
Bump punycode from 2.1.1 to 2.2.0 (#23126)
Bumps [punycode](https://github.com/bestiejs/punycode.js) from 2.1.1 to 2.2.0.
- [Release notes](https://github.com/bestiejs/punycode.js/releases)
- [Commits](https://github.com/bestiejs/punycode.js/compare/v2.1.1...v2.2.0)

---
updated-dependencies:
- dependency-name: punycode
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-01-18 10:52:11 +01:00
Claire 13227e1daf
Merge pull request #2081 from ClearlyClaire/glitch-soc/merge-upstream
Merge upstream changes
2023-01-15 16:53:30 +01:00
Claire ab59743c13 Merge branch 'main' into glitch-soc/merge-upstream
Conflicts:
- `app/views/layouts/mailer.html.haml`:
  Upstream removed a line close to one modified by glitch-soc.
  Removed the line as upstream did.
2023-01-14 22:34:09 +01:00
Jeong Arm d66dfc7b3c
Change confirm prompt for relationships management (#19411)
* Change confirm prompt for relationships management

* Add Korean translations

* Apply suggestions from code review

Co-authored-by: TobyWilkes <tobylwilkes@gmail.com>

Co-authored-by: TobyWilkes <tobylwilkes@gmail.com>
2023-01-14 14:00:23 +01:00
Jeong Arm 0e8f8a1a1c
Implement tootctl accounts prune (#18397)
* Implement tootctl accounts prune

* Optimise query

Co-authored-by: Claire <claire.github-309c@sitedethib.com>
2023-01-13 22:34:16 +01:00
Darius Kazemi 507e1d22f5
Allow admins to toggle public statistics API (#22833)
* Allow admins to toggle public statistics API

* Normalize i18n

Co-authored-by: Claire <claire.github-309c@sitedethib.com>
2023-01-13 17:14:39 +01:00
Claire 745bdb11a0
Add tootctl accounts migrate (#22330)
* Add tootctl accounts replay-migration

Fixes #22281

* Change `tootctl accounts replay-migration` to `tootctl accounts migrate`
2023-01-13 17:00:23 +01:00
Darius Kazemi d35fe3d5e3
Add peers API endpoint toggle to Server Settings (#22810)
* Add peers endpoint toggle to Server Settings

This places the toggle under "Discovery" and expands the hint text to explain further what the endpoint is used for. Added a "Recommended" tag since it was recommended in v3 before it was removed.

Fixes https://github.com/mastodon/mastodon/issues/22222

* i18n normalize step
2023-01-13 16:43:17 +01:00
Carl Schwan f33e22ae4c
Allow changing hide_collections setting with the api (#22790)
* Allow changing hide_collections setting with the api

This is currently only possible with app/controllers/settings/profiles_controller.rb
and is the only difference in the allowed parameter between the two controllers

* Fix the lint issue

* Use normal indent
2023-01-13 16:40:21 +01:00
David Freedman ff70e50199
Don't crash on unobtainable avatars (#22462) 2023-01-13 16:40:06 +01:00
nametoolong 332a411fad
Remove title from mailer layout (#23078) 2023-01-13 15:12:26 +01:00
Claire afd0d424da
Merge pull request #2080 from ClearlyClaire/glitch-soc/merge-upstream
Merge upstream changes
2023-01-13 12:26:34 +01:00
Claire f79c200f7e
Change wording of admin report handling actions (#18388)
* Change admin report handling UI to display appropriate text for remote reports

Change from “Decide which action to take to resolve this report. If you take a
punitive action against the reported account, an e-mail notification will be
sent to them, except when the Spam category is selected.” to “Decide which
action to take to resolve this report. This will only affect how your server
communicates with this remote account and handle its content.”

* Reword admin actions descriptions to make clear which admin actions close reports
2023-01-13 11:03:14 +01:00
Claire 21a1a8ee88
Fix crash when marking statuses as sensitive while some statuses are deleted (#22134)
* Do not offer to mark statuses as sensitive if there is no undeleted status with media attachments

* Fix crash when marking statuses as sensitive while some statuses are deleted

Fixes #21910

* Fix multiple strikes being created for a single report when selecting “Mark as sensitive”

* Add tests
2023-01-13 10:46:52 +01:00
Claire b52dc5f69d Merge branch 'main' into glitch-soc/merge-upstream 2023-01-13 10:31:52 +01:00
Claire a3a5aa1597
Fix incorrect env file generation in mastodon:setup (#23072)
Regression from #23012
2023-01-13 10:17:07 +01:00
Claire 598888a7c4 [Glitch] Remove hardcoded width from dropdown overlays
Port f4a6365f55 to glitch-soc

Signed-off-by: Claire <claire.github-309c@sitedethib.com>
2023-01-12 17:48:10 +01:00
Claire cef87ba86c Merge branch 'main' into glitch-soc/merge-upstream 2023-01-12 17:47:42 +01:00
Claire f4a6365f55
Remove hardcoded width from dropdown overlays (#23062)
* Remove hardcoded width from dropdown overlays

* Fix emoji picker position
2023-01-12 16:43:02 +01:00
Claire ebe2c10932
Change wording of the OAuth scopes descriptions (#22491)
- change `all` from “Everything” to “Full access to your Mastodon account”
- change `follow` from “Relationships” to “Follows, Mutes and Blocks”
2023-01-12 14:11:55 +01:00
Peter Simonsson a36dfbb2aa [Glitch] Fix dropdown menu positions when scrolling
Port fd33bcb3b2 to glitch-soc

Signed-off-by: Claire <claire.github-309c@sitedethib.com>
2023-01-12 11:18:22 +01:00
Claire 3e63fcd4f0 Merge branch 'main' into glitch-soc/merge-upstream
Conflicts:
- `app/models/status.rb`:
  Minor upstream refactor moved hook definitions around,
  and glitch-soc has an extra `before_create`.
  Moved the `before_create` accordingly.
- `app/services/batched_remove_status_service.rb`:
  Minor upstream refactor changed a block in which glitch-soc
  had one extra call to handle direct timelines.
  Adapted changes to keep glitch-soc's extra call.
2023-01-12 10:15:46 +01:00
Claire 15b88a83ab
Fix sanitizer parsing link text as HTML when stripping unsupported links (#22558) 2023-01-11 22:21:10 +01:00
Markus Unterwaditzer 0c689b9d01
fix: allow verification when page size exceeds 1MB (using HTML5 parser) (#22879)
* fix: allow verification when page size exceeds 1MB
Truncates the page after 1MB instead

Closes #15316

* switch to HTML5 parser, fix rubocop errors

* undo rubocop fixes

Co-authored-by: Chris Zubak-Skees <chriszs@gmail.com>
2023-01-11 21:59:13 +01:00
Peter Simonsson fd33bcb3b2
Fix dropdown menu positions when scrolling (#22916)
* Update react-overlays to latest version

* Fix breaking changes in dropdown menus

* Use react-overlays built-in arrow positioning feature
* Re-implemented `.dropdown-menu__arrow` to have a defined width and height to improve positioning
* Moved wrapping div (`.dropdown-menu` from `DropdownMenu` to `Dropdown`)
* Wrap button in a span to solve issue with ref
* Temporarily remove animations

* Fix breaking changes in emoji picker

* Wrap EmojiPickerMenu in a div where react-overlays’ ref is added

* Fix breaking changes in language dropdown

* Fix breaking changes in privacy dropdown

* Fix breaking changes in search form

* Add animations back using `@keyframes`

* Fix arrow color in light theme

* Fix linting issue

* Remove unused `mounted` state

* Remove `placement` state from components and redux

And remove the placement state from props of the menu components.

* Remove abolution position to fix flip issue

* Remove z-index to fix modals and overlay positions

* Fix lint issues

* Set placement in privacy and language components

Copy the placement state into the `PrivacyDropdown` and `LanguageDropdown` components, to apply correct styling to the buttons depending on which placement the Overlay has.

* Move `placement` state to correct component
2023-01-11 21:58:46 +01:00
Kaspar V ae62e5fa53
Fix/remove calling private method with send in model (#22951)
* fix(status): remove send usage for private unlink_from_conversations

- make unlink_from_conversations public method
- rename unlink_from_conversations to unlink_from_conversations!
- fix send call on private method in statuses_vacuum and batched_remove_status_service

* fix(feeds_vacuum): replace find_in_batches with in_batches

because active record query results should be a little more efficient than
itterating with map and each. Postgres can grasp such lists of ids much quicker
than ruby can.
Will probably make allmost no difference, but cannot hurt either.
2023-01-11 21:57:24 +01:00
Claire a65f86ae55
Fix $ not being escaped in .env.production file generated by mastodon:setup (#23012)
* Fix `$` not being escaped in `.env.production` file generated by `mastodon:setup`

* Improve robustness of dotenv escaping
2023-01-11 21:53:11 +01:00
Claire 2ba14097ff
Change trending tags admin interface to always show batch actions (#23013)
Fixes #22565
2023-01-11 21:51:43 +01:00
dependabot[bot] 7101bc534c
Bump ws from 8.11.0 to 8.12.0 (#23023)
Bumps [ws](https://github.com/websockets/ws) from 8.11.0 to 8.12.0.
- [Release notes](https://github.com/websockets/ws/releases)
- [Commits](https://github.com/websockets/ws/compare/8.11.0...8.12.0)

---
updated-dependencies:
- dependency-name: ws
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-01-11 22:02:06 +09:00
dependabot[bot] f9655d6850
Bump @babel/core from 7.20.7 to 7.20.12 (#23020)
Bumps [@babel/core](https://github.com/babel/babel/tree/HEAD/packages/babel-core) from 7.20.7 to 7.20.12.
- [Release notes](https://github.com/babel/babel/releases)
- [Changelog](https://github.com/babel/babel/blob/main/CHANGELOG.md)
- [Commits](https://github.com/babel/babel/commits/v7.20.12/packages/babel-core)

---
updated-dependencies:
- dependency-name: "@babel/core"
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-01-11 21:50:01 +09:00
dependabot[bot] f0fd8c5c38
Bump prettier from 2.8.1 to 2.8.2 (#23022)
Bumps [prettier](https://github.com/prettier/prettier) from 2.8.1 to 2.8.2.
- [Release notes](https://github.com/prettier/prettier/releases)
- [Changelog](https://github.com/prettier/prettier/blob/main/CHANGELOG.md)
- [Commits](https://github.com/prettier/prettier/compare/2.8.1...2.8.2)

---
updated-dependencies:
- dependency-name: prettier
  dependency-type: direct:development
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-01-11 21:49:39 +09:00
dependabot[bot] 167b073087
Bump immutable from 4.2.1 to 4.2.2 (#23021)
Bumps [immutable](https://github.com/immutable-js/immutable-js) from 4.2.1 to 4.2.2.
- [Release notes](https://github.com/immutable-js/immutable-js/releases)
- [Changelog](https://github.com/immutable-js/immutable-js/blob/main/CHANGELOG.md)
- [Commits](https://github.com/immutable-js/immutable-js/compare/v4.2.1...v4.2.2)

---
updated-dependencies:
- dependency-name: immutable
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-01-11 21:47:00 +09:00
dependabot[bot] 2f4dae26ee
Bump postcss from 8.4.20 to 8.4.21 (#23019)
Bumps [postcss](https://github.com/postcss/postcss) from 8.4.20 to 8.4.21.
- [Release notes](https://github.com/postcss/postcss/releases)
- [Changelog](https://github.com/postcss/postcss/blob/main/CHANGELOG.md)
- [Commits](https://github.com/postcss/postcss/compare/8.4.20...8.4.21)

---
updated-dependencies:
- dependency-name: postcss
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-01-11 21:46:07 +09:00
dependabot[bot] a66cf52448
Bump jsdom from 20.0.3 to 21.0.0 (#23018)
Bumps [jsdom](https://github.com/jsdom/jsdom) from 20.0.3 to 21.0.0.
- [Release notes](https://github.com/jsdom/jsdom/releases)
- [Changelog](https://github.com/jsdom/jsdom/blob/master/Changelog.md)
- [Commits](https://github.com/jsdom/jsdom/compare/20.0.3...21.0.0)

---
updated-dependencies:
- dependency-name: jsdom
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-01-11 21:45:37 +09:00
dependabot[bot] 973e4756e8
Bump utf-8-validate from 5.0.10 to 6.0.0 (#23017)
Bumps [utf-8-validate](https://github.com/websockets/utf-8-validate) from 5.0.10 to 6.0.0.
- [Release notes](https://github.com/websockets/utf-8-validate/releases)
- [Commits](https://github.com/websockets/utf-8-validate/compare/v5.0.10...v6.0.0)

---
updated-dependencies:
- dependency-name: utf-8-validate
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-01-11 21:44:07 +09:00
Eugen Rochko f6e34ca134
New Crowdin updates (#22953)
* New translations en.yml (Bulgarian)

* New translations en.yml (Bulgarian)

* New translations en.json (Belarusian)

* New translations en.json (Vietnamese)

* New translations en.json (Asturian)

* New translations en.yml (Bulgarian)

* New translations simple_form.en.yml (Estonian)

* New translations devise.en.yml (Estonian)

* New translations activerecord.en.yml (Estonian)

* New translations en.yml (Bulgarian)

* New translations en.yml (Bulgarian)

* New translations en.yml (German)

* New translations activerecord.en.yml (German)

* New translations simple_form.en.yml (German)

* New translations simple_form.en.yml (German)

* New translations en.json (Danish)

* New translations en.json (Arabic)

* New translations en.yml (Estonian)

* New translations devise.en.yml (Estonian)

* New translations en.json (English, United Kingdom)

* New translations simple_form.en.yml (English, United Kingdom)

* New translations en.json (Catalan)

* New translations en.yml (Catalan)

* New translations simple_form.en.yml (Catalan)

* New translations doorkeeper.en.yml (Catalan)

* New translations en.json (Galician)

* New translations en.json (Russian)

* New translations en.yml (Russian)

* New translations en.json (Hungarian)

* New translations en.yml (Russian)

* New translations en.json (Turkish)

* New translations devise.en.yml (Estonian)

* New translations en.json (Catalan)

* New translations en.json (Estonian)

* New translations en.yml (Estonian)

* New translations devise.en.yml (Estonian)

* New translations doorkeeper.en.yml (Estonian)

* New translations en.json (Estonian)

* New translations en.yml (Estonian)

* New translations simple_form.en.yml (Estonian)

* New translations devise.en.yml (Estonian)

* New translations doorkeeper.en.yml (Estonian)

* New translations en.json (Occitan)

* New translations en.yml (Occitan)

* New translations en.json (Finnish)

* New translations en.json (Hindi)

* New translations en.json (Hindi)

* New translations en.json (Czech)

* New translations en.json (Thai)

* New translations en.json (Portuguese)

* New translations en.json (Portuguese)

* New translations en.yml (Portuguese)

* New translations simple_form.en.yml (Portuguese)

* New translations en.json (Portuguese)

* New translations en.yml (Portuguese)

* New translations en.json (Estonian)

* New translations doorkeeper.en.yml (Spanish)

* New translations en.json (Estonian)

* New translations en.yml (Estonian)

* New translations doorkeeper.en.yml (Estonian)

* New translations en.yml (Portuguese)

* New translations en.yml (Portuguese)

* New translations en.json (Finnish)

* New translations en.json (Romanian)

* New translations en.json (French)

* New translations en.json (Spanish)

* New translations en.json (Afrikaans)

* New translations en.json (Arabic)

* New translations en.json (Belarusian)

* New translations en.json (Bulgarian)

* New translations en.json (Catalan)

* New translations en.json (Czech)

* New translations en.json (Danish)

* New translations en.json (German)

* New translations en.json (Greek)

* New translations en.json (Frisian)

* New translations en.json (Basque)

* New translations en.json (Finnish)

* New translations en.json (Irish)

* New translations en.json (Hebrew)

* New translations en.json (Hungarian)

* New translations en.json (Armenian)

* New translations en.json (Italian)

* New translations en.json (Japanese)

* New translations en.json (Georgian)

* New translations en.json (Korean)

* New translations en.json (Lithuanian)

* New translations en.json (Macedonian)

* New translations en.json (Dutch)

* New translations en.json (Norwegian)

* New translations en.json (Punjabi)

* New translations en.json (Polish)

* New translations en.json (Portuguese)

* New translations en.json (Russian)

* New translations en.json (Slovak)

* New translations en.json (Slovenian)

* New translations en.json (Albanian)

* New translations en.json (Serbian (Cyrillic))

* New translations en.json (Swedish)

* New translations en.json (Turkish)

* New translations en.json (Ukrainian)

* New translations en.json (Chinese Simplified)

* New translations en.json (Chinese Traditional)

* New translations en.json (Urdu (Pakistan))

* New translations en.json (Vietnamese)

* New translations en.json (Galician)

* New translations en.json (Icelandic)

* New translations en.json (Portuguese, Brazilian)

* New translations en.json (Indonesian)

* New translations en.json (Persian)

* New translations en.json (Tamil)

* New translations en.json (Spanish, Argentina)

* New translations en.json (Spanish, Mexico)

* New translations en.json (Bengali)

* New translations en.json (Marathi)

* New translations en.json (Thai)

* New translations en.json (Croatian)

* New translations en.json (Norwegian Nynorsk)

* New translations en.json (Kazakh)

* New translations en.json (Estonian)

* New translations en.json (Latvian)

* New translations en.json (Hindi)

* New translations en.json (Malay)

* New translations en.json (Telugu)

* New translations en.json (English, United Kingdom)

* New translations en.json (Welsh)

* New translations en.json (Faroese)

* New translations en.json (Esperanto)

* New translations en.json (Uyghur)

* New translations en.json (Chinese Traditional, Hong Kong)

* New translations en.json (Tatar)

* New translations en.json (Malayalam)

* New translations en.json (Breton)

* New translations en.json (French, Quebec)

* New translations en.json (Sinhala)

* New translations en.json (Cornish)

* New translations en.json (Kannada)

* New translations en.json (Scottish Gaelic)

* New translations en.json (Aragonese)

* New translations en.json (Occitan)

* New translations en.json (Serbian (Latin))

* New translations en.json (Kurmanji (Kurdish))

* New translations en.json (Sorani (Kurdish))

* New translations en.json (Scots)

* New translations en.json (Corsican)

* New translations en.json (Sardinian)

* New translations en.json (Sanskrit)

* New translations en.json (Kabyle)

* New translations en.json (Ido)

* New translations en.json (Taigi)

* New translations en.json (Silesian)

* New translations en.json (Standard Moroccan Tamazight)

* New translations en.json (Irish)

* New translations en.json (Korean)

* New translations en.json (Chinese Simplified)

* New translations en.json (Chinese Traditional)

* New translations en.json (Spanish, Argentina)

* New translations en.yml (Irish)

* New translations en.json (Latvian)

* New translations en.json (Spanish)

* New translations en.json (Bulgarian)

* New translations en.json (Hebrew)

* New translations en.json (Ukrainian)

* New translations en.json (Estonian)

* New translations en.yml (Estonian)

* New translations simple_form.en.yml (Estonian)

* New translations devise.en.yml (Estonian)

* New translations doorkeeper.en.yml (Estonian)

* New translations en.json (Belarusian)

* New translations en.json (Catalan)

* New translations en.json (Finnish)

* New translations en.json (Faroese)

* New translations en.yml (Finnish)

* New translations simple_form.en.yml (Finnish)

* New translations en.json (Czech)

* New translations en.json (Frisian)

* New translations en.json (Finnish)

* New translations en.json (Dutch)

* New translations en.json (Polish)

* New translations en.json (Icelandic)

* New translations en.json (German)

* New translations en.json (Hungarian)

* New translations en.json (Italian)

* New translations en.json (Portuguese)

* New translations en.json (Slovenian)

* New translations en.json (Serbian (Cyrillic))

* New translations en.json (Thai)

* New translations en.json (Estonian)

* New translations en.json (Serbian (Latin))

* New translations en.json (Bulgarian)

* New translations en.json (Swedish)

* New translations en.json (Bulgarian)

* New translations simple_form.en.yml (Bulgarian)

* New translations en.json (Portuguese)

* New translations en.json (Galician)

* New translations simple_form.en.yml (Bulgarian)

* New translations en.json (Esperanto)

* New translations en.json (Danish)

* New translations en.json (French)

* New translations en.json (Albanian)

* New translations en.yml (French)

* New translations en.yml (Portuguese)

* New translations en.json (Russian)

* New translations en.yml (Russian)

* New translations en.yml (Portuguese)

* New translations en.yml (Estonian)

* New translations en.json (Estonian)

* New translations en.yml (Estonian)

* New translations en.yml (Portuguese)

* New translations en.json (Belarusian)

* New translations en.json (Estonian)

* New translations en.yml (Estonian)

* New translations simple_form.en.yml (Estonian)

* New translations devise.en.yml (Estonian)

* New translations doorkeeper.en.yml (Estonian)

* New translations en.json (Estonian)

* New translations en.yml (Estonian)

* New translations simple_form.en.yml (Estonian)

* New translations en.json (Vietnamese)

* New translations en.json (Bulgarian)

* New translations en.json (Bulgarian)

* New translations en.json (Bulgarian)

* New translations en.json (Norwegian)

* New translations en.json (Norwegian Nynorsk)

* New translations devise.en.yml (Bulgarian)

* New translations en.yml (Galician)

* New translations en.json (Slovak)

* New translations devise.en.yml (Bulgarian)

* New translations en.json (Welsh)

* New translations en.yml (Portuguese)

* New translations en.yml (Portuguese)

* New translations en.json (Croatian)

* New translations simple_form.en.yml (Bulgarian)

* New translations en.json (Bulgarian)

* New translations en.yml (Bulgarian)

* New translations simple_form.en.yml (Bulgarian)

* New translations en.json (Bulgarian)

* New translations en.json (Slovak)

* New translations en.yml (Slovak)

* New translations en.yml (Portuguese)

* New translations en.json (Spanish, Mexico)

* New translations en.yml (Portuguese)

* New translations en.json (Portuguese)

* New translations en.yml (Portuguese)

* New translations simple_form.en.yml (Portuguese)

* New translations en.yml (Bulgarian)

* New translations en.json (Slovak)

* New translations en.yml (Slovak)

* Normalize

Co-authored-by: Yamagishi Kazutoshi <ykzts@desire.sh>
2023-01-11 21:41:34 +09:00
Claire 932a22219a
Merge pull request #2077 from ClearlyClaire/glitch-soc/merge-upstream
Merge upstream changes
2023-01-10 17:10:11 +01:00
Claire a7bf439cfd Merge branch 'main' into glitch-soc/merge-upstream 2023-01-10 14:43:38 +01:00
Claire cff7d967f9
Fix CSRF protection (#23037)
Fix regression from #23014
2023-01-10 14:33:40 +01:00
Holden Foreman fdabfb9d0e [Glitch] Fix footer link circle dividers' screen reader accessibility by adding aria-hidden
Port 2bcb081ce8 to glitch-soc

Signed-off-by: Claire <claire.github-309c@sitedethib.com>
2023-01-10 09:39:19 +01:00
Akira Ouchi b6a928cd04 [Glitch] Add variable autoFocus to video
Port 2195f21524 to glitch-soc

Signed-off-by: Claire <claire.github-309c@sitedethib.com>
2023-01-10 09:39:19 +01:00
Claire 9765d2b3f8 Merge branch 'main' into glitch-soc/merge-upstream
Conflicts:
- `config/environments/production.rb`:
  Upstream changed headers, and we have different ones.
  Ported upstream's change.
2023-01-10 09:39:15 +01:00
Claire aefefc74c4
Change referrer-policy to no-referrer application-wide (#23014) 2023-01-10 05:18:43 +01:00
Holden Foreman 2bcb081ce8
Fix footer link circle dividers' screen reader accessibility by adding aria-hidden (#22576)
* Fix footer link circle dividers' screen reader accessibility by adding aria-hidden

* Remove a circle erroneously added in prev commit, and make code more DRY
2023-01-09 03:54:03 +01:00
Akira Ouchi 2195f21524
Add variable autoFocus to video (#15281) (#22778)
* add variable autoFocus to video
* set autoFocus in video_modal.js
2023-01-09 03:52:37 +01:00
n0toose 105e1f0ca6
Correct hashtag warning (#22827)
Posts with any visibility setting that is not 'Public' are prevented
from being listed under any hashtag.
2023-01-09 03:18:20 +01:00
Jim Myhrberg 85ec615393
feat(puma): enable setting min puma threads in addition to max (#21048) 2023-01-06 07:55:58 +01:00
Darius Kazemi 264655c53a
Fix account search not returning followed accounts first (#22956)
* Make autosuggest for mentions return followed accounts first

This makes it so that (when elasticsearch is disabled) when a user types '@foo' in the compose box, they are first going to get accounts they follow ordered by the ranking algorithm, and then second they will get accounts they do not follow, also ordered by the ranking algorithm.

This makes behavior more consistent with user expectation and also with results when elasticsearch is enabled.

* Fix ranking order to correct direction

* One more fixup per @gargron suggestion

* Tweak to ranking to no longer include following modifier
2023-01-06 07:35:52 +01:00
Eugen Rochko d11d15748c
New Crowdin updates (#22901)
* New translations en.json (Norwegian)

* New translations en.json (English, United Kingdom)

* New translations en.yml (English, United Kingdom)

* New translations simple_form.en.yml (English, United Kingdom)

* New translations en.json (Norwegian)

* New translations en.yml (Norwegian)

* New translations en.yml (Norwegian)

* New translations simple_form.en.yml (Norwegian)

* New translations doorkeeper.en.yml (Norwegian)

* New translations en.yml (Hebrew)

* New translations en.yml (German)

* New translations activerecord.en.yml (German)

* New translations doorkeeper.en.yml (German)

* New translations en.yml (Dutch)

* New translations doorkeeper.en.yml (Dutch)

* New translations en.yml (Finnish)

* New translations en.json (Dutch)

* New translations doorkeeper.en.yml (Dutch)

* New translations en.json (Finnish)

* New translations en.yml (Finnish)

* New translations doorkeeper.en.yml (Finnish)

* New translations doorkeeper.en.yml (Dutch)

* New translations en.json (Bulgarian)

* New translations en.yml (Norwegian Nynorsk)

* New translations en.yml (Norwegian Nynorsk)

* New translations en.yml (Finnish)

* New translations en.json (Frisian)

* New translations en.json (Frisian)

* New translations en.yml (Frisian)

* New translations doorkeeper.en.yml (Frisian)

* New translations en.yml (Esperanto)

* New translations en.json (Estonian)

* New translations en.json (Romanian)

* New translations en.json (French)

* New translations en.json (Spanish)

* New translations en.json (Afrikaans)

* New translations en.json (Arabic)

* New translations en.json (Belarusian)

* New translations en.json (Bulgarian)

* New translations en.json (Catalan)

* New translations en.json (Czech)

* New translations en.json (Danish)

* New translations en.json (German)

* New translations en.json (Greek)

* New translations en.json (Frisian)

* New translations en.json (Basque)

* New translations en.json (Finnish)

* New translations en.json (Irish)

* New translations en.json (Hebrew)

* New translations en.json (Hungarian)

* New translations en.json (Armenian)

* New translations en.json (Italian)

* New translations en.json (Japanese)

* New translations en.json (Georgian)

* New translations en.json (Korean)

* New translations en.json (Lithuanian)

* New translations en.json (Macedonian)

* New translations en.json (Dutch)

* New translations en.json (Norwegian)

* New translations en.json (Punjabi)

* New translations en.json (Polish)

* New translations en.json (Portuguese)

* New translations en.json (Russian)

* New translations en.json (Slovak)

* New translations en.json (Slovenian)

* New translations en.json (Albanian)

* New translations en.json (Serbian (Cyrillic))

* New translations en.json (Swedish)

* New translations en.json (Turkish)

* New translations en.json (Ukrainian)

* New translations en.json (Chinese Simplified)

* New translations en.json (Chinese Traditional)

* New translations en.json (Urdu (Pakistan))

* New translations en.json (Vietnamese)

* New translations en.json (Galician)

* New translations en.json (Icelandic)

* New translations en.json (Portuguese, Brazilian)

* New translations en.json (Indonesian)

* New translations en.json (Persian)

* New translations en.json (Tamil)

* New translations en.json (Spanish, Argentina)

* New translations en.json (Spanish, Mexico)

* New translations en.json (Bengali)

* New translations en.json (Marathi)

* New translations en.json (Thai)

* New translations en.json (Croatian)

* New translations en.json (Norwegian Nynorsk)

* New translations en.json (Kazakh)

* New translations en.json (Estonian)

* New translations en.json (Latvian)

* New translations en.json (Hindi)

* New translations en.json (Malay)

* New translations en.json (Telugu)

* New translations en.json (Burmese)

* New translations en.json (Welsh)

* New translations en.json (Faroese)

* New translations en.json (Esperanto)

* New translations en.json (Uyghur)

* New translations en.json (Chinese Traditional, Hong Kong)

* New translations en.json (Tatar)

* New translations en.json (Malayalam)

* New translations en.json (Breton)

* New translations en.json (Latin)

* New translations en.json (Bosnian)

* New translations en.json (French, Quebec)

* New translations en.json (Sinhala)

* New translations en.json (Cornish)

* New translations en.json (Kannada)

* New translations en.json (Scottish Gaelic)

* New translations en.json (Asturian)

* New translations en.json (Aragonese)

* New translations en.json (Occitan)

* New translations en.json (Serbian (Latin))

* New translations en.json (Kurmanji (Kurdish))

* New translations en.json (Sorani (Kurdish))

* New translations en.json (Scots)

* New translations en.json (Igbo)

* New translations en.json (Corsican)

* New translations en.json (Sardinian)

* New translations en.json (Sanskrit)

* New translations en.json (Kabyle)

* New translations en.json (Ido)

* New translations en.json (Taigi)

* New translations en.json (Silesian)

* New translations en.json (Standard Moroccan Tamazight)

* Normalize

* New translations en.json (Catalan)

* New translations en.json (German)

* New translations en.json (Greek)

* New translations en.json (Frisian)

* New translations en.json (Hebrew)

* New translations en.json (Hungarian)

* New translations en.json (Italian)

* New translations en.json (Dutch)

* New translations en.json (Slovenian)

* New translations en.json (Serbian (Cyrillic))

* New translations en.json (Swedish)

* New translations en.json (Ukrainian)

* New translations en.json (Vietnamese)

* New translations en.json (Galician)

* New translations en.json (Icelandic)

* New translations en.json (Indonesian)

* New translations en.json (Latvian)

* New translations en.json (Welsh)

* New translations en.json (Faroese)

* New translations en.json (Esperanto)

* New translations en.json (Serbian (Latin))

* New translations simple_form.en.yml (Galician)

* New translations en.json (Danish)

* New translations en.json (Japanese)

* New translations en.json (Korean)

* New translations en.yml (Japanese)

* Normalize

Co-authored-by: Yamagishi Kazutoshi <ykzts@desire.sh>
2023-01-06 01:03:53 +09:00
460 changed files with 6855 additions and 3466 deletions

View file

@ -17,7 +17,7 @@ jobs:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- uses: hadolint/hadolint-action@v3.0.0
- uses: hadolint/hadolint-action@v3.1.0
- uses: docker/setup-qemu-action@v2
- uses: docker/setup-buildx-action@v2
- uses: docker/login-action@v2

View file

@ -3,6 +3,191 @@ Changelog
All notable changes to this project will be documented in this file.
## [4.1.0] - UNRELEASED
### Added
- **Add support for importing/exporting server-wide domain blocks** ([enbylenore](https://github.com/mastodon/mastodon/pull/20597), [ClearlyClaire](https://github.com/mastodon/mastodon/pull/21471), [dariusk](https://github.com/mastodon/mastodon/pull/22803), [ClearlyClaire](https://github.com/mastodon/mastodon/pull/21470))
- Add listing of followed hashtags ([connorshea](https://github.com/mastodon/mastodon/pull/21773))
- Add support for editing media description and focus point of already-sent posts ([ClearlyClaire](https://github.com/mastodon/mastodon/pull/20878))
- Add follow request banner on account header ([ClearlyClaire](https://github.com/mastodon/mastodon/pull/20785))
- Add confirmation screen when handling reports ([ClearlyClaire](https://github.com/mastodon/mastodon/pull/22375), [Gargron](https://github.com/mastodon/mastodon/pull/23156), [tribela](https://github.com/mastodon/mastodon/pull/23178))
- Add option to make the landing page be `/about` even when trends are enabled ([ClearlyClaire](https://github.com/mastodon/mastodon/pull/20808))
- Add `noindex` setting back to the admin interface ([prplecake](https://github.com/mastodon/mastodon/pull/22205))
- Add instance peers API endpoint toggle back to the admin interface ([dariusk](https://github.com/mastodon/mastodon/pull/22810))
- Add instance activity API endpoint toggle back to the admin interface ([dariusk](https://github.com/mastodon/mastodon/pull/22833))
- Add `account.approved` webhook ([Saiv46](https://github.com/mastodon/mastodon/pull/22938))
- Add 12 hours option to polls ([Pleclown](https://github.com/mastodon/mastodon/pull/21131))
- Add dropdown menu item to open admin interface for remote domains ([ClearlyClaire](https://github.com/mastodon/mastodon/pull/21895))
- Add `--remove-headers`, `--prune-profiles` and `--include-follows` flags to `tootctl media remove` ([evanphilip](https://github.com/mastodon/mastodon/pull/22149))
- Add `--email` and `--dry-run` options to `tootctl accounts delete` ([ClearlyClaire](https://github.com/mastodon/mastodon/pull/22328))
- Add `tootctl accounts migrate` ([ClearlyClaire](https://github.com/mastodon/mastodon/pull/22330))
- Add `tootctl accounts prune` ([tribela](https://github.com/mastodon/mastodon/pull/18397))
- Add `tootctl domains purge` ([ClearlyClaire](https://github.com/mastodon/mastodon/pull/22063))
- Add `SIDEKIQ_CONCURRENCY` environment variable ([muffinista](https://github.com/mastodon/mastodon/pull/19589))
- Add `MIN_THREADS` environment variable to set minimum Puma threads ([jimeh](https://github.com/mastodon/mastodon/pull/21048))
- Add explanation text to log-in page ([ClearlyClaire](https://github.com/mastodon/mastodon/pull/20946))
- Add user profile OpenGraph tag on post pages ([bramus](https://github.com/mastodon/mastodon/pull/21423))
- Add maskable icon support for Android ([workeffortwaste](https://github.com/mastodon/mastodon/pull/20904))
- Add Belarusian to supported languages ([Mixaill](https://github.com/mastodon/mastodon/pull/22022))
- Add Western Frisian to supported languages ([ykzts](https://github.com/mastodon/mastodon/pull/18602))
- Add Montenegrin to the language picker ([ayefries](https://github.com/mastodon/mastodon/pull/21013))
- Add Southern Sami and Lule Sami to the language picker ([Jullan-M](https://github.com/mastodon/mastodon/pull/21262))
- Add logging for Rails cache timeouts ([ClearlyClaire](https://github.com/mastodon/mastodon/pull/21667))
- Add color highlight for active hashtag “follow” button ([MFTabriz](https://github.com/mastodon/mastodon/pull/21629))
- Add brotli compression to `assets:precompile` ([Izorkin](https://github.com/mastodon/mastodon/pull/19025))
- Add “disabled” account filter to the `/admin/accounts` UI ([tribela](https://github.com/mastodon/mastodon/pull/21282))
- Add transparency to modal background for accessibility ([edent](https://github.com/mastodon/mastodon/pull/18081))
- Add `title` attribute to video elements in media attachments ([bramus](https://github.com/mastodon/mastodon/pull/21420))
- Add left and right margins to emojis ([dsblank](https://github.com/mastodon/mastodon/pull/20464))
- Add `reading:autoplay:gifs` to `/api/v1/preferences` ([j-f1](https://github.com/mastodon/mastodon/pull/22706))
- Add `hide_collections` parameter to `/api/v1/accounts/credentials` ([CarlSchwan](https://github.com/mastodon/mastodon/pull/22790))
- Add `policy` attribute to web push subscription objects in `/api/v1/push/subscriptions` ([ClearlyClaire](https://github.com/mastodon/mastodon/pull/23210))
- Add more specific error messages to HTTP signature verification ([ClearlyClaire](https://github.com/mastodon/mastodon/pull/21617))
- Add Storj DCS to cloud object storage options in the `mastodon:setup` rake task ([jtolio](https://github.com/mastodon/mastodon/pull/21929))
- Add checkmark symbol in the checkbox for sensitive media ([sidp](https://github.com/mastodon/mastodon/pull/22795))
- Add missing accessibility attributes to logout link in modals ([kytta](https://github.com/mastodon/mastodon/pull/22549))
- Add missing accessibility attributes to “Hide image” button in `MediaGallery` ([hs4man21](https://github.com/mastodon/mastodon/pull/22513))
- Add missing accessibility attributes to hide content warning field when disabled ([hs4man21](https://github.com/mastodon/mastodon/pull/22568))
- Add `aria-hidden` to footer circle dividers to improve accessibility ([hs4man21](https://github.com/mastodon/mastodon/pull/22576))
- Add `lang` attribute to compose form inputs ([ClearlyClaire](https://github.com/mastodon/mastodon/pull/23240))
### Changed
- **Ensure exact match is the first result in hashtag searches** ([ClearlyClaire](https://github.com/mastodon/mastodon/pull/21315))
- Change account search to return followed accounts first ([dariusk](https://github.com/mastodon/mastodon/pull/22956))
- Change batch account suspension to create a strike ([ClearlyClaire](https://github.com/mastodon/mastodon/pull/20897))
- Change default reply language to match the default language when replying to a translated post ([ClearlyClaire](https://github.com/mastodon/mastodon/pull/22272))
- Change misleading wording about waitlists ([ClearlyClaire](https://github.com/mastodon/mastodon/pull/20850))
- Increase width of the unread notification border ([connorshea](https://github.com/mastodon/mastodon/pull/21692))
- Change new post notification button on profiles to make it more apparent when it is enabled ([tribela](https://github.com/mastodon/mastodon/pull/22541))
- Change trending tags admin interface to always show batch action controls ([ClearlyClaire](https://github.com/mastodon/mastodon/pull/23013))
- Change wording of some OAuth scope descriptions ([ClearlyClaire](https://github.com/mastodon/mastodon/pull/22491))
- Change wording of admin report handling actions ([ClearlyClaire](https://github.com/mastodon/mastodon/pull/18388))
- Change confirm prompts for relationships management ([tribela](https://github.com/mastodon/mastodon/pull/19411))
- Change language surrounding disability in prompts for media descriptions ([hs4man21](https://github.com/mastodon/mastodon/pull/20923))
- Change confusing wording in the sign in banner ([ClearlyClaire](https://github.com/mastodon/mastodon/pull/22490))
- Change account moderation notes to make links clickable ([ClearlyClaire](https://github.com/mastodon/mastodon/pull/22553))
- Change email address input to be read-only for logged-in users when requesting a new confirmation e-mail ([ClearlyClaire](https://github.com/mastodon/mastodon/pull/23247))
- Save avatar or header correctly even if the other one fails ([tribela](https://github.com/mastodon/mastodon/pull/18465))
- Change `referrer-policy` to `same-origin` application-wide ([ClearlyClaire](https://github.com/mastodon/mastodon/pull/23014), [ClearlyClaire](https://github.com/mastodon/mastodon/pull/23037))
- Add 'private' to `Cache-Control`, match Rails expectations ([daxtens](https://github.com/mastodon/mastodon/pull/20608))
- Make the button that expands the compose form differentiable from the button that publishes a post ([Tak](https://github.com/mastodon/mastodon/pull/20864))
- Change automatic post deletion configuration to be accessible to moved users ([ClearlyClaire](https://github.com/mastodon/mastodon/pull/20774))
- Make tag following idempotent ([trwnh](https://github.com/mastodon/mastodon/pull/20860), [ClearlyClaire](https://github.com/mastodon/mastodon/pull/21285))
- Use buildx functions for faster builds ([inductor](https://github.com/mastodon/mastodon/pull/20692))
- Split off Dockerfile components for faster builds ([moritzheiber](https://github.com/mastodon/mastodon/pull/20933), [ineffyble](https://github.com/mastodon/mastodon/pull/20948), [BtbN](https://github.com/mastodon/mastodon/pull/21028))
- Change last occurrence of “silence” to “limit” in UI text ([cincodenada](https://github.com/mastodon/mastodon/pull/20637))
- Change “hide toot” to “hide post” ([seanthegeek](https://github.com/mastodon/mastodon/pull/22385))
- Don't allow URLs that contain non-normalized paths to be verified ([dgl](https://github.com/mastodon/mastodon/pull/20999))
- Change the “Trending now” header to be a link to the Explore page ([connorshea](https://github.com/mastodon/mastodon/pull/21759))
- Change PostgreSQL connection timeout from 2 minutes to 15 seconds ([ClearlyClaire](https://github.com/mastodon/mastodon/pull/21790))
- Make handle more easily selectable on profile page ([cadars](https://github.com/mastodon/mastodon/pull/21479))
- Allow admins to refresh remotely-suspended accounts ([ClearlyClaire](https://github.com/mastodon/mastodon/pull/22327))
- Change dropdown menu to contain “Copy link to post” even for non-public posts ([ClearlyClaire](https://github.com/mastodon/mastodon/pull/21316))
- Allow adding relays in secure mode and limited federation mode ([ineffyble](https://github.com/mastodon/mastodon/pull/22324))
- Change timestamps to be displayed using the user's timezone throughout the moderation interface ([FrancisMurillo](https://github.com/mastodon/mastodon/pull/21878), [ClearlyClaire](https://github.com/mastodon/mastodon/pull/22555))
- Change CSP directives on API to be tight and concise ([ClearlyClaire](https://github.com/mastodon/mastodon/pull/20960))
- Change web UI to not autofocus the compose form ([raboof](https://github.com/mastodon/mastodon/pull/16517))
- Change idempotency key handling for posting when database access is slow ([lambda](https://github.com/mastodon/mastodon/pull/21840))
- Change remote media files to be downloaded outside of transactions ([ClearlyClaire](https://github.com/mastodon/mastodon/pull/21796))
- Improve contrast of charts in “poll has ended” notifications ([j-f1](https://github.com/mastodon/mastodon/pull/22575))
- Change OEmbed detection and validation to be somewhat more lenient ([ineffyble](https://github.com/mastodon/mastodon/pull/22533))
- Widen ElasticSearch version detection to not display a warning for OpenSearch ([VyrCossont](https://github.com/mastodon/mastodon/pull/22422), [ClearlyClaire](https://github.com/mastodon/mastodon/pull/23064))
- Change link verification to allow pages larger than 1MB as long as the link is in the first 1MB ([untitaker](https://github.com/mastodon/mastodon/pull/22879))
- Update default Node.js version to Node.js 16 ([ineffyble](https://github.com/mastodon/mastodon/pull/22223), [ClearlyClaire](https://github.com/mastodon/mastodon/pull/22342))
### Removed
- Officially remove support for Ruby 2.6 ([ClearlyClaire](https://github.com/mastodon/mastodon/pull/21477))
- Remove `object-fit` polyfill used for old versions of Microsoft Edge ([shuuji3](https://github.com/mastodon/mastodon/pull/22693))
- Remove empty `title` tag from mailer layout ([nametoolong](https://github.com/mastodon/mastodon/pull/23078))
### Fixed
- **Fix changing domain block severity not undoing individual account effects** ([ClearlyClaire](https://github.com/mastodon/mastodon/pull/22135))
- Fix suspension worker crashing on S3-compatible setups without ACL support ([ClearlyClaire](https://github.com/mastodon/mastodon/pull/22487))
- Fix possible race conditions when suspending/unsuspending accounts ([ClearlyClaire](https://github.com/mastodon/mastodon/pull/22363))
- Fix being stuck in edit mode when deleting the edited status ([ClearlyClaire](https://github.com/mastodon/mastodon/pull/22126))
- Fix filters not being applied to some notification types ([ClearlyClaire](https://github.com/mastodon/mastodon/pull/23211))
- Fix some performance issues with `/admin/instances` ([ClearlyClaire](https://github.com/mastodon/mastodon/pull/21907))
- Fix some pre-4.0 admin audit logs ([ClearlyClaire](https://github.com/mastodon/mastodon/pull/22091))
- Fix moderation audit log items for warnings having incorrect links ([ClearlyClaire](https://github.com/mastodon/mastodon/pull/23242))
- Fix account activation being sometimes triggered before email confirmation ([ClearlyClaire](https://github.com/mastodon/mastodon/pull/23245))
- Fix missing OAuth scopes for admin APIs ([trwnh](https://github.com/mastodon/mastodon/pull/20918), [trwnh](https://github.com/mastodon/mastodon/pull/20979))
- Fix voter count not being cleared when a poll is reset ([afontenot](https://github.com/mastodon/mastodon/pull/21700))
- Fix attachments of edited statuses not being fetched ([ClearlyClaire](https://github.com/mastodon/mastodon/pull/21565))
- Fix irreversible and whole_word parameters handling in `/api/v1/filters` ([ClearlyClaire](https://github.com/mastodon/mastodon/pull/21988))
- Fix 500 error when marking posts as sensitive while some of them are deleted ([ClearlyClaire](https://github.com/mastodon/mastodon/pull/22134))
- Fix expanded statuses not always being scrolled into view ([ClearlyClaire](https://github.com/mastodon/mastodon/pull/21797))
- Fix not being able to scroll the remote interaction modal on small screens ([xendke](https://github.com/mastodon/mastodon/pull/21763))
- Fix audio player volume control on Safari ([minacle](https://github.com/mastodon/mastodon/pull/23187))
- Fix disappearing “Explore” tabs on Safari ([nyura](https://github.com/mastodon/mastodon/pull/20917), [ykzts](https://github.com/mastodon/mastodon/pull/20982))
- Fix wrong padding in RTL layout ([Gargron](https://github.com/mastodon/mastodon/pull/23157))
- Fix drag & drop upload area display in single-column mode ([ClearlyClaire](https://github.com/mastodon/mastodon/pull/23217))
- Fix being unable to get a single EmailDomainBlock from the admin API ([trwnh](https://github.com/mastodon/mastodon/pull/20846))
- Fix pagination of followed tags ([trwnh](https://github.com/mastodon/mastodon/pull/20861))
- Fix dropdown menu positions when scrolling ([sidp](https://github.com/mastodon/mastodon/pull/22916), [ClearlyClaire](https://github.com/mastodon/mastodon/pull/23062))
- Fix email with empty domain name labels passing validation ([ClearlyClaire](https://github.com/mastodon/mastodon/pull/23246))
- Fix mysterious registration failure when “Require a reason to join” is set with open registrations ([ClearlyClaire](https://github.com/mastodon/mastodon/pull/22127))
- Fix attachment rendering of edited posts in OpenGraph ([ClearlyClaire](https://github.com/mastodon/mastodon/pull/22270))
- Fix invalid/empty RSS feed link on account pages ([ClearlyClaire](https://github.com/mastodon/mastodon/pull/20772))
- Fix error in `VerifyLinkService` when processing links with no href ([joshuap](https://github.com/mastodon/mastodon/pull/20741))
- Fix error in `VerifyLinkService` when processing links with invalid URLs ([untitaker](https://github.com/mastodon/mastodon/pull/23204))
- Fix media uploads with FFmpeg 5 ([dead10ck](https://github.com/mastodon/mastodon/pull/21191))
- Fix sensitive flag not being set when replying to a post with a content warning under certain conditions ([kedamaDQ](https://github.com/mastodon/mastodon/pull/21724))
- Fix “Share @user's profile” profile menu item not working ([ClearlyClaire](https://github.com/mastodon/mastodon/pull/21490))
- Fix crash and incorrect behavior in `tootctl domains crawl` ([ClearlyClaire](https://github.com/mastodon/mastodon/pull/19004))
- Fix autoplay on iOS ([jamesadney](https://github.com/mastodon/mastodon/pull/21422))
- Fix spaces not being stripped in admin account search ([ClearlyClaire](https://github.com/mastodon/mastodon/pull/21324))
- Fix spaces not being stripped when adding relays ([ClearlyClaire](https://github.com/mastodon/mastodon/pull/22655))
- Fix infinite loading spinner instead of soft 404 for non-existing remote accounts ([ClearlyClaire](https://github.com/mastodon/mastodon/pull/21303))
- Fix minor visual issue with the top border of verified account fields ([j-f1](https://github.com/mastodon/mastodon/pull/22006))
- Fix pending account approval and rejection not being recorded in the admin audit log ([FrancisMurillo](https://github.com/mastodon/mastodon/pull/22088))
- Fix “Sign up” button with closed registrations not opening modal on mobile ([ClearlyClaire](https://github.com/mastodon/mastodon/pull/22060))
- Fix UI header overflowing on mobile ([ClearlyClaire](https://github.com/mastodon/mastodon/pull/21783))
- Fix 500 error when trying to migrate to an invalid address ([ClearlyClaire](https://github.com/mastodon/mastodon/pull/21462))
- Fix crash when trying to fetch unobtainable avatar of user using external authentication ([lochiiconnectivity](https://github.com/mastodon/mastodon/pull/22462))
- Fix potential duplicate statuses in Explore tab ([ClearlyClaire](https://github.com/mastodon/mastodon/pull/22121))
- Fix deprecation warning in `tootctl accounts rotate` ([ClearlyClaire](https://github.com/mastodon/mastodon/pull/22120))
- Fix missing style in warning and strike cards ([AtelierSnek](https://github.com/mastodon/mastodon/pull/22177), [ClearlyClaire](https://github.com/mastodon/mastodon/pull/22302))
- Fix wasteful request to `/api/v1/custom_emojis` when not logged in ([ClearlyClaire](https://github.com/mastodon/mastodon/pull/22326))
- Fix replies sometimes being delivered to user-blocked domains ([tribela](https://github.com/mastodon/mastodon/pull/22117))
- Fix admin dashboard crash when using some ElasticSearch replacements ([cortices](https://github.com/mastodon/mastodon/pull/21006))
- Fix profile avatar being slightly offset into left border ([RiedleroD](https://github.com/mastodon/mastodon/pull/20994))
- Fix N+1 queries in `NotificationsController` ([nametoolong](https://github.com/mastodon/mastodon/pull/21202))
- Fix being unable to react to announcements with the keycap number sign emoji ([kescherCode](https://github.com/mastodon/mastodon/pull/22231))
- Fix height computation of post embeds ([hodgesmr](https://github.com/mastodon/mastodon/pull/22141))
- Fix accessibility issue of the search bar due to hidden placeholder ([alexstine](https://github.com/mastodon/mastodon/pull/21275))
- Fix layout change handler not being removed due to a typo ([nschonni](https://github.com/mastodon/mastodon/pull/21829))
- Fix typo in the default `S3_HOSTNAME` used in the `mastodon:setup` rake task ([danp](https://github.com/mastodon/mastodon/pull/19932))
- Fix the top action bar appearing in the multi-column layout ([ClearlyClaire](https://github.com/mastodon/mastodon/pull/20943))
- Fix inability to use local LibreTranslate without setting `ALLOWED_PRIVATE_ADDRESSES` ([ClearlyClaire](https://github.com/mastodon/mastodon/pull/21926))
- Fix punycoded local domains not being prettified in initial state ([Tritlo](https://github.com/mastodon/mastodon/pull/21440))
- Fix CSP violation warning by removing inline CSS from SVG logo ([luxiaba](https://github.com/mastodon/mastodon/pull/20814))
- Fix margin for search field on medium window size ([minacle](https://github.com/mastodon/mastodon/pull/21606))
- Fix search popout scrolling with the page in single-column mode ([rgroothuijsen](https://github.com/mastodon/mastodon/pull/16463))
- Fix minor status cache hydration discrepancy ([ClearlyClaire](https://github.com/mastodon/mastodon/pull/19879))
- Fix `・` detection in hashtags ([parthoghosh24](https://github.com/mastodon/mastodon/pull/22888))
- Fix hashtag follows bypassing user blocks ([tribela](https://github.com/mastodon/mastodon/pull/22849))
- Fix moved accounts being incorrectly redirected to account settings when trying to view a remote profile ([ClearlyClaire](https://github.com/mastodon/mastodon/pull/22497))
- Fix site upload validations ([ClearlyClaire](https://github.com/mastodon/mastodon/pull/22479))
- Fix “Add new domain block” button using last submitted search value instead of the current one ([ClearlyClaire](https://github.com/mastodon/mastodon/pull/22485))
- Fix misleading hashtag warning when posting with “Followers only” or “Mentioned people only” visibility ([n0toose](https://github.com/mastodon/mastodon/pull/22827))
- Fix embedded posts with videos grabbing focus ([Akkiesoft](https://github.com/mastodon/mastodon/pull/22778))
- Fix `$` not being escaped in `.env.production` files generated by the `mastodon:setup` rake task ([ClearlyClaire](https://github.com/mastodon/mastodon/pull/23012), [ClearlyClaire](https://github.com/mastodon/mastodon/pull/23072))
- Fix sanitizer parsing link text as HTML when stripping unsupported links ([ClearlyClaire](https://github.com/mastodon/mastodon/pull/22558))
- Fix `scheduled_at` input not using `datetime-local` when editing announcements ([ClearlyClaire](https://github.com/mastodon/mastodon/pull/21896))
- Fix REST API serializer for `Account` not including `moved` when the moved account has itself moved ([ClearlyClaire](https://github.com/mastodon/mastodon/pull/22483))
- Fix `/api/v1/admin/trends/tags` using wrong serializer ([ClearlyClaire](https://github.com/mastodon/mastodon/pull/18943))
- Fix situations in which instance actor can be set to a Mastodon-incompatible name ([ClearlyClaire](https://github.com/mastodon/mastodon/pull/22307))
### Security
- Add `form-action` CSP directive ([ClearlyClaire](https://github.com/mastodon/mastodon/pull/20781), [ClearlyClaire](https://github.com/mastodon/mastodon/pull/20958), [ClearlyClaire](https://github.com/mastodon/mastodon/pull/20962))
- Fix unbounded recursion in account discovery ([ClearlyClaire](https://github.com/mastodon/mastodon/pull/22025))
- Revoke all authorized applications on password reset ([FrancisMurillo](https://github.com/mastodon/mastodon/pull/21325))
## [4.0.2] - 2022-11-15
### Fixed

View file

@ -10,12 +10,12 @@ gem 'puma', '~> 5.6'
gem 'rails', '~> 6.1.7'
gem 'sprockets', '~> 3.7.2'
gem 'thor', '~> 1.2'
gem 'rack', '~> 2.2.5'
gem 'rack', '~> 2.2.6'
gem 'hamlit-rails', '~> 0.2'
gem 'pg', '~> 1.4'
gem 'makara', '~> 0.5'
gem 'pghero', '~> 2.8'
gem 'pghero'
gem 'dotenv-rails', '~> 2.8'
gem 'aws-sdk-s3', '~> 1.117', require: false
@ -60,7 +60,7 @@ gem 'idn-ruby', require: 'idn'
gem 'kaminari', '~> 1.2'
gem 'link_header', '~> 0.0'
gem 'mime-types', '~> 3.4.1', require: 'mime/types/columnar'
gem 'nokogiri', '~> 1.13'
gem 'nokogiri', '~> 1.14'
gem 'nsa', '~> 0.2'
gem 'oj', '~> 3.13'
gem 'ox', '~> 2.14'

View file

@ -10,40 +10,40 @@ GIT
GEM
remote: https://rubygems.org/
specs:
actioncable (6.1.7)
actionpack (= 6.1.7)
activesupport (= 6.1.7)
actioncable (6.1.7.1)
actionpack (= 6.1.7.1)
activesupport (= 6.1.7.1)
nio4r (~> 2.0)
websocket-driver (>= 0.6.1)
actionmailbox (6.1.7)
actionpack (= 6.1.7)
activejob (= 6.1.7)
activerecord (= 6.1.7)
activestorage (= 6.1.7)
activesupport (= 6.1.7)
actionmailbox (6.1.7.1)
actionpack (= 6.1.7.1)
activejob (= 6.1.7.1)
activerecord (= 6.1.7.1)
activestorage (= 6.1.7.1)
activesupport (= 6.1.7.1)
mail (>= 2.7.1)
actionmailer (6.1.7)
actionpack (= 6.1.7)
actionview (= 6.1.7)
activejob (= 6.1.7)
activesupport (= 6.1.7)
actionmailer (6.1.7.1)
actionpack (= 6.1.7.1)
actionview (= 6.1.7.1)
activejob (= 6.1.7.1)
activesupport (= 6.1.7.1)
mail (~> 2.5, >= 2.5.4)
rails-dom-testing (~> 2.0)
actionpack (6.1.7)
actionview (= 6.1.7)
activesupport (= 6.1.7)
actionpack (6.1.7.1)
actionview (= 6.1.7.1)
activesupport (= 6.1.7.1)
rack (~> 2.0, >= 2.0.9)
rack-test (>= 0.6.3)
rails-dom-testing (~> 2.0)
rails-html-sanitizer (~> 1.0, >= 1.2.0)
actiontext (6.1.7)
actionpack (= 6.1.7)
activerecord (= 6.1.7)
activestorage (= 6.1.7)
activesupport (= 6.1.7)
actiontext (6.1.7.1)
actionpack (= 6.1.7.1)
activerecord (= 6.1.7.1)
activestorage (= 6.1.7.1)
activesupport (= 6.1.7.1)
nokogiri (>= 1.8.5)
actionview (6.1.7)
activesupport (= 6.1.7)
actionview (6.1.7.1)
activesupport (= 6.1.7.1)
builder (~> 3.1)
erubi (~> 1.4)
rails-dom-testing (~> 2.0)
@ -54,22 +54,22 @@ GEM
case_transform (>= 0.2)
jsonapi-renderer (>= 0.1.1.beta1, < 0.3)
active_record_query_trace (1.8)
activejob (6.1.7)
activesupport (= 6.1.7)
activejob (6.1.7.1)
activesupport (= 6.1.7.1)
globalid (>= 0.3.6)
activemodel (6.1.7)
activesupport (= 6.1.7)
activerecord (6.1.7)
activemodel (= 6.1.7)
activesupport (= 6.1.7)
activestorage (6.1.7)
actionpack (= 6.1.7)
activejob (= 6.1.7)
activerecord (= 6.1.7)
activesupport (= 6.1.7)
activemodel (6.1.7.1)
activesupport (= 6.1.7.1)
activerecord (6.1.7.1)
activemodel (= 6.1.7.1)
activesupport (= 6.1.7.1)
activestorage (6.1.7.1)
actionpack (= 6.1.7.1)
activejob (= 6.1.7.1)
activerecord (= 6.1.7.1)
activesupport (= 6.1.7.1)
marcel (~> 1.0)
mini_mime (>= 1.1.0)
activesupport (6.1.7)
activesupport (6.1.7.1)
concurrent-ruby (~> 1.0, >= 1.0.2)
i18n (>= 1.6, < 2)
minitest (>= 5.1)
@ -174,7 +174,7 @@ GEM
cocoon (1.2.15)
coderay (1.1.3)
color_diff (0.1)
concurrent-ruby (1.1.10)
concurrent-ruby (1.2.0)
connection_pool (2.3.0)
cose (1.2.1)
cbor (~> 0.5.9)
@ -184,6 +184,7 @@ GEM
crass (1.0.6)
css_parser (1.12.0)
addressable
date (3.3.3)
debug_inspector (1.0.0)
devise (4.8.1)
bcrypt (~> 3.0)
@ -223,7 +224,7 @@ GEM
faraday (~> 1)
multi_json
encryptor (3.0.0)
erubi (1.11.0)
erubi (1.12.0)
et-orbi (1.2.7)
tzinfo
excon (0.95.0)
@ -282,7 +283,7 @@ GEM
addressable (~> 2.7)
omniauth (>= 1.9, < 3)
openid_connect (~> 1.2)
globalid (1.0.0)
globalid (1.0.1)
activesupport (>= 5.0)
hamlit (2.13.0)
temple (>= 0.8.2)
@ -389,8 +390,11 @@ GEM
loofah (2.19.1)
crass (~> 1.0.2)
nokogiri (>= 1.5.9)
mail (2.7.1)
mail (2.8.0.1)
mini_mime (>= 0.1.1)
net-imap
net-pop
net-smtp
makara (0.5.1)
activerecord (>= 5.2.0)
marcel (1.0.2)
@ -403,12 +407,17 @@ GEM
mime-types-data (~> 3.2015)
mime-types-data (3.2022.0105)
mini_mime (1.1.2)
mini_portile2 (2.8.0)
mini_portile2 (2.8.1)
minitest (5.17.0)
msgpack (1.6.0)
multi_json (1.15.0)
multipart-post (2.1.1)
net-imap (0.3.4)
date
net-protocol
net-ldap (0.17.1)
net-pop (0.1.2)
net-protocol
net-protocol (0.1.3)
timeout
net-scp (4.0.0.rc1)
@ -417,7 +426,7 @@ GEM
net-protocol
net-ssh (7.0.1)
nio4r (2.5.8)
nokogiri (1.13.10)
nokogiri (1.14.0)
mini_portile2 (~> 2.8.0)
racc (~> 1.4)
nsa (0.2.8)
@ -453,16 +462,16 @@ GEM
openssl-signature_algorithm (1.2.1)
openssl (> 2.0, < 3.1)
orm_adapter (0.5.0)
ox (2.14.12)
ox (2.14.13)
parallel (1.22.1)
parser (3.1.3.0)
parser (3.2.0.0)
ast (~> 2.4.1)
parslet (2.0.0)
pastel (0.8.0)
tty-color (~> 0.5)
pg (1.4.5)
pghero (2.8.3)
activerecord (>= 5)
pghero (3.1.0)
activerecord (>= 6)
pkg-config (1.5.1)
posix-spawn (0.3.15)
premailer (1.18.0)
@ -488,8 +497,8 @@ GEM
pundit (2.3.0)
activesupport (>= 3.0.0)
raabro (1.4.0)
racc (1.6.1)
rack (2.2.5)
racc (1.6.2)
rack (2.2.6.2)
rack-attack (6.6.1)
rack (>= 1.0, < 3)
rack-cors (1.1.1)
@ -504,20 +513,20 @@ GEM
rack
rack-test (2.0.2)
rack (>= 1.3)
rails (6.1.7)
actioncable (= 6.1.7)
actionmailbox (= 6.1.7)
actionmailer (= 6.1.7)
actionpack (= 6.1.7)
actiontext (= 6.1.7)
actionview (= 6.1.7)
activejob (= 6.1.7)
activemodel (= 6.1.7)
activerecord (= 6.1.7)
activestorage (= 6.1.7)
activesupport (= 6.1.7)
rails (6.1.7.1)
actioncable (= 6.1.7.1)
actionmailbox (= 6.1.7.1)
actionmailer (= 6.1.7.1)
actionpack (= 6.1.7.1)
actiontext (= 6.1.7.1)
actionview (= 6.1.7.1)
activejob (= 6.1.7.1)
activemodel (= 6.1.7.1)
activerecord (= 6.1.7.1)
activestorage (= 6.1.7.1)
activesupport (= 6.1.7.1)
bundler (>= 1.15.0)
railties (= 6.1.7)
railties (= 6.1.7.1)
sprockets-rails (>= 2.0.0)
rails-controller-testing (1.0.5)
actionpack (>= 5.0.1.rc1)
@ -533,9 +542,9 @@ GEM
railties (>= 6.0.0, < 7)
rails-settings-cached (0.6.6)
rails (>= 4.2.0)
railties (6.1.7)
actionpack (= 6.1.7)
activesupport (= 6.1.7)
railties (6.1.7.1)
actionpack (= 6.1.7.1)
activesupport (= 6.1.7.1)
method_source
rake (>= 12.2)
thor (~> 1.0)
@ -551,7 +560,7 @@ GEM
redis (>= 4)
redlock (1.3.2)
redis (>= 3.0.0, < 6.0)
regexp_parser (2.6.1)
regexp_parser (2.6.2)
request_store (1.5.1)
rack (>= 1.4)
responders (3.0.1)
@ -586,18 +595,20 @@ GEM
rspec-support (3.11.1)
rspec_junit_formatter (0.6.0)
rspec-core (>= 2, < 4, != 2.12.0)
rubocop (1.42.0)
rubocop (1.44.0)
json (~> 2.3)
parallel (~> 1.10)
parser (>= 3.1.2.1)
parser (>= 3.2.0.0)
rainbow (>= 2.2.2, < 4.0)
regexp_parser (>= 1.8, < 3.0)
rexml (>= 3.2.5, < 4.0)
rubocop-ast (>= 1.24.1, < 2.0)
ruby-progressbar (~> 1.7)
unicode-display_width (>= 1.4.0, < 3.0)
unicode-display_width (>= 2.4.0, < 3.0)
rubocop-ast (1.24.1)
parser (>= 3.1.1.0)
rubocop-capybara (2.17.0)
rubocop (~> 1.41)
rubocop-performance (1.15.2)
rubocop (>= 1.7.0, < 2.0)
rubocop-ast (>= 0.4.0)
@ -605,8 +616,9 @@ GEM
activesupport (>= 4.2.0)
rack (>= 1.1)
rubocop (>= 1.33.0, < 2.0)
rubocop-rspec (2.16.0)
rubocop-rspec (2.18.1)
rubocop (~> 1.33)
rubocop-capybara (~> 2.17)
ruby-progressbar (1.11.0)
ruby-saml (1.13.0)
nokogiri (>= 1.10.5)
@ -704,7 +716,7 @@ GEM
unf (0.1.4)
unf_ext
unf_ext (0.0.8.2)
unicode-display_width (2.3.0)
unicode-display_width (2.4.2)
uniform_notifier (1.16.0)
validate_email (0.1.6)
activemodel (>= 3.0)
@ -811,7 +823,7 @@ DEPENDENCIES
memory_profiler
mime-types (~> 3.4.1)
net-ldap (~> 0.17)
nokogiri (~> 1.13)
nokogiri (~> 1.14)
nsa (~> 0.2)
oj (~> 3.13)
omniauth (~> 1.9)
@ -821,7 +833,7 @@ DEPENDENCIES
ox (~> 2.14)
parslet
pg (~> 1.4)
pghero (~> 2.8)
pghero
pkg-config (~> 1.5)
posix-spawn
premailer-rails
@ -831,7 +843,7 @@ DEPENDENCIES
public_suffix (~> 5.0)
puma (~> 5.6)
pundit (~> 2.3)
rack (~> 2.2.5)
rack (~> 2.2.6)
rack-attack (~> 6.6)
rack-cors (~> 1.1)
rack-test (~> 2.0)

View file

@ -21,7 +21,7 @@ module Admin
account_action.save!
if account_action.with_report?
redirect_to admin_reports_path
redirect_to admin_reports_path, notice: I18n.t('admin.reports.processed_msg', id: params[:report_id])
else
redirect_to admin_account_path(@account.id)
end

View file

@ -23,9 +23,7 @@ module Admin
@import = Admin::Import.new(import_params)
return render :new unless @import.validate
parse_import_data!(export_headers)
@data.take(Admin::Import::ROWS_PROCESSING_LIMIT).each do |row|
@import.csv_rows.each do |row|
domain = row['#domain'].strip
next if DomainAllow.allowed?(domain)

View file

@ -23,24 +23,30 @@ module Admin
@import = Admin::Import.new(import_params)
return render :new unless @import.validate
parse_import_data!(export_headers)
@global_private_comment = I18n.t('admin.export_domain_blocks.import.private_comment_template', source: @import.data_file_name, date: I18n.l(Time.now.utc))
@form = Form::DomainBlockBatch.new
@domain_blocks = @data.take(Admin::Import::ROWS_PROCESSING_LIMIT).filter_map do |row|
@domain_blocks = @import.csv_rows.filter_map do |row|
domain = row['#domain'].strip
next if DomainBlock.rule_for(domain).present?
domain_block = DomainBlock.new(domain: domain,
severity: row['#severity'].strip,
reject_media: row['#reject_media'].strip,
reject_reports: row['#reject_reports'].strip,
severity: row.fetch('#severity', :suspend),
reject_media: row.fetch('#reject_media', false),
reject_reports: row.fetch('#reject_reports', false),
private_comment: @global_private_comment,
public_comment: row['#public_comment']&.strip,
obfuscate: row['#obfuscate'].strip)
public_comment: row['#public_comment'],
obfuscate: row.fetch('#obfuscate', false))
domain_block if domain_block.valid?
if domain_block.invalid?
flash.now[:alert] = I18n.t('admin.export_domain_blocks.invalid_domain_block', error: domain_block.errors.full_messages.join(', '))
next
end
domain_block
rescue ArgumentError => e
flash.now[:alert] = I18n.t('admin.export_domain_blocks.invalid_domain_block', error: e.message)
next
end
@warning_domains = Instance.where(domain: @domain_blocks.map(&:domain)).where('EXISTS (SELECT 1 FROM follows JOIN accounts ON follows.account_id = accounts.id OR follows.target_account_id = accounts.id WHERE accounts.domain = instances.domain)').pluck(:domain)

View file

@ -3,6 +3,11 @@
class Admin::Reports::ActionsController < Admin::BaseController
before_action :set_report
def preview
authorize @report, :show?
@moderation_action = action_from_button
end
def create
authorize @report, :show?
@ -13,7 +18,8 @@ class Admin::Reports::ActionsController < Admin::BaseController
status_ids: @report.status_ids,
current_account: current_account,
report_id: @report.id,
send_email_notification: !@report.spam?
send_email_notification: !@report.spam?,
text: params[:text]
)
status_batch_action.save!
@ -23,13 +29,16 @@ class Admin::Reports::ActionsController < Admin::BaseController
report_id: @report.id,
target_account: @report.target_account,
current_account: current_account,
send_email_notification: !@report.spam?
send_email_notification: !@report.spam?,
text: params[:text]
)
account_action.save!
else
return redirect_to admin_report_path(@report), alert: I18n.t('admin.reports.unknown_action_msg', action: action_from_button)
end
redirect_to admin_reports_path
redirect_to admin_reports_path, notice: I18n.t('admin.reports.processed_msg', id: @report.id)
end
private
@ -47,6 +56,8 @@ class Admin::Reports::ActionsController < Admin::BaseController
'silence'
elsif params[:suspend]
'suspend'
elsif params[:moderation_action]
params[:moderation_action]
end
end
end

View file

@ -21,7 +21,17 @@ class Api::V1::Accounts::CredentialsController < Api::BaseController
private
def account_params
params.permit(:display_name, :note, :avatar, :header, :locked, :bot, :discoverable, fields_attributes: [:name, :value])
params.permit(
:display_name,
:note,
:avatar,
:header,
:locked,
:bot,
:discoverable,
:hide_collections,
fields_attributes: [:name, :value]
)
end
def user_settings_params

View file

@ -3,6 +3,14 @@
class Api::V1::Admin::Trends::TagsController < Api::V1::Trends::TagsController
before_action -> { authorize_if_got_token! :'admin:read' }
def index
if current_user&.can?(:manage_taxonomies)
render json: @tags, each_serializer: REST::Admin::TagSerializer
else
super
end
end
private
def enabled?

View file

@ -80,6 +80,7 @@ class Api::V1::StatusesController < Api::BaseController
current_account.id,
text: status_params[:status],
media_ids: status_params[:media_ids],
media_attributes: status_params[:media_attributes],
sensitive: status_params[:sensitive],
language: status_params[:language],
spoiler_text: status_params[:spoiler_text],
@ -131,6 +132,12 @@ class Api::V1::StatusesController < Api::BaseController
:scheduled_at,
:content_type,
media_ids: [],
media_attributes: [
:id,
:thumbnail,
:description,
:focus,
],
poll: [
:multiple,
:hide_totals,

View file

@ -26,14 +26,4 @@ module AdminExportControllerConcern
def import_params
params.require(:admin_import).permit(:data)
end
def import_data_path
params[:admin_import][:data].path
end
def parse_import_data!(default_headers)
data = CSV.read(import_data_path, headers: true, encoding: 'UTF-8')
data = CSV.read(import_data_path, headers: default_headers, encoding: 'UTF-8') unless data.headers&.first&.strip&.include?(default_headers[0])
@data = data.reject(&:blank?)
end
end

View file

@ -46,11 +46,11 @@ module SignatureVerification
end
def require_account_signature!
render plain: signature_verification_failure_reason, status: signature_verification_failure_code unless signed_request_account
render json: signature_verification_failure_reason, status: signature_verification_failure_code unless signed_request_account
end
def require_actor_signature!
render plain: signature_verification_failure_reason, status: signature_verification_failure_code unless signed_request_actor
render json: signature_verification_failure_reason, status: signature_verification_failure_code unless signed_request_actor
end
def signed_request?
@ -97,11 +97,11 @@ module SignatureVerification
actor = stoplight_wrap_request { actor_refresh_key!(actor) }
raise SignatureVerificationError, "Public key not found for key #{signature_params['keyId']}" if actor.nil?
raise SignatureVerificationError, "Could not refresh public key #{signature_params['keyId']}" if actor.nil?
return actor unless verify_signature(actor, signature, compare_signed_string).nil?
fail_with! "Verification failed for #{actor.to_log_human_identifier} #{actor.uri} using rsa-sha256 (RSASSA-PKCS1-v1_5 with SHA-256)"
fail_with! "Verification failed for #{actor.to_log_human_identifier} #{actor.uri} using rsa-sha256 (RSASSA-PKCS1-v1_5 with SHA-256)", signed_string: compare_signed_string, signature: signature_params['signature']
rescue SignatureVerificationError => e
fail_with! e.message
rescue HTTP::Error, OpenSSL::SSL::SSLError => e
@ -118,8 +118,8 @@ module SignatureVerification
private
def fail_with!(message)
@signature_verification_failure_reason = message
def fail_with!(message, **options)
@signature_verification_failure_reason = { error: message }.merge(options)
@signed_request_actor = nil
end
@ -209,8 +209,8 @@ module SignatureVerification
end
expires_time = Time.at(signature_params['expires'].to_i).utc if signature_params['expires'].present?
rescue ArgumentError
return false
rescue ArgumentError => e
raise SignatureVerificationError, "Invalid Date header: #{e.message}"
end
expires_time ||= created_time + 5.minutes unless created_time.nil?

View file

@ -7,17 +7,12 @@ module WebAppControllerConcern
prepend_before_action :redirect_unauthenticated_to_permalinks!
before_action :set_pack
before_action :set_app_body_class
before_action :set_referrer_policy_header
end
def set_app_body_class
@body_classes = 'app-body'
end
def set_referrer_policy_header
response.headers['Referrer-Policy'] = 'origin'
end
def redirect_unauthenticated_to_permalinks!
return if user_signed_in? # NOTE: Different from upstream because we allow moved users to log in

View file

@ -20,7 +20,7 @@ module Admin::ActionLogsHelper
when 'Status'
link_to log.human_identifier, log.permalink
when 'AccountWarning'
link_to log.human_identifier, admin_account_path(log.target_id)
link_to log.human_identifier, disputes_strike_path(log.target_id)
when 'Announcement'
link_to truncate(log.human_identifier), edit_admin_announcement_path(log.target_id)
when 'IpBlock', 'Instance', 'CustomEmoji'

View file

@ -181,6 +181,18 @@ export function submitCompose(routerHistory) {
dispatch(submitComposeRequest());
// If we're editing a post with media attachments, those have not
// necessarily been changed on the server. Do it now in the same
// API call.
let media_attributes;
if (statusId !== null) {
media_attributes = media.map(item => ({
id: item.get('id'),
description: item.get('description'),
focus: item.get('focus'),
}));
}
api(getState).request({
url: statusId === null ? '/api/v1/statuses' : `/api/v1/statuses/${statusId}`,
method: statusId === null ? 'post' : 'put',
@ -189,6 +201,7 @@ export function submitCompose(routerHistory) {
content_type: getState().getIn(['compose', 'content_type']),
in_reply_to_id: getState().getIn(['compose', 'in_reply_to'], null),
media_ids: media.map(item => item.get('id')),
media_attributes,
sensitive: getState().getIn(['compose', 'sensitive']) || (spoilerText.length > 0 && media.size !== 0),
spoiler_text: spoilerText,
visibility: getState().getIn(['compose', 'privacy']),
@ -415,11 +428,31 @@ export function changeUploadCompose(id, params) {
return (dispatch, getState) => {
dispatch(changeUploadComposeRequest());
api(getState).put(`/api/v1/media/${id}`, params).then(response => {
dispatch(changeUploadComposeSuccess(response.data));
}).catch(error => {
dispatch(changeUploadComposeFail(id, error));
});
let media = getState().getIn(['compose', 'media_attachments']).find((item) => item.get('id') === id);
// Editing already-attached media is deferred to editing the post itself.
// For simplicity's sake, fake an API reply.
if (media && !media.get('unattached')) {
let { description, focus } = params;
const data = media.toJS();
if (description) {
data.description = description;
}
if (focus) {
focus = focus.split(',');
data.meta = { focus: { x: parseFloat(focus[0]), y: parseFloat(focus[1]) } };
}
dispatch(changeUploadComposeSuccess(data, true));
} else {
api(getState).put(`/api/v1/media/${id}`, params).then(response => {
dispatch(changeUploadComposeSuccess(response.data, false));
}).catch(error => {
dispatch(changeUploadComposeFail(id, error));
});
}
};
};
@ -430,10 +463,11 @@ export function changeUploadComposeRequest() {
};
};
export function changeUploadComposeSuccess(media) {
export function changeUploadComposeSuccess(media, attached) {
return {
type: COMPOSE_UPLOAD_CHANGE_SUCCESS,
media: media,
attached: attached,
skipLoading: true,
};
};

View file

@ -1,8 +1,8 @@
export const DROPDOWN_MENU_OPEN = 'DROPDOWN_MENU_OPEN';
export const DROPDOWN_MENU_CLOSE = 'DROPDOWN_MENU_CLOSE';
export function openDropdownMenu(id, placement, keyboard, scroll_key) {
return { type: DROPDOWN_MENU_OPEN, id, placement, keyboard, scroll_key };
export function openDropdownMenu(id, keyboard, scroll_key) {
return { type: DROPDOWN_MENU_OPEN, id, keyboard, scroll_key };
}
export function closeDropdownMenu(id) {

View file

@ -1,9 +1,17 @@
import api from '../api';
import api, { getLinks } from '../api';
export const HASHTAG_FETCH_REQUEST = 'HASHTAG_FETCH_REQUEST';
export const HASHTAG_FETCH_SUCCESS = 'HASHTAG_FETCH_SUCCESS';
export const HASHTAG_FETCH_FAIL = 'HASHTAG_FETCH_FAIL';
export const FOLLOWED_HASHTAGS_FETCH_REQUEST = 'FOLLOWED_HASHTAGS_FETCH_REQUEST';
export const FOLLOWED_HASHTAGS_FETCH_SUCCESS = 'FOLLOWED_HASHTAGS_FETCH_SUCCESS';
export const FOLLOWED_HASHTAGS_FETCH_FAIL = 'FOLLOWED_HASHTAGS_FETCH_FAIL';
export const FOLLOWED_HASHTAGS_EXPAND_REQUEST = 'FOLLOWED_HASHTAGS_EXPAND_REQUEST';
export const FOLLOWED_HASHTAGS_EXPAND_SUCCESS = 'FOLLOWED_HASHTAGS_EXPAND_SUCCESS';
export const FOLLOWED_HASHTAGS_EXPAND_FAIL = 'FOLLOWED_HASHTAGS_EXPAND_FAIL';
export const HASHTAG_FOLLOW_REQUEST = 'HASHTAG_FOLLOW_REQUEST';
export const HASHTAG_FOLLOW_SUCCESS = 'HASHTAG_FOLLOW_SUCCESS';
export const HASHTAG_FOLLOW_FAIL = 'HASHTAG_FOLLOW_FAIL';
@ -37,6 +45,78 @@ export const fetchHashtagFail = error => ({
error,
});
export const fetchFollowedHashtags = () => (dispatch, getState) => {
dispatch(fetchFollowedHashtagsRequest());
api(getState).get('/api/v1/followed_tags').then(response => {
const next = getLinks(response).refs.find(link => link.rel === 'next');
dispatch(fetchFollowedHashtagsSuccess(response.data, next ? next.uri : null));
}).catch(err => {
dispatch(fetchFollowedHashtagsFail(err));
});
};
export function fetchFollowedHashtagsRequest() {
return {
type: FOLLOWED_HASHTAGS_FETCH_REQUEST,
};
};
export function fetchFollowedHashtagsSuccess(followed_tags, next) {
return {
type: FOLLOWED_HASHTAGS_FETCH_SUCCESS,
followed_tags,
next,
};
};
export function fetchFollowedHashtagsFail(error) {
return {
type: FOLLOWED_HASHTAGS_FETCH_FAIL,
error,
};
};
export function expandFollowedHashtags() {
return (dispatch, getState) => {
const url = getState().getIn(['followed_tags', 'next']);
if (url === null) {
return;
}
dispatch(expandFollowedHashtagsRequest());
api(getState).get(url).then(response => {
const next = getLinks(response).refs.find(link => link.rel === 'next');
dispatch(expandFollowedHashtagsSuccess(response.data, next ? next.uri : null));
}).catch(error => {
dispatch(expandFollowedHashtagsFail(error));
});
};
};
export function expandFollowedHashtagsRequest() {
return {
type: FOLLOWED_HASHTAGS_EXPAND_REQUEST,
};
};
export function expandFollowedHashtagsSuccess(followed_tags, next) {
return {
type: FOLLOWED_HASHTAGS_EXPAND_SUCCESS,
followed_tags,
next,
};
};
export function expandFollowedHashtagsFail(error) {
return {
type: FOLLOWED_HASHTAGS_EXPAND_FAIL,
error,
};
};
export const followHashtag = name => (dispatch, getState) => {
dispatch(followHashtagRequest(name));

View file

@ -50,7 +50,7 @@ export default class Trends extends React.PureComponent {
<Hashtag
key={hashtag.name}
name={hashtag.name}
href={`/admin/tags/${hashtag.id}`}
href={hashtag.id === undefined ? undefined : `/admin/tags/${hashtag.id}`}
people={hashtag.history[0].accounts * 1 + hashtag.history[1].accounts * 1}
uses={hashtag.history[0].uses * 1 + hashtag.history[1].uses * 1}
history={hashtag.history.reverse().map(day => day.uses)}

View file

@ -50,6 +50,7 @@ export default class AutosuggestInput extends ImmutablePureComponent {
id: PropTypes.string,
searchTokens: PropTypes.arrayOf(PropTypes.string),
maxLength: PropTypes.number,
lang: PropTypes.string,
};
static defaultProps = {
@ -185,7 +186,7 @@ export default class AutosuggestInput extends ImmutablePureComponent {
}
render () {
const { value, suggestions, disabled, placeholder, onKeyUp, autoFocus, className, id, maxLength } = this.props;
const { value, suggestions, disabled, placeholder, onKeyUp, autoFocus, className, id, maxLength, lang } = this.props;
const { suggestionsHidden } = this.state;
return (
@ -210,6 +211,7 @@ export default class AutosuggestInput extends ImmutablePureComponent {
id={id}
className={className}
maxLength={maxLength}
lang={lang}
/>
</label>

View file

@ -48,6 +48,7 @@ export default class AutosuggestTextarea extends ImmutablePureComponent {
onKeyDown: PropTypes.func,
onPaste: PropTypes.func.isRequired,
autoFocus: PropTypes.bool,
lang: PropTypes.string,
};
static defaultProps = {
@ -192,7 +193,7 @@ export default class AutosuggestTextarea extends ImmutablePureComponent {
}
render () {
const { value, suggestions, disabled, placeholder, onKeyUp, autoFocus, children } = this.props;
const { value, suggestions, disabled, placeholder, onKeyUp, autoFocus, lang, children } = this.props;
const { suggestionsHidden } = this.state;
return [
@ -216,6 +217,7 @@ export default class AutosuggestTextarea extends ImmutablePureComponent {
onPaste={this.onPaste}
dir='auto'
aria-autocomplete='list'
lang={lang}
/>
</label>
</div>

View file

@ -2,9 +2,7 @@ import React from 'react';
import PropTypes from 'prop-types';
import ImmutablePropTypes from 'react-immutable-proptypes';
import IconButton from './icon_button';
import Overlay from 'react-overlays/lib/Overlay';
import Motion from '../features/ui/util/optional_motion';
import spring from 'react-motion/lib/spring';
import Overlay from 'react-overlays/Overlay';
import { supportsPassiveEvents } from 'detect-passive-events';
import classNames from 'classnames';
import { CircularProgress } from 'flavours/glitch/components/loading_indicator';
@ -24,9 +22,6 @@ class DropdownMenu extends React.PureComponent {
scrollable: PropTypes.bool,
onClose: PropTypes.func.isRequired,
style: PropTypes.object,
placement: PropTypes.string,
arrowOffsetLeft: PropTypes.string,
arrowOffsetTop: PropTypes.string,
openedViaKeyboard: PropTypes.bool,
renderItem: PropTypes.func,
renderHeader: PropTypes.func,
@ -35,11 +30,6 @@ class DropdownMenu extends React.PureComponent {
static defaultProps = {
style: {},
placement: 'bottom',
};
state = {
mounted: false,
};
handleDocumentClick = e => {
@ -56,8 +46,6 @@ class DropdownMenu extends React.PureComponent {
if (this.focusedItem && this.props.openedViaKeyboard) {
this.focusedItem.focus({ preventScroll: true });
}
this.setState({ mounted: true });
}
componentWillUnmount () {
@ -139,40 +127,28 @@ class DropdownMenu extends React.PureComponent {
}
render () {
const { items, style, placement, arrowOffsetLeft, arrowOffsetTop, scrollable, renderHeader, loading } = this.props;
const { mounted } = this.state;
const { items, scrollable, renderHeader, loading } = this.props;
let renderItem = this.props.renderItem || this.renderItem;
return (
<Motion defaultStyle={{ opacity: 0, scaleX: 0.85, scaleY: 0.75 }} style={{ opacity: spring(1, { damping: 35, stiffness: 400 }), scaleX: spring(1, { damping: 35, stiffness: 400 }), scaleY: spring(1, { damping: 35, stiffness: 400 }) }}>
{({ opacity, scaleX, scaleY }) => (
// It should not be transformed when mounting because the resulting
// size will be used to determine the coordinate of the menu by
// react-overlays
<div className={`dropdown-menu ${placement}`} style={{ ...style, opacity: opacity, transform: mounted ? `scale(${scaleX}, ${scaleY})` : null }} ref={this.setRef}>
<div className={`dropdown-menu__arrow ${placement}`} style={{ left: arrowOffsetLeft, top: arrowOffsetTop }} />
<div className={classNames('dropdown-menu__container', { 'dropdown-menu__container--loading': loading })} ref={this.setRef}>
{loading && (
<CircularProgress size={30} strokeWidth={3.5} />
)}
<div className={classNames('dropdown-menu__container', { 'dropdown-menu__container--loading': loading })}>
{loading && (
<CircularProgress size={30} strokeWidth={3.5} />
)}
{!loading && renderHeader && (
<div className='dropdown-menu__container__header'>
{renderHeader(items)}
</div>
)}
{!loading && (
<ul className={classNames('dropdown-menu__container__list', { 'dropdown-menu__container__list--scrollable': scrollable })}>
{items.map((option, i) => renderItem(option, i, { onClick: this.handleClick, onKeyPress: this.handleItemKeyPress }))}
</ul>
)}
</div>
{!loading && renderHeader && (
<div className='dropdown-menu__container__header'>
{renderHeader(items)}
</div>
)}
</Motion>
{!loading && (
<ul className={classNames('dropdown-menu__container__list', { 'dropdown-menu__container__list--scrollable': scrollable })}>
{items.map((option, i) => renderItem(option, i, { onClick: this.handleClick, onKeyPress: this.handleItemKeyPress }))}
</ul>
)}
</div>
);
}
@ -197,7 +173,6 @@ export default class Dropdown extends React.PureComponent {
isUserTouching: PropTypes.func,
onOpen: PropTypes.func.isRequired,
onClose: PropTypes.func.isRequired,
dropdownPlacement: PropTypes.string,
openDropdownId: PropTypes.number,
openedViaKeyboard: PropTypes.bool,
renderItem: PropTypes.func,
@ -213,13 +188,11 @@ export default class Dropdown extends React.PureComponent {
id: id++,
};
handleClick = ({ target, type }) => {
handleClick = ({ type }) => {
if (this.state.id === this.props.openDropdownId) {
this.handleClose();
} else {
const { top } = target.getBoundingClientRect();
const placement = top * 2 < innerHeight ? 'bottom' : 'top';
this.props.onOpen(this.state.id, this.handleItemClick, placement, type !== 'click');
this.props.onOpen(this.state.id, this.handleItemClick, type !== 'click');
}
}
@ -303,7 +276,6 @@ export default class Dropdown extends React.PureComponent {
disabled,
loading,
scrollable,
dropdownPlacement,
openDropdownId,
openedViaKeyboard,
children,
@ -314,7 +286,6 @@ export default class Dropdown extends React.PureComponent {
const open = this.state.id === openDropdownId;
const button = children ? React.cloneElement(React.Children.only(children), {
ref: this.setTargetRef,
onClick: this.handleClick,
onMouseDown: this.handleMouseDown,
onKeyDown: this.handleButtonKeyDown,
@ -326,7 +297,6 @@ export default class Dropdown extends React.PureComponent {
active={open}
disabled={disabled}
size={size}
ref={this.setTargetRef}
onClick={this.handleClick}
onMouseDown={this.handleMouseDown}
onKeyDown={this.handleButtonKeyDown}
@ -336,19 +306,27 @@ export default class Dropdown extends React.PureComponent {
return (
<React.Fragment>
{button}
<Overlay show={open} placement={dropdownPlacement} target={this.findTarget}>
<DropdownMenu
items={items}
loading={loading}
scrollable={scrollable}
onClose={this.handleClose}
openedViaKeyboard={openedViaKeyboard}
renderItem={renderItem}
renderHeader={renderHeader}
onItemClick={this.handleItemClick}
/>
<span ref={this.setTargetRef}>
{button}
</span>
<Overlay show={open} offset={[5, 5]} placement={'bottom'} flip target={this.findTarget} popperConfig={{ strategy: 'fixed' }}>
{({ props, arrowProps, placement }) => (
<div {...props}>
<div className={`dropdown-animation dropdown-menu ${placement}`}>
<div className={`dropdown-menu__arrow ${placement}`} {...arrowProps} />
<DropdownMenu
items={items}
loading={loading}
scrollable={scrollable}
onClose={this.handleClose}
openedViaKeyboard={openedViaKeyboard}
renderItem={renderItem}
renderHeader={renderHeader}
onItemClick={this.handleItemClick}
/>
</div>
</div>
)}
</Overlay>
</React.Fragment>
);

View file

@ -4,7 +4,6 @@ import { fetchHistory } from 'flavours/glitch/actions/history';
import DropdownMenu from 'flavours/glitch/components/dropdown_menu';
const mapStateToProps = (state, { statusId }) => ({
dropdownPlacement: state.getIn(['dropdown_menu', 'placement']),
openDropdownId: state.getIn(['dropdown_menu', 'openId']),
openedViaKeyboard: state.getIn(['dropdown_menu', 'keyboard']),
items: state.getIn(['history', statusId, 'items']),
@ -13,9 +12,9 @@ const mapStateToProps = (state, { statusId }) => ({
const mapDispatchToProps = (dispatch, { statusId }) => ({
onOpen (id, onItemClick, dropdownPlacement, keyboard) {
onOpen (id, onItemClick, keyboard) {
dispatch(fetchHistory(statusId));
dispatch(openDropdownMenu(id, dropdownPlacement, keyboard));
dispatch(openDropdownMenu(id, keyboard));
},
onClose (id) {

View file

@ -5,18 +5,17 @@ import DropdownMenu from 'flavours/glitch/components/dropdown_menu';
import { isUserTouching } from '../is_mobile';
const mapStateToProps = state => ({
dropdownPlacement: state.getIn(['dropdown_menu', 'placement']),
openDropdownId: state.getIn(['dropdown_menu', 'openId']),
openedViaKeyboard: state.getIn(['dropdown_menu', 'keyboard']),
});
const mapDispatchToProps = (dispatch, { status, items, scrollKey }) => ({
onOpen(id, onItemClick, dropdownPlacement, keyboard) {
onOpen(id, onItemClick, keyboard) {
dispatch(isUserTouching() ? openModal('ACTIONS', {
status,
actions: items,
onClick: onItemClick,
}) : openDropdownMenu(id, dropdownPlacement, keyboard, scrollKey));
}) : openDropdownMenu(id, keyboard, scrollKey));
},
onClose(id) {

View file

@ -45,6 +45,7 @@ const messages = defineMessages({
follow_requests: { id: 'navigation_bar.follow_requests', defaultMessage: 'Follow requests' },
favourites: { id: 'navigation_bar.favourites', defaultMessage: 'Favourites' },
lists: { id: 'navigation_bar.lists', defaultMessage: 'Lists' },
followed_tags: { id: 'navigation_bar.followed_tags', defaultMessage: 'Followed hashtags' },
blocks: { id: 'navigation_bar.blocks', defaultMessage: 'Blocked users' },
domain_blocks: { id: 'navigation_bar.domain_blocks', defaultMessage: 'Blocked domains' },
mutes: { id: 'navigation_bar.mutes', defaultMessage: 'Muted users' },
@ -188,7 +189,7 @@ class Header extends ImmutablePureComponent {
}
if (account.getIn(['relationship', 'requested']) || account.getIn(['relationship', 'following'])) {
bellBtn = <IconButton icon='bell-o' size={24} active={account.getIn(['relationship', 'notifying'])} title={intl.formatMessage(account.getIn(['relationship', 'notifying']) ? messages.disableNotifications : messages.enableNotifications, { name: account.get('username') })} onClick={this.props.onNotifyToggle} />;
bellBtn = <IconButton icon={account.getIn(['relationship', 'notifying']) ? 'bell' : 'bell-o'} size={24} active={account.getIn(['relationship', 'notifying'])} title={intl.formatMessage(account.getIn(['relationship', 'notifying']) ? messages.disableNotifications : messages.enableNotifications, { name: account.get('username') })} onClick={this.props.onNotifyToggle} />;
}
if (me !== account.get('id')) {
@ -245,6 +246,7 @@ class Header extends ImmutablePureComponent {
menu.push({ text: intl.formatMessage(messages.follow_requests), to: '/follow_requests' });
menu.push({ text: intl.formatMessage(messages.favourites), to: '/favourites' });
menu.push({ text: intl.formatMessage(messages.lists), to: '/lists' });
menu.push({ text: intl.formatMessage(messages.followed_tags), to: '/followed_tags' });
menu.push(null);
menu.push({ text: intl.formatMessage(messages.mutes), to: '/mutes' });
menu.push({ text: intl.formatMessage(messages.blocks), to: '/blocks' });

View file

@ -59,7 +59,7 @@ class Audio extends React.PureComponent {
duration: null,
paused: true,
muted: false,
volume: 0.5,
volume: 1,
dragging: false,
revealed: this.props.visible !== undefined ? this.props.visible : (displayMedia !== 'hide_all' && !this.props.sensitive || displayMedia === 'show_all'),
};
@ -80,8 +80,8 @@ class Audio extends React.PureComponent {
_pack() {
return {
src: this.props.src,
volume: this.audio.volume,
muted: this.audio.muted,
volume: this.state.volume,
muted: this.state.muted,
currentTime: this.audio.currentTime,
poster: this.props.poster,
backgroundColor: this.props.backgroundColor,
@ -117,7 +117,8 @@ class Audio extends React.PureComponent {
this.audio = c;
if (this.audio) {
this.setState({ volume: this.audio.volume, muted: this.audio.muted });
this.audio.volume = 1;
this.audio.muted = false;
}
}
@ -208,7 +209,9 @@ class Audio extends React.PureComponent {
const muted = !this.state.muted;
this.setState({ muted }, () => {
this.audio.muted = muted;
if (this.gainNode) {
this.gainNode.gain.value = muted ? 0 : this.state.volume;
}
});
}
@ -286,7 +289,9 @@ class Audio extends React.PureComponent {
if(!isNaN(x)) {
this.setState({ volume: x }, () => {
this.audio.volume = x;
if (this.gainNode) {
this.gainNode.gain.value = this.state.muted ? 0 : x;
}
});
}
}, 15);
@ -319,20 +324,12 @@ class Audio extends React.PureComponent {
}
handleLoadedData = () => {
const { autoPlay, currentTime, volume, muted } = this.props;
const { autoPlay, currentTime } = this.props;
if (currentTime) {
this.audio.currentTime = currentTime;
}
if (volume !== undefined) {
this.audio.volume = volume;
}
if (muted !== undefined) {
this.audio.muted = muted;
}
if (autoPlay) {
this.togglePlay();
}
@ -342,11 +339,16 @@ class Audio extends React.PureComponent {
const AudioContext = window.AudioContext || window.webkitAudioContext;
const context = new AudioContext();
const source = context.createMediaElementSource(this.audio);
const gainNode = context.createGain();
gainNode.gain.value = this.state.muted ? 0 : this.state.volume;
this.visualizer.setAudioContext(context, source);
source.connect(context.destination);
source.connect(gainNode);
gainNode.connect(context.destination);
this.audioContext = context;
this.gainNode = gainNode;
}
handleDownload = () => {

View file

@ -12,6 +12,7 @@ const messages = defineMessages({
follow_requests: { id: 'navigation_bar.follow_requests', defaultMessage: 'Follow requests' },
favourites: { id: 'navigation_bar.favourites', defaultMessage: 'Favourites' },
lists: { id: 'navigation_bar.lists', defaultMessage: 'Lists' },
followed_tags: { id: 'navigation_bar.followed_tags', defaultMessage: 'Followed hashtags' },
blocks: { id: 'navigation_bar.blocks', defaultMessage: 'Blocked users' },
domain_blocks: { id: 'navigation_bar.domain_blocks', defaultMessage: 'Hidden domains' },
mutes: { id: 'navigation_bar.mutes', defaultMessage: 'Muted users' },
@ -46,6 +47,7 @@ class ActionBar extends React.PureComponent {
menu.push({ text: intl.formatMessage(messages.favourites), to: '/favourites' });
menu.push({ text: intl.formatMessage(messages.bookmarks), to: '/bookmarks' });
menu.push({ text: intl.formatMessage(messages.lists), to: '/lists' });
menu.push({ text: intl.formatMessage(messages.followed_tags), to: '/followed_tags' });
menu.push(null);
menu.push({ text: intl.formatMessage(messages.mutes), to: '/mutes' });
menu.push({ text: intl.formatMessage(messages.blocks), to: '/blocks' });

View file

@ -61,6 +61,7 @@ class ComposeForm extends ImmutablePureComponent {
anyMedia: PropTypes.bool,
isInReply: PropTypes.bool,
singleColumn: PropTypes.bool,
lang: PropTypes.string,
advancedOptions: ImmutablePropTypes.map,
layout: PropTypes.string,
@ -325,6 +326,7 @@ class ComposeForm extends ImmutablePureComponent {
searchTokens={[':']}
id='glitch.composer.spoiler.input'
className='spoiler-input__input'
lang={this.props.lang}
autoFocus={false}
/>
</div>
@ -343,6 +345,7 @@ class ComposeForm extends ImmutablePureComponent {
onSuggestionSelected={this.onSuggestionSelected}
onPaste={onPaste}
autoFocus={!showSearch && !isMobile(window.innerWidth, layout)}
lang={this.props.lang}
>
<EmojiPickerDropdown onPickEmoji={handleEmojiPick} />
<TextareaIcons advancedOptions={advancedOptions} />

View file

@ -2,7 +2,7 @@
import classNames from 'classnames';
import PropTypes from 'prop-types';
import React from 'react';
import Overlay from 'react-overlays/lib/Overlay';
import Overlay from 'react-overlays/Overlay';
// Components.
import IconButton from 'flavours/glitch/components/icon_button';
@ -45,7 +45,7 @@ export default class ComposerOptionsDropdown extends React.PureComponent {
};
// Toggles opening and closing the dropdown.
handleToggle = ({ target, type }) => {
handleToggle = ({ type }) => {
const { onModalOpen } = this.props;
const { open } = this.state;
@ -59,11 +59,9 @@ export default class ComposerOptionsDropdown extends React.PureComponent {
}
}
} else {
const { top } = target.getBoundingClientRect();
if (this.state.open && this.activeElement) {
this.activeElement.focus({ preventScroll: true });
}
this.setState({ placement: top * 2 < innerHeight ? 'bottom' : 'top' });
this.setState({ open: !this.state.open, openedViaKeyboard: type !== 'click' });
}
}
@ -158,6 +156,18 @@ export default class ComposerOptionsDropdown extends React.PureComponent {
};
}
setTargetRef = c => {
this.target = c;
}
findTarget = () => {
return this.target;
}
handleOverlayEnter = (state) => {
this.setState({ placement: state.placement });
}
// Rendering.
render () {
const {
@ -179,6 +189,7 @@ export default class ComposerOptionsDropdown extends React.PureComponent {
<div
className={classNames('privacy-dropdown', placement, { active: open })}
onKeyDown={this.handleKeyDown}
ref={this.setTargetRef}
>
<div className={classNames('privacy-dropdown__value', { active })}>
<IconButton
@ -204,18 +215,26 @@ export default class ComposerOptionsDropdown extends React.PureComponent {
containerPadding={20}
placement={placement}
show={open}
target={this}
flip
target={this.findTarget}
container={container}
popperConfig={{ strategy: 'fixed', onFirstUpdate: this.handleOverlayEnter }}
>
<DropdownMenu
items={items}
renderItemContents={renderItemContents}
onChange={onChange}
onClose={this.handleClose}
value={value}
openedViaKeyboard={this.state.openedViaKeyboard}
closeOnChange={closeOnChange}
/>
{({ props, placement }) => (
<div {...props}>
<div className={`dropdown-animation privacy-dropdown__dropdown ${placement}`}>
<DropdownMenu
items={items}
renderItemContents={renderItemContents}
onChange={onChange}
onClose={this.handleClose}
value={value}
openedViaKeyboard={this.state.openedViaKeyboard}
closeOnChange={closeOnChange}
/>
</div>
</div>
)}
</Overlay>
</div>
);

View file

@ -1,7 +1,6 @@
// Package imports.
import PropTypes from 'prop-types';
import React from 'react';
import spring from 'react-motion/lib/spring';
import ImmutablePureComponent from 'react-immutable-pure-component';
import classNames from 'classnames';
@ -10,15 +9,8 @@ import Icon from 'flavours/glitch/components/icon';
// Utils.
import { withPassive } from 'flavours/glitch/utils/dom_helpers';
import Motion from '../../ui/util/optional_motion';
import { assignHandlers } from 'flavours/glitch/utils/react_helpers';
// The spring to use with our motion.
const springMotion = spring(1, {
damping: 35,
stiffness: 400,
});
// The component.
export default class ComposerOptionsDropdownContent extends React.PureComponent {
@ -44,7 +36,6 @@ export default class ComposerOptionsDropdownContent extends React.PureComponent
};
state = {
mounted: false,
value: this.props.openedViaKeyboard ? this.props.items[0].name : undefined,
};
@ -56,7 +47,7 @@ export default class ComposerOptionsDropdownContent extends React.PureComponent
}
// Stores our node in `this.node`.
handleRef = (node) => {
setRef = (node) => {
this.node = node;
}
@ -69,7 +60,6 @@ export default class ComposerOptionsDropdownContent extends React.PureComponent
} else {
this.node.firstChild.focus({ preventScroll: true });
}
this.setState({ mounted: true });
}
// On unmounting, we remove our listeners.
@ -191,7 +181,6 @@ export default class ComposerOptionsDropdownContent extends React.PureComponent
// Rendering.
render () {
const { mounted } = this.state;
const {
items,
onChange,
@ -201,36 +190,9 @@ export default class ComposerOptionsDropdownContent extends React.PureComponent
// The result.
return (
<Motion
defaultStyle={{
opacity: 0,
scaleX: 0.85,
scaleY: 0.75,
}}
style={{
opacity: springMotion,
scaleX: springMotion,
scaleY: springMotion,
}}
>
{({ opacity, scaleX, scaleY }) => (
// It should not be transformed when mounting because the resulting
// size will be used to determine the coordinate of the menu by
// react-overlays
<div
className='privacy-dropdown__dropdown'
ref={this.handleRef}
role='listbox'
style={{
...style,
opacity: opacity,
transform: mounted ? `scale(${scaleX}, ${scaleY})` : null,
}}
>
{!!items && items.map((item, i) => this.renderItem(item, i))}
</div>
)}
</Motion>
<div style={{ ...style }} role='listbox' ref={this.setRef}>
{!!items && items.map((item, i) => this.renderItem(item, i))}
</div>
);
}

View file

@ -2,7 +2,7 @@ import React from 'react';
import PropTypes from 'prop-types';
import { defineMessages, injectIntl, FormattedMessage } from 'react-intl';
import { EmojiPicker as EmojiPickerAsync } from '../../ui/util/async-components';
import Overlay from 'react-overlays/lib/Overlay';
import Overlay from 'react-overlays/Overlay';
import classNames from 'classnames';
import ImmutablePropTypes from 'react-immutable-proptypes';
import { supportsPassiveEvents } from 'detect-passive-events';
@ -155,9 +155,6 @@ class EmojiPickerMenu extends React.PureComponent {
onClose: PropTypes.func.isRequired,
onPick: PropTypes.func.isRequired,
style: PropTypes.object,
placement: PropTypes.string,
arrowOffsetLeft: PropTypes.string,
arrowOffsetTop: PropTypes.string,
intl: PropTypes.object.isRequired,
skinTone: PropTypes.number.isRequired,
onSkinTone: PropTypes.func.isRequired,
@ -326,14 +323,13 @@ class EmojiPickerDropdown extends React.PureComponent {
state = {
active: false,
loading: false,
placement: null,
};
setRef = (c) => {
this.dropdown = c;
}
onShowDropdown = ({ target }) => {
onShowDropdown = () => {
this.setState({ active: true });
if (!EmojiPicker) {
@ -348,9 +344,6 @@ class EmojiPickerDropdown extends React.PureComponent {
this.setState({ loading: false, active: false });
});
}
const { top } = target.getBoundingClientRect();
this.setState({ placement: top * 2 < innerHeight ? 'bottom' : 'top' });
}
onHideDropdown = () => {
@ -384,7 +377,7 @@ class EmojiPickerDropdown extends React.PureComponent {
render () {
const { intl, onPickEmoji, onSkinTone, skinTone, frequentlyUsedEmojis, button } = this.props;
const title = intl.formatMessage(messages.emoji);
const { active, loading, placement } = this.state;
const { active, loading } = this.state;
return (
<div className='emoji-picker-dropdown' onKeyDown={this.handleKeyDown}>
@ -396,16 +389,22 @@ class EmojiPickerDropdown extends React.PureComponent {
/>}
</div>
<Overlay show={active} placement={placement} target={this.findTarget}>
<EmojiPickerMenu
custom_emojis={this.props.custom_emojis}
loading={loading}
onClose={this.onHideDropdown}
onPick={onPickEmoji}
onSkinTone={onSkinTone}
skinTone={skinTone}
frequentlyUsedEmojis={frequentlyUsedEmojis}
/>
<Overlay show={active} placement={'bottom'} target={this.findTarget} popperConfig={{ strategy: 'fixed' }}>
{({ props, placement })=> (
<div {...props} style={{ ...props.style, width: 299 }}>
<div className={`dropdown-animation ${placement}`}>
<EmojiPickerMenu
custom_emojis={this.props.custom_emojis}
loading={loading}
onClose={this.onHideDropdown}
onPick={onPickEmoji}
onSkinTone={onSkinTone}
skinTone={skinTone}
frequentlyUsedEmojis={frequentlyUsedEmojis}
/>
</div>
</div>
)}
</Overlay>
</div>
);

View file

@ -2,9 +2,7 @@ import React from 'react';
import PropTypes from 'prop-types';
import { injectIntl, defineMessages } from 'react-intl';
import TextIconButton from './text_icon_button';
import Overlay from 'react-overlays/lib/Overlay';
import Motion from 'flavours/glitch/features/ui/util/optional_motion';
import spring from 'react-motion/lib/spring';
import Overlay from 'react-overlays/Overlay';
import { supportsPassiveEvents } from 'detect-passive-events';
import classNames from 'classnames';
import { languages as preloadedLanguages } from 'flavours/glitch/initial_state';
@ -22,10 +20,8 @@ const listenerOptions = supportsPassiveEvents ? { passive: true } : false;
class LanguageDropdownMenu extends React.PureComponent {
static propTypes = {
style: PropTypes.object,
value: PropTypes.string.isRequired,
frequentlyUsedLanguages: PropTypes.arrayOf(PropTypes.string).isRequired,
placement: PropTypes.string.isRequired,
onClose: PropTypes.func.isRequired,
onChange: PropTypes.func.isRequired,
languages: PropTypes.arrayOf(PropTypes.arrayOf(PropTypes.string)),
@ -37,7 +33,6 @@ class LanguageDropdownMenu extends React.PureComponent {
};
state = {
mounted: false,
searchValue: '',
};
@ -50,7 +45,6 @@ class LanguageDropdownMenu extends React.PureComponent {
componentDidMount () {
document.addEventListener('click', this.handleDocumentClick, false);
document.addEventListener('touchend', this.handleDocumentClick, listenerOptions);
this.setState({ mounted: true });
// Because of https://github.com/react-bootstrap/react-bootstrap/issues/2614 we need
// to wait for a frame before focusing
@ -222,29 +216,22 @@ class LanguageDropdownMenu extends React.PureComponent {
}
render () {
const { style, placement, intl } = this.props;
const { mounted, searchValue } = this.state;
const { intl } = this.props;
const { searchValue } = this.state;
const isSearching = searchValue !== '';
const results = this.search();
return (
<Motion defaultStyle={{ opacity: 0, scaleX: 0.85, scaleY: 0.75 }} style={{ opacity: spring(1, { damping: 35, stiffness: 400 }), scaleX: spring(1, { damping: 35, stiffness: 400 }), scaleY: spring(1, { damping: 35, stiffness: 400 }) }}>
{({ opacity, scaleX, scaleY }) => (
// It should not be transformed when mounting because the resulting
// size will be used to determine the coordinate of the menu by
// react-overlays
<div className={`language-dropdown__dropdown ${placement}`} style={{ ...style, opacity: opacity, transform: mounted ? `scale(${scaleX}, ${scaleY})` : null }} ref={this.setRef}>
<div className='emoji-mart-search'>
<input type='search' value={searchValue} onChange={this.handleSearchChange} onKeyDown={this.handleSearchKeyDown} placeholder={intl.formatMessage(messages.search)} />
<button className='emoji-mart-search-icon' disabled={!isSearching} aria-label={intl.formatMessage(messages.clear)} onClick={this.handleClear}>{!isSearching ? loupeIcon : deleteIcon}</button>
</div>
<div ref={this.setRef}>
<div className='emoji-mart-search'>
<input type='search' value={searchValue} onChange={this.handleSearchChange} onKeyDown={this.handleSearchKeyDown} placeholder={intl.formatMessage(messages.search)} />
<button type='button' className='emoji-mart-search-icon' disabled={!isSearching} aria-label={intl.formatMessage(messages.clear)} onClick={this.handleClear}>{!isSearching ? loupeIcon : deleteIcon}</button>
</div>
<div className='language-dropdown__dropdown__results emoji-mart-scroll' role='listbox' ref={this.setListRef}>
{results.map(this.renderItem)}
</div>
</div>
)}
</Motion>
<div className='language-dropdown__dropdown__results emoji-mart-scroll' role='listbox' ref={this.setListRef}>
{results.map(this.renderItem)}
</div>
</div>
);
}
@ -266,14 +253,11 @@ class LanguageDropdown extends React.PureComponent {
placement: 'bottom',
};
handleToggle = ({ target }) => {
const { top } = target.getBoundingClientRect();
handleToggle = () => {
if (this.state.open && this.activeElement) {
this.activeElement.focus({ preventScroll: true });
}
this.setState({ placement: top * 2 < innerHeight ? 'bottom' : 'top' });
this.setState({ open: !this.state.open });
}
@ -293,13 +277,25 @@ class LanguageDropdown extends React.PureComponent {
onChange(value);
}
setTargetRef = c => {
this.target = c;
}
findTarget = () => {
return this.target;
}
handleOverlayEnter = (state) => {
this.setState({ placement: state.placement });
}
render () {
const { value, intl, frequentlyUsedLanguages } = this.props;
const { open, placement } = this.state;
return (
<div className={classNames('privacy-dropdown', { active: open })}>
<div className='privacy-dropdown__value'>
<div className={classNames('privacy-dropdown', placement, { active: open })}>
<div className='privacy-dropdown__value' ref={this.setTargetRef} >
<TextIconButton
className='privacy-dropdown__value-icon'
label={value && value.toUpperCase()}
@ -309,15 +305,20 @@ class LanguageDropdown extends React.PureComponent {
/>
</div>
<Overlay show={open} placement={placement} target={this}>
<LanguageDropdownMenu
value={value}
frequentlyUsedLanguages={frequentlyUsedLanguages}
onClose={this.handleClose}
onChange={this.handleChange}
placement={placement}
intl={intl}
/>
<Overlay show={open} placement={'bottom'} flip target={this.findTarget} popperConfig={{ strategy: 'fixed', onFirstUpdate: this.handleOverlayEnter }}>
{({ props, placement }) => (
<div {...props}>
<div className={`dropdown-animation language-dropdown__dropdown ${placement}`} >
<LanguageDropdownMenu
value={value}
frequentlyUsedLanguages={frequentlyUsedLanguages}
onClose={this.handleClose}
onChange={this.handleChange}
intl={intl}
/>
</div>
</div>
)}
</Overlay>
</div>
);

View file

@ -3,13 +3,12 @@ import classNames from 'classnames';
import PropTypes from 'prop-types';
import React from 'react';
import { connect } from 'react-redux';
import spring from 'react-motion/lib/spring';
import {
injectIntl,
FormattedMessage,
defineMessages,
} from 'react-intl';
import Overlay from 'react-overlays/lib/Overlay';
import Overlay from 'react-overlays/Overlay';
// Components.
import Icon from 'flavours/glitch/components/icon';
@ -17,7 +16,6 @@ import Icon from 'flavours/glitch/components/icon';
// Utils.
import { focusRoot } from 'flavours/glitch/utils/dom_helpers';
import { searchEnabled } from 'flavours/glitch/initial_state';
import Motion from '../../ui/util/optional_motion';
const messages = defineMessages({
placeholder: { id: 'search.placeholder', defaultMessage: 'Search' },
@ -26,31 +24,20 @@ const messages = defineMessages({
class SearchPopout extends React.PureComponent {
static propTypes = {
style: PropTypes.object,
};
render () {
const { style } = this.props;
const extraInformation = searchEnabled ? <FormattedMessage id='search_popout.tips.full_text' defaultMessage='Simple text returns statuses you have written, favourited, boosted, or have been mentioned in, as well as matching usernames, display names, and hashtags.' /> : <FormattedMessage id='search_popout.tips.text' defaultMessage='Simple text returns matching display names, usernames and hashtags' />;
return (
<div style={{ ...style, position: 'absolute', width: 285, zIndex: 2 }}>
<Motion defaultStyle={{ opacity: 0, scaleX: 0.85, scaleY: 0.75 }} style={{ opacity: spring(1, { damping: 35, stiffness: 400 }), scaleX: spring(1, { damping: 35, stiffness: 400 }), scaleY: spring(1, { damping: 35, stiffness: 400 }) }}>
{({ opacity, scaleX, scaleY }) => (
<div className='search-popout' style={{ opacity: opacity, transform: `scale(${scaleX}, ${scaleY})` }}>
<h4><FormattedMessage id='search_popout.search_format' defaultMessage='Advanced search format' /></h4>
<div className='search-popout'>
<h4><FormattedMessage id='search_popout.search_format' defaultMessage='Advanced search format' /></h4>
<ul>
<li><em>#example</em> <FormattedMessage id='search_popout.tips.hashtag' defaultMessage='hashtag' /></li>
<li><em>@username@domain</em> <FormattedMessage id='search_popout.tips.user' defaultMessage='user' /></li>
<li><em>URL</em> <FormattedMessage id='search_popout.tips.user' defaultMessage='user' /></li>
<li><em>URL</em> <FormattedMessage id='search_popout.tips.status' defaultMessage='status' /></li>
</ul>
<ul>
<li><em>#example</em> <FormattedMessage id='search_popout.tips.hashtag' defaultMessage='hashtag' /></li>
<li><em>@username@domain</em> <FormattedMessage id='search_popout.tips.user' defaultMessage='user' /></li>
<li><em>URL</em> <FormattedMessage id='search_popout.tips.user' defaultMessage='user' /></li>
<li><em>URL</em> <FormattedMessage id='search_popout.tips.status' defaultMessage='status' /></li>
</ul>
{extraInformation}
</div>
)}
</Motion>
{extraInformation}
</div>
);
}
@ -136,6 +123,10 @@ class Search extends React.PureComponent {
}
}
findTarget = () => {
return this.searchForm;
}
render () {
const { intl, value, submitted } = this.props;
const { expanded } = this.state;
@ -161,8 +152,14 @@ class Search extends React.PureComponent {
<Icon id='search' className={hasValue ? '' : 'active'} />
<Icon id='times-circle' className={hasValue ? 'active' : ''} />
</div>
<Overlay show={expanded && !hasValue} placement='bottom' target={this} container={this}>
<SearchPopout />
<Overlay show={expanded && !hasValue} placement='bottom' target={this.findTarget} popperConfig={{ strategy: 'fixed' }}>
{({ props, placement }) => (
<div {...props} style={{ ...props.style, width: 285, zIndex: 2 }}>
<div className={`dropdown-animation ${placement}`}>
<SearchPopout />
</div>
</div>
)}
</Overlay>
</div>
);

View file

@ -43,13 +43,13 @@ export default class Upload extends ImmutablePureComponent {
{({ scale }) => (
<div className='compose-form__upload-thumbnail' style={{ transform: `scale(${scale})`, backgroundImage: `url(${media.get('preview_url')})`, backgroundPosition: `${x}% ${y}%` }}>
<div className='compose-form__upload__actions'>
<button className='icon-button' onClick={this.handleUndoClick}><Icon id='times' /> <FormattedMessage id='upload_form.undo' defaultMessage='Delete' /></button>
{!!media.get('unattached') && (<button className='icon-button' onClick={this.handleFocalPointClick}><Icon id='pencil' /> <FormattedMessage id='upload_form.edit' defaultMessage='Edit' /></button>)}
<button type='button' className='icon-button' onClick={this.handleUndoClick}><Icon id='times' /> <FormattedMessage id='upload_form.undo' defaultMessage='Delete' /></button>
<button type='button' className='icon-button' onClick={this.handleFocalPointClick}><Icon id='pencil' /> <FormattedMessage id='upload_form.edit' defaultMessage='Edit' /></button>
</div>
{(media.get('description') || '').length === 0 && !!media.get('unattached') && (
{(media.get('description') || '').length === 0 && (
<div className='compose-form__upload__warning'>
<button className='icon-button' onClick={this.handleFocalPointClick}><Icon id='info-circle' /> <FormattedMessage id='upload_form.description_missing' defaultMessage='No description added' /></button>
<button type='button' className='icon-button' onClick={this.handleFocalPointClick}><Icon id='info-circle' /> <FormattedMessage id='upload_form.description_missing' defaultMessage='No description added' /></button>
</div>
)}
</div>

View file

@ -70,6 +70,7 @@ function mapStateToProps (state) {
mediaDescriptionConfirmation: state.getIn(['local_settings', 'confirm_missing_media_description']),
preselectOnReply: state.getIn(['local_settings', 'preselect_on_reply']),
isInReply: state.getIn(['compose', 'in_reply_to']) !== null,
lang: state.getIn(['compose', 'language']),
};
};

View file

@ -0,0 +1,89 @@
import { debounce } from 'lodash';
import PropTypes from 'prop-types';
import React from 'react';
import ImmutablePureComponent from 'react-immutable-pure-component';
import ImmutablePropTypes from 'react-immutable-proptypes';
import { defineMessages, injectIntl, FormattedMessage } from 'react-intl';
import { connect } from 'react-redux';
import ColumnHeader from 'flavours/glitch/components/column_header';
import ScrollableList from 'flavours/glitch/components/scrollable_list';
import Column from 'flavours/glitch/features/ui/components/column';
import { Helmet } from 'react-helmet';
import Hashtag from 'flavours/glitch/components/hashtag';
import { expandFollowedHashtags, fetchFollowedHashtags } from 'flavours/glitch/actions/tags';
const messages = defineMessages({
heading: { id: 'followed_tags', defaultMessage: 'Followed hashtags' },
});
const mapStateToProps = state => ({
hashtags: state.getIn(['followed_tags', 'items']),
isLoading: state.getIn(['followed_tags', 'isLoading'], true),
hasMore: !!state.getIn(['followed_tags', 'next']),
});
export default @connect(mapStateToProps)
@injectIntl
class FollowedTags extends ImmutablePureComponent {
static propTypes = {
params: PropTypes.object.isRequired,
dispatch: PropTypes.func.isRequired,
intl: PropTypes.object.isRequired,
hashtags: ImmutablePropTypes.list,
isLoading: PropTypes.bool,
hasMore: PropTypes.bool,
multiColumn: PropTypes.bool,
};
componentDidMount() {
this.props.dispatch(fetchFollowedHashtags());
};
handleLoadMore = debounce(() => {
this.props.dispatch(expandFollowedHashtags());
}, 300, { leading: true });
render () {
const { intl, hashtags, isLoading, hasMore, multiColumn } = this.props;
const emptyMessage = <FormattedMessage id='empty_column.followed_tags' defaultMessage='You have not followed any hashtags yet. When you do, they will show up here.' />;
return (
<Column bindToDocument={!multiColumn}>
<ColumnHeader
icon='hashtag'
title={intl.formatMessage(messages.heading)}
showBackButton
multiColumn={multiColumn}
/>
<ScrollableList
scrollKey='followed_tags'
emptyMessage={emptyMessage}
hasMore={hasMore}
isLoading={isLoading}
onLoadMore={this.handleLoadMore}
bindToDocument={!multiColumn}
>
{hashtags.map((hashtag) => (
<Hashtag
key={hashtag.get('name')}
name={hashtag.get('name')}
to={`/tags/${hashtag.get('name')}`}
withGraph={false}
// Taken from ImmutableHashtag. Should maybe refactor ImmutableHashtag to accept more options?
people={hashtag.getIn(['history', 0, 'accounts']) * 1 + hashtag.getIn(['history', 1, 'accounts']) * 1}
history={hashtag.get('history').reverse().map((day) => day.get('uses')).toArray()}
/>
))}
</ScrollableList>
<Helmet>
<meta name='robots' content='noindex' />
</Helmet>
</Column>
);
}
}

View file

@ -124,6 +124,7 @@ export default class Notification extends ImmutablePureComponent {
onMoveDown={onMoveDown}
onMoveUp={onMoveUp}
onMention={onMention}
contextType='notifications'
getScrollPosition={getScrollPosition}
updateScrollBottom={updateScrollBottom}
cachedMediaWidth={this.props.cachedMediaWidth}
@ -146,6 +147,7 @@ export default class Notification extends ImmutablePureComponent {
onMoveDown={onMoveDown}
onMoveUp={onMoveUp}
onMention={onMention}
contextType='notifications'
getScrollPosition={getScrollPosition}
updateScrollBottom={updateScrollBottom}
cachedMediaWidth={this.props.cachedMediaWidth}
@ -168,6 +170,7 @@ export default class Notification extends ImmutablePureComponent {
onMoveDown={onMoveDown}
onMoveUp={onMoveUp}
onMention={onMention}
contextType='notifications'
getScrollPosition={getScrollPosition}
updateScrollBottom={updateScrollBottom}
cachedMediaWidth={this.props.cachedMediaWidth}
@ -190,6 +193,7 @@ export default class Notification extends ImmutablePureComponent {
onMoveDown={onMoveDown}
onMoveUp={onMoveUp}
onMention={onMention}
contextType='notifications'
getScrollPosition={getScrollPosition}
updateScrollBottom={updateScrollBottom}
cachedMediaWidth={this.props.cachedMediaWidth}
@ -212,6 +216,7 @@ export default class Notification extends ImmutablePureComponent {
onMoveDown={onMoveDown}
onMoveUp={onMoveUp}
onMention={onMention}
contextType='notifications'
getScrollPosition={getScrollPosition}
updateScrollBottom={updateScrollBottom}
cachedMediaWidth={this.props.cachedMediaWidth}

View file

@ -320,7 +320,7 @@ class FocalPointModal extends ImmutablePureComponent {
<React.Fragment>
<label className='setting-text-label' htmlFor='upload-modal__thumbnail'><FormattedMessage id='upload_form.thumbnail' defaultMessage='Change thumbnail' /></label>
<Button disabled={isUploadingThumbnail} text={intl.formatMessage(messages.chooseImage)} onClick={this.handleFileInputClick} />
<Button disabled={isUploadingThumbnail || !media.get('unattached')} text={intl.formatMessage(messages.chooseImage)} onClick={this.handleFileInputClick} />
<label>
<span style={{ display: 'none' }}>{intl.formatMessage(messages.chooseImage)}</span>

View file

@ -52,6 +52,8 @@ class LinkFooter extends React.PureComponent {
const canInvite = signedIn && ((permissions & PERMISSION_INVITE_USERS) === PERMISSION_INVITE_USERS);
const canProfileDirectory = profileDirectory;
const DividingCircle = <span aria-hidden>{' · '}</span>;
return (
<div className='link-footer'>
<p>
@ -60,17 +62,17 @@ class LinkFooter extends React.PureComponent {
<Link key='about' to='/about'><FormattedMessage id='footer.about' defaultMessage='About' /></Link>
{canInvite && (
<>
{' · '}
{DividingCircle}
<a key='invites' href='/invites' target='_blank'><FormattedMessage id='footer.invite' defaultMessage='Invite people' /></a>
</>
)}
{canProfileDirectory && (
<>
{' · '}
{DividingCircle}
<Link key='directory' to='/directory'><FormattedMessage id='footer.directory' defaultMessage='Profiles directory' /></Link>
</>
)}
{' · '}
{DividingCircle}
<Link key='privacy-policy' to='/privacy-policy'><FormattedMessage id='footer.privacy_policy' defaultMessage='Privacy policy' /></Link>
</p>
@ -78,13 +80,13 @@ class LinkFooter extends React.PureComponent {
<strong>Mastodon</strong>:
{' '}
<a href='https://joinmastodon.org' target='_blank'><FormattedMessage id='footer.about' defaultMessage='About' /></a>
{' · '}
{DividingCircle}
<a href='https://joinmastodon.org/apps' target='_blank'><FormattedMessage id='footer.get_app' defaultMessage='Get the app' /></a>
{' · '}
{DividingCircle}
<Link to='/keyboard-shortcuts'><FormattedMessage id='footer.keyboard_shortcuts' defaultMessage='Keyboard shortcuts' /></Link>
{' · '}
{DividingCircle}
<a href={source_url} rel='noopener noreferrer' target='_blank'><FormattedMessage id='footer.source_code' defaultMessage='View source code' /></a>
{' · '}
{DividingCircle}
v{version}
</p>
</div>

View file

@ -30,7 +30,7 @@ const SignInBanner = () => {
return (
<div className='sign-in-banner'>
<p><FormattedMessage id='sign_in_banner.text' defaultMessage='Sign in to follow profiles or hashtags, favourite, share and reply to posts, or interact from your account on a different server.' /></p>
<p><FormattedMessage id='sign_in_banner.text' defaultMessage='Sign in to follow profiles or hashtags, favourite, share and reply to posts. You can also interact from your account on a different server.' /></p>
<a href='/auth/sign_in' className='button button--block'><FormattedMessage id='sign_in_banner.sign_in' defaultMessage='Sign in' /></a>
{signupButton}
</div>

View file

@ -50,6 +50,7 @@ export default class VideoModal extends ImmutablePureComponent {
autoPlay={options.autoPlay}
volume={options.defaultVolume}
onCloseVideo={onClose}
autoFocus
detailed
alt={media.get('description')}
/>

View file

@ -42,6 +42,7 @@ import {
FollowRequests,
FavouritedStatuses,
BookmarkedStatuses,
FollowedTags,
ListTimeline,
Blocks,
DomainBlocks,
@ -56,7 +57,7 @@ import {
PrivacyPolicy,
} from './util/async-components';
import { HotKeys } from 'react-hotkeys';
import initialState, { me, owner, singleUserMode, showTrends } from '../../initial_state';
import initialState, { me, owner, singleUserMode, showTrends, trendsAsLanding } from '../../initial_state';
import { closeOnboarding, INTRODUCTION_VERSION } from 'flavours/glitch/actions/onboarding';
import { defineMessages, FormattedMessage, injectIntl } from 'react-intl';
import { Helmet } from 'react-helmet';
@ -177,7 +178,7 @@ class SwitchingColumnsArea extends React.PureComponent {
}
} else if (singleUserMode && owner && initialState?.accounts[owner]) {
redirect = <Redirect from='/' to={`/@${initialState.accounts[owner].username}`} exact />;
} else if (showTrends) {
} else if (showTrends && trendsAsLanding) {
redirect = <Redirect from='/' to='/explore' exact />;
} else {
redirect = <Redirect from='/' to='/about' exact />;
@ -230,6 +231,7 @@ class SwitchingColumnsArea extends React.PureComponent {
<WrappedRoute path='/follow_requests' component={FollowRequests} content={children} />
<WrappedRoute path='/blocks' component={Blocks} content={children} />
<WrappedRoute path='/domain_blocks' component={DomainBlocks} content={children} />
<WrappedRoute path='/followed_tags' component={FollowedTags} content={children} />
<WrappedRoute path='/mutes' component={Mutes} content={children} />
<WrappedRoute path='/lists' component={Lists} content={children} />
<WrappedRoute path='/getting-started-misc' component={GettingStartedMisc} content={children} />

View file

@ -98,6 +98,10 @@ export function FavouritedStatuses () {
return import(/* webpackChunkName: "flavours/glitch/async/favourited_statuses" */'flavours/glitch/features/favourited_statuses');
}
export function FollowedTags () {
return import(/* webpackChunkName: "flavours/glitch/async/followed_tags" */'flavours/glitch/features/followed_tags');
}
export function BookmarkedStatuses () {
return import(/* webpackChunkName: "flavours/glitch/async/bookmarked_statuses" */'flavours/glitch/features/bookmarked_statuses');
}

View file

@ -124,6 +124,7 @@ class Video extends React.PureComponent {
volume: PropTypes.number,
muted: PropTypes.bool,
componentIndex: PropTypes.number,
autoFocus: PropTypes.bool,
};
static defaultProps = {
@ -537,7 +538,7 @@ class Video extends React.PureComponent {
}
render () {
const { preview, src, inline, onOpenVideo, onCloseVideo, intl, alt, letterbox, fullwidth, detailed, sensitive, editable, blurhash } = this.props;
const { preview, src, inline, onOpenVideo, onCloseVideo, intl, alt, letterbox, fullwidth, detailed, sensitive, editable, blurhash, autoFocus } = this.props;
const { containerWidth, currentTime, duration, volume, buffer, dragging, paused, fullscreen, hovered, muted, revealed } = this.state;
const progress = Math.min((currentTime / duration) * 100, 100);
const playerStyle = {};
@ -635,7 +636,7 @@ class Video extends React.PureComponent {
<div className='video-player__buttons-bar'>
<div className='video-player__buttons left'>
<button type='button' title={intl.formatMessage(paused ? messages.play : messages.pause)} aria-label={intl.formatMessage(paused ? messages.play : messages.pause)} className='player-button' onClick={this.togglePlay} autoFocus={detailed}><Icon id={paused ? 'play' : 'pause'} fixedWidth /></button>
<button type='button' title={intl.formatMessage(paused ? messages.play : messages.pause)} aria-label={intl.formatMessage(paused ? messages.play : messages.pause)} className='player-button' onClick={this.togglePlay} autoFocus={autoFocus}><Icon id={paused ? 'play' : 'pause'} fixedWidth /></button>
<button type='button' title={intl.formatMessage(muted ? messages.unmute : messages.mute)} aria-label={intl.formatMessage(muted ? messages.unmute : messages.mute)} className='player-button' onClick={this.toggleMute}><Icon id={muted ? 'volume-off' : 'volume-up'} fixedWidth /></button>
<div className={classNames('video-player__volume', { active: this.state.hovered })} onMouseDown={this.handleVolumeMouseDown} ref={this.setVolumeRef}>

View file

@ -75,6 +75,7 @@
* @property {boolean} timeline_preview
* @property {string} title
* @property {boolean} trends
* @property {boolean} trends_as_landing_page
* @property {boolean} unfollow_modal
* @property {boolean} use_blurhash
* @property {boolean=} use_pending_items
@ -134,6 +135,7 @@ export const singleUserMode = getMeta('single_user_mode');
export const source_url = getMeta('source_url');
export const timelinePreview = getMeta('timeline_preview');
export const title = getMeta('title');
export const trendsAsLanding = getMeta('trends_as_landing_page');
export const unfollowModal = getMeta('unfollow_modal');
export const useBlurhash = getMeta('use_blurhash');
export const usePendingItems = getMeta('use_pending_items');

View file

@ -551,7 +551,7 @@ export default function compose(state = initialState, action) {
.setIn(['media_modal', 'dirty'], false)
.update('media_attachments', list => list.map(item => {
if (item.get('id') === action.media.id) {
return fromJS(action.media).set('unattached', true);
return fromJS(action.media).set('unattached', !action.attached);
}
return item;

View file

@ -4,12 +4,12 @@ import {
DROPDOWN_MENU_CLOSE,
} from '../actions/dropdown_menu';
const initialState = Immutable.Map({ openId: null, placement: null, keyboard: false, scroll_key: null });
const initialState = Immutable.Map({ openId: null, keyboard: false, scroll_key: null });
export default function dropdownMenu(state = initialState, action) {
switch (action.type) {
case DROPDOWN_MENU_OPEN:
return state.merge({ openId: action.id, placement: action.placement, keyboard: action.keyboard, scroll_key: action.scroll_key });
return state.merge({ openId: action.id, keyboard: action.keyboard, scroll_key: action.scroll_key });
case DROPDOWN_MENU_CLOSE:
return state.get('openId') === action.id ? state.set('openId', null).set('scroll_key', null) : state;
default:

View file

@ -0,0 +1,42 @@
import {
FOLLOWED_HASHTAGS_FETCH_REQUEST,
FOLLOWED_HASHTAGS_FETCH_SUCCESS,
FOLLOWED_HASHTAGS_FETCH_FAIL,
FOLLOWED_HASHTAGS_EXPAND_REQUEST,
FOLLOWED_HASHTAGS_EXPAND_SUCCESS,
FOLLOWED_HASHTAGS_EXPAND_FAIL,
} from 'flavours/glitch/actions/tags';
import { Map as ImmutableMap, List as ImmutableList, fromJS } from 'immutable';
const initialState = ImmutableMap({
items: ImmutableList(),
isLoading: false,
next: null,
});
export default function followed_tags(state = initialState, action) {
switch(action.type) {
case FOLLOWED_HASHTAGS_FETCH_REQUEST:
return state.set('isLoading', true);
case FOLLOWED_HASHTAGS_FETCH_SUCCESS:
return state.withMutations(map => {
map.set('items', fromJS(action.followed_tags));
map.set('isLoading', false);
map.set('next', action.next);
});
case FOLLOWED_HASHTAGS_FETCH_FAIL:
return state.set('isLoading', false);
case FOLLOWED_HASHTAGS_EXPAND_REQUEST:
return state.set('isLoading', true);
case FOLLOWED_HASHTAGS_EXPAND_SUCCESS:
return state.withMutations(map => {
map.update('items', set => set.concat(fromJS(action.followed_tags)));
map.set('isLoading', false);
map.set('next', action.next);
});
case FOLLOWED_HASHTAGS_EXPAND_FAIL:
return state.set('isLoading', false);
default:
return state;
}
};

View file

@ -42,6 +42,7 @@ import picture_in_picture from './picture_in_picture';
import accounts_map from './accounts_map';
import history from './history';
import tags from './tags';
import followed_tags from './followed_tags';
const reducers = {
announcements,
@ -87,6 +88,7 @@ const reducers = {
picture_in_picture,
history,
tags,
followed_tags,
};
export default combineReducers(reducers);

View file

@ -1588,6 +1588,15 @@ a.sparkline {
margin-bottom: 0;
}
}
a {
color: $highlight-text-color;
text-decoration: none;
&:hover {
text-decoration: underline;
}
}
}
&__actions {

View file

@ -118,7 +118,7 @@
&.active {
border-color: $highlight-text-color;
background: $highlight-text-color;
background: $highlight-text-color url("data:image/svg+xml;utf8,<svg width='18' height='18' fill='none' xmlns='http://www.w3.org/2000/svg'><path d='M4.5 8.5L8 12l6-6' stroke='white' stroke-width='1.5'/></svg>") center center no-repeat;
}
}
}
@ -586,7 +586,6 @@
}
.privacy-dropdown__dropdown {
position: absolute;
border-radius: 4px;
box-shadow: 2px 4px 15px rgba($base-shadow-color, 0.4);
background: $simple-background-color;
@ -653,7 +652,6 @@
.language-dropdown {
&__dropdown {
position: absolute;
background: $simple-background-color;
box-shadow: 2px 4px 15px rgba($base-shadow-color, 0.4);
border-radius: 4px;

View file

@ -13,7 +13,7 @@
.emoji-picker-dropdown__menu {
background: $simple-background-color;
position: absolute;
position: relative;
box-shadow: 4px 4px 6px rgba($base-shadow-color, 0.4);
border-radius: 4px;
margin-top: 5px;

View file

@ -346,9 +346,8 @@
}
}
.dropdown-menu {
position: absolute;
transform-origin: 50% 0;
body > [data-popper-placement] {
z-index: 3;
}
.invisible {
@ -532,6 +531,42 @@
}
}
.dropdown-animation {
animation: dropdown 300ms cubic-bezier(0.1, 0.7, 0.1, 1);
@keyframes dropdown {
from {
opacity: 0;
transform: scaleX(0.85) scaleY(0.75);
}
to {
opacity: 1;
transform: scaleX(1) scaleY(1);
}
}
&.top {
transform-origin: bottom;
}
&.right {
transform-origin: left;
}
&.bottom {
transform-origin: top;
}
&.left {
transform-origin: right;
}
.reduce-motion & {
animation: none;
}
}
.dropdown {
display: inline-block;
}
@ -600,36 +635,42 @@
.dropdown-menu__arrow {
position: absolute;
width: 0;
height: 0;
border: 0 solid transparent;
&.left {
right: -5px;
margin-top: -5px;
border-width: 5px 0 5px 5px;
border-left-color: $ui-secondary-color;
&::before {
content: '';
display: block;
width: 14px;
height: 5px;
background-color: $ui-secondary-color;
mask-image: url("data:image/svg+xml;utf8,<svg width='14' height='5' xmlns='http://www.w3.org/2000/svg'><path d='M7 0L0 5h14L7 0z' fill='white'/></svg>");
}
&.top {
bottom: -5px;
margin-left: -7px;
border-width: 5px 7px 0;
border-top-color: $ui-secondary-color;
&::before {
transform: rotate(180deg);
}
}
&.right {
left: -9px;
&::before {
transform: rotate(-90deg);
}
}
&.bottom {
top: -5px;
margin-left: -7px;
border-width: 0 7px 5px;
border-bottom-color: $ui-secondary-color;
}
&.right {
left: -5px;
margin-top: -5px;
border-width: 5px 5px 5px 0;
border-right-color: $ui-secondary-color;
&.left {
right: -9px;
&::before {
transform: rotate(90deg);
}
}
}
@ -1544,14 +1585,14 @@ button.icon-button.active i.fa-retweet {
align-items: center;
background: rgba($base-overlay-background, 0.8);
display: flex;
height: 100%;
height: 100vh;
justify-content: center;
left: 0;
opacity: 0;
position: absolute;
position: fixed;
top: 0;
visibility: hidden;
width: 100%;
width: 100vw;
z-index: 2000;
* {

View file

@ -37,7 +37,6 @@
.modal-root__modal {
pointer-events: auto;
display: flex;
z-index: 9999;
}
.media-modal__zoom-button {

View file

@ -157,6 +157,15 @@ code {
padding: 0.2em 0.4em;
background: darken($ui-base-color, 12%);
}
li {
list-style: disc;
margin-left: 18px;
}
}
ul.hint {
margin-bottom: 15px;
}
span.hint {

View file

@ -12,6 +12,14 @@ html {
&.button-alternative-2 {
color: $white;
}
&.button-tertiary {
color: $highlight-text-color;
}
}
.simple_form .button.button-tertiary {
color: $highlight-text-color;
}
.status-card__actions button,
@ -285,22 +293,8 @@ html {
.dropdown-menu {
background: $white;
&__arrow {
&.left {
border-left-color: $white;
}
&.top {
border-top-color: $white;
}
&.bottom {
border-bottom-color: $white;
}
&.right {
border-right-color: $white;
}
&__arrow::before {
background-color: $white;
}
&__item {
@ -557,25 +551,6 @@ html {
}
}
.directory__tag.active > a,
.directory__tag.active > div {
border-color: $ui-highlight-color;
&,
h4,
h4 small,
.fa,
.trends__item__current {
color: $white;
}
&:hover,
&:active,
&:focus {
background: $ui-highlight-color;
}
}
.batch-table {
&__toolbar,
&__row,

View file

@ -117,7 +117,7 @@
width: 18px;
height: 18px;
flex: 0 0 auto;
margin-right: 10px;
margin-inline-end: 10px;
top: -1px;
border-radius: 50%;
vertical-align: middle;
@ -212,7 +212,7 @@
.button {
height: 36px;
padding: 0 16px;
margin-right: 10px;
margin-inline-end: 10px;
font-size: 14px;
}
}
@ -250,7 +250,7 @@
line-height: inherit;
color: $action-button-color;
border-color: $action-button-color;
margin-right: 5px;
margin-inline-end: 5px;
}
li {
@ -260,7 +260,7 @@
.poll__option {
flex: 0 0 auto;
width: calc(100% - (23px + 6px));
margin-right: 6px;
margin-inline-end: 6px;
}
}

View file

@ -160,6 +160,18 @@ export function submitCompose(routerHistory) {
dispatch(submitComposeRequest());
// If we're editing a post with media attachments, those have not
// necessarily been changed on the server. Do it now in the same
// API call.
let media_attributes;
if (statusId !== null) {
media_attributes = media.map(item => ({
id: item.get('id'),
description: item.get('description'),
focus: item.get('focus'),
}));
}
api(getState).request({
url: statusId === null ? '/api/v1/statuses' : `/api/v1/statuses/${statusId}`,
method: statusId === null ? 'post' : 'put',
@ -167,6 +179,7 @@ export function submitCompose(routerHistory) {
status,
in_reply_to_id: getState().getIn(['compose', 'in_reply_to'], null),
media_ids: media.map(item => item.get('id')),
media_attributes,
sensitive: getState().getIn(['compose', 'sensitive']),
spoiler_text: getState().getIn(['compose', 'spoiler']) ? getState().getIn(['compose', 'spoiler_text'], '') : '',
visibility: getState().getIn(['compose', 'privacy']),
@ -377,11 +390,31 @@ export function changeUploadCompose(id, params) {
return (dispatch, getState) => {
dispatch(changeUploadComposeRequest());
api(getState).put(`/api/v1/media/${id}`, params).then(response => {
dispatch(changeUploadComposeSuccess(response.data));
}).catch(error => {
dispatch(changeUploadComposeFail(id, error));
});
let media = getState().getIn(['compose', 'media_attachments']).find((item) => item.get('id') === id);
// Editing already-attached media is deferred to editing the post itself.
// For simplicity's sake, fake an API reply.
if (media && !media.get('unattached')) {
let { description, focus } = params;
const data = media.toJS();
if (description) {
data.description = description;
}
if (focus) {
focus = focus.split(',');
data.meta = { focus: { x: parseFloat(focus[0]), y: parseFloat(focus[1]) } };
}
dispatch(changeUploadComposeSuccess(data, true));
} else {
api(getState).put(`/api/v1/media/${id}`, params).then(response => {
dispatch(changeUploadComposeSuccess(response.data, false));
}).catch(error => {
dispatch(changeUploadComposeFail(id, error));
});
}
};
}
@ -392,10 +425,11 @@ export function changeUploadComposeRequest() {
};
}
export function changeUploadComposeSuccess(media) {
export function changeUploadComposeSuccess(media, attached) {
return {
type: COMPOSE_UPLOAD_CHANGE_SUCCESS,
media: media,
attached: attached,
skipLoading: true,
};
}

View file

@ -1,8 +1,8 @@
export const DROPDOWN_MENU_OPEN = 'DROPDOWN_MENU_OPEN';
export const DROPDOWN_MENU_CLOSE = 'DROPDOWN_MENU_CLOSE';
export function openDropdownMenu(id, placement, keyboard, scroll_key) {
return { type: DROPDOWN_MENU_OPEN, id, placement, keyboard, scroll_key };
export function openDropdownMenu(id, keyboard, scroll_key) {
return { type: DROPDOWN_MENU_OPEN, id, keyboard, scroll_key };
}
export function closeDropdownMenu(id) {

View file

@ -1,9 +1,17 @@
import api from '../api';
import api, { getLinks } from '../api';
export const HASHTAG_FETCH_REQUEST = 'HASHTAG_FETCH_REQUEST';
export const HASHTAG_FETCH_SUCCESS = 'HASHTAG_FETCH_SUCCESS';
export const HASHTAG_FETCH_FAIL = 'HASHTAG_FETCH_FAIL';
export const FOLLOWED_HASHTAGS_FETCH_REQUEST = 'FOLLOWED_HASHTAGS_FETCH_REQUEST';
export const FOLLOWED_HASHTAGS_FETCH_SUCCESS = 'FOLLOWED_HASHTAGS_FETCH_SUCCESS';
export const FOLLOWED_HASHTAGS_FETCH_FAIL = 'FOLLOWED_HASHTAGS_FETCH_FAIL';
export const FOLLOWED_HASHTAGS_EXPAND_REQUEST = 'FOLLOWED_HASHTAGS_EXPAND_REQUEST';
export const FOLLOWED_HASHTAGS_EXPAND_SUCCESS = 'FOLLOWED_HASHTAGS_EXPAND_SUCCESS';
export const FOLLOWED_HASHTAGS_EXPAND_FAIL = 'FOLLOWED_HASHTAGS_EXPAND_FAIL';
export const HASHTAG_FOLLOW_REQUEST = 'HASHTAG_FOLLOW_REQUEST';
export const HASHTAG_FOLLOW_SUCCESS = 'HASHTAG_FOLLOW_SUCCESS';
export const HASHTAG_FOLLOW_FAIL = 'HASHTAG_FOLLOW_FAIL';
@ -37,6 +45,78 @@ export const fetchHashtagFail = error => ({
error,
});
export const fetchFollowedHashtags = () => (dispatch, getState) => {
dispatch(fetchFollowedHashtagsRequest());
api(getState).get('/api/v1/followed_tags').then(response => {
const next = getLinks(response).refs.find(link => link.rel === 'next');
dispatch(fetchFollowedHashtagsSuccess(response.data, next ? next.uri : null));
}).catch(err => {
dispatch(fetchFollowedHashtagsFail(err));
});
};
export function fetchFollowedHashtagsRequest() {
return {
type: FOLLOWED_HASHTAGS_FETCH_REQUEST,
};
};
export function fetchFollowedHashtagsSuccess(followed_tags, next) {
return {
type: FOLLOWED_HASHTAGS_FETCH_SUCCESS,
followed_tags,
next,
};
};
export function fetchFollowedHashtagsFail(error) {
return {
type: FOLLOWED_HASHTAGS_FETCH_FAIL,
error,
};
};
export function expandFollowedHashtags() {
return (dispatch, getState) => {
const url = getState().getIn(['followed_tags', 'next']);
if (url === null) {
return;
}
dispatch(expandFollowedHashtagsRequest());
api(getState).get(url).then(response => {
const next = getLinks(response).refs.find(link => link.rel === 'next');
dispatch(expandFollowedHashtagsSuccess(response.data, next ? next.uri : null));
}).catch(error => {
dispatch(expandFollowedHashtagsFail(error));
});
};
};
export function expandFollowedHashtagsRequest() {
return {
type: FOLLOWED_HASHTAGS_EXPAND_REQUEST,
};
};
export function expandFollowedHashtagsSuccess(followed_tags, next) {
return {
type: FOLLOWED_HASHTAGS_EXPAND_SUCCESS,
followed_tags,
next,
};
};
export function expandFollowedHashtagsFail(error) {
return {
type: FOLLOWED_HASHTAGS_EXPAND_FAIL,
error,
};
};
export const followHashtag = name => (dispatch, getState) => {
dispatch(followHashtagRequest(name));

View file

@ -50,7 +50,7 @@ export default class Trends extends React.PureComponent {
<Hashtag
key={hashtag.name}
name={hashtag.name}
to={`/admin/tags/${hashtag.id}`}
to={hashtag.id === undefined ? undefined : `/admin/tags/${hashtag.id}`}
people={hashtag.history[0].accounts * 1 + hashtag.history[1].accounts * 1}
uses={hashtag.history[0].uses * 1 + hashtag.history[1].uses * 1}
history={hashtag.history.reverse().map(day => day.uses)}

View file

@ -50,6 +50,7 @@ export default class AutosuggestInput extends ImmutablePureComponent {
id: PropTypes.string,
searchTokens: PropTypes.arrayOf(PropTypes.string),
maxLength: PropTypes.number,
lang: PropTypes.string,
};
static defaultProps = {
@ -185,7 +186,7 @@ export default class AutosuggestInput extends ImmutablePureComponent {
}
render () {
const { value, suggestions, disabled, placeholder, onKeyUp, autoFocus, className, id, maxLength } = this.props;
const { value, suggestions, disabled, placeholder, onKeyUp, autoFocus, className, id, maxLength, lang } = this.props;
const { suggestionsHidden } = this.state;
return (
@ -210,6 +211,7 @@ export default class AutosuggestInput extends ImmutablePureComponent {
id={id}
className={className}
maxLength={maxLength}
lang={lang}
/>
</label>

View file

@ -48,6 +48,7 @@ export default class AutosuggestTextarea extends ImmutablePureComponent {
onKeyDown: PropTypes.func,
onPaste: PropTypes.func.isRequired,
autoFocus: PropTypes.bool,
lang: PropTypes.string,
};
static defaultProps = {
@ -192,7 +193,7 @@ export default class AutosuggestTextarea extends ImmutablePureComponent {
}
render () {
const { value, suggestions, disabled, placeholder, onKeyUp, autoFocus, children } = this.props;
const { value, suggestions, disabled, placeholder, onKeyUp, autoFocus, lang, children } = this.props;
const { suggestionsHidden } = this.state;
return [
@ -216,6 +217,7 @@ export default class AutosuggestTextarea extends ImmutablePureComponent {
onPaste={this.onPaste}
dir='auto'
aria-autocomplete='list'
lang={lang}
/>
</label>
</div>

View file

@ -2,9 +2,7 @@ import React from 'react';
import PropTypes from 'prop-types';
import ImmutablePropTypes from 'react-immutable-proptypes';
import IconButton from './icon_button';
import Overlay from 'react-overlays/lib/Overlay';
import Motion from '../features/ui/util/optional_motion';
import spring from 'react-motion/lib/spring';
import Overlay from 'react-overlays/Overlay';
import { supportsPassiveEvents } from 'detect-passive-events';
import classNames from 'classnames';
import { CircularProgress } from 'mastodon/components/loading_indicator';
@ -24,9 +22,6 @@ class DropdownMenu extends React.PureComponent {
scrollable: PropTypes.bool,
onClose: PropTypes.func.isRequired,
style: PropTypes.object,
placement: PropTypes.string,
arrowOffsetLeft: PropTypes.string,
arrowOffsetTop: PropTypes.string,
openedViaKeyboard: PropTypes.bool,
renderItem: PropTypes.func,
renderHeader: PropTypes.func,
@ -35,11 +30,6 @@ class DropdownMenu extends React.PureComponent {
static defaultProps = {
style: {},
placement: 'bottom',
};
state = {
mounted: false,
};
handleDocumentClick = e => {
@ -56,8 +46,6 @@ class DropdownMenu extends React.PureComponent {
if (this.focusedItem && this.props.openedViaKeyboard) {
this.focusedItem.focus({ preventScroll: true });
}
this.setState({ mounted: true });
}
componentWillUnmount () {
@ -139,40 +127,28 @@ class DropdownMenu extends React.PureComponent {
}
render () {
const { items, style, placement, arrowOffsetLeft, arrowOffsetTop, scrollable, renderHeader, loading } = this.props;
const { mounted } = this.state;
const { items, scrollable, renderHeader, loading } = this.props;
let renderItem = this.props.renderItem || this.renderItem;
return (
<Motion defaultStyle={{ opacity: 0, scaleX: 0.85, scaleY: 0.75 }} style={{ opacity: spring(1, { damping: 35, stiffness: 400 }), scaleX: spring(1, { damping: 35, stiffness: 400 }), scaleY: spring(1, { damping: 35, stiffness: 400 }) }}>
{({ opacity, scaleX, scaleY }) => (
// It should not be transformed when mounting because the resulting
// size will be used to determine the coordinate of the menu by
// react-overlays
<div className={`dropdown-menu ${placement}`} style={{ ...style, opacity: opacity, transform: mounted ? `scale(${scaleX}, ${scaleY})` : null }} ref={this.setRef}>
<div className={`dropdown-menu__arrow ${placement}`} style={{ left: arrowOffsetLeft, top: arrowOffsetTop }} />
<div className={classNames('dropdown-menu__container', { 'dropdown-menu__container--loading': loading })} ref={this.setRef}>
{loading && (
<CircularProgress size={30} strokeWidth={3.5} />
)}
<div className={classNames('dropdown-menu__container', { 'dropdown-menu__container--loading': loading })}>
{loading && (
<CircularProgress size={30} strokeWidth={3.5} />
)}
{!loading && renderHeader && (
<div className='dropdown-menu__container__header'>
{renderHeader(items)}
</div>
)}
{!loading && (
<ul className={classNames('dropdown-menu__container__list', { 'dropdown-menu__container__list--scrollable': scrollable })}>
{items.map((option, i) => renderItem(option, i, { onClick: this.handleClick, onKeyPress: this.handleItemKeyPress }))}
</ul>
)}
</div>
{!loading && renderHeader && (
<div className='dropdown-menu__container__header'>
{renderHeader(items)}
</div>
)}
</Motion>
{!loading && (
<ul className={classNames('dropdown-menu__container__list', { 'dropdown-menu__container__list--scrollable': scrollable })}>
{items.map((option, i) => renderItem(option, i, { onClick: this.handleClick, onKeyPress: this.handleItemKeyPress }))}
</ul>
)}
</div>
);
}
@ -197,7 +173,6 @@ export default class Dropdown extends React.PureComponent {
isUserTouching: PropTypes.func,
onOpen: PropTypes.func.isRequired,
onClose: PropTypes.func.isRequired,
dropdownPlacement: PropTypes.string,
openDropdownId: PropTypes.number,
openedViaKeyboard: PropTypes.bool,
renderItem: PropTypes.func,
@ -213,13 +188,11 @@ export default class Dropdown extends React.PureComponent {
id: id++,
};
handleClick = ({ target, type }) => {
handleClick = ({ type }) => {
if (this.state.id === this.props.openDropdownId) {
this.handleClose();
} else {
const { top } = target.getBoundingClientRect();
const placement = top * 2 < innerHeight ? 'bottom' : 'top';
this.props.onOpen(this.state.id, this.handleItemClick, placement, type !== 'click');
this.props.onOpen(this.state.id, this.handleItemClick, type !== 'click');
}
}
@ -303,7 +276,6 @@ export default class Dropdown extends React.PureComponent {
disabled,
loading,
scrollable,
dropdownPlacement,
openDropdownId,
openedViaKeyboard,
children,
@ -314,7 +286,6 @@ export default class Dropdown extends React.PureComponent {
const open = this.state.id === openDropdownId;
const button = children ? React.cloneElement(React.Children.only(children), {
ref: this.setTargetRef,
onClick: this.handleClick,
onMouseDown: this.handleMouseDown,
onKeyDown: this.handleButtonKeyDown,
@ -326,7 +297,6 @@ export default class Dropdown extends React.PureComponent {
active={open}
disabled={disabled}
size={size}
ref={this.setTargetRef}
onClick={this.handleClick}
onMouseDown={this.handleMouseDown}
onKeyDown={this.handleButtonKeyDown}
@ -336,19 +306,27 @@ export default class Dropdown extends React.PureComponent {
return (
<React.Fragment>
{button}
<Overlay show={open} placement={dropdownPlacement} target={this.findTarget}>
<DropdownMenu
items={items}
loading={loading}
scrollable={scrollable}
onClose={this.handleClose}
openedViaKeyboard={openedViaKeyboard}
renderItem={renderItem}
renderHeader={renderHeader}
onItemClick={this.handleItemClick}
/>
<span ref={this.setTargetRef}>
{button}
</span>
<Overlay show={open} offset={[5, 5]} placement={'bottom'} flip target={this.findTarget} popperConfig={{ strategy: 'fixed' }}>
{({ props, arrowProps, placement }) => (
<div {...props}>
<div className={`dropdown-animation dropdown-menu ${placement}`}>
<div className={`dropdown-menu__arrow ${placement}`} {...arrowProps} />
<DropdownMenu
items={items}
loading={loading}
scrollable={scrollable}
onClose={this.handleClose}
openedViaKeyboard={openedViaKeyboard}
renderItem={renderItem}
renderHeader={renderHeader}
onItemClick={this.handleItemClick}
/>
</div>
</div>
)}
</Overlay>
</React.Fragment>
);

View file

@ -4,7 +4,6 @@ import { fetchHistory } from 'mastodon/actions/history';
import DropdownMenu from 'mastodon/components/dropdown_menu';
const mapStateToProps = (state, { statusId }) => ({
dropdownPlacement: state.getIn(['dropdown_menu', 'placement']),
openDropdownId: state.getIn(['dropdown_menu', 'openId']),
openedViaKeyboard: state.getIn(['dropdown_menu', 'keyboard']),
items: state.getIn(['history', statusId, 'items']),
@ -13,9 +12,9 @@ const mapStateToProps = (state, { statusId }) => ({
const mapDispatchToProps = (dispatch, { statusId }) => ({
onOpen (id, onItemClick, dropdownPlacement, keyboard) {
onOpen (id, onItemClick, keyboard) {
dispatch(fetchHistory(statusId));
dispatch(openDropdownMenu(id, dropdownPlacement, keyboard));
dispatch(openDropdownMenu(id, keyboard));
},
onClose (id) {

View file

@ -6,13 +6,12 @@ import DropdownMenu from '../components/dropdown_menu';
import { isUserTouching } from '../is_mobile';
const mapStateToProps = state => ({
dropdownPlacement: state.getIn(['dropdown_menu', 'placement']),
openDropdownId: state.getIn(['dropdown_menu', 'openId']),
openedViaKeyboard: state.getIn(['dropdown_menu', 'keyboard']),
});
const mapDispatchToProps = (dispatch, { status, items, scrollKey }) => ({
onOpen(id, onItemClick, dropdownPlacement, keyboard) {
onOpen(id, onItemClick, keyboard) {
if (status) {
dispatch(fetchRelationships([status.getIn(['account', 'id'])]));
}
@ -21,7 +20,7 @@ const mapDispatchToProps = (dispatch, { status, items, scrollKey }) => ({
status,
actions: items,
onClick: onItemClick,
}) : openDropdownMenu(id, dropdownPlacement, keyboard, scrollKey));
}) : openDropdownMenu(id, keyboard, scrollKey));
},
onClose(id) {

View file

@ -46,6 +46,7 @@ const messages = defineMessages({
follow_requests: { id: 'navigation_bar.follow_requests', defaultMessage: 'Follow requests' },
favourites: { id: 'navigation_bar.favourites', defaultMessage: 'Favourites' },
lists: { id: 'navigation_bar.lists', defaultMessage: 'Lists' },
followed_tags: { id: 'navigation_bar.followed_tags', defaultMessage: 'Followed hashtags' },
blocks: { id: 'navigation_bar.blocks', defaultMessage: 'Blocked users' },
domain_blocks: { id: 'navigation_bar.domain_blocks', defaultMessage: 'Blocked domains' },
mutes: { id: 'navigation_bar.mutes', defaultMessage: 'Muted users' },
@ -193,7 +194,7 @@ class Header extends ImmutablePureComponent {
}
if (account.getIn(['relationship', 'requested']) || account.getIn(['relationship', 'following'])) {
bellBtn = <IconButton icon='bell-o' size={24} active={account.getIn(['relationship', 'notifying'])} title={intl.formatMessage(account.getIn(['relationship', 'notifying']) ? messages.disableNotifications : messages.enableNotifications, { name: account.get('username') })} onClick={this.props.onNotifyToggle} />;
bellBtn = <IconButton icon={account.getIn(['relationship', 'notifying']) ? 'bell' : 'bell-o'} size={24} active={account.getIn(['relationship', 'notifying'])} title={intl.formatMessage(account.getIn(['relationship', 'notifying']) ? messages.disableNotifications : messages.enableNotifications, { name: account.get('username') })} onClick={this.props.onNotifyToggle} />;
}
if (me !== account.get('id')) {
@ -242,6 +243,7 @@ class Header extends ImmutablePureComponent {
menu.push({ text: intl.formatMessage(messages.follow_requests), to: '/follow_requests' });
menu.push({ text: intl.formatMessage(messages.favourites), to: '/favourites' });
menu.push({ text: intl.formatMessage(messages.lists), to: '/lists' });
menu.push({ text: intl.formatMessage(messages.followed_tags), to: '/followed_tags' });
menu.push(null);
menu.push({ text: intl.formatMessage(messages.mutes), to: '/mutes' });
menu.push({ text: intl.formatMessage(messages.blocks), to: '/blocks' });

View file

@ -59,7 +59,7 @@ class Audio extends React.PureComponent {
duration: null,
paused: true,
muted: false,
volume: 0.5,
volume: 1,
dragging: false,
revealed: this.props.visible !== undefined ? this.props.visible : (displayMedia !== 'hide_all' && !this.props.sensitive || displayMedia === 'show_all'),
};
@ -80,8 +80,8 @@ class Audio extends React.PureComponent {
_pack() {
return {
src: this.props.src,
volume: this.audio.volume,
muted: this.audio.muted,
volume: this.state.volume,
muted: this.state.muted,
currentTime: this.audio.currentTime,
poster: this.props.poster,
backgroundColor: this.props.backgroundColor,
@ -115,7 +115,8 @@ class Audio extends React.PureComponent {
this.audio = c;
if (this.audio) {
this.setState({ volume: this.audio.volume, muted: this.audio.muted });
this.audio.volume = 1;
this.audio.muted = false;
}
}
@ -202,7 +203,9 @@ class Audio extends React.PureComponent {
const muted = !this.state.muted;
this.setState({ muted }, () => {
this.audio.muted = muted;
if (this.gainNode) {
this.gainNode.gain.value = muted ? 0 : this.state.volume;
}
});
}
@ -280,7 +283,9 @@ class Audio extends React.PureComponent {
if(!isNaN(x)) {
this.setState({ volume: x }, () => {
this.audio.volume = x;
if (this.gainNode) {
this.gainNode.gain.value = this.state.muted ? 0 : x;
}
});
}
}, 15);
@ -313,20 +318,12 @@ class Audio extends React.PureComponent {
}
handleLoadedData = () => {
const { autoPlay, currentTime, volume, muted } = this.props;
const { autoPlay, currentTime } = this.props;
if (currentTime) {
this.audio.currentTime = currentTime;
}
if (volume !== undefined) {
this.audio.volume = volume;
}
if (muted !== undefined) {
this.audio.muted = muted;
}
if (autoPlay) {
this.togglePlay();
}
@ -336,11 +333,16 @@ class Audio extends React.PureComponent {
const AudioContext = window.AudioContext || window.webkitAudioContext;
const context = new AudioContext();
const source = context.createMediaElementSource(this.audio);
const gainNode = context.createGain();
gainNode.gain.value = this.state.muted ? 0 : this.state.volume;
this.visualizer.setAudioContext(context, source);
source.connect(context.destination);
source.connect(gainNode);
gainNode.connect(context.destination);
this.audioContext = context;
this.gainNode = gainNode;
}
handleDownload = () => {

View file

@ -11,6 +11,7 @@ const messages = defineMessages({
follow_requests: { id: 'navigation_bar.follow_requests', defaultMessage: 'Follow requests' },
favourites: { id: 'navigation_bar.favourites', defaultMessage: 'Favourites' },
lists: { id: 'navigation_bar.lists', defaultMessage: 'Lists' },
followed_tags: { id: 'navigation_bar.followed_tags', defaultMessage: 'Followed hashtags' },
blocks: { id: 'navigation_bar.blocks', defaultMessage: 'Blocked users' },
domain_blocks: { id: 'navigation_bar.domain_blocks', defaultMessage: 'Hidden domains' },
mutes: { id: 'navigation_bar.mutes', defaultMessage: 'Muted users' },
@ -45,6 +46,7 @@ class ActionBar extends React.PureComponent {
menu.push({ text: intl.formatMessage(messages.favourites), to: '/favourites' });
menu.push({ text: intl.formatMessage(messages.bookmarks), to: '/bookmarks' });
menu.push({ text: intl.formatMessage(messages.lists), to: '/lists' });
menu.push({ text: intl.formatMessage(messages.followed_tags), to: '/followed_tags' });
menu.push(null);
menu.push({ text: intl.formatMessage(messages.mutes), to: '/mutes' });
menu.push({ text: intl.formatMessage(messages.blocks), to: '/blocks' });

View file

@ -65,6 +65,7 @@ class ComposeForm extends ImmutablePureComponent {
anyMedia: PropTypes.bool,
isInReply: PropTypes.bool,
singleColumn: PropTypes.bool,
lang: PropTypes.string,
};
static defaultProps = {
@ -241,6 +242,7 @@ class ComposeForm extends ImmutablePureComponent {
searchTokens={[':']}
id='cw-spoiler-input'
className='spoiler-input__input'
lang={this.props.lang}
/>
</div>
@ -258,6 +260,7 @@ class ComposeForm extends ImmutablePureComponent {
onSuggestionSelected={this.onSuggestionSelected}
onPaste={onPaste}
autoFocus={autoFocus}
lang={this.props.lang}
>
<EmojiPickerDropdown onPickEmoji={this.handleEmojiPick} />

View file

@ -2,7 +2,7 @@ import React from 'react';
import PropTypes from 'prop-types';
import { defineMessages, injectIntl, FormattedMessage } from 'react-intl';
import { EmojiPicker as EmojiPickerAsync } from '../../ui/util/async-components';
import Overlay from 'react-overlays/lib/Overlay';
import Overlay from 'react-overlays/Overlay';
import classNames from 'classnames';
import ImmutablePropTypes from 'react-immutable-proptypes';
import { supportsPassiveEvents } from 'detect-passive-events';
@ -154,9 +154,6 @@ class EmojiPickerMenu extends React.PureComponent {
onClose: PropTypes.func.isRequired,
onPick: PropTypes.func.isRequired,
style: PropTypes.object,
placement: PropTypes.string,
arrowOffsetLeft: PropTypes.string,
arrowOffsetTop: PropTypes.string,
intl: PropTypes.object.isRequired,
skinTone: PropTypes.number.isRequired,
onSkinTone: PropTypes.func.isRequired,
@ -324,14 +321,13 @@ class EmojiPickerDropdown extends React.PureComponent {
state = {
active: false,
loading: false,
placement: null,
};
setRef = (c) => {
this.dropdown = c;
}
onShowDropdown = ({ target }) => {
onShowDropdown = () => {
this.setState({ active: true });
if (!EmojiPicker) {
@ -346,9 +342,6 @@ class EmojiPickerDropdown extends React.PureComponent {
this.setState({ loading: false, active: false });
});
}
const { top } = target.getBoundingClientRect();
this.setState({ placement: top * 2 < innerHeight ? 'bottom' : 'top' });
}
onHideDropdown = () => {
@ -382,7 +375,7 @@ class EmojiPickerDropdown extends React.PureComponent {
render () {
const { intl, onPickEmoji, onSkinTone, skinTone, frequentlyUsedEmojis, button } = this.props;
const title = intl.formatMessage(messages.emoji);
const { active, loading, placement } = this.state;
const { active, loading } = this.state;
return (
<div className='emoji-picker-dropdown' onKeyDown={this.handleKeyDown}>
@ -394,16 +387,22 @@ class EmojiPickerDropdown extends React.PureComponent {
/>}
</div>
<Overlay show={active} placement={placement} target={this.findTarget}>
<EmojiPickerMenu
custom_emojis={this.props.custom_emojis}
loading={loading}
onClose={this.onHideDropdown}
onPick={onPickEmoji}
onSkinTone={onSkinTone}
skinTone={skinTone}
frequentlyUsedEmojis={frequentlyUsedEmojis}
/>
<Overlay show={active} placement={'bottom'} target={this.findTarget} popperConfig={{ strategy: 'fixed' }}>
{({ props, placement })=> (
<div {...props} style={{ ...props.style, width: 299 }}>
<div className={`dropdown-animation ${placement}`}>
<EmojiPickerMenu
custom_emojis={this.props.custom_emojis}
loading={loading}
onClose={this.onHideDropdown}
onPick={onPickEmoji}
onSkinTone={onSkinTone}
skinTone={skinTone}
frequentlyUsedEmojis={frequentlyUsedEmojis}
/>
</div>
</div>
)}
</Overlay>
</div>
);

View file

@ -2,9 +2,7 @@ import React from 'react';
import PropTypes from 'prop-types';
import { injectIntl, defineMessages } from 'react-intl';
import TextIconButton from './text_icon_button';
import Overlay from 'react-overlays/lib/Overlay';
import Motion from 'mastodon/features/ui/util/optional_motion';
import spring from 'react-motion/lib/spring';
import Overlay from 'react-overlays/Overlay';
import { supportsPassiveEvents } from 'detect-passive-events';
import classNames from 'classnames';
import { languages as preloadedLanguages } from 'mastodon/initial_state';
@ -22,10 +20,8 @@ const listenerOptions = supportsPassiveEvents ? { passive: true } : false;
class LanguageDropdownMenu extends React.PureComponent {
static propTypes = {
style: PropTypes.object,
value: PropTypes.string.isRequired,
frequentlyUsedLanguages: PropTypes.arrayOf(PropTypes.string).isRequired,
placement: PropTypes.string.isRequired,
onClose: PropTypes.func.isRequired,
onChange: PropTypes.func.isRequired,
languages: PropTypes.arrayOf(PropTypes.arrayOf(PropTypes.string)),
@ -37,7 +33,6 @@ class LanguageDropdownMenu extends React.PureComponent {
};
state = {
mounted: false,
searchValue: '',
};
@ -50,7 +45,6 @@ class LanguageDropdownMenu extends React.PureComponent {
componentDidMount () {
document.addEventListener('click', this.handleDocumentClick, false);
document.addEventListener('touchend', this.handleDocumentClick, listenerOptions);
this.setState({ mounted: true });
// Because of https://github.com/react-bootstrap/react-bootstrap/issues/2614 we need
// to wait for a frame before focusing
@ -222,29 +216,22 @@ class LanguageDropdownMenu extends React.PureComponent {
}
render () {
const { style, placement, intl } = this.props;
const { mounted, searchValue } = this.state;
const { intl } = this.props;
const { searchValue } = this.state;
const isSearching = searchValue !== '';
const results = this.search();
return (
<Motion defaultStyle={{ opacity: 0, scaleX: 0.85, scaleY: 0.75 }} style={{ opacity: spring(1, { damping: 35, stiffness: 400 }), scaleX: spring(1, { damping: 35, stiffness: 400 }), scaleY: spring(1, { damping: 35, stiffness: 400 }) }}>
{({ opacity, scaleX, scaleY }) => (
// It should not be transformed when mounting because the resulting
// size will be used to determine the coordinate of the menu by
// react-overlays
<div className={`language-dropdown__dropdown ${placement}`} style={{ ...style, opacity: opacity, transform: mounted ? `scale(${scaleX}, ${scaleY})` : null }} ref={this.setRef}>
<div className='emoji-mart-search'>
<input type='search' value={searchValue} onChange={this.handleSearchChange} onKeyDown={this.handleSearchKeyDown} placeholder={intl.formatMessage(messages.search)} />
<button type='button' className='emoji-mart-search-icon' disabled={!isSearching} aria-label={intl.formatMessage(messages.clear)} onClick={this.handleClear}>{!isSearching ? loupeIcon : deleteIcon}</button>
</div>
<div ref={this.setRef}>
<div className='emoji-mart-search'>
<input type='search' value={searchValue} onChange={this.handleSearchChange} onKeyDown={this.handleSearchKeyDown} placeholder={intl.formatMessage(messages.search)} />
<button type='button' className='emoji-mart-search-icon' disabled={!isSearching} aria-label={intl.formatMessage(messages.clear)} onClick={this.handleClear}>{!isSearching ? loupeIcon : deleteIcon}</button>
</div>
<div className='language-dropdown__dropdown__results emoji-mart-scroll' role='listbox' ref={this.setListRef}>
{results.map(this.renderItem)}
</div>
</div>
)}
</Motion>
<div className='language-dropdown__dropdown__results emoji-mart-scroll' role='listbox' ref={this.setListRef}>
{results.map(this.renderItem)}
</div>
</div>
);
}
@ -266,14 +253,11 @@ class LanguageDropdown extends React.PureComponent {
placement: 'bottom',
};
handleToggle = ({ target }) => {
const { top } = target.getBoundingClientRect();
handleToggle = () => {
if (this.state.open && this.activeElement) {
this.activeElement.focus({ preventScroll: true });
}
this.setState({ placement: top * 2 < innerHeight ? 'bottom' : 'top' });
this.setState({ open: !this.state.open });
}
@ -293,13 +277,25 @@ class LanguageDropdown extends React.PureComponent {
onChange(value);
}
setTargetRef = c => {
this.target = c;
}
findTarget = () => {
return this.target;
}
handleOverlayEnter = (state) => {
this.setState({ placement: state.placement });
}
render () {
const { value, intl, frequentlyUsedLanguages } = this.props;
const { open, placement } = this.state;
return (
<div className={classNames('privacy-dropdown', { active: open })}>
<div className='privacy-dropdown__value'>
<div className={classNames('privacy-dropdown', placement, { active: open })}>
<div className='privacy-dropdown__value' ref={this.setTargetRef} >
<TextIconButton
className='privacy-dropdown__value-icon'
label={value && value.toUpperCase()}
@ -309,15 +305,20 @@ class LanguageDropdown extends React.PureComponent {
/>
</div>
<Overlay show={open} placement={placement} target={this}>
<LanguageDropdownMenu
value={value}
frequentlyUsedLanguages={frequentlyUsedLanguages}
onClose={this.handleClose}
onChange={this.handleChange}
placement={placement}
intl={intl}
/>
<Overlay show={open} placement={'bottom'} flip target={this.findTarget} popperConfig={{ strategy: 'fixed', onFirstUpdate: this.handleOverlayEnter }}>
{({ props, placement }) => (
<div {...props}>
<div className={`dropdown-animation language-dropdown__dropdown ${placement}`} >
<LanguageDropdownMenu
value={value}
frequentlyUsedLanguages={frequentlyUsedLanguages}
onClose={this.handleClose}
onChange={this.handleChange}
intl={intl}
/>
</div>
</div>
)}
</Overlay>
</div>
);

View file

@ -2,9 +2,7 @@ import React from 'react';
import PropTypes from 'prop-types';
import { injectIntl, defineMessages } from 'react-intl';
import IconButton from '../../../components/icon_button';
import Overlay from 'react-overlays/lib/Overlay';
import Motion from '../../ui/util/optional_motion';
import spring from 'react-motion/lib/spring';
import Overlay from 'react-overlays/Overlay';
import { supportsPassiveEvents } from 'detect-passive-events';
import classNames from 'classnames';
import Icon from 'mastodon/components/icon';
@ -29,15 +27,10 @@ class PrivacyDropdownMenu extends React.PureComponent {
style: PropTypes.object,
items: PropTypes.array.isRequired,
value: PropTypes.string.isRequired,
placement: PropTypes.string.isRequired,
onClose: PropTypes.func.isRequired,
onChange: PropTypes.func.isRequired,
};
state = {
mounted: false,
};
handleDocumentClick = e => {
if (this.node && !this.node.contains(e.target)) {
this.props.onClose();
@ -101,7 +94,6 @@ class PrivacyDropdownMenu extends React.PureComponent {
document.addEventListener('click', this.handleDocumentClick, false);
document.addEventListener('touchend', this.handleDocumentClick, listenerOptions);
if (this.focusedItem) this.focusedItem.focus({ preventScroll: true });
this.setState({ mounted: true });
}
componentWillUnmount () {
@ -118,31 +110,23 @@ class PrivacyDropdownMenu extends React.PureComponent {
}
render () {
const { mounted } = this.state;
const { style, items, placement, value } = this.props;
const { style, items, value } = this.props;
return (
<Motion defaultStyle={{ opacity: 0, scaleX: 0.85, scaleY: 0.75 }} style={{ opacity: spring(1, { damping: 35, stiffness: 400 }), scaleX: spring(1, { damping: 35, stiffness: 400 }), scaleY: spring(1, { damping: 35, stiffness: 400 }) }}>
{({ opacity, scaleX, scaleY }) => (
// It should not be transformed when mounting because the resulting
// size will be used to determine the coordinate of the menu by
// react-overlays
<div className={`privacy-dropdown__dropdown ${placement}`} style={{ ...style, opacity: opacity, transform: mounted ? `scale(${scaleX}, ${scaleY})` : null }} role='listbox' ref={this.setRef}>
{items.map(item => (
<div role='option' tabIndex='0' key={item.value} data-index={item.value} onKeyDown={this.handleKeyDown} onClick={this.handleClick} className={classNames('privacy-dropdown__option', { active: item.value === value })} aria-selected={item.value === value} ref={item.value === value ? this.setFocusRef : null}>
<div className='privacy-dropdown__option__icon'>
<Icon id={item.icon} fixedWidth />
</div>
<div style={{ ...style }} role='listbox' ref={this.setRef}>
{items.map(item => (
<div role='option' tabIndex='0' key={item.value} data-index={item.value} onKeyDown={this.handleKeyDown} onClick={this.handleClick} className={classNames('privacy-dropdown__option', { active: item.value === value })} aria-selected={item.value === value} ref={item.value === value ? this.setFocusRef : null}>
<div className='privacy-dropdown__option__icon'>
<Icon id={item.icon} fixedWidth />
</div>
<div className='privacy-dropdown__option__content'>
<strong>{item.text}</strong>
{item.meta}
</div>
</div>
))}
<div className='privacy-dropdown__option__content'>
<strong>{item.text}</strong>
{item.meta}
</div>
</div>
)}
</Motion>
))}
</div>
);
}
@ -168,7 +152,7 @@ class PrivacyDropdown extends React.PureComponent {
placement: 'bottom',
};
handleToggle = ({ target }) => {
handleToggle = () => {
if (this.props.isUserTouching && this.props.isUserTouching()) {
if (this.state.open) {
this.props.onModalClose();
@ -179,11 +163,9 @@ class PrivacyDropdown extends React.PureComponent {
});
}
} else {
const { top } = target.getBoundingClientRect();
if (this.state.open && this.activeElement) {
this.activeElement.focus({ preventScroll: true });
}
this.setState({ placement: top * 2 < innerHeight ? 'bottom' : 'top' });
this.setState({ open: !this.state.open });
}
}
@ -247,6 +229,18 @@ class PrivacyDropdown extends React.PureComponent {
}
}
setTargetRef = c => {
this.target = c;
}
findTarget = () => {
return this.target;
}
handleOverlayEnter = (state) => {
this.setState({ placement: state.placement });
}
render () {
const { value, container, disabled, intl } = this.props;
const { open, placement } = this.state;
@ -255,7 +249,7 @@ class PrivacyDropdown extends React.PureComponent {
return (
<div className={classNames('privacy-dropdown', placement, { active: open })} onKeyDown={this.handleKeyDown}>
<div className={classNames('privacy-dropdown__value', { active: this.options.indexOf(valueOption) === (placement === 'bottom' ? 0 : (this.options.length - 1)) })}>
<div className={classNames('privacy-dropdown__value', { active: this.options.indexOf(valueOption) === (placement === 'bottom' ? 0 : (this.options.length - 1)) })} ref={this.setTargetRef}>
<IconButton
className='privacy-dropdown__value-icon'
icon={valueOption.icon}
@ -272,14 +266,19 @@ class PrivacyDropdown extends React.PureComponent {
/>
</div>
<Overlay show={open} placement={placement} target={this} container={container}>
<PrivacyDropdownMenu
items={this.options}
value={value}
onClose={this.handleClose}
onChange={this.handleChange}
placement={placement}
/>
<Overlay show={open} placement={'bottom'} flip target={this.findTarget} container={container} popperConfig={{ strategy: 'fixed', onFirstUpdate: this.handleOverlayEnter }}>
{({ props, placement }) => (
<div {...props}>
<div className={`dropdown-animation privacy-dropdown__dropdown ${placement}`}>
<PrivacyDropdownMenu
items={this.options}
value={value}
onClose={this.handleClose}
onChange={this.handleChange}
/>
</div>
</div>
)}
</Overlay>
</div>
);

View file

@ -1,9 +1,7 @@
import React from 'react';
import PropTypes from 'prop-types';
import { defineMessages, injectIntl, FormattedMessage } from 'react-intl';
import Overlay from 'react-overlays/lib/Overlay';
import Motion from '../../ui/util/optional_motion';
import spring from 'react-motion/lib/spring';
import Overlay from 'react-overlays/Overlay';
import { searchEnabled } from '../../../initial_state';
import Icon from 'mastodon/components/icon';
@ -14,31 +12,20 @@ const messages = defineMessages({
class SearchPopout extends React.PureComponent {
static propTypes = {
style: PropTypes.object,
};
render () {
const { style } = this.props;
const extraInformation = searchEnabled ? <FormattedMessage id='search_popout.tips.full_text' defaultMessage='Simple text returns statuses you have written, favourited, boosted, or have been mentioned in, as well as matching usernames, display names, and hashtags.' /> : <FormattedMessage id='search_popout.tips.text' defaultMessage='Simple text returns matching display names, usernames and hashtags' />;
return (
<div style={{ ...style, position: 'absolute', width: 285, zIndex: 2 }}>
<Motion defaultStyle={{ opacity: 0, scaleX: 0.85, scaleY: 0.75 }} style={{ opacity: spring(1, { damping: 35, stiffness: 400 }), scaleX: spring(1, { damping: 35, stiffness: 400 }), scaleY: spring(1, { damping: 35, stiffness: 400 }) }}>
{({ opacity, scaleX, scaleY }) => (
<div className='search-popout' style={{ opacity: opacity, transform: `scale(${scaleX}, ${scaleY})` }}>
<h4><FormattedMessage id='search_popout.search_format' defaultMessage='Advanced search format' /></h4>
<div className='search-popout'>
<h4><FormattedMessage id='search_popout.search_format' defaultMessage='Advanced search format' /></h4>
<ul>
<li><em>#example</em> <FormattedMessage id='search_popout.tips.hashtag' defaultMessage='hashtag' /></li>
<li><em>@username@domain</em> <FormattedMessage id='search_popout.tips.user' defaultMessage='user' /></li>
<li><em>URL</em> <FormattedMessage id='search_popout.tips.user' defaultMessage='user' /></li>
<li><em>URL</em> <FormattedMessage id='search_popout.tips.status' defaultMessage='status' /></li>
</ul>
<ul>
<li><em>#example</em> <FormattedMessage id='search_popout.tips.hashtag' defaultMessage='hashtag' /></li>
<li><em>@username@domain</em> <FormattedMessage id='search_popout.tips.user' defaultMessage='user' /></li>
<li><em>URL</em> <FormattedMessage id='search_popout.tips.user' defaultMessage='user' /></li>
<li><em>URL</em> <FormattedMessage id='search_popout.tips.status' defaultMessage='status' /></li>
</ul>
{extraInformation}
</div>
)}
</Motion>
{extraInformation}
</div>
);
}
@ -115,6 +102,10 @@ class Search extends React.PureComponent {
this.setState({ expanded: false });
}
findTarget = () => {
return this.searchForm;
}
render () {
const { intl, value, submitted } = this.props;
const { expanded } = this.state;
@ -140,8 +131,14 @@ class Search extends React.PureComponent {
<Icon id='search' className={hasValue ? '' : 'active'} />
<Icon id='times-circle' className={hasValue ? 'active' : ''} aria-label={intl.formatMessage(messages.placeholder)} />
</div>
<Overlay show={expanded && !hasValue} placement='bottom' target={this} container={this}>
<SearchPopout />
<Overlay show={expanded && !hasValue} placement='bottom' target={this.findTarget} popperConfig={{ strategy: 'fixed' }}>
{({ props, placement }) => (
<div {...props} style={{ ...props.style, width: 285, zIndex: 2 }}>
<div className={`dropdown-animation ${placement}`}>
<SearchPopout />
</div>
</div>
)}
</Overlay>
</div>
);

View file

@ -43,10 +43,10 @@ export default class Upload extends ImmutablePureComponent {
<div className='compose-form__upload-thumbnail' style={{ transform: `scale(${scale})`, backgroundImage: `url(${media.get('preview_url')})`, backgroundPosition: `${x}% ${y}%` }}>
<div className='compose-form__upload__actions'>
<button type='button' className='icon-button' onClick={this.handleUndoClick}><Icon id='times' /> <FormattedMessage id='upload_form.undo' defaultMessage='Delete' /></button>
{!!media.get('unattached') && (<button type='button' className='icon-button' onClick={this.handleFocalPointClick}><Icon id='pencil' /> <FormattedMessage id='upload_form.edit' defaultMessage='Edit' /></button>)}
<button type='button' className='icon-button' onClick={this.handleFocalPointClick}><Icon id='pencil' /> <FormattedMessage id='upload_form.edit' defaultMessage='Edit' /></button>
</div>
{(media.get('description') || '').length === 0 && !!media.get('unattached') && (
{(media.get('description') || '').length === 0 && (
<div className='compose-form__upload__warning'>
<button type='button' className='icon-button' onClick={this.handleFocalPointClick}><Icon id='info-circle' /> <FormattedMessage id='upload_form.description_missing' defaultMessage='No description added' /></button>
</div>

View file

@ -26,6 +26,7 @@ const mapStateToProps = state => ({
isUploading: state.getIn(['compose', 'is_uploading']),
anyMedia: state.getIn(['compose', 'media_attachments']).size > 0,
isInReply: state.getIn(['compose', 'in_reply_to']) !== null,
lang: state.getIn(['compose', 'language']),
});
const mapDispatchToProps = (dispatch) => ({

View file

@ -0,0 +1,89 @@
import { debounce } from 'lodash';
import PropTypes from 'prop-types';
import React from 'react';
import ImmutablePureComponent from 'react-immutable-pure-component';
import ImmutablePropTypes from 'react-immutable-proptypes';
import { defineMessages, injectIntl, FormattedMessage } from 'react-intl';
import { connect } from 'react-redux';
import ColumnHeader from 'mastodon/components/column_header';
import ScrollableList from 'mastodon/components/scrollable_list';
import Column from 'mastodon/features/ui/components/column';
import { Helmet } from 'react-helmet';
import Hashtag from 'mastodon/components/hashtag';
import { expandFollowedHashtags, fetchFollowedHashtags } from 'mastodon/actions/tags';
const messages = defineMessages({
heading: { id: 'followed_tags', defaultMessage: 'Followed hashtags' },
});
const mapStateToProps = state => ({
hashtags: state.getIn(['followed_tags', 'items']),
isLoading: state.getIn(['followed_tags', 'isLoading'], true),
hasMore: !!state.getIn(['followed_tags', 'next']),
});
export default @connect(mapStateToProps)
@injectIntl
class FollowedTags extends ImmutablePureComponent {
static propTypes = {
params: PropTypes.object.isRequired,
dispatch: PropTypes.func.isRequired,
intl: PropTypes.object.isRequired,
hashtags: ImmutablePropTypes.list,
isLoading: PropTypes.bool,
hasMore: PropTypes.bool,
multiColumn: PropTypes.bool,
};
componentDidMount() {
this.props.dispatch(fetchFollowedHashtags());
};
handleLoadMore = debounce(() => {
this.props.dispatch(expandFollowedHashtags());
}, 300, { leading: true });
render () {
const { intl, hashtags, isLoading, hasMore, multiColumn } = this.props;
const emptyMessage = <FormattedMessage id='empty_column.followed_tags' defaultMessage='You have not followed any hashtags yet. When you do, they will show up here.' />;
return (
<Column bindToDocument={!multiColumn}>
<ColumnHeader
icon='hashtag'
title={intl.formatMessage(messages.heading)}
showBackButton
multiColumn={multiColumn}
/>
<ScrollableList
scrollKey='followed_tags'
emptyMessage={emptyMessage}
hasMore={hasMore}
isLoading={isLoading}
onLoadMore={this.handleLoadMore}
bindToDocument={!multiColumn}
>
{hashtags.map((hashtag) => (
<Hashtag
key={hashtag.get('name')}
name={hashtag.get('name')}
to={`/tags/${hashtag.get('name')}`}
withGraph={false}
// Taken from ImmutableHashtag. Should maybe refactor ImmutableHashtag to accept more options?
people={hashtag.getIn(['history', 0, 'accounts']) * 1 + hashtag.getIn(['history', 1, 'accounts']) * 1}
history={hashtag.get('history').reverse().map((day) => day.get('uses')).toArray()}
/>
))}
</ScrollableList>
<Helmet>
<meta name='robots' content='noindex' />
</Helmet>
</Column>
);
}
}

View file

@ -246,7 +246,11 @@ class Notification extends ImmutablePureComponent {
}
renderStatus (notification, link) {
const { intl, unread } = this.props;
const { intl, unread, status } = this.props;
if (!status) {
return null;
}
return (
<HotKeys handlers={this.getHandlers()}>
@ -264,6 +268,7 @@ class Notification extends ImmutablePureComponent {
<StatusContainer
id={notification.get('status')}
account={notification.get('account')}
contextType='notifications'
muted
withDismiss
hidden={this.props.hidden}
@ -278,7 +283,11 @@ class Notification extends ImmutablePureComponent {
}
renderUpdate (notification, link) {
const { intl, unread } = this.props;
const { intl, unread, status } = this.props;
if (!status) {
return null;
}
return (
<HotKeys handlers={this.getHandlers()}>
@ -296,6 +305,7 @@ class Notification extends ImmutablePureComponent {
<StatusContainer
id={notification.get('status')}
account={notification.get('account')}
contextType='notifications'
muted
withDismiss
hidden={this.props.hidden}
@ -310,10 +320,14 @@ class Notification extends ImmutablePureComponent {
}
renderPoll (notification, account) {
const { intl, unread } = this.props;
const { intl, unread, status } = this.props;
const ownPoll = me === account.get('id');
const message = ownPoll ? intl.formatMessage(messages.ownPoll) : intl.formatMessage(messages.poll);
if (!status) {
return null;
}
return (
<HotKeys handlers={this.getHandlers()}>
<div className={classNames('notification notification-poll focusable', { unread })} tabIndex='0' aria-label={notificationForScreenReader(intl, message, notification.get('created_at'))}>
@ -334,6 +348,7 @@ class Notification extends ImmutablePureComponent {
<StatusContainer
id={notification.get('status')}
account={account}
contextType='notifications'
muted
withDismiss
hidden={this.props.hidden}

View file

@ -24,7 +24,7 @@ const makeMapStateToProps = () => {
const notification = getNotification(state, props.notification, props.accountId);
return {
notification: notification,
status: notification.get('status') ? getStatus(state, { id: notification.get('status') }) : null,
status: notification.get('status') ? getStatus(state, { id: notification.get('status'), contextType: 'notifications' }) : null,
report: notification.get('report') ? getReport(state, notification.get('report'), notification.getIn(['report', 'target_account', 'id'])) : null,
};
};

View file

@ -320,7 +320,7 @@ class FocalPointModal extends ImmutablePureComponent {
<React.Fragment>
<label className='setting-text-label' htmlFor='upload-modal__thumbnail'><FormattedMessage id='upload_form.thumbnail' defaultMessage='Change thumbnail' /></label>
<Button disabled={isUploadingThumbnail} text={intl.formatMessage(messages.chooseImage)} onClick={this.handleFileInputClick} />
<Button disabled={isUploadingThumbnail || !media.get('unattached')} text={intl.formatMessage(messages.chooseImage)} onClick={this.handleFileInputClick} />
<label>
<span style={{ display: 'none' }}>{intl.formatMessage(messages.chooseImage)}</span>

View file

@ -52,6 +52,8 @@ class LinkFooter extends React.PureComponent {
const canInvite = signedIn && ((permissions & PERMISSION_INVITE_USERS) === PERMISSION_INVITE_USERS);
const canProfileDirectory = profileDirectory;
const DividingCircle = <span aria-hidden>{' · '}</span>;
return (
<div className='link-footer'>
<p>
@ -60,17 +62,17 @@ class LinkFooter extends React.PureComponent {
<Link key='about' to='/about'><FormattedMessage id='footer.about' defaultMessage='About' /></Link>
{canInvite && (
<>
{' · '}
{DividingCircle}
<a key='invites' href='/invites' target='_blank'><FormattedMessage id='footer.invite' defaultMessage='Invite people' /></a>
</>
)}
{canProfileDirectory && (
<>
{' · '}
{DividingCircle}
<Link key='directory' to='/directory'><FormattedMessage id='footer.directory' defaultMessage='Profiles directory' /></Link>
</>
)}
{' · '}
{DividingCircle}
<Link key='privacy-policy' to='/privacy-policy'><FormattedMessage id='footer.privacy_policy' defaultMessage='Privacy policy' /></Link>
</p>
@ -78,13 +80,13 @@ class LinkFooter extends React.PureComponent {
<strong>Mastodon</strong>:
{' '}
<a href='https://joinmastodon.org' target='_blank'><FormattedMessage id='footer.about' defaultMessage='About' /></a>
{' · '}
{DividingCircle}
<a href='https://joinmastodon.org/apps' target='_blank'><FormattedMessage id='footer.get_app' defaultMessage='Get the app' /></a>
{' · '}
{DividingCircle}
<Link to='/keyboard-shortcuts'><FormattedMessage id='footer.keyboard_shortcuts' defaultMessage='Keyboard shortcuts' /></Link>
{' · '}
{DividingCircle}
<a href={source_url} rel='noopener noreferrer' target='_blank'><FormattedMessage id='footer.source_code' defaultMessage='View source code' /></a>
{' · '}
{DividingCircle}
v{version}
</p>
</div>

View file

@ -30,7 +30,7 @@ const SignInBanner = () => {
return (
<div className='sign-in-banner'>
<p><FormattedMessage id='sign_in_banner.text' defaultMessage='Sign in to follow profiles or hashtags, favourite, share and reply to posts, or interact from your account on a different server.' /></p>
<p><FormattedMessage id='sign_in_banner.text' defaultMessage='Sign in to follow profiles or hashtags, favourite, share and reply to posts. You can also interact from your account on a different server.' /></p>
<a href='/auth/sign_in' className='button button--block'><FormattedMessage id='sign_in_banner.sign_in' defaultMessage='Sign in' /></a>
{signupButton}
</div>

View file

@ -46,6 +46,7 @@ export default class VideoModal extends ImmutablePureComponent {
autoPlay={options.autoPlay}
volume={options.defaultVolume}
onCloseVideo={onClose}
autoFocus
detailed
alt={media.get('description')}
/>

View file

@ -42,6 +42,7 @@ import {
FollowRequests,
FavouritedStatuses,
BookmarkedStatuses,
FollowedTags,
ListTimeline,
Blocks,
DomainBlocks,
@ -54,7 +55,7 @@ import {
About,
PrivacyPolicy,
} from './util/async-components';
import initialState, { me, owner, singleUserMode, showTrends } from '../../initial_state';
import initialState, { me, owner, singleUserMode, showTrends, trendsAsLanding } from '../../initial_state';
import { closeOnboarding, INTRODUCTION_VERSION } from 'mastodon/actions/onboarding';
import Header from './components/header';
@ -163,7 +164,7 @@ class SwitchingColumnsArea extends React.PureComponent {
}
} else if (singleUserMode && owner && initialState?.accounts[owner]) {
redirect = <Redirect from='/' to={`/@${initialState.accounts[owner].username}`} exact />;
} else if (showTrends) {
} else if (showTrends && trendsAsLanding) {
redirect = <Redirect from='/' to='/explore' exact />;
} else {
redirect = <Redirect from='/' to='/about' exact />;
@ -216,6 +217,7 @@ class SwitchingColumnsArea extends React.PureComponent {
<WrappedRoute path='/follow_requests' component={FollowRequests} content={children} />
<WrappedRoute path='/blocks' component={Blocks} content={children} />
<WrappedRoute path='/domain_blocks' component={DomainBlocks} content={children} />
<WrappedRoute path='/followed_tags' component={FollowedTags} content={children} />
<WrappedRoute path='/mutes' component={Mutes} content={children} />
<WrappedRoute path='/lists' component={Lists} content={children} />

View file

@ -90,6 +90,10 @@ export function FavouritedStatuses () {
return import(/* webpackChunkName: "features/favourited_statuses" */'../../favourited_statuses');
}
export function FollowedTags () {
return import(/* webpackChunkName: "features/followed_tags" */'../../followed_tags');
}
export function BookmarkedStatuses () {
return import(/* webpackChunkName: "features/bookmarked_statuses" */'../../bookmarked_statuses');
}

View file

@ -122,6 +122,7 @@ class Video extends React.PureComponent {
volume: PropTypes.number,
muted: PropTypes.bool,
componentIndex: PropTypes.number,
autoFocus: PropTypes.bool,
};
static defaultProps = {
@ -523,7 +524,7 @@ class Video extends React.PureComponent {
}
render () {
const { preview, src, inline, onOpenVideo, onCloseVideo, intl, alt, detailed, sensitive, editable, blurhash } = this.props;
const { preview, src, inline, onOpenVideo, onCloseVideo, intl, alt, detailed, sensitive, editable, blurhash, autoFocus } = this.props;
const { containerWidth, currentTime, duration, volume, buffer, dragging, paused, fullscreen, hovered, muted, revealed } = this.state;
const progress = Math.min((currentTime / duration) * 100, 100);
const playerStyle = {};
@ -617,7 +618,7 @@ class Video extends React.PureComponent {
<div className='video-player__buttons-bar'>
<div className='video-player__buttons left'>
<button type='button' title={intl.formatMessage(paused ? messages.play : messages.pause)} aria-label={intl.formatMessage(paused ? messages.play : messages.pause)} className='player-button' onClick={this.togglePlay} autoFocus={detailed}><Icon id={paused ? 'play' : 'pause'} fixedWidth /></button>
<button type='button' title={intl.formatMessage(paused ? messages.play : messages.pause)} aria-label={intl.formatMessage(paused ? messages.play : messages.pause)} className='player-button' onClick={this.togglePlay} autoFocus={autoFocus}><Icon id={paused ? 'play' : 'pause'} fixedWidth /></button>
<button type='button' title={intl.formatMessage(muted ? messages.unmute : messages.mute)} aria-label={intl.formatMessage(muted ? messages.unmute : messages.mute)} className='player-button' onClick={this.toggleMute}><Icon id={muted ? 'volume-off' : 'volume-up'} fixedWidth /></button>
<div className={classNames('video-player__volume', { active: this.state.hovered })} onMouseDown={this.handleVolumeMouseDown} ref={this.setVolumeRef}>

View file

@ -75,6 +75,7 @@
* @property {boolean} timeline_preview
* @property {string} title
* @property {boolean} trends
* @property {boolean} trends_as_landing_page
* @property {boolean} unfollow_modal
* @property {boolean} use_blurhash
* @property {boolean=} use_pending_items
@ -126,6 +127,7 @@ export const singleUserMode = getMeta('single_user_mode');
export const source_url = getMeta('source_url');
export const timelinePreview = getMeta('timeline_preview');
export const title = getMeta('title');
export const trendsAsLanding = getMeta('trends_as_landing_page');
export const unfollowModal = getMeta('unfollow_modal');
export const useBlurhash = getMeta('use_blurhash');
export const usePendingItems = getMeta('use_pending_items');

View file

@ -128,7 +128,7 @@
"compose.language.search": "Soek tale...",
"compose_form.direct_message_warning_learn_more": "Leer meer",
"compose_form.encryption_warning": "Plasings op Mastodon is nie van punt tot punt versleutel nie. Moet geen sensitiewe inligting op Mastodon deel nie.",
"compose_form.hashtag_warning": "Hierdie plasing is ongelys en sal dus onder geen hutsetiket verskyn nie. Slegs openbare plasings is vindbaar wanneer daar na hutsetikette gesoek word.",
"compose_form.hashtag_warning": "This post won't be listed under any hashtag as it is unlisted. Only public posts can be searched by hashtag.",
"compose_form.lock_disclaimer": "Jou rekening is nie {locked} nie. Enigiemand kan jou volg en sien wat jy vir jou volgers plaas.",
"compose_form.lock_disclaimer.lock": "gesluit",
"compose_form.placeholder": "Wat wil jy deel?",
@ -221,6 +221,7 @@
"empty_column.favourites": "Hierdie plasing het nog nie goedkeurings ontvang nie. As iemand dit as gunsteling merk, sien jy dit hier.",
"empty_column.follow_recommendations": "Looks like no suggestions could be generated for you. You can try using search to look for people you might know or explore trending hashtags.",
"empty_column.follow_requests": "Jy het nog geen volgversoeke nie. Wanneer jy een ontvang, sal dit hier vertoon.",
"empty_column.followed_tags": "You have not followed any hashtags yet. When you do, they will show up here.",
"empty_column.hashtag": "Daar is nog niks vir hierdie hutsetiket nie.",
"empty_column.home": "Your home timeline is empty! Follow more people to fill it up. {suggestions}",
"empty_column.home.suggestions": "See some suggestions",
@ -263,6 +264,7 @@
"follow_request.authorize": "Authorize",
"follow_request.reject": "Reject",
"follow_requests.unlocked_explanation": "Even though your account is not locked, the {domain} staff thought you might want to review follow requests from these accounts manually.",
"followed_tags": "Followed hashtags",
"footer.about": "Oor",
"footer.directory": "Profielgids",
"footer.get_app": "Kry die app",
@ -379,6 +381,7 @@
"navigation_bar.favourites": "Gunstelinge",
"navigation_bar.filters": "Muted words",
"navigation_bar.follow_requests": "Follow requests",
"navigation_bar.followed_tags": "Followed hashtags",
"navigation_bar.follows_and_followers": "Follows and followers",
"navigation_bar.lists": "Lyste",
"navigation_bar.logout": "Teken uit",
@ -540,8 +543,9 @@
"server_banner.server_stats": "Server stats:",
"sign_in_banner.create_account": "Create account",
"sign_in_banner.sign_in": "Sign in",
"sign_in_banner.text": "Sign in to follow profiles or hashtags, favourite, share and reply to posts, or interact from your account on a different server.",
"sign_in_banner.text": "Sign in to follow profiles or hashtags, favourite, share and reply to posts. You can also interact from your account on a different server.",
"status.admin_account": "Open moderation interface for @{name}",
"status.admin_domain": "Open moderation interface for {domain}",
"status.admin_status": "Open hierdie plasing as moderator",
"status.block": "Block @{name}",
"status.bookmark": "Bookmark",
@ -558,7 +562,7 @@
"status.favourite": "Favourite",
"status.filter": "Filter this post",
"status.filtered": "Filtered",
"status.hide": "Hide toot",
"status.hide": "Hide post",
"status.history.created": "{name} created {date}",
"status.history.edited": "{name} edited {date}",
"status.load_more": "Load more",

View file

@ -54,7 +54,7 @@
"account.posts_with_replies": "Publicacions y respuestas",
"account.report": "Denunciar a @{name}",
"account.requested": "Esperando l'aprebación",
"account.requested_follow": "{name} has requested to follow you",
"account.requested_follow": "{name} ha demandau seguir-te",
"account.share": "Compartir lo perfil de @{name}",
"account.show_reblogs": "Amostrar retutz de @{name}",
"account.statuses_counter": "{count, plural, one {{counter} Publicación} other {{counter} Publicaciones}}",
@ -128,7 +128,7 @@
"compose.language.search": "Buscar idiomas...",
"compose_form.direct_message_warning_learn_more": "Aprender mas",
"compose_form.encryption_warning": "Las publicacions en Mastodon no son zifradas de cabo a cabo. No comparta garra información sensible en Mastodon.",
"compose_form.hashtag_warning": "Esta publicación no s'amostrará baixo garra hashtag perque no ye listada. Nomás las publicacions publicas se pueden buscar per hashtag.",
"compose_form.hashtag_warning": "Este tut no s'amostrará en cada etiqueta, ya que no ye publico. Nomás los tuts publicos se pueden buscar per etiqueta.",
"compose_form.lock_disclaimer": "La tuya cuenta no ye {locked}. Totz pueden seguir-te pa veyer las tuyas publicacions nomás pa seguidores.",
"compose_form.lock_disclaimer.lock": "blocau",
"compose_form.placeholder": "En qué yes pensando?",
@ -221,6 +221,7 @@
"empty_column.favourites": "Dengún ha marcau esta publicación como favorita. Quan belún lo faiga, amaneixerá aquí.",
"empty_column.follow_recommendations": "Pareixe que no s'ha puesto chenerar garra sucherencia pa tu. Puetz prebar a buscar a chent que talment conoixcas u explorar los hashtags que son en tendencia.",
"empty_column.follow_requests": "No tiens garra petición de seguidor. Quan recibas una, s'amostrará aquí.",
"empty_column.followed_tags": "You have not followed any hashtags yet. When you do, they will show up here.",
"empty_column.hashtag": "No i hai cosa en este hashtag encara.",
"empty_column.home": "La tuya linia temporal ye vueda! Sigue a mas personas pa replenar-la. {suggestions}",
"empty_column.home.suggestions": "Veyer qualques sucherencias",
@ -236,11 +237,11 @@
"errors.unexpected_crash.copy_stacktrace": "Copiar lo seguimiento de pila en o portafuellas",
"errors.unexpected_crash.report_issue": "Informar d'un problema/error",
"explore.search_results": "Resultaus de busqueda",
"explore.suggested_follows": "For you",
"explore.suggested_follows": "Pa tu",
"explore.title": "Explorar",
"explore.trending_links": "News",
"explore.trending_statuses": "Posts",
"explore.trending_tags": "Hashtags",
"explore.trending_links": "Noticias",
"explore.trending_statuses": "Publicacions",
"explore.trending_tags": "Etiquetas",
"filter_modal.added.context_mismatch_explanation": "Esta categoría de filtro no s'aplica a lo contexto en o qual ha accediu a esta publlicación. Si quiers que la publicación sía filtrada tamién en este contexto, habrás d'editar lo filtro.",
"filter_modal.added.context_mismatch_title": "Lo contexto no coincide!",
"filter_modal.added.expired_explanation": "Esta categoría de filtro ha caducau, amenesterá cambiar la calendata de caducidat pa que s'aplique.",
@ -263,6 +264,7 @@
"follow_request.authorize": "Autorizar",
"follow_request.reject": "Refusar",
"follow_requests.unlocked_explanation": "Tot y que la tuya cuenta no ye privada, lo personal de {domain} ha pensau que talment habrías de revisar manualment las solicitutz de seguimiento d'estas cuentas.",
"followed_tags": "Followed hashtags",
"footer.about": "Sobre",
"footer.directory": "Directorio de perfils",
"footer.get_app": "Obtener l'aplicación",
@ -379,6 +381,7 @@
"navigation_bar.favourites": "Favoritos",
"navigation_bar.filters": "Parolas silenciadas",
"navigation_bar.follow_requests": "Solicitutz pa seguir-te",
"navigation_bar.followed_tags": "Followed hashtags",
"navigation_bar.follows_and_followers": "Seguindo y seguidores",
"navigation_bar.lists": "Listas",
"navigation_bar.logout": "Zarrar sesión",
@ -540,8 +543,9 @@
"server_banner.server_stats": "Estatisticas d'o servidor:",
"sign_in_banner.create_account": "Creyar cuenta",
"sign_in_banner.sign_in": "Iniciar sesión",
"sign_in_banner.text": "Inicia sesión en este servidor pa seguir perfils u etiquetas, alzar, compartir y responder a mensaches. Tamién puetz interactuar dende unatra cuenta en un servidor diferent.",
"sign_in_banner.text": "Sign in to follow profiles or hashtags, favourite, share and reply to posts. You can also interact from your account on a different server.",
"status.admin_account": "Ubrir interficie de moderación pa @{name}",
"status.admin_domain": "Ubrir interficie de moderación pa {domain}",
"status.admin_status": "Ubrir este estau en a interficie de moderación",
"status.block": "Blocar a @{name}",
"status.bookmark": "Anyadir marcador",
@ -558,7 +562,7 @@
"status.favourite": "Favorito",
"status.filter": "Filtrar esta publicación",
"status.filtered": "Filtrau",
"status.hide": "Amagar publicación",
"status.hide": "Amagar la publicación",
"status.history.created": "{name} creyó {date}",
"status.history.edited": "{name} editó {date}",
"status.load_more": "Cargar mas",

View file

@ -128,7 +128,7 @@
"compose.language.search": "البحث عن لغة…",
"compose_form.direct_message_warning_learn_more": "تَعَلَّم المَزيد",
"compose_form.encryption_warning": "إنّ المنشورات على ماستدون ليست مشفرة من النهاية إلى النهاية. لا تشارك أي معلومات حساسة عبر ماستدون.",
"compose_form.hashtag_warning": "لن يُدرَج هذا المنشور تحت أي وسم بما أنَّه غير مُدرَج. فقط المنشورات العامة يُمكن البحث عنها بواسطة الوسم.",
"compose_form.hashtag_warning": "This post won't be listed under any hashtag as it is unlisted. Only public posts can be searched by hashtag.",
"compose_form.lock_disclaimer": "حسابُك غير {locked}. يُمكن لأي شخص مُتابعتك لرؤية (منشورات المتابعين فقط).",
"compose_form.lock_disclaimer.lock": "مُقفَل",
"compose_form.placeholder": "فِيمَ تُفكِّر؟",
@ -221,6 +221,7 @@
"empty_column.favourites": "لم يقم أي أحد بالإعجاب بهذا المنشور بعد. عندما يقوم أحدهم بذلك سوف يظهر هنا.",
"empty_column.follow_recommendations": "يبدو أنه لا يمكن إنشاء أي اقتراحات لك. يمكنك البحث عن أشخاص قد تعرفهم أو استكشاف الوسوم الرائجة.",
"empty_column.follow_requests": "ليس عندك أي طلب للمتابعة بعد. سوف تظهر طلباتك هنا إن قمت بتلقي البعض منها.",
"empty_column.followed_tags": "You have not followed any hashtags yet. When you do, they will show up here.",
"empty_column.hashtag": "ليس هناك بعدُ أي محتوى ذو علاقة بهذا الوسم.",
"empty_column.home": "إنّ الخيط الزمني لصفحتك الرئيسية فارغ. قم بزيارة {public} أو استخدم حقل البحث لكي تكتشف مستخدمين آخرين.",
"empty_column.home.suggestions": "شاهد بعض الاقتراحات",
@ -263,6 +264,7 @@
"follow_request.authorize": "ترخيص",
"follow_request.reject": "رفض",
"follow_requests.unlocked_explanation": "على الرغم من أن حسابك غير مقفل، فإن موظفين الـ{domain} ظنوا أنك قد ترغب في مراجعة طلبات المتابعة من هذه الحسابات يدوياً.",
"followed_tags": "الوسوم المتابَعة",
"footer.about": "حَول",
"footer.directory": "دليل الصفحات التعريفية",
"footer.get_app": "احصل على التطبيق",
@ -379,6 +381,7 @@
"navigation_bar.favourites": "المفضلة",
"navigation_bar.filters": "الكلمات المكتومة",
"navigation_bar.follow_requests": "طلبات المتابعة",
"navigation_bar.followed_tags": "الوسوم المتابَعة",
"navigation_bar.follows_and_followers": "المتابِعين والمتابَعون",
"navigation_bar.lists": "القوائم",
"navigation_bar.logout": "خروج",
@ -540,8 +543,9 @@
"server_banner.server_stats": "إحصائيات الخادم:",
"sign_in_banner.create_account": "أنشئ حسابًا",
"sign_in_banner.sign_in": "تسجيل الدخول",
"sign_in_banner.text": "قم بالولوج بحسابك لمتابعة الصفحات الشخصية أو الوسوم، أو لإضافة الرسائل إلى المفضلة ومشاركتها والرد عليها أو التفاعل بواسطة حسابك المتواجد على خادم مختلف.",
"sign_in_banner.text": "Sign in to follow profiles or hashtags, favourite, share and reply to posts. You can also interact from your account on a different server.",
"status.admin_account": "افتح الواجهة الإدارية لـ @{name}",
"status.admin_domain": "فتح واجهة الإشراف لـ {domain}",
"status.admin_status": "افتح هذا المنشور على واجهة الإشراف",
"status.block": "احجب @{name}",
"status.bookmark": "أضفه إلى الفواصل المرجعية",
@ -558,7 +562,7 @@
"status.favourite": "أضف إلى المفضلة",
"status.filter": "تصفية هذه الرسالة",
"status.filtered": "مُصفّى",
"status.hide": "اخف التبويق",
"status.hide": "إخفاء المنشور",
"status.history.created": "أنشأه {name} {date}",
"status.history.edited": "عدله {name} {date}",
"status.load_more": "حمّل المزيد",

View file

@ -4,7 +4,7 @@
"about.disclaimer": "Mastodon ye software gratuito ya de códigu llibre, ya una marca rexistrada de Mastodon gGmbH.",
"about.domain_blocks.no_reason_available": "El motivu nun ta disponible",
"about.domain_blocks.preamble": "Polo xeneral, Mastodon permítete ver el conteníu ya interactuar colos perfiles d'otros sirvidores nel fediversu. Estes son les esceiciones que se ficieron nesti sirvidor.",
"about.domain_blocks.silenced.explanation": "Polo xeneral, nun ves los perfiles ya el conteníu d'esti sirvidor sacante que los busques o decidas siguilos.",
"about.domain_blocks.silenced.explanation": "Polo xeneral, nun ves los perfiles ya'l conteníu d'esti sirvidor sacante que los busques o decidas siguilos.",
"about.domain_blocks.silenced.title": "Llendóse",
"about.domain_blocks.suspended.explanation": "Nun se procesa, atroxa nin intercambia nengún datu d'esti sirvidor, lo que fai que cualesquier interaición o comunicación colos sos perfiles seya imposible.",
"about.domain_blocks.suspended.title": "Suspendióse",
@ -54,7 +54,7 @@
"account.posts_with_replies": "Artículos ya rempuestes",
"account.report": "Report @{name}",
"account.requested": "Awaiting approval. Click to cancel follow request",
"account.requested_follow": "{name} has requested to follow you",
"account.requested_follow": "{name} solicitó siguite",
"account.share": "Share @{name}'s profile",
"account.show_reblogs": "Amosar los artículos compartíos de @{name}",
"account.statuses_counter": "{count, plural, one {{counter} artículu} other {{counter} artículos}}",
@ -221,6 +221,7 @@
"empty_column.favourites": "No one has favourited this post yet. When someone does, they will show up here.",
"empty_column.follow_recommendations": "Paez que nun se puen xenerar suxerencies pa ti. Pues tentar d'usar la busca p'atopar perfiles que pues conocer o esplorar les etiquetes en tendencia.",
"empty_column.follow_requests": "Entá nun tienes nenguna solicitú de siguimientu. Cuando recibas una, apaez equí.",
"empty_column.followed_tags": "You have not followed any hashtags yet. When you do, they will show up here.",
"empty_column.hashtag": "Entá nun hai nada con esta etiqueta.",
"empty_column.home": "Your home timeline is empty! Follow more people to fill it up. {suggestions}",
"empty_column.home.suggestions": "See some suggestions",
@ -263,6 +264,7 @@
"follow_request.authorize": "Autorizar",
"follow_request.reject": "Refugar",
"follow_requests.unlocked_explanation": "Magar que la to cuenta nun seya privada, el personal del dominiu «{domain}» pensó qu'a lo meyor quies revisar manualmente les solicitúes de siguimientu d'estes cuentes.",
"followed_tags": "Followed hashtags",
"footer.about": "Tocante a",
"footer.directory": "Direutoriu de perfiles",
"footer.get_app": "Consiguir l'aplicación",
@ -379,6 +381,7 @@
"navigation_bar.favourites": "Favoritos",
"navigation_bar.filters": "Pallabres desactivaes",
"navigation_bar.follow_requests": "Solicitúes de siguimientu",
"navigation_bar.followed_tags": "Followed hashtags",
"navigation_bar.follows_and_followers": "Follows and followers",
"navigation_bar.lists": "Llistes",
"navigation_bar.logout": "Zarrar la sesión",
@ -389,7 +392,7 @@
"navigation_bar.public_timeline": "Llinia de tiempu federada",
"navigation_bar.search": "Search",
"navigation_bar.security": "Seguranza",
"not_signed_in_indicator.not_signed_in": "You need to sign in to access this resource.",
"not_signed_in_indicator.not_signed_in": "Tienes d'aniciar la sesión p'acceder a esti recursu.",
"notification.admin.report": "{name} informó de: {target}",
"notification.admin.sign_up": "{name} rexistróse",
"notification.favourite": "{name} marcó'l to artículu como favoritu",
@ -540,8 +543,9 @@
"server_banner.server_stats": "Estadístiques del sirvidor:",
"sign_in_banner.create_account": "Crear una cuenta",
"sign_in_banner.sign_in": "Sign in",
"sign_in_banner.text": "Anicia la sesión pa siguir a perfiles o etiquetes, marcar como favoritu, compartir o responder a artículos, o interactuar cola to cuenta nun sirvidor diferente.",
"sign_in_banner.text": "Sign in to follow profiles or hashtags, favourite, share and reply to posts. You can also interact from your account on a different server.",
"status.admin_account": "Open moderation interface for @{name}",
"status.admin_domain": "Open moderation interface for {domain}",
"status.admin_status": "Open this status in the moderation interface",
"status.block": "Block @{name}",
"status.bookmark": "Meter en Marcadores",
@ -558,7 +562,7 @@
"status.favourite": "Favourite",
"status.filter": "Filter this post",
"status.filtered": "Filtered",
"status.hide": "Hide toot",
"status.hide": "Anubrir l'artículu",
"status.history.created": "{name} creó {date}",
"status.history.edited": "{name} editó {date}",
"status.load_more": "Cargar más",

View file

@ -128,7 +128,7 @@
"compose.language.search": "Шукаць мовы...",
"compose_form.direct_message_warning_learn_more": "Даведацца больш",
"compose_form.encryption_warning": "Допісы ў Mastodon не абаронены скразным шыфраваннем. Не дзяліцеся ніякай канфідэнцыяльнай інфармацыяй в Mastodon.",
"compose_form.hashtag_warning": "Гэты допіс не будзе паказаны пад аніякім хэштэгам, так як ён мае тып \"Не паказваць у стужках\". Толькі публічныя допісы могуць быць знойдзены па хэштэгу.",
"compose_form.hashtag_warning": "Гэты допіс не будзе паказаны пад аніякім хэштэгам, бо ён не публічны. Толькі публічныя допісы можна знайсці па хэштэгу.",
"compose_form.lock_disclaimer": "Ваш уліковы запіс не {locked}. Усе могуць падпісацца на вас, каб бачыць допісы толькі для падпісчыкаў.",
"compose_form.lock_disclaimer.lock": "закрыты",
"compose_form.placeholder": "Што здарылася?",
@ -221,6 +221,7 @@
"empty_column.favourites": "Ніхто яшчэ не ўпадабаў гэты допіс. Калі гэта адбудзецца, вы ўбачыце гэтых людзей тут.",
"empty_column.follow_recommendations": "Здаецца, прапаноў для вас няма. Вы можаце паспрабаваць выкарыстаць пошук, каб знайсці людзей, якіх вы можаце ведаць, ці даследаваць папулярныя хэштэгі.",
"empty_column.follow_requests": "У вас яшчэ няма запытаў на падпіскуі. Калі вы атрымаеце запыт, ён з'явяцца тут.",
"empty_column.followed_tags": "You have not followed any hashtags yet. When you do, they will show up here.",
"empty_column.hashtag": "Па гэтаму хэштэгу пакуль што нічога няма.",
"empty_column.home": "Галоўная стужка пустая! Падпішыцеся на іншых людзей, каб запоўніць яе. {suggestions}",
"empty_column.home.suggestions": "Глядзіце прапановы",
@ -263,6 +264,7 @@
"follow_request.authorize": "Аўтарызацыя",
"follow_request.reject": "Адхіліць",
"follow_requests.unlocked_explanation": "Ваш акаўнт не схаваны, аднак прадстаўнікі {domain} палічылі, што вы можаце захацець праглядзець запыты на падпіску з гэтых профіляў уручную.",
"followed_tags": "Followed hashtags",
"footer.about": "Пра нас",
"footer.directory": "Дырэкторыя профіляў",
"footer.get_app": "Спампаваць праграму",
@ -379,6 +381,7 @@
"navigation_bar.favourites": "Упадабаныя",
"navigation_bar.filters": "Ігнараваныя словы",
"navigation_bar.follow_requests": "Запыты на падпіску",
"navigation_bar.followed_tags": "Followed hashtags",
"navigation_bar.follows_and_followers": "Падпіскі і падпісчыкі",
"navigation_bar.lists": "Спісы",
"navigation_bar.logout": "Выйсці",
@ -540,8 +543,9 @@
"server_banner.server_stats": "Статыстыка сервера:",
"sign_in_banner.create_account": "Стварыць уліковы запіс",
"sign_in_banner.sign_in": "Увайсці",
"sign_in_banner.text": "Увайдзіце, каб падпісацца на людзей і тэгі, каб адказваць на допісы, дзяліцца імі і падабаць іх, альбо кантактаваць з вашага ўліковага запісу на іншым серверы.",
"sign_in_banner.text": "Sign in to follow profiles or hashtags, favourite, share and reply to posts. You can also interact from your account on a different server.",
"status.admin_account": "Адкрыць інтэрфейс мадэратара для @{name}",
"status.admin_domain": "Адкрыць інтэрфейс мадэратара для {domain}",
"status.admin_status": "Адкрыць гэты допіс у інтэрфейсе мадэрацыі",
"status.block": "Заблакаваць @{name}",
"status.bookmark": "Дадаць закладку",

View file

@ -1,7 +1,7 @@
{
"about.blocks": "Модерирани сървъри",
"about.contact": "За контакти:",
"about.disclaimer": "Mastodon е безплатен софтуер с отворен изходен код и търговска марка Mastodon gGmbH.",
"about.disclaimer": "Mastodon е безплатен софтуер с отворен изходен код и търговска марка на Mastodon gGmbH.",
"about.domain_blocks.no_reason_available": "Няма налична причина",
"about.domain_blocks.preamble": "Mastodon обикновено позволява да разглеждате съдържание и да взаимодействате с други потребители от всякакви сървъри във Федивърс. Има изключения, направени конкретно за този сървър.",
"about.domain_blocks.silenced.explanation": "Обикновено няма да виждате профили и съдържание, освен ако изрично не го потърсите или се включете в него, следвайки го.",
@ -38,7 +38,7 @@
"account.follows.empty": "Потребителят още никого не следва.",
"account.follows_you": "Следва ви",
"account.go_to_profile": "Към профила",
"account.hide_reblogs": "Скриване на споделяния от @{name}",
"account.hide_reblogs": "Скриване на подсилвания от @{name}",
"account.joined_short": "Дата на присъединяване",
"account.languages": "Промяна на езиците, за които сте абонирани",
"account.link_verified_on": "Собствеността върху тази връзка е проверена на {date}",
@ -49,14 +49,14 @@
"account.mute": "Заглушаване на @{name}",
"account.mute_notifications": "Заглушаване на известия от @{name}",
"account.muted": "Заглушено",
"account.open_original_page": "Отваряне на оригиналната страница",
"account.open_original_page": "Отваряне на първообразната страница",
"account.posts": "Публикации",
"account.posts_with_replies": "Публ. и отговори",
"account.report": "Докладване на @{name}",
"account.requested": "Чака се одобрение. Щракнете за отмяна на заявката за последване",
"account.requested_follow": "{name} поиска да ви последва",
"account.share": "Споделяне на профила на @{name}",
"account.show_reblogs": "Показване на споделяния от @{name}",
"account.show_reblogs": "Показване на подсилвания от @{name}",
"account.statuses_counter": "{count, plural, one {{counter} публикация} other {{counter} публикации}}",
"account.unblock": "Отблокиране на @{name}",
"account.unblock_domain": "Отблокиране на домейн {domain}",
@ -93,7 +93,7 @@
"bundle_modal_error.close": "Затваряне",
"bundle_modal_error.message": "Нещо се обърка, зареждайки компонента.",
"bundle_modal_error.retry": "Нов опит",
"closed_registrations.other_server_instructions": "Поради това че Mastodon е децентрализиран, можеш да създадеш акаунт на друг сървър, от който можеш да комуникираш с този.",
"closed_registrations.other_server_instructions": "Oткак e децентрализиранa Mastodon, може да създадете акаунт на друг сървър и още може да взаимодействате с този.",
"closed_registrations_modal.description": "Създаването на акаунт в {domain} сега не е възможно, но обърнете внимание, че нямате нужда от акаунт конкретно на {domain}, за да ползвате Mastodon.",
"closed_registrations_modal.find_another_server": "Намиране на друг сървър",
"closed_registrations_modal.preamble": "Mastodon е децентрализиран, така че няма значение къде създавате акаунта си, ще може да последвате и взаимодействате с всеки на този сървър. Може дори да стартирате свой собствен сървър!",
@ -128,14 +128,14 @@
"compose.language.search": "Търсене на езици...",
"compose_form.direct_message_warning_learn_more": "Още информация",
"compose_form.encryption_warning": "Публикациите в Mastodon не са криптирани от край до край. Не споделяйте никаква чувствителна информация там.",
"compose_form.hashtag_warning": "Тази публикация няма да бъде изброена под нито един хаштаг, тъй като е скрита. Само публични публикации могат да се търсят по хаштаг.",
"compose_form.lock_disclaimer": "Вашият акаунт не е {locked}. Всеки може да ви последва, за да прегледа вашите публикации само за последователи.",
"compose_form.hashtag_warning": "Тази публикация няма да се вписва под никакъв хаштаг, тъй като не е обществена. Само публични публикации могат да се търсят по хаштаг.",
"compose_form.lock_disclaimer": "Вашият акаунт не е в положение {locked}. Всеки може да ви последва, за да разглежда публикациите ви само за последователи.",
"compose_form.lock_disclaimer.lock": "заключено",
"compose_form.placeholder": "Какво мислите?",
"compose_form.poll.add_option": "Добавяне на избор",
"compose_form.poll.duration": "Времетраене на анкетата",
"compose_form.poll.option_placeholder": "Избор {number}",
"compose_form.poll.remove_option": "Премахване на избора",
"compose_form.poll.remove_option": "Премахване на този избор",
"compose_form.poll.switch_to_multiple": "Промяна на анкетата, за да се позволят множество възможни избора",
"compose_form.poll.switch_to_single": "Промяна на анкетата, за да се позволи един възможен избор",
"compose_form.publish": "Публикуване",
@ -168,7 +168,7 @@
"confirmations.mute.explanation": "Това ще скрие публикациите от тях и публикации, които ги споменават, но все още ще им позволява да виждат публикациите ви и да ви следват.",
"confirmations.mute.message": "Наистина ли искате да заглушите {name}?",
"confirmations.redraft.confirm": "Изтриване и преработване",
"confirmations.redraft.message": "Сигурни ли сте, че искате да изтриете тази публикация и да я върнете в чернова? Ще загубите споделянията и маркиранията като любима, и отговорите към оригинала ще останат висящи.",
"confirmations.redraft.message": "Сигурни ли сте, че искате да изтриете тази публикация и да я върнете в чернова? Ще загубите подсилванията и означаванията като любима, и отговорите към оригинала ще останат висящи.",
"confirmations.reply.confirm": "Отговор",
"confirmations.reply.message": "Отговарянето сега ще замени съобщението, което в момента съставяте. Сигурни ли сте, че искате да продължите?",
"confirmations.unfollow.confirm": "Без следване",
@ -213,27 +213,28 @@
"empty_column.account_unavailable": "Профилът не е наличен",
"empty_column.blocks": "Още не сте блокирали никакви потребители.",
"empty_column.bookmarked_statuses": "Още не сте отметнали публикации. Отметвайки някоя, то тя ще се покаже тук.",
"empty_column.community": "Локалният инфопоток е празен. Публикувайте нещо, за да започнете!",
"empty_column.community": "Местната часова ос е празна. Напишете нещо обществено, за да завъртите нещата!",
"empty_column.direct": "Все още нямате лични съобщения. Когато изпратите или получите някое, то ще се покаже тук.",
"empty_column.domain_blocks": "Още няма блокирани домейни.",
"empty_column.explore_statuses": "Няма нищо популярно в момента. Проверете пак по-късно!",
"empty_column.explore_statuses": "Няма нищо налагащо се в момента. Проверете пак по-късно!",
"empty_column.favourited_statuses": "Още нямате любими публикации. Поставяйки някоя в любими, то тя ще се покаже тук.",
"empty_column.favourites": "Още никой не е поставил публикацията в любими. Когато някой го направи, този човек ще се покаже тук.",
"empty_column.follow_recommendations": "Изглежда, че няма генерирани предложения за вас. Можете да опитате да търсите за хора, които знаете или да разгледате популярните тагове.",
"empty_column.follow_requests": "Все още нямате заявки за последване. Когато получите такава, тя ще се покаже тук.",
"empty_column.follow_recommendations": "Изглежда, че няма предложения, които може да се породят за вас. Може да опитате да потърсите хора, които познавате или да разгледате налагащи се хаштагове.",
"empty_column.follow_requests": "Още нямате заявки за последване. Получавайки такава, то тя ще се покаже тук.",
"empty_column.followed_tags": "Още не сте последвали никакви хаштагове. Последваните хаштагове ще се покажат тук.",
"empty_column.hashtag": "Още няма нищо в този хаштаг.",
"empty_column.home": "Вашата начална часова ос е празна! Последвайте повече хора, за да я запълните. {suggestions}",
"empty_column.home.suggestions": "Преглед на някои предложения",
"empty_column.list": "Още няма нищо в този списък. Когато членовете на списъка публикуват нови публикации, то те ще се появят тук.",
"empty_column.list": "Все още списъкът е празен. Членуващите на списъка, публикуващи нови публикации, ще се появят тук.",
"empty_column.lists": "Все още нямате списъци. Когато създадете такъв, той ще се покаже тук.",
"empty_column.mutes": "Още не сте заглушавали потребители.",
"empty_column.notifications": "Все още нямате известия. Взаимодействайте с другите, за да започнете разговора.",
"empty_column.public": "Тук няма нищо! Публикувайте нещо или последвайте потребители от други сървъри, за да го напълните",
"error.unexpected_crash.explanation": "Поради грешка в нашия код или проблем със съвместимостта на браузъра, тази страница не може да се покаже правилно.",
"error.unexpected_crash.explanation_addons": "Тази страница не може да се покаже правилно. Тази грешка вероятно е причинена от добавка на браузъра или инструменти за автоматичен превод.",
"error.unexpected_crash.next_steps": "Опитайте да опресните страницата. Ако това не помогне, все още можете да използвате Mastodon чрез различен браузър или приложение.",
"error.unexpected_crash.explanation_addons": "Страницата не можа да се покаже правилно. Тази грешка вероятно е причинена от добавка на браузъра или от средства за автоматичен превод.",
"error.unexpected_crash.next_steps": "Опитайте опресняване на страницата. Ако това не помогне, още може да използвате Mastodon през различен браузър или приложение.",
"error.unexpected_crash.next_steps_addons": "Опитайте се да ги изключите и да опресните страницата. Ако това не помогне, то още може да използвате Mastodon чрез различен браузър или приложение.",
"errors.unexpected_crash.copy_stacktrace": "Копиране на stacktrace-а в клипборда",
"errors.unexpected_crash.copy_stacktrace": "Копиране на трасето на стека в буферната памет",
"errors.unexpected_crash.report_issue": "Сигнал за проблем",
"explore.search_results": "Резултати от търсенето",
"explore.suggested_follows": "За вас",
@ -250,7 +251,7 @@
"filter_modal.added.settings_link": "страница с настройки",
"filter_modal.added.short_explanation": "Тази публикация е добавена към следната категория на филтъра: {title}.",
"filter_modal.added.title": "Филтърът е добавен!",
"filter_modal.select_filter.context_mismatch": "не е приложимо за този контекст",
"filter_modal.select_filter.context_mismatch": "неприложимо за контекста",
"filter_modal.select_filter.expired": "изтекло",
"filter_modal.select_filter.prompt_new": "Нова категория: {name}",
"filter_modal.select_filter.search": "Търсене или създаване",
@ -259,10 +260,11 @@
"filter_modal.title.status": "Филтриране на публ.",
"follow_recommendations.done": "Готово",
"follow_recommendations.heading": "Следвайте хора, от които харесвате да виждате публикации! Ето някои предложения.",
"follow_recommendations.lead": "Съобщения от хора, които следвате, ще се показват в хронологичен ред във вашия основен инфопоток. Не се страхувайте, че ще сгрешите, по всяко време много лесно можете да спрете да ги следвате!",
"follow_recommendations.lead": "Публикациите от последваните, ще се показват в хронологичен ред в началния ви инфоканал. Не се страхувайте, че ще сгрешите, по всяко време много лесно може да спрете да ги следвате!",
"follow_request.authorize": "Упълномощаване",
"follow_request.reject": "Отхвърляне",
"follow_requests.unlocked_explanation": "Въпреки че акаунтът ви не е заключен, служителите на {domain} помислиха, че може да искате да преглеждате ръчно заявките за последване на тези профили.",
"followed_tags": "Последвани хештагове",
"footer.about": "Относно",
"footer.directory": "Директория на профилите",
"footer.get_app": "Вземане на приложението",
@ -284,35 +286,35 @@
"hashtag.follow": "Следване на хаштаг",
"hashtag.unfollow": "Спиране на следване на хаштаг",
"home.column_settings.basic": "Основно",
"home.column_settings.show_reblogs": "Показване на споделяния",
"home.column_settings.show_reblogs": "Показване на подсилванията",
"home.column_settings.show_replies": "Показване на отговорите",
"home.hide_announcements": "Скриване на оповестяванията",
"home.show_announcements": "Показване на оповестяванията",
"interaction_modal.description.favourite": "Ако имате профил в Mastodon, можете да маркирате публикация като любима, за да уведомите автора, че я оценявате, и да я запазите за по-късно.",
"interaction_modal.description.follow": "Ако имате регистрация в Mastodon, то може да последвате {name}, за да виждате публикациите от този профил в началния си инфопоток.",
"interaction_modal.description.reblog": "Ако имате профил в Mastodon, можете да споделите тази публикация със своите последователи.",
"interaction_modal.description.reply": "Ако имате профил в Mastodon, можете да добавите отговор към тази публикация.",
"interaction_modal.description.favourite": "С акаунт в Mastodon може да направите тази публикация като любима, за да известите автора, че я цените, и да я запазите за по-късно.",
"interaction_modal.description.follow": "С акаунт в Mastodon може да последвате {name}, за да получавате публикациите от този акаунт в началния си инфоканал.",
"interaction_modal.description.reblog": "С акаунт в Mastodon може да подсилите тази публикация, за да я споделите с последователите си.",
"interaction_modal.description.reply": "С акаунт в Mastodon може да добавите отговор към тази публикация.",
"interaction_modal.on_another_server": "На различен сървър",
"interaction_modal.on_this_server": "На този сървър",
"interaction_modal.other_server_instructions": "Копипейстнете този URL адрес в полето за търсене на любимото си приложение Mastodon или мрежови интерфейс на своя Mastodon сървър.",
"interaction_modal.preamble": "Откак Mastodon е децентрализиран, може да употребявате съществуващ акаунт, разположен на друг сървър на Mastodon или съвместима платформа, ако нямате акаунт на този сървър.",
"interaction_modal.title.favourite": "Любими публикации на {name}",
"interaction_modal.title.follow": "Последване на {name}",
"interaction_modal.title.reblog": "Споделете публикацията от {name}",
"interaction_modal.title.reblog": "Подсилване на публикацията на {name}",
"interaction_modal.title.reply": "Отговаряне на публикацията на {name}",
"intervals.full.days": "{number, plural, one {# ден} other {# дни}}",
"intervals.full.hours": "{number, plural, one {# час} other {# часа}}",
"intervals.full.minutes": "{number, plural, one {# минута} other {# минути}}",
"keyboard_shortcuts.back": "Навигиране назад",
"keyboard_shortcuts.blocked": "Отваряне на списъка с блокирани потребители",
"keyboard_shortcuts.boost": "за споделяне",
"keyboard_shortcuts.boost": "Подсилване на публикация",
"keyboard_shortcuts.column": "Съсредоточение на колона",
"keyboard_shortcuts.compose": "Фокус на текстовото пространство за композиране",
"keyboard_shortcuts.compose": "Фокус на текстовата зона за съставяне",
"keyboard_shortcuts.description": "Опис",
"keyboard_shortcuts.direct": "за отваряне на колоната с лични съобщения",
"keyboard_shortcuts.down": "Преместване надолу в списъка",
"keyboard_shortcuts.enter": "Отваряне на публикация",
"keyboard_shortcuts.favourite": "Любима публикация",
"keyboard_shortcuts.favourite": "Към любими публикации",
"keyboard_shortcuts.favourites": "Отваряне на списъка с любими",
"keyboard_shortcuts.federated": "Отваряне на федерирания инфопоток",
"keyboard_shortcuts.heading": "Клавишни съчетания",
@ -320,7 +322,7 @@
"keyboard_shortcuts.hotkey": "Бърз клавиш",
"keyboard_shortcuts.legend": "Показване на тази легенда",
"keyboard_shortcuts.local": "Отваряне на местна часова ос",
"keyboard_shortcuts.mention": "Споменаване на автор",
"keyboard_shortcuts.mention": "Споменаване на автора",
"keyboard_shortcuts.muted": "Отваряне на списъка със заглушени потребители",
"keyboard_shortcuts.my_profile": "Отваряне на профила ви",
"keyboard_shortcuts.notifications": "Отваряне на колоната с известия",
@ -330,12 +332,12 @@
"keyboard_shortcuts.reply": "Отговаряне на публикация",
"keyboard_shortcuts.requests": "Отваряне на списъка със заявки за последване",
"keyboard_shortcuts.search": "Фокус на лентата за търсене",
"keyboard_shortcuts.spoilers": "за показване/скриване на ПС полето",
"keyboard_shortcuts.spoilers": "Показване/скриване на полето за предупреждение на съдържание",
"keyboard_shortcuts.start": "Отваряне на колоната \"първи стъпки\"",
"keyboard_shortcuts.toggle_hidden": "за показване/скриване на текст зад ПС",
"keyboard_shortcuts.toggle_hidden": "Показване/скриване на текст зад предупреждение на съдържание",
"keyboard_shortcuts.toggle_sensitivity": "Показване/скриване на мултимедията",
"keyboard_shortcuts.toot": "Начало на нова публикация",
"keyboard_shortcuts.unfocus": "Разфокусиране на текстовото поле за композиране/търсене",
"keyboard_shortcuts.unfocus": "Разфокусиране на текстовото поле за съставяне/търсене",
"keyboard_shortcuts.up": "Преместване нагоре в списъка",
"lightbox.close": "Затваряне",
"lightbox.compress": "Свиване на полето за преглед на образи",
@ -366,7 +368,7 @@
"mute_modal.duration": "Времетраене",
"mute_modal.hide_notifications": "Скривате ли известията от този потребител?",
"mute_modal.indefinite": "Неопределено",
"navigation_bar.about": "За тази инстанция",
"navigation_bar.about": "Относно",
"navigation_bar.blocks": "Блокирани потребители",
"navigation_bar.bookmarks": "Отметки",
"navigation_bar.community_timeline": "Локален инфопоток",
@ -379,6 +381,7 @@
"navigation_bar.favourites": "Любими",
"navigation_bar.filters": "Заглушени думи",
"navigation_bar.follow_requests": "Заявки за последване",
"navigation_bar.followed_tags": "Последвани хештагове",
"navigation_bar.follows_and_followers": "Последвания и последователи",
"navigation_bar.lists": "Списъци",
"navigation_bar.logout": "Излизане",
@ -389,7 +392,7 @@
"navigation_bar.public_timeline": "Федеративна хронология",
"navigation_bar.search": "Търсене",
"navigation_bar.security": "Сигурност",
"not_signed_in_indicator.not_signed_in": "Трябва да влезете за достъп до този ресурс.",
"not_signed_in_indicator.not_signed_in": "Трябва ви вход за достъп до ресурса.",
"notification.admin.report": "{name} докладва {target}",
"notification.admin.sign_up": "{name} се регистрира",
"notification.favourite": "{name} сложи в любими ваша публикация",
@ -398,7 +401,7 @@
"notification.mention": "{name} ви спомена",
"notification.own_poll": "Анкетата ви приключи",
"notification.poll": "Анкета, в която гласувахте, приключи",
"notification.reblog": "{name} сподели вашата публикация",
"notification.reblog": "{name} подсили ваша публикация",
"notification.status": "{name} току-що публикува",
"notification.update": "{name} промени публикация",
"notifications.clear": "Изчистване на известията",
@ -415,7 +418,7 @@
"notifications.column_settings.mention": "Споменавания:",
"notifications.column_settings.poll": "Резултати от анкета:",
"notifications.column_settings.push": "Изскачащи известия",
"notifications.column_settings.reblog": "Споделяния:",
"notifications.column_settings.reblog": "Подсилвания:",
"notifications.column_settings.show": "Показване в колоната",
"notifications.column_settings.sound": "Пускане на звук",
"notifications.column_settings.status": "Нови публикации:",
@ -423,7 +426,7 @@
"notifications.column_settings.unread_notifications.highlight": "Изтъкване на непрочетените известия",
"notifications.column_settings.update": "Промени:",
"notifications.filter.all": "Всичко",
"notifications.filter.boosts": "Споделяния",
"notifications.filter.boosts": "Подсилвания",
"notifications.filter.favourites": "Любими",
"notifications.filter.follows": "Последвания",
"notifications.filter.mentions": "Споменавания",
@ -433,7 +436,7 @@
"notifications.group": "{count} известия",
"notifications.mark_as_read": "Отбелязване на всички известия като прочетени",
"notifications.permission_denied": "Известията на работния плот не са налични поради предварително отказана заявка за разрешение в браузъра",
"notifications.permission_denied_alert": "Известията на работния плот не могат да бъдат активирани, тъй като разрешението на браузъра е отказвано преди",
"notifications.permission_denied_alert": "Известията на работния плот не могат да се включат, тъй като разрешението на браузъра е отказвано преди",
"notifications.permission_required": "Известията на работния плот ги няма, щото няма дадено нужното позволение.",
"notifications_permission_banner.enable": "Включване на известията на работния плот",
"notifications_permission_banner.how_to_control": "За да получавате известия, когато Mastodon не е отворен, включете известията на работния плот. Може да управлявате точно кои видове взаимодействия пораждат известия на работния плот чрез бутона {icon} по-горе, след като бъдат включени.",
@ -486,23 +489,23 @@
"report.close": "Готово",
"report.comment.title": "Има ли нещо друго, което смятате, че трябва да знаем?",
"report.forward": "Препращане до {target}",
"report.forward_hint": "Акаунтът е от друг сървър. Ще изпратите ли анонимно копие на доклада и там?",
"report.forward_hint": "Акаунтът е от друг сървър. Ще изпратите ли безимено копие на доклада и там?",
"report.mute": "Заглушаване",
"report.mute_explanation": "Няма да виждате публикациите на това лице. То още може да ви следва и да вижда публикациите ви и няма да знае, че е заглушено.",
"report.next": "Напред",
"report.placeholder": "Допълнителни коментари",
"report.reasons.dislike": "Не ми харесва",
"report.reasons.dislike_description": "Не е нещо, които искам да виждам",
"report.reasons.dislike_description": "Не е нещо, което искате да виждате",
"report.reasons.other": "Нещо друго е",
"report.reasons.other_description": "Проблемът не попада в нито една от другите категории",
"report.reasons.spam": "Спам е",
"report.reasons.spam_description": "Зловредни връзки, фалшиви взаимодействия, или повтарящи се отговори",
"report.reasons.spam_description": "Зловредни връзки, фалшиви ангажименти, или повтарящи се отговори",
"report.reasons.violation": "Нарушава правилата на сървъра",
"report.reasons.violation_description": "Знаете, че нарушава особени правила",
"report.rules.subtitle": "Изберете всичко, което да се прилага",
"report.rules.title": "Кои правила са нарушени?",
"report.statuses.subtitle": "Изберете всичко, което да се прилага",
"report.statuses.title": "Има ли някакви публикации, подкрепящи този доклад?",
"report.statuses.title": "Има ли някакви публикации, подкрепящи доклада?",
"report.submit": "Подаване",
"report.target": "Докладване на {target}",
"report.thanks.take_action": "Ето възможностите ви за управление какво виждате в Mastodon:",
@ -511,7 +514,7 @@
"report.thanks.title_actionable": "Благодарности за докладването, ще го прегледаме.",
"report.unfollow": "Стоп на следването на @{name}",
"report.unfollow_explanation": "Последвали сте този акаунт. За да не виждате повече публикациите му в началния си инфопоток, спрете да го следвате.",
"report_notification.attached_statuses": "прикачено {count, plural, one {{count} публикация} other {{count} публикации}}",
"report_notification.attached_statuses": "{count, plural, one {прикаченa {count} публикация} other {прикачени {count} публикации}}",
"report_notification.categories.other": "Друго",
"report_notification.categories.spam": "Спам",
"report_notification.categories.violation": "Нарушение на правилото",
@ -540,13 +543,14 @@
"server_banner.server_stats": "Статистика на сървъра:",
"sign_in_banner.create_account": "Създаване на акаунт",
"sign_in_banner.sign_in": "Вход",
"sign_in_banner.text": "Влезте, за да последвате профили или хаштагове, любимо, споделяне и отговаряне на публикации или взаимодействие от акаунта ви на друг сървър.",
"sign_in_banner.text": "Влезте, за да последвате профили или хаштагове, отбелязвате като любими, споделяте и отговаряте на публикации. Можете също така да взаимодействате от акаунта ви на друг сървър.",
"status.admin_account": "Отваряне на интерфейс за модериране за @{name}",
"status.admin_status": "Отваряне на тази публикация в интерфейс на модериране",
"status.admin_domain": "Отваряне на модериращия интерфейс за {domain}",
"status.admin_status": "Отваряне на публикацията в модериращия интерфейс",
"status.block": "Блокиране на @{name}",
"status.bookmark": "Отмятане",
"status.cancel_reblog_private": "Отсподеляне",
"status.cannot_reblog": "Тази публикация не може да бъде споделена",
"status.cancel_reblog_private": "Край на подсилването",
"status.cannot_reblog": "Публикация не може да се подсили",
"status.copy": "Копиране на връзката към публикация",
"status.delete": "Изтриване",
"status.detailed_status": "Подробен изглед на разговора",
@ -571,17 +575,17 @@
"status.pin": "Закачане в профила",
"status.pinned": "Закачена публикация",
"status.read_more": "Още за четене",
"status.reblog": "Споделяне",
"status.reblog_private": "Споделяне с оригинална видимост",
"status.reblogged_by": "{name} сподели",
"status.reblogs.empty": "Все още никой не е споделил тази публикация. Когато някой го направи, ще се покаже тук.",
"status.reblog": "Подсилване",
"status.reblog_private": "Подсилване с оригиналната видимост",
"status.reblogged_by": "{name} подсили",
"status.reblogs.empty": "Още никого не е подсилвал публикацията. Подсилващият ще се покаже тук.",
"status.redraft": "Изтриване и преработване",
"status.remove_bookmark": "Премахване на отметката",
"status.replied_to": "В отговор до {name}",
"status.reply": "Отговор",
"status.replyAll": "Отговор на тема",
"status.replyAll": "Отговор на нишка",
"status.report": "Докладване на @{name}",
"status.sensitive_warning": "Чувствително съдържание",
"status.sensitive_warning": "Деликатно съдържание",
"status.share": "Споделяне",
"status.show_filter_reason": "Покажи въпреки това",
"status.show_less": "Показване на по-малко",
@ -599,7 +603,7 @@
"subscribed_languages.target": "Смяна на езика за {target}",
"suggestions.dismiss": "Отхвърляне на предложение",
"suggestions.header": "Може да имате интерес от…",
"tabs_bar.federated_timeline": "Федерална",
"tabs_bar.federated_timeline": "Федеративен",
"tabs_bar.home": "Начало",
"tabs_bar.local_timeline": "Местни",
"tabs_bar.notifications": "Известия",
@ -620,22 +624,22 @@
"units.short.thousand": "{count}хил",
"upload_area.title": "Влачене и пускане за качване",
"upload_button.label": "Добавете файл с образ, видео или звук",
"upload_error.limit": "Превишено ограничение за качване на файлове.",
"upload_error.limit": "Превишено ограничението за качване на файлове.",
"upload_error.poll": "Качването на файлове не е позволено с анкети.",
"upload_form.audio_description": "Опишете за хора със загубен слух",
"upload_form.description": "Опишете за хора със зрително увреждане",
"upload_form.audio_description": "Опишете за хора, които са глухи или трудно чуват",
"upload_form.description": "Опишете за хора, които са слепи или имат слабо зрение",
"upload_form.description_missing": "Няма добавен опис",
"upload_form.edit": "Редактиране",
"upload_form.thumbnail": "Промяна на миниобраза",
"upload_form.undo": "Изтриване",
"upload_form.video_description": "Опишете за хора със загубен слух или зрително увреждане",
"upload_form.video_description": "Опишете за хора, които са глухи или трудно чуват, слепи или имат слабо зрение",
"upload_modal.analyzing_picture": "Снимков анализ…",
"upload_modal.apply": "Прилагане",
"upload_modal.applying": "Прилагане…",
"upload_modal.choose_image": "Избор на образ",
"upload_modal.description_placeholder": "Ах, чудна българска земьо, полюшвай цъфтящи жита",
"upload_modal.detect_text": "Откриване на текст от картина",
"upload_modal.edit_media": "Редакция на мултимедия",
"upload_modal.edit_media": "Промяна на мултимедия",
"upload_modal.hint": "Щракнете или плъзнете кръга на визуализацията, за да изберете фокусна точка, която винаги ще бъде видима на всички миниатюри.",
"upload_modal.preparing_ocr": "Подготовка за оптично разпознаване на знаци…",
"upload_modal.preview_label": "Нагледно ({ratio})",

View file

@ -128,7 +128,7 @@
"compose.language.search": "Search languages...",
"compose_form.direct_message_warning_learn_more": "আরো জানুন",
"compose_form.encryption_warning": "Posts on Mastodon are not end-to-end encrypted. Do not share any dangerous information over Mastodon.",
"compose_form.hashtag_warning": "কোনো হ্যাশট্যাগের ভেতরে এই টুটটি থাকবেনা কারণ এটি তালিকাবহির্ভূত। শুধুমাত্র প্রকাশ্য ঠোটগুলো হ্যাশট্যাগের ভেতরে খুঁজে পাওয়া যাবে।",
"compose_form.hashtag_warning": "This post won't be listed under any hashtag as it is unlisted. Only public posts can be searched by hashtag.",
"compose_form.lock_disclaimer": "আপনার নিবন্ধনে তালা দেওয়া নেই, যে কেও আপনাকে অনুসরণ করতে পারবে এবং অনুশারকদের জন্য লেখা দেখতে পারবে।",
"compose_form.lock_disclaimer.lock": "তালা দেওয়া",
"compose_form.placeholder": "আপনি কি ভাবছেন ?",
@ -221,6 +221,7 @@
"empty_column.favourites": "কেও এখনো এটাকে পছন্দের টুট হিসেবে চিহ্নিত করেনি। যদি করে, তখন তাদের এখানে পাওয়া যাবে।",
"empty_column.follow_recommendations": "Looks like no suggestions could be generated for you. You can try using search to look for people you might know or explore trending hashtags.",
"empty_column.follow_requests": "তোমার এখনো কোনো অনুসরণের আবেদন পাওনি। যদি কেউ পাঠায়, এখানে পাওয়া যাবে।",
"empty_column.followed_tags": "You have not followed any hashtags yet. When you do, they will show up here.",
"empty_column.hashtag": "এই হেসটাগে এখনো কিছু নেই।",
"empty_column.home": "আপনার বাড়ির সময়রেখা এখনো খালি! {public} এ ঘুরে আসুন অথবা অনুসন্ধান বেবহার করে শুরু করতে পারেন এবং অন্য ব্যবহারকারীদের সাথে সাক্ষাৎ করতে পারেন।",
"empty_column.home.suggestions": "See some suggestions",
@ -263,6 +264,7 @@
"follow_request.authorize": "অনুমতি দিন",
"follow_request.reject": "প্রত্যাখ্যান করুন",
"follow_requests.unlocked_explanation": "আপনার অ্যাকাউন্টটি লক না থাকলেও, {domain} কর্মীরা ভেবেছিলেন যে আপনি এই অ্যাকাউন্টগুলি থেকে ম্যানুয়ালি অনুসরণের অনুরোধগুলি পর্যালোচনা করতে চাইতে পারেন।",
"followed_tags": "Followed hashtags",
"footer.about": "About",
"footer.directory": "Profiles directory",
"footer.get_app": "Get the app",
@ -379,6 +381,7 @@
"navigation_bar.favourites": "পছন্দের",
"navigation_bar.filters": "বন্ধ করা শব্দ",
"navigation_bar.follow_requests": "অনুসরণের অনুরোধগুলি",
"navigation_bar.followed_tags": "Followed hashtags",
"navigation_bar.follows_and_followers": "অনুসরণ এবং অনুসরণকারী",
"navigation_bar.lists": "তালিকাগুলো",
"navigation_bar.logout": "বাইরে যান",
@ -540,8 +543,9 @@
"server_banner.server_stats": "Server stats:",
"sign_in_banner.create_account": "Create account",
"sign_in_banner.sign_in": "Sign in",
"sign_in_banner.text": "Sign in to follow profiles or hashtags, favourite, share and reply to posts, or interact from your account on a different server.",
"sign_in_banner.text": "Sign in to follow profiles or hashtags, favourite, share and reply to posts. You can also interact from your account on a different server.",
"status.admin_account": "@{name} র জন্য পরিচালনার ইন্টারফেসে ঢুকুন",
"status.admin_domain": "Open moderation interface for {domain}",
"status.admin_status": "যায় লেখাটি পরিচালনার ইন্টারফেসে খুলুন",
"status.block": "@{name} কে ব্লক করুন",
"status.bookmark": "বুকমার্ক",
@ -558,7 +562,7 @@
"status.favourite": "পছন্দের করতে",
"status.filter": "Filter this post",
"status.filtered": "ছাঁকনিদিত",
"status.hide": "Hide toot",
"status.hide": "Hide post",
"status.history.created": "{name} created {date}",
"status.history.edited": "{name} edited {date}",
"status.load_more": "আরো দেখুন",

View file

@ -128,7 +128,7 @@
"compose.language.search": "Klask yezhoù...",
"compose_form.direct_message_warning_learn_more": "Gouzout hiroc'h",
"compose_form.encryption_warning": "Toudoù war Mastodon na vezont ket sifret penn-da-benn. Na rannit ket titouroù kizidik dre Mastodon.",
"compose_form.hashtag_warning": "Ne vo ket listennet an toud-mañ dindan gerioù-klik ebet dre m'eo anlistennet. N'eus nemet an toudoù foran a c'hall bezañ klasket dre c'her-klik.",
"compose_form.hashtag_warning": "This post won't be listed under any hashtag as it is unlisted. Only public posts can be searched by hashtag.",
"compose_form.lock_disclaimer": "N'eo ket {locked} ho kont. An holl a c'hal ho heuliañ evit gwelet ho toudoù prevez.",
"compose_form.lock_disclaimer.lock": "prennet",
"compose_form.placeholder": "Petra emaoc'h o soñjal e-barzh ?",
@ -221,6 +221,7 @@
"empty_column.favourites": "Den ebet n'eus ouzhpennet an toud-mañ en e reoù muiañ-karet c'hoazh. Pa vo graet gant unan bennak e teuio war wel amañ.",
"empty_column.follow_recommendations": "War a seblant ne c'hall ket bezañ savet erbedadenn ebet evidoc'h. Gallout a rit implijout un enklask evit kavout tud a anavezfec'h pe furchal ar gerioù-klik diouzh ar c'hiz.",
"empty_column.follow_requests": "N'ho peus reked heuliañ ebet c'hoazh. Pa vo resevet unan e teuio war wel amañ.",
"empty_column.followed_tags": "You have not followed any hashtags yet. When you do, they will show up here.",
"empty_column.hashtag": "N'eus netra er ger-klik-mañ c'hoazh.",
"empty_column.home": "Goullo eo ho red-amzer degemer! Kit da weladenniñ {public} pe implijit ar c'hlask evit kregiñ ganti ha kejañ gant implijer·ien·ezed all.",
"empty_column.home.suggestions": "Gwellout damvenegoù",
@ -263,6 +264,7 @@
"follow_request.authorize": "Aotren",
"follow_request.reject": "Nac'hañ",
"follow_requests.unlocked_explanation": "Daoust ma n'eo ket ho kont prennet, skipailh {domain} a soñj e fellfe deoc'h gwiriekaat pedadennoù heuliañ deus ar c'hontoù-se diwar-zorn.",
"followed_tags": "Followed hashtags",
"footer.about": "Diwar-benn",
"footer.directory": "Kavlec'h ar profiloù",
"footer.get_app": "Pellgargañ an arload",
@ -379,6 +381,7 @@
"navigation_bar.favourites": "Ar re vuiañ-karet",
"navigation_bar.filters": "Gerioù kuzhet",
"navigation_bar.follow_requests": "Pedadoù heuliañ",
"navigation_bar.followed_tags": "Followed hashtags",
"navigation_bar.follows_and_followers": "Heuliadennoù ha heulier·ezed·ien",
"navigation_bar.lists": "Listennoù",
"navigation_bar.logout": "Digennaskañ",
@ -540,8 +543,9 @@
"server_banner.server_stats": "Stadegoù ar servijer :",
"sign_in_banner.create_account": "Krouiñ ur gont",
"sign_in_banner.sign_in": "Kevreañ",
"sign_in_banner.text": "Sign in to follow profiles or hashtags, favourite, share and reply to posts, or interact from your account on a different server.",
"sign_in_banner.text": "Sign in to follow profiles or hashtags, favourite, share and reply to posts. You can also interact from your account on a different server.",
"status.admin_account": "Digeriñ etrefas evezherezh evit @{name}",
"status.admin_domain": "Open moderation interface for {domain}",
"status.admin_status": "Digeriñ an toud e-barzh an etrefas evezherezh",
"status.block": "Berzañ @{name}",
"status.bookmark": "Ouzhpennañ d'ar sinedoù",
@ -558,7 +562,7 @@
"status.favourite": "Muiañ-karet",
"status.filter": "Silañ ar c'hannad-mañ",
"status.filtered": "Silet",
"status.hide": "Kuzhat ar c'hannad",
"status.hide": "Hide post",
"status.history.created": "Krouet gant {name} {date}",
"status.history.edited": "Kemmet gant {name} {date}",
"status.load_more": "Kargañ muioc'h",

View file

@ -221,6 +221,7 @@
"empty_column.favourites": "No one has favourited this post yet. When someone does, they will show up here.",
"empty_column.follow_recommendations": "Looks like no suggestions could be generated for you. You can try using search to look for people you might know or explore trending hashtags.",
"empty_column.follow_requests": "You don't have any follow requests yet. When you receive one, it will show up here.",
"empty_column.followed_tags": "You have not followed any hashtags yet. When you do, they will show up here.",
"empty_column.hashtag": "There is nothing in this hashtag yet.",
"empty_column.home": "Your home timeline is empty! Follow more people to fill it up. {suggestions}",
"empty_column.home.suggestions": "See some suggestions",
@ -263,6 +264,7 @@
"follow_request.authorize": "Authorize",
"follow_request.reject": "Reject",
"follow_requests.unlocked_explanation": "Even though your account is not locked, the {domain} staff thought you might want to review follow requests from these accounts manually.",
"followed_tags": "Followed hashtags",
"footer.about": "About",
"footer.directory": "Profiles directory",
"footer.get_app": "Get the app",
@ -379,6 +381,7 @@
"navigation_bar.favourites": "Favourites",
"navigation_bar.filters": "Muted words",
"navigation_bar.follow_requests": "Follow requests",
"navigation_bar.followed_tags": "Followed hashtags",
"navigation_bar.follows_and_followers": "Follows and followers",
"navigation_bar.lists": "Lists",
"navigation_bar.logout": "Logout",
@ -540,8 +543,9 @@
"server_banner.server_stats": "Server stats:",
"sign_in_banner.create_account": "Create account",
"sign_in_banner.sign_in": "Sign in",
"sign_in_banner.text": "Sign in to follow profiles or hashtags, favourite, share and reply to posts, or interact from your account on a different server.",
"sign_in_banner.text": "Sign in to follow profiles or hashtags, favourite, share and reply to posts. You can also interact from your account on a different server.",
"status.admin_account": "Open moderation interface for @{name}",
"status.admin_domain": "Open moderation interface for {domain}",
"status.admin_status": "Open this status in the moderation interface",
"status.block": "Block @{name}",
"status.bookmark": "Bookmark",
@ -558,7 +562,7 @@
"status.favourite": "Favourite",
"status.filter": "Filter this post",
"status.filtered": "Filtered",
"status.hide": "Hide toot",
"status.hide": "Hide post",
"status.history.created": "{name} created {date}",
"status.history.edited": "{name} edited {date}",
"status.load_more": "Load more",

View file

@ -21,13 +21,13 @@
"account.browse_more_on_origin_server": "Navega més en el perfil original",
"account.cancel_follow_request": "Retira la sol·licitud de seguiment",
"account.direct": "Missatge directe a @{name}",
"account.disable_notifications": "No em notifiquis les publicacions de @{name}",
"account.disable_notifications": "Deixa de notificar-me els tuts de @{name}",
"account.domain_blocked": "Domini blocat",
"account.edit_profile": "Edita el perfil",
"account.enable_notifications": "Notifica'm les publicacions de @{name}",
"account.enable_notifications": "Notifica'm els tuts de @{name}",
"account.endorse": "Recomana en el perfil",
"account.featured_tags.last_status_at": "Darrera publicació el {date}",
"account.featured_tags.last_status_never": "No hi ha publicacions",
"account.featured_tags.last_status_never": "No hi ha tuts",
"account.featured_tags.title": "etiquetes destacades de {name}",
"account.follow": "Segueix",
"account.followers": "Seguidors",
@ -57,7 +57,7 @@
"account.requested_follow": "{name} ha demanat de seguir-te",
"account.share": "Comparteix el perfil de @{name}",
"account.show_reblogs": "Mostra els impulsos de @{name}",
"account.statuses_counter": "{count, plural, one {{counter} Publicació} other {{counter} Publicacions}}",
"account.statuses_counter": "{count, plural, one {{counter} Tut} other {{counter} Tuts}}",
"account.unblock": "Desbloca @{name}",
"account.unblock_domain": "Desbloca el domini {domain}",
"account.unblock_short": "Desbloca",
@ -128,8 +128,8 @@
"compose.language.search": "Cerca idiomes...",
"compose_form.direct_message_warning_learn_more": "Més informació",
"compose_form.encryption_warning": "Els tuts a Mastodon no estant xifrats punt a punt. No comparteixis informació sensible mitjançant Mastodon.",
"compose_form.hashtag_warning": "Aquest tut no es mostrarà en cap etiqueta, ja que no està llistat. Només els tuts públics es poden cercar per etiqueta.",
"compose_form.lock_disclaimer": "El teu compte no està {locked}. Tothom pot seguir-te i veure les publicacions de només per a seguidors.",
"compose_form.hashtag_warning": "Aquest tut no es mostrarà en cap etiqueta, ja que no és públic. Només els tuts públics es poden cercar per etiqueta.",
"compose_form.lock_disclaimer": "El teu compte no està {locked}. Tothom pot seguir-te i veure els tuts de només per a seguidors.",
"compose_form.lock_disclaimer.lock": "blocat",
"compose_form.placeholder": "Què et passa pel cap?",
"compose_form.poll.add_option": "Afegeix una opció",
@ -165,7 +165,7 @@
"confirmations.logout.confirm": "Tanca la sessió",
"confirmations.logout.message": "Segur que vols tancar la sessió?",
"confirmations.mute.confirm": "Silencia",
"confirmations.mute.explanation": "Això amagarà les seves publicacions i les que els mencionen, però encara els permetrà veure les teves i seguir-te.",
"confirmations.mute.explanation": "Això amagarà els tuts d'ells i els d'els que els mencionin, però encara els permetrà veure els teus tuts i seguir-te.",
"confirmations.mute.message": "Segur que vols silenciar {name}?",
"confirmations.redraft.confirm": "Elimina i reescriu-la",
"confirmations.redraft.message": "Segur que vols eliminar aquesta publicació i tornar-la a escriure? Es perdran tots els impulsos i els favorits, i les respostes a la publicació original quedaran aïllades.",
@ -185,7 +185,7 @@
"directory.recently_active": "Actius recentment",
"disabled_account_banner.account_settings": "Paràmetres del compte",
"disabled_account_banner.text": "El teu compte {disabledAccount} està desactivat.",
"dismissable_banner.community_timeline": "Aquestes són les publicacions més recents d'usuaris amb el compte a {domain}.",
"dismissable_banner.community_timeline": "Aquests són els tuts públics més recents d'usuaris amb els seus comptes a {domain}.",
"dismissable_banner.dismiss": "Ometre",
"dismissable_banner.explore_links": "Gent d'aquest i d'altres servidors de la xarxa descentralitzada estan comentant ara mateix aquestes notícies.",
"dismissable_banner.explore_statuses": "Aquests tuts d'aquest i altres servidors de la xarxa descentralitzada estan guanyant l'atenció ara mateix en aquest servidor.",
@ -221,6 +221,7 @@
"empty_column.favourites": "Encara no ha marcat ningú aquesta publicació com a preferida. Quan ho faci algú apareixerà aquí.",
"empty_column.follow_recommendations": "Sembla que no s'han pogut generar suggeriments per a tu. Pots provar d'usar la cerca per a trobar persones que vulguis conèixer o explorar les etiquetes en tendència.",
"empty_column.follow_requests": "Encara no tens cap petició de seguiment. Quan en rebis una, apareixerà aquí.",
"empty_column.followed_tags": "Encara no segueixes cap etiqueta. Quan ho facis, es mostraran aquí.",
"empty_column.hashtag": "Encara no hi ha res en aquesta etiqueta.",
"empty_column.home": "La teva línia de temps és buida! Segueix més gent per a emplenar-la. {suggestions}",
"empty_column.home.suggestions": "Mostra alguns suggeriments",
@ -259,10 +260,11 @@
"filter_modal.title.status": "Filtra un tut",
"follow_recommendations.done": "Fet",
"follow_recommendations.heading": "Segueix a la gent de la que t'agradaria veure els seus tuts! Aquí hi ha algunes recomanacions.",
"follow_recommendations.lead": "Les publicacions dels usuaris que segueixes es mostraran en ordre cronològic en la teva línia de temps d'Inici. No tinguis por de cometre errors, pots deixar de seguir-los en qualsevol moment!",
"follow_recommendations.lead": "Els tuts dels usuaris que segueixes es mostraran en ordre cronològic en la teva línia de temps Inici. No tinguis por en cometre errors, pots fàcilment deixar de seguir-los en qualsevol moment!",
"follow_request.authorize": "Autoritza",
"follow_request.reject": "Rebutja",
"follow_requests.unlocked_explanation": "Tot i que el teu compte no està blocat, el personal de {domain} ha pensat que és possible que vulguis revisar manualment les sol·licituds de seguiment daquests comptes.",
"followed_tags": "Etiquetes seguides",
"footer.about": "Quant a",
"footer.directory": "Directori de perfils",
"footer.get_app": "Aconsegueix l'app",
@ -289,7 +291,7 @@
"home.hide_announcements": "Amaga els anuncis",
"home.show_announcements": "Mostra els anuncis",
"interaction_modal.description.favourite": "Amb un compte a Mastodon pots afavorir aquesta publicació, que l'autor sàpiga que t'ha agradat i desar-la per a més endavant.",
"interaction_modal.description.follow": "Amb un compte a Mastodon, pots seguir a {name} per a rebre les seves publicacions en la teva línia de temps d'Inici.",
"interaction_modal.description.follow": "Amb un compte a Mastodon, pots seguir a {name} per a rebre els seus tuts en la teva línia de temps d'Inici.",
"interaction_modal.description.reblog": "Amb un compte a Mastodon, pots impulsar aquesta publicació per a compartir-la amb els teus seguidors.",
"interaction_modal.description.reply": "Amb un compte a Mastodon, pots respondre aquest tut.",
"interaction_modal.on_another_server": "A un altre servidor",
@ -379,6 +381,7 @@
"navigation_bar.favourites": "Preferits",
"navigation_bar.filters": "Paraules silenciades",
"navigation_bar.follow_requests": "Sol·licituds de seguiment",
"navigation_bar.followed_tags": "Etiquetes seguides",
"navigation_bar.follows_and_followers": "Seguint i seguidors",
"navigation_bar.lists": "Llistes",
"navigation_bar.logout": "Tanca la sessió",
@ -418,7 +421,7 @@
"notifications.column_settings.reblog": "Impulsos:",
"notifications.column_settings.show": "Mostra a la columna",
"notifications.column_settings.sound": "Reprodueix so",
"notifications.column_settings.status": "Noves publicacions:",
"notifications.column_settings.status": "Nous tuts:",
"notifications.column_settings.unread_notifications.category": "Notificacions no llegides",
"notifications.column_settings.unread_notifications.highlight": "Destaca les notificacions no llegides",
"notifications.column_settings.update": "Edicions:",
@ -475,7 +478,7 @@
"relative_time.today": "avui",
"reply_indicator.cancel": "Cancel·la",
"report.block": "Bloca",
"report.block_explanation": "No veuràs les seves publicacions. Ell no podran veure les teves ni seguir-te. Podran saber que estan blocats.",
"report.block_explanation": "No veuràs els seus tuts. Ells no podran veure els teus tuts ni et podran seguir. Podran saber que estan blocats.",
"report.categories.other": "Altres",
"report.categories.spam": "Brossa",
"report.categories.violation": "El contingut viola una o més regles del servidor",
@ -488,7 +491,7 @@
"report.forward": "Reenvia a {target}",
"report.forward_hint": "El compte és d'un altre servidor. Vols enviar-hi també una còpia anònima de l'informe?",
"report.mute": "Silencia",
"report.mute_explanation": "No veuràs les seves publicacions. Encara pot seguir-te i veure les teves publicacions, però no sabrà que ha estat silenciat.",
"report.mute_explanation": "No veuràs els seus tuts. Encara poden seguir-te i veure els teus tuts, però no sabran que han estat silenciats.",
"report.next": "Següent",
"report.placeholder": "Comentaris addicionals",
"report.reasons.dislike": "No m'agrada",
@ -510,7 +513,7 @@
"report.thanks.title": "No ho vols veure?",
"report.thanks.title_actionable": "Gràcies per informar, ho investigarem.",
"report.unfollow": "Deixa de seguir @{name}",
"report.unfollow_explanation": "Segueixes aquest compte. Per no veure les seves publicacions a la teva línia de temps d'Inici deixa de seguir-lo.",
"report.unfollow_explanation": "Estàs seguint aquest compte. Per no veure els seus tuts a la teva línia de temps d'Inici, deixa de seguir-lo.",
"report_notification.attached_statuses": "{count, plural, one {{count} tut} other {{count} tuts}} adjunts",
"report_notification.categories.other": "Altres",
"report_notification.categories.spam": "Brossa",
@ -540,8 +543,9 @@
"server_banner.server_stats": "Estadístiques del servidor:",
"sign_in_banner.create_account": "Registra'm",
"sign_in_banner.sign_in": "Inicia sessió",
"sign_in_banner.text": "Inicia la sessió per seguir perfils o etiquetes, afavorir, compartir i respondre a publicacions o interactuar des del teu compte en un servidor diferent.",
"sign_in_banner.text": "Inicia la sessió per a seguir perfils o etiquetes, afavorir, compartir i respondre publicacions. També pots interactuar des del teu compte a un servidor diferent.",
"status.admin_account": "Obre la interfície de moderació per a @{name}",
"status.admin_domain": "Obre la interfície de moderació per a @{domain}",
"status.admin_status": "Obrir aquest tut a la interfície de moderació",
"status.block": "Bloca @{name}",
"status.bookmark": "Marca",

View file

@ -128,7 +128,7 @@
"compose.language.search": "گەڕان بە زمانەکان...",
"compose_form.direct_message_warning_learn_more": "زیاتر فێربه",
"compose_form.encryption_warning": "Posts on Mastodon are not end-to-end encrypted. Do not share any dangerous information over Mastodon.",
"compose_form.hashtag_warning": "ئەم توتە لە ژێر هیچ هاشتاگییەک دا ناکرێت وەک ئەوەی لە لیستەکەدا نەریزراوە. تەنها توتی گشتی دەتوانرێت بە هاشتاگی بگەڕێت.",
"compose_form.hashtag_warning": "This post won't be listed under any hashtag as it is unlisted. Only public posts can be searched by hashtag.",
"compose_form.lock_disclaimer": "هەژمێرەکەی لە حاڵەتی {locked}. هەر کەسێک دەتوانێت شوێنت بکەوێت بۆ پیشاندانی بابەتەکانی تەنها دوایخۆی.",
"compose_form.lock_disclaimer.lock": "قفڵ دراوە",
"compose_form.placeholder": "چی لە مێشکتدایە?",
@ -221,6 +221,7 @@
"empty_column.favourites": "کەس ئەم توتەی دڵخواز نەکردووە،کاتێک کەسێک وا بکات، لێرە دەرئەکەون.",
"empty_column.follow_recommendations": "پێدەچێت هیچ پێشنیارێک بۆ تۆ دروست نەکرێت. دەتوانیت هەوڵبدەیت گەڕان بەکاربهێنیت بۆ گەڕان بەدوای ئەو کەسانەی کە ڕەنگە بیانناسیت یان بەدوای هاشتاگە ڕەوتەکاندا بگەڕێیت.",
"empty_column.follow_requests": "تۆ هێشتا هیچ داواکارییەکی بەدواداچووت نیە. کاتێک یەکێکت بۆ هات، لێرە دەرئەکەویت.",
"empty_column.followed_tags": "You have not followed any hashtags yet. When you do, they will show up here.",
"empty_column.hashtag": "هێشتا هیچ شتێک لەم هاشتاگەدا نییە.",
"empty_column.home": "تایم لاینی ماڵەوەت بەتاڵە! سەردانی {public} بکە یان گەڕان بەکاربێنە بۆ دەستپێکردن و بینینی بەکارهێنەرانی تر.",
"empty_column.home.suggestions": "چەند پێشنیارێک ببینە",
@ -263,6 +264,7 @@
"follow_request.authorize": "ده‌سه‌ڵاتپێدراو",
"follow_request.reject": "ڕەتکردنەوە",
"follow_requests.unlocked_explanation": "هەرچەندە هەژمارەکەت داخراو نییە، ستافی {domain} وا بیریان کردەوە کە لەوانەیە بتانەوێت پێداچوونەوە بە داواکاریەکانی ئەم هەژمارەدا بکەن بە دەستی.",
"followed_tags": "Followed hashtags",
"footer.about": "About",
"footer.directory": "Profiles directory",
"footer.get_app": "Get the app",
@ -379,6 +381,7 @@
"navigation_bar.favourites": "دڵخوازەکان",
"navigation_bar.filters": "وشە کپەکان",
"navigation_bar.follow_requests": "بەدواداچوی داواکاریەکان بکە",
"navigation_bar.followed_tags": "Followed hashtags",
"navigation_bar.follows_and_followers": "شوێنکەوتوو و شوێنکەوتوان",
"navigation_bar.lists": "لیستەکان",
"navigation_bar.logout": "دەرچوون",
@ -540,8 +543,9 @@
"server_banner.server_stats": "دۆخی ڕاژەکار:",
"sign_in_banner.create_account": "هەژمار دروستبکە",
"sign_in_banner.sign_in": "بچۆ ژوورەوە",
"sign_in_banner.text": "چوونەژوورەوە بۆ فۆڵۆوکردنی پڕۆفایلی یان هاشتاگەکان، دڵخوازکردن، هاوبەشکردن و وەڵامدانەوەی پۆستەکان، یان کارلێککردن لە ئەکاونتەکەتەوە لەسەر سێرڤەرێکی جیاواز.",
"sign_in_banner.text": "Sign in to follow profiles or hashtags, favourite, share and reply to posts. You can also interact from your account on a different server.",
"status.admin_account": "کردنەوەی میانڕەوی بەڕێوەبەر بۆ @{name}",
"status.admin_domain": "Open moderation interface for {domain}",
"status.admin_status": "ئەم توتە بکەوە لە ناو ڕووکاری بەڕیوەبەر",
"status.block": "@{name} ئاستەنگ بکە",
"status.bookmark": "نیشانه",
@ -558,7 +562,7 @@
"status.favourite": "دڵخواز",
"status.filter": "ئەم پۆستە فلتەر بکە",
"status.filtered": "پاڵاوتن",
"status.hide": "شاردنەوەی توت",
"status.hide": "Hide post",
"status.history.created": "{name} دروستکراوە لە{date}",
"status.history.edited": "{name} دروستکاریکراوە لە{date}",
"status.load_more": "زیاتر بار بکە",

View file

@ -128,7 +128,7 @@
"compose.language.search": "Search languages...",
"compose_form.direct_message_warning_learn_more": "Amparà di più",
"compose_form.encryption_warning": "Posts on Mastodon are not end-to-end encrypted. Do not share any dangerous information over Mastodon.",
"compose_form.hashtag_warning": "Stu statutu ùn hè \"Micca listatu\" è ùn sarà micca listatu indè e circate da hashtag. Per esse vistu in quesse, u statutu deve esse \"Pubblicu\".",
"compose_form.hashtag_warning": "This post won't be listed under any hashtag as it is unlisted. Only public posts can be searched by hashtag.",
"compose_form.lock_disclaimer": "U vostru contu ùn hè micca {locked}. Tuttu u mondu pò seguitavi è vede i vostri statuti privati.",
"compose_form.lock_disclaimer.lock": "privatu",
"compose_form.placeholder": "À chè pensate?",
@ -221,6 +221,7 @@
"empty_column.favourites": "Nisunu hà aghjuntu stu statutu à i so favuriti. Quandu qualch'unu farà quessa, u so contu sarà mustratu quì.",
"empty_column.follow_recommendations": "Si pare ch'ùn s'hè micca pussutu generà e ricumandazione per voi. Pudete sempre pruvà d'utilizà a ricerca per truvà ghjente chì cunnuscete, o splurà l'hashtag in tindenza.",
"empty_column.follow_requests": "Ùn avete manc'una dumanda d'abbunamentu. Quandu averete una, sarà mustrata quì.",
"empty_column.followed_tags": "You have not followed any hashtags yet. When you do, they will show up here.",
"empty_column.hashtag": "Ùn c'hè ancu nunda quì.",
"empty_column.home": "A vostr'accolta hè viota! Pudete andà nant'à {public} o pruvà a ricerca per truvà parsone da siguità.",
"empty_column.home.suggestions": "Vede qualchì ricumandazione",
@ -263,6 +264,7 @@
"follow_request.authorize": "Auturizà",
"follow_request.reject": "Righjittà",
"follow_requests.unlocked_explanation": "U vostru contu ùn hè micca privatu, ma a squadra d'amministrazione di {domain} pensa chì e dumande d'abbunamentu di questi conti anu bisognu d'esse verificate manualmente.",
"followed_tags": "Followed hashtags",
"footer.about": "About",
"footer.directory": "Profiles directory",
"footer.get_app": "Get the app",
@ -379,6 +381,7 @@
"navigation_bar.favourites": "Favuriti",
"navigation_bar.filters": "Parolle silenzate",
"navigation_bar.follow_requests": "Dumande d'abbunamentu",
"navigation_bar.followed_tags": "Followed hashtags",
"navigation_bar.follows_and_followers": "Abbunati è abbunamenti",
"navigation_bar.lists": "Liste",
"navigation_bar.logout": "Scunnettassi",
@ -540,8 +543,9 @@
"server_banner.server_stats": "Server stats:",
"sign_in_banner.create_account": "Create account",
"sign_in_banner.sign_in": "Sign in",
"sign_in_banner.text": "Sign in to follow profiles or hashtags, favourite, share and reply to posts, or interact from your account on a different server.",
"sign_in_banner.text": "Sign in to follow profiles or hashtags, favourite, share and reply to posts. You can also interact from your account on a different server.",
"status.admin_account": "Apre l'interfaccia di muderazione per @{name}",
"status.admin_domain": "Open moderation interface for {domain}",
"status.admin_status": "Apre stu statutu in l'interfaccia di muderazione",
"status.block": "Bluccà @{name}",
"status.bookmark": "Segnalibru",
@ -558,7 +562,7 @@
"status.favourite": "Aghjunghje à i favuriti",
"status.filter": "Filter this post",
"status.filtered": "Filtratu",
"status.hide": "Hide toot",
"status.hide": "Hide post",
"status.history.created": "{name} created {date}",
"status.history.edited": "{name} edited {date}",
"status.load_more": "Vede di più",

View file

@ -128,7 +128,7 @@
"compose.language.search": "Prohledat jazyky...",
"compose_form.direct_message_warning_learn_more": "Zjistit více",
"compose_form.encryption_warning": "Příspěvky na Mastodonu nejsou end-to-end šifrovány. Nesdílejte přes Mastodon žádné citlivé informace.",
"compose_form.hashtag_warning": "Tento příspěvek nebude zobrazen pod žádným hashtagem, neboť je neveřejný. Pouze veřejné příspěvky mohou být vyhledány podle hashtagu.",
"compose_form.hashtag_warning": "Tento příspěvek nebude zobrazen pod žádným hashtagem, protože není veřejný. Podle hashtagu lze vyhledávat jen veřejné příspěvky.",
"compose_form.lock_disclaimer": "Váš účet není {locked}. Kdokoliv vás může sledovat a vidět vaše příspěvky učené pouze pro sledující.",
"compose_form.lock_disclaimer.lock": "zamčený",
"compose_form.placeholder": "Co se vám honí hlavou?",
@ -221,6 +221,7 @@
"empty_column.favourites": "Tento příspěvek si zatím nikdo neoblíbil. Až to někdo udělá, zobrazí se zde.",
"empty_column.follow_recommendations": "Zdá se, že pro vás nelze vygenerovat žádné návrhy. Můžete zkusit přes vyhledávání nalézt lidi, které znáte, nebo prozkoumat populární hashtagy.",
"empty_column.follow_requests": "Zatím nemáte žádné žádosti o sledování. Až nějakou obdržíte, zobrazí se zde.",
"empty_column.followed_tags": "You have not followed any hashtags yet. When you do, they will show up here.",
"empty_column.hashtag": "Pod tímto hashtagem zde zatím nic není.",
"empty_column.home": "Vaše domovská časová osa je prázdná! Naplňte ji sledováním dalších lidí. {suggestions}",
"empty_column.home.suggestions": "Prohlédněte si návrhy",
@ -263,6 +264,7 @@
"follow_request.authorize": "Autorizovat",
"follow_request.reject": "Zamítnout",
"follow_requests.unlocked_explanation": "Přestože váš účet není zamčený, administrátor {domain} usoudil, že byste mohli chtít tyto žádosti o sledování zkontrolovat ručně.",
"followed_tags": "Followed hashtags",
"footer.about": "O aplikaci",
"footer.directory": "Adresář profilů",
"footer.get_app": "Získat aplikaci",
@ -379,6 +381,7 @@
"navigation_bar.favourites": "Oblíbené",
"navigation_bar.filters": "Skrytá slova",
"navigation_bar.follow_requests": "Žádosti o sledování",
"navigation_bar.followed_tags": "Followed hashtags",
"navigation_bar.follows_and_followers": "Sledovaní a sledující",
"navigation_bar.lists": "Seznamy",
"navigation_bar.logout": "Odhlásit se",
@ -540,8 +543,9 @@
"server_banner.server_stats": "Statistiky serveru:",
"sign_in_banner.create_account": "Vytvořit účet",
"sign_in_banner.sign_in": "Přihlásit se",
"sign_in_banner.text": "Přihlaste se pro sledování profilů nebo hashtagů, oblíbení, sdílení a odpovědí na příspěvky nebo interakci z vašeho účtu na jiném serveru.",
"sign_in_banner.text": "Sign in to follow profiles or hashtags, favourite, share and reply to posts. You can also interact from your account on a different server.",
"status.admin_account": "Otevřít moderátorské rozhraní pro @{name}",
"status.admin_domain": "Otevřít moderátorské rozhraní pro {domain}",
"status.admin_status": "Otevřít tento příspěvek v moderátorském rozhraní",
"status.block": "Blokovat @{name}",
"status.bookmark": "Přidat do záložek",

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