mirror of
https://github.com/cheeaun/phanpy.git
synced 2024-11-21 16:55:25 +03:00
Merge branch 'main' into feature-detect
This commit is contained in:
commit
7b926f73fb
63 changed files with 21154 additions and 19559 deletions
41
.github/ISSUE_TEMPLATE/bug_report.md
vendored
41
.github/ISSUE_TEMPLATE/bug_report.md
vendored
|
@ -1,41 +0,0 @@
|
||||||
---
|
|
||||||
name: Bug report
|
|
||||||
about: Create a report to help us improve
|
|
||||||
title: ''
|
|
||||||
labels: ''
|
|
||||||
assignees: ''
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
**Describe the bug**
|
|
||||||
A clear and concise description of what the bug is.
|
|
||||||
- Which site: [e.g. dev.phanpy.social OR phanpy.social]
|
|
||||||
- Which site version: [On Phanpy, go to Settings -> About]
|
|
||||||
- Which instance: [e.g. mastodon.social]
|
|
||||||
|
|
||||||
**To Reproduce**
|
|
||||||
Steps to reproduce the behavior:
|
|
||||||
1. Go to '...'
|
|
||||||
2. Click on '....'
|
|
||||||
3. Scroll down to '....'
|
|
||||||
4. See error
|
|
||||||
|
|
||||||
**Expected behavior**
|
|
||||||
A clear and concise description of what you expected to happen.
|
|
||||||
|
|
||||||
**Screenshots**
|
|
||||||
If applicable, add screenshots to help explain your problem.
|
|
||||||
|
|
||||||
**Desktop (please complete the following information):**
|
|
||||||
- OS: [e.g. iOS]
|
|
||||||
- Browser [e.g. chrome, safari]
|
|
||||||
- Version [e.g. 22]
|
|
||||||
|
|
||||||
**Smartphone (please complete the following information):**
|
|
||||||
- Device: [e.g. iPhone6]
|
|
||||||
- OS: [e.g. iOS8.1]
|
|
||||||
- Browser [e.g. stock browser, safari]
|
|
||||||
- Version [e.g. 22]
|
|
||||||
|
|
||||||
**Additional context**
|
|
||||||
Add any other context about the problem here.
|
|
81
.github/ISSUE_TEMPLATE/bug_report.yml
vendored
Normal file
81
.github/ISSUE_TEMPLATE/bug_report.yml
vendored
Normal file
|
@ -0,0 +1,81 @@
|
||||||
|
name: "Bug report"
|
||||||
|
description: "Create a report to help us improve"
|
||||||
|
|
||||||
|
labels:
|
||||||
|
- "bug"
|
||||||
|
|
||||||
|
body:
|
||||||
|
- type: input
|
||||||
|
id: "site"
|
||||||
|
attributes:
|
||||||
|
label: "Site"
|
||||||
|
description: |-
|
||||||
|
What site(s) did you encounter this bug on?
|
||||||
|
placeholder: |-
|
||||||
|
phanpy.social
|
||||||
|
|
||||||
|
- type: input
|
||||||
|
id: "version"
|
||||||
|
attributes:
|
||||||
|
label: "Version"
|
||||||
|
description: |-
|
||||||
|
Which Phanpy version(s) did you encounter this bug on?
|
||||||
|
You can see and copy your current version by opening the Settings menu and scrolling down to the About section.
|
||||||
|
placeholder: |-
|
||||||
|
2024.10.08.0a176e2
|
||||||
|
|
||||||
|
- type: input
|
||||||
|
id: "instance"
|
||||||
|
attributes:
|
||||||
|
label: "Instance"
|
||||||
|
description: |-
|
||||||
|
Which instance(s) did you encounter this bug on?
|
||||||
|
placeholder: |-
|
||||||
|
mastodon.social
|
||||||
|
|
||||||
|
- type: textarea
|
||||||
|
id: "Browser"
|
||||||
|
attributes:
|
||||||
|
label: "Browser"
|
||||||
|
description: |-
|
||||||
|
Which browser(s) did you encounter this bug on?
|
||||||
|
placeholder: |-
|
||||||
|
- Firefox 132.0b5 on Windows 11
|
||||||
|
- Safari 18 on iOS 18 on iPhone 16 Pro Max
|
||||||
|
|
||||||
|
- type: textarea
|
||||||
|
id: "description"
|
||||||
|
attributes:
|
||||||
|
label: "Bug description"
|
||||||
|
description: |-
|
||||||
|
A concise description of what the bug is.
|
||||||
|
If applicable, add screenshots to help explain your problem.
|
||||||
|
You can paste screenshots here and GitHub will convert them to Markdown for you.
|
||||||
|
|
||||||
|
- type: textarea
|
||||||
|
id: "steps"
|
||||||
|
attributes:
|
||||||
|
label: "To reproduce"
|
||||||
|
description: |-
|
||||||
|
A list of steps that can be performed to make the bug happen again.
|
||||||
|
If possible, add screenshots to help demonstrate the steps.
|
||||||
|
You can paste screenshots here and GitHub will convert them to Markdown for you.
|
||||||
|
placeholder: |-
|
||||||
|
1. Go to '...'
|
||||||
|
2. Click on '...'
|
||||||
|
3. Scroll down to '...'
|
||||||
|
4. See error
|
||||||
|
|
||||||
|
- type: textarea
|
||||||
|
id: "behavior"
|
||||||
|
attributes:
|
||||||
|
label: "Expected behavior"
|
||||||
|
description: |-
|
||||||
|
A concise description of what you expected to happen.
|
||||||
|
|
||||||
|
- type: textarea
|
||||||
|
id: "other"
|
||||||
|
attributes:
|
||||||
|
label: "Other"
|
||||||
|
description: |-
|
||||||
|
Anything you want to add?
|
1
.github/ISSUE_TEMPLATE/config.yml
vendored
Normal file
1
.github/ISSUE_TEMPLATE/config.yml
vendored
Normal file
|
@ -0,0 +1 @@
|
||||||
|
blank_issues_enabled: true
|
20
.github/ISSUE_TEMPLATE/feature_request.md
vendored
20
.github/ISSUE_TEMPLATE/feature_request.md
vendored
|
@ -1,20 +0,0 @@
|
||||||
---
|
|
||||||
name: Feature request
|
|
||||||
about: Suggest an idea for this project
|
|
||||||
title: ''
|
|
||||||
labels: 'enhancement'
|
|
||||||
assignees: ''
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
**Is your feature request related to a problem? Please describe.**
|
|
||||||
A clear and concise description of what the problem is. Ex. I'm always frustrated when [...]
|
|
||||||
|
|
||||||
**Describe the solution you'd like**
|
|
||||||
A clear and concise description of what you want to happen.
|
|
||||||
|
|
||||||
**Describe alternatives you've considered**
|
|
||||||
A clear and concise description of any alternative solutions or features you've considered.
|
|
||||||
|
|
||||||
**Additional context**
|
|
||||||
Add any other context or screenshots about the feature request here.
|
|
37
.github/ISSUE_TEMPLATE/feature_request.yml
vendored
Normal file
37
.github/ISSUE_TEMPLATE/feature_request.yml
vendored
Normal file
|
@ -0,0 +1,37 @@
|
||||||
|
name: "Feature request"
|
||||||
|
description: "Suggest an idea for this project"
|
||||||
|
|
||||||
|
labels:
|
||||||
|
- "enhancement"
|
||||||
|
|
||||||
|
|
||||||
|
body:
|
||||||
|
- type: textarea
|
||||||
|
id: "problem"
|
||||||
|
attributes:
|
||||||
|
label: "Problem I have"
|
||||||
|
description: |-
|
||||||
|
If your request is related to a problem, please provide a clear and concise description of what the problem is.
|
||||||
|
placeholder: |-
|
||||||
|
I'm always frustrated when [...]
|
||||||
|
|
||||||
|
- type: textarea
|
||||||
|
id: "solution"
|
||||||
|
attributes:
|
||||||
|
label: "Solution I'd like"
|
||||||
|
description: |-
|
||||||
|
A clear and concise description of what you want to happen.
|
||||||
|
|
||||||
|
- type: textarea
|
||||||
|
id: "alternatives"
|
||||||
|
attributes:
|
||||||
|
label: "Alternatives considered"
|
||||||
|
description: |-
|
||||||
|
A clear and concise description of any alternative solutions or features you've considered.
|
||||||
|
|
||||||
|
- type: textarea
|
||||||
|
id: "other"
|
||||||
|
attributes:
|
||||||
|
label: "Other"
|
||||||
|
description: |-
|
||||||
|
Anything you want to add?
|
28
README.md
28
README.md
|
@ -259,18 +259,20 @@ See documentation for [lingva-translate](https://github.com/thedaviddelta/lingva
|
||||||
These are self-hosted by other wonderful folks.
|
These are self-hosted by other wonderful folks.
|
||||||
|
|
||||||
- [ferengi.one](https://m.ferengi.one/) by [@david@weaknotes.com](https://weaknotes.com/@david)
|
- [ferengi.one](https://m.ferengi.one/) by [@david@weaknotes.com](https://weaknotes.com/@david)
|
||||||
- [phanpy.blaede.family](https://phanpy.blaede.family/) by [@cassidy@blaede.family](https://mastodon.blaede.family/@cassidy)
|
|
||||||
- [phanpy.mstdn.mx](https://phanpy.mstdn.mx/) by [@maop@mstdn.mx](https://mstdn.mx/@maop)
|
|
||||||
- [phanpy.vmst.io](https://phanpy.vmst.io/) by [@vmstan@vmst.io](https://vmst.io/@vmstan)
|
|
||||||
- [phanpy.gotosocial.social](https://phanpy.gotosocial.social/) by [@admin@gotosocial.social](https://gotosocial.social/@admin)
|
|
||||||
- [phanpy.bauxite.tech](https://phanpy.bauxite.tech) by [@b4ux1t3@hachyderm.io](https://hachyderm.io/@b4ux1t3)
|
|
||||||
- [phanpy.hear-me.social](https://phanpy.hear-me.social) by [@admin@hear-me.social](https://hear-me.social/@admin)
|
|
||||||
- [phanpy.fulda.social](https://phanpy.fulda.social) by [@Ganneff@fulda.social](https://fulda.social/@Ganneff)
|
|
||||||
- [phanpy.crmbl.uk](https://phanpy.crmbl.uk) by [@snail@crmbl.uk](https://mstdn.crmbl.uk/@snail)
|
|
||||||
- [halo.mookiesplace.com](https://halo.mookiesplace.com) by [@mookie@mookiesplace.com](https://mookiesplace.com/@mookie)
|
- [halo.mookiesplace.com](https://halo.mookiesplace.com) by [@mookie@mookiesplace.com](https://mookiesplace.com/@mookie)
|
||||||
- [social.qrk.one](https://social.qrk.one) by [@kev@fosstodon.org](https://fosstodon.org/@kev)
|
- [phanpy.bauxite.tech](https://phanpy.bauxite.tech) by [@b4ux1t3@hachyderm.io](https://hachyderm.io/@b4ux1t3)
|
||||||
|
- [phanpy.blaede.family](https://phanpy.blaede.family/) by [@cassidy@blaede.family](https://mastodon.blaede.family/@cassidy)
|
||||||
|
- [phanpy.crmbl.uk](https://phanpy.crmbl.uk) by [@snail@crmbl.uk](https://mstdn.crmbl.uk/@snail)
|
||||||
- [phanpy.cz](https://phanpy.cz) by [@zdendys@mamutovo.cz](https://mamutovo.cz/@zdendys)
|
- [phanpy.cz](https://phanpy.cz) by [@zdendys@mamutovo.cz](https://mamutovo.cz/@zdendys)
|
||||||
|
- [phanpy.fulda.social](https://phanpy.fulda.social) by [@Ganneff@fulda.social](https://fulda.social/@Ganneff)
|
||||||
|
- [phanpy.gotosocial.social](https://phanpy.gotosocial.social/) by [@admin@gotosocial.social](https://gotosocial.social/@admin)
|
||||||
|
- [phanpy.hear-me.social](https://phanpy.hear-me.social) by [@admin@hear-me.social](https://hear-me.social/@admin)
|
||||||
|
- [phanpy.mastodon.world](https://phanpy.mastodon.world) by [@ruud@mastodon.world](https://mastodon.world/@ruud)
|
||||||
|
- [phanpy.mstdn.mx](https://phanpy.mstdn.mx/) by [@maop@mstdn.mx](https://mstdn.mx/@maop)
|
||||||
- [phanpy.social.tchncs.de](https://phanpy.social.tchncs.de) by [@milan@social.tchncs.de](https://social.tchncs.de/@milan)
|
- [phanpy.social.tchncs.de](https://phanpy.social.tchncs.de) by [@milan@social.tchncs.de](https://social.tchncs.de/@milan)
|
||||||
|
- [phanpy.tilde.zone](https://phanpy.tilde.zone) by [@ben@tilde.zone](https://tilde.zone/@ben)
|
||||||
|
- [phanpy.vmst.io](https://phanpy.vmst.io/) by [@vmstan@vmst.io](https://vmst.io/@vmstan)
|
||||||
|
- [social.qrk.one](https://social.qrk.one) by [@kev@fosstodon.org](https://fosstodon.org/@kev)
|
||||||
|
|
||||||
> Note: Add yours by creating a pull request.
|
> Note: Add yours by creating a pull request.
|
||||||
|
|
||||||
|
@ -303,7 +305,7 @@ Costs involved in running and developing this web app:
|
||||||
- <img src="https://crowdin-static.downloads.crowdin.com/avatar/15525631/medium/51293156034d0236f1a1020c10f7d539_default.png" alt="" width="16" height="16" /> cbo92 (French)
|
- <img src="https://crowdin-static.downloads.crowdin.com/avatar/15525631/medium/51293156034d0236f1a1020c10f7d539_default.png" alt="" width="16" height="16" /> cbo92 (French)
|
||||||
- <img src="https://crowdin-static.downloads.crowdin.com/avatar/15910131/medium/67fab7eeab5551853450e76e2ef19e59.jpeg" alt="" width="16" height="16" /> CDN (Chinese Simplified)
|
- <img src="https://crowdin-static.downloads.crowdin.com/avatar/15910131/medium/67fab7eeab5551853450e76e2ef19e59.jpeg" alt="" width="16" height="16" /> CDN (Chinese Simplified)
|
||||||
- <img src="https://crowdin-static.downloads.crowdin.com/avatar/16556801/medium/ed5e501ca1f3cc6525d2da28db646346.jpeg" alt="" width="16" height="16" /> dannypsnl (Chinese Traditional)
|
- <img src="https://crowdin-static.downloads.crowdin.com/avatar/16556801/medium/ed5e501ca1f3cc6525d2da28db646346.jpeg" alt="" width="16" height="16" /> dannypsnl (Chinese Traditional)
|
||||||
- <img src="https://crowdin-static.downloads.crowdin.com/avatar/3711/medium/d95ddd44e8dcb3a039f8a3463aed781d_default.png" alt="" width="16" height="16" /> databio (Catalan)
|
- <img src="https://crowdin-static.downloads.crowdin.com/avatar/3711/medium/c97239bb54623a50eb43cc6b801bb156.jpg" alt="" width="16" height="16" /> databio (Catalan)
|
||||||
- <img src="https://crowdin-static.downloads.crowdin.com/avatar/16533843/medium/ac7af8776858a992d992cf6702d1aaae.jpg" alt="" width="16" height="16" /> Dizro (Italian)
|
- <img src="https://crowdin-static.downloads.crowdin.com/avatar/16533843/medium/ac7af8776858a992d992cf6702d1aaae.jpg" alt="" width="16" height="16" /> Dizro (Italian)
|
||||||
- <img src="https://crowdin-static.downloads.crowdin.com/avatar/16574625/medium/f2ac3a4f32f104a3a6d4085d4bcb3924_default.png" alt="" width="16" height="16" /> Drift6944 (Czech)
|
- <img src="https://crowdin-static.downloads.crowdin.com/avatar/16574625/medium/f2ac3a4f32f104a3a6d4085d4bcb3924_default.png" alt="" width="16" height="16" /> Drift6944 (Czech)
|
||||||
- <img src="https://crowdin-static.downloads.crowdin.com/avatar/12618120/medium/ccb11bd042bbf4c7189033f7af2dbd32_default.png" alt="" width="16" height="16" /> drydenwu (Chinese Traditional)
|
- <img src="https://crowdin-static.downloads.crowdin.com/avatar/12618120/medium/ccb11bd042bbf4c7189033f7af2dbd32_default.png" alt="" width="16" height="16" /> drydenwu (Chinese Traditional)
|
||||||
|
@ -315,11 +317,13 @@ Costs involved in running and developing this web app:
|
||||||
- <img src="https://crowdin-static.downloads.crowdin.com/avatar/15248754/medium/0dac6334ea0f4e8d4194a605c0a5594a.jpeg" alt="" width="16" height="16" /> hongminhee (Korean)
|
- <img src="https://crowdin-static.downloads.crowdin.com/avatar/15248754/medium/0dac6334ea0f4e8d4194a605c0a5594a.jpeg" alt="" width="16" height="16" /> hongminhee (Korean)
|
||||||
- <img src="https://crowdin-static.downloads.crowdin.com/avatar/16529833/medium/2122d0c5d61c00786ab6d5e5672d4098.png" alt="" width="16" height="16" /> Hugoglyph (Esperanto, Spanish)
|
- <img src="https://crowdin-static.downloads.crowdin.com/avatar/16529833/medium/2122d0c5d61c00786ab6d5e5672d4098.png" alt="" width="16" height="16" /> Hugoglyph (Esperanto, Spanish)
|
||||||
- <img src="https://crowdin-static.downloads.crowdin.com/avatar/13454728/medium/1f78b7124b3c962bc4ae55e8d701fc91_default.png" alt="" width="16" height="16" /> isard (Catalan)
|
- <img src="https://crowdin-static.downloads.crowdin.com/avatar/13454728/medium/1f78b7124b3c962bc4ae55e8d701fc91_default.png" alt="" width="16" height="16" /> isard (Catalan)
|
||||||
|
- <img src="https://crowdin-static.downloads.crowdin.com/avatar/16646485/medium/5d76c44212a4048a815ab437fb170856_default.png" alt="" width="16" height="16" /> kaliuwu (Polish)
|
||||||
- <img src="https://crowdin-static.downloads.crowdin.com/avatar/16532403/medium/4cefb19623bcc44d7cdb9e25aebf5250.jpeg" alt="" width="16" height="16" /> karlafej (Czech)
|
- <img src="https://crowdin-static.downloads.crowdin.com/avatar/16532403/medium/4cefb19623bcc44d7cdb9e25aebf5250.jpeg" alt="" width="16" height="16" /> karlafej (Czech)
|
||||||
- <img src="https://crowdin-static.downloads.crowdin.com/avatar/15791971/medium/88bdda3090339f16f6083390d32bb434_default.png" alt="" width="16" height="16" /> katullo11 (Italian)
|
- <img src="https://crowdin-static.downloads.crowdin.com/avatar/15791971/medium/88bdda3090339f16f6083390d32bb434_default.png" alt="" width="16" height="16" /> katullo11 (Italian)
|
||||||
- <img src="https://crowdin-static.downloads.crowdin.com/avatar/14677260/medium/e53420d200961f48602324e18c091bdc.png" alt="" width="16" height="16" /> Kytta (German)
|
- <img src="https://crowdin-static.downloads.crowdin.com/avatar/14677260/medium/e53420d200961f48602324e18c091bdc.png" alt="" width="16" height="16" /> Kytta (German)
|
||||||
- <img src="https://crowdin-static.downloads.crowdin.com/avatar/16529521/medium/ae6add93a901b0fefa2d9b1077920d73.png" alt="" width="16" height="16" /> llun (Thai)
|
- <img src="https://crowdin-static.downloads.crowdin.com/avatar/16529521/medium/ae6add93a901b0fefa2d9b1077920d73.png" alt="" width="16" height="16" /> llun (Thai)
|
||||||
- <img src="https://crowdin-static.downloads.crowdin.com/avatar/16291756/medium/2366972cc86287353708aff1ded3f3c1.jpg" alt="" width="16" height="16" /> lucasofchirst (Occitan, Portuguese, Portuguese, Brazilian)
|
- <img src="https://crowdin-static.downloads.crowdin.com/avatar/16291756/medium/c008af10bc117fa9c9dcb70f2b291ee6.jpg" alt="" width="16" height="16" /> lucasofchirst (Occitan, Portuguese, Portuguese, Brazilian)
|
||||||
|
- <img src="https://crowdin-static.downloads.crowdin.com/avatar/16640089/medium/4b7d8d275d7a7bff564adde51e09b473_default.png" alt="" width="16" height="16" /> LukeHong (Chinese Traditional)
|
||||||
- <img src="https://crowdin-static.downloads.crowdin.com/avatar/16537713/medium/825f0bf1a14fc545a76891a52839d86e_default.png" alt="" width="16" height="16" /> marcin.kozinski (Polish)
|
- <img src="https://crowdin-static.downloads.crowdin.com/avatar/16537713/medium/825f0bf1a14fc545a76891a52839d86e_default.png" alt="" width="16" height="16" /> marcin.kozinski (Polish)
|
||||||
- <img src="https://crowdin-static.downloads.crowdin.com/avatar/13521465/medium/76cb9aa6b753ce900a70478bff7fcea0.png" alt="" width="16" height="16" /> mkljczkk (Polish)
|
- <img src="https://crowdin-static.downloads.crowdin.com/avatar/13521465/medium/76cb9aa6b753ce900a70478bff7fcea0.png" alt="" width="16" height="16" /> mkljczkk (Polish)
|
||||||
- <img src="https://crowdin-static.downloads.crowdin.com/avatar/12882812/medium/77744d8db46e9a3e09030e1a02b7a572.jpeg" alt="" width="16" height="16" /> mojosoeun (Korean)
|
- <img src="https://crowdin-static.downloads.crowdin.com/avatar/12882812/medium/77744d8db46e9a3e09030e1a02b7a572.jpeg" alt="" width="16" height="16" /> mojosoeun (Korean)
|
||||||
|
@ -337,7 +341,7 @@ Costs involved in running and developing this web app:
|
||||||
- <img src="https://crowdin-static.downloads.crowdin.com/avatar/16539171/medium/0ce95ef6b3b0566136191fbedc1563d0.png" alt="" width="16" height="16" /> SadmL_AI (Russian)
|
- <img src="https://crowdin-static.downloads.crowdin.com/avatar/16539171/medium/0ce95ef6b3b0566136191fbedc1563d0.png" alt="" width="16" height="16" /> SadmL_AI (Russian)
|
||||||
- <img src="https://crowdin-static.downloads.crowdin.com/avatar/12381015/medium/35e3557fd61d85f9a5b84545d9e3feb4.png" alt="" width="16" height="16" /> shuuji3 (Japanese)
|
- <img src="https://crowdin-static.downloads.crowdin.com/avatar/12381015/medium/35e3557fd61d85f9a5b84545d9e3feb4.png" alt="" width="16" height="16" /> shuuji3 (Japanese)
|
||||||
- <img src="https://crowdin-static.downloads.crowdin.com/avatar/14565190/medium/79100599131b7776e9803e4b696915a3_default.png" alt="" width="16" height="16" /> Sky_NiniKo (French)
|
- <img src="https://crowdin-static.downloads.crowdin.com/avatar/14565190/medium/79100599131b7776e9803e4b696915a3_default.png" alt="" width="16" height="16" /> Sky_NiniKo (French)
|
||||||
- <img src="https://crowdin-static.downloads.crowdin.com/avatar/13143526/medium/2f15fa6d8e1703c7b82bb608b116a30a.png" alt="" width="16" height="16" /> Steffo99 (Italian)
|
- <img src="https://crowdin-static.downloads.crowdin.com/avatar/13143526/medium/30871da23d51d7e41bb02f3c92d7f104.png" alt="" width="16" height="16" /> Steffo99 (Italian)
|
||||||
- <img src="https://crowdin-static.downloads.crowdin.com/avatar/16532441/medium/1a47e8d80c95636e02d2260f6e233ca5.png" alt="" width="16" height="16" /> Su5hicz (Czech)
|
- <img src="https://crowdin-static.downloads.crowdin.com/avatar/16532441/medium/1a47e8d80c95636e02d2260f6e233ca5.png" alt="" width="16" height="16" /> Su5hicz (Czech)
|
||||||
- <img src="https://crowdin-static.downloads.crowdin.com/avatar/16530049/medium/683f3581620c6b4a5c753b416ed695a7.jpeg" alt="" width="16" height="16" /> tferrermo (Spanish)
|
- <img src="https://crowdin-static.downloads.crowdin.com/avatar/16530049/medium/683f3581620c6b4a5c753b416ed695a7.jpeg" alt="" width="16" height="16" /> tferrermo (Spanish)
|
||||||
- <img src="https://crowdin-static.downloads.crowdin.com/avatar/15752199/medium/7e9efd828c4691368d063b19d19eb894.png" alt="" width="16" height="16" /> tkbremnes (Norwegian Bokmal)
|
- <img src="https://crowdin-static.downloads.crowdin.com/avatar/15752199/medium/7e9efd828c4691368d063b19d19eb894.png" alt="" width="16" height="16" /> tkbremnes (Norwegian Bokmal)
|
||||||
|
|
694
package-lock.json
generated
694
package-lock.json
generated
File diff suppressed because it is too large
Load diff
26
package.json
26
package.json
|
@ -16,15 +16,15 @@
|
||||||
"readme:i18n-volunteers": "node scripts/update-i18n-volunteers-readme.js"
|
"readme:i18n-volunteers": "node scripts/update-i18n-volunteers-readme.js"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@formatjs/intl-localematcher": "~0.5.4",
|
"@formatjs/intl-localematcher": "~0.5.5",
|
||||||
"@formatjs/intl-segmenter": "~11.5.7",
|
"@formatjs/intl-segmenter": "~11.6.0",
|
||||||
"@formkit/auto-animate": "~0.8.2",
|
"@formkit/auto-animate": "~0.8.2",
|
||||||
"@github/text-expander-element": "~2.7.2",
|
"@github/text-expander-element": "~2.8.0",
|
||||||
"@iconify-icons/mingcute": "~1.2.9",
|
"@iconify-icons/mingcute": "~1.2.9",
|
||||||
"@justinribeiro/lite-youtube": "~1.5.0",
|
"@justinribeiro/lite-youtube": "~1.5.0",
|
||||||
"@lingui/detect-locale": "~4.11.4",
|
"@lingui/detect-locale": "~4.13.0",
|
||||||
"@lingui/macro": "~4.11.4",
|
"@lingui/macro": "~4.13.0",
|
||||||
"@lingui/react": "~4.11.4",
|
"@lingui/react": "~4.13.0",
|
||||||
"@szhsin/react-menu": "~4.2.2",
|
"@szhsin/react-menu": "~4.2.2",
|
||||||
"compare-versions": "~6.1.1",
|
"compare-versions": "~6.1.1",
|
||||||
"fast-blurhash": "~1.1.4",
|
"fast-blurhash": "~1.1.4",
|
||||||
|
@ -36,11 +36,11 @@
|
||||||
"js-cookie": "~3.0.5",
|
"js-cookie": "~3.0.5",
|
||||||
"just-debounce-it": "~3.2.0",
|
"just-debounce-it": "~3.2.0",
|
||||||
"lz-string": "~1.5.0",
|
"lz-string": "~1.5.0",
|
||||||
"masto": "~6.8.0",
|
"masto": "~6.10.1",
|
||||||
"moize": "~6.1.6",
|
"moize": "~6.1.6",
|
||||||
"p-retry": "~6.2.0",
|
"p-retry": "~6.2.0",
|
||||||
"p-throttle": "~6.2.0",
|
"p-throttle": "~6.2.0",
|
||||||
"preact": "~10.24.2",
|
"preact": "~10.24.3",
|
||||||
"punycode": "~2.3.1",
|
"punycode": "~2.3.1",
|
||||||
"react-hotkeys-hook": "~4.5.1",
|
"react-hotkeys-hook": "~4.5.1",
|
||||||
"react-intersection-observer": "~9.13.1",
|
"react-intersection-observer": "~9.13.1",
|
||||||
|
@ -51,22 +51,22 @@
|
||||||
"tinyld": "~1.3.4",
|
"tinyld": "~1.3.4",
|
||||||
"toastify-js": "~1.12.0",
|
"toastify-js": "~1.12.0",
|
||||||
"uid": "~2.0.2",
|
"uid": "~2.0.2",
|
||||||
"use-debounce": "~10.0.3",
|
"use-debounce": "~10.0.4",
|
||||||
"use-long-press": "~3.2.0",
|
"use-long-press": "~3.2.0",
|
||||||
"use-resize-observer": "~9.1.0",
|
"use-resize-observer": "~9.1.0",
|
||||||
"valtio": "2.0.0"
|
"valtio": "2.0.0"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@ianvs/prettier-plugin-sort-imports": "~4.3.1",
|
"@ianvs/prettier-plugin-sort-imports": "~4.3.1",
|
||||||
"@lingui/cli": "~4.11.4",
|
"@lingui/cli": "~4.13.0",
|
||||||
"@lingui/vite-plugin": "~4.11.4",
|
"@lingui/vite-plugin": "~4.13.0",
|
||||||
"@preact/preset-vite": "~2.9.1",
|
"@preact/preset-vite": "~2.9.1",
|
||||||
"babel-plugin-macros": "~3.1.0",
|
"babel-plugin-macros": "~3.1.0",
|
||||||
"postcss": "~8.4.47",
|
"postcss": "~8.4.47",
|
||||||
"postcss-dark-theme-class": "~1.3.0",
|
"postcss-dark-theme-class": "~1.3.0",
|
||||||
"postcss-preset-env": "~10.0.6",
|
"postcss-preset-env": "~10.0.8",
|
||||||
"twitter-text": "~3.1.0",
|
"twitter-text": "~3.1.0",
|
||||||
"vite": "~5.4.8",
|
"vite": "~5.4.10",
|
||||||
"vite-plugin-generate-file": "~0.2.0",
|
"vite-plugin-generate-file": "~0.2.0",
|
||||||
"vite-plugin-html-config": "~2.0.2",
|
"vite-plugin-html-config": "~2.0.2",
|
||||||
"vite-plugin-pwa": "~0.20.5",
|
"vite-plugin-pwa": "~0.20.5",
|
||||||
|
|
118
src/app.css
118
src/app.css
|
@ -371,7 +371,8 @@ a[href^='http'][rel*='nofollow']:visited:not(:has(div)) {
|
||||||
content-visibility: auto;
|
content-visibility: auto;
|
||||||
contain-intrinsic-size: auto 160px;
|
contain-intrinsic-size: auto 160px;
|
||||||
}
|
}
|
||||||
.timeline.contextual > li:is(:hover, :focus-visible) {
|
.timeline.contextual
|
||||||
|
> li:is(:hover, :focus-visible, :focus-within, :has(.status-menu-open)) {
|
||||||
/* Needed to undo the overflow: hidden "effect" due to "content-visibility: auto" */
|
/* Needed to undo the overflow: hidden "effect" due to "content-visibility: auto" */
|
||||||
content-visibility: visible !important;
|
content-visibility: visible !important;
|
||||||
}
|
}
|
||||||
|
@ -380,10 +381,14 @@ a[href^='http'][rel*='nofollow']:visited:not(:has(div)) {
|
||||||
}
|
}
|
||||||
|
|
||||||
.timeline.contextual {
|
.timeline.contextual {
|
||||||
--thread-start: 40px;
|
--indent-large-start: 40px;
|
||||||
--line-start: 40px;
|
--indent-small-start: 10px;
|
||||||
|
--thread-start: var(--indent-small-start);
|
||||||
|
--line-start: var(--indent-small-start);
|
||||||
--line-width: 3px;
|
--line-width: 3px;
|
||||||
--line-end: calc(var(--line-start) + var(--line-width));
|
--line-end: calc(var(--line-start) + var(--line-width));
|
||||||
|
--indent-large-end: calc(var(--indent-large-start) + var(--line-width));
|
||||||
|
--indent-small-end: calc(var(--indent-small-start) + var(--line-width));
|
||||||
--line-margin-end: 16px;
|
--line-margin-end: 16px;
|
||||||
--line-radius: 10px;
|
--line-radius: 10px;
|
||||||
--line-diameter: calc(var(--line-radius) * 2);
|
--line-diameter: calc(var(--line-radius) * 2);
|
||||||
|
@ -394,6 +399,68 @@ a[href^='http'][rel*='nofollow']:visited:not(:has(div)) {
|
||||||
:dir(rtl) & {
|
:dir(rtl) & {
|
||||||
--line-curve: -45deg;
|
--line-curve: -45deg;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
> li:is(.hero:has(+ .thread), .hero:last-child, .thread, .ancestor) {
|
||||||
|
--thread-start: var(--indent-large-start);
|
||||||
|
--line-start: var(--indent-large-start);
|
||||||
|
--line-end: calc(var(--line-start) + var(--line-width));
|
||||||
|
}
|
||||||
|
|
||||||
|
> li.descendant.thread:has(+ .descendant:not(.thread)):after {
|
||||||
|
position: absolute;
|
||||||
|
inset-inline-start: 10px;
|
||||||
|
bottom: 0;
|
||||||
|
content: '';
|
||||||
|
display: block;
|
||||||
|
--curves-width: calc(var(--line-start) + var(--line-width) - 10px);
|
||||||
|
width: var(--curves-width);
|
||||||
|
background-color: var(--bg-color);
|
||||||
|
background-repeat: no-repeat;
|
||||||
|
/* border-bottom: var(--line-width) dotted var(--comment-line-color); */
|
||||||
|
/* height: calc(var(--line-diameter) - var(--line-width));
|
||||||
|
background-image: linear-gradient(
|
||||||
|
transparent calc(var(--line-radius) - var(--line-width)),
|
||||||
|
var(--comment-line-color) calc(var(--line-radius) - var(--line-width))
|
||||||
|
var(--line-radius),
|
||||||
|
transparent var(--line-radius)
|
||||||
|
),
|
||||||
|
radial-gradient(
|
||||||
|
circle at bottom var(--forward),
|
||||||
|
transparent calc(var(--line-radius) - var(--line-width)),
|
||||||
|
var(--comment-line-color) calc(var(--line-radius) - var(--line-width))
|
||||||
|
var(--line-radius),
|
||||||
|
transparent var(--line-radius)
|
||||||
|
),
|
||||||
|
radial-gradient(
|
||||||
|
circle at top var(--backward),
|
||||||
|
transparent calc(var(--line-radius) - var(--line-width)),
|
||||||
|
var(--comment-line-color) calc(var(--line-radius) - var(--line-width))
|
||||||
|
var(--line-radius),
|
||||||
|
transparent var(--line-radius)
|
||||||
|
);
|
||||||
|
background-position: var(--line-radius) 0, bottom var(--backward),
|
||||||
|
top var(--forward);
|
||||||
|
background-size: calc(100% - var(--line-radius) * 2) auto,
|
||||||
|
var(--line-radius), var(--line-radius); */
|
||||||
|
--curves-radius: calc(var(--curves-width) / 2);
|
||||||
|
height: calc(var(--curves-width) - var(--line-width));
|
||||||
|
background-image: radial-gradient(
|
||||||
|
circle at bottom var(--forward),
|
||||||
|
transparent calc(var(--curves-radius) - var(--line-width)),
|
||||||
|
var(--comment-line-color) calc(var(--curves-radius) - var(--line-width))
|
||||||
|
var(--curves-radius),
|
||||||
|
transparent var(--curves-radius)
|
||||||
|
),
|
||||||
|
radial-gradient(
|
||||||
|
circle at top var(--backward),
|
||||||
|
transparent calc(var(--curves-radius) - var(--line-width)),
|
||||||
|
var(--comment-line-color) calc(var(--curves-radius) - var(--line-width))
|
||||||
|
var(--curves-radius),
|
||||||
|
transparent var(--curves-radius)
|
||||||
|
);
|
||||||
|
background-size: var(--curves-radius);
|
||||||
|
background-position: top var(--backward), bottom var(--forward);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
.timeline.contextual > li {
|
.timeline.contextual > li {
|
||||||
background-image: linear-gradient(
|
background-image: linear-gradient(
|
||||||
|
@ -405,6 +472,28 @@ a[href^='http'][rel*='nofollow']:visited:not(:has(div)) {
|
||||||
transparent var(--line-end),
|
transparent var(--line-end),
|
||||||
transparent
|
transparent
|
||||||
);
|
);
|
||||||
|
&.hero:not(:has(+ .thread), :first-child, :only-child, :last-child) {
|
||||||
|
background-image: linear-gradient(
|
||||||
|
var(--line-dir),
|
||||||
|
transparent,
|
||||||
|
transparent var(--indent-small-start),
|
||||||
|
var(--comment-line-color) var(--indent-small-start),
|
||||||
|
var(--comment-line-color) var(--indent-small-end),
|
||||||
|
transparent var(--indent-small-end),
|
||||||
|
transparent
|
||||||
|
),
|
||||||
|
linear-gradient(
|
||||||
|
var(--line-dir),
|
||||||
|
transparent,
|
||||||
|
transparent var(--indent-large-start),
|
||||||
|
var(--comment-line-color) var(--indent-large-start),
|
||||||
|
var(--comment-line-color) var(--indent-large-end),
|
||||||
|
transparent var(--indent-large-end),
|
||||||
|
transparent
|
||||||
|
);
|
||||||
|
background-size: 100% 50%;
|
||||||
|
background-position: bottom, top;
|
||||||
|
}
|
||||||
background-repeat: no-repeat;
|
background-repeat: no-repeat;
|
||||||
transition: opacity 0.3s ease-in-out;
|
transition: opacity 0.3s ease-in-out;
|
||||||
}
|
}
|
||||||
|
@ -426,7 +515,7 @@ a[href^='http'][rel*='nofollow']:visited:not(:has(div)) {
|
||||||
.timeline.contextual
|
.timeline.contextual
|
||||||
> li.descendant:not(.thread)
|
> li.descendant:not(.thread)
|
||||||
> :is(.status-link, .status-focus) {
|
> :is(.status-link, .status-focus) {
|
||||||
padding-inline-start: 40px;
|
padding-inline-start: var(--line-start);
|
||||||
}
|
}
|
||||||
.timeline.contextual .replies[data-scroll-left]:not([data-scroll-left='0']) {
|
.timeline.contextual .replies[data-scroll-left]:not([data-scroll-left='0']) {
|
||||||
background-color: var(--bg-color);
|
background-color: var(--bg-color);
|
||||||
|
@ -1306,6 +1395,11 @@ a[href^='http'][rel*='nofollow']:visited:not(:has(div)) {
|
||||||
user-select: none;
|
user-select: none;
|
||||||
width: 100%;
|
width: 100%;
|
||||||
gap: 16px;
|
gap: 16px;
|
||||||
|
--accent-gradient: var(--accent-gradient-light);
|
||||||
|
@media (prefers-color-scheme: dark) {
|
||||||
|
--accent-gradient: var(--accent-gradient-dark);
|
||||||
|
}
|
||||||
|
background-image: linear-gradient(to var(--forward), var(--accent-gradient));
|
||||||
}
|
}
|
||||||
.carousel::-webkit-scrollbar {
|
.carousel::-webkit-scrollbar {
|
||||||
display: none;
|
display: none;
|
||||||
|
@ -1320,7 +1414,10 @@ a[href^='http'][rel*='nofollow']:visited:not(:has(div)) {
|
||||||
width: 100%;
|
width: 100%;
|
||||||
height: 100vh;
|
height: 100vh;
|
||||||
height: 100dvh;
|
height: 100dvh;
|
||||||
background-color: var(--accent-alpha-color);
|
background-color: var(--accent-light-color);
|
||||||
|
@media (prefers-color-scheme: dark) {
|
||||||
|
background-color: var(--accent-dark-color);
|
||||||
|
}
|
||||||
/* background-image: radial-gradient(
|
/* background-image: radial-gradient(
|
||||||
closest-side,
|
closest-side,
|
||||||
var(--accent-color) 10%,
|
var(--accent-color) 10%,
|
||||||
|
@ -1861,7 +1958,7 @@ body > .szh-menu-container {
|
||||||
box-shadow: 0 3px 24px -3px var(--drop-shadow-color);
|
box-shadow: 0 3px 24px -3px var(--drop-shadow-color);
|
||||||
}
|
}
|
||||||
.szh-menu__header {
|
.szh-menu__header {
|
||||||
margin: -8px 0 8px;
|
margin: -4px 0 8px;
|
||||||
padding: 8px 16px;
|
padding: 8px 16px;
|
||||||
color: var(--text-insignificant-color);
|
color: var(--text-insignificant-color);
|
||||||
font-size: 90%;
|
font-size: 90%;
|
||||||
|
@ -2542,6 +2639,15 @@ ul.link-list li a .icon {
|
||||||
}
|
}
|
||||||
#columns > * {
|
#columns > * {
|
||||||
padding: 0 16px;
|
padding: 0 16px;
|
||||||
|
|
||||||
|
&:is(#notifications-page) {
|
||||||
|
padding-inline: 0;
|
||||||
|
|
||||||
|
.timeline-deck > header {
|
||||||
|
margin-inline: 16px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
border-inline: var(--hairline-width) solid var(--bg-faded-color);
|
border-inline: var(--hairline-width) solid var(--bg-faded-color);
|
||||||
/* border-radius: 16px; */
|
/* border-radius: 16px; */
|
||||||
/* box-shadow: -4px 0 16px -8px var(--drop-shadow-color); */
|
/* box-shadow: -4px 0 16px -8px var(--drop-shadow-color); */
|
||||||
|
|
|
@ -210,6 +210,12 @@ const isIOS = /iPad|iPhone|iPod/.test(navigator.userAgent);
|
||||||
if (isIOS) {
|
if (isIOS) {
|
||||||
document.addEventListener('visibilitychange', () => {
|
document.addEventListener('visibilitychange', () => {
|
||||||
if (document.visibilityState === 'visible') {
|
if (document.visibilityState === 'visible') {
|
||||||
|
// Don't reset theme color if media modal is showing
|
||||||
|
// Media modal will set its own theme color based on the media's color
|
||||||
|
const showingMediaModal =
|
||||||
|
document.getElementsByClassName('media-modal-container').length > 0;
|
||||||
|
if (showingMediaModal) return;
|
||||||
|
|
||||||
const theme = store.local.get('theme');
|
const theme = store.local.get('theme');
|
||||||
let $meta;
|
let $meta;
|
||||||
if (theme) {
|
if (theme) {
|
||||||
|
|
|
@ -413,7 +413,7 @@ function AccountInfo({
|
||||||
<span>██</span> <Trans>Followers</Trans>
|
<span>██</span> <Trans>Followers</Trans>
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
<span>██</span> <Trans>Following</Trans>
|
<span>██</span> <Trans id="following.stats">Following</Trans>
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
<span>██</span> <Trans>Posts</Trans>
|
<span>██</span> <Trans>Posts</Trans>
|
||||||
|
@ -739,7 +739,10 @@ function AccountInfo({
|
||||||
// states.showAccount = false;
|
// states.showAccount = false;
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
states.showGenericAccounts = {
|
states.showGenericAccounts = {
|
||||||
heading: t`Following`,
|
heading: t({
|
||||||
|
id: 'following.stats',
|
||||||
|
message: 'Following',
|
||||||
|
}),
|
||||||
fetchAccounts: fetchFollowing,
|
fetchAccounts: fetchFollowing,
|
||||||
instance,
|
instance,
|
||||||
excludeRelationshipAttrs: isSelf ? ['following'] : [],
|
excludeRelationshipAttrs: isSelf ? ['following'] : [],
|
||||||
|
@ -753,7 +756,7 @@ function AccountInfo({
|
||||||
<span title={followingCount}>
|
<span title={followingCount}>
|
||||||
{shortenNumber(followingCount)}
|
{shortenNumber(followingCount)}
|
||||||
</span>{' '}
|
</span>{' '}
|
||||||
<Trans>Following</Trans>
|
<Trans id="following.stats">Following</Trans>
|
||||||
<br />
|
<br />
|
||||||
</LinkOrDiv>
|
</LinkOrDiv>
|
||||||
<LinkOrDiv
|
<LinkOrDiv
|
||||||
|
|
|
@ -12,9 +12,16 @@ import Notifications from '../pages/notifications';
|
||||||
import Public from '../pages/public';
|
import Public from '../pages/public';
|
||||||
import Search from '../pages/search';
|
import Search from '../pages/search';
|
||||||
import Trending from '../pages/trending';
|
import Trending from '../pages/trending';
|
||||||
|
import isRTL from '../utils/is-rtl';
|
||||||
import states from '../utils/states';
|
import states from '../utils/states';
|
||||||
import useTitle from '../utils/useTitle';
|
import useTitle from '../utils/useTitle';
|
||||||
|
|
||||||
|
const scrollIntoViewOptions = {
|
||||||
|
block: 'nearest',
|
||||||
|
inline: 'nearest',
|
||||||
|
behavior: 'smooth',
|
||||||
|
};
|
||||||
|
|
||||||
function Columns() {
|
function Columns() {
|
||||||
useTitle(t`Home`, '/');
|
useTitle(t`Home`, '/');
|
||||||
const snapStates = useSnapshot(states);
|
const snapStates = useSnapshot(states);
|
||||||
|
@ -50,12 +57,42 @@ function Columns() {
|
||||||
useHotkeys(['1', '2', '3', '4', '5', '6', '7', '8', '9'], (e, handler) => {
|
useHotkeys(['1', '2', '3', '4', '5', '6', '7', '8', '9'], (e, handler) => {
|
||||||
try {
|
try {
|
||||||
const index = parseInt(handler.keys[0], 10) - 1;
|
const index = parseInt(handler.keys[0], 10) - 1;
|
||||||
document.querySelectorAll('#columns > *')[index].focus();
|
const $column = document.querySelectorAll('#columns > *')[index];
|
||||||
|
if ($column) {
|
||||||
|
$column.focus();
|
||||||
|
$column.scrollIntoView(scrollIntoViewOptions);
|
||||||
|
}
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
console.error(e);
|
console.error(e);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
useHotkeys(['[', ']'], (e, handler) => {
|
||||||
|
const key = handler.keys[0];
|
||||||
|
const currentFocusedColumn = document.activeElement.closest('#columns > *');
|
||||||
|
|
||||||
|
const rtl = isRTL();
|
||||||
|
const prevColKey = rtl ? ']' : '[';
|
||||||
|
const nextColKey = rtl ? '[' : ']';
|
||||||
|
let $column;
|
||||||
|
|
||||||
|
if (key === prevColKey) {
|
||||||
|
// If [, focus on left of focused column, else first column
|
||||||
|
$column = currentFocusedColumn
|
||||||
|
? currentFocusedColumn.previousElementSibling
|
||||||
|
: document.querySelectorAll('#columns > *')[0];
|
||||||
|
} else if (key === nextColKey) {
|
||||||
|
// If ], focus on right of focused column, else 2nd column
|
||||||
|
$column = currentFocusedColumn
|
||||||
|
? currentFocusedColumn.nextElementSibling
|
||||||
|
: document.querySelectorAll('#columns > *')[1];
|
||||||
|
}
|
||||||
|
if ($column) {
|
||||||
|
$column.focus();
|
||||||
|
$column.scrollIntoView(scrollIntoViewOptions);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div
|
<div
|
||||||
id="columns"
|
id="columns"
|
||||||
|
|
|
@ -197,6 +197,7 @@ function highlightText(text, { maxCharacters = Infinity }) {
|
||||||
|
|
||||||
// const rtf = new Intl.RelativeTimeFormat();
|
// const rtf = new Intl.RelativeTimeFormat();
|
||||||
const RTF = mem((locale) => new Intl.RelativeTimeFormat(locale || undefined));
|
const RTF = mem((locale) => new Intl.RelativeTimeFormat(locale || undefined));
|
||||||
|
const LF = mem((locale) => new Intl.ListFormat(locale || undefined));
|
||||||
|
|
||||||
const CUSTOM_EMOJIS_COUNT = 100;
|
const CUSTOM_EMOJIS_COUNT = 100;
|
||||||
|
|
||||||
|
@ -210,6 +211,7 @@ function Compose({
|
||||||
}) {
|
}) {
|
||||||
const { i18n } = useLingui();
|
const { i18n } = useLingui();
|
||||||
const rtf = RTF(i18n.locale);
|
const rtf = RTF(i18n.locale);
|
||||||
|
const lf = LF(i18n.locale);
|
||||||
|
|
||||||
console.warn('RENDER COMPOSER');
|
console.warn('RENDER COMPOSER');
|
||||||
const { masto, instance } = api();
|
const { masto, instance } = api();
|
||||||
|
@ -226,11 +228,11 @@ function Compose({
|
||||||
const {
|
const {
|
||||||
statuses: {
|
statuses: {
|
||||||
maxCharacters,
|
maxCharacters,
|
||||||
maxMediaAttachments,
|
maxMediaAttachments, // Beware: it can be undefined!
|
||||||
charactersReservedPerUrl,
|
charactersReservedPerUrl,
|
||||||
} = {},
|
} = {},
|
||||||
mediaAttachments: {
|
mediaAttachments: {
|
||||||
supportedMimeTypes = [],
|
supportedMimeTypes,
|
||||||
imageSizeLimit,
|
imageSizeLimit,
|
||||||
imageMatrixLimit,
|
imageMatrixLimit,
|
||||||
videoSizeLimit,
|
videoSizeLimit,
|
||||||
|
@ -600,15 +602,31 @@ function Compose({
|
||||||
const handleItems = (e) => {
|
const handleItems = (e) => {
|
||||||
const { items } = e.clipboardData || e.dataTransfer;
|
const { items } = e.clipboardData || e.dataTransfer;
|
||||||
const files = [];
|
const files = [];
|
||||||
|
const unsupportedFiles = [];
|
||||||
for (let i = 0; i < items.length; i++) {
|
for (let i = 0; i < items.length; i++) {
|
||||||
const item = items[i];
|
const item = items[i];
|
||||||
if (item.kind === 'file') {
|
if (item.kind === 'file') {
|
||||||
const file = item.getAsFile();
|
const file = item.getAsFile();
|
||||||
if (file && supportedMimeTypes.includes(file.type)) {
|
if (
|
||||||
|
supportedMimeTypes !== undefined &&
|
||||||
|
!supportedMimeTypes.includes(file.type)
|
||||||
|
) {
|
||||||
|
unsupportedFiles.push(file);
|
||||||
|
} else {
|
||||||
files.push(file);
|
files.push(file);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
if (unsupportedFiles.length > 0) {
|
||||||
|
alert(
|
||||||
|
plural(unsupportedFiles.length, {
|
||||||
|
one: `File ${unsupportedFiles[0].name} is not supported.`,
|
||||||
|
other: `Files ${lf.format(
|
||||||
|
unsupportedFiles.map((f) => f.name),
|
||||||
|
)} are not supported.`,
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
}
|
||||||
if (files.length > 0 && mediaAttachments.length >= maxMediaAttachments) {
|
if (files.length > 0 && mediaAttachments.length >= maxMediaAttachments) {
|
||||||
alert(
|
alert(
|
||||||
plural(maxMediaAttachments, {
|
plural(maxMediaAttachments, {
|
||||||
|
@ -623,8 +641,10 @@ function Compose({
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
e.stopPropagation();
|
e.stopPropagation();
|
||||||
// Auto-cut-off files to avoid exceeding maxMediaAttachments
|
// Auto-cut-off files to avoid exceeding maxMediaAttachments
|
||||||
|
let allowedFiles = files;
|
||||||
|
if (maxMediaAttachments !== undefined) {
|
||||||
const max = maxMediaAttachments - mediaAttachments.length;
|
const max = maxMediaAttachments - mediaAttachments.length;
|
||||||
const allowedFiles = files.slice(0, max);
|
allowedFiles = allowedFiles.slice(0, max);
|
||||||
if (allowedFiles.length <= 0) {
|
if (allowedFiles.length <= 0) {
|
||||||
alert(
|
alert(
|
||||||
plural(maxMediaAttachments, {
|
plural(maxMediaAttachments, {
|
||||||
|
@ -634,6 +654,7 @@ function Compose({
|
||||||
);
|
);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
}
|
||||||
const mediaFiles = allowedFiles.map((file) => ({
|
const mediaFiles = allowedFiles.map((file) => ({
|
||||||
file,
|
file,
|
||||||
type: file.type,
|
type: file.type,
|
||||||
|
@ -1307,8 +1328,11 @@ function Compose({
|
||||||
<label class="toolbar-button">
|
<label class="toolbar-button">
|
||||||
<input
|
<input
|
||||||
type="file"
|
type="file"
|
||||||
accept={supportedMimeTypes.join(',')}
|
accept={supportedMimeTypes?.join(',')}
|
||||||
multiple={mediaAttachments.length < maxMediaAttachments - 1}
|
multiple={
|
||||||
|
maxMediaAttachments === undefined ||
|
||||||
|
maxMediaAttachments - mediaAttachments >= 2
|
||||||
|
}
|
||||||
disabled={
|
disabled={
|
||||||
uiState === 'loading' ||
|
uiState === 'loading' ||
|
||||||
mediaAttachments.length >= maxMediaAttachments ||
|
mediaAttachments.length >= maxMediaAttachments ||
|
||||||
|
@ -1400,7 +1424,8 @@ function Compose({
|
||||||
class="toolbar-button gif-picker-button"
|
class="toolbar-button gif-picker-button"
|
||||||
disabled={
|
disabled={
|
||||||
uiState === 'loading' ||
|
uiState === 'loading' ||
|
||||||
mediaAttachments.length >= maxMediaAttachments ||
|
(maxMediaAttachments !== undefined &&
|
||||||
|
mediaAttachments.length >= maxMediaAttachments) ||
|
||||||
!!poll
|
!!poll
|
||||||
}
|
}
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
|
|
|
@ -23,7 +23,7 @@
|
||||||
pointer-events: auto;
|
pointer-events: auto;
|
||||||
max-width: 100%;
|
max-width: 100%;
|
||||||
max-height: 100%;
|
max-height: 100%;
|
||||||
width: max(var(--width), 480px);
|
width: max(var(--width), var(--main-width));
|
||||||
height: auto;
|
height: auto;
|
||||||
aspect-ratio: var(--aspect-ratio);
|
aspect-ratio: var(--aspect-ratio);
|
||||||
}
|
}
|
||||||
|
|
|
@ -113,6 +113,14 @@ export default memo(function KeyboardShortcutsHelp() {
|
||||||
</Trans>
|
</Trans>
|
||||||
),
|
),
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
action: t`Focus next column in multi-column mode`,
|
||||||
|
keys: <kbd>]</kbd>,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
action: t`Focus previous column in multi-column mode`,
|
||||||
|
keys: <kbd>[</kbd>,
|
||||||
|
},
|
||||||
{
|
{
|
||||||
action: t`Compose new post`,
|
action: t`Compose new post`,
|
||||||
keys: <kbd>c</kbd>,
|
keys: <kbd>c</kbd>,
|
||||||
|
|
|
@ -14,6 +14,7 @@ import { oklab2rgb, rgb2oklab } from '../utils/color-utils';
|
||||||
import isRTL from '../utils/is-rtl';
|
import isRTL from '../utils/is-rtl';
|
||||||
import showToast from '../utils/show-toast';
|
import showToast from '../utils/show-toast';
|
||||||
import states from '../utils/states';
|
import states from '../utils/states';
|
||||||
|
import store from '../utils/store';
|
||||||
|
|
||||||
import Icon from './icon';
|
import Icon from './icon';
|
||||||
import Link from './link';
|
import Link from './link';
|
||||||
|
@ -115,39 +116,95 @@ function MediaModal({
|
||||||
return () => clearTimeout(timer);
|
return () => clearTimeout(timer);
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
const mediaAccentColors = useMemo(() => {
|
const mediaOklabColors = useMemo(() => {
|
||||||
return mediaAttachments?.map((media) => {
|
return mediaAttachments?.map((media) => {
|
||||||
const { blurhash } = media;
|
const { blurhash } = media;
|
||||||
if (blurhash) {
|
if (blurhash) {
|
||||||
const averageColor = getBlurHashAverageColor(blurhash);
|
const averageColor = getBlurHashAverageColor(blurhash);
|
||||||
const labAverageColor = rgb2oklab(averageColor);
|
return rgb2oklab(averageColor);
|
||||||
return oklab2rgb([0.6, labAverageColor[1], labAverageColor[2]]);
|
|
||||||
}
|
}
|
||||||
return null;
|
return null;
|
||||||
});
|
});
|
||||||
}, [mediaAttachments]);
|
}, [mediaAttachments]);
|
||||||
const mediaAccentGradient = useMemo(() => {
|
// const mediaAccentColors = useMemo(() => {
|
||||||
|
// return mediaOklabColors?.map((labAverageColor) => {
|
||||||
|
// if (labAverageColor) {
|
||||||
|
// return oklab2rgb([0.6, labAverageColor[1], labAverageColor[2]]);
|
||||||
|
// }
|
||||||
|
// return null;
|
||||||
|
// });
|
||||||
|
// }, [mediaOklabColors]);
|
||||||
|
const mediaAccentColors = useMemo(() => {
|
||||||
|
return mediaOklabColors?.map((labAverageColor) => {
|
||||||
|
if (labAverageColor) {
|
||||||
|
return {
|
||||||
|
light: oklab2rgb([0.95, labAverageColor[1], labAverageColor[2]]),
|
||||||
|
dark: oklab2rgb([0.25, labAverageColor[1], labAverageColor[2]]),
|
||||||
|
default: oklab2rgb([0.6, labAverageColor[1], labAverageColor[2]]),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
return {};
|
||||||
|
});
|
||||||
|
});
|
||||||
|
// const mediaAccentGradient = useMemo(() => {
|
||||||
|
// const gap = 5;
|
||||||
|
// const range = 100 / mediaAccentColors.length;
|
||||||
|
// return (
|
||||||
|
// mediaAccentColors
|
||||||
|
// ?.map((color, i) => {
|
||||||
|
// const start = i * range + gap;
|
||||||
|
// const end = (i + 1) * range - gap;
|
||||||
|
// if (color) {
|
||||||
|
// return `
|
||||||
|
// rgba(${color?.join(',')}, 0.4) ${start}%,
|
||||||
|
// rgba(${color?.join(',')}, 0.4) ${end}%
|
||||||
|
// `;
|
||||||
|
// }
|
||||||
|
|
||||||
|
// return `
|
||||||
|
// transparent ${start}%,
|
||||||
|
// transparent ${end}%
|
||||||
|
// `;
|
||||||
|
// })
|
||||||
|
// ?.join(', ') || 'transparent'
|
||||||
|
// );
|
||||||
|
// }, [mediaAccentColors]);
|
||||||
|
const mediaAccentGradients = useMemo(() => {
|
||||||
const gap = 5;
|
const gap = 5;
|
||||||
const range = 100 / mediaAccentColors.length;
|
const range = 100 / mediaAccentColors.length;
|
||||||
return (
|
const colors = mediaAccentColors.map((color, i) => {
|
||||||
mediaAccentColors
|
|
||||||
?.map((color, i) => {
|
|
||||||
const start = i * range + gap;
|
const start = i * range + gap;
|
||||||
const end = (i + 1) * range - gap;
|
const end = (i + 1) * range - gap;
|
||||||
if (color) {
|
if (color?.light && color?.dark) {
|
||||||
return `
|
return {
|
||||||
rgba(${color?.join(',')}, 0.4) ${start}%,
|
light: `
|
||||||
rgba(${color?.join(',')}, 0.4) ${end}%
|
rgb(${color.light?.join(',')}) ${start}%,
|
||||||
`;
|
rgb(${color.light?.join(',')}) ${end}%
|
||||||
|
`,
|
||||||
|
dark: `
|
||||||
|
rgb(${color.dark?.join(',')}) ${start}%,
|
||||||
|
rgb(${color.dark?.join(',')}) ${end}%
|
||||||
|
`,
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
return `
|
return {
|
||||||
|
light: `
|
||||||
transparent ${start}%,
|
transparent ${start}%,
|
||||||
transparent ${end}%
|
transparent ${end}%
|
||||||
`;
|
`,
|
||||||
})
|
dark: `
|
||||||
?.join(', ') || 'transparent'
|
transparent ${start}%,
|
||||||
);
|
transparent ${end}%
|
||||||
|
`,
|
||||||
|
};
|
||||||
|
});
|
||||||
|
const lightGradient = colors.map((color) => color.light).join(', ');
|
||||||
|
const darkGradient = colors.map((color) => color.dark).join(', ');
|
||||||
|
return {
|
||||||
|
light: lightGradient,
|
||||||
|
dark: darkGradient,
|
||||||
|
};
|
||||||
}, [mediaAccentColors]);
|
}, [mediaAccentColors]);
|
||||||
|
|
||||||
let toastRef = useRef(null);
|
let toastRef = useRef(null);
|
||||||
|
@ -157,6 +214,46 @@ function MediaModal({
|
||||||
};
|
};
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
|
useLayoutEffect(() => {
|
||||||
|
const currentColor = mediaAccentColors[currentIndex];
|
||||||
|
let $meta;
|
||||||
|
let metaColor;
|
||||||
|
if (currentColor) {
|
||||||
|
const theme = store.local.get('theme');
|
||||||
|
if (theme) {
|
||||||
|
const mediaColor = `rgb(${currentColor[theme].join(',')})`;
|
||||||
|
console.log({ mediaColor });
|
||||||
|
$meta = document.querySelector(
|
||||||
|
`meta[name="theme-color"][data-theme-setting="manual"]`,
|
||||||
|
);
|
||||||
|
if ($meta) {
|
||||||
|
metaColor = $meta.content;
|
||||||
|
$meta.content = mediaColor;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
const colorScheme = window.matchMedia('(prefers-color-scheme: dark)')
|
||||||
|
.matches
|
||||||
|
? 'dark'
|
||||||
|
: 'light';
|
||||||
|
const mediaColor = `rgb(${currentColor[colorScheme].join(',')})`;
|
||||||
|
console.log({ mediaColor });
|
||||||
|
$meta = document.querySelector(
|
||||||
|
`meta[name="theme-color"][media*="${colorScheme}"]`,
|
||||||
|
);
|
||||||
|
if ($meta) {
|
||||||
|
metaColor = $meta.content;
|
||||||
|
$meta.content = mediaColor;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return () => {
|
||||||
|
// Reset meta color
|
||||||
|
if ($meta && metaColor) {
|
||||||
|
$meta.content = metaColor;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}, [currentIndex, mediaAccentColors]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div
|
<div
|
||||||
class={`media-modal-container media-modal-count-${mediaAttachments?.length}`}
|
class={`media-modal-container media-modal-count-${mediaAttachments?.length}`}
|
||||||
|
@ -179,8 +276,10 @@ function MediaModal({
|
||||||
mediaAttachments.length > 1
|
mediaAttachments.length > 1
|
||||||
? {
|
? {
|
||||||
backgroundAttachment: 'local',
|
backgroundAttachment: 'local',
|
||||||
backgroundImage: `linear-gradient(
|
'--accent-gradient-light': mediaAccentGradients?.light,
|
||||||
to ${isRTL() ? 'left' : 'right'}, ${mediaAccentGradient})`,
|
'--accent-gradient-dark': mediaAccentGradients?.dark,
|
||||||
|
// backgroundImage: `linear-gradient(
|
||||||
|
// to ${isRTL() ? 'left' : 'right'}, ${mediaAccentGradient})`,
|
||||||
}
|
}
|
||||||
: {}
|
: {}
|
||||||
}
|
}
|
||||||
|
@ -194,8 +293,14 @@ function MediaModal({
|
||||||
style={
|
style={
|
||||||
accentColor
|
accentColor
|
||||||
? {
|
? {
|
||||||
'--accent-color': `rgb(${accentColor?.join(',')})`,
|
'--accent-color': `rgb(${accentColor.default.join(',')})`,
|
||||||
'--accent-alpha-color': `rgba(${accentColor?.join(
|
'--accent-light-color': `rgb(${accentColor.light?.join(
|
||||||
|
',',
|
||||||
|
)})`,
|
||||||
|
'--accent-dark-color': `rgb(${accentColor.dark?.join(
|
||||||
|
',',
|
||||||
|
)})`,
|
||||||
|
'--accent-alpha-color': `rgba(${accentColor.default.join(
|
||||||
',',
|
',',
|
||||||
)}, 0.4)`,
|
)}, 0.4)`,
|
||||||
}
|
}
|
||||||
|
@ -260,8 +365,8 @@ function MediaModal({
|
||||||
e.stopPropagation();
|
e.stopPropagation();
|
||||||
const left =
|
const left =
|
||||||
carouselRef.current.clientWidth * i * (isRTL() ? -1 : 1);
|
carouselRef.current.clientWidth * i * (isRTL() ? -1 : 1);
|
||||||
carouselRef.current.scrollTo({ left, behavior: 'smooth' });
|
|
||||||
carouselRef.current.focus();
|
carouselRef.current.focus();
|
||||||
|
carouselRef.current.scrollTo({ left, behavior: 'smooth' });
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<Icon icon="round" size="s" alt="⸱" />
|
<Icon icon="round" size="s" alt="⸱" />
|
||||||
|
|
|
@ -74,6 +74,7 @@ function Media({
|
||||||
showCaption,
|
showCaption,
|
||||||
allowLongerCaption,
|
allowLongerCaption,
|
||||||
altIndex,
|
altIndex,
|
||||||
|
checkAspectRatio = true,
|
||||||
onClick = () => {},
|
onClick = () => {},
|
||||||
}) {
|
}) {
|
||||||
let {
|
let {
|
||||||
|
@ -354,7 +355,7 @@ function Media({
|
||||||
}
|
}
|
||||||
|
|
||||||
// Check natural aspect ratio vs display aspect ratio
|
// Check natural aspect ratio vs display aspect ratio
|
||||||
if ($media) {
|
if (checkAspectRatio && $media) {
|
||||||
const {
|
const {
|
||||||
clientWidth,
|
clientWidth,
|
||||||
clientHeight,
|
clientHeight,
|
||||||
|
|
|
@ -190,7 +190,7 @@ function NavMenu(props) {
|
||||||
<MenuLink to="/following">
|
<MenuLink to="/following">
|
||||||
<Icon icon="following" size="l" />{' '}
|
<Icon icon="following" size="l" />{' '}
|
||||||
<span>
|
<span>
|
||||||
<Trans>Following</Trans>
|
<Trans id="following.title">Following</Trans>
|
||||||
</span>
|
</span>
|
||||||
</MenuLink>
|
</MenuLink>
|
||||||
)}
|
)}
|
||||||
|
|
|
@ -134,7 +134,10 @@ const fetchAccountTitle = pmem(async ({ id }) => {
|
||||||
export const SHORTCUTS_META = {
|
export const SHORTCUTS_META = {
|
||||||
following: {
|
following: {
|
||||||
id: 'home',
|
id: 'home',
|
||||||
title: (_, index) => (index === 0 ? t`Home` : t`Following`),
|
title: (_, index) =>
|
||||||
|
index === 0
|
||||||
|
? t`Home`
|
||||||
|
: t({ id: 'following.title', message: 'Following' }),
|
||||||
path: '/',
|
path: '/',
|
||||||
icon: 'home',
|
icon: 'home',
|
||||||
},
|
},
|
||||||
|
|
|
@ -30,10 +30,10 @@ function Shortcuts() {
|
||||||
if (!shortcuts.length) {
|
if (!shortcuts.length) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
if (
|
const isMultiColumnMode =
|
||||||
settings.shortcutsViewMode === 'multi-column' ||
|
settings.shortcutsViewMode === 'multi-column' ||
|
||||||
(!settings.shortcutsViewMode && settings.shortcutsColumnsMode)
|
(!settings.shortcutsViewMode && settings.shortcutsColumnsMode);
|
||||||
) {
|
if (isMultiColumnMode) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -87,7 +87,9 @@ function Shortcuts() {
|
||||||
.filter(Boolean);
|
.filter(Boolean);
|
||||||
|
|
||||||
const navigate = useNavigate();
|
const navigate = useNavigate();
|
||||||
useHotkeys(['1', '2', '3', '4', '5', '6', '7', '8', '9'], (e, handler) => {
|
useHotkeys(
|
||||||
|
['1', '2', '3', '4', '5', '6', '7', '8', '9'],
|
||||||
|
(e, handler) => {
|
||||||
const index = parseInt(handler.keys[0], 10) - 1;
|
const index = parseInt(handler.keys[0], 10) - 1;
|
||||||
if (index < formattedShortcuts.length) {
|
if (index < formattedShortcuts.length) {
|
||||||
const { path } = formattedShortcuts[index];
|
const { path } = formattedShortcuts[index];
|
||||||
|
@ -96,7 +98,11 @@ function Shortcuts() {
|
||||||
menuRef.current?.closeMenu?.();
|
menuRef.current?.closeMenu?.();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
},
|
||||||
|
{
|
||||||
|
enabled: !isMultiColumnMode,
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
const [lists, setLists] = useState([]);
|
const [lists, setLists] = useState([]);
|
||||||
|
|
||||||
|
|
|
@ -247,9 +247,12 @@
|
||||||
:is(.content.truncated, .poll, .media-container.truncated) {
|
:is(.content.truncated, .poll, .media-container.truncated) {
|
||||||
mask-image: linear-gradient(to bottom, #000 40px, transparent);
|
mask-image: linear-gradient(to bottom, #000 40px, transparent);
|
||||||
}
|
}
|
||||||
.status-card .card {
|
.status-card {
|
||||||
|
.card,
|
||||||
|
.card-byline {
|
||||||
display: none;
|
display: none;
|
||||||
}
|
}
|
||||||
|
}
|
||||||
.timeline-deck .status-card .content.truncated:after {
|
.timeline-deck .status-card .content.truncated:after {
|
||||||
/* Don't show "Read more" in status cards */
|
/* Don't show "Read more" in status cards */
|
||||||
content: none !important;
|
content: none !important;
|
||||||
|
@ -1995,7 +1998,11 @@ a.card:is(:hover, :focus):visited {
|
||||||
gap: 4px;
|
gap: 4px;
|
||||||
color: var(--text-insignificant-color);
|
color: var(--text-insignificant-color);
|
||||||
padding: 2px 8px;
|
padding: 2px 8px;
|
||||||
align-items: center;
|
align-items: flex-start;
|
||||||
|
|
||||||
|
.icon {
|
||||||
|
min-height: 1em;
|
||||||
|
}
|
||||||
|
|
||||||
.avatar {
|
.avatar {
|
||||||
width: 16px !important;
|
width: 16px !important;
|
||||||
|
@ -2124,8 +2131,8 @@ a.card:is(:hover, :focus):visited {
|
||||||
color: var(--text-insignificant-color);
|
color: var(--text-insignificant-color);
|
||||||
font-size: 90%;
|
font-size: 90%;
|
||||||
}
|
}
|
||||||
.status .extra-meta * {
|
.status .extra-meta .icon {
|
||||||
vertical-align: middle;
|
vertical-align: text-bottom;
|
||||||
}
|
}
|
||||||
.status .extra-meta a {
|
.status .extra-meta a {
|
||||||
color: inherit;
|
color: inherit;
|
||||||
|
|
|
@ -125,6 +125,46 @@ function getPostText(status) {
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function isTranslateble(content) {
|
||||||
|
if (!content) return false;
|
||||||
|
content = content.trim();
|
||||||
|
if (!content) return false;
|
||||||
|
const text = getHTMLText(content, {
|
||||||
|
preProcess: (dom) => {
|
||||||
|
// Remove .mention, pre, code, a:has(.invisible)
|
||||||
|
for (const a of dom.querySelectorAll(
|
||||||
|
'.mention, pre, code, a:has(.invisible)',
|
||||||
|
)) {
|
||||||
|
a.remove();
|
||||||
|
}
|
||||||
|
},
|
||||||
|
});
|
||||||
|
return !!text;
|
||||||
|
}
|
||||||
|
|
||||||
|
function getHTMLTextForDetectLang(content) {
|
||||||
|
return getHTMLText(content, {
|
||||||
|
preProcess: (dom) => {
|
||||||
|
// Remove anything that can skew the language detection
|
||||||
|
|
||||||
|
// Remove .mention, .hashtag, pre, code, a:has(.invisible)
|
||||||
|
for (const a of dom.querySelectorAll(
|
||||||
|
'.mention, .hashtag, pre, code, a:has(.invisible)',
|
||||||
|
)) {
|
||||||
|
a.remove();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Remove links that contains text that starts with https?://
|
||||||
|
for (const a of dom.querySelectorAll('a')) {
|
||||||
|
const text = a.innerText.trim();
|
||||||
|
if (text.startsWith('https://') || text.startsWith('http://')) {
|
||||||
|
a.remove();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
const HTTP_REGEX = /^http/i;
|
const HTTP_REGEX = /^http/i;
|
||||||
const PostContent =
|
const PostContent =
|
||||||
/*memo(*/
|
/*memo(*/
|
||||||
|
@ -343,32 +383,10 @@ function Status({
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (!content) return;
|
if (!content) return;
|
||||||
if (_language) return;
|
if (_language) return;
|
||||||
|
if (languageAutoDetected) return;
|
||||||
let timer;
|
let timer;
|
||||||
timer = setTimeout(async () => {
|
timer = setTimeout(async () => {
|
||||||
let detected = await detectLang(
|
let detected = await detectLang(getHTMLTextForDetectLang(content));
|
||||||
getHTMLText(content, {
|
|
||||||
preProcess: (dom) => {
|
|
||||||
// Remove anything that can skew the language detection
|
|
||||||
|
|
||||||
// Remove .mention, .hashtag, pre, code, a:has(.invisible)
|
|
||||||
dom
|
|
||||||
.querySelectorAll(
|
|
||||||
'.mention, .hashtag, pre, code, a:has(.invisible)',
|
|
||||||
)
|
|
||||||
.forEach((a) => {
|
|
||||||
a.remove();
|
|
||||||
});
|
|
||||||
|
|
||||||
// Remove links that contains text that starts with https?://
|
|
||||||
dom.querySelectorAll('a').forEach((a) => {
|
|
||||||
const text = a.innerText.trim();
|
|
||||||
if (text.startsWith('https://') || text.startsWith('http://')) {
|
|
||||||
a.remove();
|
|
||||||
}
|
|
||||||
});
|
|
||||||
},
|
|
||||||
}),
|
|
||||||
);
|
|
||||||
setLanguageAutoDetected(detected);
|
setLanguageAutoDetected(detected);
|
||||||
}, 1000);
|
}, 1000);
|
||||||
return () => clearTimeout(timer);
|
return () => clearTimeout(timer);
|
||||||
|
@ -2061,6 +2079,7 @@ function Status({
|
||||||
class="content"
|
class="content"
|
||||||
ref={contentRef}
|
ref={contentRef}
|
||||||
data-read-more={_(readMoreText)}
|
data-read-more={_(readMoreText)}
|
||||||
|
inert={!!spoilerText && !showSpoiler ? true : undefined}
|
||||||
>
|
>
|
||||||
<PostContent
|
<PostContent
|
||||||
post={status}
|
post={status}
|
||||||
|
@ -2101,8 +2120,7 @@ function Status({
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
{(((enableTranslate || inlineTranslate) &&
|
{(((enableTranslate || inlineTranslate) &&
|
||||||
!!content.trim() &&
|
isTranslateble(content) &&
|
||||||
!!getHTMLText(emojifyText(content, emojis)) &&
|
|
||||||
differentLanguage) ||
|
differentLanguage) ||
|
||||||
forceTranslate) && (
|
forceTranslate) && (
|
||||||
<TranslationBlock
|
<TranslationBlock
|
||||||
|
@ -2202,6 +2220,7 @@ function Status({
|
||||||
}
|
}
|
||||||
: undefined
|
: undefined
|
||||||
}
|
}
|
||||||
|
checkAspectRatio={mediaAttachments.length === 1}
|
||||||
/>
|
/>
|
||||||
))}
|
))}
|
||||||
</div>
|
</div>
|
||||||
|
@ -2248,6 +2267,19 @@ function Status({
|
||||||
/> */}
|
/> */}
|
||||||
<span>{_(visibilityText[visibility])}</span> •{' '}
|
<span>{_(visibilityText[visibility])}</span> •{' '}
|
||||||
<a href={url} target="_blank" rel="noopener noreferrer">
|
<a href={url} target="_blank" rel="noopener noreferrer">
|
||||||
|
{
|
||||||
|
// within a day
|
||||||
|
new Date().getTime() - createdAtDate.getTime() <
|
||||||
|
86400000 && (
|
||||||
|
<>
|
||||||
|
<RelativeTime
|
||||||
|
datetime={createdAtDate}
|
||||||
|
format="micro"
|
||||||
|
/>{' '}
|
||||||
|
‒{' '}
|
||||||
|
</>
|
||||||
|
)
|
||||||
|
}
|
||||||
<time
|
<time
|
||||||
class="created"
|
class="created"
|
||||||
datetime={createdAtDate.toISOString()}
|
datetime={createdAtDate.toISOString()}
|
||||||
|
|
|
@ -15,13 +15,13 @@
|
||||||
"code": "cs-CZ",
|
"code": "cs-CZ",
|
||||||
"nativeName": "čeština",
|
"nativeName": "čeština",
|
||||||
"name": "Czech",
|
"name": "Czech",
|
||||||
"completion": 80
|
"completion": 79
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"code": "de-DE",
|
"code": "de-DE",
|
||||||
"nativeName": "Deutsch",
|
"nativeName": "Deutsch",
|
||||||
"name": "German",
|
"name": "German",
|
||||||
"completion": 96
|
"completion": 95
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"code": "eo-UY",
|
"code": "eo-UY",
|
||||||
|
@ -57,13 +57,13 @@
|
||||||
"code": "fr-FR",
|
"code": "fr-FR",
|
||||||
"nativeName": "français",
|
"nativeName": "français",
|
||||||
"name": "French",
|
"name": "French",
|
||||||
"completion": 99
|
"completion": 98
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"code": "gl-ES",
|
"code": "gl-ES",
|
||||||
"nativeName": "galego",
|
"nativeName": "galego",
|
||||||
"name": "Galician",
|
"name": "Galician",
|
||||||
"completion": 100
|
"completion": 99
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"code": "he-IL",
|
"code": "he-IL",
|
||||||
|
@ -75,7 +75,7 @@
|
||||||
"code": "it-IT",
|
"code": "it-IT",
|
||||||
"nativeName": "italiano",
|
"nativeName": "italiano",
|
||||||
"name": "Italian",
|
"name": "Italian",
|
||||||
"completion": 100
|
"completion": 99
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"code": "ja-JP",
|
"code": "ja-JP",
|
||||||
|
@ -87,13 +87,13 @@
|
||||||
"code": "kab",
|
"code": "kab",
|
||||||
"nativeName": "Taqbaylit",
|
"nativeName": "Taqbaylit",
|
||||||
"name": "Kabyle",
|
"name": "Kabyle",
|
||||||
"completion": 100
|
"completion": 99
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"code": "ko-KR",
|
"code": "ko-KR",
|
||||||
"nativeName": "한국어",
|
"nativeName": "한국어",
|
||||||
"name": "Korean",
|
"name": "Korean",
|
||||||
"completion": 97
|
"completion": 96
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"code": "lt-LT",
|
"code": "lt-LT",
|
||||||
|
@ -105,19 +105,19 @@
|
||||||
"code": "nb-NO",
|
"code": "nb-NO",
|
||||||
"nativeName": "norsk bokmål",
|
"nativeName": "norsk bokmål",
|
||||||
"name": "Norwegian Bokmål",
|
"name": "Norwegian Bokmål",
|
||||||
"completion": 31
|
"completion": 51
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"code": "nl-NL",
|
"code": "nl-NL",
|
||||||
"nativeName": "Nederlands",
|
"nativeName": "Nederlands",
|
||||||
"name": "Dutch",
|
"name": "Dutch",
|
||||||
"completion": 83
|
"completion": 82
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"code": "pl-PL",
|
"code": "pl-PL",
|
||||||
"nativeName": "polski",
|
"nativeName": "polski",
|
||||||
"name": "Polish",
|
"name": "Polish",
|
||||||
"completion": 6
|
"completion": 10
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"code": "pt-BR",
|
"code": "pt-BR",
|
||||||
|
@ -147,7 +147,7 @@
|
||||||
"code": "uk-UA",
|
"code": "uk-UA",
|
||||||
"nativeName": "українська",
|
"nativeName": "українська",
|
||||||
"name": "Ukrainian",
|
"name": "Ukrainian",
|
||||||
"completion": 26
|
"completion": 100
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"code": "zh-CN",
|
"code": "zh-CN",
|
||||||
|
@ -159,6 +159,6 @@
|
||||||
"code": "zh-TW",
|
"code": "zh-TW",
|
||||||
"nativeName": "繁體中文",
|
"nativeName": "繁體中文",
|
||||||
"name": "Traditional Chinese",
|
"name": "Traditional Chinese",
|
||||||
"completion": 15
|
"completion": 32
|
||||||
}
|
}
|
||||||
]
|
]
|
1302
src/locales/ar-SA.po
generated
1302
src/locales/ar-SA.po
generated
File diff suppressed because it is too large
Load diff
1246
src/locales/ca-ES.po
generated
1246
src/locales/ca-ES.po
generated
File diff suppressed because it is too large
Load diff
1302
src/locales/cs-CZ.po
generated
1302
src/locales/cs-CZ.po
generated
File diff suppressed because it is too large
Load diff
1302
src/locales/de-DE.po
generated
1302
src/locales/de-DE.po
generated
File diff suppressed because it is too large
Load diff
1142
src/locales/en.po
generated
1142
src/locales/en.po
generated
File diff suppressed because it is too large
Load diff
1198
src/locales/eo-UY.po
generated
1198
src/locales/eo-UY.po
generated
File diff suppressed because it is too large
Load diff
1210
src/locales/es-ES.po
generated
1210
src/locales/es-ES.po
generated
File diff suppressed because it is too large
Load diff
1194
src/locales/eu-ES.po
generated
1194
src/locales/eu-ES.po
generated
File diff suppressed because it is too large
Load diff
982
src/locales/fa-IR.po
generated
982
src/locales/fa-IR.po
generated
File diff suppressed because it is too large
Load diff
1306
src/locales/fi-FI.po
generated
1306
src/locales/fi-FI.po
generated
File diff suppressed because it is too large
Load diff
1306
src/locales/fr-FR.po
generated
1306
src/locales/fr-FR.po
generated
File diff suppressed because it is too large
Load diff
1306
src/locales/gl-ES.po
generated
1306
src/locales/gl-ES.po
generated
File diff suppressed because it is too large
Load diff
1300
src/locales/he-IL.po
generated
1300
src/locales/he-IL.po
generated
File diff suppressed because it is too large
Load diff
1192
src/locales/it-IT.po
generated
1192
src/locales/it-IT.po
generated
File diff suppressed because it is too large
Load diff
1304
src/locales/ja-JP.po
generated
1304
src/locales/ja-JP.po
generated
File diff suppressed because it is too large
Load diff
1306
src/locales/kab.po
generated
1306
src/locales/kab.po
generated
File diff suppressed because it is too large
Load diff
1324
src/locales/ko-KR.po
generated
1324
src/locales/ko-KR.po
generated
File diff suppressed because it is too large
Load diff
1236
src/locales/lt-LT.po
generated
1236
src/locales/lt-LT.po
generated
File diff suppressed because it is too large
Load diff
1538
src/locales/nb-NO.po
generated
1538
src/locales/nb-NO.po
generated
File diff suppressed because it is too large
Load diff
1314
src/locales/nl-NL.po
generated
1314
src/locales/nl-NL.po
generated
File diff suppressed because it is too large
Load diff
1300
src/locales/oc-FR.po
generated
1300
src/locales/oc-FR.po
generated
File diff suppressed because it is too large
Load diff
1286
src/locales/pl-PL.po
generated
1286
src/locales/pl-PL.po
generated
File diff suppressed because it is too large
Load diff
1326
src/locales/pt-BR.po
generated
1326
src/locales/pt-BR.po
generated
File diff suppressed because it is too large
Load diff
1334
src/locales/pt-PT.po
generated
1334
src/locales/pt-PT.po
generated
File diff suppressed because it is too large
Load diff
1308
src/locales/ru-RU.po
generated
1308
src/locales/ru-RU.po
generated
File diff suppressed because it is too large
Load diff
1328
src/locales/th-TH.po
generated
1328
src/locales/th-TH.po
generated
File diff suppressed because it is too large
Load diff
2632
src/locales/uk-UA.po
generated
2632
src/locales/uk-UA.po
generated
File diff suppressed because it is too large
Load diff
1312
src/locales/zh-CN.po
generated
1312
src/locales/zh-CN.po
generated
File diff suppressed because it is too large
Load diff
1598
src/locales/zh-TW.po
generated
1598
src/locales/zh-TW.po
generated
File diff suppressed because it is too large
Load diff
|
@ -232,7 +232,7 @@ function AccountStatuses() {
|
||||||
const { i18n } = useLingui();
|
const { i18n } = useLingui();
|
||||||
let title = t`Account posts`;
|
let title = t`Account posts`;
|
||||||
if (account?.acct) {
|
if (account?.acct) {
|
||||||
const acctDisplay = /@/.test(account.acct) ? '' : '@' + account.acct;
|
const acctDisplay = (/@/.test(account.acct) ? '' : '@') + account.acct;
|
||||||
const accountDisplay = account?.displayName
|
const accountDisplay = account?.displayName
|
||||||
? `${account.displayName} (${acctDisplay})`
|
? `${account.displayName} (${acctDisplay})`
|
||||||
: `${acctDisplay}`;
|
: `${acctDisplay}`;
|
||||||
|
|
|
@ -1061,11 +1061,11 @@ function Catchup() {
|
||||||
onClick={async () => {
|
onClick={async () => {
|
||||||
const yes = confirm(t`Remove this catch-up?`);
|
const yes = confirm(t`Remove this catch-up?`);
|
||||||
if (yes) {
|
if (yes) {
|
||||||
let t = showToast(
|
let st = showToast(
|
||||||
t`Removing Catch-up ${pc.id}`,
|
t`Removing Catch-up ${pc.id}`,
|
||||||
);
|
);
|
||||||
await db.catchup.del(pc.id);
|
await db.catchup.del(pc.id);
|
||||||
t?.hideToast?.();
|
st?.hideToast?.();
|
||||||
showToast(t`Catch-up ${pc.id} removed`);
|
showToast(t`Catch-up ${pc.id} removed`);
|
||||||
reloadCatchups();
|
reloadCatchups();
|
||||||
}
|
}
|
||||||
|
@ -1468,7 +1468,7 @@ function Catchup() {
|
||||||
))}
|
))}
|
||||||
</fieldset> */}
|
</fieldset> */}
|
||||||
<span class="filter-label">
|
<span class="filter-label">
|
||||||
<Trans>Group</Trans>
|
<Trans id="group.filter">Group</Trans>
|
||||||
</span>{' '}
|
</span>{' '}
|
||||||
<fieldset class="radio-field-group">
|
<fieldset class="radio-field-group">
|
||||||
{FILTER_GROUPS.map((key) => (
|
{FILTER_GROUPS.map((key) => (
|
||||||
|
|
|
@ -17,7 +17,14 @@ import useTitle from '../utils/useTitle';
|
||||||
const LIMIT = 20;
|
const LIMIT = 20;
|
||||||
|
|
||||||
function Following({ title, path, id, ...props }) {
|
function Following({ title, path, id, ...props }) {
|
||||||
useTitle(title || t`Following`, path || '/following');
|
useTitle(
|
||||||
|
title ||
|
||||||
|
t({
|
||||||
|
id: 'following.title',
|
||||||
|
message: 'Following',
|
||||||
|
}),
|
||||||
|
path || '/following',
|
||||||
|
);
|
||||||
const { masto, streaming, instance } = api();
|
const { masto, streaming, instance } = api();
|
||||||
const snapStates = useSnapshot(states);
|
const snapStates = useSnapshot(states);
|
||||||
const homeIterator = useRef();
|
const homeIterator = useRef();
|
||||||
|
@ -131,7 +138,7 @@ function Following({ title, path, id, ...props }) {
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Timeline
|
<Timeline
|
||||||
title={title || t`Following`}
|
title={title || t({ id: 'following.title', message: 'Following' })}
|
||||||
id={id || 'following'}
|
id={id || 'following'}
|
||||||
emptyText={t`Nothing to see here.`}
|
emptyText={t`Nothing to see here.`}
|
||||||
errorText={t`Unable to load posts.`}
|
errorText={t`Unable to load posts.`}
|
||||||
|
|
|
@ -27,6 +27,11 @@
|
||||||
margin: 0;
|
margin: 0;
|
||||||
opacity: 1;
|
opacity: 1;
|
||||||
|
|
||||||
|
#columns & {
|
||||||
|
position: static;
|
||||||
|
background-color: inherit;
|
||||||
|
}
|
||||||
|
|
||||||
small {
|
small {
|
||||||
font-weight: normal;
|
font-weight: normal;
|
||||||
font-size: var(--text-size);
|
font-size: var(--text-size);
|
||||||
|
@ -556,6 +561,11 @@
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
gap: 4px;
|
gap: 4px;
|
||||||
|
|
||||||
|
small {
|
||||||
|
flex: 1;
|
||||||
|
min-width: 0;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -24,6 +24,12 @@ const SHORT_LIMIT = 5;
|
||||||
const LIMIT = 40;
|
const LIMIT = 40;
|
||||||
const emptySearchParams = new URLSearchParams();
|
const emptySearchParams = new URLSearchParams();
|
||||||
|
|
||||||
|
const scrollIntoViewOptions = {
|
||||||
|
block: 'nearest',
|
||||||
|
inline: 'center',
|
||||||
|
behavior: 'smooth',
|
||||||
|
};
|
||||||
|
|
||||||
function Search({ columnMode, ...props }) {
|
function Search({ columnMode, ...props }) {
|
||||||
const params = columnMode ? {} : useParams();
|
const params = columnMode ? {} : useParams();
|
||||||
const { masto, instance, authenticated } = api({
|
const { masto, instance, authenticated } = api({
|
||||||
|
@ -186,10 +192,79 @@ function Search({ columnMode, ...props }) {
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
|
|
||||||
|
const itemsSelector = '.timeline > li > a, .hashtag-list > li > a';
|
||||||
|
const jRef = useHotkeys('j', () => {
|
||||||
|
const activeItem = document.activeElement.closest(itemsSelector);
|
||||||
|
const activeItemRect = activeItem?.getBoundingClientRect();
|
||||||
|
const allItems = Array.from(
|
||||||
|
scrollableRef.current.querySelectorAll(itemsSelector),
|
||||||
|
);
|
||||||
|
if (
|
||||||
|
activeItem &&
|
||||||
|
activeItemRect.top < scrollableRef.current.clientHeight &&
|
||||||
|
activeItemRect.bottom > 0
|
||||||
|
) {
|
||||||
|
const activeItemIndex = allItems.indexOf(activeItem);
|
||||||
|
let nextItem = allItems[activeItemIndex + 1];
|
||||||
|
if (nextItem) {
|
||||||
|
nextItem.focus();
|
||||||
|
nextItem.scrollIntoView(scrollIntoViewOptions);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
const topmostItem = allItems.find((item) => {
|
||||||
|
const itemRect = item.getBoundingClientRect();
|
||||||
|
return itemRect.top >= 44 && itemRect.left >= 0;
|
||||||
|
});
|
||||||
|
if (topmostItem) {
|
||||||
|
topmostItem.focus();
|
||||||
|
topmostItem.scrollIntoView(scrollIntoViewOptions);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
const kRef = useHotkeys('k', () => {
|
||||||
|
// focus on previous status after active item
|
||||||
|
const activeItem = document.activeElement.closest(itemsSelector);
|
||||||
|
const activeItemRect = activeItem?.getBoundingClientRect();
|
||||||
|
const allItems = Array.from(
|
||||||
|
scrollableRef.current.querySelectorAll(itemsSelector),
|
||||||
|
);
|
||||||
|
if (
|
||||||
|
activeItem &&
|
||||||
|
activeItemRect.top < scrollableRef.current.clientHeight &&
|
||||||
|
activeItemRect.bottom > 0
|
||||||
|
) {
|
||||||
|
const activeItemIndex = allItems.indexOf(activeItem);
|
||||||
|
let prevItem = allItems[activeItemIndex - 1];
|
||||||
|
if (prevItem) {
|
||||||
|
prevItem.focus();
|
||||||
|
prevItem.scrollIntoView(scrollIntoViewOptions);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
const topmostItem = allItems.find((item) => {
|
||||||
|
const itemRect = item.getBoundingClientRect();
|
||||||
|
return itemRect.top >= 44 && itemRect.left >= 0;
|
||||||
|
});
|
||||||
|
if (topmostItem) {
|
||||||
|
topmostItem.focus();
|
||||||
|
topmostItem.scrollIntoView(scrollIntoViewOptions);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
const [filterBarParent] = useAutoAnimate();
|
const [filterBarParent] = useAutoAnimate();
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div id="search-page" class="deck-container" ref={scrollableRef}>
|
<div
|
||||||
|
id="search-page"
|
||||||
|
class="deck-container"
|
||||||
|
tabIndex="-1"
|
||||||
|
ref={(node) => {
|
||||||
|
scrollableRef.current = node;
|
||||||
|
jRef(node);
|
||||||
|
kRef(node);
|
||||||
|
}}
|
||||||
|
>
|
||||||
<div class="timeline-deck deck">
|
<div class="timeline-deck deck">
|
||||||
<header class={uiState === 'loading' ? 'loading' : ''}>
|
<header class={uiState === 'loading' ? 'loading' : ''}>
|
||||||
<div class="header-grid">
|
<div class="header-grid">
|
||||||
|
|
|
@ -14,6 +14,7 @@ import targetLanguages from '../data/lingva-target-languages';
|
||||||
import { api } from '../utils/api';
|
import { api } from '../utils/api';
|
||||||
import getTranslateTargetLanguage from '../utils/get-translate-target-language';
|
import getTranslateTargetLanguage from '../utils/get-translate-target-language';
|
||||||
import localeCode2Text from '../utils/localeCode2Text';
|
import localeCode2Text from '../utils/localeCode2Text';
|
||||||
|
import prettyBytes from '../utils/pretty-bytes';
|
||||||
import {
|
import {
|
||||||
initSubscription,
|
initSubscription,
|
||||||
isPushSupported,
|
isPushSupported,
|
||||||
|
@ -856,6 +857,13 @@ function Settings({ onClose }) {
|
||||||
>
|
>
|
||||||
Show keys count
|
Show keys count
|
||||||
</button>{' '}
|
</button>{' '}
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
class="plain2 small"
|
||||||
|
onClick={async () => alert(await getCachesSize())}
|
||||||
|
>
|
||||||
|
Show cache size
|
||||||
|
</button>{' '}
|
||||||
<button
|
<button
|
||||||
type="button"
|
type="button"
|
||||||
class="plain2 small"
|
class="plain2 small"
|
||||||
|
@ -902,6 +910,33 @@ async function getCachesKeys() {
|
||||||
return total;
|
return total;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async function getCachesSize() {
|
||||||
|
const keys = await caches.keys();
|
||||||
|
let total = {};
|
||||||
|
let TOTAL = 0;
|
||||||
|
for (const key of keys) {
|
||||||
|
const cache = await caches.open(key);
|
||||||
|
const k = await cache.keys();
|
||||||
|
for (const item of k) {
|
||||||
|
try {
|
||||||
|
const response = await cache.match(item);
|
||||||
|
const blob = await response.blob();
|
||||||
|
total[key] = (total[key] || 0) + blob.size;
|
||||||
|
TOTAL += blob.size;
|
||||||
|
} catch (e) {
|
||||||
|
alert('Failed to get cache size for ' + item);
|
||||||
|
alert(e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return {
|
||||||
|
...Object.fromEntries(
|
||||||
|
Object.entries(total).map(([k, v]) => [k, prettyBytes(v)]),
|
||||||
|
),
|
||||||
|
TOTAL: prettyBytes(TOTAL),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
function clearCacheKey(key) {
|
function clearCacheKey(key) {
|
||||||
return caches.delete(key);
|
return caches.delete(key);
|
||||||
}
|
}
|
||||||
|
|
|
@ -208,6 +208,11 @@ function StatusParent(props) {
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// oldest first
|
||||||
|
function createdAtSort(a, b) {
|
||||||
|
return new Date(b.created_at) - new Date(a.created_at);
|
||||||
|
}
|
||||||
|
|
||||||
function StatusThread({ id, closeLink = '/', instance: propInstance }) {
|
function StatusThread({ id, closeLink = '/', instance: propInstance }) {
|
||||||
const [searchParams, setSearchParams] = useSearchParams();
|
const [searchParams, setSearchParams] = useSearchParams();
|
||||||
const mediaParam = searchParams.get('media');
|
const mediaParam = searchParams.get('media');
|
||||||
|
@ -321,6 +326,9 @@ function StatusThread({ id, closeLink = '/', instance: propInstance }) {
|
||||||
const context = await contextFetch;
|
const context = await contextFetch;
|
||||||
const { ancestors, descendants } = context;
|
const { ancestors, descendants } = context;
|
||||||
|
|
||||||
|
ancestors.sort(createdAtSort);
|
||||||
|
descendants.sort(createdAtSort);
|
||||||
|
|
||||||
totalDescendants.current = descendants?.length || 0;
|
totalDescendants.current = descendants?.length || 0;
|
||||||
|
|
||||||
const missingStatuses = new Set();
|
const missingStatuses = new Set();
|
||||||
|
@ -360,7 +368,11 @@ function StatusThread({ id, closeLink = '/', instance: propInstance }) {
|
||||||
nestedDescendants.push(status);
|
nestedDescendants.push(status);
|
||||||
} else if (
|
} else if (
|
||||||
!status.inReplyToAccountId &&
|
!status.inReplyToAccountId &&
|
||||||
nestedDescendants.find((s) => s.id === status.inReplyToId) &&
|
nestedDescendants.find(
|
||||||
|
(s) =>
|
||||||
|
s.id === status.inReplyToId &&
|
||||||
|
s.account.id === heroStatus.account.id,
|
||||||
|
) &&
|
||||||
status.account.id === heroStatus.account.id
|
status.account.id === heroStatus.account.id
|
||||||
) {
|
) {
|
||||||
// If replying to hero's own statuses, it's part of the thread, level 1
|
// If replying to hero's own statuses, it's part of the thread, level 1
|
||||||
|
@ -380,19 +392,35 @@ function StatusThread({ id, closeLink = '/', instance: propInstance }) {
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// sort hero author to top
|
||||||
|
nestedDescendants.sort((a, b) => {
|
||||||
|
const heroAccountID = heroStatus.account.id;
|
||||||
|
if (a.account.id === heroAccountID && b.account.id !== heroAccountID)
|
||||||
|
return -1;
|
||||||
|
if (b.account.id === heroAccountID && a.account.id !== heroAccountID)
|
||||||
|
return 1;
|
||||||
|
return 0;
|
||||||
|
});
|
||||||
|
|
||||||
console.log({ ancestors, descendants, nestedDescendants });
|
console.log({ ancestors, descendants, nestedDescendants });
|
||||||
if (missingStatuses.size) {
|
if (missingStatuses.size) {
|
||||||
console.error('Missing statuses', [...missingStatuses]);
|
console.error('Missing statuses', [...missingStatuses]);
|
||||||
}
|
}
|
||||||
|
|
||||||
function expandReplies(_replies) {
|
let descendantLevelsCount = 1;
|
||||||
|
function expandReplies(_replies, level) {
|
||||||
|
const nextLevel = level + 1;
|
||||||
|
if (nextLevel > descendantLevelsCount) {
|
||||||
|
descendantLevelsCount = level;
|
||||||
|
}
|
||||||
return _replies?.map((_r) => ({
|
return _replies?.map((_r) => ({
|
||||||
id: _r.id,
|
id: _r.id,
|
||||||
account: _r.account,
|
account: _r.account,
|
||||||
repliesCount: _r.repliesCount,
|
repliesCount: _r.repliesCount,
|
||||||
content: _r.content,
|
content: _r.content,
|
||||||
weight: calcStatusWeight(_r),
|
weight: calcStatusWeight(_r),
|
||||||
replies: expandReplies(_r.__replies),
|
level: nextLevel,
|
||||||
|
replies: expandReplies(_r.__replies, nextLevel),
|
||||||
}));
|
}));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -418,7 +446,8 @@ function StatusThread({ id, closeLink = '/', instance: propInstance }) {
|
||||||
descendant: true,
|
descendant: true,
|
||||||
thread: s.account.id === heroStatus.account.id,
|
thread: s.account.id === heroStatus.account.id,
|
||||||
weight: calcStatusWeight(s),
|
weight: calcStatusWeight(s),
|
||||||
replies: expandReplies(s.__replies),
|
level: 1,
|
||||||
|
replies: expandReplies(s.__replies, 1),
|
||||||
})),
|
})),
|
||||||
];
|
];
|
||||||
|
|
||||||
|
@ -429,12 +458,13 @@ function StatusThread({ id, closeLink = '/', instance: propInstance }) {
|
||||||
};
|
};
|
||||||
|
|
||||||
// Set limit to hero's index
|
// Set limit to hero's index
|
||||||
const heroLimit = allStatuses.findIndex((s) => s.id === id);
|
// const heroLimit = allStatuses.findIndex((s) => s.id === id);
|
||||||
|
const heroLimit = ancestors.length || 0; // 0-indexed
|
||||||
if (heroLimit >= limit) {
|
if (heroLimit >= limit) {
|
||||||
setLimit(heroLimit + 1);
|
setLimit(heroLimit + 1);
|
||||||
}
|
}
|
||||||
|
|
||||||
console.log({ allStatuses });
|
console.log({ allStatuses, descendantLevelsCount });
|
||||||
setStatuses(allStatuses);
|
setStatuses(allStatuses);
|
||||||
cachedStatusesMap[id] = allStatuses;
|
cachedStatusesMap[id] = allStatuses;
|
||||||
|
|
||||||
|
@ -564,7 +594,10 @@ function StatusThread({ id, closeLink = '/', instance: propInstance }) {
|
||||||
useTitle(
|
useTitle(
|
||||||
heroDisplayName && heroContentText
|
heroDisplayName && heroContentText
|
||||||
? `${heroDisplayName}: "${heroContentText}"`
|
? `${heroDisplayName}: "${heroContentText}"`
|
||||||
: t`Post`,
|
: t({
|
||||||
|
id: 'post.title',
|
||||||
|
message: 'Post',
|
||||||
|
}),
|
||||||
'/:instance?/s/:id',
|
'/:instance?/s/:id',
|
||||||
);
|
);
|
||||||
|
|
||||||
|
@ -747,6 +780,7 @@ function StatusThread({ id, closeLink = '/', instance: propInstance }) {
|
||||||
replies,
|
replies,
|
||||||
repliesCount,
|
repliesCount,
|
||||||
weight,
|
weight,
|
||||||
|
level,
|
||||||
} = status;
|
} = status;
|
||||||
const isHero = statusID === id;
|
const isHero = statusID === id;
|
||||||
const isLinkable = isThread || ancestor;
|
const isLinkable = isThread || ancestor;
|
||||||
|
@ -920,7 +954,7 @@ function StatusThread({ id, closeLink = '/', instance: propInstance }) {
|
||||||
instance={instance}
|
instance={instance}
|
||||||
replies={replies}
|
replies={replies}
|
||||||
hasParentThread={thread}
|
hasParentThread={thread}
|
||||||
level={1}
|
level={level}
|
||||||
accWeight={weight}
|
accWeight={weight}
|
||||||
openAll={totalDescendants.current < SUBCOMMENTS_OPEN_ALL_LIMIT}
|
openAll={totalDescendants.current < SUBCOMMENTS_OPEN_ALL_LIMIT}
|
||||||
parentLink={{
|
parentLink={{
|
||||||
|
@ -1094,7 +1128,7 @@ function StatusThread({ id, closeLink = '/', instance: propInstance }) {
|
||||||
</>
|
</>
|
||||||
) : (
|
) : (
|
||||||
<>
|
<>
|
||||||
Post{' '}
|
<Trans id="post.title">Post</Trans>{' '}
|
||||||
<button
|
<button
|
||||||
type="button"
|
type="button"
|
||||||
class="ancestors-indicator light small"
|
class="ancestors-indicator light small"
|
||||||
|
@ -1403,22 +1437,40 @@ function SubComments({
|
||||||
};
|
};
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
|
// If not open, delay render replies
|
||||||
|
const [renderReplies, setRenderReplies] = useState(openBefore || open);
|
||||||
|
useEffect(() => {
|
||||||
|
let timer;
|
||||||
|
if (!openBefore && !open) {
|
||||||
|
timer = setTimeout(() => setRenderReplies(true), 100);
|
||||||
|
}
|
||||||
|
return () => clearTimeout(timer);
|
||||||
|
}, [openBefore, open]);
|
||||||
|
|
||||||
|
const Container = open ? 'div' : 'details';
|
||||||
|
const isDetails = Container === 'details';
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<details
|
<Container
|
||||||
ref={detailsRef}
|
ref={detailsRef}
|
||||||
class="replies"
|
class="replies"
|
||||||
open={openBefore || open}
|
open={isDetails ? openBefore || open : undefined}
|
||||||
onToggle={(e) => {
|
onToggle={
|
||||||
|
isDetails
|
||||||
|
? (e) => {
|
||||||
const { open } = e.target;
|
const { open } = e.target;
|
||||||
// use first reply as ID
|
// use first reply as ID
|
||||||
cachedRepliesToggle[replies[0].id] = open;
|
cachedRepliesToggle[replies[0].id] = open;
|
||||||
}}
|
}
|
||||||
|
: undefined
|
||||||
|
}
|
||||||
style={{
|
style={{
|
||||||
'--comments-level': level,
|
'--comments-level': level,
|
||||||
}}
|
}}
|
||||||
data-comments-level={level}
|
data-comments-level={level}
|
||||||
data-comments-level-overflow={level > 4}
|
data-comments-level-overflow={level > 4}
|
||||||
>
|
>
|
||||||
|
{!open && (
|
||||||
<summary class="replies-summary" hidden={open}>
|
<summary class="replies-summary" hidden={open}>
|
||||||
<span class="avatars">
|
<span class="avatars">
|
||||||
{accounts.map((a) => (
|
{accounts.map((a) => (
|
||||||
|
@ -1478,6 +1530,8 @@ function SubComments({
|
||||||
</Link>
|
</Link>
|
||||||
)}
|
)}
|
||||||
</summary>
|
</summary>
|
||||||
|
)}
|
||||||
|
{renderReplies && (
|
||||||
<ul>
|
<ul>
|
||||||
{replies.map((r) => (
|
{replies.map((r) => (
|
||||||
<li key={r.id}>
|
<li key={r.id}>
|
||||||
|
@ -1512,7 +1566,7 @@ function SubComments({
|
||||||
<SubComments
|
<SubComments
|
||||||
instance={instance}
|
instance={instance}
|
||||||
replies={r.replies}
|
replies={r.replies}
|
||||||
level={level + 1}
|
level={r.level}
|
||||||
accWeight={!open ? r.weight : totalWeight}
|
accWeight={!open ? r.weight : totalWeight}
|
||||||
openAll={openAll}
|
openAll={openAll}
|
||||||
parentLink={{
|
parentLink={{
|
||||||
|
@ -1526,7 +1580,8 @@ function SubComments({
|
||||||
</li>
|
</li>
|
||||||
))}
|
))}
|
||||||
</ul>
|
</ul>
|
||||||
</details>
|
)}
|
||||||
|
</Container>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -27,12 +27,19 @@ const CODE_BLOCK_END_REGEX = /```$/;
|
||||||
const INLINE_CODE_REGEX = /`[^`]+`/;
|
const INLINE_CODE_REGEX = /`[^`]+`/;
|
||||||
const TWITTER_DOMAIN_REGEX = /(twitter|x)\.com/i;
|
const TWITTER_DOMAIN_REGEX = /(twitter|x)\.com/i;
|
||||||
const TWITTER_MENTION_REGEX = /@[a-zA-Z0-9_]+@(twitter|x)\.com/;
|
const TWITTER_MENTION_REGEX = /@[a-zA-Z0-9_]+@(twitter|x)\.com/;
|
||||||
const TWITTER_MENTION_CAPTURE_REGEX = /(@([a-zA-Z0-9_]+)@(twitter|x)\.com)/;
|
const TWITTER_MENTION_CAPTURE_REGEX = /(@([a-zA-Z0-9_]+)@(twitter|x)\.com)/g;
|
||||||
|
const CODE_INLINE_CAPTURE_REGEX = /(`[^]+?`)/g;
|
||||||
|
|
||||||
function createDOM(html, isDocumentFragment) {
|
function createDOM(html, isDocumentFragment) {
|
||||||
|
if (isDocumentFragment) {
|
||||||
const tpl = document.createElement('template');
|
const tpl = document.createElement('template');
|
||||||
tpl.innerHTML = html;
|
tpl.innerHTML = html;
|
||||||
return isDocumentFragment ? tpl.content : tpl;
|
return tpl.content;
|
||||||
|
} else {
|
||||||
|
const tpl = document.createElement('div');
|
||||||
|
tpl.innerHTML = html;
|
||||||
|
return tpl;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function _enhanceContent(content, opts = {}) {
|
function _enhanceContent(content, opts = {}) {
|
||||||
|
@ -110,13 +117,11 @@ function _enhanceContent(content, opts = {}) {
|
||||||
// ======
|
// ======
|
||||||
// Convert :shortcode: to <img />
|
// Convert :shortcode: to <img />
|
||||||
let textNodes;
|
let textNodes;
|
||||||
if (enhancedContent.includes(':')) {
|
if (enhancedContent.includes(':') && emojis?.length) {
|
||||||
textNodes = extractTextNodes(dom);
|
textNodes = extractTextNodes(dom);
|
||||||
for (const node of textNodes) {
|
for (const node of textNodes) {
|
||||||
let html = escapeHTML(node.nodeValue);
|
let html = escapeHTML(node.nodeValue);
|
||||||
if (emojis) {
|
|
||||||
html = emojifyText(html, emojis);
|
html = emojifyText(html, emojis);
|
||||||
}
|
|
||||||
fauxDiv.innerHTML = html;
|
fauxDiv.innerHTML = html;
|
||||||
node.replaceWith(...fauxDiv.childNodes);
|
node.replaceWith(...fauxDiv.childNodes);
|
||||||
}
|
}
|
||||||
|
@ -193,7 +198,7 @@ function _enhanceContent(content, opts = {}) {
|
||||||
for (const node of textNodes) {
|
for (const node of textNodes) {
|
||||||
let html = escapeHTML(node.nodeValue);
|
let html = escapeHTML(node.nodeValue);
|
||||||
if (INLINE_CODE_REGEX.test(html)) {
|
if (INLINE_CODE_REGEX.test(html)) {
|
||||||
html = html.replaceAll(/(`[^]+?`)/g, '<code>$1</code>');
|
html = html.replaceAll(CODE_INLINE_CAPTURE_REGEX, '<code>$1</code>');
|
||||||
}
|
}
|
||||||
fauxDiv.innerHTML = html;
|
fauxDiv.innerHTML = html;
|
||||||
// const nodes = [...fauxDiv.childNodes];
|
// const nodes = [...fauxDiv.childNodes];
|
||||||
|
@ -285,6 +290,17 @@ function _enhanceContent(content, opts = {}) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// FIX CLOAK MODE FOR SAFARI
|
||||||
|
// Workaround for Safari so that `text-decoration-thickness` works
|
||||||
|
// Wrap child text nodes in spans
|
||||||
|
for (const node of dom.childNodes) {
|
||||||
|
if (node.nodeType === Node.TEXT_NODE) {
|
||||||
|
const span = document.createElement('span');
|
||||||
|
span.textContent = node.textContent;
|
||||||
|
dom.replaceChild(span, node);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if (postEnhanceDOM) {
|
if (postEnhanceDOM) {
|
||||||
queueMicrotask(() => postEnhanceDOM(dom));
|
queueMicrotask(() => postEnhanceDOM(dom));
|
||||||
// postEnhanceDOM(dom); // mutate dom
|
// postEnhanceDOM(dom); // mutate dom
|
||||||
|
|
|
@ -3,7 +3,7 @@ const focusDeck = () => {
|
||||||
const columns = document.getElementById('columns');
|
const columns = document.getElementById('columns');
|
||||||
if (columns) {
|
if (columns) {
|
||||||
// Focus first column
|
// Focus first column
|
||||||
// columns.querySelector('.deck-container')?.focus?.();
|
columns.querySelector('.deck-container')?.focus?.();
|
||||||
} else {
|
} else {
|
||||||
const modals = document.querySelectorAll('#modal-container > *');
|
const modals = document.querySelectorAll('#modal-container > *');
|
||||||
if (modals?.length) {
|
if (modals?.length) {
|
||||||
|
|
|
@ -9,6 +9,10 @@ const notificationTypeKeys = {
|
||||||
poll: ['status'],
|
poll: ['status'],
|
||||||
update: ['status'],
|
update: ['status'],
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const GROUP_TYPES = ['favourite', 'reblog', 'follow'];
|
||||||
|
const groupable = (type) => GROUP_TYPES.includes(type);
|
||||||
|
|
||||||
export function fixNotifications(notifications) {
|
export function fixNotifications(notifications) {
|
||||||
return notifications.filter((notification) => {
|
return notifications.filter((notification) => {
|
||||||
const { type, id, createdAt } = notification;
|
const { type, id, createdAt } = notification;
|
||||||
|
@ -85,7 +89,7 @@ export function groupNotifications2(groupNotifications) {
|
||||||
} = gn;
|
} = gn;
|
||||||
const date = createdAt ? new Date(createdAt).toLocaleDateString() : '';
|
const date = createdAt ? new Date(createdAt).toLocaleDateString() : '';
|
||||||
let virtualType = type;
|
let virtualType = type;
|
||||||
const sameCount =
|
// const sameCount =
|
||||||
notificationsCount > 0 && notificationsCount === sampleAccounts?.length;
|
notificationsCount > 0 && notificationsCount === sampleAccounts?.length;
|
||||||
// if (sameCount && (type === 'favourite' || type === 'reblog')) {
|
// if (sameCount && (type === 'favourite' || type === 'reblog')) {
|
||||||
if (type === 'favourite' || type === 'reblog') {
|
if (type === 'favourite' || type === 'reblog') {
|
||||||
|
@ -94,7 +98,9 @@ export function groupNotifications2(groupNotifications) {
|
||||||
// const key = `${status?.id}-${virtualType}-${date}-${sameCount ? 1 : 0}`;
|
// const key = `${status?.id}-${virtualType}-${date}-${sameCount ? 1 : 0}`;
|
||||||
const key = `${status?.id}-${virtualType}-${date}`;
|
const key = `${status?.id}-${virtualType}-${date}`;
|
||||||
const mappedNotification = notificationsMap[key];
|
const mappedNotification = notificationsMap[key];
|
||||||
if (mappedNotification) {
|
if (!groupable(type)) {
|
||||||
|
newGroupNotifications1.push(gn);
|
||||||
|
} else if (mappedNotification) {
|
||||||
// Merge sampleAccounts + merge _types
|
// Merge sampleAccounts + merge _types
|
||||||
sampleAccounts.forEach((a) => {
|
sampleAccounts.forEach((a) => {
|
||||||
const mappedAccount = mappedNotification.sampleAccounts.find(
|
const mappedAccount = mappedNotification.sampleAccounts.find(
|
||||||
|
@ -199,7 +205,7 @@ export default function groupNotifications(notifications) {
|
||||||
}
|
}
|
||||||
const key = `${status?.id}-${virtualType}-${date}`;
|
const key = `${status?.id}-${virtualType}-${date}`;
|
||||||
const mappedNotification = notificationsMap[key];
|
const mappedNotification = notificationsMap[key];
|
||||||
if (virtualType === 'follow_request') {
|
if (!groupable(type)) {
|
||||||
cleanNotifications[j++] = notification;
|
cleanNotifications[j++] = notification;
|
||||||
} else if (mappedNotification?.account) {
|
} else if (mappedNotification?.account) {
|
||||||
const mappedAccount = mappedNotification._accounts.find(
|
const mappedAccount = mappedNotification._accounts.find(
|
||||||
|
|
|
@ -132,18 +132,14 @@ export function getCurrentNodeInfo() {
|
||||||
// Massage these instance configurations to match the Mastodon API
|
// Massage these instance configurations to match the Mastodon API
|
||||||
// - Pleroma
|
// - Pleroma
|
||||||
function getInstanceConfiguration(instance) {
|
function getInstanceConfiguration(instance) {
|
||||||
const {
|
const { configuration, maxMediaAttachments, maxTootChars, pollLimits } =
|
||||||
configuration,
|
instance;
|
||||||
maxMediaAttachments,
|
|
||||||
maxTootChars,
|
|
||||||
pleroma,
|
|
||||||
pollLimits,
|
|
||||||
} = instance;
|
|
||||||
|
|
||||||
const statuses = configuration?.statuses || {};
|
const statuses = configuration?.statuses || {};
|
||||||
if (maxMediaAttachments) {
|
if (maxMediaAttachments) {
|
||||||
statuses.maxMediaAttachments ??= maxMediaAttachments;
|
statuses.maxMediaAttachments ??= maxMediaAttachments;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (maxTootChars) {
|
if (maxTootChars) {
|
||||||
statuses.maxCharacters ??= maxTootChars;
|
statuses.maxCharacters ??= maxTootChars;
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in a new issue