Merge branch 'develop' into feat/scylladb
This commit is contained in:
commit
c2b3e81936
59 changed files with 1591 additions and 411 deletions
|
@ -170,11 +170,11 @@ reservedUsernames: [
|
||||||
# Whether disable HSTS
|
# Whether disable HSTS
|
||||||
#disableHsts: true
|
#disableHsts: true
|
||||||
|
|
||||||
# Number of worker processes
|
# Number of worker processes by type.
|
||||||
#clusterLimit: 1
|
# The sum must not exceed the number of available cores.
|
||||||
|
#clusterLimits:
|
||||||
# Worker only mode
|
# web: 1
|
||||||
#onlyQueueProcessor: 1
|
# queue: 1
|
||||||
|
|
||||||
# Job concurrency per worker
|
# Job concurrency per worker
|
||||||
# deliverJobConcurrency: 128
|
# deliverJobConcurrency: 128
|
||||||
|
|
|
@ -3,7 +3,13 @@
|
||||||
**What does this PR do?** _(Please give us a brief description of what this PR does.)_
|
**What does this PR do?** _(Please give us a brief description of what this PR does.)_
|
||||||
|
|
||||||
**Contribution Guidelines**
|
**Contribution Guidelines**
|
||||||
By submitting this issue, you agree to follow our [Contribution Guidelines](https://git.joinfirefish.org/firefish/firefish/-/blob/develop/CONTRIBUTING.md)
|
By submitting this merge request, you agree to follow our [Contribution Guidelines](https://git.joinfirefish.org/firefish/firefish/-/blob/develop/CONTRIBUTING.md)
|
||||||
- [ ] I agree to follow this project's Contribution Guidelines
|
- [ ] I agree to follow this project's Contribution Guidelines
|
||||||
- [ ] I have made sure to test this pull request
|
- [ ] I have made sure to test this pull request
|
||||||
- [ ] I have made sure to run `pnpm run format` before submitting this pull request
|
- [ ] I have made sure to run `pnpm run format` before submitting this pull request
|
||||||
|
|
||||||
|
If this merge request makes changes to the Firefish API, please update `docs/api-change.md`
|
||||||
|
- [ ] I updated the documentation
|
||||||
|
|
||||||
|
<!-- Uncomment if your merge request has multiple authors -->
|
||||||
|
<!-- Co-authored-by: Name <email@email.com> -->
|
||||||
|
|
617
CHANGELOG.md
617
CHANGELOG.md
|
@ -1,6 +1,6 @@
|
||||||
# Changelog
|
# Changelog
|
||||||
|
|
||||||
## [1.0.4-beta] - 2023-08-02
|
## [1.0.4-beta2] - 2023-09-02
|
||||||
|
|
||||||
### Bug Fixes
|
### Bug Fixes
|
||||||
|
|
||||||
|
@ -16,8 +16,6 @@
|
||||||
|
|
||||||
- Fix: :bug: make admin users page properly direct user cards to about page
|
- Fix: :bug: make admin users page properly direct user cards to about page
|
||||||
|
|
||||||
- Fix?
|
|
||||||
|
|
||||||
- Fix: :globe_with_meridians: copying origin: "remote" -> "origin"
|
- Fix: :globe_with_meridians: copying origin: "remote" -> "origin"
|
||||||
|
|
||||||
- Fix: :lipstick: don't round corners on status images/server icon
|
- Fix: :lipstick: don't round corners on status images/server icon
|
||||||
|
@ -106,23 +104,72 @@ Closes #10581
|
||||||
|
|
||||||
- Fix: :busts_in_silhouette: naskya is fullstack
|
- Fix: :busts_in_silhouette: naskya is fullstack
|
||||||
|
|
||||||
- Fix Japanese locale
|
- Fix: :green_heart: Docker env for CI
|
||||||
|
|
||||||
### Documentation
|
- Fix: :green_heart: docker service alias
|
||||||
|
|
||||||
- Docs: :memo: repo move
|
- Fix: :lipstick: properly style announcement content, add seperator
|
||||||
|
|
||||||
- Docs: :memo: fix link
|
- Fix: generate stream id with timestamp
|
||||||
|
|
||||||
- Docs: :memo: 1.0.3 changelog
|
- Fix: add original id to the stream
|
||||||
|
|
||||||
- Docs: :memo: Add explicit FoundKey commits to CHANGELOG
|
- Fix: :bug: double-slash in proxy url
|
||||||
|
|
||||||
- Docs: :memo: AUR package
|
- Fix: :bug: null host meilisearch
|
||||||
|
|
||||||
- Docs: :memo: remove links to FIREFISH.md
|
- Fix: :globe_with_meridians: i18n key for "confirm"
|
||||||
|
|
||||||
Closes #10601
|
- Fix: :bug: offline html responsive viewport
|
||||||
|
|
||||||
|
- Fix: building re2 in Dockerfile
|
||||||
|
|
||||||
|
- Fix: :children_crossing: make importing emoji packs clearer
|
||||||
|
|
||||||
|
- Fix: updatePerson's Followings.update call not working if no sharedInbox
|
||||||
|
- Fix: :bug: double comma
|
||||||
|
|
||||||
|
- Fix: :adhesive_bandage: add small and center to MFM tags list
|
||||||
|
|
||||||
|
- Fix: veiry url
|
||||||
|
|
||||||
|
- Fix: exclude localhost
|
||||||
|
|
||||||
|
- Fix: exclude ula and lla
|
||||||
|
|
||||||
|
- Fix: remove brackets
|
||||||
|
|
||||||
|
- Fix: 🚑 thumbnail serving
|
||||||
|
|
||||||
|
- Fix: change character limits to allow for long instance domains
|
||||||
|
|
||||||
|
- Fix: :pencil2: "can not" -> "cannot"
|
||||||
|
|
||||||
|
- Fix: :lock: cannot change note visibility
|
||||||
|
|
||||||
|
- Fix: :bug: cannot quote own note
|
||||||
|
|
||||||
|
- Fix: :bug: fix DNS lookup issue
|
||||||
|
|
||||||
|
https://github.com/szmarczak/cacheable-lookup/pull/62
|
||||||
|
|
||||||
|
- Fix: :bug: caching wrong DNS entry when federating with an instance that cannot properly handle inbound IPv6 requests
|
||||||
|
|
||||||
|
- Fix: remove native-utils from migration's dependencies
|
||||||
|
|
||||||
|
- Fix: :recycle: Manifest (icons, name, orientation)
|
||||||
|
|
||||||
|
Closes #10694
|
||||||
|
|
||||||
|
- Fix: :bug: unlock undefined
|
||||||
|
|
||||||
|
- Fix: :bug: more strange unlock calls
|
||||||
|
|
||||||
|
- Fix: :bug: natural PWA orientation
|
||||||
|
|
||||||
|
fixes chrome mobile rotating screen even when device rotation is off
|
||||||
|
|
||||||
|
- Fix: :lipstick: Announcement padding/margins
|
||||||
|
|
||||||
|
|
||||||
### Features
|
### Features
|
||||||
|
@ -149,6 +196,12 @@ ref: https://frfsh.plus.st/notes/9hqswpwiwjaihcgo
|
||||||
Co-authored-by: syuilo <Syuilotan@yahoo.co.jp>
|
Co-authored-by: syuilo <Syuilotan@yahoo.co.jp>
|
||||||
Co-authored-by: Acid Chicken (硫酸鶏) <root@acid-chiken.com>
|
Co-authored-by: Acid Chicken (硫酸鶏) <root@acid-chiken.com>
|
||||||
|
|
||||||
|
- Add migration to fix corrupted stream ids
|
||||||
|
|
||||||
|
- Feat: Fetch total posts of users on create/update
|
||||||
|
|
||||||
|
- Feat: post translation in Traditional Chinese
|
||||||
|
|
||||||
|
|
||||||
### Miscellaneous Tasks
|
### Miscellaneous Tasks
|
||||||
|
|
||||||
|
@ -758,11 +811,491 @@ Translate-URL: https://hosted.weblate.org/projects/firefish/locales/de/
|
||||||
- Chore: Merge branch 'origin/develop' into Weblate.
|
- Chore: Merge branch 'origin/develop' into Weblate.
|
||||||
|
|
||||||
- Chore: update ja-JP.yml
|
- Chore: update ja-JP.yml
|
||||||
|
- Chore: :bookmark: v1.0.4-beta
|
||||||
|
|
||||||
|
- Chore: Translated using Weblate (French)
|
||||||
|
|
||||||
|
Currently translated at 99.7% (1839 of 1844 strings)
|
||||||
|
|
||||||
|
Translation: Firefish/locales
|
||||||
|
Translate-URL: https://hosted.weblate.org/projects/firefish/locales/fr/
|
||||||
|
|
||||||
|
- Chore: Translated using Weblate (Japanese)
|
||||||
|
|
||||||
|
Currently translated at 100.0% (1844 of 1844 strings)
|
||||||
|
|
||||||
|
Translation: Firefish/locales
|
||||||
|
Translate-URL: https://hosted.weblate.org/projects/firefish/locales/ja/
|
||||||
|
|
||||||
|
- Chore: Translated using Weblate (Turkish)
|
||||||
|
|
||||||
|
Currently translated at 99.9% (1843 of 1844 strings)
|
||||||
|
|
||||||
|
Translation: Firefish/locales
|
||||||
|
Translate-URL: https://hosted.weblate.org/projects/firefish/locales/tr/
|
||||||
|
|
||||||
|
- Chore: Translated using Weblate (Ukrainian)
|
||||||
|
|
||||||
|
Currently translated at 100.0% (1844 of 1844 strings)
|
||||||
|
|
||||||
|
Translation: Firefish/locales
|
||||||
|
Translate-URL: https://hosted.weblate.org/projects/firefish/locales/uk/
|
||||||
|
|
||||||
|
- Chore: Translated using Weblate (Bulgarian (bul_BG))
|
||||||
|
|
||||||
|
Currently translated at 20.9% (386 of 1844 strings)
|
||||||
|
|
||||||
|
Translation: Firefish/locales
|
||||||
|
Translate-URL: https://hosted.weblate.org/projects/firefish/locales/bul_BG/
|
||||||
|
|
||||||
|
- Chore: Merge branch 'origin/develop' into Weblate.
|
||||||
|
|
||||||
|
- Chore: Translated using Weblate (French)
|
||||||
|
|
||||||
|
Currently translated at 99.6% (1839 of 1846 strings)
|
||||||
|
|
||||||
|
Translation: Firefish/locales
|
||||||
|
Translate-URL: https://hosted.weblate.org/projects/firefish/locales/fr/
|
||||||
|
|
||||||
|
- Chore: Translated using Weblate (Catalan)
|
||||||
|
|
||||||
|
Currently translated at 100.0% (1846 of 1846 strings)
|
||||||
|
|
||||||
|
Translation: Firefish/locales
|
||||||
|
Translate-URL: https://hosted.weblate.org/projects/firefish/locales/ca/
|
||||||
|
|
||||||
|
- Chore: Translated using Weblate (French)
|
||||||
|
|
||||||
|
Currently translated at 99.6% (1839 of 1846 strings)
|
||||||
|
|
||||||
|
Translation: Firefish/locales
|
||||||
|
Translate-URL: https://hosted.weblate.org/projects/firefish/locales/fr/
|
||||||
|
|
||||||
|
- Chore: Translated using Weblate (French)
|
||||||
|
|
||||||
|
Currently translated at 99.6% (1839 of 1846 strings)
|
||||||
|
|
||||||
|
Translation: Firefish/locales
|
||||||
|
Translate-URL: https://hosted.weblate.org/projects/firefish/locales/fr/
|
||||||
|
|
||||||
|
- Chore: Merge branch 'origin/develop' into Weblate.
|
||||||
|
|
||||||
|
- Chore: Translated using Weblate (Indonesian)
|
||||||
|
|
||||||
|
Currently translated at 100.0% (1846 of 1846 strings)
|
||||||
|
|
||||||
|
Translation: Firefish/locales
|
||||||
|
Translate-URL: https://hosted.weblate.org/projects/firefish/locales/id/
|
||||||
|
|
||||||
|
- Chore: Translated using Weblate (Portuguese (Brazil))
|
||||||
|
|
||||||
|
Currently translated at 12.0% (222 of 1846 strings)
|
||||||
|
|
||||||
|
Translation: Firefish/locales
|
||||||
|
Translate-URL: https://hosted.weblate.org/projects/firefish/locales/pt_BR/
|
||||||
|
|
||||||
|
- Chore: Merge branch 'origin/develop' into Weblate.
|
||||||
|
|
||||||
|
- Chore: Translated using Weblate (German)
|
||||||
|
|
||||||
|
Currently translated at 100.0% (1846 of 1846 strings)
|
||||||
|
|
||||||
|
Translation: Firefish/locales
|
||||||
|
Translate-URL: https://hosted.weblate.org/projects/firefish/locales/de/
|
||||||
|
|
||||||
|
- Chore: Translated using Weblate (German)
|
||||||
|
|
||||||
|
Currently translated at 100.0% (1846 of 1846 strings)
|
||||||
|
|
||||||
|
Translation: Firefish/locales
|
||||||
|
Translate-URL: https://hosted.weblate.org/projects/firefish/locales/de/
|
||||||
|
|
||||||
|
- Chore: Translated using Weblate (French)
|
||||||
|
|
||||||
|
Currently translated at 99.7% (1841 of 1846 strings)
|
||||||
|
|
||||||
|
Translation: Firefish/locales
|
||||||
|
Translate-URL: https://hosted.weblate.org/projects/firefish/locales/fr/
|
||||||
|
|
||||||
|
- Chore: Translated using Weblate (Norwegian Bokmål)
|
||||||
|
|
||||||
|
Currently translated at 32.4% (599 of 1846 strings)
|
||||||
|
|
||||||
|
Translation: Firefish/locales
|
||||||
|
Translate-URL: https://hosted.weblate.org/projects/firefish/locales/nb_NO/
|
||||||
|
|
||||||
|
- Chore: Translated using Weblate (Ukrainian)
|
||||||
|
|
||||||
|
Currently translated at 100.0% (1846 of 1846 strings)
|
||||||
|
|
||||||
|
Translation: Firefish/locales
|
||||||
|
Translate-URL: https://hosted.weblate.org/projects/firefish/locales/uk/
|
||||||
|
|
||||||
|
- Chore: Translated using Weblate (Vietnamese)
|
||||||
|
|
||||||
|
Currently translated at 93.1% (1720 of 1846 strings)
|
||||||
|
|
||||||
|
Translation: Firefish/locales
|
||||||
|
Translate-URL: https://hosted.weblate.org/projects/firefish/locales/vi/
|
||||||
|
|
||||||
|
- Chore: Merge branch 'origin/develop' into Weblate.
|
||||||
|
|
||||||
|
- Chore: up pnpm to 8.6.11
|
||||||
|
|
||||||
|
- Chore: :arrow_up: up deps
|
||||||
|
|
||||||
|
- Chore: :art: format
|
||||||
|
|
||||||
|
- Chore: Translated using Weblate (Norwegian Bokmål)
|
||||||
|
|
||||||
|
Currently translated at 36.1% (668 of 1846 strings)
|
||||||
|
|
||||||
|
Translation: Firefish/locales
|
||||||
|
Translate-URL: https://hosted.weblate.org/projects/firefish/locales/nb_NO/
|
||||||
|
|
||||||
|
- Chore: Merge branch 'origin/develop' into Weblate.
|
||||||
|
|
||||||
|
- Chore: remove unused items
|
||||||
|
|
||||||
|
- Chore: :art: format
|
||||||
|
|
||||||
|
- Chore: Translated using Weblate (Spanish)
|
||||||
|
|
||||||
|
Currently translated at 100.0% (1846 of 1846 strings)
|
||||||
|
|
||||||
|
Translation: Firefish/locales
|
||||||
|
Translate-URL: https://hosted.weblate.org/projects/firefish/locales/es/
|
||||||
|
|
||||||
|
- Chore: Translated using Weblate (French)
|
||||||
|
|
||||||
|
Currently translated at 100.0% (1846 of 1846 strings)
|
||||||
|
|
||||||
|
Translation: Firefish/locales
|
||||||
|
Translate-URL: https://hosted.weblate.org/projects/firefish/locales/fr/
|
||||||
|
|
||||||
|
- Chore: Translated using Weblate (French)
|
||||||
|
|
||||||
|
Currently translated at 100.0% (1846 of 1846 strings)
|
||||||
|
|
||||||
|
Translation: Firefish/locales
|
||||||
|
Translate-URL: https://hosted.weblate.org/projects/firefish/locales/fr/
|
||||||
|
|
||||||
|
- Chore: Merge branch 'origin/develop' into Weblate.
|
||||||
|
|
||||||
|
- Chore: Translated using Weblate (Catalan)
|
||||||
|
|
||||||
|
Currently translated at 100.0% (1850 of 1850 strings)
|
||||||
|
|
||||||
|
Translation: Firefish/locales
|
||||||
|
Translate-URL: https://hosted.weblate.org/projects/firefish/locales/ca/
|
||||||
|
|
||||||
|
- Chore: Translated using Weblate (French)
|
||||||
|
|
||||||
|
Currently translated at 99.5% (1842 of 1850 strings)
|
||||||
|
|
||||||
|
Translation: Firefish/locales
|
||||||
|
Translate-URL: https://hosted.weblate.org/projects/firefish/locales/fr/
|
||||||
|
|
||||||
|
- Chore: Translated using Weblate (Indonesian)
|
||||||
|
|
||||||
|
Currently translated at 100.0% (1850 of 1850 strings)
|
||||||
|
|
||||||
|
Translation: Firefish/locales
|
||||||
|
Translate-URL: https://hosted.weblate.org/projects/firefish/locales/id/
|
||||||
|
|
||||||
|
- Chore: Translated using Weblate (Japanese)
|
||||||
|
|
||||||
|
Currently translated at 100.0% (1850 of 1850 strings)
|
||||||
|
|
||||||
|
Translation: Firefish/locales
|
||||||
|
Translate-URL: https://hosted.weblate.org/projects/firefish/locales/ja/
|
||||||
|
|
||||||
|
- Chore: Translated using Weblate (Russian)
|
||||||
|
|
||||||
|
Currently translated at 100.0% (1850 of 1850 strings)
|
||||||
|
|
||||||
|
Translation: Firefish/locales
|
||||||
|
Translate-URL: https://hosted.weblate.org/projects/firefish/locales/ru/
|
||||||
|
|
||||||
|
- Chore: Translated using Weblate (Bulgarian (bul_BG))
|
||||||
|
|
||||||
|
Currently translated at 23.4% (433 of 1850 strings)
|
||||||
|
|
||||||
|
Translation: Firefish/locales
|
||||||
|
Translate-URL: https://hosted.weblate.org/projects/firefish/locales/bul_BG/
|
||||||
|
|
||||||
|
- Chore: Merge branch 'origin/develop' into Weblate.
|
||||||
|
|
||||||
|
- Chore: codeberg → gitlab
|
||||||
|
|
||||||
|
- Chore: Translated using Weblate (German)
|
||||||
|
|
||||||
|
Currently translated at 100.0% (1850 of 1850 strings)
|
||||||
|
|
||||||
|
Translation: Firefish/locales
|
||||||
|
Translate-URL: https://hosted.weblate.org/projects/firefish/locales/de/
|
||||||
|
|
||||||
|
- Chore: Merge branch 'origin/develop' into Weblate.
|
||||||
|
|
||||||
|
- Chore: Translated using Weblate (Japanese)
|
||||||
|
|
||||||
|
Currently translated at 100.0% (1850 of 1850 strings)
|
||||||
|
|
||||||
|
Translation: Firefish/locales
|
||||||
|
Translate-URL: https://hosted.weblate.org/projects/firefish/locales/ja/
|
||||||
|
|
||||||
|
- Chore: Translated using Weblate (German)
|
||||||
|
|
||||||
|
Currently translated at 100.0% (1850 of 1850 strings)
|
||||||
|
|
||||||
|
Translation: Firefish/locales
|
||||||
|
Translate-URL: https://hosted.weblate.org/projects/firefish/locales/de/
|
||||||
|
|
||||||
|
- Chore: Translated using Weblate (Ukrainian)
|
||||||
|
|
||||||
|
Currently translated at 100.0% (1850 of 1850 strings)
|
||||||
|
|
||||||
|
Translation: Firefish/locales
|
||||||
|
Translate-URL: https://hosted.weblate.org/projects/firefish/locales/uk/
|
||||||
|
|
||||||
|
- Chore: Merge branch 'origin/develop' into Weblate.
|
||||||
|
|
||||||
|
- Chore: Translated using Weblate (Japanese)
|
||||||
|
|
||||||
|
Currently translated at 100.0% (1850 of 1850 strings)
|
||||||
|
|
||||||
|
Translation: Firefish/locales
|
||||||
|
Translate-URL: https://hosted.weblate.org/projects/firefish/locales/ja/
|
||||||
|
|
||||||
|
- Chore: Translated using Weblate (Japanese (Kansai))
|
||||||
|
|
||||||
|
Currently translated at 69.1% (1280 of 1850 strings)
|
||||||
|
|
||||||
|
Translation: Firefish/locales
|
||||||
|
Translate-URL: https://hosted.weblate.org/projects/firefish/locales/ja_KS/
|
||||||
|
|
||||||
|
- Chore: Merge branch 'origin/develop' into Weblate.
|
||||||
|
|
||||||
|
- Chore: Translated using Weblate (Catalan)
|
||||||
|
|
||||||
|
Currently translated at 100.0% (1850 of 1850 strings)
|
||||||
|
|
||||||
|
Translation: Firefish/locales
|
||||||
|
Translate-URL: https://hosted.weblate.org/projects/firefish/locales/ca/
|
||||||
|
|
||||||
|
- Chore: Translated using Weblate (German)
|
||||||
|
|
||||||
|
Currently translated at 100.0% (1850 of 1850 strings)
|
||||||
|
|
||||||
|
Translation: Firefish/locales
|
||||||
|
Translate-URL: https://hosted.weblate.org/projects/firefish/locales/de/
|
||||||
|
|
||||||
|
- Chore: Translated using Weblate (German)
|
||||||
|
|
||||||
|
Currently translated at 100.0% (1850 of 1850 strings)
|
||||||
|
|
||||||
|
Translation: Firefish/locales
|
||||||
|
Translate-URL: https://hosted.weblate.org/projects/firefish/locales/de/
|
||||||
|
|
||||||
|
- Chore: Translated using Weblate (French)
|
||||||
|
|
||||||
|
Currently translated at 99.6% (1844 of 1850 strings)
|
||||||
|
|
||||||
|
Translation: Firefish/locales
|
||||||
|
Translate-URL: https://hosted.weblate.org/projects/firefish/locales/fr/
|
||||||
|
|
||||||
|
- Chore: Translated using Weblate (Indonesian)
|
||||||
|
|
||||||
|
Currently translated at 100.0% (1850 of 1850 strings)
|
||||||
|
|
||||||
|
Translation: Firefish/locales
|
||||||
|
Translate-URL: https://hosted.weblate.org/projects/firefish/locales/id/
|
||||||
|
|
||||||
|
- Chore: Translated using Weblate (Japanese)
|
||||||
|
|
||||||
|
Currently translated at 100.0% (1850 of 1850 strings)
|
||||||
|
|
||||||
|
Translation: Firefish/locales
|
||||||
|
Translate-URL: https://hosted.weblate.org/projects/firefish/locales/ja/
|
||||||
|
|
||||||
|
- Chore: Translated using Weblate (Japanese (Kansai))
|
||||||
|
|
||||||
|
Currently translated at 69.9% (1294 of 1850 strings)
|
||||||
|
|
||||||
|
Translation: Firefish/locales
|
||||||
|
Translate-URL: https://hosted.weblate.org/projects/firefish/locales/ja_KS/
|
||||||
|
|
||||||
|
- Chore: Translated using Weblate (Norwegian Bokmål)
|
||||||
|
|
||||||
|
Currently translated at 54.0% (1000 of 1850 strings)
|
||||||
|
|
||||||
|
Translation: Firefish/locales
|
||||||
|
Translate-URL: https://hosted.weblate.org/projects/firefish/locales/nb_NO/
|
||||||
|
|
||||||
|
- Chore: Translated using Weblate (Bulgarian (bul_BG))
|
||||||
|
|
||||||
|
Currently translated at 23.5% (435 of 1850 strings)
|
||||||
|
|
||||||
|
Translation: Firefish/locales
|
||||||
|
Translate-URL: https://hosted.weblate.org/projects/firefish/locales/bul_BG/
|
||||||
|
|
||||||
|
- Chore: Merge branch 'origin/develop' into Weblate.
|
||||||
|
|
||||||
|
- Chore: Translated using Weblate (Italian)
|
||||||
|
|
||||||
|
Currently translated at 88.4% (1637 of 1850 strings)
|
||||||
|
|
||||||
|
Translation: Firefish/locales
|
||||||
|
Translate-URL: https://hosted.weblate.org/projects/firefish/locales/it/
|
||||||
|
|
||||||
|
- Chore: Translated using Weblate (Chinese (Simplified))
|
||||||
|
|
||||||
|
Currently translated at 100.0% (1850 of 1850 strings)
|
||||||
|
|
||||||
|
Translation: Firefish/locales
|
||||||
|
Translate-URL: https://hosted.weblate.org/projects/firefish/locales/zh_Hans/
|
||||||
|
|
||||||
|
- Chore: Merge branch 'origin/develop' into Weblate.
|
||||||
|
|
||||||
|
- Chore: :art: format
|
||||||
|
|
||||||
|
- Chore: Translated using Weblate (Italian)
|
||||||
|
|
||||||
|
Currently translated at 98.5% (1824 of 1850 strings)
|
||||||
|
|
||||||
|
Translation: Firefish/locales
|
||||||
|
Translate-URL: https://hosted.weblate.org/projects/firefish/locales/it/
|
||||||
|
|
||||||
|
- Chore: Translated using Weblate (Russian)
|
||||||
|
|
||||||
|
Currently translated at 100.0% (1850 of 1850 strings)
|
||||||
|
|
||||||
|
Translation: Firefish/locales
|
||||||
|
Translate-URL: https://hosted.weblate.org/projects/firefish/locales/ru/
|
||||||
|
|
||||||
|
- Chore: Translated using Weblate (Korean)
|
||||||
|
|
||||||
|
Currently translated at 100.0% (1850 of 1850 strings)
|
||||||
|
|
||||||
|
Translation: Firefish/locales
|
||||||
|
Translate-URL: https://hosted.weblate.org/projects/firefish/locales/ko/
|
||||||
|
|
||||||
|
- Chore: Merge branch 'origin/develop' into Weblate.
|
||||||
|
|
||||||
|
- Update stop words
|
||||||
|
- Chore: :art: format
|
||||||
|
|
||||||
|
- Chore: Translated using Weblate (Indonesian)
|
||||||
|
|
||||||
|
Currently translated at 100.0% (1850 of 1850 strings)
|
||||||
|
|
||||||
|
Translation: Firefish/locales
|
||||||
|
Translate-URL: https://hosted.weblate.org/projects/firefish/locales/id/
|
||||||
|
|
||||||
|
- Chore: Translated using Weblate (Italian)
|
||||||
|
|
||||||
|
Currently translated at 98.9% (1830 of 1850 strings)
|
||||||
|
|
||||||
|
Translation: Firefish/locales
|
||||||
|
Translate-URL: https://hosted.weblate.org/projects/firefish/locales/it/
|
||||||
|
|
||||||
|
- Chore: Merge branch 'origin/develop' into Weblate.
|
||||||
|
|
||||||
|
- Chore: Translated using Weblate (French)
|
||||||
|
|
||||||
|
Currently translated at 99.6% (1843 of 1850 strings)
|
||||||
|
|
||||||
|
Translation: Firefish/locales
|
||||||
|
Translate-URL: https://hosted.weblate.org/projects/firefish/locales/fr/
|
||||||
|
|
||||||
|
- Chore: Translated using Weblate (Italian)
|
||||||
|
|
||||||
|
Currently translated at 98.9% (1831 of 1850 strings)
|
||||||
|
|
||||||
|
Translation: Firefish/locales
|
||||||
|
Translate-URL: https://hosted.weblate.org/projects/firefish/locales/it/
|
||||||
|
|
||||||
|
- Chore: Translated using Weblate (Korean)
|
||||||
|
|
||||||
|
Currently translated at 100.0% (1850 of 1850 strings)
|
||||||
|
|
||||||
|
Translation: Firefish/locales
|
||||||
|
Translate-URL: https://hosted.weblate.org/projects/firefish/locales/ko/
|
||||||
|
|
||||||
|
- Chore: Translated using Weblate (Thai)
|
||||||
|
|
||||||
|
Currently translated at 56.5% (1047 of 1850 strings)
|
||||||
|
|
||||||
|
Translation: Firefish/locales
|
||||||
|
Translate-URL: https://hosted.weblate.org/projects/firefish/locales/th/
|
||||||
|
|
||||||
|
- Chore: Merge branch 'origin/develop' into Weblate.
|
||||||
|
|
||||||
|
- Chore: :technologist: More recommended VSCode extensions
|
||||||
|
|
||||||
|
Iconify to preview Phosphor icons, Conventional Commits for commit style
|
||||||
|
|
||||||
|
- Chore: :technologist: More recommended VSCode extensions
|
||||||
|
|
||||||
|
Docker, GitLab Workflow, JSON5, Prettier, YAML, and Pretty TS Errors
|
||||||
|
|
||||||
|
- Chore: Translated using Weblate (Thai)
|
||||||
|
|
||||||
|
Currently translated at 58.6% (1085 of 1850 strings)
|
||||||
|
|
||||||
|
Translation: Firefish/locales
|
||||||
|
Translate-URL: https://hosted.weblate.org/projects/firefish/locales/th/
|
||||||
|
|
||||||
|
- Chore: Merge branch 'origin/develop' into Weblate.
|
||||||
|
|
||||||
|
- Chore, refactor: remove unused, fix some type errors (client/src/pages)
|
||||||
|
|
||||||
|
- Chore: :rotating_light: lint
|
||||||
|
|
||||||
|
- Chore: :wrench: linting config
|
||||||
|
|
||||||
|
- Chore: 🚨 lint megalodon
|
||||||
|
|
||||||
|
- Chore: :arrow_up: up deps (properly)
|
||||||
|
|
||||||
|
- Chore: :hammer: build greet js -> sh
|
||||||
|
|
||||||
|
- Chore: :art: script format
|
||||||
|
|
||||||
|
- Chore: Translated using Weblate (Italian)
|
||||||
|
|
||||||
|
Currently translated at 99.5% (1841 of 1850 strings)
|
||||||
|
|
||||||
|
Translation: Firefish/locales
|
||||||
|
Translate-URL: https://hosted.weblate.org/projects/firefish/locales/it/
|
||||||
|
|
||||||
|
- Chore: Translated using Weblate (Portuguese (Portugal))
|
||||||
|
|
||||||
|
Currently translated at 33.4% (619 of 1850 strings)
|
||||||
|
|
||||||
|
Translation: Firefish/locales
|
||||||
|
Translate-URL: https://hosted.weblate.org/projects/firefish/locales/pt_PT/
|
||||||
|
|
||||||
|
- Chore: Merge branch 'origin/develop' into Weblate.
|
||||||
|
|
||||||
|
- Chore: Translated using Weblate (Portuguese (Portugal))
|
||||||
|
|
||||||
|
Currently translated at 33.4% (619 of 1850 strings)
|
||||||
|
|
||||||
|
Translation: Firefish/locales
|
||||||
|
Translate-URL: https://hosted.weblate.org/projects/firefish/locales/pt_PT/
|
||||||
|
|
||||||
|
- Chore: Merge branch 'origin/develop' into Weblate.
|
||||||
|
|
||||||
|
|
||||||
### Performance
|
### Performance
|
||||||
|
|
||||||
- Perf: :zap: featured posts query limit
|
- Perf: :zap: featured posts query limit
|
||||||
|
|
||||||
|
- Perf: :zap: delete transformedOptions key -> assign undefined with key literal
|
||||||
|
|
||||||
|
- Perf: :zap: seperate web and queue workers
|
||||||
|
|
||||||
|
|
||||||
### Refactor
|
### Refactor
|
||||||
|
|
||||||
|
@ -778,6 +1311,26 @@ ref: https://git.joinfirefish.org/firefish/firefish/-/issues/10527#note_230
|
||||||
|
|
||||||
- Refactor: :busts_in_silhouette: Add original Misskey contributors
|
- Refactor: :busts_in_silhouette: Add original Misskey contributors
|
||||||
|
|
||||||
|
- Refactor: :recycle: better offline page
|
||||||
|
|
||||||
|
- Refactor: :children_crossing: only index public posts
|
||||||
|
|
||||||
|
- Refactor: :coffin: remove old woodpecker scripts
|
||||||
|
|
||||||
|
- Refactor: :recycle: No Vue Reactivity
|
||||||
|
|
||||||
|
Performed with https://github.com/edison1105/drop-reactivity-transform , Reactivity Transform was an experimental feature and has now been deprecated.
|
||||||
|
|
||||||
|
- Refactor: remove regex
|
||||||
|
|
||||||
|
- Refactor: :recycle: types in AP kernel
|
||||||
|
|
||||||
|
This file seriously needs to be refactored properly...
|
||||||
|
|
||||||
|
- Refactor: :egg: new ansi art
|
||||||
|
|
||||||
|
- Refactor: :egg: ansi art for master.ts
|
||||||
|
|
||||||
|
|
||||||
### Styling
|
### Styling
|
||||||
|
|
||||||
|
@ -797,6 +1350,20 @@ ref: https://git.joinfirefish.org/firefish/firefish/-/issues/10527#note_230
|
||||||
|
|
||||||
### Miscellaneous Tasks
|
### Miscellaneous Tasks
|
||||||
|
|
||||||
|
- Chore: Translated using Weblate (Russian)
|
||||||
|
|
||||||
|
Currently translated at 99.4% (1826 of 1836 strings)
|
||||||
|
|
||||||
|
Translation: Calckey/locales
|
||||||
|
Translate-URL: https://hosted.weblate.org/projects/firefish/locales/ru/
|
||||||
|
|
||||||
|
- Chore: Translated using Weblate (Russian)
|
||||||
|
|
||||||
|
Currently translated at 99.4% (1826 of 1836 strings)
|
||||||
|
|
||||||
|
Translation: Calckey/locales
|
||||||
|
Translate-URL: https://hosted.weblate.org/projects/firefish/locales/ru/
|
||||||
|
|
||||||
- Chore: Translated using Weblate (Japanese)
|
- Chore: Translated using Weblate (Japanese)
|
||||||
|
|
||||||
Currently translated at 100.0% (1836 of 1836 strings)
|
Currently translated at 100.0% (1836 of 1836 strings)
|
||||||
|
@ -891,7 +1458,7 @@ Translate-URL: https://hosted.weblate.org/projects/firefish/locales/ko/
|
||||||
|
|
||||||
### Refactor
|
### Refactor
|
||||||
|
|
||||||
- Refactor: :recycle: create-drive-file endpoint
|
- Refactor: :recycle: create drive file endpoint
|
||||||
|
|
||||||
Adjusts ratelimit to 250 files every 10 minutes, fixes error text, fixes reused variable name.
|
Adjusts ratelimit to 250 files every 10 minutes, fixes error text, fixes reused variable name.
|
||||||
|
|
||||||
|
@ -901,22 +1468,6 @@ Adjusts ratelimit to 250 files every 10 minutes, fixes error text, fixes reused
|
||||||
|
|
||||||
for real this time
|
for real this time
|
||||||
|
|
||||||
### Miscellaneous Tasks
|
|
||||||
|
|
||||||
- Chore: Translated using Weblate (Russian)
|
|
||||||
|
|
||||||
Currently translated at 99.4% (1826 of 1836 strings)
|
|
||||||
|
|
||||||
Translation: Calckey/locales
|
|
||||||
Translate-URL: https://hosted.weblate.org/projects/firefish/locales/ru/
|
|
||||||
|
|
||||||
- Chore: Translated using Weblate (Russian)
|
|
||||||
|
|
||||||
Currently translated at 99.4% (1826 of 1836 strings)
|
|
||||||
|
|
||||||
Translation: Calckey/locales
|
|
||||||
Translate-URL: https://hosted.weblate.org/projects/firefish/locales/ru/
|
|
||||||
|
|
||||||
|
|
||||||
## [1.0.0] - 2023-07-19
|
## [1.0.0] - 2023-07-19
|
||||||
|
|
||||||
|
@ -8900,7 +9451,7 @@ Resolve #7540
|
||||||
* truncate user information if it is too long
|
* truncate user information if it is too long
|
||||||
|
|
||||||
Some AP software allows for user names or summaries to be very long.
|
Some AP software allows for user names or summaries to be very long.
|
||||||
Misskey cannot handle this and the profile page cannot be opened and
|
Misskey can not handle this and the profile page can not be opened and
|
||||||
no activities from such users can be seen.
|
no activities from such users can be seen.
|
||||||
|
|
||||||
Instead, the user name and summary are cut off after the maximum length
|
Instead, the user name and summary are cut off after the maximum length
|
||||||
|
@ -9902,7 +10453,7 @@ This duplicated processing can be avoided by querying the database directly.
|
||||||
|
|
||||||
Misskey will only use ActivityPub follow requests for users that are local
|
Misskey will only use ActivityPub follow requests for users that are local
|
||||||
and are requesting to follow a remote user. This check is to ensure that
|
and are requesting to follow a remote user. This check is to ensure that
|
||||||
this endpoint cannot be used by other services or instances.
|
this endpoint can not be used by other services or instances.
|
||||||
|
|
||||||
* fix: missing import
|
* fix: missing import
|
||||||
|
|
||||||
|
@ -14921,7 +15472,7 @@ Defaults for `local` and `withFiles` are based on the behaviour of the endpoint.
|
||||||
|
|
||||||
* fix: define required fields
|
* fix: define required fields
|
||||||
|
|
||||||
- `notes/create`: the default for `text` has been removed because ajv cannot handle
|
- `notes/create`: the default for `text` has been removed because ajv can not handle
|
||||||
`default` inside of `anyOf`, see
|
`default` inside of `anyOf`, see
|
||||||
https://ajv.js.org/guide/modifying-data.html#assigning-defaults
|
https://ajv.js.org/guide/modifying-data.html#assigning-defaults
|
||||||
and the default value cannot be `null` if text is `nullable: false` in the `anyOf`
|
and the default value cannot be `null` if text is `nullable: false` in the `anyOf`
|
||||||
|
@ -15551,7 +16102,7 @@ unnecessarily loaded.
|
||||||
* remove duplicate null check
|
* remove duplicate null check
|
||||||
|
|
||||||
The variable is checked for null in the lines above and the function
|
The variable is checked for null in the lines above and the function
|
||||||
returns if so. Therefore, it cannot be null at this point.
|
returns if so. Therefore, it can not be null at this point.
|
||||||
|
|
||||||
* simplify `getJsonSchema`
|
* simplify `getJsonSchema`
|
||||||
|
|
||||||
|
|
|
@ -23,31 +23,32 @@ Before creating an issue, please check the following:
|
||||||
> Do not close issues that are about to be resolved. It should remain open until a commit that actually resolves it is merged.
|
> Do not close issues that are about to be resolved. It should remain open until a commit that actually resolves it is merged.
|
||||||
|
|
||||||
## Before implementation
|
## Before implementation
|
||||||
When you want to add a feature or fix a bug, **first have the design and policy reviewed in an Issue** (if it is not there, please make one). Without this step, there is a high possibility that the PR will not be merged even if it is implemented.
|
When you want to add a feature or fix a bug, **first have the design and policy reviewed in an Issue** (if it is not there, please make one). Without this step, there is a high possibility that the MR will not be merged even if it is implemented.
|
||||||
|
|
||||||
At this point, you also need to clarify the goals of the PR you will create, and make sure that the other members of the team are aware of them.
|
At this point, you also need to clarify the goals of the MR you will create, and make sure that the other members of the team are aware of them.
|
||||||
PRs that do not have a clear set of do's and don'ts tend to be bloated and difficult to review.
|
MRs that do not have a clear set of do's and don'ts tend to be bloated and difficult to review.
|
||||||
|
|
||||||
Also, when you start implementation, assign yourself to the Issue (if you cannot do it yourself, ask another member to assign you). By expressing your intention to work the Issue, you can prevent conflicts in the work.
|
Also, when you start implementation, assign yourself to the Issue (if you cannot do it yourself, ask another member to assign you). By expressing your intention to work the Issue, you can prevent conflicts in the work.
|
||||||
|
|
||||||
## Well-known branches
|
## Well-known branches
|
||||||
- The **`main`** branch is tracking the latest release and used for production purposes.
|
- The **`main`** branch is tracking the latest release and used for production purposes.
|
||||||
- The **`develop`** branch is where we work for the next release.
|
- The **`develop`** branch is where we work for the next release.
|
||||||
- When you create a PR, basically target it to this branch. **But create a different branch**
|
- When you create a MR, basically target it to this branch. **But create a different branch**
|
||||||
- The **`l10n_develop`** branch is reserved for localization management.
|
- The **`l10n_develop`** branch is reserved for localization management.
|
||||||
- **`feature/*`** branches are reserved for the development of a specific feature
|
- **`feature/*`** branches are reserved for the development of a specific feature
|
||||||
|
|
||||||
## Creating a PR
|
## Creating a merge request (MR)
|
||||||
Thank you for your PR! Before creating a PR, please check the following:
|
Thank you for your MR! Before creating a MR, please check the following:
|
||||||
- If possible, prefix the title with a keyword that identifies the type of this PR, as shown below.
|
- If possible, prefix the title with a keyword that identifies the type of this MR, as shown below.
|
||||||
- `fix` / `refactor` / `feat` / `enhance` / `perf` / `chore` etc. You are also welcome to use gitmoji. This is important as we use these to A) easier read the git history and B) generate our changelog. Without propper prefixing it is possible that your PR is rejected.
|
- `fix` / `refactor` / `feat` / `enhance` / `perf` / `chore` etc. You are also welcome to use gitmoji. This is important as we use these to A) easier read the git history and B) generate our changelog. Without propper prefixing it is possible that your MR is rejected.
|
||||||
- Also, make sure that the granularity of this PR is appropriate. Please do not include more than one type of change or interest in a single PR.
|
- Also, make sure that the granularity of this MR is appropriate. Please do not include more than one type of change or interest in a single MR.
|
||||||
- If there is an Issue which will be resolved by this PR, please include a reference to the Issue in the text. Good examples include `Closing: #21` or `Resolves: #21`
|
- If there is an Issue which will be resolved by this MR, please include a reference to the Issue in the text. Good examples include `Closing: #21` or `Resolves: #21`
|
||||||
- Check if there are any documents that need to be created or updated due to this change.
|
- Check if there are any documents that need to be created or updated due to this change.
|
||||||
|
- For example, you need to update `docs/api-change.md` if the MR includes API changes.
|
||||||
- If you have added a feature or fixed a bug, please add a test case if possible.
|
- If you have added a feature or fixed a bug, please add a test case if possible.
|
||||||
- Please make sure that formatting, tests and Lint are passed in advance.
|
- Please make sure that formatting, tests and Lint are passed in advance.
|
||||||
- You can run it with `pnpm run format`, `pnpm run test` and `pnpm run lint`. [See more info](#testing)
|
- You can run it with `pnpm run format`, `pnpm run test` and `pnpm run lint`. [See more info](#testing)
|
||||||
- If this PR includes UI changes, please attach a screenshot in the text.
|
- If this MR includes UI changes, please attach a screenshot in the text.
|
||||||
|
|
||||||
Thanks for your cooperation 🤗
|
Thanks for your cooperation 🤗
|
||||||
|
|
||||||
|
@ -56,12 +57,12 @@ Be willing to comment on the good points and not just the things you want fixed
|
||||||
|
|
||||||
### Review perspective
|
### Review perspective
|
||||||
- Scope
|
- Scope
|
||||||
- Are the goals of the PR clear?
|
- Are the goals of the MR clear?
|
||||||
- Is the granularity of the PR appropriate?
|
- Is the granularity of the MR appropriate?
|
||||||
- Security
|
- Security
|
||||||
- Does merging this PR create a vulnerability?
|
- Does merging this MR create a vulnerability?
|
||||||
- Performance
|
- Performance
|
||||||
- Will merging this PR cause unexpected performance degradation?
|
- Will merging this MR cause unexpected performance degradation?
|
||||||
- Is there a more efficient way?
|
- Is there a more efficient way?
|
||||||
- Testing
|
- Testing
|
||||||
- Does the test ensure the expected behavior?
|
- Does the test ensure the expected behavior?
|
||||||
|
@ -69,12 +70,14 @@ Be willing to comment on the good points and not just the things you want fixed
|
||||||
- Does it check for anomalies?
|
- Does it check for anomalies?
|
||||||
|
|
||||||
## Deploy (SOON)
|
## Deploy (SOON)
|
||||||
The `/deploy` command by issue comment can be used to deploy the contents of a PR to the preview environment.
|
The `/deploy` command by issue comment can be used to deploy the contents of a MR to the preview environment.
|
||||||
```
|
```
|
||||||
/deploy sha=<commit hash>
|
/deploy sha=<commit hash>
|
||||||
```
|
```
|
||||||
An actual domain will be assigned so you can test the federation.
|
An actual domain will be assigned so you can test the federation.
|
||||||
|
|
||||||
|
# THE FOLLOWING IS OUTDATED:
|
||||||
|
|
||||||
## Merge
|
## Merge
|
||||||
|
|
||||||
## Release
|
## Release
|
||||||
|
@ -95,9 +98,6 @@ During development, it is useful to use the `yarn dev` command.
|
||||||
This command monitors the server-side and client-side source files and automatically builds them if they are modified.
|
This command monitors the server-side and client-side source files and automatically builds them if they are modified.
|
||||||
In addition, it will also automatically start the Misskey server process.
|
In addition, it will also automatically start the Misskey server process.
|
||||||
|
|
||||||
|
|
||||||
# THE FOLLOWING IS OUTDATED:
|
|
||||||
|
|
||||||
## Testing
|
## Testing
|
||||||
- Test codes are located in [`/test`](/test).
|
- Test codes are located in [`/test`](/test).
|
||||||
|
|
||||||
|
|
|
@ -209,6 +209,7 @@ Please don't use ElasticSearch unless you already have an ElasticSearch setup an
|
||||||
- To add custom error images, place them in the `./custom/assets/badges` directory, replacing the files already there.
|
- To add custom error images, place them in the `./custom/assets/badges` directory, replacing the files already there.
|
||||||
- To add custom sounds, place only mp3 files in the `./custom/assets/sounds` directory.
|
- To add custom sounds, place only mp3 files in the `./custom/assets/sounds` directory.
|
||||||
- To update custom assets without rebuilding, just run `pnpm run gulp`.
|
- To update custom assets without rebuilding, just run `pnpm run gulp`.
|
||||||
|
- To block ChatGPT, CommonCrawl, or other crawlers from indexing your instance, uncomment the respective rules in `./custom/robots.txt`.
|
||||||
|
|
||||||
## 🧑🔬 Configuring a new server
|
## 🧑🔬 Configuring a new server
|
||||||
|
|
||||||
|
|
14
custom/assets/robots.txt
Normal file
14
custom/assets/robots.txt
Normal file
|
@ -0,0 +1,14 @@
|
||||||
|
User-agent: *
|
||||||
|
Allow: /
|
||||||
|
|
||||||
|
# Uncomment the following to block CommonCrawl
|
||||||
|
#
|
||||||
|
# User-agent: CCBot
|
||||||
|
# User-agent: CCBot/2.0
|
||||||
|
# User-agent: CCBot/3.1
|
||||||
|
# Disallow: /
|
||||||
|
|
||||||
|
# Uncomment the following to block ChatGPT
|
||||||
|
#
|
||||||
|
# User-agent: GPTBot
|
||||||
|
# Disallow: /
|
7
docs/api-change.md
Normal file
7
docs/api-change.md
Normal file
|
@ -0,0 +1,7 @@
|
||||||
|
# Changes to the Firefish API
|
||||||
|
|
||||||
|
## v1.0.5 (unreleased)
|
||||||
|
|
||||||
|
### dev11
|
||||||
|
|
||||||
|
- `notes/translate` now requires credentials.
|
|
@ -762,8 +762,7 @@ no: "No"
|
||||||
driveFilesCount: "Number of Drive files"
|
driveFilesCount: "Number of Drive files"
|
||||||
driveUsage: "Drive space usage"
|
driveUsage: "Drive space usage"
|
||||||
noCrawle: "Reject crawler indexing"
|
noCrawle: "Reject crawler indexing"
|
||||||
noCrawleDescription: "Ask search engines to not index your profile page, posts, Pages,
|
noCrawleDescription: "Ask external search engines to not index your content."
|
||||||
etc."
|
|
||||||
lockedAccountInfo: "Unless you set your post visiblity to \"Followers only\", your
|
lockedAccountInfo: "Unless you set your post visiblity to \"Followers only\", your
|
||||||
posts will be visible to anyone, even if you require followers to be manually approved."
|
posts will be visible to anyone, even if you require followers to be manually approved."
|
||||||
alwaysMarkSensitive: "Mark as NSFW by default"
|
alwaysMarkSensitive: "Mark as NSFW by default"
|
||||||
|
@ -1139,6 +1138,10 @@ confirm: "Confirm"
|
||||||
importZip: "Import ZIP"
|
importZip: "Import ZIP"
|
||||||
exportZip: "Export ZIP"
|
exportZip: "Export ZIP"
|
||||||
emojiPackCreator: "Emoji pack creator"
|
emojiPackCreator: "Emoji pack creator"
|
||||||
|
indexable: "Indexable"
|
||||||
|
indexableDescription: "Allow built-in search to show your public posts"
|
||||||
|
languageForTranslation: "Post translation language"
|
||||||
|
detectPostLanguage: "Automatically detect the language and show a translate button for posts in foreign languages"
|
||||||
|
|
||||||
_sensitiveMediaDetection:
|
_sensitiveMediaDetection:
|
||||||
description: "Reduces the effort of server moderation through automatically recognizing
|
description: "Reduces the effort of server moderation through automatically recognizing
|
||||||
|
|
|
@ -874,7 +874,7 @@ pubSub: "Cuentas Pub/Sub"
|
||||||
lastCommunication: "Última comunicación"
|
lastCommunication: "Última comunicación"
|
||||||
resolved: "Resuelto"
|
resolved: "Resuelto"
|
||||||
unresolved: "Sin resolver"
|
unresolved: "Sin resolver"
|
||||||
breakFollow: "Dejar de seguir"
|
breakFollow: "Quitar seguidor"
|
||||||
itsOn: "¡Está encendido!"
|
itsOn: "¡Está encendido!"
|
||||||
itsOff: "¡Está apagado!"
|
itsOff: "¡Está apagado!"
|
||||||
emailRequiredForSignup: "Se requere una dirección de correo electrónico para el registro
|
emailRequiredForSignup: "Se requere una dirección de correo electrónico para el registro
|
||||||
|
|
|
@ -309,11 +309,11 @@ emptyDrive: "Le Drive est vide"
|
||||||
emptyFolder: "Le dossier est vide"
|
emptyFolder: "Le dossier est vide"
|
||||||
unableToDelete: "Suppression impossible"
|
unableToDelete: "Suppression impossible"
|
||||||
inputNewFileName: "Entrez un nouveau nom de fichier"
|
inputNewFileName: "Entrez un nouveau nom de fichier"
|
||||||
inputNewDescription: "Veuillez entrer une nouvelle description"
|
inputNewDescription: "Veuillez entrer une nouvelle description au fichier"
|
||||||
inputNewFolderName: "Entrez un nouveau nom de dossier"
|
inputNewFolderName: "Entrez un nouveau nom de dossier"
|
||||||
circularReferenceFolder: "Le dossier de destination est un sous-dossier du dossier
|
circularReferenceFolder: "Le dossier de destination est un sous-dossier du dossier
|
||||||
que vous souhaitez déplacer."
|
que vous souhaitez déplacer."
|
||||||
hasChildFilesOrFolders: "Impossible de supprimer ce dossier car il n'est pas vide."
|
hasChildFilesOrFolders: "Impossible de supprimer ce dossier, car il n'est pas vide."
|
||||||
copyUrl: "Copier l’URL"
|
copyUrl: "Copier l’URL"
|
||||||
rename: "Renommer"
|
rename: "Renommer"
|
||||||
avatar: "Avatar"
|
avatar: "Avatar"
|
||||||
|
@ -605,7 +605,7 @@ disablePlayer: "Fermer le lecteur vidéo"
|
||||||
expandTweet: "Étendre le tweet"
|
expandTweet: "Étendre le tweet"
|
||||||
themeEditor: "Éditeur de thèmes"
|
themeEditor: "Éditeur de thèmes"
|
||||||
description: "Description"
|
description: "Description"
|
||||||
describeFile: "Ajouter une description d'image"
|
describeFile: "Ajouter une description"
|
||||||
enterFileDescription: "Saisissez une description"
|
enterFileDescription: "Saisissez une description"
|
||||||
author: "Auteur·rice"
|
author: "Auteur·rice"
|
||||||
leaveConfirm: "Vous avez des modifications non-sauvegardées. Voulez-vous les ignorer
|
leaveConfirm: "Vous avez des modifications non-sauvegardées. Voulez-vous les ignorer
|
||||||
|
@ -2085,7 +2085,7 @@ silenceThisInstance: Masquer ce serveur
|
||||||
silencedInstances: Serveurs masqués
|
silencedInstances: Serveurs masqués
|
||||||
silenced: Masqué
|
silenced: Masqué
|
||||||
deleted: Effacé
|
deleted: Effacé
|
||||||
editNote: Modifier publication
|
editNote: Modifier la publication
|
||||||
edited: 'Modifié à {date} {time}'
|
edited: 'Modifié à {date} {time}'
|
||||||
flagShowTimelineRepliesDescription: Si activé, affiche dans le fil les réponses des
|
flagShowTimelineRepliesDescription: Si activé, affiche dans le fil les réponses des
|
||||||
utilisatieur·rice·s aux publications des autres.
|
utilisatieur·rice·s aux publications des autres.
|
||||||
|
@ -2209,4 +2209,4 @@ addRe: Ajouter "re:" au début d’un avertissement de contenu (CW) en réponse
|
||||||
confirm: Confirmer
|
confirm: Confirmer
|
||||||
importZip: Importer ZIP
|
importZip: Importer ZIP
|
||||||
exportZip: Exporter ZIP
|
exportZip: Exporter ZIP
|
||||||
emojiPackCreator: Créateur de pack d’emoji
|
emojiPackCreator: Créateur de pack d’émoji
|
||||||
|
|
|
@ -157,7 +157,7 @@ flagAsCatDescription: "Ti compariranno le orecchie e parlerai come un gatto!"
|
||||||
autoAcceptFollowed: "Accetta in automatico i follow dagli account che segui"
|
autoAcceptFollowed: "Accetta in automatico i follow dagli account che segui"
|
||||||
addAccount: "Aggiungi account"
|
addAccount: "Aggiungi account"
|
||||||
loginFailed: "Accesso non riuscito"
|
loginFailed: "Accesso non riuscito"
|
||||||
showOnRemote: "Apri la pagina di origine"
|
showOnRemote: "Visita la pagina di origine"
|
||||||
general: "Generali"
|
general: "Generali"
|
||||||
wallpaper: "Sfondo"
|
wallpaper: "Sfondo"
|
||||||
setWallpaper: "Imposta sfondo"
|
setWallpaper: "Imposta sfondo"
|
||||||
|
|
|
@ -988,6 +988,8 @@ youHaveUnreadAnnouncements: "未読のお知らせがあります"
|
||||||
neverShow: "今後表示しない"
|
neverShow: "今後表示しない"
|
||||||
remindMeLater: "また後で"
|
remindMeLater: "また後で"
|
||||||
addRe: "閲覧注意の投稿への返信で、注釈の先頭に\"re:\"を追加する"
|
addRe: "閲覧注意の投稿への返信で、注釈の先頭に\"re:\"を追加する"
|
||||||
|
languageForTranslation: "投稿翻訳に使用する言語"
|
||||||
|
detectPostLanguage: "投稿の言語を自動検出し、外国語の投稿に翻訳ボタンを表示する"
|
||||||
|
|
||||||
_sensitiveMediaDetection:
|
_sensitiveMediaDetection:
|
||||||
description: "機械学習を使って自動でセンシティブなメディアを検出し、モデレーションに役立てられます。サーバーの負荷が少し増えます。"
|
description: "機械学習を使って自動でセンシティブなメディアを検出し、モデレーションに役立てられます。サーバーの負荷が少し増えます。"
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
{
|
{
|
||||||
"name": "firefish",
|
"name": "firefish",
|
||||||
"version": "1.0.5-dev8",
|
"version": "1.0.5-dev11",
|
||||||
"codename": "aqua",
|
"codename": "aqua",
|
||||||
"repository": {
|
"repository": {
|
||||||
"type": "git",
|
"type": "git",
|
||||||
|
|
BIN
packages/backend/assets/icons/maskable.png
Normal file
BIN
packages/backend/assets/icons/maskable.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 15 KiB |
|
@ -6,6 +6,7 @@ mod m20230531_180824_drop_reversi;
|
||||||
mod m20230627_185451_index_note_url;
|
mod m20230627_185451_index_note_url;
|
||||||
mod m20230709_000510_move_antenna_to_cache;
|
mod m20230709_000510_move_antenna_to_cache;
|
||||||
mod m20230806_170616_fix_antenna_stream_ids;
|
mod m20230806_170616_fix_antenna_stream_ids;
|
||||||
|
mod m20230904_013244_is_indexable;
|
||||||
|
|
||||||
pub struct Migrator;
|
pub struct Migrator;
|
||||||
|
|
||||||
|
@ -17,6 +18,7 @@ impl MigratorTrait for Migrator {
|
||||||
Box::new(m20230627_185451_index_note_url::Migration),
|
Box::new(m20230627_185451_index_note_url::Migration),
|
||||||
Box::new(m20230709_000510_move_antenna_to_cache::Migration),
|
Box::new(m20230709_000510_move_antenna_to_cache::Migration),
|
||||||
Box::new(m20230806_170616_fix_antenna_stream_ids::Migration),
|
Box::new(m20230806_170616_fix_antenna_stream_ids::Migration),
|
||||||
|
Box::new(m20230904_013244_is_indexable::Migration),
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,74 @@
|
||||||
|
use sea_orm_migration::prelude::*;
|
||||||
|
|
||||||
|
#[derive(DeriveMigrationName)]
|
||||||
|
pub struct Migration;
|
||||||
|
|
||||||
|
#[async_trait::async_trait]
|
||||||
|
impl MigrationTrait for Migration {
|
||||||
|
async fn up(&self, manager: &SchemaManager) -> Result<(), DbErr> {
|
||||||
|
manager
|
||||||
|
.alter_table(
|
||||||
|
Table::alter()
|
||||||
|
.table(User::Table)
|
||||||
|
.add_column(
|
||||||
|
ColumnDef::new(User::IsIndexable)
|
||||||
|
.boolean()
|
||||||
|
.not_null()
|
||||||
|
.default(true),
|
||||||
|
)
|
||||||
|
.to_owned(),
|
||||||
|
)
|
||||||
|
.await?;
|
||||||
|
manager
|
||||||
|
.alter_table(
|
||||||
|
Table::alter()
|
||||||
|
.table(UserProfile::Table)
|
||||||
|
.add_column(
|
||||||
|
ColumnDef::new(UserProfile::IsIndexable)
|
||||||
|
.boolean()
|
||||||
|
.not_null()
|
||||||
|
.default(true),
|
||||||
|
)
|
||||||
|
.to_owned(),
|
||||||
|
)
|
||||||
|
.await?;
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn down(&self, manager: &SchemaManager) -> Result<(), DbErr> {
|
||||||
|
manager
|
||||||
|
.alter_table(
|
||||||
|
Table::alter()
|
||||||
|
.table(User::Table)
|
||||||
|
.drop_column(User::IsIndexable)
|
||||||
|
.to_owned(),
|
||||||
|
)
|
||||||
|
.await?;
|
||||||
|
manager
|
||||||
|
.alter_table(
|
||||||
|
Table::alter()
|
||||||
|
.table(UserProfile::Table)
|
||||||
|
.drop_column(UserProfile::IsIndexable)
|
||||||
|
.to_owned(),
|
||||||
|
)
|
||||||
|
.await?;
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Learn more at https://docs.rs/sea-query#iden
|
||||||
|
#[derive(Iden)]
|
||||||
|
enum User {
|
||||||
|
Table,
|
||||||
|
#[iden = "isIndexable"]
|
||||||
|
IsIndexable,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Iden)]
|
||||||
|
enum UserProfile {
|
||||||
|
Table,
|
||||||
|
#[iden = "isIndexable"]
|
||||||
|
IsIndexable,
|
||||||
|
}
|
|
@ -71,6 +71,8 @@ pub struct Model {
|
||||||
pub also_known_as: Option<String>,
|
pub also_known_as: Option<String>,
|
||||||
#[sea_orm(column_name = "speakAsCat")]
|
#[sea_orm(column_name = "speakAsCat")]
|
||||||
pub speak_as_cat: bool,
|
pub speak_as_cat: bool,
|
||||||
|
#[sea_orm(column_name = "isIndexable")]
|
||||||
|
pub is_indexable: bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Copy, Clone, Debug, EnumIter, DeriveRelation)]
|
#[derive(Copy, Clone, Debug, EnumIter, DeriveRelation)]
|
||||||
|
|
|
@ -75,6 +75,8 @@ pub struct Model {
|
||||||
pub moderation_note: String,
|
pub moderation_note: String,
|
||||||
#[sea_orm(column_name = "preventAiLearning")]
|
#[sea_orm(column_name = "preventAiLearning")]
|
||||||
pub prevent_ai_learning: bool,
|
pub prevent_ai_learning: bool,
|
||||||
|
#[sea_orm(column_name = "isIndexable")]
|
||||||
|
pub is_indexable: bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Copy, Clone, Debug, EnumIter, DeriveRelation)]
|
#[derive(Copy, Clone, Debug, EnumIter, DeriveRelation)]
|
||||||
|
|
|
@ -19,7 +19,12 @@ const ev = new Xev();
|
||||||
* Init process
|
* Init process
|
||||||
*/
|
*/
|
||||||
export default async function () {
|
export default async function () {
|
||||||
process.title = `Firefish (${cluster.isPrimary ? "master" : "worker"})`;
|
const mode =
|
||||||
|
process.env.mode && ["web", "queue"].includes(process.env.mode)
|
||||||
|
? `(${process.env.mode})`
|
||||||
|
: "";
|
||||||
|
const type = cluster.isPrimary ? "(master)" : "(worker)";
|
||||||
|
process.title = `Firefish ${mode} ${type}`;
|
||||||
|
|
||||||
if (cluster.isPrimary || envOption.disableClustering) {
|
if (cluster.isPrimary || envOption.disableClustering) {
|
||||||
await masterMain();
|
await masterMain();
|
||||||
|
|
|
@ -111,7 +111,7 @@ export async function masterMain() {
|
||||||
bootLogger.succ("Firefish initialized");
|
bootLogger.succ("Firefish initialized");
|
||||||
|
|
||||||
if (!envOption.disableClustering) {
|
if (!envOption.disableClustering) {
|
||||||
await spawnWorkers(config.clusterLimit);
|
await spawnWorkers(config.clusterLimits);
|
||||||
}
|
}
|
||||||
|
|
||||||
bootLogger.succ(
|
bootLogger.succ(
|
||||||
|
@ -120,7 +120,11 @@ export async function masterMain() {
|
||||||
true,
|
true,
|
||||||
);
|
);
|
||||||
|
|
||||||
if (!envOption.noDaemons && !config.onlyQueueProcessor) {
|
if (
|
||||||
|
!envOption.noDaemons &&
|
||||||
|
config.clusterLimits?.web &&
|
||||||
|
config.clusterLimits?.web >= 1
|
||||||
|
) {
|
||||||
import("../daemons/server-stats.js").then((x) => x.default());
|
import("../daemons/server-stats.js").then((x) => x.default());
|
||||||
import("../daemons/queue-stats.js").then((x) => x.default());
|
import("../daemons/queue-stats.js").then((x) => x.default());
|
||||||
import("../daemons/janitor.js").then((x) => x.default());
|
import("../daemons/janitor.js").then((x) => x.default());
|
||||||
|
@ -136,7 +140,7 @@ function showEnvironment(): void {
|
||||||
|
|
||||||
if (env !== "production") {
|
if (env !== "production") {
|
||||||
logger.warn("The environment is not in production mode.");
|
logger.warn("The environment is not in production mode.");
|
||||||
logger.warn("DO NOT USE FOR PRODUCTION PURPOSE!", null, true);
|
logger.warn("DO NOT USE THIS IN PRODUCTION!", null, true);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -194,19 +198,35 @@ async function connectDb(): Promise<void> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async function spawnWorkers(limit = 1) {
|
async function spawnWorkers(
|
||||||
const workers = Math.min(limit, os.cpus().length);
|
clusterLimits: Required<Config["clusterLimits"]>,
|
||||||
bootLogger.info(`Starting ${workers} worker${workers === 1 ? "" : "s"}...`);
|
): Promise<void> {
|
||||||
await Promise.all([...Array(workers)].map(spawnWorker));
|
const modes = ["web", "queue"];
|
||||||
|
const cpus = os.cpus().length;
|
||||||
|
for (const mode of modes.filter((mode) => clusterLimits[mode] > cpus)) {
|
||||||
|
bootLogger.warn(
|
||||||
|
`configuration warning: cluster limit for ${mode} exceeds number of cores (${cpus})`,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
const total = modes.reduce((acc, mode) => acc + clusterLimits[mode], 0);
|
||||||
|
const workers = new Array(total);
|
||||||
|
workers.fill("web", 0, clusterLimits?.web);
|
||||||
|
workers.fill("queue", clusterLimits?.web);
|
||||||
|
|
||||||
|
bootLogger.info(
|
||||||
|
`Starting ${clusterLimits?.web} web workers and ${clusterLimits?.queue} queue workers (total ${total})...`,
|
||||||
|
);
|
||||||
|
await Promise.all(workers.map((mode) => spawnWorker(mode)));
|
||||||
bootLogger.succ("All workers started");
|
bootLogger.succ("All workers started");
|
||||||
}
|
}
|
||||||
|
|
||||||
function spawnWorker(): Promise<void> {
|
function spawnWorker(mode: "web" | "queue"): Promise<void> {
|
||||||
return new Promise((res) => {
|
return new Promise((res) => {
|
||||||
const worker = cluster.fork();
|
const worker = cluster.fork({ mode });
|
||||||
worker.on("message", (message) => {
|
worker.on("message", (message) => {
|
||||||
if (message === "listenFailed") {
|
if (message === "listenFailed") {
|
||||||
bootLogger.error("The server Listen failed due to the previous error.");
|
bootLogger.error("The server listen failed due to the previous error.");
|
||||||
process.exit(1);
|
process.exit(1);
|
||||||
}
|
}
|
||||||
if (message !== "ready") return;
|
if (message !== "ready") return;
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
import cluster from "node:cluster";
|
import cluster from "node:cluster";
|
||||||
import { initDb } from "../db/postgre.js";
|
import { initDb } from "../db/postgre.js";
|
||||||
import config from "@/config/index.js";
|
import config from "@/config/index.js";
|
||||||
|
import os from "node:os";
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Init worker process
|
* Init worker process
|
||||||
|
@ -8,13 +9,20 @@ import config from "@/config/index.js";
|
||||||
export async function workerMain() {
|
export async function workerMain() {
|
||||||
await initDb();
|
await initDb();
|
||||||
|
|
||||||
if (!config.onlyQueueProcessor) {
|
if (!process.env.mode || process.env.mode === "web") {
|
||||||
// start server
|
// start server
|
||||||
await import("../server/index.js").then((x) => x.default());
|
await import("../server/index.js").then((x) => x.default());
|
||||||
}
|
}
|
||||||
|
|
||||||
// start job queue
|
if (!process.env.mode || process.env.mode === "queue") {
|
||||||
import("../queue/index.js").then((x) => x.default());
|
// start job queue
|
||||||
|
import("../queue/index.js").then((x) => x.default());
|
||||||
|
|
||||||
|
if (process.env.mode === "queue") {
|
||||||
|
// if this is an exclusive queue worker, renice to have higher priority
|
||||||
|
os.setPriority(os.constants.priority.PRIORITY_BELOW_NORMAL);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if (cluster.isWorker) {
|
if (cluster.isWorker) {
|
||||||
// Send a 'ready' message to parent process
|
// Send a 'ready' message to parent process
|
||||||
|
|
|
@ -59,6 +59,23 @@ export default function load() {
|
||||||
if (config.cacheServer && !config.cacheServer.prefix)
|
if (config.cacheServer && !config.cacheServer.prefix)
|
||||||
config.cacheServer.prefix = mixin.hostname;
|
config.cacheServer.prefix = mixin.hostname;
|
||||||
|
|
||||||
|
if (!config.clusterLimits) {
|
||||||
|
config.clusterLimits = {
|
||||||
|
web: 1,
|
||||||
|
queue: 1,
|
||||||
|
};
|
||||||
|
} else {
|
||||||
|
config.clusterLimits = {
|
||||||
|
web: 1,
|
||||||
|
queue: 1,
|
||||||
|
...config.clusterLimits,
|
||||||
|
};
|
||||||
|
|
||||||
|
if (config.clusterLimits.web! < 1 || config.clusterLimits.queue! < 1) {
|
||||||
|
throw new Error("Invalid cluster limits");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
return Object.assign(config, mixin);
|
return Object.assign(config, mixin);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -77,9 +77,10 @@ export type Source = {
|
||||||
|
|
||||||
accesslog?: string;
|
accesslog?: string;
|
||||||
|
|
||||||
clusterLimit?: number;
|
clusterLimits?: {
|
||||||
|
web?: number;
|
||||||
onlyQueueProcessor?: boolean;
|
queue?: number;
|
||||||
|
};
|
||||||
|
|
||||||
cuid?: {
|
cuid?: {
|
||||||
length?: number;
|
length?: number;
|
||||||
|
|
2
packages/backend/src/global.d.ts
vendored
2
packages/backend/src/global.d.ts
vendored
|
@ -1,2 +1,2 @@
|
||||||
// biome-ignore lint/suspicious/noExplicitAny: i have no idea
|
// rome-ignore lint/suspicious/noExplicitAny: i have no idea
|
||||||
type FIXME = any;
|
type FIXME = any;
|
||||||
|
|
|
@ -12,29 +12,38 @@ const retryDelay = 100;
|
||||||
* @param timeout Lock timeout (ms), The timeout releases previous lock.
|
* @param timeout Lock timeout (ms), The timeout releases previous lock.
|
||||||
* @returns Unlock function
|
* @returns Unlock function
|
||||||
*/
|
*/
|
||||||
export async function getApLock(uri: string, timeout = 30 * 1000) {
|
export async function getApLock(
|
||||||
|
uri: string,
|
||||||
|
timeout = 30 * 1000,
|
||||||
|
): Promise<Mutex> {
|
||||||
const lock = new Mutex(redisClient, `ap-object:${uri}`, {
|
const lock = new Mutex(redisClient, `ap-object:${uri}`, {
|
||||||
lockTimeout: timeout,
|
lockTimeout: timeout,
|
||||||
retryInterval: retryDelay,
|
retryInterval: retryDelay,
|
||||||
});
|
});
|
||||||
await lock.acquire();
|
await lock.acquire();
|
||||||
|
return lock;
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function getFetchInstanceMetadataLock(
|
export async function getFetchInstanceMetadataLock(
|
||||||
host: string,
|
host: string,
|
||||||
timeout = 30 * 1000,
|
timeout = 30 * 1000,
|
||||||
) {
|
): Promise<Mutex> {
|
||||||
const lock = new Mutex(redisClient, `instance:${host}`, {
|
const lock = new Mutex(redisClient, `instance:${host}`, {
|
||||||
lockTimeout: timeout,
|
lockTimeout: timeout,
|
||||||
retryInterval: retryDelay,
|
retryInterval: retryDelay,
|
||||||
});
|
});
|
||||||
await lock.acquire();
|
await lock.acquire();
|
||||||
|
return lock;
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function getChartInsertLock(lockKey: string, timeout = 30 * 1000) {
|
export async function getChartInsertLock(
|
||||||
|
lockKey: string,
|
||||||
|
timeout = 30 * 1000,
|
||||||
|
): Promise<Mutex> {
|
||||||
const lock = new Mutex(redisClient, `chart-insert:${lockKey}`, {
|
const lock = new Mutex(redisClient, `chart-insert:${lockKey}`, {
|
||||||
lockTimeout: timeout,
|
lockTimeout: timeout,
|
||||||
retryInterval: retryDelay,
|
retryInterval: retryDelay,
|
||||||
});
|
});
|
||||||
await lock.acquire();
|
await lock.acquire();
|
||||||
|
return lock;
|
||||||
}
|
}
|
||||||
|
|
|
@ -167,6 +167,12 @@ export class UserProfile {
|
||||||
})
|
})
|
||||||
public noCrawle: boolean;
|
public noCrawle: boolean;
|
||||||
|
|
||||||
|
@Column("boolean", {
|
||||||
|
default: true,
|
||||||
|
comment: "Whether User is indexable.",
|
||||||
|
})
|
||||||
|
public isIndexable: boolean;
|
||||||
|
|
||||||
@Column("boolean", {
|
@Column("boolean", {
|
||||||
default: true,
|
default: true,
|
||||||
})
|
})
|
||||||
|
|
|
@ -265,6 +265,13 @@ export class User {
|
||||||
})
|
})
|
||||||
public driveCapacityOverrideMb: number | null;
|
public driveCapacityOverrideMb: number | null;
|
||||||
|
|
||||||
|
@Index()
|
||||||
|
@Column("boolean", {
|
||||||
|
default: true,
|
||||||
|
comment: "Whether the User is indexable.",
|
||||||
|
})
|
||||||
|
public isIndexable: boolean;
|
||||||
|
|
||||||
constructor(data: Partial<User>) {
|
constructor(data: Partial<User>) {
|
||||||
if (data == null) return;
|
if (data == null) return;
|
||||||
|
|
||||||
|
|
|
@ -474,6 +474,7 @@ export const UserRepository = db.getRepository(User).extend({
|
||||||
isModerator: user.isModerator || falsy,
|
isModerator: user.isModerator || falsy,
|
||||||
isBot: user.isBot || falsy,
|
isBot: user.isBot || falsy,
|
||||||
isLocked: user.isLocked,
|
isLocked: user.isLocked,
|
||||||
|
isIndexable: user.isIndexable,
|
||||||
isCat: user.isCat || falsy,
|
isCat: user.isCat || falsy,
|
||||||
speakAsCat: user.speakAsCat || falsy,
|
speakAsCat: user.speakAsCat || falsy,
|
||||||
instance: user.host
|
instance: user.host
|
||||||
|
|
|
@ -66,6 +66,11 @@ export const packedUserLiteSchema = {
|
||||||
nullable: false,
|
nullable: false,
|
||||||
optional: true,
|
optional: true,
|
||||||
},
|
},
|
||||||
|
isIndexable: {
|
||||||
|
type: "boolean",
|
||||||
|
nullable: false,
|
||||||
|
optional: true,
|
||||||
|
},
|
||||||
speakAsCat: {
|
speakAsCat: {
|
||||||
type: "boolean",
|
type: "boolean",
|
||||||
nullable: false,
|
nullable: false,
|
||||||
|
|
|
@ -32,6 +32,8 @@ export default async function (
|
||||||
// Interrupt if you block the announcement destination
|
// Interrupt if you block the announcement destination
|
||||||
if (await shouldBlockInstance(extractDbHost(uri))) return;
|
if (await shouldBlockInstance(extractDbHost(uri))) return;
|
||||||
|
|
||||||
|
const lock = await getApLock(uri);
|
||||||
|
|
||||||
try {
|
try {
|
||||||
// Check if something with the same URI is already registered
|
// Check if something with the same URI is already registered
|
||||||
const exist = await fetchNote(uri);
|
const exist = await fetchNote(uri);
|
||||||
|
@ -78,6 +80,6 @@ export default async function (
|
||||||
uri,
|
uri,
|
||||||
});
|
});
|
||||||
} finally {
|
} finally {
|
||||||
await getApLock(uri);
|
await lock.release();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -31,6 +31,8 @@ export default async function (
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const lock = await getApLock(uri);
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const exist = await fetchNote(note);
|
const exist = await fetchNote(note);
|
||||||
if (exist) return "skip: note exists";
|
if (exist) return "skip: note exists";
|
||||||
|
@ -44,6 +46,6 @@ export default async function (
|
||||||
throw e;
|
throw e;
|
||||||
}
|
}
|
||||||
} finally {
|
} finally {
|
||||||
await getApLock(uri);
|
await lock.release();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -13,6 +13,8 @@ export default async function (
|
||||||
): Promise<string> {
|
): Promise<string> {
|
||||||
logger.info(`Deleting the Note: ${uri}`);
|
logger.info(`Deleting the Note: ${uri}`);
|
||||||
|
|
||||||
|
const lock = await getApLock(uri);
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const dbResolver = new DbResolver();
|
const dbResolver = new DbResolver();
|
||||||
const note = await dbResolver.getNoteFromApId(uri);
|
const note = await dbResolver.getNoteFromApId(uri);
|
||||||
|
@ -37,6 +39,6 @@ export default async function (
|
||||||
await deleteNode(actor, note);
|
await deleteNode(actor, note);
|
||||||
return "ok: note deleted";
|
return "ok: note deleted";
|
||||||
} finally {
|
} finally {
|
||||||
await getApLock(uri);
|
await lock.release();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -454,6 +454,8 @@ export async function resolveNote(
|
||||||
`host ${extractDbHost(uri)} is blocked`,
|
`host ${extractDbHost(uri)} is blocked`,
|
||||||
);
|
);
|
||||||
|
|
||||||
|
const lock = await getApLock(uri);
|
||||||
|
|
||||||
try {
|
try {
|
||||||
//#region Returns if already registered with this server
|
//#region Returns if already registered with this server
|
||||||
const exist = await fetchNote(uri);
|
const exist = await fetchNote(uri);
|
||||||
|
@ -476,7 +478,7 @@ export async function resolveNote(
|
||||||
// Since the attached Note Object may be disguised, always specify the uri and fetch it from the server.
|
// Since the attached Note Object may be disguised, always specify the uri and fetch it from the server.
|
||||||
return await createNote(uri, resolver, true);
|
return await createNote(uri, resolver, true);
|
||||||
} finally {
|
} finally {
|
||||||
await getApLock(uri);
|
await lock.release();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -209,10 +209,10 @@ export async function createPerson(
|
||||||
|
|
||||||
if (typeof person.followers === "string") {
|
if (typeof person.followers === "string") {
|
||||||
try {
|
try {
|
||||||
let data = await fetch(person.followers, {
|
const data = await fetch(person.followers, {
|
||||||
headers: { Accept: "application/json" },
|
headers: { Accept: "application/json" },
|
||||||
});
|
});
|
||||||
let json_data = JSON.parse(await data.text());
|
const json_data = JSON.parse(await data.text());
|
||||||
|
|
||||||
followersCount = json_data.totalItems;
|
followersCount = json_data.totalItems;
|
||||||
} catch {
|
} catch {
|
||||||
|
@ -224,10 +224,10 @@ export async function createPerson(
|
||||||
|
|
||||||
if (typeof person.following === "string") {
|
if (typeof person.following === "string") {
|
||||||
try {
|
try {
|
||||||
let data = await fetch(person.following, {
|
const data = await fetch(person.following, {
|
||||||
headers: { Accept: "application/json" },
|
headers: { Accept: "application/json" },
|
||||||
});
|
});
|
||||||
let json_data = JSON.parse(await data.text());
|
const json_data = JSON.parse(await data.text());
|
||||||
|
|
||||||
followingCount = json_data.totalItems;
|
followingCount = json_data.totalItems;
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
|
@ -239,10 +239,10 @@ export async function createPerson(
|
||||||
|
|
||||||
if (typeof person.outbox === "string") {
|
if (typeof person.outbox === "string") {
|
||||||
try {
|
try {
|
||||||
let data = await fetch(person.outbox, {
|
const data = await fetch(person.outbox, {
|
||||||
headers: { Accept: "application/json" },
|
headers: { Accept: "application/json" },
|
||||||
});
|
});
|
||||||
let json_data = JSON.parse(await data.text());
|
const json_data = JSON.parse(await data.text());
|
||||||
|
|
||||||
notesCount = json_data.totalItems;
|
notesCount = json_data.totalItems;
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
|
@ -306,6 +306,7 @@ export async function createPerson(
|
||||||
tags,
|
tags,
|
||||||
isBot,
|
isBot,
|
||||||
isCat: (person as any).isCat === true,
|
isCat: (person as any).isCat === true,
|
||||||
|
isIndexable: person.indexable,
|
||||||
}),
|
}),
|
||||||
)) as IRemoteUser;
|
)) as IRemoteUser;
|
||||||
|
|
||||||
|
@ -555,6 +556,7 @@ export async function updatePerson(
|
||||||
tags,
|
tags,
|
||||||
isBot: getApType(object) !== "Person",
|
isBot: getApType(object) !== "Person",
|
||||||
isCat: (person as any).isCat === true,
|
isCat: (person as any).isCat === true,
|
||||||
|
isIndexable: person.indexable,
|
||||||
isLocked: !!person.manuallyApprovesFollowers,
|
isLocked: !!person.manuallyApprovesFollowers,
|
||||||
movedToUri: person.movedTo || null,
|
movedToUri: person.movedTo || null,
|
||||||
alsoKnownAs: person.alsoKnownAs || null,
|
alsoKnownAs: person.alsoKnownAs || null,
|
||||||
|
|
|
@ -30,6 +30,7 @@ export const renderActivity = (x: any): IActivity | null => {
|
||||||
Emoji: "toot:Emoji",
|
Emoji: "toot:Emoji",
|
||||||
featured: "toot:featured",
|
featured: "toot:featured",
|
||||||
discoverable: "toot:discoverable",
|
discoverable: "toot:discoverable",
|
||||||
|
indexable: "toot:indexable",
|
||||||
// schema
|
// schema
|
||||||
schema: "http://schema.org#",
|
schema: "http://schema.org#",
|
||||||
PropertyValue: "schema:PropertyValue",
|
PropertyValue: "schema:PropertyValue",
|
||||||
|
|
|
@ -81,6 +81,7 @@ export async function renderPerson(user: ILocalUser) {
|
||||||
discoverable: !!user.isExplorable,
|
discoverable: !!user.isExplorable,
|
||||||
publicKey: renderKey(user, keypair, "#main-key"),
|
publicKey: renderKey(user, keypair, "#main-key"),
|
||||||
isCat: user.isCat,
|
isCat: user.isCat,
|
||||||
|
indexable: user.isIndexable,
|
||||||
attachment: attachment.length ? attachment : undefined,
|
attachment: attachment.length ? attachment : undefined,
|
||||||
} as any;
|
} as any;
|
||||||
|
|
||||||
|
|
|
@ -190,8 +190,9 @@ export interface IActor extends IObject {
|
||||||
movedTo?: string;
|
movedTo?: string;
|
||||||
alsoKnownAs?: string[];
|
alsoKnownAs?: string[];
|
||||||
discoverable?: boolean;
|
discoverable?: boolean;
|
||||||
|
indexable?: boolean;
|
||||||
inbox: string;
|
inbox: string;
|
||||||
sharedInbox?: string; // backward compatibility.. ig
|
sharedInbox?: string; // Backwards compatibility
|
||||||
publicKey?: {
|
publicKey?: {
|
||||||
id: string;
|
id: string;
|
||||||
publicKeyPem: string;
|
publicKeyPem: string;
|
||||||
|
|
|
@ -60,6 +60,7 @@ export default define(meta, paramDef, async (ps, me) => {
|
||||||
emailVerified: profile.emailVerified,
|
emailVerified: profile.emailVerified,
|
||||||
autoAcceptFollowed: profile.autoAcceptFollowed,
|
autoAcceptFollowed: profile.autoAcceptFollowed,
|
||||||
noCrawle: profile.noCrawle,
|
noCrawle: profile.noCrawle,
|
||||||
|
isIndexable: profile.isIndexable,
|
||||||
preventAiLearning: profile.preventAiLearning,
|
preventAiLearning: profile.preventAiLearning,
|
||||||
alwaysMarkNsfw: profile.alwaysMarkNsfw,
|
alwaysMarkNsfw: profile.alwaysMarkNsfw,
|
||||||
autoSensitive: profile.autoSensitive,
|
autoSensitive: profile.autoSensitive,
|
||||||
|
|
|
@ -121,6 +121,7 @@ export const paramDef = {
|
||||||
isBot: { type: "boolean" },
|
isBot: { type: "boolean" },
|
||||||
isCat: { type: "boolean" },
|
isCat: { type: "boolean" },
|
||||||
speakAsCat: { type: "boolean" },
|
speakAsCat: { type: "boolean" },
|
||||||
|
isIndexable: { type: "boolean" },
|
||||||
injectFeaturedNote: { type: "boolean" },
|
injectFeaturedNote: { type: "boolean" },
|
||||||
receiveAnnouncementEmail: { type: "boolean" },
|
receiveAnnouncementEmail: { type: "boolean" },
|
||||||
alwaysMarkNsfw: { type: "boolean" },
|
alwaysMarkNsfw: { type: "boolean" },
|
||||||
|
@ -207,6 +208,10 @@ export default define(meta, paramDef, async (ps, _user, token) => {
|
||||||
if (typeof ps.preventAiLearning === "boolean")
|
if (typeof ps.preventAiLearning === "boolean")
|
||||||
profileUpdates.preventAiLearning = ps.preventAiLearning;
|
profileUpdates.preventAiLearning = ps.preventAiLearning;
|
||||||
if (typeof ps.isCat === "boolean") updates.isCat = ps.isCat;
|
if (typeof ps.isCat === "boolean") updates.isCat = ps.isCat;
|
||||||
|
if (typeof ps.isIndexable === "boolean") {
|
||||||
|
updates.isIndexable = ps.isIndexable;
|
||||||
|
profileUpdates.isIndexable = ps.isIndexable;
|
||||||
|
}
|
||||||
if (typeof ps.speakAsCat === "boolean") updates.speakAsCat = ps.speakAsCat;
|
if (typeof ps.speakAsCat === "boolean") updates.speakAsCat = ps.speakAsCat;
|
||||||
if (typeof ps.injectFeaturedNote === "boolean")
|
if (typeof ps.injectFeaturedNote === "boolean")
|
||||||
profileUpdates.injectFeaturedNote = ps.injectFeaturedNote;
|
profileUpdates.injectFeaturedNote = ps.injectFeaturedNote;
|
||||||
|
|
|
@ -764,7 +764,7 @@ export default define(meta, paramDef, async (ps, user) => {
|
||||||
throw new ApiError(meta.errors.noSuchNote);
|
throw new ApiError(meta.errors.noSuchNote);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (publishing) {
|
if (publishing && user.isIndexable) {
|
||||||
index(note, true);
|
index(note, true);
|
||||||
|
|
||||||
// Publish update event for the updated note details
|
// Publish update event for the updated note details
|
||||||
|
|
|
@ -4,7 +4,6 @@ import config from "@/config/index.js";
|
||||||
import { Converter } from "opencc-js";
|
import { Converter } from "opencc-js";
|
||||||
import { getAgentByUrl } from "@/misc/fetch.js";
|
import { getAgentByUrl } from "@/misc/fetch.js";
|
||||||
import { fetchMeta } from "@/misc/fetch-meta.js";
|
import { fetchMeta } from "@/misc/fetch-meta.js";
|
||||||
import { Notes } from "@/models/index.js";
|
|
||||||
import { ApiError } from "../../error.js";
|
import { ApiError } from "../../error.js";
|
||||||
import { getNote } from "../../common/getters.js";
|
import { getNote } from "../../common/getters.js";
|
||||||
import define from "../../define.js";
|
import define from "../../define.js";
|
||||||
|
@ -12,7 +11,7 @@ import define from "../../define.js";
|
||||||
export const meta = {
|
export const meta = {
|
||||||
tags: ["notes"],
|
tags: ["notes"],
|
||||||
|
|
||||||
requireCredential: false,
|
requireCredential: true,
|
||||||
requireCredentialPrivateMode: true,
|
requireCredentialPrivateMode: true,
|
||||||
|
|
||||||
res: {
|
res: {
|
||||||
|
|
|
@ -7,7 +7,7 @@
|
||||||
"display": "standalone",
|
"display": "standalone",
|
||||||
"background_color": "#1f1d2e",
|
"background_color": "#1f1d2e",
|
||||||
"theme_color": "#31748f",
|
"theme_color": "#31748f",
|
||||||
"orientation": "any",
|
"orientation": "natural",
|
||||||
"icons": [
|
"icons": [
|
||||||
{
|
{
|
||||||
"src": "/static-assets/icons/192.png",
|
"src": "/static-assets/icons/192.png",
|
||||||
|
@ -22,7 +22,7 @@
|
||||||
"purpose": "any"
|
"purpose": "any"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"src": "/static-assets/icons/512.png",
|
"src": "/static-assets/icons/maskable.png",
|
||||||
"sizes": "512x512",
|
"sizes": "512x512",
|
||||||
"type": "image/png",
|
"type": "image/png",
|
||||||
"purpose": "maskable"
|
"purpose": "maskable"
|
||||||
|
|
|
@ -24,9 +24,11 @@ block meta
|
||||||
unless privateMode
|
unless privateMode
|
||||||
if profile.noCrawle
|
if profile.noCrawle
|
||||||
meta(name='robots' content='noindex')
|
meta(name='robots' content='noindex')
|
||||||
|
|
||||||
if profile.preventAiLearning
|
if profile.preventAiLearning
|
||||||
meta(name='robots' content='noai')
|
meta(name='robots' content='noai')
|
||||||
meta(name='robots' content='noimageai')
|
meta(name='robots' content='noimageai')
|
||||||
|
meta(name='GPTBot' content='noindex')
|
||||||
|
|
||||||
meta(name='misskey:user-username' content=user.username)
|
meta(name='misskey:user-username' content=user.username)
|
||||||
meta(name='misskey:user-id' content=user.id)
|
meta(name='misskey:user-id' content=user.id)
|
||||||
|
|
|
@ -430,6 +430,7 @@ export default abstract class Chart<T extends Schema> {
|
||||||
? `${this.name}:${date}:${span}:${group}`
|
? `${this.name}:${date}:${span}:${group}`
|
||||||
: `${this.name}:${date}:${span}`;
|
: `${this.name}:${date}:${span}`;
|
||||||
|
|
||||||
|
const lock = await getChartInsertLock(lockKey);
|
||||||
try {
|
try {
|
||||||
// ロック内でもう1回チェックする
|
// ロック内でもう1回チェックする
|
||||||
const currentLog = (await repository.findOneBy({
|
const currentLog = (await repository.findOneBy({
|
||||||
|
@ -465,14 +466,14 @@ export default abstract class Chart<T extends Schema> {
|
||||||
|
|
||||||
return log;
|
return log;
|
||||||
} finally {
|
} finally {
|
||||||
await getChartInsertLock(lockKey);
|
await lock.release();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
protected commit(diff: Commit<T>, group: string | null = null): void {
|
protected commit(diff: Commit<T>, group: string | null = null): void {
|
||||||
for (const [k, v] of Object.entries(diff)) {
|
for (const [k, v] of Object.entries(diff)) {
|
||||||
if (v == null || v === 0 || (Array.isArray(v) && v.length === 0))
|
if (v == null || v === 0 || (Array.isArray(v) && v.length === 0))
|
||||||
// biome-ignore lint/performance/noDelete: needs to be deleted not just set to undefined
|
// rome-ignore lint/performance/noDelete: needs to be deleted not just set to undefined
|
||||||
delete diff[k];
|
delete diff[k];
|
||||||
}
|
}
|
||||||
this.buffer.push({
|
this.buffer.push({
|
||||||
|
|
|
@ -15,6 +15,8 @@ export async function fetchInstanceMetadata(
|
||||||
instance: Instance,
|
instance: Instance,
|
||||||
force = false,
|
force = false,
|
||||||
): Promise<void> {
|
): Promise<void> {
|
||||||
|
const lock = await getFetchInstanceMetadataLock(instance.host);
|
||||||
|
|
||||||
if (!force) {
|
if (!force) {
|
||||||
const _instance = await Instances.findOneBy({ host: instance.host });
|
const _instance = await Instances.findOneBy({ host: instance.host });
|
||||||
const now = Date.now();
|
const now = Date.now();
|
||||||
|
@ -22,7 +24,7 @@ export async function fetchInstanceMetadata(
|
||||||
_instance?.infoUpdatedAt &&
|
_instance?.infoUpdatedAt &&
|
||||||
now - _instance.infoUpdatedAt.getTime() < 1000 * 60 * 60 * 24
|
now - _instance.infoUpdatedAt.getTime() < 1000 * 60 * 60 * 24
|
||||||
) {
|
) {
|
||||||
await getFetchInstanceMetadataLock(instance.host);
|
await lock.release();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -78,7 +80,7 @@ export async function fetchInstanceMetadata(
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
logger.error(`Failed to update metadata of ${instance.host}: ${e}`);
|
logger.error(`Failed to update metadata of ${instance.host}: ${e}`);
|
||||||
} finally {
|
} finally {
|
||||||
await getFetchInstanceMetadataLock(instance.host);
|
await lock.release();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -173,11 +173,12 @@ export default async (
|
||||||
createdAt: User["createdAt"];
|
createdAt: User["createdAt"];
|
||||||
isBot: User["isBot"];
|
isBot: User["isBot"];
|
||||||
inbox?: User["inbox"];
|
inbox?: User["inbox"];
|
||||||
|
isIndexable?: User["isIndexable"];
|
||||||
},
|
},
|
||||||
data: Option,
|
data: Option,
|
||||||
silent = false,
|
silent = false,
|
||||||
) =>
|
) =>
|
||||||
// biome-ignore lint/suspicious/noAsyncPromiseExecutor: FIXME
|
// rome-ignore lint/suspicious/noAsyncPromiseExecutor: FIXME
|
||||||
new Promise<Note>(async (res, rej) => {
|
new Promise<Note>(async (res, rej) => {
|
||||||
const dontFederateInitially = data.visibility === "hidden";
|
const dontFederateInitially = data.visibility === "hidden";
|
||||||
|
|
||||||
|
@ -666,7 +667,9 @@ export default async (
|
||||||
}
|
}
|
||||||
|
|
||||||
// Register to search database
|
// Register to search database
|
||||||
await index(note, false);
|
if (user.isIndexable) {
|
||||||
|
await index(note, false);
|
||||||
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
async function renderNoteOrRenoteActivity(data: Option, note: Note) {
|
async function renderNoteOrRenoteActivity(data: Option, note: Note) {
|
||||||
|
|
|
@ -151,6 +151,7 @@ describe("ユーザー", () => {
|
||||||
carefulBot: user.carefulBot,
|
carefulBot: user.carefulBot,
|
||||||
autoAcceptFollowed: user.autoAcceptFollowed,
|
autoAcceptFollowed: user.autoAcceptFollowed,
|
||||||
noCrawle: user.noCrawle,
|
noCrawle: user.noCrawle,
|
||||||
|
isIndexable: user.isIndexable,
|
||||||
preventAiLearning: user.preventAiLearning,
|
preventAiLearning: user.preventAiLearning,
|
||||||
isExplorable: user.isExplorable,
|
isExplorable: user.isExplorable,
|
||||||
isDeleted: user.isDeleted,
|
isDeleted: user.isDeleted,
|
||||||
|
@ -529,6 +530,8 @@ describe("ユーザー", () => {
|
||||||
{ parameters: (): object => ({ autoAcceptFollowed: false }) },
|
{ parameters: (): object => ({ autoAcceptFollowed: false }) },
|
||||||
{ parameters: (): object => ({ noCrawle: true }) },
|
{ parameters: (): object => ({ noCrawle: true }) },
|
||||||
{ parameters: (): object => ({ noCrawle: false }) },
|
{ parameters: (): object => ({ noCrawle: false }) },
|
||||||
|
{ parameters: (): object => ({ isIndexable: true }) },
|
||||||
|
{ parameters: (): object => ({ isIndexable: false }) },
|
||||||
{ parameters: (): object => ({ preventAiLearning: false }) },
|
{ parameters: (): object => ({ preventAiLearning: false }) },
|
||||||
{ parameters: (): object => ({ preventAiLearning: true }) },
|
{ parameters: (): object => ({ preventAiLearning: true }) },
|
||||||
{ parameters: (): object => ({ isBot: true }) },
|
{ parameters: (): object => ({ isBot: true }) },
|
||||||
|
|
|
@ -63,7 +63,7 @@
|
||||||
"katex": "0.16.8",
|
"katex": "0.16.8",
|
||||||
"matter-js": "0.19.0",
|
"matter-js": "0.19.0",
|
||||||
"mfm-js": "0.23.3",
|
"mfm-js": "0.23.3",
|
||||||
"photoswipe": "5.3.8",
|
"photoswipe": "5.3.9",
|
||||||
"prettier": "3.0.3",
|
"prettier": "3.0.3",
|
||||||
"prettier-plugin-vue": "1.1.6",
|
"prettier-plugin-vue": "1.1.6",
|
||||||
"prismjs": "1.29.0",
|
"prismjs": "1.29.0",
|
||||||
|
@ -81,6 +81,7 @@
|
||||||
"three": "0.156.0",
|
"three": "0.156.0",
|
||||||
"throttle-debounce": "5.0.0",
|
"throttle-debounce": "5.0.0",
|
||||||
"tinycolor2": "1.6.0",
|
"tinycolor2": "1.6.0",
|
||||||
|
"tinyld": "^1.3.4",
|
||||||
"tsc-alias": "1.8.7",
|
"tsc-alias": "1.8.7",
|
||||||
"tsconfig-paths": "4.2.0",
|
"tsconfig-paths": "4.2.0",
|
||||||
"twemoji-parser": "14.0.0",
|
"twemoji-parser": "14.0.0",
|
||||||
|
|
|
@ -8,8 +8,10 @@
|
||||||
<div :class="$style.time">
|
<div :class="$style.time">
|
||||||
<MkTime :time="announcement.createdAt" />
|
<MkTime :time="announcement.createdAt" />
|
||||||
<div v-if="announcement.updatedAt">
|
<div v-if="announcement.updatedAt">
|
||||||
{{ i18n.ts.updatedAt }}:
|
<small>
|
||||||
<MkTime :time="announcement.createdAt" />
|
{{ i18n.ts.updatedAt }}:
|
||||||
|
<MkTime :time="announcement.createdAt" />
|
||||||
|
</small>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<Mfm :text="text" />
|
<Mfm :text="text" />
|
||||||
|
@ -80,6 +82,6 @@ const gotIt = () => {
|
||||||
}
|
}
|
||||||
|
|
||||||
.gotIt {
|
.gotIt {
|
||||||
margin: 8px 0 0 0;
|
margin: 1rem 0 1rem 2rem;
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|
|
@ -111,7 +111,7 @@
|
||||||
></MkSubNoteContent>
|
></MkSubNoteContent>
|
||||||
<div v-if="translating || translation" class="translation">
|
<div v-if="translating || translation" class="translation">
|
||||||
<MkLoading v-if="translating" mini />
|
<MkLoading v-if="translating" mini />
|
||||||
<div v-else class="translated">
|
<div v-else-if="translation != null" class="translated">
|
||||||
<b
|
<b
|
||||||
>{{
|
>{{
|
||||||
i18n.t("translatedFrom", {
|
i18n.t("translatedFrom", {
|
||||||
|
@ -219,6 +219,18 @@
|
||||||
<i class="ph-minus ph-bold ph-lg"></i>
|
<i class="ph-minus ph-bold ph-lg"></i>
|
||||||
</button>
|
</button>
|
||||||
<XQuoteButton class="button" :note="appearNote" />
|
<XQuoteButton class="button" :note="appearNote" />
|
||||||
|
<button
|
||||||
|
v-if="
|
||||||
|
$i != null &&
|
||||||
|
isForeignLanguage &&
|
||||||
|
translation == null
|
||||||
|
"
|
||||||
|
class="button _button"
|
||||||
|
@click.stop="translate"
|
||||||
|
v-tooltip.noDelay.bottom="i18n.ts.translate"
|
||||||
|
>
|
||||||
|
<i class="ph-translate ph-bold ph-lg"></i>
|
||||||
|
</button>
|
||||||
<button
|
<button
|
||||||
ref="menuButton"
|
ref="menuButton"
|
||||||
v-tooltip.noDelay.bottom="i18n.ts.more"
|
v-tooltip.noDelay.bottom="i18n.ts.more"
|
||||||
|
@ -259,6 +271,7 @@ import { computed, inject, onMounted, ref } from "vue";
|
||||||
import * as mfm from "mfm-js";
|
import * as mfm from "mfm-js";
|
||||||
import type { Ref } from "vue";
|
import type { Ref } from "vue";
|
||||||
import type * as misskey from "firefish-js";
|
import type * as misskey from "firefish-js";
|
||||||
|
import { detect as detectLanguage_ } from "tinyld";
|
||||||
import MkSubNoteContent from "./MkSubNoteContent.vue";
|
import MkSubNoteContent from "./MkSubNoteContent.vue";
|
||||||
import MkNoteSub from "@/components/MkNoteSub.vue";
|
import MkNoteSub from "@/components/MkNoteSub.vue";
|
||||||
import XNoteHeader from "@/components/MkNoteHeader.vue";
|
import XNoteHeader from "@/components/MkNoteHeader.vue";
|
||||||
|
@ -346,6 +359,57 @@ const translation = ref(null);
|
||||||
const translating = ref(false);
|
const translating = ref(false);
|
||||||
const enableEmojiReactions = defaultStore.state.enableEmojiReactions;
|
const enableEmojiReactions = defaultStore.state.enableEmojiReactions;
|
||||||
const expandOnNoteClick = defaultStore.state.expandOnNoteClick;
|
const expandOnNoteClick = defaultStore.state.expandOnNoteClick;
|
||||||
|
const lang = localStorage.getItem("lang");
|
||||||
|
const translateLang = localStorage.getItem("translateLang");
|
||||||
|
|
||||||
|
function detectLanguage(text: string) {
|
||||||
|
const nodes = mfm.parse(text);
|
||||||
|
const filtered = mfm.extract(nodes, (node) => {
|
||||||
|
return node.type === "text" || node.type === "quote";
|
||||||
|
});
|
||||||
|
const purified = mfm.toString(filtered);
|
||||||
|
return detectLanguage_(purified);
|
||||||
|
}
|
||||||
|
|
||||||
|
const isForeignLanguage: boolean =
|
||||||
|
defaultStore.state.detectPostLanguage &&
|
||||||
|
appearNote.value.text != null &&
|
||||||
|
(() => {
|
||||||
|
const targetLang = (translateLang || lang || navigator.language)?.slice(
|
||||||
|
0,
|
||||||
|
2,
|
||||||
|
);
|
||||||
|
const postLang = detectLanguage(appearNote.value.text);
|
||||||
|
return postLang !== "" && postLang !== targetLang;
|
||||||
|
})();
|
||||||
|
|
||||||
|
async function translate_(noteId: number, targetLang: string) {
|
||||||
|
return await os.api("notes/translate", {
|
||||||
|
noteId: noteId,
|
||||||
|
targetLang: targetLang,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
async function translate() {
|
||||||
|
if (translation.value != null) return;
|
||||||
|
translating.value = true;
|
||||||
|
translation.value = await translate_(
|
||||||
|
appearNote.value.id,
|
||||||
|
translateLang || lang || navigator.language,
|
||||||
|
);
|
||||||
|
|
||||||
|
// use UI language as the second translation language
|
||||||
|
if (
|
||||||
|
translateLang != null &&
|
||||||
|
lang != null &&
|
||||||
|
translateLang !== lang &&
|
||||||
|
(!translation.value ||
|
||||||
|
translation.value.sourceLang.toLowerCase() ===
|
||||||
|
translateLang.slice(0, 2))
|
||||||
|
)
|
||||||
|
translation.value = await translate_(appearNote.value.id, lang);
|
||||||
|
translating.value = false;
|
||||||
|
}
|
||||||
|
|
||||||
const keymap = {
|
const keymap = {
|
||||||
r: () => reply(true),
|
r: () => reply(true),
|
||||||
|
|
|
@ -124,6 +124,18 @@
|
||||||
<i class="ph-minus ph-bold ph-lg"></i>
|
<i class="ph-minus ph-bold ph-lg"></i>
|
||||||
</button>
|
</button>
|
||||||
<XQuoteButton class="button" :note="appearNote" />
|
<XQuoteButton class="button" :note="appearNote" />
|
||||||
|
<button
|
||||||
|
v-if="
|
||||||
|
$i != null &&
|
||||||
|
isForeignLanguage &&
|
||||||
|
translation == null
|
||||||
|
"
|
||||||
|
class="button _button"
|
||||||
|
@click.stop="translate"
|
||||||
|
v-tooltip.noDelay.bottom="i18n.ts.translate"
|
||||||
|
>
|
||||||
|
<i class="ph-translate ph-bold ph-lg"></i>
|
||||||
|
</button>
|
||||||
<button
|
<button
|
||||||
ref="menuButton"
|
ref="menuButton"
|
||||||
v-tooltip.noDelay.bottom="i18n.ts.more"
|
v-tooltip.noDelay.bottom="i18n.ts.more"
|
||||||
|
@ -180,6 +192,8 @@
|
||||||
import { computed, inject, ref } from "vue";
|
import { computed, inject, ref } from "vue";
|
||||||
import type { Ref } from "vue";
|
import type { Ref } from "vue";
|
||||||
import type * as misskey from "firefish-js";
|
import type * as misskey from "firefish-js";
|
||||||
|
import * as mfm from "mfm-js";
|
||||||
|
import { detect as detectLanguage_ } from "tinyld";
|
||||||
import XNoteHeader from "@/components/MkNoteHeader.vue";
|
import XNoteHeader from "@/components/MkNoteHeader.vue";
|
||||||
import MkSubNoteContent from "@/components/MkSubNoteContent.vue";
|
import MkSubNoteContent from "@/components/MkSubNoteContent.vue";
|
||||||
import XReactionsViewer from "@/components/MkReactionsViewer.vue";
|
import XReactionsViewer from "@/components/MkReactionsViewer.vue";
|
||||||
|
@ -266,6 +280,57 @@ const replies: misskey.entities.Note[] =
|
||||||
.reverse() ?? [];
|
.reverse() ?? [];
|
||||||
const enableEmojiReactions = defaultStore.state.enableEmojiReactions;
|
const enableEmojiReactions = defaultStore.state.enableEmojiReactions;
|
||||||
const expandOnNoteClick = defaultStore.state.expandOnNoteClick;
|
const expandOnNoteClick = defaultStore.state.expandOnNoteClick;
|
||||||
|
const lang = localStorage.getItem("lang");
|
||||||
|
const translateLang = localStorage.getItem("translateLang");
|
||||||
|
|
||||||
|
function detectLanguage(text: string) {
|
||||||
|
const nodes = mfm.parse(text);
|
||||||
|
const filtered = mfm.extract(nodes, (node) => {
|
||||||
|
return node.type === "text" || node.type === "quote";
|
||||||
|
});
|
||||||
|
const purified = mfm.toString(filtered);
|
||||||
|
return detectLanguage_(purified);
|
||||||
|
}
|
||||||
|
|
||||||
|
const isForeignLanguage: boolean =
|
||||||
|
defaultStore.state.detectPostLanguage &&
|
||||||
|
appearNote.value.text != null &&
|
||||||
|
(() => {
|
||||||
|
const targetLang = (translateLang || lang || navigator.language)?.slice(
|
||||||
|
0,
|
||||||
|
2,
|
||||||
|
);
|
||||||
|
const postLang = detectLanguage(appearNote.value.text);
|
||||||
|
return postLang !== "" && postLang !== targetLang;
|
||||||
|
})();
|
||||||
|
|
||||||
|
async function translate_(noteId: number, targetLang: string) {
|
||||||
|
return await os.api("notes/translate", {
|
||||||
|
noteId: noteId,
|
||||||
|
targetLang: targetLang,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
async function translate() {
|
||||||
|
if (translation.value != null) return;
|
||||||
|
translating.value = true;
|
||||||
|
translation.value = await translate_(
|
||||||
|
appearNote.value.id,
|
||||||
|
translateLang || lang || navigator.language,
|
||||||
|
);
|
||||||
|
|
||||||
|
// use UI language as the second translation language
|
||||||
|
if (
|
||||||
|
translateLang != null &&
|
||||||
|
lang != null &&
|
||||||
|
translateLang !== lang &&
|
||||||
|
(!translation.value ||
|
||||||
|
translation.value.sourceLang.toLowerCase() ===
|
||||||
|
translateLang.slice(0, 2))
|
||||||
|
)
|
||||||
|
translation.value = await translate_(appearNote.value.id, lang);
|
||||||
|
translating.value = false;
|
||||||
|
}
|
||||||
|
|
||||||
useNoteCapture({
|
useNoteCapture({
|
||||||
rootEl: el,
|
rootEl: el,
|
||||||
|
|
|
@ -16,8 +16,12 @@
|
||||||
class="announcement _panel"
|
class="announcement _panel"
|
||||||
>
|
>
|
||||||
<div class="_title">
|
<div class="_title">
|
||||||
<span v-if="$i && !announcement.isRead">🆕 </span>
|
<h3>
|
||||||
<h3>{{ announcement.title }}</h3>
|
<span v-if="$i && !announcement.isRead">
|
||||||
|
🆕
|
||||||
|
</span>
|
||||||
|
{{ announcement.title }}
|
||||||
|
</h3>
|
||||||
<MkTime :time="announcement.createdAt" />
|
<MkTime :time="announcement.createdAt" />
|
||||||
<div v-if="announcement.updatedAt">
|
<div v-if="announcement.updatedAt">
|
||||||
{{ i18n.ts.updatedAt }}:
|
{{ i18n.ts.updatedAt }}:
|
||||||
|
@ -85,7 +89,7 @@ definePageMetadata({
|
||||||
}
|
}
|
||||||
|
|
||||||
> ._title {
|
> ._title {
|
||||||
padding: 14px 32px !important;
|
padding: 0.5rem 2rem !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
> ._seperator {
|
> ._seperator {
|
||||||
|
@ -93,7 +97,7 @@ definePageMetadata({
|
||||||
}
|
}
|
||||||
|
|
||||||
> ._content {
|
> ._content {
|
||||||
padding: 2rem;
|
padding: 0 2rem !important;
|
||||||
|
|
||||||
> img {
|
> img {
|
||||||
display: block;
|
display: block;
|
||||||
|
|
|
@ -45,108 +45,97 @@
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script lang="ts">
|
<script lang="ts" setup>
|
||||||
import { defineComponent } from "vue";
|
import { ref, onMounted } from "vue";
|
||||||
import XForm from "./auth.form.vue";
|
import XForm from "./auth.form.vue";
|
||||||
import MkSignin from "@/components/MkSignin.vue";
|
import MkSignin from "@/components/MkSignin.vue";
|
||||||
import MkKeyValue from "@/components/MkKeyValue.vue";
|
import MkKeyValue from "@/components/MkKeyValue.vue";
|
||||||
import * as os from "@/os";
|
import * as os from "@/os";
|
||||||
import { login } from "@/account";
|
import { login } from "@/account";
|
||||||
import { i18n } from "@/i18n";
|
import { i18n } from "@/i18n";
|
||||||
|
import { $i } from "@/account";
|
||||||
|
|
||||||
export default defineComponent({
|
const props = defineProps<{
|
||||||
components: {
|
token: string;
|
||||||
XForm,
|
}>();
|
||||||
MkSignin,
|
const state = ref("");
|
||||||
MkKeyValue,
|
const session = ref();
|
||||||
},
|
const fetching = ref(true);
|
||||||
props: ["token"],
|
const auth_code = ref("");
|
||||||
data() {
|
|
||||||
return {
|
|
||||||
state: null,
|
|
||||||
session: null,
|
|
||||||
fetching: true,
|
|
||||||
i18n,
|
|
||||||
auth_code: null,
|
|
||||||
};
|
|
||||||
},
|
|
||||||
mounted() {
|
|
||||||
if (!this.$i) return;
|
|
||||||
|
|
||||||
// Fetch session
|
onMounted(() => {
|
||||||
os.api("auth/session/show", {
|
if (!$i) return;
|
||||||
token: this.token,
|
|
||||||
})
|
|
||||||
.then((session) => {
|
|
||||||
this.session = session;
|
|
||||||
this.fetching = false;
|
|
||||||
|
|
||||||
// 既に連携していた場合
|
os.api("auth/session/show", { token: props.token })
|
||||||
if (this.session.app.isAuthorized) {
|
.then((sess: any) => {
|
||||||
os.api("auth/accept", {
|
session.value = sess;
|
||||||
token: this.session.token,
|
fetching.value = false;
|
||||||
}).then(() => {
|
|
||||||
this.accepted();
|
if (session.value.app.isAuthorized) {
|
||||||
});
|
os.api("auth/accept", { token: session.value.token }).then(
|
||||||
} else {
|
() => {
|
||||||
this.state = "waiting";
|
accepted();
|
||||||
}
|
},
|
||||||
})
|
);
|
||||||
.catch((error) => {
|
} else {
|
||||||
this.state = "fetch-session-error";
|
state.value = "waiting";
|
||||||
this.fetching = false;
|
|
||||||
});
|
|
||||||
},
|
|
||||||
methods: {
|
|
||||||
accepted() {
|
|
||||||
this.state = "accepted";
|
|
||||||
const getUrlParams = () =>
|
|
||||||
window.location.search
|
|
||||||
.substring(1)
|
|
||||||
.split("&")
|
|
||||||
.reduce((result, query) => {
|
|
||||||
const [k, v] = query.split("=");
|
|
||||||
result[k] = decodeURI(v);
|
|
||||||
return result;
|
|
||||||
}, {});
|
|
||||||
const isMastodon = !!getUrlParams().mastodon;
|
|
||||||
if (this.session.app.callbackUrl && isMastodon) {
|
|
||||||
const callbackUrl = new URL(this.session.app.callbackUrl);
|
|
||||||
callbackUrl.searchParams.append("code", this.session.token);
|
|
||||||
if (getUrlParams().state)
|
|
||||||
callbackUrl.searchParams.append(
|
|
||||||
"state",
|
|
||||||
getUrlParams().state,
|
|
||||||
);
|
|
||||||
location.href = callbackUrl.toString();
|
|
||||||
} else if (this.session.app.callbackUrl) {
|
|
||||||
const url = new URL(this.session.app.callbackUrl);
|
|
||||||
if (
|
|
||||||
[
|
|
||||||
"javascript:",
|
|
||||||
"file:",
|
|
||||||
"data:",
|
|
||||||
"mailto:",
|
|
||||||
"tel:",
|
|
||||||
].includes(url.protocol)
|
|
||||||
)
|
|
||||||
throw new Error("invalid url");
|
|
||||||
if (
|
|
||||||
this.session.app.callbackUrl === "urn:ietf:wg:oauth:2.0:oob"
|
|
||||||
) {
|
|
||||||
this.auth_code = this.session.token;
|
|
||||||
} else {
|
|
||||||
location.href = `${this.session.app.callbackUrl}?token=${
|
|
||||||
this.session.token
|
|
||||||
}&code=${this.session.token}&state=${
|
|
||||||
getUrlParams().state || ""
|
|
||||||
}`;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
},
|
})
|
||||||
onLogin(res) {
|
.catch((error) => {
|
||||||
login(res.i);
|
state.value = "fetch-session-error";
|
||||||
},
|
fetching.value = false;
|
||||||
},
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
const getUrlParams = () =>
|
||||||
|
window.location.search
|
||||||
|
.substring(1)
|
||||||
|
.split("&")
|
||||||
|
.reduce((result, query) => {
|
||||||
|
const [k, v] = query.split("=");
|
||||||
|
result[k] = decodeURI(v);
|
||||||
|
return result;
|
||||||
|
}, {});
|
||||||
|
|
||||||
|
const accepted = () => {
|
||||||
|
state.value = "accepted";
|
||||||
|
const isMastodon = !!getUrlParams().mastodon;
|
||||||
|
if (session.value.app.callbackUrl && isMastodon) {
|
||||||
|
const redirectUri = decodeURIComponent(getUrlParams().redirect_uri);
|
||||||
|
if (
|
||||||
|
!session.value.app.callbackUrl
|
||||||
|
.split("\n")
|
||||||
|
.some((p) => p === redirectUri)
|
||||||
|
) {
|
||||||
|
state.value = "fetch-session-error";
|
||||||
|
fetching.value = false;
|
||||||
|
throw new Error("Callback URI doesn't match registered app");
|
||||||
|
}
|
||||||
|
const callbackUrl = new URL(redirectUri);
|
||||||
|
callbackUrl.searchParams.append("code", session.value.token);
|
||||||
|
if (getUrlParams().state)
|
||||||
|
callbackUrl.searchParams.append("state", getUrlParams().state);
|
||||||
|
location.href = callbackUrl.toString();
|
||||||
|
} else if (session.value.app.callbackUrl) {
|
||||||
|
const url = new URL(session.value.app.callbackUrl);
|
||||||
|
if (
|
||||||
|
["javascript:", "file:", "data:", "mailto:", "tel:"].includes(
|
||||||
|
url.protocol,
|
||||||
|
)
|
||||||
|
) {
|
||||||
|
throw new Error("Invalid URL");
|
||||||
|
}
|
||||||
|
if (session.value.app.callbackUrl === "urn:ietf:wg:oauth:2.0:oob") {
|
||||||
|
auth_code.value = session.value.token;
|
||||||
|
} else {
|
||||||
|
location.href = `${session.value.app.callbackUrl}?token=${
|
||||||
|
session.value.token
|
||||||
|
}&code=${session.value.token}&state=${getUrlParams().state || ""}`;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const onLogin = (res) => {
|
||||||
|
login(res.i);
|
||||||
|
};
|
||||||
</script>
|
</script>
|
||||||
|
|
|
@ -17,6 +17,15 @@
|
||||||
</template>
|
</template>
|
||||||
</FormSelect>
|
</FormSelect>
|
||||||
|
|
||||||
|
<FormSelect v-model="translateLang" class="_formBlock">
|
||||||
|
<template #label>
|
||||||
|
{{ i18n.ts.languageForTranslation }}
|
||||||
|
</template>
|
||||||
|
<option v-for="x in langs" :key="x[0]" :value="x[0]">
|
||||||
|
{{ x[1] }}
|
||||||
|
</option>
|
||||||
|
</FormSelect>
|
||||||
|
|
||||||
<FormRadios v-model="overridedDeviceKind" class="_formBlock">
|
<FormRadios v-model="overridedDeviceKind" class="_formBlock">
|
||||||
<template #label>{{ i18n.ts.overridedDeviceKind }}</template>
|
<template #label>{{ i18n.ts.overridedDeviceKind }}</template>
|
||||||
<option :value="null">{{ i18n.ts.auto }}</option>
|
<option :value="null">{{ i18n.ts.auto }}</option>
|
||||||
|
@ -71,6 +80,9 @@
|
||||||
{{ i18n.ts.reflectMayTakeTime }}</template
|
{{ i18n.ts.reflectMayTakeTime }}</template
|
||||||
></FormSwitch
|
></FormSwitch
|
||||||
>
|
>
|
||||||
|
<FormSwitch v-model="detectPostLanguage" class="_formBlock">{{
|
||||||
|
i18n.ts.detectPostLanguage
|
||||||
|
}}</FormSwitch>
|
||||||
|
|
||||||
<FormSelect v-model="serverDisconnectedBehavior" class="_formBlock">
|
<FormSelect v-model="serverDisconnectedBehavior" class="_formBlock">
|
||||||
<template #label>{{ i18n.ts.whenServerDisconnected }}</template>
|
<template #label>{{ i18n.ts.whenServerDisconnected }}</template>
|
||||||
|
@ -266,6 +278,7 @@ import { definePageMetadata } from "@/scripts/page-metadata";
|
||||||
import { deviceKind } from "@/scripts/device-kind";
|
import { deviceKind } from "@/scripts/device-kind";
|
||||||
|
|
||||||
const lang = ref(localStorage.getItem("lang"));
|
const lang = ref(localStorage.getItem("lang"));
|
||||||
|
const translateLang = ref(localStorage.getItem("translateLang"));
|
||||||
const fontSize = ref(localStorage.getItem("fontSize"));
|
const fontSize = ref(localStorage.getItem("fontSize"));
|
||||||
const useSystemFont = ref(localStorage.getItem("useSystemFont") != null);
|
const useSystemFont = ref(localStorage.getItem("useSystemFont") != null);
|
||||||
|
|
||||||
|
@ -357,6 +370,9 @@ const showAdminUpdates = computed(
|
||||||
const showTimelineReplies = computed(
|
const showTimelineReplies = computed(
|
||||||
defaultStore.makeGetterSetter("showTimelineReplies"),
|
defaultStore.makeGetterSetter("showTimelineReplies"),
|
||||||
);
|
);
|
||||||
|
const detectPostLanguage = computed(
|
||||||
|
defaultStore.makeGetterSetter("detectPostLanguage"),
|
||||||
|
);
|
||||||
|
|
||||||
watch(swipeOnDesktop, () => {
|
watch(swipeOnDesktop, () => {
|
||||||
defaultStore.set("swipeOnMobile", true);
|
defaultStore.set("swipeOnMobile", true);
|
||||||
|
@ -367,6 +383,10 @@ watch(lang, () => {
|
||||||
localStorage.removeItem("locale");
|
localStorage.removeItem("locale");
|
||||||
});
|
});
|
||||||
|
|
||||||
|
watch(translateLang, () => {
|
||||||
|
localStorage.setItem("translateLang", translateLang.value as string);
|
||||||
|
});
|
||||||
|
|
||||||
watch(fontSize, () => {
|
watch(fontSize, () => {
|
||||||
if (fontSize.value == null) {
|
if (fontSize.value == null) {
|
||||||
localStorage.removeItem("fontSize");
|
localStorage.removeItem("fontSize");
|
||||||
|
@ -386,6 +406,7 @@ watch(useSystemFont, () => {
|
||||||
watch(
|
watch(
|
||||||
[
|
[
|
||||||
lang,
|
lang,
|
||||||
|
translateLang,
|
||||||
fontSize,
|
fontSize,
|
||||||
useSystemFont,
|
useSystemFont,
|
||||||
enableInfiniteScroll,
|
enableInfiniteScroll,
|
||||||
|
|
|
@ -52,6 +52,14 @@
|
||||||
i18n.ts.hideOnlineStatusDescription
|
i18n.ts.hideOnlineStatusDescription
|
||||||
}}</template>
|
}}</template>
|
||||||
</FormSwitch>
|
</FormSwitch>
|
||||||
|
<FormSwitch
|
||||||
|
v-model="isIndexable"
|
||||||
|
class="_formBlock"
|
||||||
|
@update:modelValue="save()"
|
||||||
|
>
|
||||||
|
{{ i18n.ts.indexable }}
|
||||||
|
<template #caption>{{ i18n.ts.indexableDescription }}</template>
|
||||||
|
</FormSwitch>
|
||||||
<FormSwitch
|
<FormSwitch
|
||||||
v-model="noCrawle"
|
v-model="noCrawle"
|
||||||
class="_formBlock"
|
class="_formBlock"
|
||||||
|
@ -155,6 +163,7 @@ import { definePageMetadata } from "@/scripts/page-metadata";
|
||||||
const isLocked = ref($i.isLocked);
|
const isLocked = ref($i.isLocked);
|
||||||
const autoAcceptFollowed = ref($i.autoAcceptFollowed);
|
const autoAcceptFollowed = ref($i.autoAcceptFollowed);
|
||||||
const noCrawle = ref($i.noCrawle);
|
const noCrawle = ref($i.noCrawle);
|
||||||
|
const isIndexable = ref($i.isIndexable);
|
||||||
const isExplorable = ref($i.isExplorable);
|
const isExplorable = ref($i.isExplorable);
|
||||||
const hideOnlineStatus = ref($i.hideOnlineStatus);
|
const hideOnlineStatus = ref($i.hideOnlineStatus);
|
||||||
const publicReactions = ref($i.publicReactions);
|
const publicReactions = ref($i.publicReactions);
|
||||||
|
@ -178,6 +187,7 @@ function save() {
|
||||||
isLocked: !!isLocked.value,
|
isLocked: !!isLocked.value,
|
||||||
autoAcceptFollowed: !!autoAcceptFollowed.value,
|
autoAcceptFollowed: !!autoAcceptFollowed.value,
|
||||||
noCrawle: !!noCrawle.value,
|
noCrawle: !!noCrawle.value,
|
||||||
|
isIndexable: !!isIndexable.value,
|
||||||
isExplorable: !!isExplorable.value,
|
isExplorable: !!isExplorable.value,
|
||||||
hideOnlineStatus: !!hideOnlineStatus.value,
|
hideOnlineStatus: !!hideOnlineStatus.value,
|
||||||
publicReactions: !!publicReactions.value,
|
publicReactions: !!publicReactions.value,
|
||||||
|
|
|
@ -237,15 +237,35 @@ export function getNoteMenu(props: {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async function translate_(noteId: number, targetLang: string) {
|
||||||
|
return await os.api("notes/translate", {
|
||||||
|
noteId: noteId,
|
||||||
|
targetLang: targetLang,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
async function translate(): Promise<void> {
|
async function translate(): Promise<void> {
|
||||||
|
const translateLang = localStorage.getItem("translateLang");
|
||||||
|
const lang = localStorage.getItem("lang");
|
||||||
|
|
||||||
if (props.translation.value != null) return;
|
if (props.translation.value != null) return;
|
||||||
props.translating.value = true;
|
props.translating.value = true;
|
||||||
const res = await os.api("notes/translate", {
|
props.translation.value = await translate_(
|
||||||
noteId: appearNote.id,
|
appearNote.id,
|
||||||
targetLang: localStorage.getItem("lang") || navigator.language,
|
translateLang || lang || navigator.language,
|
||||||
});
|
);
|
||||||
|
|
||||||
|
// use UI language as the second translation target
|
||||||
|
if (
|
||||||
|
translateLang != null &&
|
||||||
|
lang != null &&
|
||||||
|
translateLang !== lang &&
|
||||||
|
(!props.translation.value ||
|
||||||
|
props.translation.value.sourceLang.toLowerCase() ===
|
||||||
|
translateLang.slice(0, 2))
|
||||||
|
)
|
||||||
|
props.translation.value = await translate_(appearNote.id, lang);
|
||||||
props.translating.value = false;
|
props.translating.value = false;
|
||||||
props.translation.value = res;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
let menu;
|
let menu;
|
||||||
|
|
|
@ -1,6 +1,5 @@
|
||||||
import { markRaw, ref } from "vue";
|
import { markRaw, ref } from "vue";
|
||||||
import { Storage } from "./pizzax";
|
import { Storage } from "./pizzax";
|
||||||
import { Theme } from "./scripts/theme";
|
|
||||||
|
|
||||||
export const postFormActions = [];
|
export const postFormActions = [];
|
||||||
export const userActions = [];
|
export const userActions = [];
|
||||||
|
@ -346,6 +345,10 @@ export const defaultStore = markRaw(
|
||||||
where: "account",
|
where: "account",
|
||||||
default: true,
|
default: true,
|
||||||
},
|
},
|
||||||
|
detectPostLanguage: {
|
||||||
|
where: "deviceAccount",
|
||||||
|
default: true,
|
||||||
|
},
|
||||||
}),
|
}),
|
||||||
);
|
);
|
||||||
|
|
||||||
|
|
|
@ -57,6 +57,14 @@
|
||||||
><i class="ph-image-square ph-bold ph-lg icon"></i
|
><i class="ph-image-square ph-bold ph-lg icon"></i
|
||||||
>{{ i18n.ts.gallery }}</MkA
|
>{{ i18n.ts.gallery }}</MkA
|
||||||
>
|
>
|
||||||
|
<button
|
||||||
|
class="_button link"
|
||||||
|
active-class="active"
|
||||||
|
@click="search()"
|
||||||
|
>
|
||||||
|
<i class="ph-magnifying-glass ph-bold ph-lg icon"></i
|
||||||
|
><span>{{ i18n.ts.search }}</span>
|
||||||
|
</button>
|
||||||
<div class="action">
|
<div class="action">
|
||||||
<button class="_buttonPrimary" @click="signup()">
|
<button class="_buttonPrimary" @click="signup()">
|
||||||
{{ i18n.ts.signup }}
|
{{ i18n.ts.signup }}
|
||||||
|
|
605
pnpm-lock.yaml
605
pnpm-lock.yaml
File diff suppressed because it is too large
Load diff
Loading…
Reference in a new issue