mirror of
https://github.com/cheeaun/phanpy.git
synced 2025-01-18 15:13:36 +03:00
Merge branch 'main' into feature/paste-attach
# Conflicts: # src/locales/en.po
This commit is contained in:
commit
aa332743d4
61 changed files with 14477 additions and 9894 deletions
22
.github/workflows/prettier-pr.yml
vendored
Normal file
22
.github/workflows/prettier-pr.yml
vendored
Normal file
|
@ -0,0 +1,22 @@
|
||||||
|
name: Prettier on pull requests
|
||||||
|
|
||||||
|
on:
|
||||||
|
pull_request:
|
||||||
|
workflow_dispatch:
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
prettier:
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v4
|
||||||
|
# Need node to install prettier plugin(s)
|
||||||
|
- uses: actions/setup-node@v4
|
||||||
|
with:
|
||||||
|
node-version: 20
|
||||||
|
- run: npm ci
|
||||||
|
- uses: creyD/prettier_action@v4.3
|
||||||
|
with:
|
||||||
|
dry: true
|
||||||
|
# Don't write anything
|
||||||
|
prettier_options: '--check --config .prettierrc'
|
||||||
|
file_pattern: '.'
|
|
@ -304,6 +304,8 @@ Costs involved in running and developing this web app:
|
||||||
- <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/d95ddd44e8dcb3a039f8a3463aed781d_default.png" 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/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)
|
||||||
- <img src="https://crowdin-static.downloads.crowdin.com/avatar/13557465/medium/8feebf3677fa80c01e8c54c4fbe097e0_default.png" alt="" width="16" height="16" /> elissarc (French)
|
- <img src="https://crowdin-static.downloads.crowdin.com/avatar/13557465/medium/8feebf3677fa80c01e8c54c4fbe097e0_default.png" alt="" width="16" height="16" /> elissarc (French)
|
||||||
- <img src="https://crowdin-static.downloads.crowdin.com/avatar/16528627/medium/9036f6eced0257f4e1ea4c5bd499de2d_default.png" alt="" width="16" height="16" /> ElPamplina (Spanish)
|
- <img src="https://crowdin-static.downloads.crowdin.com/avatar/16528627/medium/9036f6eced0257f4e1ea4c5bd499de2d_default.png" alt="" width="16" height="16" /> ElPamplina (Spanish)
|
||||||
|
@ -311,6 +313,7 @@ Costs involved in running and developing this web app:
|
||||||
- <img src="https://crowdin-static.downloads.crowdin.com/avatar/14444512/medium/99d0e7a3076deccbdfe0aa0b0612308c.jpeg" alt="" width="16" height="16" /> Freeesia (Japanese)
|
- <img src="https://crowdin-static.downloads.crowdin.com/avatar/14444512/medium/99d0e7a3076deccbdfe0aa0b0612308c.jpeg" alt="" width="16" height="16" /> Freeesia (Japanese)
|
||||||
- <img src="https://crowdin-static.downloads.crowdin.com/avatar/12617257/medium/a201650da44fed28890b0e0d8477a663.jpg" alt="" width="16" height="16" /> ghose (Galician)
|
- <img src="https://crowdin-static.downloads.crowdin.com/avatar/12617257/medium/a201650da44fed28890b0e0d8477a663.jpg" alt="" width="16" height="16" /> ghose (Galician)
|
||||||
- <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/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/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)
|
||||||
|
@ -318,6 +321,7 @@ Costs involved in running and developing this web app:
|
||||||
- <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/e1c4210f15537394cc764b8bc2dffe37.jpg" alt="" width="16" height="16" /> lucasofchirst (Occitan, Portuguese, Portuguese, Brazilian)
|
- <img src="https://crowdin-static.downloads.crowdin.com/avatar/16291756/medium/e1c4210f15537394cc764b8bc2dffe37.jpg" alt="" width="16" height="16" /> lucasofchirst (Occitan, Portuguese, Portuguese, Brazilian)
|
||||||
- <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/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)
|
||||||
- <img src="https://crowdin-static.downloads.crowdin.com/avatar/13613969/medium/c7834ddc0ada84a79671697a944bb274.png" alt="" width="16" height="16" /> moreal (Korean)
|
- <img src="https://crowdin-static.downloads.crowdin.com/avatar/13613969/medium/c7834ddc0ada84a79671697a944bb274.png" alt="" width="16" height="16" /> moreal (Korean)
|
||||||
- <img src="https://crowdin-static.downloads.crowdin.com/avatar/14158861/medium/ba1ff31dc5743b067ea6685f735229a5_default.png" alt="" width="16" height="16" /> MrWillCom (Chinese Simplified)
|
- <img src="https://crowdin-static.downloads.crowdin.com/avatar/14158861/medium/ba1ff31dc5743b067ea6685f735229a5_default.png" alt="" width="16" height="16" /> MrWillCom (Chinese Simplified)
|
||||||
|
@ -331,12 +335,12 @@ Costs involved in running and developing this web app:
|
||||||
- <img src="https://crowdin-static.downloads.crowdin.com/avatar/13422319/medium/66632a98d73d48e36753d94ebcec9d4f.png" alt="" width="16" height="16" /> rwmpelstilzchen (Esperanto, Hebrew)
|
- <img src="https://crowdin-static.downloads.crowdin.com/avatar/13422319/medium/66632a98d73d48e36753d94ebcec9d4f.png" alt="" width="16" height="16" /> rwmpelstilzchen (Esperanto, Hebrew)
|
||||||
- <img src="https://crowdin-static.downloads.crowdin.com/avatar/16538605/medium/bcdb6e3286b7d6237923f3a9383eed29.png" alt="" width="16" height="16" /> SadmL (Russian)
|
- <img src="https://crowdin-static.downloads.crowdin.com/avatar/16538605/medium/bcdb6e3286b7d6237923f3a9383eed29.png" alt="" width="16" height="16" /> SadmL (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/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/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/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/16533843/medium/7314c15492ef90118c33a80a427e6c87_default.png" alt="" width="16" height="16" /> Talos00 (Italian)
|
|
||||||
- <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/16527851/medium/649e5a9a8a8cc61ced670d89e9cca082.png" alt="" width="16" height="16" /> tux93 (German)
|
- <img src="https://crowdin-static.downloads.crowdin.com/avatar/16527851/medium/649e5a9a8a8cc61ced670d89e9cca082.png" alt="" width="16" height="16" /> tux93 (German)
|
||||||
- <img src="https://crowdin-static.downloads.crowdin.com/avatar/16529833/medium/2991a65722acd721849656223014cd49.png" alt="" width="16" height="16" /> Urbestro (Esperanto, Spanish)
|
|
||||||
- <img src="https://crowdin-static.downloads.crowdin.com/avatar/14427566/medium/ab733b5044c21867fc5a9d1b22cd2c03.png" alt="" width="16" height="16" /> Vac31. (Lithuanian)
|
- <img src="https://crowdin-static.downloads.crowdin.com/avatar/14427566/medium/ab733b5044c21867fc5a9d1b22cd2c03.png" alt="" width="16" height="16" /> Vac31. (Lithuanian)
|
||||||
- <img src="https://crowdin-static.downloads.crowdin.com/avatar/16026914/medium/e3ca187f354a298ef0c9d02a0ed17be7.jpg" alt="" width="16" height="16" /> valtlai (Finnish)
|
- <img src="https://crowdin-static.downloads.crowdin.com/avatar/16026914/medium/e3ca187f354a298ef0c9d02a0ed17be7.jpg" alt="" width="16" height="16" /> valtlai (Finnish)
|
||||||
- <img src="https://crowdin-static.downloads.crowdin.com/avatar/16563757/medium/af4556c13862d1fd593b51084a159b75_default.png" alt="" width="16" height="16" /> voyagercy (Chinese Traditional)
|
- <img src="https://crowdin-static.downloads.crowdin.com/avatar/16563757/medium/af4556c13862d1fd593b51084a159b75_default.png" alt="" width="16" height="16" /> voyagercy (Chinese Traditional)
|
||||||
|
|
116
package-lock.json
generated
116
package-lock.json
generated
|
@ -32,7 +32,7 @@
|
||||||
"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.23.2",
|
"preact": "~10.24.0",
|
||||||
"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",
|
||||||
|
@ -52,20 +52,21 @@
|
||||||
"@ianvs/prettier-plugin-sort-imports": "~4.3.1",
|
"@ianvs/prettier-plugin-sort-imports": "~4.3.1",
|
||||||
"@lingui/cli": "~4.11.4",
|
"@lingui/cli": "~4.11.4",
|
||||||
"@lingui/vite-plugin": "~4.11.4",
|
"@lingui/vite-plugin": "~4.11.4",
|
||||||
"@preact/preset-vite": "~2.9.0",
|
"@preact/preset-vite": "~2.9.1",
|
||||||
"babel-plugin-macros": "~3.1.0",
|
"babel-plugin-macros": "~3.1.0",
|
||||||
"postcss": "~8.4.45",
|
"postcss": "~8.4.47",
|
||||||
"postcss-dark-theme-class": "~1.3.0",
|
"postcss-dark-theme-class": "~1.3.0",
|
||||||
"postcss-preset-env": "~10.0.2",
|
"postcss-preset-env": "~10.0.3",
|
||||||
"twitter-text": "~3.1.0",
|
"twitter-text": "~3.1.0",
|
||||||
"vite": "~5.4.3",
|
"vite": "~5.4.7",
|
||||||
"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",
|
||||||
"vite-plugin-remove-console": "~2.2.0",
|
"vite-plugin-remove-console": "~2.2.0",
|
||||||
"vite-plugin-run": "~0.5.2",
|
"vite-plugin-run": "~0.6.0",
|
||||||
"workbox-cacheable-response": "~7.1.0",
|
"workbox-cacheable-response": "~7.1.0",
|
||||||
"workbox-expiration": "~7.1.0",
|
"workbox-expiration": "~7.1.0",
|
||||||
|
"workbox-navigation-preload": "~7.1.0",
|
||||||
"workbox-routing": "~7.1.0",
|
"workbox-routing": "~7.1.0",
|
||||||
"workbox-strategies": "~7.1.0"
|
"workbox-strategies": "~7.1.0"
|
||||||
}
|
}
|
||||||
|
@ -4265,9 +4266,9 @@
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@preact/preset-vite": {
|
"node_modules/@preact/preset-vite": {
|
||||||
"version": "2.9.0",
|
"version": "2.9.1",
|
||||||
"resolved": "https://registry.npmjs.org/@preact/preset-vite/-/preset-vite-2.9.0.tgz",
|
"resolved": "https://registry.npmjs.org/@preact/preset-vite/-/preset-vite-2.9.1.tgz",
|
||||||
"integrity": "sha512-B9yVT7AkR6owrt84K3pLNyaKSvlioKdw65VqE/zMiR6HMovPekpsrwBNs5DJhBFEd5cvLMtCjHNHZ9P7Oblveg==",
|
"integrity": "sha512-JecWzrOx7ogFhklSMhY+aH/24pajL0Vx+beEgau3WDMUUAo32cpUo/UqerPhLOyhCKXlxK9a3cRoa8g68ZAp5g==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@babel/code-frame": "^7.22.13",
|
"@babel/code-frame": "^7.22.13",
|
||||||
|
@ -4280,7 +4281,6 @@
|
||||||
"kolorist": "^1.8.0",
|
"kolorist": "^1.8.0",
|
||||||
"magic-string": "0.30.5",
|
"magic-string": "0.30.5",
|
||||||
"node-html-parser": "^6.1.10",
|
"node-html-parser": "^6.1.10",
|
||||||
"resolve": "^1.22.8",
|
|
||||||
"source-map": "^0.7.4",
|
"source-map": "^0.7.4",
|
||||||
"stack-trace": "^1.0.0-pre2"
|
"stack-trace": "^1.0.0-pre2"
|
||||||
},
|
},
|
||||||
|
@ -5709,9 +5709,9 @@
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/cssdb": {
|
"node_modules/cssdb": {
|
||||||
"version": "8.1.0",
|
"version": "8.1.1",
|
||||||
"resolved": "https://registry.npmjs.org/cssdb/-/cssdb-8.1.0.tgz",
|
"resolved": "https://registry.npmjs.org/cssdb/-/cssdb-8.1.1.tgz",
|
||||||
"integrity": "sha512-BQN57lfS4dYt2iL0LgyrlDbefZKEtUyrO8rbzrbGrqBk6OoyNTQLF+porY9DrpDBjLo4NEvj2IJttC7vf3x+Ew==",
|
"integrity": "sha512-kRbSRgZoxtZNl5snb3nOzBkFOt5AwnephcUTIEFc2DebKG9PN50/cHarlwOooTxYQ/gxsnKs3BxykhNLmfvyLg==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"funding": [
|
"funding": [
|
||||||
{
|
{
|
||||||
|
@ -5943,11 +5943,10 @@
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/ejs": {
|
"node_modules/ejs": {
|
||||||
"version": "3.1.9",
|
"version": "3.1.10",
|
||||||
"resolved": "https://registry.npmjs.org/ejs/-/ejs-3.1.9.tgz",
|
"resolved": "https://registry.npmjs.org/ejs/-/ejs-3.1.10.tgz",
|
||||||
"integrity": "sha512-rC+QVNMJWv+MtPgkt0y+0rVEIdbtxVADApW9JXrUVlzHetgcyczP/E7DJmWJ4fJCZF2cPcBk0laWO9ZHMG3DmQ==",
|
"integrity": "sha512-UeJmFfOrAQS8OJWPZ4qtgHyWExa088/MtK5UEyoJGFH67cDEXkZSviOiKRCZ4Xij0zxI3JECgYs3oKx+AizQBA==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "Apache-2.0",
|
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"jake": "^10.8.5"
|
"jake": "^10.8.5"
|
||||||
},
|
},
|
||||||
|
@ -7782,16 +7781,16 @@
|
||||||
"license": "MIT"
|
"license": "MIT"
|
||||||
},
|
},
|
||||||
"node_modules/micromatch": {
|
"node_modules/micromatch": {
|
||||||
"version": "4.0.2",
|
"version": "4.0.8",
|
||||||
"resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.2.tgz",
|
"resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.8.tgz",
|
||||||
"integrity": "sha512-y7FpHSbMUMoyPbYUSzO6PaZ6FyRnQOpHuKwbo1G+Knck95XVU4QAiKdGEnj5wwoS7PlOgthX/09u5iFJ+aYf5Q==",
|
"integrity": "sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"braces": "^3.0.1",
|
"braces": "^3.0.3",
|
||||||
"picomatch": "^2.0.5"
|
"picomatch": "^2.3.1"
|
||||||
},
|
},
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">=8"
|
"node": ">=8.6"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/mime-db": {
|
"node_modules/mime-db": {
|
||||||
|
@ -8363,9 +8362,9 @@
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/postcss": {
|
"node_modules/postcss": {
|
||||||
"version": "8.4.45",
|
"version": "8.4.47",
|
||||||
"resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.45.tgz",
|
"resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.47.tgz",
|
||||||
"integrity": "sha512-7KTLTdzdZZYscUc65XmjFiB73vBhBfbPztCYdUNvlaso9PrzjzcmjqBPR0lNGkcVlcO4BjiO5rK/qNz+XAen1Q==",
|
"integrity": "sha512-56rxCq7G/XfB4EkXq9Egn5GCqugWvDFjafDOThIdMBsI15iqPqR5r15TfSr1YPYeEI19YeaXMCbY6u88Y76GLQ==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"funding": [
|
"funding": [
|
||||||
{
|
{
|
||||||
|
@ -8383,8 +8382,8 @@
|
||||||
],
|
],
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"nanoid": "^3.3.7",
|
"nanoid": "^3.3.7",
|
||||||
"picocolors": "^1.0.1",
|
"picocolors": "^1.1.0",
|
||||||
"source-map-js": "^1.2.0"
|
"source-map-js": "^1.2.1"
|
||||||
},
|
},
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": "^10 || ^12 || >=14"
|
"node": "^10 || ^12 || >=14"
|
||||||
|
@ -8864,9 +8863,9 @@
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/postcss-opacity-percentage": {
|
"node_modules/postcss-opacity-percentage": {
|
||||||
"version": "2.0.0",
|
"version": "3.0.0",
|
||||||
"resolved": "https://registry.npmjs.org/postcss-opacity-percentage/-/postcss-opacity-percentage-2.0.0.tgz",
|
"resolved": "https://registry.npmjs.org/postcss-opacity-percentage/-/postcss-opacity-percentage-3.0.0.tgz",
|
||||||
"integrity": "sha512-lyDrCOtntq5Y1JZpBFzIWm2wG9kbEdujpNt4NLannF+J9c8CgFIzPa80YQfdza+Y+yFfzbYj/rfoOsYsooUWTQ==",
|
"integrity": "sha512-K6HGVzyxUxd/VgZdX04DCtdwWJ4NGLG212US4/LA1TLAbHgmAsTWVR86o+gGIbFtnTkfOpb9sCRBx8K7HO66qQ==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"funding": [
|
"funding": [
|
||||||
{
|
{
|
||||||
|
@ -8878,12 +8877,11 @@
|
||||||
"url": "https://liberapay.com/mrcgrtz"
|
"url": "https://liberapay.com/mrcgrtz"
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"license": "MIT",
|
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": "^14 || ^16 || >=18"
|
"node": ">=18"
|
||||||
},
|
},
|
||||||
"peerDependencies": {
|
"peerDependencies": {
|
||||||
"postcss": "^8.2"
|
"postcss": "^8.4"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/postcss-overflow-shorthand": {
|
"node_modules/postcss-overflow-shorthand": {
|
||||||
|
@ -8947,9 +8945,9 @@
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/postcss-preset-env": {
|
"node_modules/postcss-preset-env": {
|
||||||
"version": "10.0.2",
|
"version": "10.0.3",
|
||||||
"resolved": "https://registry.npmjs.org/postcss-preset-env/-/postcss-preset-env-10.0.2.tgz",
|
"resolved": "https://registry.npmjs.org/postcss-preset-env/-/postcss-preset-env-10.0.3.tgz",
|
||||||
"integrity": "sha512-PMxqnz0RQYMUmUi6p4P7BhC9EVGyEUCIdwn4vJ7Fy1jvc2QP4mMH75BSBB1mBFqjl3x4xYwyCNMhGZ8y0+/qOA==",
|
"integrity": "sha512-1nrZ4IeBXEEj53IMoRKE+k/Ub6nQb3gFjaxTeyUNG5zv3JQclFDY5GKKhAi3nsa1lnPMWgzQX+/1y6wUt2+I7Q==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"funding": [
|
"funding": [
|
||||||
{
|
{
|
||||||
|
@ -8997,7 +8995,7 @@
|
||||||
"css-blank-pseudo": "^7.0.0",
|
"css-blank-pseudo": "^7.0.0",
|
||||||
"css-has-pseudo": "^7.0.0",
|
"css-has-pseudo": "^7.0.0",
|
||||||
"css-prefers-color-scheme": "^10.0.0",
|
"css-prefers-color-scheme": "^10.0.0",
|
||||||
"cssdb": "^8.1.0",
|
"cssdb": "^8.1.1",
|
||||||
"postcss-attribute-case-insensitive": "^7.0.0",
|
"postcss-attribute-case-insensitive": "^7.0.0",
|
||||||
"postcss-clamp": "^4.1.0",
|
"postcss-clamp": "^4.1.0",
|
||||||
"postcss-color-functional-notation": "^7.0.2",
|
"postcss-color-functional-notation": "^7.0.2",
|
||||||
|
@ -9016,7 +9014,7 @@
|
||||||
"postcss-lab-function": "^7.0.2",
|
"postcss-lab-function": "^7.0.2",
|
||||||
"postcss-logical": "^8.0.0",
|
"postcss-logical": "^8.0.0",
|
||||||
"postcss-nesting": "^13.0.0",
|
"postcss-nesting": "^13.0.0",
|
||||||
"postcss-opacity-percentage": "^2.0.0",
|
"postcss-opacity-percentage": "^3.0.0",
|
||||||
"postcss-overflow-shorthand": "^6.0.0",
|
"postcss-overflow-shorthand": "^6.0.0",
|
||||||
"postcss-page-break": "^3.0.4",
|
"postcss-page-break": "^3.0.4",
|
||||||
"postcss-place": "^10.0.0",
|
"postcss-place": "^10.0.0",
|
||||||
|
@ -9112,9 +9110,9 @@
|
||||||
"license": "MIT"
|
"license": "MIT"
|
||||||
},
|
},
|
||||||
"node_modules/preact": {
|
"node_modules/preact": {
|
||||||
"version": "10.23.2",
|
"version": "10.24.0",
|
||||||
"resolved": "https://registry.npmjs.org/preact/-/preact-10.23.2.tgz",
|
"resolved": "https://registry.npmjs.org/preact/-/preact-10.24.0.tgz",
|
||||||
"integrity": "sha512-kKYfePf9rzKnxOAKDpsWhg/ysrHPqT+yQ7UW4JjdnqjFIeNUnNcEJvhuA8fDenxAGWzUqtd51DfVg7xp/8T9NA==",
|
"integrity": "sha512-aK8Cf+jkfyuZ0ZZRG9FbYqwmEiGQ4y/PUO4SuTWoyWL244nZZh7bd5h2APd4rSNDYTBNghg1L+5iJN3Skxtbsw==",
|
||||||
"funding": {
|
"funding": {
|
||||||
"type": "opencollective",
|
"type": "opencollective",
|
||||||
"url": "https://opencollective.com/preact"
|
"url": "https://opencollective.com/preact"
|
||||||
|
@ -9793,9 +9791,9 @@
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/source-map-js": {
|
"node_modules/source-map-js": {
|
||||||
"version": "1.2.0",
|
"version": "1.2.1",
|
||||||
"resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.0.tgz",
|
"resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.1.tgz",
|
||||||
"integrity": "sha512-itJW8lvSA0TXEphiRoawsCksnlf8SyvmFzIhltqAHluXd88pkCd+cXJVHTDwdCr0IzwptSm035IHQktUu1QUMg==",
|
"integrity": "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">=0.10.0"
|
"node": ">=0.10.0"
|
||||||
|
@ -10538,9 +10536,9 @@
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/vite": {
|
"node_modules/vite": {
|
||||||
"version": "5.4.3",
|
"version": "5.4.7",
|
||||||
"resolved": "https://registry.npmjs.org/vite/-/vite-5.4.3.tgz",
|
"resolved": "https://registry.npmjs.org/vite/-/vite-5.4.7.tgz",
|
||||||
"integrity": "sha512-IH+nl64eq9lJjFqU+/yrRnrHPVTlgy42/+IzbOdaFDVlyLgI/wDlf+FCobXLX1cT0X5+7LMyH1mIy2xJdLfo8Q==",
|
"integrity": "sha512-5l2zxqMEPVENgvzTuBpHer2awaetimj2BGkhBPdnwKbPNOlHsODU+oiazEZzLK7KhAnOrO+XGYJYn4ZlUhDtDQ==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"esbuild": "^0.21.3",
|
"esbuild": "^0.21.3",
|
||||||
|
@ -10657,16 +10655,16 @@
|
||||||
"dev": true
|
"dev": true
|
||||||
},
|
},
|
||||||
"node_modules/vite-plugin-run": {
|
"node_modules/vite-plugin-run": {
|
||||||
"version": "0.5.2",
|
"version": "0.6.0",
|
||||||
"resolved": "https://registry.npmjs.org/vite-plugin-run/-/vite-plugin-run-0.5.2.tgz",
|
"resolved": "https://registry.npmjs.org/vite-plugin-run/-/vite-plugin-run-0.6.0.tgz",
|
||||||
"integrity": "sha512-ZrbdZ2gNJwvW4MMQr6a4Udioq6+06VgBytviYi/hgRQnz3SCQAcRJu7QKqlIbH229/lNyYTdvkglottfkUlNyQ==",
|
"integrity": "sha512-B5iHHz6MjXodmTxZPlEQAOJQAzi47wCqVqSDYo71A7b8MzS+MklwmZ384lb4xUy71PPTEZAxjNs0bIDqL4ly8g==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@antfu/utils": "^0.7.6",
|
"@antfu/utils": "^0.7.10",
|
||||||
"debug": "^4.3.4",
|
"debug": "^4.3.7",
|
||||||
"execa": "5.1",
|
"execa": "5.1.1",
|
||||||
"minimatch": "^9.0.3",
|
"minimatch": "^9.0.5",
|
||||||
"picocolors": "^1.0.0"
|
"picocolors": "^1.1.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/vite-plugin-run/node_modules/brace-expansion": {
|
"node_modules/vite-plugin-run/node_modules/brace-expansion": {
|
||||||
|
@ -11071,9 +11069,9 @@
|
||||||
"dev": true
|
"dev": true
|
||||||
},
|
},
|
||||||
"node_modules/ws": {
|
"node_modules/ws": {
|
||||||
"version": "8.17.0",
|
"version": "8.18.0",
|
||||||
"resolved": "https://registry.npmjs.org/ws/-/ws-8.17.0.tgz",
|
"resolved": "https://registry.npmjs.org/ws/-/ws-8.18.0.tgz",
|
||||||
"integrity": "sha512-uJq6108EgZMAl20KagGkzCKfMEjxmKvZHG7Tlq0Z6nOky7YF7aq4mOx6xK8TJ/i1LeK4Qus7INktacctDgY8Ow==",
|
"integrity": "sha512-8VbfWfHLbbwu3+N6OKsOMpBdT4kXPDDB9cJk2bJ6mh9ucxdlnNvH1e+roYkKmN9Nxw2yjz7VzeO9oOz2zJ04Pw==",
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">=10.0.0"
|
"node": ">=10.0.0"
|
||||||
},
|
},
|
||||||
|
|
13
package.json
13
package.json
|
@ -40,7 +40,7 @@
|
||||||
"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.23.2",
|
"preact": "~10.24.0",
|
||||||
"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",
|
||||||
|
@ -60,20 +60,21 @@
|
||||||
"@ianvs/prettier-plugin-sort-imports": "~4.3.1",
|
"@ianvs/prettier-plugin-sort-imports": "~4.3.1",
|
||||||
"@lingui/cli": "~4.11.4",
|
"@lingui/cli": "~4.11.4",
|
||||||
"@lingui/vite-plugin": "~4.11.4",
|
"@lingui/vite-plugin": "~4.11.4",
|
||||||
"@preact/preset-vite": "~2.9.0",
|
"@preact/preset-vite": "~2.9.1",
|
||||||
"babel-plugin-macros": "~3.1.0",
|
"babel-plugin-macros": "~3.1.0",
|
||||||
"postcss": "~8.4.45",
|
"postcss": "~8.4.47",
|
||||||
"postcss-dark-theme-class": "~1.3.0",
|
"postcss-dark-theme-class": "~1.3.0",
|
||||||
"postcss-preset-env": "~10.0.2",
|
"postcss-preset-env": "~10.0.3",
|
||||||
"twitter-text": "~3.1.0",
|
"twitter-text": "~3.1.0",
|
||||||
"vite": "~5.4.3",
|
"vite": "~5.4.7",
|
||||||
"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",
|
||||||
"vite-plugin-remove-console": "~2.2.0",
|
"vite-plugin-remove-console": "~2.2.0",
|
||||||
"vite-plugin-run": "~0.5.2",
|
"vite-plugin-run": "~0.6.0",
|
||||||
"workbox-cacheable-response": "~7.1.0",
|
"workbox-cacheable-response": "~7.1.0",
|
||||||
"workbox-expiration": "~7.1.0",
|
"workbox-expiration": "~7.1.0",
|
||||||
|
"workbox-navigation-preload": "~7.1.0",
|
||||||
"workbox-routing": "~7.1.0",
|
"workbox-routing": "~7.1.0",
|
||||||
"workbox-strategies": "~7.1.0"
|
"workbox-strategies": "~7.1.0"
|
||||||
},
|
},
|
||||||
|
|
65
public/sw.js
65
public/sw.js
|
@ -1,5 +1,6 @@
|
||||||
import { CacheableResponsePlugin } from 'workbox-cacheable-response';
|
import { CacheableResponsePlugin } from 'workbox-cacheable-response';
|
||||||
import { ExpirationPlugin } from 'workbox-expiration';
|
import { ExpirationPlugin } from 'workbox-expiration';
|
||||||
|
import * as navigationPreload from 'workbox-navigation-preload';
|
||||||
import { RegExpRoute, registerRoute, Route } from 'workbox-routing';
|
import { RegExpRoute, registerRoute, Route } from 'workbox-routing';
|
||||||
import {
|
import {
|
||||||
CacheFirst,
|
CacheFirst,
|
||||||
|
@ -7,19 +8,48 @@ import {
|
||||||
StaleWhileRevalidate,
|
StaleWhileRevalidate,
|
||||||
} from 'workbox-strategies';
|
} from 'workbox-strategies';
|
||||||
|
|
||||||
|
navigationPreload.enable();
|
||||||
|
|
||||||
self.__WB_DISABLE_DEV_LOGS = true;
|
self.__WB_DISABLE_DEV_LOGS = true;
|
||||||
|
|
||||||
|
const iconsRoute = new Route(
|
||||||
|
({ request, sameOrigin }) => {
|
||||||
|
const isIcon = request.url.includes('/icons/');
|
||||||
|
return sameOrigin && isIcon;
|
||||||
|
},
|
||||||
|
new CacheFirst({
|
||||||
|
cacheName: 'icons',
|
||||||
|
plugins: [
|
||||||
|
new ExpirationPlugin({
|
||||||
|
// Weirdly high maxEntries number, due to some old icons suddenly disappearing and not rendering
|
||||||
|
// NOTE: Temporary fix
|
||||||
|
maxEntries: 300,
|
||||||
|
maxAgeSeconds: 3 * 24 * 60 * 60, // 3 days
|
||||||
|
purgeOnQuotaError: true,
|
||||||
|
}),
|
||||||
|
new CacheableResponsePlugin({
|
||||||
|
statuses: [0, 200],
|
||||||
|
}),
|
||||||
|
],
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
registerRoute(iconsRoute);
|
||||||
|
|
||||||
const assetsRoute = new Route(
|
const assetsRoute = new Route(
|
||||||
({ request, sameOrigin }) => {
|
({ request, sameOrigin }) => {
|
||||||
const isAsset =
|
const isAsset =
|
||||||
request.destination === 'style' || request.destination === 'script';
|
request.destination === 'style' || request.destination === 'script';
|
||||||
const hasHash = /-[0-9a-f]{4,}\./i.test(request.url);
|
const hasHash = /-[0-9a-z-]{4,}\./i.test(request.url);
|
||||||
return sameOrigin && isAsset && hasHash;
|
return sameOrigin && isAsset && hasHash;
|
||||||
},
|
},
|
||||||
new NetworkFirst({
|
new NetworkFirst({
|
||||||
cacheName: 'assets',
|
cacheName: 'assets',
|
||||||
networkTimeoutSeconds: 5,
|
networkTimeoutSeconds: 5,
|
||||||
plugins: [
|
plugins: [
|
||||||
|
new ExpirationPlugin({
|
||||||
|
maxEntries: 30,
|
||||||
|
purgeOnQuotaError: true,
|
||||||
|
}),
|
||||||
new CacheableResponsePlugin({
|
new CacheableResponsePlugin({
|
||||||
statuses: [0, 200],
|
statuses: [0, 200],
|
||||||
}),
|
}),
|
||||||
|
@ -41,8 +71,7 @@ const imageRoute = new Route(
|
||||||
cacheName: 'remote-images',
|
cacheName: 'remote-images',
|
||||||
plugins: [
|
plugins: [
|
||||||
new ExpirationPlugin({
|
new ExpirationPlugin({
|
||||||
maxEntries: 50,
|
maxEntries: 30,
|
||||||
maxAgeSeconds: 3 * 24 * 60 * 60, // 3 days
|
|
||||||
purgeOnQuotaError: true,
|
purgeOnQuotaError: true,
|
||||||
}),
|
}),
|
||||||
new CacheableResponsePlugin({
|
new CacheableResponsePlugin({
|
||||||
|
@ -53,40 +82,18 @@ const imageRoute = new Route(
|
||||||
);
|
);
|
||||||
registerRoute(imageRoute);
|
registerRoute(imageRoute);
|
||||||
|
|
||||||
const iconsRoute = new Route(
|
|
||||||
({ request, sameOrigin }) => {
|
|
||||||
const isIcon = request.url.includes('/icons/');
|
|
||||||
return sameOrigin && isIcon;
|
|
||||||
},
|
|
||||||
new CacheFirst({
|
|
||||||
cacheName: 'icons',
|
|
||||||
plugins: [
|
|
||||||
new ExpirationPlugin({
|
|
||||||
maxEntries: 300,
|
|
||||||
maxAgeSeconds: 3 * 24 * 60 * 60, // 3 days
|
|
||||||
purgeOnQuotaError: true,
|
|
||||||
}),
|
|
||||||
new CacheableResponsePlugin({
|
|
||||||
statuses: [0, 200],
|
|
||||||
}),
|
|
||||||
],
|
|
||||||
}),
|
|
||||||
);
|
|
||||||
registerRoute(iconsRoute);
|
|
||||||
|
|
||||||
// 1-day cache for
|
// 1-day cache for
|
||||||
// - /api/v1/instance
|
|
||||||
// - /api/v1/custom_emojis
|
// - /api/v1/custom_emojis
|
||||||
// - /api/v1/preferences
|
|
||||||
// - /api/v1/lists/:id
|
// - /api/v1/lists/:id
|
||||||
// - /api/v1/announcements
|
// - /api/v1/announcements
|
||||||
const apiExtendedRoute = new RegExpRoute(
|
const apiExtendedRoute = new RegExpRoute(
|
||||||
/^https?:\/\/[^\/]+\/api\/v\d+\/(instance|custom_emojis|preferences|lists\/\d+|announcements)$/,
|
/^https?:\/\/[^\/]+\/api\/v\d+\/(custom_emojis|lists\/\d+|announcements)$/,
|
||||||
new StaleWhileRevalidate({
|
new StaleWhileRevalidate({
|
||||||
cacheName: 'api-extended',
|
cacheName: 'api-extended',
|
||||||
plugins: [
|
plugins: [
|
||||||
new ExpirationPlugin({
|
new ExpirationPlugin({
|
||||||
maxAgeSeconds: 24 * 60 * 60, // 1 day
|
maxAgeSeconds: 12 * 60 * 60, // 12 hours
|
||||||
|
purgeOnQuotaError: true,
|
||||||
}),
|
}),
|
||||||
new CacheableResponsePlugin({
|
new CacheableResponsePlugin({
|
||||||
statuses: [0, 200],
|
statuses: [0, 200],
|
||||||
|
@ -127,7 +134,9 @@ const apiRoute = new RegExpRoute(
|
||||||
networkTimeoutSeconds: 5,
|
networkTimeoutSeconds: 5,
|
||||||
plugins: [
|
plugins: [
|
||||||
new ExpirationPlugin({
|
new ExpirationPlugin({
|
||||||
|
maxEntries: 30,
|
||||||
maxAgeSeconds: 5 * 60, // 5 minutes
|
maxAgeSeconds: 5 * 60, // 5 minutes
|
||||||
|
purgeOnQuotaError: true,
|
||||||
}),
|
}),
|
||||||
new CacheableResponsePlugin({
|
new CacheableResponsePlugin({
|
||||||
statuses: [0, 200],
|
statuses: [0, 200],
|
||||||
|
|
29
src/app.css
29
src/app.css
|
@ -367,10 +367,14 @@ a[href^='http'][rel*='nofollow']:visited:not(:has(div)) {
|
||||||
border-bottom: var(--hairline-width) solid var(--divider-color);
|
border-bottom: var(--hairline-width) solid var(--divider-color);
|
||||||
--line-dir: var(--to-forward);
|
--line-dir: var(--to-forward);
|
||||||
}
|
}
|
||||||
.timeline:not(.contextual) > li + li {
|
.timeline > li + li:not(.timeline-item-carousel, .hero, .ancestor) {
|
||||||
content-visibility: auto;
|
content-visibility: auto;
|
||||||
contain-intrinsic-size: auto 160px;
|
contain-intrinsic-size: auto 160px;
|
||||||
}
|
}
|
||||||
|
.timeline.contextual > li:is(:hover, :focus-visible) {
|
||||||
|
/* Needed to undo the overflow: hidden "effect" due to "content-visibility: auto" */
|
||||||
|
content-visibility: visible !important;
|
||||||
|
}
|
||||||
.timeline.flat > li {
|
.timeline.flat > li {
|
||||||
border-bottom: none;
|
border-bottom: none;
|
||||||
}
|
}
|
||||||
|
@ -1634,6 +1638,7 @@ body:has(.media-modal-container + .status-deck) .media-post-link {
|
||||||
/* SHEET */
|
/* SHEET */
|
||||||
|
|
||||||
.sheet {
|
.sheet {
|
||||||
|
timeline-scope: --sheet-scroll;
|
||||||
align-self: flex-end;
|
align-self: flex-end;
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
|
@ -1708,6 +1713,27 @@ body:has(.media-modal-container + .status-deck) .media-post-link {
|
||||||
padding-right: max(16px, env(safe-area-inset-right));
|
padding-right: max(16px, env(safe-area-inset-right));
|
||||||
user-select: none;
|
user-select: none;
|
||||||
}
|
}
|
||||||
|
@keyframes header-border {
|
||||||
|
0% {
|
||||||
|
box-shadow: none;
|
||||||
|
}
|
||||||
|
100% {
|
||||||
|
box-shadow: 0 0 0 1px var(--outline-color),
|
||||||
|
0 8px 16px -8px var(--drop-shadow-color);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@supports (animation-timeline: scroll()) {
|
||||||
|
.sheet header {
|
||||||
|
animation: header-border 1s linear both;
|
||||||
|
animation-timeline: --sheet-scroll;
|
||||||
|
animation-range: 0 8px;
|
||||||
|
position: relative;
|
||||||
|
z-index: 1;
|
||||||
|
}
|
||||||
|
.sheet header + main {
|
||||||
|
mask-image: none !important;
|
||||||
|
}
|
||||||
|
}
|
||||||
.sheet .sheet-close:not(.outer) + header {
|
.sheet .sheet-close:not(.outer) + header {
|
||||||
padding-right: max(44px, env(safe-area-inset-right));
|
padding-right: max(44px, env(safe-area-inset-right));
|
||||||
|
|
||||||
|
@ -1726,6 +1752,7 @@ body:has(.media-modal-container + .status-deck) .media-post-link {
|
||||||
align-items: center;
|
align-items: center;
|
||||||
}
|
}
|
||||||
.sheet main {
|
.sheet main {
|
||||||
|
scroll-timeline: --sheet-scroll;
|
||||||
overflow: auto;
|
overflow: auto;
|
||||||
overflow-x: hidden;
|
overflow-x: hidden;
|
||||||
overscroll-behavior: contain;
|
overscroll-behavior: contain;
|
||||||
|
|
54
src/app.jsx
54
src/app.jsx
|
@ -2,6 +2,7 @@ import './app.css';
|
||||||
|
|
||||||
import { useLingui } from '@lingui/react';
|
import { useLingui } from '@lingui/react';
|
||||||
import debounce from 'just-debounce-it';
|
import debounce from 'just-debounce-it';
|
||||||
|
import { memo } from 'preact/compat';
|
||||||
import {
|
import {
|
||||||
useEffect,
|
useEffect,
|
||||||
useLayoutEffect,
|
useLayoutEffect,
|
||||||
|
@ -47,6 +48,8 @@ import Trending from './pages/trending';
|
||||||
import Welcome from './pages/welcome';
|
import Welcome from './pages/welcome';
|
||||||
import {
|
import {
|
||||||
api,
|
api,
|
||||||
|
hasInstance,
|
||||||
|
hasPreferences,
|
||||||
initAccount,
|
initAccount,
|
||||||
initClient,
|
initClient,
|
||||||
initInstance,
|
initInstance,
|
||||||
|
@ -327,7 +330,9 @@ function App() {
|
||||||
const [isLoggedIn, setIsLoggedIn] = useState(false);
|
const [isLoggedIn, setIsLoggedIn] = useState(false);
|
||||||
const [uiState, setUIState] = useState('loading');
|
const [uiState, setUIState] = useState('loading');
|
||||||
__BENCHMARK.start('app-init');
|
__BENCHMARK.start('app-init');
|
||||||
|
__BENCHMARK.start('time-to-following');
|
||||||
__BENCHMARK.start('time-to-home');
|
__BENCHMARK.start('time-to-home');
|
||||||
|
__BENCHMARK.start('time-to-isLoggedIn');
|
||||||
useLingui();
|
useLingui();
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
|
@ -407,19 +412,28 @@ function App() {
|
||||||
setUIState('loading');
|
setUIState('loading');
|
||||||
(async () => {
|
(async () => {
|
||||||
try {
|
try {
|
||||||
await initPreferences(client);
|
if (hasPreferences() && hasInstance(instance)) {
|
||||||
await initInstance(client, instance);
|
// Non-blocking
|
||||||
|
initPreferences(client);
|
||||||
|
initInstance(client, instance);
|
||||||
|
} else {
|
||||||
|
await Promise.allSettled([
|
||||||
|
initPreferences(client),
|
||||||
|
initInstance(client, instance),
|
||||||
|
]);
|
||||||
|
}
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
} finally {
|
} finally {
|
||||||
setIsLoggedIn(true);
|
setIsLoggedIn(true);
|
||||||
setUIState('default');
|
setUIState('default');
|
||||||
|
__BENCHMARK.end('app-init');
|
||||||
}
|
}
|
||||||
})();
|
})();
|
||||||
} else {
|
} else {
|
||||||
setUIState('default');
|
setUIState('default');
|
||||||
}
|
|
||||||
__BENCHMARK.end('app-init');
|
__BENCHMARK.end('app-init');
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Cleanup
|
// Cleanup
|
||||||
store.sessionCookie.del('clientID');
|
store.sessionCookie.del('clientID');
|
||||||
|
@ -439,27 +453,36 @@ function App() {
|
||||||
return <HttpRoute />;
|
return <HttpRoute />;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (uiState === 'loading') {
|
||||||
|
return <Loader id="loader-root" />;
|
||||||
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<PrimaryRoutes isLoggedIn={isLoggedIn} loading={uiState === 'loading'} />
|
<PrimaryRoutes isLoggedIn={isLoggedIn} />
|
||||||
<SecondaryRoutes isLoggedIn={isLoggedIn} />
|
<SecondaryRoutes isLoggedIn={isLoggedIn} />
|
||||||
{uiState === 'default' && (
|
|
||||||
<Routes>
|
<Routes>
|
||||||
<Route path="/:instance?/s/:id" element={<StatusRoute />} />
|
<Route path="/:instance?/s/:id" element={<StatusRoute />} />
|
||||||
</Routes>
|
</Routes>
|
||||||
)}
|
|
||||||
{isLoggedIn && <ComposeButton />}
|
{isLoggedIn && <ComposeButton />}
|
||||||
{isLoggedIn && <Shortcuts />}
|
{isLoggedIn && <Shortcuts />}
|
||||||
<Modals />
|
<Modals />
|
||||||
{isLoggedIn && <NotificationService />}
|
{isLoggedIn && <NotificationService />}
|
||||||
<BackgroundService isLoggedIn={isLoggedIn} />
|
<BackgroundService isLoggedIn={isLoggedIn} />
|
||||||
{uiState !== 'loading' && <SearchCommand onClose={focusDeck} />}
|
<SearchCommand onClose={focusDeck} />
|
||||||
<KeyboardShortcutsHelp />
|
<KeyboardShortcutsHelp />
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
function PrimaryRoutes({ isLoggedIn, loading }) {
|
function Root({ isLoggedIn }) {
|
||||||
|
if (isLoggedIn) {
|
||||||
|
__BENCHMARK.end('time-to-isLoggedIn');
|
||||||
|
}
|
||||||
|
return isLoggedIn ? <Home /> : <Welcome />;
|
||||||
|
}
|
||||||
|
|
||||||
|
const PrimaryRoutes = memo(({ isLoggedIn }) => {
|
||||||
const location = useLocation();
|
const location = useLocation();
|
||||||
const nonRootLocation = useMemo(() => {
|
const nonRootLocation = useMemo(() => {
|
||||||
const { pathname } = location;
|
const { pathname } = location;
|
||||||
|
@ -468,23 +491,12 @@ function PrimaryRoutes({ isLoggedIn, loading }) {
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Routes location={nonRootLocation || location}>
|
<Routes location={nonRootLocation || location}>
|
||||||
<Route
|
<Route path="/" element={<Root isLoggedIn={isLoggedIn} />} />
|
||||||
path="/"
|
|
||||||
element={
|
|
||||||
isLoggedIn ? (
|
|
||||||
<Home />
|
|
||||||
) : loading ? (
|
|
||||||
<Loader id="loader-root" />
|
|
||||||
) : (
|
|
||||||
<Welcome />
|
|
||||||
)
|
|
||||||
}
|
|
||||||
/>
|
|
||||||
<Route path="/login" element={<Login />} />
|
<Route path="/login" element={<Login />} />
|
||||||
<Route path="/welcome" element={<Welcome />} />
|
<Route path="/welcome" element={<Welcome />} />
|
||||||
</Routes>
|
</Routes>
|
||||||
);
|
);
|
||||||
}
|
});
|
||||||
|
|
||||||
function getPrevLocation() {
|
function getPrevLocation() {
|
||||||
return states.prevLocation || null;
|
return states.prevLocation || null;
|
||||||
|
|
|
@ -1488,6 +1488,9 @@ function RelatedActions({
|
||||||
</span>
|
</span>
|
||||||
</>
|
</>
|
||||||
}
|
}
|
||||||
|
itemProps={{
|
||||||
|
className: 'danger',
|
||||||
|
}}
|
||||||
menuItemClassName="danger"
|
menuItemClassName="danger"
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
// if (!blocking && !confirm(`Block @${username}?`)) {
|
// if (!blocking && !confirm(`Block @${username}?`)) {
|
||||||
|
|
|
@ -721,6 +721,11 @@
|
||||||
}
|
}
|
||||||
|
|
||||||
.custom-emojis-list {
|
.custom-emojis-list {
|
||||||
|
.section-container {
|
||||||
|
position: relative;
|
||||||
|
content-visibility: auto;
|
||||||
|
content-intrinsic-size: auto 88px;
|
||||||
|
}
|
||||||
.section-header {
|
.section-header {
|
||||||
font-size: 80%;
|
font-size: 80%;
|
||||||
text-transform: uppercase;
|
text-transform: uppercase;
|
||||||
|
@ -730,6 +735,10 @@
|
||||||
top: 0;
|
top: 0;
|
||||||
background-color: var(--bg-color);
|
background-color: var(--bg-color);
|
||||||
z-index: 1;
|
z-index: 1;
|
||||||
|
display: inline-block;
|
||||||
|
padding-inline-end: 8px;
|
||||||
|
pointer-events: none;
|
||||||
|
border-end-end-radius: 8px;
|
||||||
}
|
}
|
||||||
section {
|
section {
|
||||||
display: flex;
|
display: flex;
|
||||||
|
|
|
@ -16,7 +16,7 @@ import {
|
||||||
} from 'preact/hooks';
|
} from 'preact/hooks';
|
||||||
import { useHotkeys } from 'react-hotkeys-hook';
|
import { useHotkeys } from 'react-hotkeys-hook';
|
||||||
import stringLength from 'string-length';
|
import stringLength from 'string-length';
|
||||||
import { detectAll } from 'tinyld/light';
|
// import { detectAll } from 'tinyld/light';
|
||||||
import { uid } from 'uid/single';
|
import { uid } from 'uid/single';
|
||||||
import { useDebouncedCallback, useThrottledCallback } from 'use-debounce';
|
import { useDebouncedCallback, useThrottledCallback } from 'use-debounce';
|
||||||
import { useSnapshot } from 'valtio';
|
import { useSnapshot } from 'valtio';
|
||||||
|
@ -1185,11 +1185,12 @@ function Compose({
|
||||||
<option value="public">
|
<option value="public">
|
||||||
<Trans>Public</Trans>
|
<Trans>Public</Trans>
|
||||||
</option>
|
</option>
|
||||||
{(supports('@pleroma/local-visibility-post') || supports('@akkoma/local-visibility-post')) &&
|
{(supports('@pleroma/local-visibility-post') ||
|
||||||
|
supports('@akkoma/local-visibility-post')) && (
|
||||||
<option value="local">
|
<option value="local">
|
||||||
<Trans>Local</Trans>
|
<Trans>Local</Trans>
|
||||||
</option>
|
</option>
|
||||||
}
|
)}
|
||||||
<option value="unlisted">
|
<option value="unlisted">
|
||||||
<Trans>Unlisted</Trans>
|
<Trans>Unlisted</Trans>
|
||||||
</option>
|
</option>
|
||||||
|
@ -1260,7 +1261,10 @@ function Compose({
|
||||||
onDescriptionChange={(value) => {
|
onDescriptionChange={(value) => {
|
||||||
setMediaAttachments((attachments) => {
|
setMediaAttachments((attachments) => {
|
||||||
const newAttachments = [...attachments];
|
const newAttachments = [...attachments];
|
||||||
newAttachments[i].description = value;
|
newAttachments[i] = {
|
||||||
|
...newAttachments[i],
|
||||||
|
description: value,
|
||||||
|
};
|
||||||
return newAttachments;
|
return newAttachments;
|
||||||
});
|
});
|
||||||
}}
|
}}
|
||||||
|
@ -1686,7 +1690,8 @@ const getCustomEmojis = pmem(_getCustomEmojis, {
|
||||||
maxAge: 30 * 60 * 1000, // 30 minutes
|
maxAge: 30 * 60 * 1000, // 30 minutes
|
||||||
});
|
});
|
||||||
|
|
||||||
const detectLangs = (text) => {
|
const detectLangs = async (text) => {
|
||||||
|
const { detectAll } = await import('tinyld/light');
|
||||||
const langs = detectAll(text);
|
const langs = detectAll(text);
|
||||||
if (langs?.length) {
|
if (langs?.length) {
|
||||||
// return max 2
|
// return max 2
|
||||||
|
@ -1976,13 +1981,15 @@ const Textarea = forwardRef((props, ref) => {
|
||||||
});
|
});
|
||||||
const text = dom.innerText?.trim();
|
const text = dom.innerText?.trim();
|
||||||
if (!text) return;
|
if (!text) return;
|
||||||
const langs = detectLangs(text);
|
(async () => {
|
||||||
|
const langs = await detectLangs(text);
|
||||||
if (langs?.length) {
|
if (langs?.length) {
|
||||||
onTrigger?.({
|
onTrigger?.({
|
||||||
name: 'auto-detect-language',
|
name: 'auto-detect-language',
|
||||||
languages: langs,
|
languages: langs,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
})();
|
||||||
}, 2000);
|
}, 2000);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
@ -3171,7 +3178,7 @@ function CustomEmojisModal({
|
||||||
Object.entries(customEmojisCatList).map(
|
Object.entries(customEmojisCatList).map(
|
||||||
([category, emojis]) =>
|
([category, emojis]) =>
|
||||||
!!emojis?.length && (
|
!!emojis?.length && (
|
||||||
<>
|
<div class="section-container">
|
||||||
<div class="section-header">
|
<div class="section-header">
|
||||||
{{
|
{{
|
||||||
'--recent--': t`Recently used`,
|
'--recent--': t`Recently used`,
|
||||||
|
@ -3182,7 +3189,7 @@ function CustomEmojisModal({
|
||||||
emojis={emojis}
|
emojis={emojis}
|
||||||
onSelect={onSelectEmoji}
|
onSelect={onSelectEmoji}
|
||||||
/>
|
/>
|
||||||
</>
|
</div>
|
||||||
),
|
),
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
|
|
|
@ -60,10 +60,10 @@
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
a {
|
a.link-block {
|
||||||
min-width: 240px;
|
width: 240px;
|
||||||
flex-grow: 1;
|
flex-shrink: 0;
|
||||||
max-width: 320px;
|
/* max-width: 320px; */
|
||||||
text-decoration: none;
|
text-decoration: none;
|
||||||
color: inherit;
|
color: inherit;
|
||||||
border-radius: 16px;
|
border-radius: 16px;
|
||||||
|
@ -81,7 +81,7 @@
|
||||||
background-clip: border-box;
|
background-clip: border-box;
|
||||||
background-origin: border-box;
|
background-origin: border-box;
|
||||||
min-height: 160px;
|
min-height: 160px;
|
||||||
height: 320px;
|
height: 340px;
|
||||||
max-height: 50vh;
|
max-height: 50vh;
|
||||||
|
|
||||||
&:not(:active):is(:hover, :focus-visible) {
|
&:not(:active):is(:hover, :focus-visible) {
|
||||||
|
@ -113,6 +113,12 @@
|
||||||
opacity: 0.5;
|
opacity: 0.5;
|
||||||
mix-blend-mode: luminosity;
|
mix-blend-mode: luminosity;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.byline {
|
||||||
|
transition-duration: 0.3s;
|
||||||
|
opacity: 0.75;
|
||||||
|
mix-blend-mode: luminosity;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
&.active {
|
&.active {
|
||||||
|
@ -217,10 +223,29 @@
|
||||||
-webkit-box-orient: vertical;
|
-webkit-box-orient: vertical;
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
font-size: 90%;
|
font-size: 90%;
|
||||||
|
|
||||||
|
&.more-lines {
|
||||||
|
-webkit-line-clamp: 3;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
hr {
|
hr {
|
||||||
margin: 4px 0;
|
margin: 4px 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.byline {
|
||||||
|
white-space: nowrap;
|
||||||
|
mask-image: linear-gradient(var(--to-backward), transparent, black 32px);
|
||||||
|
|
||||||
|
a {
|
||||||
|
color: inherit;
|
||||||
|
}
|
||||||
|
|
||||||
|
.avatar {
|
||||||
|
width: 16px !important;
|
||||||
|
height: 16px !important;
|
||||||
|
opacity: 0.8;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -72,7 +72,7 @@ function Modal({ children, onClose, onClick, class: className, minimized }) {
|
||||||
<div
|
<div
|
||||||
ref={(node) => {
|
ref={(node) => {
|
||||||
modalRef.current = node;
|
modalRef.current = node;
|
||||||
escRef.current = node?.querySelector?.('[tabindex="-1"]') || node;
|
escRef(node?.querySelector?.('[tabindex="-1"]') || node);
|
||||||
}}
|
}}
|
||||||
className={className}
|
className={className}
|
||||||
onClick={(e) => {
|
onClick={(e) => {
|
||||||
|
|
|
@ -3,6 +3,7 @@ import { useLingui } from '@lingui/react';
|
||||||
import { Fragment } from 'preact';
|
import { Fragment } from 'preact';
|
||||||
import { memo } from 'preact/compat';
|
import { memo } from 'preact/compat';
|
||||||
|
|
||||||
|
import { api } from '../utils/api';
|
||||||
import shortenNumber from '../utils/shorten-number';
|
import shortenNumber from '../utils/shorten-number';
|
||||||
import states, { statusKey } from '../utils/states';
|
import states, { statusKey } from '../utils/states';
|
||||||
import { getCurrentAccountID } from '../utils/store-utils';
|
import { getCurrentAccountID } from '../utils/store-utils';
|
||||||
|
@ -302,6 +303,7 @@ function Notification({
|
||||||
disableContextMenu,
|
disableContextMenu,
|
||||||
}) {
|
}) {
|
||||||
const { _ } = useLingui();
|
const { _ } = useLingui();
|
||||||
|
const { masto } = api();
|
||||||
const {
|
const {
|
||||||
id,
|
id,
|
||||||
status,
|
status,
|
||||||
|
@ -313,9 +315,11 @@ function Notification({
|
||||||
_ids,
|
_ids,
|
||||||
_accounts,
|
_accounts,
|
||||||
_statuses,
|
_statuses,
|
||||||
|
_groupKeys,
|
||||||
// Server-side grouped notification
|
// Server-side grouped notification
|
||||||
sampleAccounts,
|
sampleAccounts,
|
||||||
notificationsCount,
|
notificationsCount,
|
||||||
|
groupKey,
|
||||||
} = notification;
|
} = notification;
|
||||||
let { type } = notification;
|
let { type } = notification;
|
||||||
|
|
||||||
|
@ -374,7 +378,7 @@ function Notification({
|
||||||
if (typeof text === 'function') {
|
if (typeof text === 'function') {
|
||||||
const count =
|
const count =
|
||||||
_accounts?.length || sampleAccounts?.length || (account ? 1 : 0);
|
_accounts?.length || sampleAccounts?.length || (account ? 1 : 0);
|
||||||
const postsCount = _statuses?.length || 0;
|
const postsCount = _statuses?.length || (status ? 1 : 0);
|
||||||
if (type === 'admin.report') {
|
if (type === 'admin.report') {
|
||||||
const targetAccount = report?.targetAccount;
|
const targetAccount = report?.targetAccount;
|
||||||
if (targetAccount) {
|
if (targetAccount) {
|
||||||
|
@ -399,7 +403,11 @@ function Notification({
|
||||||
emoji?.shortcode ===
|
emoji?.shortcode ===
|
||||||
notification.emoji.replace(/^:/, '').replace(/:$/, ''),
|
notification.emoji.replace(/^:/, '').replace(/:$/, ''),
|
||||||
); // Emoji object instead of string
|
); // Emoji object instead of string
|
||||||
text = text({ emoji: notification.emoji, emojiURL });
|
text = text({
|
||||||
|
account: <NameText account={account} showAvatar />,
|
||||||
|
emoji: notification.emoji,
|
||||||
|
emojiURL,
|
||||||
|
});
|
||||||
} else {
|
} else {
|
||||||
text = text({
|
text = text({
|
||||||
account: account ? (
|
account: account ? (
|
||||||
|
@ -439,10 +447,15 @@ function Notification({
|
||||||
|
|
||||||
console.debug('RENDER Notification', notification.id);
|
console.debug('RENDER Notification', notification.id);
|
||||||
|
|
||||||
|
const sameCount =
|
||||||
|
notificationsCount > 0 && notificationsCount <= sampleAccounts?.length;
|
||||||
|
const expandAccounts = sameCount ? 'local' : 'remote';
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div
|
<div
|
||||||
class={`notification notification-${type}`}
|
class={`notification notification-${type}`}
|
||||||
data-notification-id={_ids || id}
|
data-notification-id={_ids || id}
|
||||||
|
data-group-key={_groupKeys?.join(' ') || groupKey}
|
||||||
tabIndex="0"
|
tabIndex="0"
|
||||||
>
|
>
|
||||||
<div
|
<div
|
||||||
|
@ -546,6 +559,57 @@ function Notification({
|
||||||
</a>{' '}
|
</a>{' '}
|
||||||
</Fragment>
|
</Fragment>
|
||||||
))}
|
))}
|
||||||
|
{type === 'favourite+reblog' && expandAccounts === 'remote' ? (
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
class="small plain"
|
||||||
|
data-group-keys={_groupKeys?.join(' ')}
|
||||||
|
onClick={() => {
|
||||||
|
states.showGenericAccounts = {
|
||||||
|
heading: genericAccountsHeading,
|
||||||
|
fetchAccounts: async () => {
|
||||||
|
const keyAccounts = await Promise.allSettled(
|
||||||
|
_groupKeys.map(async (gKey) => {
|
||||||
|
const iterator = masto.v2.notifications
|
||||||
|
.$select(gKey)
|
||||||
|
.accounts.list();
|
||||||
|
return [gKey, (await iterator.next()).value];
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
const accounts = [];
|
||||||
|
for (const keyAccount of keyAccounts) {
|
||||||
|
const [key, _accounts] = keyAccount.value;
|
||||||
|
const type = /^favourite/.test(key)
|
||||||
|
? 'favourite'
|
||||||
|
: /^reblog/.test(key)
|
||||||
|
? 'reblog'
|
||||||
|
: null;
|
||||||
|
if (!type) continue;
|
||||||
|
for (const account of _accounts) {
|
||||||
|
const theAccount = accounts.find(
|
||||||
|
(a) => a.id === account.id,
|
||||||
|
);
|
||||||
|
if (theAccount) {
|
||||||
|
theAccount._types.push(type);
|
||||||
|
} else {
|
||||||
|
account._types = [type];
|
||||||
|
accounts.push(account);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return {
|
||||||
|
done: true,
|
||||||
|
value: accounts,
|
||||||
|
};
|
||||||
|
},
|
||||||
|
showReactions: true,
|
||||||
|
postID: statusKey(actualStatusID, instance),
|
||||||
|
};
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<Icon icon="chevron-down" />
|
||||||
|
</button>
|
||||||
|
) : (
|
||||||
<button
|
<button
|
||||||
type="button"
|
type="button"
|
||||||
class="small plain"
|
class="small plain"
|
||||||
|
@ -555,6 +619,7 @@ function Notification({
|
||||||
`+${_accounts.length - AVATARS_LIMIT}`}
|
`+${_accounts.length - AVATARS_LIMIT}`}
|
||||||
<Icon icon="chevron-down" />
|
<Icon icon="chevron-down" />
|
||||||
</button>
|
</button>
|
||||||
|
)}
|
||||||
</p>
|
</p>
|
||||||
)}
|
)}
|
||||||
{!_accounts?.length && sampleAccounts?.length > 1 && (
|
{!_accounts?.length && sampleAccounts?.length > 1 && (
|
||||||
|
|
|
@ -14,10 +14,12 @@ function isValidDate(value) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const resolvedLocale = new Intl.DateTimeFormat().resolvedOptions().locale;
|
const resolvedLocale = mem(
|
||||||
|
() => new Intl.DateTimeFormat().resolvedOptions().locale,
|
||||||
|
);
|
||||||
const DTF = mem((locale, opts = {}) => {
|
const DTF = mem((locale, opts = {}) => {
|
||||||
const regionlessLocale = locale.replace(/-[a-z]+$/i, '');
|
const regionlessLocale = locale.replace(/-[a-z]+$/i, '');
|
||||||
const lang = localeMatch([regionlessLocale], [resolvedLocale], locale);
|
const lang = localeMatch([regionlessLocale], [resolvedLocale()], locale);
|
||||||
try {
|
try {
|
||||||
return new Intl.DateTimeFormat(lang, opts);
|
return new Intl.DateTimeFormat(lang, opts);
|
||||||
} catch (e) {}
|
} catch (e) {}
|
||||||
|
|
|
@ -1990,6 +1990,20 @@ a.card:is(:hover, :focus):visited {
|
||||||
.card.large.card-post {
|
.card.large.card-post {
|
||||||
flex-direction: column-reverse;
|
flex-direction: column-reverse;
|
||||||
}
|
}
|
||||||
|
.card-byline-author {
|
||||||
|
display: inline-flex;
|
||||||
|
gap: 4px;
|
||||||
|
color: var(--text-insignificant-color);
|
||||||
|
padding: 2px 8px;
|
||||||
|
align-items: center;
|
||||||
|
|
||||||
|
.avatar {
|
||||||
|
width: 16px !important;
|
||||||
|
height: 16px !important;
|
||||||
|
opacity: 0.8;
|
||||||
|
vertical-align: middle;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/* POLLS */
|
/* POLLS */
|
||||||
|
|
||||||
|
|
|
@ -26,7 +26,7 @@ import {
|
||||||
} from 'preact/hooks';
|
} from 'preact/hooks';
|
||||||
import punycode from 'punycode/';
|
import punycode from 'punycode/';
|
||||||
import { useHotkeys } from 'react-hotkeys-hook';
|
import { useHotkeys } from 'react-hotkeys-hook';
|
||||||
import { detectAll } from 'tinyld/light';
|
// import { detectAll } from 'tinyld/light';
|
||||||
import { useLongPress } from 'use-long-press';
|
import { useLongPress } from 'use-long-press';
|
||||||
import { useSnapshot } from 'valtio';
|
import { useSnapshot } from 'valtio';
|
||||||
|
|
||||||
|
@ -51,7 +51,6 @@ import htmlContentLength from '../utils/html-content-length';
|
||||||
import isRTL from '../utils/is-rtl';
|
import isRTL from '../utils/is-rtl';
|
||||||
import isMastodonLinkMaybe from '../utils/isMastodonLinkMaybe';
|
import isMastodonLinkMaybe from '../utils/isMastodonLinkMaybe';
|
||||||
import localeMatch from '../utils/locale-match';
|
import localeMatch from '../utils/locale-match';
|
||||||
import mem from '../utils/mem';
|
|
||||||
import niceDateTime from '../utils/nice-date-time';
|
import niceDateTime from '../utils/nice-date-time';
|
||||||
import openCompose from '../utils/open-compose';
|
import openCompose from '../utils/open-compose';
|
||||||
import pmem from '../utils/pmem';
|
import pmem from '../utils/pmem';
|
||||||
|
@ -168,7 +167,8 @@ const SIZE_CLASS = {
|
||||||
l: 'large',
|
l: 'large',
|
||||||
};
|
};
|
||||||
|
|
||||||
const detectLang = mem((text) => {
|
const detectLang = pmem(async (text) => {
|
||||||
|
const { detectAll } = await import('tinyld/light');
|
||||||
text = text?.trim();
|
text = text?.trim();
|
||||||
|
|
||||||
// Ref: https://github.com/komodojp/tinyld/blob/develop/docs/benchmark.md
|
// Ref: https://github.com/komodojp/tinyld/blob/develop/docs/benchmark.md
|
||||||
|
@ -304,8 +304,8 @@ function Status({
|
||||||
if (!content) return;
|
if (!content) return;
|
||||||
if (_language) return;
|
if (_language) return;
|
||||||
let timer;
|
let timer;
|
||||||
timer = setTimeout(() => {
|
timer = setTimeout(async () => {
|
||||||
let detected = detectLang(
|
let detected = await detectLang(
|
||||||
getHTMLText(content, {
|
getHTMLText(content, {
|
||||||
preProcess: (dom) => {
|
preProcess: (dom) => {
|
||||||
// Remove anything that can skew the language detection
|
// Remove anything that can skew the language detection
|
||||||
|
@ -1519,11 +1519,11 @@ function Status({
|
||||||
node?.closest?.(
|
node?.closest?.(
|
||||||
'.timeline-item, .timeline-item-alt, .status-link, .status-focus',
|
'.timeline-item, .timeline-item-alt, .status-link, .status-focus',
|
||||||
) || node;
|
) || node;
|
||||||
rRef.current = nodeRef;
|
rRef(nodeRef);
|
||||||
fRef.current = nodeRef;
|
fRef(nodeRef);
|
||||||
dRef.current = nodeRef;
|
dRef(nodeRef);
|
||||||
bRef.current = nodeRef;
|
bRef(nodeRef);
|
||||||
xRef.current = nodeRef;
|
xRef(nodeRef);
|
||||||
}}
|
}}
|
||||||
tabindex="-1"
|
tabindex="-1"
|
||||||
class={`status ${
|
class={`status ${
|
||||||
|
@ -2152,6 +2152,9 @@ function Status({
|
||||||
selfReferential={
|
selfReferential={
|
||||||
card?.url === status.url || card?.url === status.uri
|
card?.url === status.url || card?.url === status.uri
|
||||||
}
|
}
|
||||||
|
selfAuthor={card?.authors?.some(
|
||||||
|
(a) => a.account?.url === accountURL,
|
||||||
|
)}
|
||||||
instance={currentInstance}
|
instance={currentInstance}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
|
@ -2564,7 +2567,27 @@ function isCardPost(domain) {
|
||||||
return ['x.com', 'twitter.com', 'threads.net', 'bsky.app'].includes(domain);
|
return ['x.com', 'twitter.com', 'threads.net', 'bsky.app'].includes(domain);
|
||||||
}
|
}
|
||||||
|
|
||||||
function Card({ card, selfReferential, instance }) {
|
function Byline({ authors, hidden, children }) {
|
||||||
|
if (hidden) return children;
|
||||||
|
if (!authors?.[0]?.account?.id) return children;
|
||||||
|
const author = authors[0].account;
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div class="card-byline">
|
||||||
|
{children}
|
||||||
|
<div class="card-byline-author">
|
||||||
|
<Icon icon="link" size="s" />{' '}
|
||||||
|
<small>
|
||||||
|
<Trans comment="More from [Author]">
|
||||||
|
More from <NameText account={author} showAvatar />
|
||||||
|
</Trans>
|
||||||
|
</small>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
function Card({ card, selfReferential, selfAuthor, instance }) {
|
||||||
const snapStates = useSnapshot(states);
|
const snapStates = useSnapshot(states);
|
||||||
const {
|
const {
|
||||||
blurhash,
|
blurhash,
|
||||||
|
@ -2584,6 +2607,7 @@ function Card({ card, selfReferential, instance }) {
|
||||||
embedUrl,
|
embedUrl,
|
||||||
language,
|
language,
|
||||||
publishedAt,
|
publishedAt,
|
||||||
|
authors,
|
||||||
} = card;
|
} = card;
|
||||||
|
|
||||||
/* type
|
/* type
|
||||||
|
@ -2677,6 +2701,7 @@ function Card({ card, selfReferential, instance }) {
|
||||||
const isPost = isCardPost(domain);
|
const isPost = isCardPost(domain);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
<Byline hidden={!!selfAuthor} authors={authors}>
|
||||||
<a
|
<a
|
||||||
href={cardStatusURL || url}
|
href={cardStatusURL || url}
|
||||||
target={cardStatusURL ? null : '_blank'}
|
target={cardStatusURL ? null : '_blank'}
|
||||||
|
@ -2706,7 +2731,10 @@ function Card({ card, selfReferential, instance }) {
|
||||||
'--anim-duration':
|
'--anim-duration':
|
||||||
width &&
|
width &&
|
||||||
height &&
|
height &&
|
||||||
`${Math.min(Math.max(Math.max(width, height) / 100, 5), 120)}s`,
|
`${Math.min(
|
||||||
|
Math.max(Math.max(width, height) / 100, 5),
|
||||||
|
120,
|
||||||
|
)}s`,
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
@ -2731,6 +2759,7 @@ function Card({ card, selfReferential, instance }) {
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
</a>
|
</a>
|
||||||
|
</Byline>
|
||||||
);
|
);
|
||||||
} else if (type === 'photo') {
|
} else if (type === 'photo') {
|
||||||
return (
|
return (
|
||||||
|
@ -3514,7 +3543,7 @@ function FilteredStatus({
|
||||||
<span class="status-filtered-info">
|
<span class="status-filtered-info">
|
||||||
<span class="status-filtered-info-1">
|
<span class="status-filtered-info-1">
|
||||||
{isReblog ? (
|
{isReblog ? (
|
||||||
<Trans>
|
<Trans comment="[Name] [Visibility icon] boosted">
|
||||||
<NameText account={status.account} instance={instance} />{' '}
|
<NameText account={status.account} instance={instance} />{' '}
|
||||||
<Icon
|
<Icon
|
||||||
icon={visibilityIconsMap[visibility]}
|
icon={visibilityIconsMap[visibility]}
|
||||||
|
|
|
@ -19,6 +19,7 @@ export default function SubMenu2(props) {
|
||||||
menuRef.current?.openMenu?.();
|
menuRef.current?.openMenu?.();
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
...props.itemProps,
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
|
|
|
@ -390,10 +390,10 @@ function Timeline({
|
||||||
}`}
|
}`}
|
||||||
ref={(node) => {
|
ref={(node) => {
|
||||||
scrollableRef.current = node;
|
scrollableRef.current = node;
|
||||||
jRef.current = node;
|
jRef(node);
|
||||||
kRef.current = node;
|
kRef(node);
|
||||||
oRef.current = node;
|
oRef(node);
|
||||||
dotRef.current = node;
|
dotRef(node);
|
||||||
}}
|
}}
|
||||||
tabIndex="-1"
|
tabIndex="-1"
|
||||||
onClick={(e) => {
|
onClick={(e) => {
|
||||||
|
|
|
@ -15,7 +15,7 @@
|
||||||
"code": "cs-CZ",
|
"code": "cs-CZ",
|
||||||
"nativeName": "čeština",
|
"nativeName": "čeština",
|
||||||
"name": "Czech",
|
"name": "Czech",
|
||||||
"completion": 79
|
"completion": 80
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"code": "de-DE",
|
"code": "de-DE",
|
||||||
|
@ -27,7 +27,7 @@
|
||||||
"code": "eo-UY",
|
"code": "eo-UY",
|
||||||
"nativeName": "Esperanto",
|
"nativeName": "Esperanto",
|
||||||
"name": "Esperanto",
|
"name": "Esperanto",
|
||||||
"completion": 61
|
"completion": 100
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"code": "es-ES",
|
"code": "es-ES",
|
||||||
|
@ -75,13 +75,13 @@
|
||||||
"code": "it-IT",
|
"code": "it-IT",
|
||||||
"nativeName": "italiano",
|
"nativeName": "italiano",
|
||||||
"name": "Italian",
|
"name": "Italian",
|
||||||
"completion": 95
|
"completion": 100
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"code": "ja-JP",
|
"code": "ja-JP",
|
||||||
"nativeName": "日本語",
|
"nativeName": "日本語",
|
||||||
"name": "Japanese",
|
"name": "Japanese",
|
||||||
"completion": 31
|
"completion": 52
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"code": "kab",
|
"code": "kab",
|
||||||
|
@ -99,19 +99,25 @@
|
||||||
"code": "lt-LT",
|
"code": "lt-LT",
|
||||||
"nativeName": "lietuvių",
|
"nativeName": "lietuvių",
|
||||||
"name": "Lithuanian",
|
"name": "Lithuanian",
|
||||||
"completion": 43
|
"completion": 80
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"code": "nb-NO",
|
||||||
|
"nativeName": "norsk bokmål",
|
||||||
|
"name": "Norwegian Bokmål",
|
||||||
|
"completion": 4
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"code": "nl-NL",
|
"code": "nl-NL",
|
||||||
"nativeName": "Nederlands",
|
"nativeName": "Nederlands",
|
||||||
"name": "Dutch",
|
"name": "Dutch",
|
||||||
"completion": 84
|
"completion": 83
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"code": "pl-PL",
|
"code": "pl-PL",
|
||||||
"nativeName": "polski",
|
"nativeName": "polski",
|
||||||
"name": "Polish",
|
"name": "Polish",
|
||||||
"completion": 2
|
"completion": 6
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"code": "pt-BR",
|
"code": "pt-BR",
|
||||||
|
|
720
src/locales/ar-SA.po
generated
720
src/locales/ar-SA.po
generated
File diff suppressed because it is too large
Load diff
713
src/locales/ca-ES.po
generated
713
src/locales/ca-ES.po
generated
File diff suppressed because it is too large
Load diff
756
src/locales/cs-CZ.po
generated
756
src/locales/cs-CZ.po
generated
File diff suppressed because it is too large
Load diff
720
src/locales/de-DE.po
generated
720
src/locales/de-DE.po
generated
File diff suppressed because it is too large
Load diff
1435
src/locales/eo-UY.po
generated
1435
src/locales/eo-UY.po
generated
File diff suppressed because it is too large
Load diff
811
src/locales/es-ES.po
generated
811
src/locales/es-ES.po
generated
File diff suppressed because it is too large
Load diff
724
src/locales/eu-ES.po
generated
724
src/locales/eu-ES.po
generated
File diff suppressed because it is too large
Load diff
407
src/locales/fa-IR.po
generated
407
src/locales/fa-IR.po
generated
File diff suppressed because it is too large
Load diff
724
src/locales/fi-FI.po
generated
724
src/locales/fi-FI.po
generated
File diff suppressed because it is too large
Load diff
495
src/locales/fr-FR.po
generated
495
src/locales/fr-FR.po
generated
File diff suppressed because it is too large
Load diff
511
src/locales/gl-ES.po
generated
511
src/locales/gl-ES.po
generated
File diff suppressed because it is too large
Load diff
722
src/locales/he-IL.po
generated
722
src/locales/he-IL.po
generated
File diff suppressed because it is too large
Load diff
621
src/locales/it-IT.po
generated
621
src/locales/it-IT.po
generated
File diff suppressed because it is too large
Load diff
1086
src/locales/ja-JP.po
generated
1086
src/locales/ja-JP.po
generated
File diff suppressed because it is too large
Load diff
740
src/locales/kab.po
generated
740
src/locales/kab.po
generated
File diff suppressed because it is too large
Load diff
720
src/locales/ko-KR.po
generated
720
src/locales/ko-KR.po
generated
File diff suppressed because it is too large
Load diff
1378
src/locales/lt-LT.po
generated
1378
src/locales/lt-LT.po
generated
File diff suppressed because it is too large
Load diff
3754
src/locales/nb-NO.po
generated
Normal file
3754
src/locales/nb-NO.po
generated
Normal file
File diff suppressed because it is too large
Load diff
393
src/locales/nl-NL.po
generated
393
src/locales/nl-NL.po
generated
File diff suppressed because it is too large
Load diff
722
src/locales/oc-FR.po
generated
722
src/locales/oc-FR.po
generated
File diff suppressed because it is too large
Load diff
804
src/locales/pl-PL.po
generated
804
src/locales/pl-PL.po
generated
File diff suppressed because it is too large
Load diff
529
src/locales/pt-BR.po
generated
529
src/locales/pt-BR.po
generated
File diff suppressed because it is too large
Load diff
631
src/locales/pt-PT.po
generated
631
src/locales/pt-PT.po
generated
File diff suppressed because it is too large
Load diff
517
src/locales/ru-RU.po
generated
517
src/locales/ru-RU.po
generated
File diff suppressed because it is too large
Load diff
722
src/locales/th-TH.po
generated
722
src/locales/th-TH.po
generated
File diff suppressed because it is too large
Load diff
722
src/locales/uk-UA.po
generated
722
src/locales/uk-UA.po
generated
File diff suppressed because it is too large
Load diff
501
src/locales/zh-CN.po
generated
501
src/locales/zh-CN.po
generated
File diff suppressed because it is too large
Load diff
722
src/locales/zh-TW.po
generated
722
src/locales/zh-TW.po
generated
File diff suppressed because it is too large
Load diff
32
src/main.jsx
32
src/main.jsx
|
@ -48,6 +48,38 @@ setTimeout(() => {
|
||||||
} catch (e) {}
|
} catch (e) {}
|
||||||
}, 5000);
|
}, 5000);
|
||||||
|
|
||||||
|
// Service worker cache cleanup
|
||||||
|
if ('serviceWorker' in navigator && typeof caches !== 'undefined') {
|
||||||
|
const MAX_SW_CACHE_SIZE = 50;
|
||||||
|
const IGNORE_CACHE_KEYS = ['icons'];
|
||||||
|
let clearRanOnce = false;
|
||||||
|
const FAST_INTERVAL = 10_000; // 10 seconds
|
||||||
|
const SLOW_INTERVAL = 60 * 60 * 1000; // 1 hour
|
||||||
|
async function clearCaches() {
|
||||||
|
if (window.__IDLE__) {
|
||||||
|
try {
|
||||||
|
const keys = await caches.keys();
|
||||||
|
for (const key of keys) {
|
||||||
|
if (IGNORE_CACHE_KEYS.includes(key)) continue;
|
||||||
|
const cache = await caches.open(key);
|
||||||
|
const _keys = await cache.keys();
|
||||||
|
if (_keys.length > MAX_SW_CACHE_SIZE) {
|
||||||
|
console.warn('Cleaning cache', key, _keys.length);
|
||||||
|
const deleteKeys = _keys.slice(MAX_SW_CACHE_SIZE);
|
||||||
|
for (const deleteKey of deleteKeys) {
|
||||||
|
await cache.delete(deleteKey);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
clearRanOnce = true;
|
||||||
|
} catch (e) {} // Silent fail
|
||||||
|
}
|
||||||
|
// Once cleared, clear again at slower interval
|
||||||
|
setTimeout(clearCaches, clearRanOnce ? SLOW_INTERVAL : FAST_INTERVAL);
|
||||||
|
}
|
||||||
|
setTimeout(clearCaches, FAST_INTERVAL);
|
||||||
|
}
|
||||||
|
|
||||||
window.__CLOAK__ = () => {
|
window.__CLOAK__ = () => {
|
||||||
document.body.classList.toggle('cloak');
|
document.body.classList.toggle('cloak');
|
||||||
};
|
};
|
||||||
|
|
|
@ -666,6 +666,7 @@
|
||||||
black calc(100% - 1em),
|
black calc(100% - 1em),
|
||||||
transparent 100%
|
transparent 100%
|
||||||
);
|
);
|
||||||
|
padding-top: 0.1em;
|
||||||
|
|
||||||
@media (min-width: 40em) {
|
@media (min-width: 40em) {
|
||||||
--width: 25vw;
|
--width: 25vw;
|
||||||
|
@ -697,7 +698,7 @@
|
||||||
|
|
||||||
&:is(.catchup-group-account, .catchup-selected-author):is(
|
&:is(.catchup-group-account, .catchup-selected-author):is(
|
||||||
.catchup-filter-original,
|
.catchup-filter-original,
|
||||||
.catchup-filter-reply
|
.catchup-filter-replies
|
||||||
)
|
)
|
||||||
> li {
|
> li {
|
||||||
margin-bottom: 0;
|
margin-bottom: 0;
|
||||||
|
|
|
@ -842,10 +842,10 @@ function Catchup() {
|
||||||
<div
|
<div
|
||||||
ref={(node) => {
|
ref={(node) => {
|
||||||
scrollableRef.current = node;
|
scrollableRef.current = node;
|
||||||
jRef.current = node;
|
jRef(node);
|
||||||
kRef.current = node;
|
kRef(node);
|
||||||
hlRef.current = node;
|
hlRef(node);
|
||||||
escRef.current = node;
|
escRef(node);
|
||||||
}}
|
}}
|
||||||
id="catchup-page"
|
id="catchup-page"
|
||||||
class="deck-container"
|
class="deck-container"
|
||||||
|
@ -1193,6 +1193,7 @@ function Catchup() {
|
||||||
href={url}
|
href={url}
|
||||||
target="_blank"
|
target="_blank"
|
||||||
rel="noopener noreferrer"
|
rel="noopener noreferrer"
|
||||||
|
class="link-block"
|
||||||
style={
|
style={
|
||||||
accentColor
|
accentColor
|
||||||
? {
|
? {
|
||||||
|
|
|
@ -22,6 +22,7 @@ function Following({ title, path, id, ...props }) {
|
||||||
const snapStates = useSnapshot(states);
|
const snapStates = useSnapshot(states);
|
||||||
const homeIterator = useRef();
|
const homeIterator = useRef();
|
||||||
const latestItem = useRef();
|
const latestItem = useRef();
|
||||||
|
__BENCHMARK.end('time-to-following');
|
||||||
|
|
||||||
console.debug('RENDER Following', title, id);
|
console.debug('RENDER Following', title, id);
|
||||||
const supportsPixelfed = supports('@pixelfed/home-include-reblogs');
|
const supportsPixelfed = supports('@pixelfed/home-include-reblogs');
|
||||||
|
@ -66,7 +67,6 @@ function Following({ title, path, id, ...props }) {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
__BENCHMARK.end('fetch-home-first');
|
__BENCHMARK.end('fetch-home-first');
|
||||||
__BENCHMARK.end('time-to-home');
|
|
||||||
return {
|
return {
|
||||||
...results,
|
...results,
|
||||||
value,
|
value,
|
||||||
|
|
|
@ -27,6 +27,7 @@ import {
|
||||||
function Home() {
|
function Home() {
|
||||||
const { _ } = useLingui();
|
const { _ } = useLingui();
|
||||||
const snapStates = useSnapshot(states);
|
const snapStates = useSnapshot(states);
|
||||||
|
__BENCHMARK.end('time-to-home');
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
(async () => {
|
(async () => {
|
||||||
const keys = await db.drafts.keys();
|
const keys = await db.drafts.keys();
|
||||||
|
|
|
@ -1,3 +1,39 @@
|
||||||
|
#notifications-page {
|
||||||
|
.notification {
|
||||||
|
content-visibility: auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
.timeline-header {
|
||||||
|
position: sticky;
|
||||||
|
--mask-padding: 8px;
|
||||||
|
top: calc(48px - var(--mask-padding));
|
||||||
|
transition: top 0.5s ease-in-out;
|
||||||
|
header[hidden] ~ & {
|
||||||
|
top: calc(-1 * var(--mask-padding));
|
||||||
|
}
|
||||||
|
z-index: 1;
|
||||||
|
background-color: inherit;
|
||||||
|
mask-image: linear-gradient(
|
||||||
|
to bottom,
|
||||||
|
transparent,
|
||||||
|
#000 var(--mask-padding) calc(100% - var(--mask-padding) * 2),
|
||||||
|
transparent
|
||||||
|
);
|
||||||
|
|
||||||
|
@media (min-width: 40em) {
|
||||||
|
background-color: var(--bg-faded-color);
|
||||||
|
}
|
||||||
|
padding-block: 16px;
|
||||||
|
margin: 0;
|
||||||
|
opacity: 1;
|
||||||
|
|
||||||
|
small {
|
||||||
|
font-weight: normal;
|
||||||
|
font-size: var(--text-size);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
.notification {
|
.notification {
|
||||||
display: flex;
|
display: flex;
|
||||||
padding: 16px !important;
|
padding: 16px !important;
|
||||||
|
@ -10,8 +46,8 @@
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
.notification.notification-mention {
|
.notification-type.notification-mention {
|
||||||
margin-top: 16px;
|
padding-top: 16px;
|
||||||
}
|
}
|
||||||
.only-mentions .notification:not(.notification-mention),
|
.only-mentions .notification:not(.notification-mention),
|
||||||
.only-mentions .timeline-empty {
|
.only-mentions .timeline-empty {
|
||||||
|
@ -139,7 +175,6 @@
|
||||||
max-height: 320px;
|
max-height: 320px;
|
||||||
filter: none;
|
filter: none;
|
||||||
background-color: var(--bg-color);
|
background-color: var(--bg-color);
|
||||||
margin-top: calc(-16px - 1px);
|
|
||||||
border-color: var(--reply-to-color);
|
border-color: var(--reply-to-color);
|
||||||
box-shadow: 0 0 0 3px var(--reply-to-faded-color);
|
box-shadow: 0 0 0 3px var(--reply-to-faded-color);
|
||||||
}
|
}
|
||||||
|
@ -203,6 +238,8 @@
|
||||||
}
|
}
|
||||||
|
|
||||||
#mentions-option {
|
#mentions-option {
|
||||||
|
position: relative;
|
||||||
|
z-index: 2;
|
||||||
float: right;
|
float: right;
|
||||||
&:dir(rtl) {
|
&:dir(rtl) {
|
||||||
float: left;
|
float: left;
|
||||||
|
|
|
@ -4,7 +4,13 @@ import { msg, Plural, t, Trans } from '@lingui/macro';
|
||||||
import { useLingui } from '@lingui/react';
|
import { useLingui } from '@lingui/react';
|
||||||
import { Fragment } from 'preact';
|
import { Fragment } from 'preact';
|
||||||
import { memo } from 'preact/compat';
|
import { memo } from 'preact/compat';
|
||||||
import { useCallback, useEffect, useRef, useState } from 'preact/hooks';
|
import {
|
||||||
|
useCallback,
|
||||||
|
useEffect,
|
||||||
|
useMemo,
|
||||||
|
useRef,
|
||||||
|
useState,
|
||||||
|
} from 'preact/hooks';
|
||||||
import { useHotkeys } from 'react-hotkeys-hook';
|
import { useHotkeys } from 'react-hotkeys-hook';
|
||||||
import { InView } from 'react-intersection-observer';
|
import { InView } from 'react-intersection-observer';
|
||||||
import { useSearchParams } from 'react-router-dom';
|
import { useSearchParams } from 'react-router-dom';
|
||||||
|
@ -63,7 +69,7 @@ export function mastoFetchNotifications(opts = {}) {
|
||||||
memSupportsGroupedNotifications()
|
memSupportsGroupedNotifications()
|
||||||
) {
|
) {
|
||||||
// https://github.com/mastodon/mastodon/pull/29889
|
// https://github.com/mastodon/mastodon/pull/29889
|
||||||
return masto.v2_alpha.notifications.list({
|
return masto.v2.notifications.list({
|
||||||
limit: NOTIFICATIONS_GROUPED_LIMIT,
|
limit: NOTIFICATIONS_GROUPED_LIMIT,
|
||||||
...opts,
|
...opts,
|
||||||
});
|
});
|
||||||
|
@ -471,15 +477,24 @@ function Notifications({ columnMode }) {
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
const today = new Date();
|
||||||
|
const todaySubHeading = useMemo(() => {
|
||||||
|
return niceDateTime(today, {
|
||||||
|
forceOpts: {
|
||||||
|
weekday: 'long',
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}, [today]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div
|
<div
|
||||||
id="notifications-page"
|
id="notifications-page"
|
||||||
class="deck-container"
|
class="deck-container"
|
||||||
ref={(node) => {
|
ref={(node) => {
|
||||||
scrollableRef.current = node;
|
scrollableRef.current = node;
|
||||||
jRef.current = node;
|
jRef(node);
|
||||||
kRef.current = node;
|
kRef(node);
|
||||||
oRef.current = node;
|
oRef(node);
|
||||||
}}
|
}}
|
||||||
tabIndex="-1"
|
tabIndex="-1"
|
||||||
>
|
>
|
||||||
|
@ -726,7 +741,8 @@ function Notifications({ columnMode }) {
|
||||||
</label>
|
</label>
|
||||||
</div>
|
</div>
|
||||||
<h2 class="timeline-header">
|
<h2 class="timeline-header">
|
||||||
<Trans>Today</Trans>
|
<Trans>Today</Trans>{' '}
|
||||||
|
<small class="insignificant bidi-isolate">{todaySubHeading}</small>
|
||||||
</h2>
|
</h2>
|
||||||
{showTodayEmpty && (
|
{showTodayEmpty && (
|
||||||
<p class="ui-state insignificant">
|
<p class="ui-state insignificant">
|
||||||
|
@ -757,9 +773,21 @@ function Notifications({ columnMode }) {
|
||||||
: niceDateTime(currentDay, {
|
: niceDateTime(currentDay, {
|
||||||
hideTime: true,
|
hideTime: true,
|
||||||
});
|
});
|
||||||
|
const subHeading = niceDateTime(currentDay, {
|
||||||
|
forceOpts: {
|
||||||
|
weekday: 'long',
|
||||||
|
},
|
||||||
|
});
|
||||||
return (
|
return (
|
||||||
<Fragment key={notification._ids || notification.id}>
|
<Fragment key={notification._ids || notification.id}>
|
||||||
{differentDay && <h2 class="timeline-header">{heading}</h2>}
|
{differentDay && (
|
||||||
|
<h2 class="timeline-header">
|
||||||
|
<span>{heading}</span>{' '}
|
||||||
|
<small class="insignificant bidi-isolate">
|
||||||
|
{subHeading}
|
||||||
|
</small>
|
||||||
|
</h2>
|
||||||
|
)}
|
||||||
<Notification
|
<Notification
|
||||||
instance={instance}
|
instance={instance}
|
||||||
notification={notification}
|
notification={notification}
|
||||||
|
|
|
@ -34,6 +34,7 @@
|
||||||
align-items: center;
|
align-items: center;
|
||||||
flex-wrap: wrap;
|
flex-wrap: wrap;
|
||||||
border-bottom: var(--hairline-width) solid var(--outline-color);
|
border-bottom: var(--hairline-width) solid var(--outline-color);
|
||||||
|
gap: 4px;
|
||||||
|
|
||||||
&.block {
|
&.block {
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
|
|
|
@ -841,6 +841,42 @@ function Settings({ onClose }) {
|
||||||
)}
|
)}
|
||||||
</ul>
|
</ul>
|
||||||
)}
|
)}
|
||||||
|
<p>Service Worker Cache</p>
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
class="plain2 small"
|
||||||
|
onClick={async () => alert(await getCachesKeys())}
|
||||||
|
>
|
||||||
|
Show keys count
|
||||||
|
</button>{' '}
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
class="plain2 small"
|
||||||
|
onClick={() => {
|
||||||
|
const key = prompt('Enter cache key');
|
||||||
|
if (!key) return;
|
||||||
|
try {
|
||||||
|
clearCacheKey(key);
|
||||||
|
} catch (e) {
|
||||||
|
alert(e);
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
Clear cache key
|
||||||
|
</button>{' '}
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
class="plain2 small"
|
||||||
|
onClick={() => {
|
||||||
|
try {
|
||||||
|
clearCaches();
|
||||||
|
} catch (e) {
|
||||||
|
alert(e);
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
Clear all caches
|
||||||
|
</button>
|
||||||
</details>
|
</details>
|
||||||
)}
|
)}
|
||||||
</main>
|
</main>
|
||||||
|
@ -848,6 +884,28 @@ function Settings({ onClose }) {
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async function getCachesKeys() {
|
||||||
|
const keys = await caches.keys();
|
||||||
|
const total = {};
|
||||||
|
for (const key of keys) {
|
||||||
|
const cache = await caches.open(key);
|
||||||
|
const k = await cache.keys();
|
||||||
|
total[key] = k.length;
|
||||||
|
}
|
||||||
|
return total;
|
||||||
|
}
|
||||||
|
|
||||||
|
function clearCacheKey(key) {
|
||||||
|
return caches.delete(key);
|
||||||
|
}
|
||||||
|
|
||||||
|
async function clearCaches() {
|
||||||
|
const keys = await caches.keys();
|
||||||
|
for (const key of keys) {
|
||||||
|
await caches.delete(key);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
function PushNotificationsSection({ onClose }) {
|
function PushNotificationsSection({ onClose }) {
|
||||||
if (!isPushSupported()) return null;
|
if (!isPushSupported()) return null;
|
||||||
|
|
||||||
|
|
|
@ -13,6 +13,7 @@ import Icon from '../components/icon';
|
||||||
import Link from '../components/link';
|
import Link from '../components/link';
|
||||||
import Loader from '../components/loader';
|
import Loader from '../components/loader';
|
||||||
import Menu2 from '../components/menu2';
|
import Menu2 from '../components/menu2';
|
||||||
|
import NameText from '../components/name-text';
|
||||||
import RelativeTime from '../components/relative-time';
|
import RelativeTime from '../components/relative-time';
|
||||||
import Timeline from '../components/timeline';
|
import Timeline from '../components/timeline';
|
||||||
import { api } from '../utils/api';
|
import { api } from '../utils/api';
|
||||||
|
@ -229,6 +230,7 @@ function Trending({ columnMode, ...props }) {
|
||||||
</header>
|
</header>
|
||||||
{links.map((link) => {
|
{links.map((link) => {
|
||||||
const {
|
const {
|
||||||
|
authors,
|
||||||
authorName,
|
authorName,
|
||||||
authorUrl,
|
authorUrl,
|
||||||
blurhash,
|
blurhash,
|
||||||
|
@ -244,6 +246,11 @@ function Trending({ columnMode, ...props }) {
|
||||||
url,
|
url,
|
||||||
width,
|
width,
|
||||||
} = link;
|
} = link;
|
||||||
|
const author = authors?.[0]?.account?.id
|
||||||
|
? authors[0].account
|
||||||
|
: null;
|
||||||
|
const isShortTitle = title.length < 30;
|
||||||
|
const hasAuthor = !!(authorName || author);
|
||||||
const domain = punycode.toUnicode(
|
const domain = punycode.toUnicode(
|
||||||
URL.parse(url)
|
URL.parse(url)
|
||||||
.hostname.replace(/^www\./, '')
|
.hostname.replace(/^www\./, '')
|
||||||
|
@ -267,13 +274,13 @@ function Trending({ columnMode, ...props }) {
|
||||||
href={url}
|
href={url}
|
||||||
target="_blank"
|
target="_blank"
|
||||||
rel="noopener noreferrer"
|
rel="noopener noreferrer"
|
||||||
class={
|
class={`link-block ${
|
||||||
hasCurrentLink
|
hasCurrentLink
|
||||||
? currentLink === url
|
? currentLink === url
|
||||||
? 'active'
|
? 'active'
|
||||||
: 'inactive'
|
: 'inactive'
|
||||||
: ''
|
: ''
|
||||||
}
|
}`}
|
||||||
style={
|
style={
|
||||||
accentColor
|
accentColor
|
||||||
? {
|
? {
|
||||||
|
@ -322,7 +329,9 @@ function Trending({ columnMode, ...props }) {
|
||||||
</header>
|
</header>
|
||||||
{!!description && (
|
{!!description && (
|
||||||
<p
|
<p
|
||||||
class="description"
|
class={`description ${
|
||||||
|
hasAuthor && !isShortTitle ? '' : 'more-lines'
|
||||||
|
}`}
|
||||||
lang={language}
|
lang={language}
|
||||||
dir="auto"
|
dir="auto"
|
||||||
title={description}
|
title={description}
|
||||||
|
@ -330,6 +339,31 @@ function Trending({ columnMode, ...props }) {
|
||||||
{description}
|
{description}
|
||||||
</p>
|
</p>
|
||||||
)}
|
)}
|
||||||
|
{hasAuthor && (
|
||||||
|
<>
|
||||||
|
<hr />
|
||||||
|
<p class="byline">
|
||||||
|
<small>
|
||||||
|
<Trans comment="By [Author]">
|
||||||
|
By{' '}
|
||||||
|
{author ? (
|
||||||
|
<NameText account={author} showAvatar />
|
||||||
|
) : authorUrl ? (
|
||||||
|
<a
|
||||||
|
href={authorUrl}
|
||||||
|
target="_blank"
|
||||||
|
rel="noopener noreferrer"
|
||||||
|
>
|
||||||
|
{authorName}
|
||||||
|
</a>
|
||||||
|
) : (
|
||||||
|
authorName
|
||||||
|
)}
|
||||||
|
</Trans>
|
||||||
|
</small>
|
||||||
|
</p>
|
||||||
|
</>
|
||||||
|
)}
|
||||||
</div>
|
</div>
|
||||||
</article>
|
</article>
|
||||||
</a>
|
</a>
|
||||||
|
|
|
@ -57,6 +57,11 @@ export function initClient({ instance, accessToken }) {
|
||||||
return client;
|
return client;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function hasInstance(instance) {
|
||||||
|
const instances = store.local.getJSON('instances') || {};
|
||||||
|
return !!instances[instance];
|
||||||
|
}
|
||||||
|
|
||||||
// Get the instance information
|
// Get the instance information
|
||||||
// The config is needed for composing
|
// The config is needed for composing
|
||||||
export async function initInstance(client, instance) {
|
export async function initInstance(client, instance) {
|
||||||
|
@ -64,6 +69,7 @@ export async function initInstance(client, instance) {
|
||||||
const { masto, accessToken } = client;
|
const { masto, accessToken } = client;
|
||||||
// Request v2, fallback to v1 if fail
|
// Request v2, fallback to v1 if fail
|
||||||
let info;
|
let info;
|
||||||
|
__BENCHMARK.start('fetch-instance');
|
||||||
try {
|
try {
|
||||||
info = await masto.v2.instance.fetch();
|
info = await masto.v2.instance.fetch();
|
||||||
} catch (e) {}
|
} catch (e) {}
|
||||||
|
@ -72,6 +78,7 @@ export async function initInstance(client, instance) {
|
||||||
info = await masto.v1.instance.fetch();
|
info = await masto.v1.instance.fetch();
|
||||||
} catch (e) {}
|
} catch (e) {}
|
||||||
}
|
}
|
||||||
|
__BENCHMARK.end('fetch-instance');
|
||||||
if (!info) return;
|
if (!info) return;
|
||||||
console.log(info);
|
console.log(info);
|
||||||
const {
|
const {
|
||||||
|
@ -111,6 +118,7 @@ export async function initInstance(client, instance) {
|
||||||
// masto.ws = streamClient;
|
// masto.ws = streamClient;
|
||||||
console.log('🎏 Streaming API client:', client);
|
console.log('🎏 Streaming API client:', client);
|
||||||
}
|
}
|
||||||
|
__BENCHMARK.end('init-instance');
|
||||||
}
|
}
|
||||||
|
|
||||||
// Get the account information and store it
|
// Get the account information and store it
|
||||||
|
@ -129,11 +137,17 @@ export async function initAccount(client, instance, accessToken, vapidKey) {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function hasPreferences() {
|
||||||
|
return !!store.account.get('preferences');
|
||||||
|
}
|
||||||
|
|
||||||
// Get preferences
|
// Get preferences
|
||||||
export async function initPreferences(client) {
|
export async function initPreferences(client) {
|
||||||
try {
|
try {
|
||||||
const { masto } = client;
|
const { masto } = client;
|
||||||
|
__BENCHMARK.start('fetch-preferences');
|
||||||
const preferences = await masto.v1.preferences.fetch();
|
const preferences = await masto.v1.preferences.fetch();
|
||||||
|
__BENCHMARK.end('fetch-preferences');
|
||||||
store.account.set('preferences', preferences);
|
store.account.set('preferences', preferences);
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
// silently fail
|
// silently fail
|
||||||
|
|
|
@ -1,16 +1,17 @@
|
||||||
import translationTargetLanguages from '../data/lingva-target-languages';
|
import translationTargetLanguages from '../data/lingva-target-languages';
|
||||||
|
|
||||||
import localeMatch from './locale-match';
|
import localeMatch from './locale-match';
|
||||||
|
import mem from './mem';
|
||||||
import states from './states';
|
import states from './states';
|
||||||
|
|
||||||
const locales = [
|
const locales = mem(() => [
|
||||||
new Intl.DateTimeFormat().resolvedOptions().locale,
|
new Intl.DateTimeFormat().resolvedOptions().locale,
|
||||||
...navigator.languages,
|
...navigator.languages,
|
||||||
];
|
]);
|
||||||
|
|
||||||
const localeTargetLanguages = () =>
|
const localeTargetLanguages = () =>
|
||||||
localeMatch(
|
localeMatch(
|
||||||
locales,
|
locales(),
|
||||||
translationTargetLanguages.map((l) => l.code.replace('_', '-')), // The underscore will fail Intl.Locale inside `match`
|
translationTargetLanguages.map((l) => l.code.replace('_', '-')), // The underscore will fail Intl.Locale inside `match`
|
||||||
'en',
|
'en',
|
||||||
);
|
);
|
||||||
|
|
|
@ -68,66 +68,105 @@ export function groupNotifications2(groupNotifications) {
|
||||||
};
|
};
|
||||||
});
|
});
|
||||||
|
|
||||||
// DISABLED FOR NOW.
|
|
||||||
// Merge favourited and reblogged of same status into a single notification
|
// Merge favourited and reblogged of same status into a single notification
|
||||||
// - new type: "favourite+reblog"
|
// - new type: "favourite+reblog"
|
||||||
// - sum numbers for `notificationsCount` and `sampleAccounts`
|
// - sum numbers for `notificationsCount` and `sampleAccounts`
|
||||||
// const mappedNotifications = {};
|
const notificationsMap = {};
|
||||||
// const newNewGroupNotifications = [];
|
const newGroupNotifications1 = [];
|
||||||
// for (let i = 0; i < newGroupNotifications.length; i++) {
|
for (let i = 0; i < newGroupNotifications.length; i++) {
|
||||||
// const gn = newGroupNotifications[i];
|
const gn = newGroupNotifications[i];
|
||||||
// const { type, status, createdAt, notificationsCount, sampleAccounts } = gn;
|
const {
|
||||||
// const date = createdAt ? new Date(createdAt).toLocaleDateString() : '';
|
type,
|
||||||
// let virtualType = type;
|
status,
|
||||||
// if (type === 'favourite' || type === 'reblog') {
|
createdAt,
|
||||||
// virtualType = 'favourite+reblog';
|
notificationsCount,
|
||||||
// }
|
sampleAccounts,
|
||||||
// const key = `${status?.id}-${virtualType}-${date}`;
|
groupKey,
|
||||||
// const mappedNotification = mappedNotifications[key];
|
} = gn;
|
||||||
// if (mappedNotification) {
|
const date = createdAt ? new Date(createdAt).toLocaleDateString() : '';
|
||||||
// const accountIDs = mappedNotification.sampleAccounts.map((a) => a.id);
|
let virtualType = type;
|
||||||
// sampleAccounts.forEach((a) => {
|
const sameCount =
|
||||||
// if (!accountIDs.includes(a.id)) {
|
notificationsCount > 0 && notificationsCount === sampleAccounts?.length;
|
||||||
// mappedNotification.sampleAccounts.push(a);
|
// if (sameCount && (type === 'favourite' || type === 'reblog')) {
|
||||||
// }
|
if (type === 'favourite' || type === 'reblog') {
|
||||||
// });
|
virtualType = 'favourite+reblog';
|
||||||
// mappedNotification.notificationsCount = Math.max(
|
}
|
||||||
// mappedNotification.notificationsCount,
|
// const key = `${status?.id}-${virtualType}-${date}-${sameCount ? 1 : 0}`;
|
||||||
// notificationsCount,
|
const key = `${status?.id}-${virtualType}-${date}`;
|
||||||
// mappedNotification.sampleAccounts.length,
|
const mappedNotification = notificationsMap[key];
|
||||||
// );
|
if (mappedNotification) {
|
||||||
// } else {
|
// Merge sampleAccounts + merge _types
|
||||||
// mappedNotifications[key] = {
|
sampleAccounts.forEach((a) => {
|
||||||
// ...gn,
|
const mappedAccount = mappedNotification.sampleAccounts.find(
|
||||||
// type: virtualType,
|
(ma) => ma.id === a.id,
|
||||||
// };
|
);
|
||||||
// newNewGroupNotifications.push(mappedNotifications[key]);
|
if (!mappedAccount) {
|
||||||
// }
|
mappedNotification.sampleAccounts.push({
|
||||||
// }
|
...a,
|
||||||
|
_types: [type],
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
mappedAccount._types.push(type);
|
||||||
|
mappedAccount._types.sort().reverse();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
// mappedNotification.notificationsCount =
|
||||||
|
// mappedNotification.sampleAccounts.length;
|
||||||
|
mappedNotification.notificationsCount = Math.min(
|
||||||
|
mappedNotification.notificationsCount,
|
||||||
|
notificationsCount,
|
||||||
|
);
|
||||||
|
mappedNotification._notificationsCount.push(notificationsCount);
|
||||||
|
mappedNotification._accounts = mappedNotification.sampleAccounts;
|
||||||
|
mappedNotification._groupKeys.push(groupKey);
|
||||||
|
} else {
|
||||||
|
const accounts = sampleAccounts.map((a) => ({
|
||||||
|
...a,
|
||||||
|
_types: [type],
|
||||||
|
}));
|
||||||
|
notificationsMap[key] = {
|
||||||
|
...gn,
|
||||||
|
sampleAccounts: accounts,
|
||||||
|
type: virtualType,
|
||||||
|
_accounts: accounts,
|
||||||
|
_groupKeys: groupKey ? [groupKey] : [],
|
||||||
|
_notificationsCount: [notificationsCount],
|
||||||
|
};
|
||||||
|
newGroupNotifications1.push(notificationsMap[key]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// 2nd pass.
|
// 2nd pass.
|
||||||
// - Group 1 account favourte/reblog multiple posts
|
// - Group 1 account favourte/reblog multiple posts
|
||||||
// - _statuses: [status, status, ...]
|
// - _statuses: [status, status, ...]
|
||||||
const notificationsMap2 = {};
|
const notificationsMap2 = {};
|
||||||
const newGroupNotifications2 = [];
|
const newGroupNotifications2 = [];
|
||||||
for (let i = 0; i < newGroupNotifications.length; i++) {
|
for (let i = 0; i < newGroupNotifications1.length; i++) {
|
||||||
const gn = newGroupNotifications[i];
|
const gn = newGroupNotifications1[i];
|
||||||
const { type, account, _accounts, sampleAccounts, createdAt } = gn;
|
const { type, account, _accounts, sampleAccounts, createdAt, groupKey } =
|
||||||
|
gn;
|
||||||
const date = createdAt ? new Date(createdAt).toLocaleDateString() : '';
|
const date = createdAt ? new Date(createdAt).toLocaleDateString() : '';
|
||||||
const hasOneAccount =
|
const hasOneAccount =
|
||||||
sampleAccounts?.length === 1 || _accounts?.length === 1;
|
sampleAccounts?.length === 1 || _accounts?.length === 1;
|
||||||
if ((type === 'favourite' || type === 'reblog') && hasOneAccount) {
|
if (
|
||||||
|
(type === 'favourite' ||
|
||||||
|
type === 'reblog' ||
|
||||||
|
type === 'favourite+reblog') &&
|
||||||
|
hasOneAccount
|
||||||
|
) {
|
||||||
const key = `${account?.id}-${type}-${date}`;
|
const key = `${account?.id}-${type}-${date}`;
|
||||||
const mappedNotification = notificationsMap2[key];
|
const mappedNotification = notificationsMap2[key];
|
||||||
if (mappedNotification) {
|
if (mappedNotification) {
|
||||||
mappedNotification._statuses.push(gn.status);
|
mappedNotification._statuses.push(gn.status);
|
||||||
mappedNotification._ids += `-${gn.id}`;
|
mappedNotification._ids += `-${gn.id}`;
|
||||||
|
mappedNotification._groupKeys.push(groupKey);
|
||||||
} else {
|
} else {
|
||||||
let n = (notificationsMap2[key] = {
|
let n = (notificationsMap2[key] = {
|
||||||
...gn,
|
...gn,
|
||||||
type,
|
type,
|
||||||
_ids: gn.id,
|
_ids: gn.id,
|
||||||
_statuses: [gn.status],
|
_statuses: [gn.status],
|
||||||
|
_groupKeys: groupKey ? [groupKey] : [],
|
||||||
});
|
});
|
||||||
newGroupNotifications2.push(n);
|
newGroupNotifications2.push(n);
|
||||||
}
|
}
|
||||||
|
@ -136,6 +175,8 @@ export function groupNotifications2(groupNotifications) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
console.log('newGroupNotifications2', newGroupNotifications2);
|
||||||
|
|
||||||
return newGroupNotifications2;
|
return newGroupNotifications2;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -3,14 +3,16 @@ import { i18n } from '@lingui/core';
|
||||||
import localeMatch from './locale-match';
|
import localeMatch from './locale-match';
|
||||||
import mem from './mem';
|
import mem from './mem';
|
||||||
|
|
||||||
const defaultLocale = new Intl.DateTimeFormat().resolvedOptions().locale;
|
const defaultLocale = mem(
|
||||||
|
() => new Intl.DateTimeFormat().resolvedOptions().locale,
|
||||||
|
);
|
||||||
|
|
||||||
const _DateTimeFormat = (opts) => {
|
const _DateTimeFormat = (opts) => {
|
||||||
const { locale, dateYear, hideTime, formatOpts } = opts || {};
|
const { locale, dateYear, hideTime, formatOpts, forceOpts } = opts || {};
|
||||||
const regionlessLocale = locale.replace(/-[a-z]+$/i, '');
|
const regionlessLocale = locale.replace(/-[a-z]+$/i, '');
|
||||||
const loc = localeMatch([regionlessLocale], [defaultLocale], locale);
|
const loc = localeMatch([regionlessLocale], [defaultLocale], locale);
|
||||||
const currentYear = new Date().getFullYear();
|
const currentYear = new Date().getFullYear();
|
||||||
const options = {
|
const options = forceOpts || {
|
||||||
// Show year if not current year
|
// Show year if not current year
|
||||||
year: dateYear === currentYear ? undefined : 'numeric',
|
year: dateYear === currentYear ? undefined : 'numeric',
|
||||||
month: 'short',
|
month: 'short',
|
||||||
|
|
Loading…
Add table
Reference in a new issue