commit
a7a3d5605b
38
.github/ISSUE_TEMPLATE/bug_report.md
vendored
Normal file
38
.github/ISSUE_TEMPLATE/bug_report.md
vendored
Normal file
|
@ -0,0 +1,38 @@
|
||||||
|
---
|
||||||
|
name: Bug report
|
||||||
|
about: Create a report to help us improve
|
||||||
|
title: ''
|
||||||
|
labels: ''
|
||||||
|
assignees: ''
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
**Describe the bug**
|
||||||
|
A clear and concise description of what the bug is.
|
||||||
|
|
||||||
|
**To Reproduce**
|
||||||
|
Steps to reproduce the behavior:
|
||||||
|
1. Go to '...'
|
||||||
|
2. Click on '....'
|
||||||
|
3. Scroll down to '....'
|
||||||
|
4. See error
|
||||||
|
|
||||||
|
**Expected behavior**
|
||||||
|
A clear and concise description of what you expected to happen.
|
||||||
|
|
||||||
|
**Screenshots**
|
||||||
|
If applicable, add screenshots to help explain your problem.
|
||||||
|
|
||||||
|
**Desktop (please complete the following information):**
|
||||||
|
- OS: [e.g. iOS]
|
||||||
|
- Browser [e.g. chrome, safari]
|
||||||
|
- Version [e.g. 22]
|
||||||
|
|
||||||
|
**Smartphone (please complete the following information):**
|
||||||
|
- Device: [e.g. iPhone6]
|
||||||
|
- OS: [e.g. iOS8.1]
|
||||||
|
- Browser [e.g. stock browser, safari]
|
||||||
|
- Version [e.g. 22]
|
||||||
|
|
||||||
|
**Additional context**
|
||||||
|
Add any other context about the problem here.
|
|
@ -79,10 +79,13 @@ It's been **more than 15 years**.
|
||||||
|
|
||||||
And here I am. Building a Mastodon web client.
|
And here I am. Building a Mastodon web client.
|
||||||
|
|
||||||
## Alternative clients
|
## Alternative web clients
|
||||||
|
|
||||||
- [Pinafore](https://pinafore.social/)
|
- [Pinafore](https://pinafore.social/)
|
||||||
|
- [Soapbox](https://fe.soapbox.pub/)
|
||||||
- [Elk](https://m.webtoo.ls/@elk)
|
- [Elk](https://m.webtoo.ls/@elk)
|
||||||
|
- [Mastodeck](https://mastodeck.com/)
|
||||||
|
-
|
||||||
- [More...](https://github.com/tleb/awesome-mastodon#clients)
|
- [More...](https://github.com/tleb/awesome-mastodon#clients)
|
||||||
|
|
||||||
## License
|
## License
|
||||||
|
|
228
package-lock.json
generated
228
package-lock.json
generated
|
@ -8,13 +8,14 @@
|
||||||
"name": "phanpy",
|
"name": "phanpy",
|
||||||
"version": "0.1.0",
|
"version": "0.1.0",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@github/relative-time-element": "~4.1.5",
|
|
||||||
"@github/text-expander-element": "~2.3.0",
|
"@github/text-expander-element": "~2.3.0",
|
||||||
|
"dayjs": "~1.11.7",
|
||||||
|
"dayjs-twitter": "~0.5.0",
|
||||||
"fast-blurhash": "~1.1.2",
|
"fast-blurhash": "~1.1.2",
|
||||||
"history": "~5.3.0",
|
"history": "~5.3.0",
|
||||||
"iconify-icon": "~1.0.2",
|
"iconify-icon": "~1.0.2",
|
||||||
"just-debounce-it": "~3.2.0",
|
"just-debounce-it": "~3.2.0",
|
||||||
"masto": "~5.1.0",
|
"masto": "~5.1.1",
|
||||||
"mem": "~9.0.2",
|
"mem": "~9.0.2",
|
||||||
"preact": "~10.11.3",
|
"preact": "~10.11.3",
|
||||||
"preact-router": "~4.1.0",
|
"preact-router": "~4.1.0",
|
||||||
|
@ -24,7 +25,7 @@
|
||||||
"swiped-events": "~1.1.7",
|
"swiped-events": "~1.1.7",
|
||||||
"toastify-js": "~1.12.0",
|
"toastify-js": "~1.12.0",
|
||||||
"use-resize-observer": "~9.1.0",
|
"use-resize-observer": "~9.1.0",
|
||||||
"valtio": "~1.8.0"
|
"valtio": "~1.8.2"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@preact/preset-vite": "~2.5.0",
|
"@preact/preset-vite": "~2.5.0",
|
||||||
|
@ -33,10 +34,11 @@
|
||||||
"postcss": "~8.4.20",
|
"postcss": "~8.4.20",
|
||||||
"postcss-dark-theme-class": "~0.7.3",
|
"postcss-dark-theme-class": "~0.7.3",
|
||||||
"twitter-text": "~3.1.0",
|
"twitter-text": "~3.1.0",
|
||||||
"vite": "~4.0.3",
|
"vite": "~4.0.4",
|
||||||
"vite-plugin-html-config": "~1.0.11",
|
"vite-plugin-html-config": "~1.0.11",
|
||||||
"vite-plugin-html-env": "~1.2.7",
|
"vite-plugin-html-env": "~1.2.7",
|
||||||
"vite-plugin-pwa": "~0.14.0",
|
"vite-plugin-pwa": "~0.14.1",
|
||||||
|
"vite-plugin-remove-console": "~1.3.0",
|
||||||
"workbox-cacheable-response": "~6.5.4",
|
"workbox-cacheable-response": "~6.5.4",
|
||||||
"workbox-expiration": "~6.5.4",
|
"workbox-expiration": "~6.5.4",
|
||||||
"workbox-routing": "~6.5.4",
|
"workbox-routing": "~6.5.4",
|
||||||
|
@ -311,7 +313,7 @@
|
||||||
"version": "7.18.6",
|
"version": "7.18.6",
|
||||||
"resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.18.6.tgz",
|
"resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.18.6.tgz",
|
||||||
"integrity": "sha512-0NFvs3VkuSYbFi1x2Vd6tKrywq+z/cLeYC/RJNFrIX/30Bf5aiGYbtvGXolEktzJH8o5E5KJ3tT+nkxuuZFVlA==",
|
"integrity": "sha512-0NFvs3VkuSYbFi1x2Vd6tKrywq+z/cLeYC/RJNFrIX/30Bf5aiGYbtvGXolEktzJH8o5E5KJ3tT+nkxuuZFVlA==",
|
||||||
"devOptional": true,
|
"dev": true,
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@babel/types": "^7.18.6"
|
"@babel/types": "^7.18.6"
|
||||||
},
|
},
|
||||||
|
@ -433,7 +435,7 @@
|
||||||
"version": "7.19.4",
|
"version": "7.19.4",
|
||||||
"resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.19.4.tgz",
|
"resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.19.4.tgz",
|
||||||
"integrity": "sha512-nHtDoQcuqFmwYNYPz3Rah5ph2p8PFeFCsZk9A/48dPc/rGocJ5J3hAAZ7pb76VWX3fZKu+uEr/FhH5jLx7umrw==",
|
"integrity": "sha512-nHtDoQcuqFmwYNYPz3Rah5ph2p8PFeFCsZk9A/48dPc/rGocJ5J3hAAZ7pb76VWX3fZKu+uEr/FhH5jLx7umrw==",
|
||||||
"devOptional": true,
|
"dev": true,
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">=6.9.0"
|
"node": ">=6.9.0"
|
||||||
}
|
}
|
||||||
|
@ -442,7 +444,7 @@
|
||||||
"version": "7.19.1",
|
"version": "7.19.1",
|
||||||
"resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.19.1.tgz",
|
"resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.19.1.tgz",
|
||||||
"integrity": "sha512-awrNfaMtnHUr653GgGEs++LlAvW6w+DcPrOliSMXWCKo597CwL5Acf/wWdNkf/tfEQE3mjkeD1YOVZOUV/od1w==",
|
"integrity": "sha512-awrNfaMtnHUr653GgGEs++LlAvW6w+DcPrOliSMXWCKo597CwL5Acf/wWdNkf/tfEQE3mjkeD1YOVZOUV/od1w==",
|
||||||
"devOptional": true,
|
"dev": true,
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">=6.9.0"
|
"node": ">=6.9.0"
|
||||||
}
|
}
|
||||||
|
@ -1694,7 +1696,7 @@
|
||||||
"version": "7.20.5",
|
"version": "7.20.5",
|
||||||
"resolved": "https://registry.npmjs.org/@babel/types/-/types-7.20.5.tgz",
|
"resolved": "https://registry.npmjs.org/@babel/types/-/types-7.20.5.tgz",
|
||||||
"integrity": "sha512-c9fst/h2/dcF7H+MJKZ2T0KjEQ8hY/BNnDk/H3XY8C4Aw/eWQXWn/lWntHF9ooUBnGmEvbfGrTgLWc+um0YDUg==",
|
"integrity": "sha512-c9fst/h2/dcF7H+MJKZ2T0KjEQ8hY/BNnDk/H3XY8C4Aw/eWQXWn/lWntHF9ooUBnGmEvbfGrTgLWc+um0YDUg==",
|
||||||
"devOptional": true,
|
"dev": true,
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@babel/helper-string-parser": "^7.19.4",
|
"@babel/helper-string-parser": "^7.19.4",
|
||||||
"@babel/helper-validator-identifier": "^7.19.1",
|
"@babel/helper-validator-identifier": "^7.19.1",
|
||||||
|
@ -2061,11 +2063,6 @@
|
||||||
"resolved": "https://registry.npmjs.org/@github/combobox-nav/-/combobox-nav-2.1.5.tgz",
|
"resolved": "https://registry.npmjs.org/@github/combobox-nav/-/combobox-nav-2.1.5.tgz",
|
||||||
"integrity": "sha512-dmG1PuppNKHnBBEcfylWDwj9SSxd/E/qd8mC1G/klQC3s7ps5q6JZ034mwkkG0LKfI+Y+UgEua/ROD776N400w=="
|
"integrity": "sha512-dmG1PuppNKHnBBEcfylWDwj9SSxd/E/qd8mC1G/klQC3s7ps5q6JZ034mwkkG0LKfI+Y+UgEua/ROD776N400w=="
|
||||||
},
|
},
|
||||||
"node_modules/@github/relative-time-element": {
|
|
||||||
"version": "4.1.5",
|
|
||||||
"resolved": "https://registry.npmjs.org/@github/relative-time-element/-/relative-time-element-4.1.5.tgz",
|
|
||||||
"integrity": "sha512-WAf1EQV5Sn6jGuAIQur/ztKlEV9R+VHDNwqEbeaOb6s9fiwM5z7+ujlWNZtgFkDp3lF0H8D/f0vdiPlfHz0ZTQ=="
|
|
||||||
},
|
|
||||||
"node_modules/@github/text-expander-element": {
|
"node_modules/@github/text-expander-element": {
|
||||||
"version": "2.3.0",
|
"version": "2.3.0",
|
||||||
"resolved": "https://registry.npmjs.org/@github/text-expander-element/-/text-expander-element-2.3.0.tgz",
|
"resolved": "https://registry.npmjs.org/@github/text-expander-element/-/text-expander-element-2.3.0.tgz",
|
||||||
|
@ -3000,6 +2997,19 @@
|
||||||
"node": ">=8"
|
"node": ">=8"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/dayjs": {
|
||||||
|
"version": "1.11.7",
|
||||||
|
"resolved": "https://registry.npmjs.org/dayjs/-/dayjs-1.11.7.tgz",
|
||||||
|
"integrity": "sha512-+Yw9U6YO5TQohxLcIkrXBeY73WP3ejHWVvx8XCk3gxvQDCTEmS48ZrSZCKciI7Bhl/uCMyxYtE9UqRILmFphkQ=="
|
||||||
|
},
|
||||||
|
"node_modules/dayjs-twitter": {
|
||||||
|
"version": "0.5.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/dayjs-twitter/-/dayjs-twitter-0.5.0.tgz",
|
||||||
|
"integrity": "sha512-SZ7qEUISstBLUXdlGAbLrwr6zfRM9kaCfbq4uVTerM/HXzuHiiGzzUqAJVhxt+3tf69E+ocmQdP6YYpOINv05w==",
|
||||||
|
"dependencies": {
|
||||||
|
"duration-js": "^4.0.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/debug": {
|
"node_modules/debug": {
|
||||||
"version": "4.3.4",
|
"version": "4.3.4",
|
||||||
"resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz",
|
"resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz",
|
||||||
|
@ -3059,6 +3069,11 @@
|
||||||
"tslib": "^2.0.3"
|
"tslib": "^2.0.3"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/duration-js": {
|
||||||
|
"version": "4.0.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/duration-js/-/duration-js-4.0.0.tgz",
|
||||||
|
"integrity": "sha512-qoXjOsH97r+NrOa6sK5V2cwBOouVG/LI9jwgwKvjVkyqGpZ72yilWjjzFJYPqqbvNZDwpRMaLEUFE+PTefvOEA=="
|
||||||
|
},
|
||||||
"node_modules/ejs": {
|
"node_modules/ejs": {
|
||||||
"version": "3.1.8",
|
"version": "3.1.8",
|
||||||
"resolved": "https://registry.npmjs.org/ejs/-/ejs-3.1.8.tgz",
|
"resolved": "https://registry.npmjs.org/ejs/-/ejs-3.1.8.tgz",
|
||||||
|
@ -3140,7 +3155,7 @@
|
||||||
"version": "0.16.7",
|
"version": "0.16.7",
|
||||||
"resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.16.7.tgz",
|
"resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.16.7.tgz",
|
||||||
"integrity": "sha512-P6OBFYFSQOGzfApqCeYKqfKRRbCIRsdppTXFo4aAvtiW3o8TTyiIplBvHJI171saPAiy3WlawJHCveJVIOIx1A==",
|
"integrity": "sha512-P6OBFYFSQOGzfApqCeYKqfKRRbCIRsdppTXFo4aAvtiW3o8TTyiIplBvHJI171saPAiy3WlawJHCveJVIOIx1A==",
|
||||||
"devOptional": true,
|
"dev": true,
|
||||||
"hasInstallScript": true,
|
"hasInstallScript": true,
|
||||||
"bin": {
|
"bin": {
|
||||||
"esbuild": "bin/esbuild"
|
"esbuild": "bin/esbuild"
|
||||||
|
@ -3368,7 +3383,7 @@
|
||||||
"version": "1.1.1",
|
"version": "1.1.1",
|
||||||
"resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.1.tgz",
|
"resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.1.tgz",
|
||||||
"integrity": "sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A==",
|
"integrity": "sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A==",
|
||||||
"devOptional": true
|
"dev": true
|
||||||
},
|
},
|
||||||
"node_modules/function.prototype.name": {
|
"node_modules/function.prototype.name": {
|
||||||
"version": "1.1.5",
|
"version": "1.1.5",
|
||||||
|
@ -3505,7 +3520,7 @@
|
||||||
"version": "1.0.3",
|
"version": "1.0.3",
|
||||||
"resolved": "https://registry.npmjs.org/has/-/has-1.0.3.tgz",
|
"resolved": "https://registry.npmjs.org/has/-/has-1.0.3.tgz",
|
||||||
"integrity": "sha512-f2dvO0VU6Oej7RkWJGrehjbzMAjFp5/VKPp5tTpWIV4JHHZK1/BxbFRtf/siA2SWTe09caDmVtYYzWEIbBS4zw==",
|
"integrity": "sha512-f2dvO0VU6Oej7RkWJGrehjbzMAjFp5/VKPp5tTpWIV4JHHZK1/BxbFRtf/siA2SWTe09caDmVtYYzWEIbBS4zw==",
|
||||||
"devOptional": true,
|
"dev": true,
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"function-bind": "^1.1.1"
|
"function-bind": "^1.1.1"
|
||||||
},
|
},
|
||||||
|
@ -3678,7 +3693,7 @@
|
||||||
"version": "2.11.0",
|
"version": "2.11.0",
|
||||||
"resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.11.0.tgz",
|
"resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.11.0.tgz",
|
||||||
"integrity": "sha512-RRjxlvLDkD1YJwDbroBHMb+cukurkDWNyHx7D3oNB5x9rb5ogcksMC5wHCadcXoo67gVr/+3GFySh3134zi6rw==",
|
"integrity": "sha512-RRjxlvLDkD1YJwDbroBHMb+cukurkDWNyHx7D3oNB5x9rb5ogcksMC5wHCadcXoo67gVr/+3GFySh3134zi6rw==",
|
||||||
"devOptional": true,
|
"dev": true,
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"has": "^1.0.3"
|
"has": "^1.0.3"
|
||||||
},
|
},
|
||||||
|
@ -4153,9 +4168,9 @@
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/masto": {
|
"node_modules/masto": {
|
||||||
"version": "5.1.0",
|
"version": "5.1.1",
|
||||||
"resolved": "https://registry.npmjs.org/masto/-/masto-5.1.0.tgz",
|
"resolved": "https://registry.npmjs.org/masto/-/masto-5.1.1.tgz",
|
||||||
"integrity": "sha512-/Rvi44BKv9AGGv08Oo63dA2WHE3kwCUtNb1/W0brK9alLaCSboOwTjoWtK46ovjmsm8TugNtKqj2lscxwcFhDQ==",
|
"integrity": "sha512-IvfdpCiayM4tM58aTf/tfkSq0MGW1kKEAwJvgVRbzmwlE4PBt1WnGvZXQg6CiLkcKBMTQaDjLR0sBaGmPrVGCQ==",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@mastojs/ponyfills": "^1.0.4",
|
"@mastojs/ponyfills": "^1.0.4",
|
||||||
"change-case": "^4.1.2",
|
"change-case": "^4.1.2",
|
||||||
|
@ -4274,7 +4289,7 @@
|
||||||
"version": "3.3.4",
|
"version": "3.3.4",
|
||||||
"resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.4.tgz",
|
"resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.4.tgz",
|
||||||
"integrity": "sha512-MqBkQh/OHTS2egovRtLk45wEyNXwF+cokD+1YPf9u5VfJiRdAiRwB2froX5Co9Rh20xs4siNPm8naNotSD6RBw==",
|
"integrity": "sha512-MqBkQh/OHTS2egovRtLk45wEyNXwF+cokD+1YPf9u5VfJiRdAiRwB2froX5Co9Rh20xs4siNPm8naNotSD6RBw==",
|
||||||
"devOptional": true,
|
"dev": true,
|
||||||
"bin": {
|
"bin": {
|
||||||
"nanoid": "bin/nanoid.cjs"
|
"nanoid": "bin/nanoid.cjs"
|
||||||
},
|
},
|
||||||
|
@ -4437,13 +4452,13 @@
|
||||||
"version": "1.0.7",
|
"version": "1.0.7",
|
||||||
"resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.7.tgz",
|
"resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.7.tgz",
|
||||||
"integrity": "sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==",
|
"integrity": "sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==",
|
||||||
"devOptional": true
|
"dev": true
|
||||||
},
|
},
|
||||||
"node_modules/picocolors": {
|
"node_modules/picocolors": {
|
||||||
"version": "1.0.0",
|
"version": "1.0.0",
|
||||||
"resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.0.0.tgz",
|
"resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.0.0.tgz",
|
||||||
"integrity": "sha512-1fygroTLlHu66zi26VoTDv8yRgm0Fccecssto+MhsZ0D/DGW2sm8E8AjW7NU5VVTRt5GxbeZ5qBuJr+HyLYkjQ==",
|
"integrity": "sha512-1fygroTLlHu66zi26VoTDv8yRgm0Fccecssto+MhsZ0D/DGW2sm8E8AjW7NU5VVTRt5GxbeZ5qBuJr+HyLYkjQ==",
|
||||||
"devOptional": true
|
"dev": true
|
||||||
},
|
},
|
||||||
"node_modules/picomatch": {
|
"node_modules/picomatch": {
|
||||||
"version": "2.3.1",
|
"version": "2.3.1",
|
||||||
|
@ -4461,7 +4476,7 @@
|
||||||
"version": "8.4.20",
|
"version": "8.4.20",
|
||||||
"resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.20.tgz",
|
"resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.20.tgz",
|
||||||
"integrity": "sha512-6Q04AXR1212bXr5fh03u8aAwbLxAQNGQ/Q1LNa0VfOI06ZAlhPHtQvE4OIdpj4kLThXilalPnmDSOD65DcHt+g==",
|
"integrity": "sha512-6Q04AXR1212bXr5fh03u8aAwbLxAQNGQ/Q1LNa0VfOI06ZAlhPHtQvE4OIdpj4kLThXilalPnmDSOD65DcHt+g==",
|
||||||
"devOptional": true,
|
"dev": true,
|
||||||
"funding": [
|
"funding": [
|
||||||
{
|
{
|
||||||
"type": "opencollective",
|
"type": "opencollective",
|
||||||
|
@ -4549,9 +4564,9 @@
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/proxy-compare": {
|
"node_modules/proxy-compare": {
|
||||||
"version": "2.3.0",
|
"version": "2.4.0",
|
||||||
"resolved": "https://registry.npmjs.org/proxy-compare/-/proxy-compare-2.3.0.tgz",
|
"resolved": "https://registry.npmjs.org/proxy-compare/-/proxy-compare-2.4.0.tgz",
|
||||||
"integrity": "sha512-c3L2CcAi7f7pvlD0D7xsF+2CQIW8C3HaYx2Pfgq8eA4HAl3GAH6/dVYsyBbYF/0XJs2ziGLrzmz5fmzPm6A0pQ=="
|
"integrity": "sha512-FD8KmQUQD6Mfpd0hywCOzcon/dbkFP8XBd9F1ycbKtvVsfv6TsFUKJ2eC0Iz2y+KzlkdT1Z8SY6ZSgm07zOyqg=="
|
||||||
},
|
},
|
||||||
"node_modules/punycode": {
|
"node_modules/punycode": {
|
||||||
"version": "2.1.1",
|
"version": "2.1.1",
|
||||||
|
@ -4739,7 +4754,7 @@
|
||||||
"version": "1.22.1",
|
"version": "1.22.1",
|
||||||
"resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.1.tgz",
|
"resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.1.tgz",
|
||||||
"integrity": "sha512-nBpuuYuY5jFsli/JIs1oldw6fOQCBioohqWZg/2hiaOybXOft4lonv85uDOKXdf8rhyK159cxU5cDcK/NKk8zw==",
|
"integrity": "sha512-nBpuuYuY5jFsli/JIs1oldw6fOQCBioohqWZg/2hiaOybXOft4lonv85uDOKXdf8rhyK159cxU5cDcK/NKk8zw==",
|
||||||
"devOptional": true,
|
"dev": true,
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"is-core-module": "^2.9.0",
|
"is-core-module": "^2.9.0",
|
||||||
"path-parse": "^1.0.7",
|
"path-parse": "^1.0.7",
|
||||||
|
@ -4766,7 +4781,7 @@
|
||||||
"version": "3.7.4",
|
"version": "3.7.4",
|
||||||
"resolved": "https://registry.npmjs.org/rollup/-/rollup-3.7.4.tgz",
|
"resolved": "https://registry.npmjs.org/rollup/-/rollup-3.7.4.tgz",
|
||||||
"integrity": "sha512-jN9rx3k5pfg9H9al0r0y1EYKSeiRANZRYX32SuNXAnKzh6cVyf4LZVto1KAuDnbHT03E1CpsgqDKaqQ8FZtgxw==",
|
"integrity": "sha512-jN9rx3k5pfg9H9al0r0y1EYKSeiRANZRYX32SuNXAnKzh6cVyf4LZVto1KAuDnbHT03E1CpsgqDKaqQ8FZtgxw==",
|
||||||
"devOptional": true,
|
"dev": true,
|
||||||
"bin": {
|
"bin": {
|
||||||
"rollup": "dist/bin/rollup"
|
"rollup": "dist/bin/rollup"
|
||||||
},
|
},
|
||||||
|
@ -4908,7 +4923,7 @@
|
||||||
"version": "1.0.2",
|
"version": "1.0.2",
|
||||||
"resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.0.2.tgz",
|
"resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.0.2.tgz",
|
||||||
"integrity": "sha512-R0XvVJ9WusLiqTCEiGCmICCMplcCkIwwR11mOSD9CR5u+IXYdiseeEuXCVAjS54zqwkLcPNnmU4OeJ6tUrWhDw==",
|
"integrity": "sha512-R0XvVJ9WusLiqTCEiGCmICCMplcCkIwwR11mOSD9CR5u+IXYdiseeEuXCVAjS54zqwkLcPNnmU4OeJ6tUrWhDw==",
|
||||||
"devOptional": true,
|
"dev": true,
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">=0.10.0"
|
"node": ">=0.10.0"
|
||||||
}
|
}
|
||||||
|
@ -5044,7 +5059,7 @@
|
||||||
"version": "1.0.0",
|
"version": "1.0.0",
|
||||||
"resolved": "https://registry.npmjs.org/supports-preserve-symlinks-flag/-/supports-preserve-symlinks-flag-1.0.0.tgz",
|
"resolved": "https://registry.npmjs.org/supports-preserve-symlinks-flag/-/supports-preserve-symlinks-flag-1.0.0.tgz",
|
||||||
"integrity": "sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==",
|
"integrity": "sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==",
|
||||||
"devOptional": true,
|
"dev": true,
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">= 0.4"
|
"node": ">= 0.4"
|
||||||
},
|
},
|
||||||
|
@ -5106,7 +5121,7 @@
|
||||||
"version": "2.0.0",
|
"version": "2.0.0",
|
||||||
"resolved": "https://registry.npmjs.org/to-fast-properties/-/to-fast-properties-2.0.0.tgz",
|
"resolved": "https://registry.npmjs.org/to-fast-properties/-/to-fast-properties-2.0.0.tgz",
|
||||||
"integrity": "sha512-/OaKK0xYrs3DmxRYqL/yDc+FxFUVYhDlXMhRmv3z915w2HF1tnN1omB354j8VUGO/hbRzyD6Y3sA7v7GS/ceog==",
|
"integrity": "sha512-/OaKK0xYrs3DmxRYqL/yDc+FxFUVYhDlXMhRmv3z915w2HF1tnN1omB354j8VUGO/hbRzyD6Y3sA7v7GS/ceog==",
|
||||||
"devOptional": true,
|
"dev": true,
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">=4"
|
"node": ">=4"
|
||||||
}
|
}
|
||||||
|
@ -5336,50 +5351,30 @@
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/valtio": {
|
"node_modules/valtio": {
|
||||||
"version": "1.8.0",
|
"version": "1.8.2",
|
||||||
"resolved": "https://registry.npmjs.org/valtio/-/valtio-1.8.0.tgz",
|
"resolved": "https://registry.npmjs.org/valtio/-/valtio-1.8.2.tgz",
|
||||||
"integrity": "sha512-lNw7wM0Qb9iBzXMju+XCn+UiIlf5uCe5pcI8XRqcvxEZ/mnRXyKXoOodPDKB8cIAVekA3Q3zWA7rboCdS4ea7g==",
|
"integrity": "sha512-ypFWPi3aY04tojWAFPbTYBDw5iFaCDbKAJ2XqhmY2XOSorNtaCZJNg++FSssv8gMJwmPXfrU/RjncQtsoOHbUg==",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"proxy-compare": "2.3.0",
|
"proxy-compare": "2.4.0",
|
||||||
"use-sync-external-store": "1.2.0"
|
"use-sync-external-store": "1.2.0"
|
||||||
},
|
},
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">=12.7.0"
|
"node": ">=12.7.0"
|
||||||
},
|
},
|
||||||
"peerDependencies": {
|
"peerDependencies": {
|
||||||
"@babel/helper-module-imports": ">=7.12",
|
"react": ">=16.8"
|
||||||
"@babel/types": ">=7.13",
|
|
||||||
"aslemammad-vite-plugin-macro": ">=1.0.0-alpha.1",
|
|
||||||
"babel-plugin-macros": ">=3.0",
|
|
||||||
"react": ">=16.8",
|
|
||||||
"vite": ">=2.8.6"
|
|
||||||
},
|
},
|
||||||
"peerDependenciesMeta": {
|
"peerDependenciesMeta": {
|
||||||
"@babel/helper-module-imports": {
|
|
||||||
"optional": true
|
|
||||||
},
|
|
||||||
"@babel/types": {
|
|
||||||
"optional": true
|
|
||||||
},
|
|
||||||
"aslemammad-vite-plugin-macro": {
|
|
||||||
"optional": true
|
|
||||||
},
|
|
||||||
"babel-plugin-macros": {
|
|
||||||
"optional": true
|
|
||||||
},
|
|
||||||
"react": {
|
"react": {
|
||||||
"optional": true
|
"optional": true
|
||||||
},
|
|
||||||
"vite": {
|
|
||||||
"optional": true
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/vite": {
|
"node_modules/vite": {
|
||||||
"version": "4.0.3",
|
"version": "4.0.4",
|
||||||
"resolved": "https://registry.npmjs.org/vite/-/vite-4.0.3.tgz",
|
"resolved": "https://registry.npmjs.org/vite/-/vite-4.0.4.tgz",
|
||||||
"integrity": "sha512-HvuNv1RdE7deIfQb8mPk51UKjqptO/4RXZ5yXSAvurd5xOckwS/gg8h9Tky3uSbnjYTgUm0hVCet1cyhKd73ZA==",
|
"integrity": "sha512-xevPU7M8FU0i/80DMR+YhgrzR5KS2ORy1B4xcX/cXLsvnUWvfHuqMmVU6N0YiJ4JWGRJJsLCgjEzKjG9/GKoSw==",
|
||||||
"devOptional": true,
|
"dev": true,
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"esbuild": "^0.16.3",
|
"esbuild": "^0.16.3",
|
||||||
"postcss": "^8.4.20",
|
"postcss": "^8.4.20",
|
||||||
|
@ -5449,9 +5444,9 @@
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/vite-plugin-pwa": {
|
"node_modules/vite-plugin-pwa": {
|
||||||
"version": "0.14.0",
|
"version": "0.14.1",
|
||||||
"resolved": "https://registry.npmjs.org/vite-plugin-pwa/-/vite-plugin-pwa-0.14.0.tgz",
|
"resolved": "https://registry.npmjs.org/vite-plugin-pwa/-/vite-plugin-pwa-0.14.1.tgz",
|
||||||
"integrity": "sha512-3wZx47PLWTckOQhc8Y6YZjAbNZ89Ovh4TdCT97MGhgl7aFd2LUekVnAmIgFwgMqyxzJ93nmkPF/ALpEW/i2qCg==",
|
"integrity": "sha512-5zx7yhQ8RTLwV71+GA9YsQQ63ALKG8XXIMqRJDdZkR8ZYftFcRgnzM7wOWmQZ/DATspyhPih5wCdcZnAIsM+mA==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@rollup/plugin-replace": "^5.0.1",
|
"@rollup/plugin-replace": "^5.0.1",
|
||||||
|
@ -5471,6 +5466,12 @@
|
||||||
"workbox-window": "^6.5.4"
|
"workbox-window": "^6.5.4"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/vite-plugin-remove-console": {
|
||||||
|
"version": "1.3.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/vite-plugin-remove-console/-/vite-plugin-remove-console-1.3.0.tgz",
|
||||||
|
"integrity": "sha512-5a/OLYB6yNRHMuHj9rBQRYMQ1NBKffxA8BaD77urUBLcGOWMHFHALjh6C26wZfZd41KytSwLp6DhvNKU78mNJg==",
|
||||||
|
"dev": true
|
||||||
|
},
|
||||||
"node_modules/webidl-conversions": {
|
"node_modules/webidl-conversions": {
|
||||||
"version": "4.0.2",
|
"version": "4.0.2",
|
||||||
"resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-4.0.2.tgz",
|
"resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-4.0.2.tgz",
|
||||||
|
@ -6068,7 +6069,7 @@
|
||||||
"version": "7.18.6",
|
"version": "7.18.6",
|
||||||
"resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.18.6.tgz",
|
"resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.18.6.tgz",
|
||||||
"integrity": "sha512-0NFvs3VkuSYbFi1x2Vd6tKrywq+z/cLeYC/RJNFrIX/30Bf5aiGYbtvGXolEktzJH8o5E5KJ3tT+nkxuuZFVlA==",
|
"integrity": "sha512-0NFvs3VkuSYbFi1x2Vd6tKrywq+z/cLeYC/RJNFrIX/30Bf5aiGYbtvGXolEktzJH8o5E5KJ3tT+nkxuuZFVlA==",
|
||||||
"devOptional": true,
|
"dev": true,
|
||||||
"requires": {
|
"requires": {
|
||||||
"@babel/types": "^7.18.6"
|
"@babel/types": "^7.18.6"
|
||||||
}
|
}
|
||||||
|
@ -6160,13 +6161,13 @@
|
||||||
"version": "7.19.4",
|
"version": "7.19.4",
|
||||||
"resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.19.4.tgz",
|
"resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.19.4.tgz",
|
||||||
"integrity": "sha512-nHtDoQcuqFmwYNYPz3Rah5ph2p8PFeFCsZk9A/48dPc/rGocJ5J3hAAZ7pb76VWX3fZKu+uEr/FhH5jLx7umrw==",
|
"integrity": "sha512-nHtDoQcuqFmwYNYPz3Rah5ph2p8PFeFCsZk9A/48dPc/rGocJ5J3hAAZ7pb76VWX3fZKu+uEr/FhH5jLx7umrw==",
|
||||||
"devOptional": true
|
"dev": true
|
||||||
},
|
},
|
||||||
"@babel/helper-validator-identifier": {
|
"@babel/helper-validator-identifier": {
|
||||||
"version": "7.19.1",
|
"version": "7.19.1",
|
||||||
"resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.19.1.tgz",
|
"resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.19.1.tgz",
|
||||||
"integrity": "sha512-awrNfaMtnHUr653GgGEs++LlAvW6w+DcPrOliSMXWCKo597CwL5Acf/wWdNkf/tfEQE3mjkeD1YOVZOUV/od1w==",
|
"integrity": "sha512-awrNfaMtnHUr653GgGEs++LlAvW6w+DcPrOliSMXWCKo597CwL5Acf/wWdNkf/tfEQE3mjkeD1YOVZOUV/od1w==",
|
||||||
"devOptional": true
|
"dev": true
|
||||||
},
|
},
|
||||||
"@babel/helper-validator-option": {
|
"@babel/helper-validator-option": {
|
||||||
"version": "7.18.6",
|
"version": "7.18.6",
|
||||||
|
@ -7010,7 +7011,7 @@
|
||||||
"version": "7.20.5",
|
"version": "7.20.5",
|
||||||
"resolved": "https://registry.npmjs.org/@babel/types/-/types-7.20.5.tgz",
|
"resolved": "https://registry.npmjs.org/@babel/types/-/types-7.20.5.tgz",
|
||||||
"integrity": "sha512-c9fst/h2/dcF7H+MJKZ2T0KjEQ8hY/BNnDk/H3XY8C4Aw/eWQXWn/lWntHF9ooUBnGmEvbfGrTgLWc+um0YDUg==",
|
"integrity": "sha512-c9fst/h2/dcF7H+MJKZ2T0KjEQ8hY/BNnDk/H3XY8C4Aw/eWQXWn/lWntHF9ooUBnGmEvbfGrTgLWc+um0YDUg==",
|
||||||
"devOptional": true,
|
"dev": true,
|
||||||
"requires": {
|
"requires": {
|
||||||
"@babel/helper-string-parser": "^7.19.4",
|
"@babel/helper-string-parser": "^7.19.4",
|
||||||
"@babel/helper-validator-identifier": "^7.19.1",
|
"@babel/helper-validator-identifier": "^7.19.1",
|
||||||
|
@ -7176,11 +7177,6 @@
|
||||||
"resolved": "https://registry.npmjs.org/@github/combobox-nav/-/combobox-nav-2.1.5.tgz",
|
"resolved": "https://registry.npmjs.org/@github/combobox-nav/-/combobox-nav-2.1.5.tgz",
|
||||||
"integrity": "sha512-dmG1PuppNKHnBBEcfylWDwj9SSxd/E/qd8mC1G/klQC3s7ps5q6JZ034mwkkG0LKfI+Y+UgEua/ROD776N400w=="
|
"integrity": "sha512-dmG1PuppNKHnBBEcfylWDwj9SSxd/E/qd8mC1G/klQC3s7ps5q6JZ034mwkkG0LKfI+Y+UgEua/ROD776N400w=="
|
||||||
},
|
},
|
||||||
"@github/relative-time-element": {
|
|
||||||
"version": "4.1.5",
|
|
||||||
"resolved": "https://registry.npmjs.org/@github/relative-time-element/-/relative-time-element-4.1.5.tgz",
|
|
||||||
"integrity": "sha512-WAf1EQV5Sn6jGuAIQur/ztKlEV9R+VHDNwqEbeaOb6s9fiwM5z7+ujlWNZtgFkDp3lF0H8D/f0vdiPlfHz0ZTQ=="
|
|
||||||
},
|
|
||||||
"@github/text-expander-element": {
|
"@github/text-expander-element": {
|
||||||
"version": "2.3.0",
|
"version": "2.3.0",
|
||||||
"resolved": "https://registry.npmjs.org/@github/text-expander-element/-/text-expander-element-2.3.0.tgz",
|
"resolved": "https://registry.npmjs.org/@github/text-expander-element/-/text-expander-element-2.3.0.tgz",
|
||||||
|
@ -7918,6 +7914,19 @@
|
||||||
"integrity": "sha512-v1plID3y9r/lPhviJ1wrXpLeyUIGAZ2SHNYTEapm7/8A9nLPoyvVp3RK/EPFqn5kEznyWgYZNsRtYYIWbuG8KA==",
|
"integrity": "sha512-v1plID3y9r/lPhviJ1wrXpLeyUIGAZ2SHNYTEapm7/8A9nLPoyvVp3RK/EPFqn5kEznyWgYZNsRtYYIWbuG8KA==",
|
||||||
"dev": true
|
"dev": true
|
||||||
},
|
},
|
||||||
|
"dayjs": {
|
||||||
|
"version": "1.11.7",
|
||||||
|
"resolved": "https://registry.npmjs.org/dayjs/-/dayjs-1.11.7.tgz",
|
||||||
|
"integrity": "sha512-+Yw9U6YO5TQohxLcIkrXBeY73WP3ejHWVvx8XCk3gxvQDCTEmS48ZrSZCKciI7Bhl/uCMyxYtE9UqRILmFphkQ=="
|
||||||
|
},
|
||||||
|
"dayjs-twitter": {
|
||||||
|
"version": "0.5.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/dayjs-twitter/-/dayjs-twitter-0.5.0.tgz",
|
||||||
|
"integrity": "sha512-SZ7qEUISstBLUXdlGAbLrwr6zfRM9kaCfbq4uVTerM/HXzuHiiGzzUqAJVhxt+3tf69E+ocmQdP6YYpOINv05w==",
|
||||||
|
"requires": {
|
||||||
|
"duration-js": "^4.0.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
"debug": {
|
"debug": {
|
||||||
"version": "4.3.4",
|
"version": "4.3.4",
|
||||||
"resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz",
|
"resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz",
|
||||||
|
@ -7957,6 +7966,11 @@
|
||||||
"tslib": "^2.0.3"
|
"tslib": "^2.0.3"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"duration-js": {
|
||||||
|
"version": "4.0.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/duration-js/-/duration-js-4.0.0.tgz",
|
||||||
|
"integrity": "sha512-qoXjOsH97r+NrOa6sK5V2cwBOouVG/LI9jwgwKvjVkyqGpZ72yilWjjzFJYPqqbvNZDwpRMaLEUFE+PTefvOEA=="
|
||||||
|
},
|
||||||
"ejs": {
|
"ejs": {
|
||||||
"version": "3.1.8",
|
"version": "3.1.8",
|
||||||
"resolved": "https://registry.npmjs.org/ejs/-/ejs-3.1.8.tgz",
|
"resolved": "https://registry.npmjs.org/ejs/-/ejs-3.1.8.tgz",
|
||||||
|
@ -8020,7 +8034,7 @@
|
||||||
"version": "0.16.7",
|
"version": "0.16.7",
|
||||||
"resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.16.7.tgz",
|
"resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.16.7.tgz",
|
||||||
"integrity": "sha512-P6OBFYFSQOGzfApqCeYKqfKRRbCIRsdppTXFo4aAvtiW3o8TTyiIplBvHJI171saPAiy3WlawJHCveJVIOIx1A==",
|
"integrity": "sha512-P6OBFYFSQOGzfApqCeYKqfKRRbCIRsdppTXFo4aAvtiW3o8TTyiIplBvHJI171saPAiy3WlawJHCveJVIOIx1A==",
|
||||||
"devOptional": true,
|
"dev": true,
|
||||||
"requires": {
|
"requires": {
|
||||||
"@esbuild/android-arm": "0.16.7",
|
"@esbuild/android-arm": "0.16.7",
|
||||||
"@esbuild/android-arm64": "0.16.7",
|
"@esbuild/android-arm64": "0.16.7",
|
||||||
|
@ -8202,7 +8216,7 @@
|
||||||
"version": "1.1.1",
|
"version": "1.1.1",
|
||||||
"resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.1.tgz",
|
"resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.1.tgz",
|
||||||
"integrity": "sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A==",
|
"integrity": "sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A==",
|
||||||
"devOptional": true
|
"dev": true
|
||||||
},
|
},
|
||||||
"function.prototype.name": {
|
"function.prototype.name": {
|
||||||
"version": "1.1.5",
|
"version": "1.1.5",
|
||||||
|
@ -8303,7 +8317,7 @@
|
||||||
"version": "1.0.3",
|
"version": "1.0.3",
|
||||||
"resolved": "https://registry.npmjs.org/has/-/has-1.0.3.tgz",
|
"resolved": "https://registry.npmjs.org/has/-/has-1.0.3.tgz",
|
||||||
"integrity": "sha512-f2dvO0VU6Oej7RkWJGrehjbzMAjFp5/VKPp5tTpWIV4JHHZK1/BxbFRtf/siA2SWTe09caDmVtYYzWEIbBS4zw==",
|
"integrity": "sha512-f2dvO0VU6Oej7RkWJGrehjbzMAjFp5/VKPp5tTpWIV4JHHZK1/BxbFRtf/siA2SWTe09caDmVtYYzWEIbBS4zw==",
|
||||||
"devOptional": true,
|
"dev": true,
|
||||||
"requires": {
|
"requires": {
|
||||||
"function-bind": "^1.1.1"
|
"function-bind": "^1.1.1"
|
||||||
}
|
}
|
||||||
|
@ -8431,7 +8445,7 @@
|
||||||
"version": "2.11.0",
|
"version": "2.11.0",
|
||||||
"resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.11.0.tgz",
|
"resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.11.0.tgz",
|
||||||
"integrity": "sha512-RRjxlvLDkD1YJwDbroBHMb+cukurkDWNyHx7D3oNB5x9rb5ogcksMC5wHCadcXoo67gVr/+3GFySh3134zi6rw==",
|
"integrity": "sha512-RRjxlvLDkD1YJwDbroBHMb+cukurkDWNyHx7D3oNB5x9rb5ogcksMC5wHCadcXoo67gVr/+3GFySh3134zi6rw==",
|
||||||
"devOptional": true,
|
"dev": true,
|
||||||
"requires": {
|
"requires": {
|
||||||
"has": "^1.0.3"
|
"has": "^1.0.3"
|
||||||
}
|
}
|
||||||
|
@ -8777,9 +8791,9 @@
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"masto": {
|
"masto": {
|
||||||
"version": "5.1.0",
|
"version": "5.1.1",
|
||||||
"resolved": "https://registry.npmjs.org/masto/-/masto-5.1.0.tgz",
|
"resolved": "https://registry.npmjs.org/masto/-/masto-5.1.1.tgz",
|
||||||
"integrity": "sha512-/Rvi44BKv9AGGv08Oo63dA2WHE3kwCUtNb1/W0brK9alLaCSboOwTjoWtK46ovjmsm8TugNtKqj2lscxwcFhDQ==",
|
"integrity": "sha512-IvfdpCiayM4tM58aTf/tfkSq0MGW1kKEAwJvgVRbzmwlE4PBt1WnGvZXQg6CiLkcKBMTQaDjLR0sBaGmPrVGCQ==",
|
||||||
"requires": {
|
"requires": {
|
||||||
"@mastojs/ponyfills": "^1.0.4",
|
"@mastojs/ponyfills": "^1.0.4",
|
||||||
"change-case": "^4.1.2",
|
"change-case": "^4.1.2",
|
||||||
|
@ -8867,7 +8881,7 @@
|
||||||
"version": "3.3.4",
|
"version": "3.3.4",
|
||||||
"resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.4.tgz",
|
"resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.4.tgz",
|
||||||
"integrity": "sha512-MqBkQh/OHTS2egovRtLk45wEyNXwF+cokD+1YPf9u5VfJiRdAiRwB2froX5Co9Rh20xs4siNPm8naNotSD6RBw==",
|
"integrity": "sha512-MqBkQh/OHTS2egovRtLk45wEyNXwF+cokD+1YPf9u5VfJiRdAiRwB2froX5Co9Rh20xs4siNPm8naNotSD6RBw==",
|
||||||
"devOptional": true
|
"dev": true
|
||||||
},
|
},
|
||||||
"no-case": {
|
"no-case": {
|
||||||
"version": "3.0.4",
|
"version": "3.0.4",
|
||||||
|
@ -8994,13 +9008,13 @@
|
||||||
"version": "1.0.7",
|
"version": "1.0.7",
|
||||||
"resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.7.tgz",
|
"resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.7.tgz",
|
||||||
"integrity": "sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==",
|
"integrity": "sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==",
|
||||||
"devOptional": true
|
"dev": true
|
||||||
},
|
},
|
||||||
"picocolors": {
|
"picocolors": {
|
||||||
"version": "1.0.0",
|
"version": "1.0.0",
|
||||||
"resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.0.0.tgz",
|
"resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.0.0.tgz",
|
||||||
"integrity": "sha512-1fygroTLlHu66zi26VoTDv8yRgm0Fccecssto+MhsZ0D/DGW2sm8E8AjW7NU5VVTRt5GxbeZ5qBuJr+HyLYkjQ==",
|
"integrity": "sha512-1fygroTLlHu66zi26VoTDv8yRgm0Fccecssto+MhsZ0D/DGW2sm8E8AjW7NU5VVTRt5GxbeZ5qBuJr+HyLYkjQ==",
|
||||||
"devOptional": true
|
"dev": true
|
||||||
},
|
},
|
||||||
"picomatch": {
|
"picomatch": {
|
||||||
"version": "2.3.1",
|
"version": "2.3.1",
|
||||||
|
@ -9012,7 +9026,7 @@
|
||||||
"version": "8.4.20",
|
"version": "8.4.20",
|
||||||
"resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.20.tgz",
|
"resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.20.tgz",
|
||||||
"integrity": "sha512-6Q04AXR1212bXr5fh03u8aAwbLxAQNGQ/Q1LNa0VfOI06ZAlhPHtQvE4OIdpj4kLThXilalPnmDSOD65DcHt+g==",
|
"integrity": "sha512-6Q04AXR1212bXr5fh03u8aAwbLxAQNGQ/Q1LNa0VfOI06ZAlhPHtQvE4OIdpj4kLThXilalPnmDSOD65DcHt+g==",
|
||||||
"devOptional": true,
|
"dev": true,
|
||||||
"requires": {
|
"requires": {
|
||||||
"nanoid": "^3.3.4",
|
"nanoid": "^3.3.4",
|
||||||
"picocolors": "^1.0.0",
|
"picocolors": "^1.0.0",
|
||||||
|
@ -9057,9 +9071,9 @@
|
||||||
"dev": true
|
"dev": true
|
||||||
},
|
},
|
||||||
"proxy-compare": {
|
"proxy-compare": {
|
||||||
"version": "2.3.0",
|
"version": "2.4.0",
|
||||||
"resolved": "https://registry.npmjs.org/proxy-compare/-/proxy-compare-2.3.0.tgz",
|
"resolved": "https://registry.npmjs.org/proxy-compare/-/proxy-compare-2.4.0.tgz",
|
||||||
"integrity": "sha512-c3L2CcAi7f7pvlD0D7xsF+2CQIW8C3HaYx2Pfgq8eA4HAl3GAH6/dVYsyBbYF/0XJs2ziGLrzmz5fmzPm6A0pQ=="
|
"integrity": "sha512-FD8KmQUQD6Mfpd0hywCOzcon/dbkFP8XBd9F1ycbKtvVsfv6TsFUKJ2eC0Iz2y+KzlkdT1Z8SY6ZSgm07zOyqg=="
|
||||||
},
|
},
|
||||||
"punycode": {
|
"punycode": {
|
||||||
"version": "2.1.1",
|
"version": "2.1.1",
|
||||||
|
@ -9200,7 +9214,7 @@
|
||||||
"version": "1.22.1",
|
"version": "1.22.1",
|
||||||
"resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.1.tgz",
|
"resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.1.tgz",
|
||||||
"integrity": "sha512-nBpuuYuY5jFsli/JIs1oldw6fOQCBioohqWZg/2hiaOybXOft4lonv85uDOKXdf8rhyK159cxU5cDcK/NKk8zw==",
|
"integrity": "sha512-nBpuuYuY5jFsli/JIs1oldw6fOQCBioohqWZg/2hiaOybXOft4lonv85uDOKXdf8rhyK159cxU5cDcK/NKk8zw==",
|
||||||
"devOptional": true,
|
"dev": true,
|
||||||
"requires": {
|
"requires": {
|
||||||
"is-core-module": "^2.9.0",
|
"is-core-module": "^2.9.0",
|
||||||
"path-parse": "^1.0.7",
|
"path-parse": "^1.0.7",
|
||||||
|
@ -9217,7 +9231,7 @@
|
||||||
"version": "3.7.4",
|
"version": "3.7.4",
|
||||||
"resolved": "https://registry.npmjs.org/rollup/-/rollup-3.7.4.tgz",
|
"resolved": "https://registry.npmjs.org/rollup/-/rollup-3.7.4.tgz",
|
||||||
"integrity": "sha512-jN9rx3k5pfg9H9al0r0y1EYKSeiRANZRYX32SuNXAnKzh6cVyf4LZVto1KAuDnbHT03E1CpsgqDKaqQ8FZtgxw==",
|
"integrity": "sha512-jN9rx3k5pfg9H9al0r0y1EYKSeiRANZRYX32SuNXAnKzh6cVyf4LZVto1KAuDnbHT03E1CpsgqDKaqQ8FZtgxw==",
|
||||||
"devOptional": true,
|
"dev": true,
|
||||||
"requires": {
|
"requires": {
|
||||||
"fsevents": "~2.3.2"
|
"fsevents": "~2.3.2"
|
||||||
}
|
}
|
||||||
|
@ -9312,7 +9326,7 @@
|
||||||
"version": "1.0.2",
|
"version": "1.0.2",
|
||||||
"resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.0.2.tgz",
|
"resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.0.2.tgz",
|
||||||
"integrity": "sha512-R0XvVJ9WusLiqTCEiGCmICCMplcCkIwwR11mOSD9CR5u+IXYdiseeEuXCVAjS54zqwkLcPNnmU4OeJ6tUrWhDw==",
|
"integrity": "sha512-R0XvVJ9WusLiqTCEiGCmICCMplcCkIwwR11mOSD9CR5u+IXYdiseeEuXCVAjS54zqwkLcPNnmU4OeJ6tUrWhDw==",
|
||||||
"devOptional": true
|
"dev": true
|
||||||
},
|
},
|
||||||
"source-map-support": {
|
"source-map-support": {
|
||||||
"version": "0.5.21",
|
"version": "0.5.21",
|
||||||
|
@ -9415,7 +9429,7 @@
|
||||||
"version": "1.0.0",
|
"version": "1.0.0",
|
||||||
"resolved": "https://registry.npmjs.org/supports-preserve-symlinks-flag/-/supports-preserve-symlinks-flag-1.0.0.tgz",
|
"resolved": "https://registry.npmjs.org/supports-preserve-symlinks-flag/-/supports-preserve-symlinks-flag-1.0.0.tgz",
|
||||||
"integrity": "sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==",
|
"integrity": "sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==",
|
||||||
"devOptional": true
|
"dev": true
|
||||||
},
|
},
|
||||||
"swiped-events": {
|
"swiped-events": {
|
||||||
"version": "1.1.7",
|
"version": "1.1.7",
|
||||||
|
@ -9456,7 +9470,7 @@
|
||||||
"version": "2.0.0",
|
"version": "2.0.0",
|
||||||
"resolved": "https://registry.npmjs.org/to-fast-properties/-/to-fast-properties-2.0.0.tgz",
|
"resolved": "https://registry.npmjs.org/to-fast-properties/-/to-fast-properties-2.0.0.tgz",
|
||||||
"integrity": "sha512-/OaKK0xYrs3DmxRYqL/yDc+FxFUVYhDlXMhRmv3z915w2HF1tnN1omB354j8VUGO/hbRzyD6Y3sA7v7GS/ceog==",
|
"integrity": "sha512-/OaKK0xYrs3DmxRYqL/yDc+FxFUVYhDlXMhRmv3z915w2HF1tnN1omB354j8VUGO/hbRzyD6Y3sA7v7GS/ceog==",
|
||||||
"devOptional": true
|
"dev": true
|
||||||
},
|
},
|
||||||
"to-regex-range": {
|
"to-regex-range": {
|
||||||
"version": "5.0.1",
|
"version": "5.0.1",
|
||||||
|
@ -9629,19 +9643,19 @@
|
||||||
"requires": {}
|
"requires": {}
|
||||||
},
|
},
|
||||||
"valtio": {
|
"valtio": {
|
||||||
"version": "1.8.0",
|
"version": "1.8.2",
|
||||||
"resolved": "https://registry.npmjs.org/valtio/-/valtio-1.8.0.tgz",
|
"resolved": "https://registry.npmjs.org/valtio/-/valtio-1.8.2.tgz",
|
||||||
"integrity": "sha512-lNw7wM0Qb9iBzXMju+XCn+UiIlf5uCe5pcI8XRqcvxEZ/mnRXyKXoOodPDKB8cIAVekA3Q3zWA7rboCdS4ea7g==",
|
"integrity": "sha512-ypFWPi3aY04tojWAFPbTYBDw5iFaCDbKAJ2XqhmY2XOSorNtaCZJNg++FSssv8gMJwmPXfrU/RjncQtsoOHbUg==",
|
||||||
"requires": {
|
"requires": {
|
||||||
"proxy-compare": "2.3.0",
|
"proxy-compare": "2.4.0",
|
||||||
"use-sync-external-store": "1.2.0"
|
"use-sync-external-store": "1.2.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"vite": {
|
"vite": {
|
||||||
"version": "4.0.3",
|
"version": "4.0.4",
|
||||||
"resolved": "https://registry.npmjs.org/vite/-/vite-4.0.3.tgz",
|
"resolved": "https://registry.npmjs.org/vite/-/vite-4.0.4.tgz",
|
||||||
"integrity": "sha512-HvuNv1RdE7deIfQb8mPk51UKjqptO/4RXZ5yXSAvurd5xOckwS/gg8h9Tky3uSbnjYTgUm0hVCet1cyhKd73ZA==",
|
"integrity": "sha512-xevPU7M8FU0i/80DMR+YhgrzR5KS2ORy1B4xcX/cXLsvnUWvfHuqMmVU6N0YiJ4JWGRJJsLCgjEzKjG9/GKoSw==",
|
||||||
"devOptional": true,
|
"dev": true,
|
||||||
"requires": {
|
"requires": {
|
||||||
"esbuild": "^0.16.3",
|
"esbuild": "^0.16.3",
|
||||||
"fsevents": "~2.3.2",
|
"fsevents": "~2.3.2",
|
||||||
|
@ -9665,9 +9679,9 @@
|
||||||
"requires": {}
|
"requires": {}
|
||||||
},
|
},
|
||||||
"vite-plugin-pwa": {
|
"vite-plugin-pwa": {
|
||||||
"version": "0.14.0",
|
"version": "0.14.1",
|
||||||
"resolved": "https://registry.npmjs.org/vite-plugin-pwa/-/vite-plugin-pwa-0.14.0.tgz",
|
"resolved": "https://registry.npmjs.org/vite-plugin-pwa/-/vite-plugin-pwa-0.14.1.tgz",
|
||||||
"integrity": "sha512-3wZx47PLWTckOQhc8Y6YZjAbNZ89Ovh4TdCT97MGhgl7aFd2LUekVnAmIgFwgMqyxzJ93nmkPF/ALpEW/i2qCg==",
|
"integrity": "sha512-5zx7yhQ8RTLwV71+GA9YsQQ63ALKG8XXIMqRJDdZkR8ZYftFcRgnzM7wOWmQZ/DATspyhPih5wCdcZnAIsM+mA==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"requires": {
|
"requires": {
|
||||||
"@rollup/plugin-replace": "^5.0.1",
|
"@rollup/plugin-replace": "^5.0.1",
|
||||||
|
@ -9679,6 +9693,12 @@
|
||||||
"workbox-window": "^6.5.4"
|
"workbox-window": "^6.5.4"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"vite-plugin-remove-console": {
|
||||||
|
"version": "1.3.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/vite-plugin-remove-console/-/vite-plugin-remove-console-1.3.0.tgz",
|
||||||
|
"integrity": "sha512-5a/OLYB6yNRHMuHj9rBQRYMQ1NBKffxA8BaD77urUBLcGOWMHFHALjh6C26wZfZd41KytSwLp6DhvNKU78mNJg==",
|
||||||
|
"dev": true
|
||||||
|
},
|
||||||
"webidl-conversions": {
|
"webidl-conversions": {
|
||||||
"version": "4.0.2",
|
"version": "4.0.2",
|
||||||
"resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-4.0.2.tgz",
|
"resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-4.0.2.tgz",
|
||||||
|
|
14
package.json
14
package.json
|
@ -7,16 +7,17 @@
|
||||||
"build": "vite build",
|
"build": "vite build",
|
||||||
"preview": "vite preview",
|
"preview": "vite preview",
|
||||||
"fetch-instances": "env $(cat .env.dev | grep -v \"#\" | xargs) node scripts/fetch-instances-list.js",
|
"fetch-instances": "env $(cat .env.dev | grep -v \"#\" | xargs) node scripts/fetch-instances-list.js",
|
||||||
"source-map-explorer": "npx source-map-explorer dist/assets/*.js"
|
"sourcemap": "npx source-map-explorer dist/assets/*.js"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@github/relative-time-element": "~4.1.5",
|
|
||||||
"@github/text-expander-element": "~2.3.0",
|
"@github/text-expander-element": "~2.3.0",
|
||||||
|
"dayjs": "~1.11.7",
|
||||||
|
"dayjs-twitter": "~0.5.0",
|
||||||
"fast-blurhash": "~1.1.2",
|
"fast-blurhash": "~1.1.2",
|
||||||
"history": "~5.3.0",
|
"history": "~5.3.0",
|
||||||
"iconify-icon": "~1.0.2",
|
"iconify-icon": "~1.0.2",
|
||||||
"just-debounce-it": "~3.2.0",
|
"just-debounce-it": "~3.2.0",
|
||||||
"masto": "~5.1.0",
|
"masto": "~5.1.1",
|
||||||
"mem": "~9.0.2",
|
"mem": "~9.0.2",
|
||||||
"preact": "~10.11.3",
|
"preact": "~10.11.3",
|
||||||
"preact-router": "~4.1.0",
|
"preact-router": "~4.1.0",
|
||||||
|
@ -26,7 +27,7 @@
|
||||||
"swiped-events": "~1.1.7",
|
"swiped-events": "~1.1.7",
|
||||||
"toastify-js": "~1.12.0",
|
"toastify-js": "~1.12.0",
|
||||||
"use-resize-observer": "~9.1.0",
|
"use-resize-observer": "~9.1.0",
|
||||||
"valtio": "~1.8.0"
|
"valtio": "~1.8.2"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@preact/preset-vite": "~2.5.0",
|
"@preact/preset-vite": "~2.5.0",
|
||||||
|
@ -35,10 +36,11 @@
|
||||||
"postcss": "~8.4.20",
|
"postcss": "~8.4.20",
|
||||||
"postcss-dark-theme-class": "~0.7.3",
|
"postcss-dark-theme-class": "~0.7.3",
|
||||||
"twitter-text": "~3.1.0",
|
"twitter-text": "~3.1.0",
|
||||||
"vite": "~4.0.3",
|
"vite": "~4.0.4",
|
||||||
"vite-plugin-html-config": "~1.0.11",
|
"vite-plugin-html-config": "~1.0.11",
|
||||||
"vite-plugin-html-env": "~1.2.7",
|
"vite-plugin-html-env": "~1.2.7",
|
||||||
"vite-plugin-pwa": "~0.14.0",
|
"vite-plugin-pwa": "~0.14.1",
|
||||||
|
"vite-plugin-remove-console": "~1.3.0",
|
||||||
"workbox-cacheable-response": "~6.5.4",
|
"workbox-cacheable-response": "~6.5.4",
|
||||||
"workbox-expiration": "~6.5.4",
|
"workbox-expiration": "~6.5.4",
|
||||||
"workbox-routing": "~6.5.4",
|
"workbox-routing": "~6.5.4",
|
||||||
|
|
36
public/sw.js
36
public/sw.js
|
@ -3,6 +3,8 @@ import { ExpirationPlugin } from 'workbox-expiration';
|
||||||
import { RegExpRoute, registerRoute, Route } from 'workbox-routing';
|
import { RegExpRoute, registerRoute, Route } from 'workbox-routing';
|
||||||
import { CacheFirst, StaleWhileRevalidate } from 'workbox-strategies';
|
import { CacheFirst, StaleWhileRevalidate } from 'workbox-strategies';
|
||||||
|
|
||||||
|
self.__WB_DISABLE_DEV_LOGS = true;
|
||||||
|
|
||||||
const imageRoute = new Route(
|
const imageRoute = new Route(
|
||||||
({ request, sameOrigin }) => {
|
({ request, sameOrigin }) => {
|
||||||
const isRemote = !sameOrigin;
|
const isRemote = !sameOrigin;
|
||||||
|
@ -44,20 +46,20 @@ const apiExtendedRoute = new RegExpRoute(
|
||||||
);
|
);
|
||||||
registerRoute(apiExtendedRoute);
|
registerRoute(apiExtendedRoute);
|
||||||
|
|
||||||
// Not caching API requests, doesn't seem to be necessary fo now
|
const apiRoute = new RegExpRoute(
|
||||||
//
|
// Matches:
|
||||||
// const apiRoute = new RegExpRoute(
|
// - statuses/:id/context - some contexts are really huge
|
||||||
// /^https?:\/\/[^\/]+\/api\//,
|
/^https?:\/\/[^\/]+\/api\/v\d+\/(statuses\/\d+\/context)/,
|
||||||
// new StaleWhileRevalidate({
|
new StaleWhileRevalidate({
|
||||||
// cacheName: 'api',
|
cacheName: 'api',
|
||||||
// plugins: [
|
plugins: [
|
||||||
// new ExpirationPlugin({
|
new ExpirationPlugin({
|
||||||
// maxAgeSeconds: 60, // 1 minute
|
maxAgeSeconds: 5 * 60, // 5 minutes
|
||||||
// }),
|
}),
|
||||||
// new CacheableResponsePlugin({
|
new CacheableResponsePlugin({
|
||||||
// statuses: [0, 200],
|
statuses: [0, 200],
|
||||||
// }),
|
}),
|
||||||
// ],
|
],
|
||||||
// }),
|
}),
|
||||||
// );
|
);
|
||||||
// registerRoute(apiRoute);
|
registerRoute(apiRoute);
|
||||||
|
|
|
@ -3,7 +3,8 @@ import fs from 'fs';
|
||||||
const { INSTANCES_SOCIAL_SECRET_TOKEN } = process.env;
|
const { INSTANCES_SOCIAL_SECRET_TOKEN } = process.env;
|
||||||
|
|
||||||
const params = new URLSearchParams({
|
const params = new URLSearchParams({
|
||||||
count: 200,
|
count: 0,
|
||||||
|
min_users: 1_000,
|
||||||
sort_by: 'active_users',
|
sort_by: 'active_users',
|
||||||
sort_order: 'desc',
|
sort_order: 'desc',
|
||||||
});
|
});
|
||||||
|
|
48
src/app.css
48
src/app.css
|
@ -90,6 +90,13 @@ a.mention span {
|
||||||
grid-template-columns: 1fr 1fr 1fr;
|
grid-template-columns: 1fr 1fr 1fr;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
user-select: none;
|
user-select: none;
|
||||||
|
transition: transform 0.5s ease-in-out;
|
||||||
|
user-select: none;
|
||||||
|
}
|
||||||
|
.deck header[hidden] {
|
||||||
|
transform: translateY(-100%);
|
||||||
|
pointer-events: none;
|
||||||
|
user-select: none;
|
||||||
}
|
}
|
||||||
.deck header > .header-side:last-of-type {
|
.deck header > .header-side:last-of-type {
|
||||||
text-align: right;
|
text-align: right;
|
||||||
|
@ -348,8 +355,10 @@ a.mention span {
|
||||||
display: block;
|
display: block;
|
||||||
text-decoration-line: none;
|
text-decoration-line: none;
|
||||||
color: inherit;
|
color: inherit;
|
||||||
|
user-select: none;
|
||||||
transition: background-color 0.2s ease-out;
|
transition: background-color 0.2s ease-out;
|
||||||
-webkit-tap-highlight-color: transparent;
|
-webkit-tap-highlight-color: transparent;
|
||||||
|
animation: appear 0.2s ease-out;
|
||||||
}
|
}
|
||||||
.status-link:is(:hover, :focus) {
|
.status-link:is(:hover, :focus) {
|
||||||
background-color: var(--link-bg-hover-color);
|
background-color: var(--link-bg-hover-color);
|
||||||
|
@ -357,7 +366,6 @@ a.mention span {
|
||||||
}
|
}
|
||||||
.status-link:active {
|
.status-link:active {
|
||||||
filter: brightness(0.95);
|
filter: brightness(0.95);
|
||||||
transform: translateY(0.5px);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.ui-state {
|
.ui-state {
|
||||||
|
@ -374,18 +382,17 @@ a.mention span {
|
||||||
z-index: 1000;
|
z-index: 1000;
|
||||||
display: flex;
|
display: flex;
|
||||||
background-color: var(--backdrop-color);
|
background-color: var(--backdrop-color);
|
||||||
|
animation: appear 0.2s ease-out;
|
||||||
}
|
}
|
||||||
.deck-backdrop > a {
|
.deck-backdrop > a {
|
||||||
flex-grow: 1;
|
flex-grow: 1;
|
||||||
backdrop-filter: saturate(0.75);
|
/* backdrop-filter: saturate(0.75); */
|
||||||
}
|
}
|
||||||
@keyframes slide-in {
|
@keyframes slide-in {
|
||||||
0% {
|
0% {
|
||||||
opacity: 0.5;
|
|
||||||
transform: translate3d(100%, 0, 0);
|
transform: translate3d(100%, 0, 0);
|
||||||
}
|
}
|
||||||
100% {
|
100% {
|
||||||
opacity: 1;
|
|
||||||
transform: translate3d(0, 0, 0);
|
transform: translate3d(0, 0, 0);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -402,7 +409,6 @@ a.mention span {
|
||||||
|
|
||||||
.decks {
|
.decks {
|
||||||
flex-grow: 1;
|
flex-grow: 1;
|
||||||
transition: transform 0.5s var(--timing-function);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.deck-close {
|
.deck-close {
|
||||||
|
@ -436,7 +442,7 @@ a.mention span {
|
||||||
}
|
}
|
||||||
.updates-button {
|
.updates-button {
|
||||||
position: absolute;
|
position: absolute;
|
||||||
z-index: 1;
|
z-index: 2;
|
||||||
animation: fade-from-top 2s ease-out;
|
animation: fade-from-top 2s ease-out;
|
||||||
left: 50%;
|
left: 50%;
|
||||||
margin-top: 8px;
|
margin-top: 8px;
|
||||||
|
@ -602,7 +608,18 @@ button.carousel-dot[disabled].active {
|
||||||
z-index: 1;
|
z-index: 1;
|
||||||
box-shadow: 0 3px 8px -1px var(--bg-faded-blur-color),
|
box-shadow: 0 3px 8px -1px var(--bg-faded-blur-color),
|
||||||
0 10px 36px -4px var(--button-bg-blur-color);
|
0 10px 36px -4px var(--button-bg-blur-color);
|
||||||
transition: background-color 0.2s ease-in-out;
|
transition: all 0.3s ease-in-out;
|
||||||
|
}
|
||||||
|
#compose-button[hidden] {
|
||||||
|
transform: translateY(200%);
|
||||||
|
pointer-events: none;
|
||||||
|
user-select: none;
|
||||||
|
}
|
||||||
|
#compose-button .icon {
|
||||||
|
transition: transform 0.3s ease-in-out;
|
||||||
|
}
|
||||||
|
#compose-button[hidden] .icon {
|
||||||
|
transform: rotate(90deg);
|
||||||
}
|
}
|
||||||
#compose-button:is(:hover, :focus) {
|
#compose-button:is(:hover, :focus) {
|
||||||
background-color: var(--button-bg-color);
|
background-color: var(--button-bg-color);
|
||||||
|
@ -610,7 +627,6 @@ button.carousel-dot[disabled].active {
|
||||||
}
|
}
|
||||||
#compose-button:active {
|
#compose-button:active {
|
||||||
filter: brightness(0.75);
|
filter: brightness(0.75);
|
||||||
transform: translateY(1px);
|
|
||||||
}
|
}
|
||||||
#compose-button .icon {
|
#compose-button .icon {
|
||||||
filter: drop-shadow(0 1px 2px var(--button-bg-color));
|
filter: drop-shadow(0 1px 2px var(--button-bg-color));
|
||||||
|
@ -637,6 +653,10 @@ button.carousel-dot[disabled].active {
|
||||||
padding: 16px 16px 8px;
|
padding: 16px 16px 8px;
|
||||||
padding-left: max(16px, env(safe-area-inset-left));
|
padding-left: max(16px, env(safe-area-inset-left));
|
||||||
padding-right: max(16px, env(safe-area-inset-right));
|
padding-right: max(16px, env(safe-area-inset-right));
|
||||||
|
user-select: none;
|
||||||
|
}
|
||||||
|
.sheet header :is(h1, h2, h3) {
|
||||||
|
margin: 0;
|
||||||
}
|
}
|
||||||
.sheet main {
|
.sheet main {
|
||||||
overflow: auto;
|
overflow: auto;
|
||||||
|
@ -800,6 +820,14 @@ meter.donut:is(.danger, .explode):after {
|
||||||
filter: brightness(0.8);
|
filter: brightness(0.8);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* AVATARS STACK */
|
||||||
|
|
||||||
|
.avatars-stack {
|
||||||
|
display: flex;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
gap: 4px;
|
||||||
|
}
|
||||||
|
|
||||||
@media (min-width: 40em) {
|
@media (min-width: 40em) {
|
||||||
html,
|
html,
|
||||||
body {
|
body {
|
||||||
|
@ -808,7 +836,11 @@ meter.donut:is(.danger, .explode):after {
|
||||||
#app {
|
#app {
|
||||||
display: flex;
|
display: flex;
|
||||||
}
|
}
|
||||||
|
.decks {
|
||||||
|
transition: transform 0.4s var(--timing-function);
|
||||||
|
}
|
||||||
.decks:has(~ .deck-backdrop) {
|
.decks:has(~ .deck-backdrop) {
|
||||||
|
transition: transform 0.4s ease-out;
|
||||||
transform: translate3d(-5vw, 0, 0);
|
transform: translate3d(-5vw, 0, 0);
|
||||||
}
|
}
|
||||||
.deck-backdrop .deck {
|
.deck-backdrop .deck {
|
||||||
|
|
50
src/app.jsx
50
src/app.jsx
|
@ -82,7 +82,7 @@ function App() {
|
||||||
let account = accounts.find((a) => a.info.id === mastoAccount.id);
|
let account = accounts.find((a) => a.info.id === mastoAccount.id);
|
||||||
if (account) {
|
if (account) {
|
||||||
account.info = mastoAccount;
|
account.info = mastoAccount;
|
||||||
account.instanceURL = instanceURL;
|
account.instanceURL = instanceURL.toLowerCase();
|
||||||
account.accessToken = accessToken;
|
account.accessToken = accessToken;
|
||||||
} else {
|
} else {
|
||||||
account = {
|
account = {
|
||||||
|
@ -166,7 +166,7 @@ function App() {
|
||||||
console.log(info);
|
console.log(info);
|
||||||
const { uri, domain } = info;
|
const { uri, domain } = info;
|
||||||
const instances = store.local.getJSON('instances') || {};
|
const instances = store.local.getJSON('instances') || {};
|
||||||
instances[domain || uri] = info;
|
instances[(domain || uri).toLowerCase()] = info;
|
||||||
store.local.setJSON('instances', instances);
|
store.local.setJSON('instances', instances);
|
||||||
})();
|
})();
|
||||||
});
|
});
|
||||||
|
@ -177,31 +177,12 @@ function App() {
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
{isLoggedIn && currentDeck && (
|
{isLoggedIn && currentDeck && (
|
||||||
<>
|
<div class="decks">
|
||||||
<button
|
{/* Home will never be unmounted */}
|
||||||
type="button"
|
<Home hidden={currentDeck !== 'home'} />
|
||||||
id="compose-button"
|
{/* Notifications can be unmounted */}
|
||||||
onClick={(e) => {
|
{currentDeck === 'notifications' && <Notifications />}
|
||||||
if (e.shiftKey) {
|
</div>
|
||||||
const newWin = openCompose();
|
|
||||||
if (!newWin) {
|
|
||||||
alert('Looks like your browser is blocking popups.');
|
|
||||||
states.showCompose = true;
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
states.showCompose = true;
|
|
||||||
}
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<Icon icon="quill" size="xxl" alt="Compose" />
|
|
||||||
</button>
|
|
||||||
<div class="decks">
|
|
||||||
{/* Home will never be unmounted */}
|
|
||||||
<Home hidden={currentDeck !== 'home'} />
|
|
||||||
{/* Notifications can be unmounted */}
|
|
||||||
{currentDeck === 'notifications' && <Notifications />}
|
|
||||||
</div>
|
|
||||||
</>
|
|
||||||
)}
|
)}
|
||||||
{!isLoggedIn && uiState === 'loading' && <Loader />}
|
{!isLoggedIn && uiState === 'loading' && <Loader />}
|
||||||
<Router
|
<Router
|
||||||
|
@ -237,13 +218,22 @@ function App() {
|
||||||
replyToStatus={
|
replyToStatus={
|
||||||
typeof snapStates.showCompose !== 'boolean'
|
typeof snapStates.showCompose !== 'boolean'
|
||||||
? snapStates.showCompose.replyToStatus
|
? snapStates.showCompose.replyToStatus
|
||||||
: null
|
: window.__COMPOSE__?.replyToStatus || null
|
||||||
|
}
|
||||||
|
editStatus={
|
||||||
|
states.showCompose?.editStatus ||
|
||||||
|
window.__COMPOSE__?.editStatus ||
|
||||||
|
null
|
||||||
|
}
|
||||||
|
draftStatus={
|
||||||
|
states.showCompose?.draftStatus ||
|
||||||
|
window.__COMPOSE__?.draftStatus ||
|
||||||
|
null
|
||||||
}
|
}
|
||||||
editStatus={states.showCompose?.editStatus || null}
|
|
||||||
draftStatus={states.showCompose?.draftStatus || null}
|
|
||||||
onClose={(results) => {
|
onClose={(results) => {
|
||||||
const { newStatus } = results || {};
|
const { newStatus } = results || {};
|
||||||
states.showCompose = false;
|
states.showCompose = false;
|
||||||
|
window.__COMPOSE__ = null;
|
||||||
if (newStatus) {
|
if (newStatus) {
|
||||||
states.reloadStatusPage++;
|
states.reloadStatusPage++;
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
|
|
|
@ -197,17 +197,19 @@ function Account({ account }) {
|
||||||
{relationshipUIState !== 'loading' && relationship && (
|
{relationshipUIState !== 'loading' && relationship && (
|
||||||
<button
|
<button
|
||||||
type="button"
|
type="button"
|
||||||
class={`${following ? 'light' : ''} swap`}
|
class={`${following || requested ? 'light swap' : ''}`}
|
||||||
data-swap-state={following ? 'danger' : ''}
|
data-swap-state={following || requested ? 'danger' : ''}
|
||||||
disabled={relationshipUIState === 'loading'}
|
disabled={relationshipUIState === 'loading'}
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
setRelationshipUIState('loading');
|
setRelationshipUIState('loading');
|
||||||
(async () => {
|
(async () => {
|
||||||
try {
|
try {
|
||||||
let newRelationship;
|
let newRelationship;
|
||||||
if (following) {
|
if (following || requested) {
|
||||||
const yes = confirm(
|
const yes = confirm(
|
||||||
'Are you sure that you want to unfollow this account?',
|
requested
|
||||||
|
? 'Are you sure that you want to withdraw follow request?'
|
||||||
|
: 'Are you sure that you want to unfollow this account?',
|
||||||
);
|
);
|
||||||
if (yes) {
|
if (yes) {
|
||||||
newRelationship = await masto.v1.accounts.unfollow(
|
newRelationship = await masto.v1.accounts.unfollow(
|
||||||
|
@ -231,10 +233,18 @@ function Account({ account }) {
|
||||||
<span>Following</span>
|
<span>Following</span>
|
||||||
<span>Unfollow…</span>
|
<span>Unfollow…</span>
|
||||||
</>
|
</>
|
||||||
|
) : requested ? (
|
||||||
|
<>
|
||||||
|
<span>Requested</span>
|
||||||
|
<span>Withdraw…</span>
|
||||||
|
</>
|
||||||
|
) : locked ? (
|
||||||
|
<>
|
||||||
|
<Icon icon="lock" /> <span>Follow</span>
|
||||||
|
</>
|
||||||
) : (
|
) : (
|
||||||
'Follow'
|
'Follow'
|
||||||
)}
|
)}
|
||||||
{/* {following ? 'Unfollow…' : 'Follow'} */}
|
|
||||||
</button>
|
</button>
|
||||||
)}
|
)}
|
||||||
</p>
|
</p>
|
||||||
|
|
|
@ -28,7 +28,7 @@
|
||||||
max-width: 100%;
|
max-width: 100%;
|
||||||
height: 4em;
|
height: 4em;
|
||||||
min-height: 4em;
|
min-height: 4em;
|
||||||
max-height: 10em;
|
max-height: 50vh;
|
||||||
resize: vertical;
|
resize: vertical;
|
||||||
line-height: 1.4;
|
line-height: 1.4;
|
||||||
}
|
}
|
||||||
|
@ -255,16 +255,34 @@
|
||||||
align-items: stretch;
|
align-items: stretch;
|
||||||
}
|
}
|
||||||
#compose-container .media-preview {
|
#compose-container .media-preview {
|
||||||
flex-shrink: 1;
|
flex-shrink: 0;
|
||||||
|
border: 1px solid var(--outline-color);
|
||||||
|
border-radius: 4px;
|
||||||
|
overflow: hidden;
|
||||||
|
width: 80px;
|
||||||
|
height: 80px;
|
||||||
|
/* checkerboard background */
|
||||||
|
background-image: linear-gradient(
|
||||||
|
45deg,
|
||||||
|
var(--img-bg-color) 25%,
|
||||||
|
transparent 25%
|
||||||
|
),
|
||||||
|
linear-gradient(-45deg, var(--img-bg-color) 25%, transparent 25%),
|
||||||
|
linear-gradient(45deg, transparent 75%, var(--img-bg-color) 75%),
|
||||||
|
linear-gradient(-45deg, transparent 75%, var(--img-bg-color) 75%);
|
||||||
|
background-size: 10px 10px;
|
||||||
|
background-position: 0 0, 0 5px, 5px -5px, -5px 0px;
|
||||||
}
|
}
|
||||||
#compose-container .media-preview > * {
|
#compose-container .media-preview > * {
|
||||||
min-width: 80px;
|
width: 80px;
|
||||||
width: 80px !important;
|
|
||||||
height: 80px;
|
height: 80px;
|
||||||
object-fit: contain;
|
object-fit: contain;
|
||||||
background-color: var(--img-bg-color);
|
vertical-align: middle;
|
||||||
border-radius: 8px;
|
pointer-events: none;
|
||||||
border: 1px solid var(--outline-color);
|
}
|
||||||
|
#compose-container .media-preview:hover {
|
||||||
|
box-shadow: 0 0 0 2px var(--link-light-color);
|
||||||
|
cursor: pointer;
|
||||||
}
|
}
|
||||||
#compose-container .media-attachment textarea {
|
#compose-container .media-attachment textarea {
|
||||||
height: 80px;
|
height: 80px;
|
||||||
|
@ -389,3 +407,39 @@
|
||||||
display: none;
|
display: none;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#media-sheet main {
|
||||||
|
padding-top: 8px;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
flex: 1;
|
||||||
|
}
|
||||||
|
#media-sheet textarea {
|
||||||
|
width: 100%;
|
||||||
|
height: 10em;
|
||||||
|
margin-top: 8px;
|
||||||
|
}
|
||||||
|
#media-sheet .media-preview {
|
||||||
|
border: 2px solid var(--outline-color);
|
||||||
|
border-radius: 8px;
|
||||||
|
overflow: hidden;
|
||||||
|
box-shadow: 0 2px 16px var(--img-bg-color);
|
||||||
|
/* checkerboard background */
|
||||||
|
background-image: linear-gradient(
|
||||||
|
45deg,
|
||||||
|
var(--img-bg-color) 25%,
|
||||||
|
transparent 25%
|
||||||
|
),
|
||||||
|
linear-gradient(-45deg, var(--img-bg-color) 25%, transparent 25%),
|
||||||
|
linear-gradient(45deg, transparent 75%, var(--img-bg-color) 75%),
|
||||||
|
linear-gradient(-45deg, transparent 75%, var(--img-bg-color) 75%);
|
||||||
|
background-size: 20px 20px;
|
||||||
|
background-position: 0 0, 0 10px, 10px -10px, -10px 0px;
|
||||||
|
}
|
||||||
|
#media-sheet .media-preview > * {
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
max-height: 50vh;
|
||||||
|
object-fit: contain;
|
||||||
|
vertical-align: middle;
|
||||||
|
}
|
||||||
|
|
|
@ -1,19 +1,25 @@
|
||||||
import './compose.css';
|
import './compose.css';
|
||||||
|
|
||||||
import '@github/text-expander-element';
|
import '@github/text-expander-element';
|
||||||
|
import { forwardRef } from 'preact/compat';
|
||||||
import { useEffect, useMemo, useRef, useState } from 'preact/hooks';
|
import { useEffect, useMemo, useRef, useState } from 'preact/hooks';
|
||||||
|
import { useHotkeys } from 'react-hotkeys-hook';
|
||||||
import stringLength from 'string-length';
|
import stringLength from 'string-length';
|
||||||
|
import { useSnapshot } from 'valtio';
|
||||||
|
|
||||||
import supportedLanguages from '../data/status-supported-languages';
|
import supportedLanguages from '../data/status-supported-languages';
|
||||||
import urlRegex from '../data/url-regex';
|
import urlRegex from '../data/url-regex';
|
||||||
import emojifyText from '../utils/emojify-text';
|
import emojifyText from '../utils/emojify-text';
|
||||||
import openCompose from '../utils/open-compose';
|
import openCompose from '../utils/open-compose';
|
||||||
|
import states from '../utils/states';
|
||||||
import store from '../utils/store';
|
import store from '../utils/store';
|
||||||
|
import useDebouncedCallback from '../utils/useDebouncedCallback';
|
||||||
import visibilityIconsMap from '../utils/visibility-icons-map';
|
import visibilityIconsMap from '../utils/visibility-icons-map';
|
||||||
|
|
||||||
import Avatar from './avatar';
|
import Avatar from './avatar';
|
||||||
import Icon from './icon';
|
import Icon from './icon';
|
||||||
import Loader from './loader';
|
import Loader from './loader';
|
||||||
|
import Modal from './modal';
|
||||||
import Status from './status';
|
import Status from './status';
|
||||||
|
|
||||||
const supportedLanguagesMap = supportedLanguages.reduce((acc, l) => {
|
const supportedLanguagesMap = supportedLanguages.reduce((acc, l) => {
|
||||||
|
@ -53,6 +59,16 @@ menu.className = 'text-expander-menu';
|
||||||
|
|
||||||
const DEFAULT_LANG = 'en';
|
const DEFAULT_LANG = 'en';
|
||||||
|
|
||||||
|
// https://github.com/mastodon/mastodon/blob/c4a429ed47e85a6bbf0d470a41cc2f64cf120c19/app/javascript/mastodon/features/compose/util/counter.js
|
||||||
|
const urlRegexObj = new RegExp(urlRegex.source, urlRegex.flags);
|
||||||
|
const usernameRegex = /(^|[^\/\w])@(([a-z0-9_]+)@[a-z0-9\.\-]+[a-z0-9]+)/gi;
|
||||||
|
const urlPlaceholder = '$2xxxxxxxxxxxxxxxxxxxxxxx';
|
||||||
|
function countableText(inputText) {
|
||||||
|
return inputText
|
||||||
|
.replace(urlRegexObj, urlPlaceholder)
|
||||||
|
.replace(usernameRegex, '$1@$3');
|
||||||
|
}
|
||||||
|
|
||||||
function Compose({
|
function Compose({
|
||||||
onClose,
|
onClose,
|
||||||
replyToStatus,
|
replyToStatus,
|
||||||
|
@ -61,6 +77,7 @@ function Compose({
|
||||||
standalone,
|
standalone,
|
||||||
hasOpener,
|
hasOpener,
|
||||||
}) {
|
}) {
|
||||||
|
console.warn('RENDER COMPOSER');
|
||||||
const [uiState, setUIState] = useState('default');
|
const [uiState, setUIState] = useState('default');
|
||||||
|
|
||||||
const accounts = store.local.getJSON('accounts');
|
const accounts = store.local.getJSON('accounts');
|
||||||
|
@ -72,9 +89,9 @@ function Compose({
|
||||||
const configuration = useMemo(() => {
|
const configuration = useMemo(() => {
|
||||||
try {
|
try {
|
||||||
const instances = store.local.getJSON('instances');
|
const instances = store.local.getJSON('instances');
|
||||||
const currentInstance = accounts.find(
|
const currentInstance = accounts
|
||||||
(a) => a.info.id === currentAccount,
|
.find((a) => a.info.id === currentAccount)
|
||||||
).instanceURL;
|
.instanceURL.toLowerCase();
|
||||||
const config = instances[currentInstance].configuration;
|
const config = instances[currentInstance].configuration;
|
||||||
console.log(config);
|
console.log(config);
|
||||||
return config;
|
return config;
|
||||||
|
@ -222,130 +239,6 @@ function Compose({
|
||||||
}
|
}
|
||||||
}, [draftStatus, editStatus, replyToStatus]);
|
}, [draftStatus, editStatus, replyToStatus]);
|
||||||
|
|
||||||
const textExpanderRef = useRef();
|
|
||||||
const textExpanderTextRef = useRef('');
|
|
||||||
useEffect(() => {
|
|
||||||
if (textExpanderRef.current) {
|
|
||||||
const handleChange = (e) => {
|
|
||||||
// console.log('text-expander-change', e);
|
|
||||||
const { key, provide, text } = e.detail;
|
|
||||||
textExpanderTextRef.current = text;
|
|
||||||
|
|
||||||
if (text === '') {
|
|
||||||
provide(
|
|
||||||
Promise.resolve({
|
|
||||||
matched: false,
|
|
||||||
}),
|
|
||||||
);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (key === ':') {
|
|
||||||
// const emojis = customEmojis.current.filter((emoji) =>
|
|
||||||
// emoji.shortcode.startsWith(text),
|
|
||||||
// );
|
|
||||||
const emojis = filterShortcodes(customEmojis.current, text);
|
|
||||||
let html = '';
|
|
||||||
emojis.forEach((emoji) => {
|
|
||||||
const { shortcode, url } = emoji;
|
|
||||||
html += `
|
|
||||||
<li role="option" data-value="${encodeHTML(shortcode)}">
|
|
||||||
<img src="${encodeHTML(
|
|
||||||
url,
|
|
||||||
)}" width="16" height="16" alt="" loading="lazy" />
|
|
||||||
:${encodeHTML(shortcode)}:
|
|
||||||
</li>`;
|
|
||||||
});
|
|
||||||
// console.log({ emojis, html });
|
|
||||||
menu.innerHTML = html;
|
|
||||||
provide(
|
|
||||||
Promise.resolve({
|
|
||||||
matched: emojis.length > 0,
|
|
||||||
fragment: menu,
|
|
||||||
}),
|
|
||||||
);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
const type = {
|
|
||||||
'@': 'accounts',
|
|
||||||
'#': 'hashtags',
|
|
||||||
}[key];
|
|
||||||
provide(
|
|
||||||
new Promise((resolve) => {
|
|
||||||
const searchResults = masto.v2.search({
|
|
||||||
type,
|
|
||||||
q: text,
|
|
||||||
limit: 5,
|
|
||||||
});
|
|
||||||
searchResults.then((value) => {
|
|
||||||
if (text !== textExpanderTextRef.current) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
console.log({ value, type, v: value[type] });
|
|
||||||
const results = value[type];
|
|
||||||
console.log('RESULTS', value, results);
|
|
||||||
let html = '';
|
|
||||||
results.forEach((result) => {
|
|
||||||
const {
|
|
||||||
name,
|
|
||||||
avatarStatic,
|
|
||||||
displayName,
|
|
||||||
username,
|
|
||||||
acct,
|
|
||||||
emojis,
|
|
||||||
} = result;
|
|
||||||
const displayNameWithEmoji = emojifyText(displayName, emojis);
|
|
||||||
// const item = menuItem.cloneNode();
|
|
||||||
if (acct) {
|
|
||||||
html += `
|
|
||||||
<li role="option" data-value="${encodeHTML(acct)}">
|
|
||||||
<span class="avatar">
|
|
||||||
<img src="${encodeHTML(
|
|
||||||
avatarStatic,
|
|
||||||
)}" width="16" height="16" alt="" loading="lazy" />
|
|
||||||
</span>
|
|
||||||
<span>
|
|
||||||
<b>${displayNameWithEmoji || username}</b>
|
|
||||||
<br>@${encodeHTML(acct)}
|
|
||||||
</span>
|
|
||||||
</li>
|
|
||||||
`;
|
|
||||||
} else {
|
|
||||||
html += `
|
|
||||||
<li role="option" data-value="${encodeHTML(name)}">
|
|
||||||
<span>#<b>${encodeHTML(name)}</b></span>
|
|
||||||
</li>
|
|
||||||
`;
|
|
||||||
}
|
|
||||||
menu.innerHTML = html;
|
|
||||||
});
|
|
||||||
console.log('MENU', results, menu);
|
|
||||||
resolve({
|
|
||||||
matched: results.length > 0,
|
|
||||||
fragment: menu,
|
|
||||||
});
|
|
||||||
});
|
|
||||||
}),
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
textExpanderRef.current.addEventListener(
|
|
||||||
'text-expander-change',
|
|
||||||
handleChange,
|
|
||||||
);
|
|
||||||
|
|
||||||
textExpanderRef.current.addEventListener('text-expander-value', (e) => {
|
|
||||||
const { key, item } = e.detail;
|
|
||||||
if (key === ':') {
|
|
||||||
e.detail.value = `:${item.dataset.value}:`;
|
|
||||||
} else {
|
|
||||||
e.detail.value = `${key}${item.dataset.value}`;
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}, []);
|
|
||||||
|
|
||||||
const formRef = useRef();
|
const formRef = useRef();
|
||||||
|
|
||||||
const beforeUnloadCopy =
|
const beforeUnloadCopy =
|
||||||
|
@ -431,19 +324,28 @@ function Compose({
|
||||||
});
|
});
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
const [charCount, setCharCount] = useState(
|
|
||||||
textareaRef.current?.value?.length +
|
|
||||||
spoilerTextRef.current?.value?.length || 0,
|
|
||||||
);
|
|
||||||
const leftChars = maxCharacters - charCount;
|
|
||||||
const getCharCount = () => {
|
const getCharCount = () => {
|
||||||
const { value } = textareaRef.current;
|
const { value } = textareaRef.current;
|
||||||
const { value: spoilerText } = spoilerTextRef.current;
|
const { value: spoilerText } = spoilerTextRef.current;
|
||||||
return stringLength(countableText(value)) + stringLength(spoilerText);
|
return stringLength(countableText(value)) + stringLength(spoilerText);
|
||||||
};
|
};
|
||||||
const updateCharCount = () => {
|
const updateCharCount = () => {
|
||||||
setCharCount(getCharCount());
|
const count = getCharCount();
|
||||||
|
states.composerCharacterCount = count;
|
||||||
};
|
};
|
||||||
|
useEffect(updateCharCount, []);
|
||||||
|
|
||||||
|
useHotkeys(
|
||||||
|
'esc',
|
||||||
|
() => {
|
||||||
|
if (!standalone && confirmClose()) {
|
||||||
|
onClose();
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
enableOnFormTags: true,
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div id="compose-container" class={standalone ? 'standalone' : ''}>
|
<div id="compose-container" class={standalone ? 'standalone' : ''}>
|
||||||
|
@ -463,21 +365,21 @@ function Compose({
|
||||||
disabled={uiState === 'loading'}
|
disabled={uiState === 'loading'}
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
// If there are non-ID media attachments (not yet uploaded), show confirmation dialog because they are not going to be passed to the new window
|
// If there are non-ID media attachments (not yet uploaded), show confirmation dialog because they are not going to be passed to the new window
|
||||||
const containNonIDMediaAttachments =
|
// const containNonIDMediaAttachments =
|
||||||
mediaAttachments.length > 0 &&
|
// mediaAttachments.length > 0 &&
|
||||||
mediaAttachments.some((media) => !media.id);
|
// mediaAttachments.some((media) => !media.id);
|
||||||
if (containNonIDMediaAttachments) {
|
// if (containNonIDMediaAttachments) {
|
||||||
const yes = confirm(
|
// const yes = confirm(
|
||||||
'You have media attachments that are not yet uploaded. Opening a new window will discard them and you will need to re-attach them. Are you sure you want to continue?',
|
// 'You have media attachments that are not yet uploaded. Opening a new window will discard them and you will need to re-attach them. Are you sure you want to continue?',
|
||||||
);
|
// );
|
||||||
if (!yes) {
|
// if (!yes) {
|
||||||
return;
|
// return;
|
||||||
}
|
// }
|
||||||
}
|
// }
|
||||||
|
|
||||||
const mediaAttachmentsWithIDs = mediaAttachments.filter(
|
// const mediaAttachmentsWithIDs = mediaAttachments.filter(
|
||||||
(media) => media.id,
|
// (media) => media.id,
|
||||||
);
|
// );
|
||||||
|
|
||||||
const newWin = openCompose({
|
const newWin = openCompose({
|
||||||
editStatus,
|
editStatus,
|
||||||
|
@ -489,7 +391,7 @@ function Compose({
|
||||||
language,
|
language,
|
||||||
sensitive,
|
sensitive,
|
||||||
poll,
|
poll,
|
||||||
mediaAttachments: mediaAttachmentsWithIDs,
|
mediaAttachments,
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -524,17 +426,17 @@ function Compose({
|
||||||
disabled={uiState === 'loading'}
|
disabled={uiState === 'loading'}
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
// If there are non-ID media attachments (not yet uploaded), show confirmation dialog because they are not going to be passed to the new window
|
// If there are non-ID media attachments (not yet uploaded), show confirmation dialog because they are not going to be passed to the new window
|
||||||
const containNonIDMediaAttachments =
|
// const containNonIDMediaAttachments =
|
||||||
mediaAttachments.length > 0 &&
|
// mediaAttachments.length > 0 &&
|
||||||
mediaAttachments.some((media) => !media.id);
|
// mediaAttachments.some((media) => !media.id);
|
||||||
if (containNonIDMediaAttachments) {
|
// if (containNonIDMediaAttachments) {
|
||||||
const yes = confirm(
|
// const yes = confirm(
|
||||||
'You have media attachments that are not yet uploaded. Opening a new window will discard them and you will need to re-attach them. Are you sure you want to continue?',
|
// 'You have media attachments that are not yet uploaded. Opening a new window will discard them and you will need to re-attach them. Are you sure you want to continue?',
|
||||||
);
|
// );
|
||||||
if (!yes) {
|
// if (!yes) {
|
||||||
return;
|
// return;
|
||||||
}
|
// }
|
||||||
}
|
// }
|
||||||
|
|
||||||
if (!window.opener) {
|
if (!window.opener) {
|
||||||
alert('Looks like you closed the parent window.');
|
alert('Looks like you closed the parent window.');
|
||||||
|
@ -548,13 +450,13 @@ function Compose({
|
||||||
if (!yes) return;
|
if (!yes) return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const mediaAttachmentsWithIDs = mediaAttachments.filter(
|
// const mediaAttachmentsWithIDs = mediaAttachments.filter(
|
||||||
(media) => media.id,
|
// (media) => media.id,
|
||||||
);
|
// );
|
||||||
|
|
||||||
onClose({
|
onClose({
|
||||||
fn: () => {
|
fn: () => {
|
||||||
window.opener.__STATES__.showCompose = {
|
const passData = {
|
||||||
editStatus,
|
editStatus,
|
||||||
replyToStatus,
|
replyToStatus,
|
||||||
draftStatus: {
|
draftStatus: {
|
||||||
|
@ -564,9 +466,11 @@ function Compose({
|
||||||
language,
|
language,
|
||||||
sensitive,
|
sensitive,
|
||||||
poll,
|
poll,
|
||||||
mediaAttachments: mediaAttachmentsWithIDs,
|
mediaAttachments,
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
window.opener.__COMPOSE__ = passData;
|
||||||
|
window.opener.__STATES__.showCompose = true;
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
}}
|
}}
|
||||||
|
@ -766,7 +670,7 @@ function Compose({
|
||||||
name="sensitive"
|
name="sensitive"
|
||||||
type="checkbox"
|
type="checkbox"
|
||||||
checked={sensitive}
|
checked={sensitive}
|
||||||
disabled={uiState === 'loading' || !!editStatus}
|
disabled={uiState === 'loading'}
|
||||||
onChange={(e) => {
|
onChange={(e) => {
|
||||||
const sensitive = e.target.checked;
|
const sensitive = e.target.checked;
|
||||||
setSensitive(sensitive);
|
setSensitive(sensitive);
|
||||||
|
@ -803,41 +707,22 @@ function Compose({
|
||||||
</select>
|
</select>
|
||||||
</label>{' '}
|
</label>{' '}
|
||||||
</div>
|
</div>
|
||||||
<text-expander ref={textExpanderRef} keys="@ # :">
|
<Textarea
|
||||||
<textarea
|
ref={textareaRef}
|
||||||
ref={textareaRef}
|
placeholder={
|
||||||
placeholder={
|
replyToStatus
|
||||||
replyToStatus
|
? 'Post your reply'
|
||||||
? 'Post your reply'
|
: editStatus
|
||||||
: editStatus
|
? 'Edit your status'
|
||||||
? 'Edit your status'
|
: 'What are you doing?'
|
||||||
: 'What are you doing?'
|
}
|
||||||
}
|
required={mediaAttachments.length === 0}
|
||||||
required={mediaAttachments.length === 0}
|
disabled={uiState === 'loading'}
|
||||||
autoCapitalize="sentences"
|
onInput={() => {
|
||||||
autoComplete="on"
|
updateCharCount();
|
||||||
autoCorrect="on"
|
}}
|
||||||
spellCheck="true"
|
maxCharacters={maxCharacters}
|
||||||
dir="auto"
|
/>
|
||||||
rows="6"
|
|
||||||
cols="50"
|
|
||||||
name="status"
|
|
||||||
disabled={uiState === 'loading'}
|
|
||||||
onInput={(e) => {
|
|
||||||
const { scrollHeight, offsetHeight, clientHeight, value } =
|
|
||||||
e.target;
|
|
||||||
const offset = offsetHeight - clientHeight;
|
|
||||||
e.target.style.height = value
|
|
||||||
? scrollHeight + offset + 'px'
|
|
||||||
: null;
|
|
||||||
updateCharCount();
|
|
||||||
}}
|
|
||||||
style={{
|
|
||||||
maxHeight: `${maxCharacters / 50}em`,
|
|
||||||
'--text-weight': (1 + charCount / 140).toFixed(1) || 1,
|
|
||||||
}}
|
|
||||||
></textarea>
|
|
||||||
</text-expander>
|
|
||||||
{mediaAttachments.length > 0 && (
|
{mediaAttachments.length > 0 && (
|
||||||
<div class="media-attachments">
|
<div class="media-attachments">
|
||||||
{mediaAttachments.map((attachment, i) => {
|
{mediaAttachments.map((attachment, i) => {
|
||||||
|
@ -942,26 +827,8 @@ function Compose({
|
||||||
</button>{' '}
|
</button>{' '}
|
||||||
<div class="spacer" />
|
<div class="spacer" />
|
||||||
{uiState === 'loading' && <Loader abrupt />}{' '}
|
{uiState === 'loading' && <Loader abrupt />}{' '}
|
||||||
{uiState !== 'loading' && charCount > maxCharacters / 2 && (
|
{uiState !== 'loading' && (
|
||||||
<>
|
<CharCountMeter maxCharacters={maxCharacters} />
|
||||||
<meter
|
|
||||||
class={`donut ${
|
|
||||||
leftChars <= -10
|
|
||||||
? 'explode'
|
|
||||||
: leftChars <= 0
|
|
||||||
? 'danger'
|
|
||||||
: leftChars <= 20
|
|
||||||
? 'warning'
|
|
||||||
: ''
|
|
||||||
}`}
|
|
||||||
value={charCount}
|
|
||||||
max={maxCharacters}
|
|
||||||
data-left={leftChars}
|
|
||||||
style={{
|
|
||||||
'--percentage': (charCount / maxCharacters) * 100,
|
|
||||||
}}
|
|
||||||
/>{' '}
|
|
||||||
</>
|
|
||||||
)}
|
)}
|
||||||
<label class="toolbar-button">
|
<label class="toolbar-button">
|
||||||
<span class="icon-text">
|
<span class="icon-text">
|
||||||
|
@ -997,32 +864,269 @@ function Compose({
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const Textarea = forwardRef((props, ref) => {
|
||||||
|
const [text, setText] = useState(ref.current?.value || '');
|
||||||
|
const { maxCharacters, ...textareaProps } = props;
|
||||||
|
const snapStates = useSnapshot(states);
|
||||||
|
const charCount = snapStates.composerCharacterCount;
|
||||||
|
|
||||||
|
const textExpanderRef = useRef();
|
||||||
|
const textExpanderTextRef = useRef('');
|
||||||
|
useEffect(() => {
|
||||||
|
let handleChange, handleValue, handleCommited;
|
||||||
|
if (textExpanderRef.current) {
|
||||||
|
handleChange = (e) => {
|
||||||
|
// console.log('text-expander-change', e);
|
||||||
|
const { key, provide, text } = e.detail;
|
||||||
|
textExpanderTextRef.current = text;
|
||||||
|
|
||||||
|
if (text === '') {
|
||||||
|
provide(
|
||||||
|
Promise.resolve({
|
||||||
|
matched: false,
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (key === ':') {
|
||||||
|
// const emojis = customEmojis.current.filter((emoji) =>
|
||||||
|
// emoji.shortcode.startsWith(text),
|
||||||
|
// );
|
||||||
|
const emojis = filterShortcodes(customEmojis.current, text);
|
||||||
|
let html = '';
|
||||||
|
emojis.forEach((emoji) => {
|
||||||
|
const { shortcode, url } = emoji;
|
||||||
|
html += `
|
||||||
|
<li role="option" data-value="${encodeHTML(shortcode)}">
|
||||||
|
<img src="${encodeHTML(
|
||||||
|
url,
|
||||||
|
)}" width="16" height="16" alt="" loading="lazy" />
|
||||||
|
:${encodeHTML(shortcode)}:
|
||||||
|
</li>`;
|
||||||
|
});
|
||||||
|
// console.log({ emojis, html });
|
||||||
|
menu.innerHTML = html;
|
||||||
|
provide(
|
||||||
|
Promise.resolve({
|
||||||
|
matched: emojis.length > 0,
|
||||||
|
fragment: menu,
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const type = {
|
||||||
|
'@': 'accounts',
|
||||||
|
'#': 'hashtags',
|
||||||
|
}[key];
|
||||||
|
provide(
|
||||||
|
new Promise((resolve) => {
|
||||||
|
const searchResults = masto.v2.search({
|
||||||
|
type,
|
||||||
|
q: text,
|
||||||
|
limit: 5,
|
||||||
|
});
|
||||||
|
searchResults.then((value) => {
|
||||||
|
if (text !== textExpanderTextRef.current) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
console.log({ value, type, v: value[type] });
|
||||||
|
const results = value[type];
|
||||||
|
console.log('RESULTS', value, results);
|
||||||
|
let html = '';
|
||||||
|
results.forEach((result) => {
|
||||||
|
const {
|
||||||
|
name,
|
||||||
|
avatarStatic,
|
||||||
|
displayName,
|
||||||
|
username,
|
||||||
|
acct,
|
||||||
|
emojis,
|
||||||
|
} = result;
|
||||||
|
const displayNameWithEmoji = emojifyText(displayName, emojis);
|
||||||
|
// const item = menuItem.cloneNode();
|
||||||
|
if (acct) {
|
||||||
|
html += `
|
||||||
|
<li role="option" data-value="${encodeHTML(acct)}">
|
||||||
|
<span class="avatar">
|
||||||
|
<img src="${encodeHTML(
|
||||||
|
avatarStatic,
|
||||||
|
)}" width="16" height="16" alt="" loading="lazy" />
|
||||||
|
</span>
|
||||||
|
<span>
|
||||||
|
<b>${displayNameWithEmoji || username}</b>
|
||||||
|
<br>@${encodeHTML(acct)}
|
||||||
|
</span>
|
||||||
|
</li>
|
||||||
|
`;
|
||||||
|
} else {
|
||||||
|
html += `
|
||||||
|
<li role="option" data-value="${encodeHTML(name)}">
|
||||||
|
<span>#<b>${encodeHTML(name)}</b></span>
|
||||||
|
</li>
|
||||||
|
`;
|
||||||
|
}
|
||||||
|
menu.innerHTML = html;
|
||||||
|
});
|
||||||
|
console.log('MENU', results, menu);
|
||||||
|
resolve({
|
||||||
|
matched: results.length > 0,
|
||||||
|
fragment: menu,
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
textExpanderRef.current.addEventListener(
|
||||||
|
'text-expander-change',
|
||||||
|
handleChange,
|
||||||
|
);
|
||||||
|
|
||||||
|
handleValue = (e) => {
|
||||||
|
const { key, item } = e.detail;
|
||||||
|
if (key === ':') {
|
||||||
|
e.detail.value = `:${item.dataset.value}:`;
|
||||||
|
} else {
|
||||||
|
e.detail.value = `${key}${item.dataset.value}`;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
textExpanderRef.current.addEventListener(
|
||||||
|
'text-expander-value',
|
||||||
|
handleValue,
|
||||||
|
);
|
||||||
|
|
||||||
|
handleCommited = (e) => {
|
||||||
|
const { input } = e.detail;
|
||||||
|
setText(input.value);
|
||||||
|
};
|
||||||
|
|
||||||
|
textExpanderRef.current.addEventListener(
|
||||||
|
'text-expander-committed',
|
||||||
|
handleCommited,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return () => {
|
||||||
|
if (textExpanderRef.current) {
|
||||||
|
textExpanderRef.current.removeEventListener(
|
||||||
|
'text-expander-change',
|
||||||
|
handleChange,
|
||||||
|
);
|
||||||
|
textExpanderRef.current.removeEventListener(
|
||||||
|
'text-expander-value',
|
||||||
|
handleValue,
|
||||||
|
);
|
||||||
|
textExpanderRef.current.removeEventListener(
|
||||||
|
'text-expander-committed',
|
||||||
|
handleCommited,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<text-expander ref={textExpanderRef} keys="@ # :">
|
||||||
|
<textarea
|
||||||
|
autoCapitalize="sentences"
|
||||||
|
autoComplete="on"
|
||||||
|
autoCorrect="on"
|
||||||
|
spellCheck="true"
|
||||||
|
dir="auto"
|
||||||
|
rows="6"
|
||||||
|
cols="50"
|
||||||
|
{...textareaProps}
|
||||||
|
ref={ref}
|
||||||
|
name="status"
|
||||||
|
value={text}
|
||||||
|
onInput={(e) => {
|
||||||
|
const { scrollHeight, offsetHeight, clientHeight, value } = e.target;
|
||||||
|
setText(value);
|
||||||
|
const offset = offsetHeight - clientHeight;
|
||||||
|
e.target.style.height = value ? scrollHeight + offset + 'px' : null;
|
||||||
|
props.onInput?.(e);
|
||||||
|
}}
|
||||||
|
style={{
|
||||||
|
width: '100%',
|
||||||
|
height: '4em',
|
||||||
|
'--text-weight': (1 + charCount / 140).toFixed(1) || 1,
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</text-expander>
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
function CharCountMeter({ maxCharacters = 500 }) {
|
||||||
|
const snapStates = useSnapshot(states);
|
||||||
|
const charCount = snapStates.composerCharacterCount;
|
||||||
|
const leftChars = maxCharacters - charCount;
|
||||||
|
if (charCount <= maxCharacters / 2) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
return (
|
||||||
|
<meter
|
||||||
|
class={`donut ${
|
||||||
|
leftChars <= -10
|
||||||
|
? 'explode'
|
||||||
|
: leftChars <= 0
|
||||||
|
? 'danger'
|
||||||
|
: leftChars <= 20
|
||||||
|
? 'warning'
|
||||||
|
: ''
|
||||||
|
}`}
|
||||||
|
value={charCount}
|
||||||
|
max={maxCharacters}
|
||||||
|
data-left={leftChars}
|
||||||
|
style={{
|
||||||
|
'--percentage': (charCount / maxCharacters) * 100,
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
function MediaAttachment({
|
function MediaAttachment({
|
||||||
attachment,
|
attachment,
|
||||||
disabled,
|
disabled,
|
||||||
onDescriptionChange = () => {},
|
onDescriptionChange = () => {},
|
||||||
onRemove = () => {},
|
onRemove = () => {},
|
||||||
}) {
|
}) {
|
||||||
const { url, type, id, description } = attachment;
|
const { url, type, id } = attachment;
|
||||||
|
console.log({ attachment });
|
||||||
|
const [description, setDescription] = useState(attachment.description);
|
||||||
const suffixType = type.split('/')[0];
|
const suffixType = type.split('/')[0];
|
||||||
return (
|
const debouncedOnDescriptionChange = useDebouncedCallback(
|
||||||
<div class="media-attachment">
|
onDescriptionChange,
|
||||||
<div class="media-preview">
|
500,
|
||||||
{suffixType === 'image' ? (
|
);
|
||||||
<img src={url} alt="" />
|
|
||||||
) : suffixType === 'video' || suffixType === 'gifv' ? (
|
const [showModal, setShowModal] = useState(false);
|
||||||
<video src={url} playsinline muted />
|
const textareaRef = useRef(null);
|
||||||
) : suffixType === 'audio' ? (
|
useEffect(() => {
|
||||||
<audio src={url} controls />
|
let timer;
|
||||||
) : null}
|
if (showModal && textareaRef.current) {
|
||||||
</div>
|
timer = setTimeout(() => {
|
||||||
|
textareaRef.current.focus();
|
||||||
|
}, 100);
|
||||||
|
}
|
||||||
|
return () => {
|
||||||
|
clearTimeout(timer);
|
||||||
|
};
|
||||||
|
}, [showModal]);
|
||||||
|
|
||||||
|
const descTextarea = (
|
||||||
|
<>
|
||||||
{!!id ? (
|
{!!id ? (
|
||||||
<div class="media-desc">
|
<div class="media-desc">
|
||||||
<span class="tag">Uploaded</span>
|
<span class="tag">Uploaded</span>
|
||||||
<p title={description}>{description || <i>No description</i>}</p>
|
<p title={description}>
|
||||||
|
{attachment.description || <i>No description</i>}
|
||||||
|
</p>
|
||||||
</div>
|
</div>
|
||||||
) : (
|
) : (
|
||||||
<textarea
|
<textarea
|
||||||
|
ref={textareaRef}
|
||||||
value={description || ''}
|
value={description || ''}
|
||||||
placeholder={
|
placeholder={
|
||||||
{
|
{
|
||||||
|
@ -1041,21 +1145,79 @@ function MediaAttachment({
|
||||||
// TODO: Un-hard-code this maxlength, ref: https://github.com/mastodon/mastodon/blob/b59fb28e90bc21d6fd1a6bafd13cfbd81ab5be54/app/models/media_attachment.rb#L39
|
// TODO: Un-hard-code this maxlength, ref: https://github.com/mastodon/mastodon/blob/b59fb28e90bc21d6fd1a6bafd13cfbd81ab5be54/app/models/media_attachment.rb#L39
|
||||||
onInput={(e) => {
|
onInput={(e) => {
|
||||||
const { value } = e.target;
|
const { value } = e.target;
|
||||||
onDescriptionChange(value);
|
setDescription(value);
|
||||||
|
debouncedOnDescriptionChange(value);
|
||||||
}}
|
}}
|
||||||
></textarea>
|
></textarea>
|
||||||
)}
|
)}
|
||||||
<div class="media-aside">
|
</>
|
||||||
<button
|
);
|
||||||
type="button"
|
|
||||||
class="plain close-button"
|
return (
|
||||||
disabled={disabled}
|
<>
|
||||||
onClick={onRemove}
|
<div class="media-attachment">
|
||||||
|
<div
|
||||||
|
class="media-preview"
|
||||||
|
onClick={() => {
|
||||||
|
setShowModal(true);
|
||||||
|
}}
|
||||||
>
|
>
|
||||||
<Icon icon="x" />
|
{suffixType === 'image' ? (
|
||||||
</button>
|
<img src={url} alt="" />
|
||||||
|
) : suffixType === 'video' || suffixType === 'gifv' ? (
|
||||||
|
<video src={url} playsinline muted />
|
||||||
|
) : suffixType === 'audio' ? (
|
||||||
|
<audio src={url} controls />
|
||||||
|
) : null}
|
||||||
|
</div>
|
||||||
|
{descTextarea}
|
||||||
|
<div class="media-aside">
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
class="plain close-button"
|
||||||
|
disabled={disabled}
|
||||||
|
onClick={onRemove}
|
||||||
|
>
|
||||||
|
<Icon icon="x" />
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
{showModal && (
|
||||||
|
<Modal
|
||||||
|
onClick={(e) => {
|
||||||
|
if (e.target === e.currentTarget) {
|
||||||
|
setShowModal(false);
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<div id="media-sheet" class="sheet">
|
||||||
|
<header>
|
||||||
|
<h2>
|
||||||
|
{
|
||||||
|
{
|
||||||
|
image: 'Edit image description',
|
||||||
|
video: 'Edit video description',
|
||||||
|
audio: 'Edit audio description',
|
||||||
|
}[suffixType]
|
||||||
|
}
|
||||||
|
</h2>
|
||||||
|
</header>
|
||||||
|
<main tabIndex="-1">
|
||||||
|
<div class="media-preview">
|
||||||
|
{suffixType === 'image' ? (
|
||||||
|
<img src={url} alt="" />
|
||||||
|
) : suffixType === 'video' || suffixType === 'gifv' ? (
|
||||||
|
<video src={url} playsinline controls />
|
||||||
|
) : suffixType === 'audio' ? (
|
||||||
|
<audio src={url} controls />
|
||||||
|
) : null}
|
||||||
|
</div>
|
||||||
|
{descTextarea}
|
||||||
|
</main>
|
||||||
|
</div>
|
||||||
|
</Modal>
|
||||||
|
)}
|
||||||
|
</>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1205,16 +1367,6 @@ function encodeHTML(str) {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
// https://github.com/mastodon/mastodon/blob/c4a429ed47e85a6bbf0d470a41cc2f64cf120c19/app/javascript/mastodon/features/compose/util/counter.js
|
|
||||||
const urlRegexObj = new RegExp(urlRegex.source, urlRegex.flags);
|
|
||||||
const usernameRegex = /(^|[^\/\w])@(([a-z0-9_]+)@[a-z0-9\.\-]+[a-z0-9]+)/gi;
|
|
||||||
const urlPlaceholder = '$2xxxxxxxxxxxxxxxxxxxxxxx';
|
|
||||||
function countableText(inputText) {
|
|
||||||
return inputText
|
|
||||||
.replace(urlRegexObj, urlPlaceholder)
|
|
||||||
.replace(usernameRegex, '$1@$3');
|
|
||||||
}
|
|
||||||
|
|
||||||
function removeNullUndefined(obj) {
|
function removeNullUndefined(obj) {
|
||||||
for (let key in obj) {
|
for (let key in obj) {
|
||||||
if (obj[key] === null || obj[key] === undefined) {
|
if (obj[key] === null || obj[key] === undefined) {
|
||||||
|
|
|
@ -16,6 +16,8 @@ function NameText({ account, showAvatar, showAcct, short, external, onClick }) {
|
||||||
username.toLowerCase().trim() ===
|
username.toLowerCase().trim() ===
|
||||||
(displayName || '')
|
(displayName || '')
|
||||||
.replace(/(\:(\w|\+|\-)+\:)(?=|[\!\.\?]|$)/g, '') // Remove shortcodes, regex from https://regex101.com/r/iE9uV0/1
|
.replace(/(\:(\w|\+|\-)+\:)(?=|[\!\.\?]|$)/g, '') // Remove shortcodes, regex from https://regex101.com/r/iE9uV0/1
|
||||||
|
.replace(/\s+/g, '') // E.g. "My name" === "myname"
|
||||||
|
.replace(/[^a-z0-9]/gi, '') // Remove non-alphanumeric characters
|
||||||
.toLowerCase()
|
.toLowerCase()
|
||||||
.trim()
|
.trim()
|
||||||
) {
|
) {
|
||||||
|
|
58
src/components/relative-time.jsx
Normal file
58
src/components/relative-time.jsx
Normal file
|
@ -0,0 +1,58 @@
|
||||||
|
// Twitter-style relative time component
|
||||||
|
// Seconds = 1s
|
||||||
|
// Minutes = 1m
|
||||||
|
// Hours = 1h
|
||||||
|
// Days = 1d
|
||||||
|
// After 7 days, use DD/MM/YYYY or MM/DD/YYYY
|
||||||
|
import dayjs from 'dayjs';
|
||||||
|
import dayjsTwitter from 'dayjs-twitter';
|
||||||
|
import localizedFormat from 'dayjs/plugin/localizedFormat';
|
||||||
|
import relativeTime from 'dayjs/plugin/relativeTime';
|
||||||
|
import { useEffect, useState } from 'preact/hooks';
|
||||||
|
|
||||||
|
dayjs.extend(dayjsTwitter);
|
||||||
|
dayjs.extend(localizedFormat);
|
||||||
|
dayjs.extend(relativeTime);
|
||||||
|
|
||||||
|
const dtf = new Intl.DateTimeFormat();
|
||||||
|
|
||||||
|
export default function RelativeTime({ datetime, format }) {
|
||||||
|
if (!datetime) return null;
|
||||||
|
const date = dayjs(datetime);
|
||||||
|
const [dateStr, setDateStr] = useState('');
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
let timer, raf;
|
||||||
|
const update = () => {
|
||||||
|
raf = requestAnimationFrame(() => {
|
||||||
|
let str;
|
||||||
|
if (format === 'micro') {
|
||||||
|
// If date <= 1 day ago or day is within this year
|
||||||
|
const now = dayjs();
|
||||||
|
const dayDiff = now.diff(date, 'day');
|
||||||
|
if (dayDiff <= 1 || now.year() === date.year()) {
|
||||||
|
str = date.twitter();
|
||||||
|
} else {
|
||||||
|
str = dtf.format(date.toDate());
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
str = date.fromNow();
|
||||||
|
}
|
||||||
|
setDateStr(str);
|
||||||
|
|
||||||
|
timer = setTimeout(update, 30_000);
|
||||||
|
});
|
||||||
|
};
|
||||||
|
raf = requestAnimationFrame(update);
|
||||||
|
return () => {
|
||||||
|
clearTimeout(timer);
|
||||||
|
cancelAnimationFrame(raf);
|
||||||
|
};
|
||||||
|
}, [date]);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<time datetime={date.toISOString()} title={date.format('LLLL')}>
|
||||||
|
{dateStr}
|
||||||
|
</time>
|
||||||
|
);
|
||||||
|
}
|
|
@ -103,6 +103,9 @@
|
||||||
justify-content: space-between;
|
justify-content: space-between;
|
||||||
white-space: nowrap;
|
white-space: nowrap;
|
||||||
}
|
}
|
||||||
|
.status.small > .container > .meta {
|
||||||
|
margin-bottom: 4px;
|
||||||
|
}
|
||||||
.status > .container > .meta > * {
|
.status > .container > .meta > * {
|
||||||
min-width: 0;
|
min-width: 0;
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
|
@ -179,33 +182,56 @@
|
||||||
gap: 8px;
|
gap: 8px;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
}
|
}
|
||||||
.status .content-container.has-spoiler .spoiler ~ * {
|
.status
|
||||||
/* filter: blur(6px) invert(0.5); */
|
.content-container.has-spoiler
|
||||||
filter: url(#spoiler);
|
.spoiler
|
||||||
transform: translate3d(-5px, -5px, 0);
|
~ *:not(.media-container, .card),
|
||||||
|
.status .content-container.has-spoiler .spoiler ~ .card .meta-container {
|
||||||
|
filter: blur(6px) invert(0.5);
|
||||||
|
/* filter: url(#spoiler); */
|
||||||
|
text-rendering: optimizeSpeed;
|
||||||
|
image-rendering: crisp-edges;
|
||||||
|
image-rendering: pixelated;
|
||||||
|
/* transform: translate3d(-5px, -5px, 0); */
|
||||||
pointer-events: none;
|
pointer-events: none;
|
||||||
user-select: none;
|
user-select: none;
|
||||||
contain: layout;
|
contain: layout;
|
||||||
}
|
}
|
||||||
@media (prefers-color-scheme: dark) {
|
.status .content-container.has-spoiler .spoiler ~ .media-container .media > *,
|
||||||
.status .content-container.has-spoiler .spoiler ~ * {
|
.status .content-container.has-spoiler .spoiler ~ .card > img {
|
||||||
|
filter: blur(32px);
|
||||||
|
image-rendering: crisp-edges;
|
||||||
|
image-rendering: pixelated;
|
||||||
|
animation: none !important;
|
||||||
|
}
|
||||||
|
/* @media (prefers-color-scheme: dark) {
|
||||||
|
.status
|
||||||
|
.content-container.has-spoiler
|
||||||
|
.spoiler
|
||||||
|
~ *:not(.media-container, .card),
|
||||||
|
.status .content-container.has-spoiler .spoiler ~ .card .meta-container {
|
||||||
filter: url(#spoiler-dark);
|
filter: url(#spoiler-dark);
|
||||||
}
|
}
|
||||||
}
|
} */
|
||||||
.status .content-container.has-spoiler .spoiler ~ .content ~ * {
|
|
||||||
opacity: 0.5;
|
|
||||||
}
|
|
||||||
.status .content-container.show-spoiler .spoiler {
|
.status .content-container.show-spoiler .spoiler {
|
||||||
border-style: dotted;
|
border-style: dotted;
|
||||||
}
|
}
|
||||||
.status .content-container.show-spoiler .spoiler ~ * {
|
.status
|
||||||
|
.content-container.show-spoiler
|
||||||
|
.spoiler
|
||||||
|
~ *:not(.media-container, .card),
|
||||||
|
.status .content-container.show-spoiler .spoiler ~ .card .meta-container {
|
||||||
filter: none !important;
|
filter: none !important;
|
||||||
transform: none;
|
transform: none;
|
||||||
pointer-events: auto;
|
pointer-events: auto;
|
||||||
user-select: auto;
|
user-select: auto;
|
||||||
|
text-rendering: auto;
|
||||||
|
image-rendering: auto;
|
||||||
}
|
}
|
||||||
.status .content-container.has-spoiler .spoiler ~ .content ~ * {
|
.status .content-container.show-spoiler .spoiler ~ .media-container .media > *,
|
||||||
opacity: 1;
|
.status .content-container.show-spoiler .spoiler ~ .card > img {
|
||||||
|
filter: none;
|
||||||
|
image-rendering: auto;
|
||||||
}
|
}
|
||||||
|
|
||||||
.timeline-deck .status .content {
|
.timeline-deck .status .content {
|
||||||
|
@ -353,48 +379,67 @@
|
||||||
height: 100%;
|
height: 100%;
|
||||||
object-fit: contain;
|
object-fit: contain;
|
||||||
}
|
}
|
||||||
.status .media-video {
|
.status .media-video,
|
||||||
|
.status .media-gif {
|
||||||
position: relative;
|
position: relative;
|
||||||
background-clip: padding-box;
|
background-clip: padding-box;
|
||||||
}
|
}
|
||||||
.status .media-video:before {
|
.status .media-video[data-formatted-duration]:before {
|
||||||
/* draw a circle in the middle */
|
pointer-events: none;
|
||||||
content: '';
|
content: '⏵';
|
||||||
position: absolute;
|
|
||||||
top: 50%;
|
|
||||||
left: 50%;
|
|
||||||
transform: translate(-50%, -50%);
|
|
||||||
width: 70px;
|
width: 70px;
|
||||||
height: 70px;
|
height: 70px;
|
||||||
border-radius: 50%;
|
font-size: 50px;
|
||||||
|
position: absolute;
|
||||||
|
text-indent: 3px;
|
||||||
|
left: 50%;
|
||||||
|
top: 50%;
|
||||||
|
transform: translate(-50%, -50%);
|
||||||
|
color: var(--text-insignificant-color);
|
||||||
background-color: var(--bg-blur-color);
|
background-color: var(--bg-blur-color);
|
||||||
backdrop-filter: blur(6px) saturate(3) invert(0.2);
|
backdrop-filter: blur(6px) saturate(3) invert(0.2);
|
||||||
z-index: 1;
|
display: flex;
|
||||||
|
place-content: center;
|
||||||
|
place-items: center;
|
||||||
|
border-radius: 70px;
|
||||||
}
|
}
|
||||||
.status .media-video:after {
|
.status .media-video[data-formatted-duration]:hover:before {
|
||||||
/* show play icon */
|
color: var(--text-color);
|
||||||
content: '';
|
}
|
||||||
position: absolute;
|
.status .media-video[data-formatted-duration]:after {
|
||||||
top: 50%;
|
font-size: 12px;
|
||||||
left: 50%;
|
|
||||||
transform: translate(-35%, -50%);
|
|
||||||
width: 0;
|
|
||||||
height: 0;
|
|
||||||
border-style: solid;
|
|
||||||
border-width: 15px 0 15px 26px;
|
|
||||||
border-color: transparent transparent transparent
|
|
||||||
var(--text-insignificant-color);
|
|
||||||
pointer-events: none;
|
pointer-events: none;
|
||||||
opacity: 0.75;
|
content: attr(data-formatted-duration);
|
||||||
z-index: 2;
|
position: absolute;
|
||||||
|
bottom: 8px;
|
||||||
|
left: 8px;
|
||||||
|
color: var(--bg-color);
|
||||||
|
background-color: var(--text-color);
|
||||||
|
backdrop-filter: blur(6px) saturate(3) invert(0.2);
|
||||||
|
border-radius: 4px;
|
||||||
|
padding: 0 4px;
|
||||||
}
|
}
|
||||||
.status .media-video:hover:after {
|
.status .media-gif[data-label]:not(:hover):after {
|
||||||
opacity: 1;
|
font-size: 12px;
|
||||||
|
font-weight: bold;
|
||||||
|
pointer-events: none;
|
||||||
|
content: attr(data-label);
|
||||||
|
position: absolute;
|
||||||
|
bottom: 8px;
|
||||||
|
left: 8px;
|
||||||
|
color: var(--bg-faded-color);
|
||||||
|
background-color: var(--text-insignificant-color);
|
||||||
|
backdrop-filter: blur(6px) saturate(3) invert(0.2);
|
||||||
|
border-radius: 4px;
|
||||||
|
padding: 0 4px;
|
||||||
}
|
}
|
||||||
.status .media-gif video {
|
.status .media-gif video {
|
||||||
object-fit: cover;
|
object-fit: cover;
|
||||||
pointer-events: none;
|
pointer-events: none;
|
||||||
}
|
}
|
||||||
|
.status .media-contain video {
|
||||||
|
object-fit: contain !important;
|
||||||
|
}
|
||||||
.status .media-audio {
|
.status .media-audio {
|
||||||
border: 0;
|
border: 0;
|
||||||
min-height: 0;
|
min-height: 0;
|
||||||
|
@ -434,6 +479,7 @@
|
||||||
width: 100%;
|
width: 100%;
|
||||||
max-height: 50vh;
|
max-height: 50vh;
|
||||||
border-inline-end: 0;
|
border-inline-end: 0;
|
||||||
|
border-block-end: 1px solid var(--outline-color);
|
||||||
}
|
}
|
||||||
.card:is(:hover, :focus) .image {
|
.card:is(:hover, :focus) .image {
|
||||||
animation: position-object 5s ease-in-out 1s 5;
|
animation: position-object 5s ease-in-out 1s 5;
|
||||||
|
@ -447,6 +493,9 @@
|
||||||
flex-grow: 1;
|
flex-grow: 1;
|
||||||
align-self: center;
|
align-self: center;
|
||||||
}
|
}
|
||||||
|
.card.large .meta-container {
|
||||||
|
align-self: flex-start;
|
||||||
|
}
|
||||||
.card .title {
|
.card .title {
|
||||||
line-height: 1.25;
|
line-height: 1.25;
|
||||||
font-weight: normal;
|
font-weight: normal;
|
||||||
|
|
|
@ -28,6 +28,7 @@ import visibilityIconsMap from '../utils/visibility-icons-map';
|
||||||
|
|
||||||
import Avatar from './avatar';
|
import Avatar from './avatar';
|
||||||
import Icon from './icon';
|
import Icon from './icon';
|
||||||
|
import RelativeTime from './relative-time';
|
||||||
|
|
||||||
function fetchAccount(id) {
|
function fetchAccount(id) {
|
||||||
return masto.v1.accounts.fetch(id);
|
return masto.v1.accounts.fetch(id);
|
||||||
|
@ -94,6 +95,7 @@ function Status({
|
||||||
filtered,
|
filtered,
|
||||||
card,
|
card,
|
||||||
createdAt,
|
createdAt,
|
||||||
|
inReplyToId,
|
||||||
inReplyToAccountId,
|
inReplyToAccountId,
|
||||||
content,
|
content,
|
||||||
mentions,
|
mentions,
|
||||||
|
@ -252,14 +254,7 @@ function Status({
|
||||||
alt={visibility}
|
alt={visibility}
|
||||||
size="s"
|
size="s"
|
||||||
/>{' '}
|
/>{' '}
|
||||||
<relative-time
|
<RelativeTime datetime={createdAtDate} format="micro" />
|
||||||
datetime={createdAtDate.toISOString()}
|
|
||||||
format="micro"
|
|
||||||
threshold="P1D"
|
|
||||||
prefix=""
|
|
||||||
>
|
|
||||||
{createdAtDate.toLocaleString()}
|
|
||||||
</relative-time>
|
|
||||||
</a>
|
</a>
|
||||||
) : (
|
) : (
|
||||||
<span class="time">
|
<span class="time">
|
||||||
|
@ -268,37 +263,34 @@ function Status({
|
||||||
alt={visibility}
|
alt={visibility}
|
||||||
size="s"
|
size="s"
|
||||||
/>{' '}
|
/>{' '}
|
||||||
<relative-time
|
<RelativeTime datetime={createdAtDate} format="micro" />
|
||||||
datetime={createdAtDate.toISOString()}
|
|
||||||
format="micro"
|
|
||||||
threshold="P1D"
|
|
||||||
prefix=""
|
|
||||||
>
|
|
||||||
{createdAtDate.toLocaleString()}
|
|
||||||
</relative-time>
|
|
||||||
</span>
|
</span>
|
||||||
))}
|
))}
|
||||||
</div>
|
</div>
|
||||||
{inReplyToAccountId && !withinContext && size !== 's' && (
|
{!!inReplyToId &&
|
||||||
<>
|
!!inReplyToAccountId &&
|
||||||
{inReplyToAccountId === status.account.id ? (
|
!withinContext &&
|
||||||
<div class="status-thread-badge">
|
size !== 's' && (
|
||||||
<Icon icon="thread" size="s" />
|
<>
|
||||||
Thread
|
{inReplyToAccountId === status.account.id ? (
|
||||||
</div>
|
<div class="status-thread-badge">
|
||||||
) : (
|
<Icon icon="thread" size="s" />
|
||||||
!!inReplyToAccount &&
|
Thread
|
||||||
!mentions.find((mention) => {
|
|
||||||
return mention.id === inReplyToAccountId;
|
|
||||||
}) && (
|
|
||||||
<div class="status-reply-badge">
|
|
||||||
<Icon icon="reply" />{' '}
|
|
||||||
<NameText account={inReplyToAccount} short />
|
|
||||||
</div>
|
</div>
|
||||||
)
|
) : (
|
||||||
)}
|
!!inReplyToAccount &&
|
||||||
</>
|
(!!spoilerText ||
|
||||||
)}
|
!mentions.find((mention) => {
|
||||||
|
return mention.id === inReplyToAccountId;
|
||||||
|
})) && (
|
||||||
|
<div class="status-reply-badge">
|
||||||
|
<Icon icon="reply" />{' '}
|
||||||
|
<NameText account={inReplyToAccount} short />
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
)}
|
||||||
|
</>
|
||||||
|
)}
|
||||||
<div
|
<div
|
||||||
class={`content-container ${
|
class={`content-container ${
|
||||||
sensitive || spoilerText ? 'has-spoiler' : ''
|
sensitive || spoilerText ? 'has-spoiler' : ''
|
||||||
|
@ -430,6 +422,7 @@ function Status({
|
||||||
<Media
|
<Media
|
||||||
key={media.id}
|
key={media.id}
|
||||||
media={media}
|
media={media}
|
||||||
|
autoAnimate={size === 'l'}
|
||||||
onClick={(e) => {
|
onClick={(e) => {
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
e.stopPropagation();
|
e.stopPropagation();
|
||||||
|
@ -440,8 +433,10 @@ function Status({
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
{!!card &&
|
{!!card &&
|
||||||
(size === 'l' ||
|
!sensitive &&
|
||||||
(size === 'm' && !poll && !mediaAttachments.length)) && (
|
!spoilerText &&
|
||||||
|
!poll &&
|
||||||
|
!mediaAttachments.length && (
|
||||||
<Card
|
<Card
|
||||||
card={card}
|
card={card}
|
||||||
size={!poll && !mediaAttachments.length ? 'l' : 'm'}
|
size={!poll && !mediaAttachments.length ? 'l' : 'm'}
|
||||||
|
@ -658,7 +653,6 @@ function Status({
|
||||||
index={showMediaModal}
|
index={showMediaModal}
|
||||||
onClose={() => {
|
onClose={() => {
|
||||||
setShowMediaModal(false);
|
setShowMediaModal(false);
|
||||||
statusRef.current?.focus();
|
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
</Modal>
|
</Modal>
|
||||||
|
@ -695,7 +689,7 @@ video = Video clip
|
||||||
audio = Audio track
|
audio = Audio track
|
||||||
*/
|
*/
|
||||||
|
|
||||||
function Media({ media, showOriginal, onClick = () => {} }) {
|
function Media({ media, showOriginal, autoAnimate, onClick = () => {} }) {
|
||||||
const { blurhash, description, meta, previewUrl, remoteUrl, url, type } =
|
const { blurhash, description, meta, previewUrl, remoteUrl, url, type } =
|
||||||
media;
|
media;
|
||||||
const { original, small, focus } = meta || {};
|
const { original, small, focus } = meta || {};
|
||||||
|
@ -748,7 +742,7 @@ function Media({ media, showOriginal, onClick = () => {} }) {
|
||||||
alt={description}
|
alt={description}
|
||||||
width={width}
|
width={width}
|
||||||
height={height}
|
height={height}
|
||||||
loading="lazy"
|
loading={showOriginal ? 'eager' : 'lazy'}
|
||||||
style={
|
style={
|
||||||
!showOriginal && {
|
!showOriginal && {
|
||||||
backgroundColor:
|
backgroundColor:
|
||||||
|
@ -762,17 +756,24 @@ function Media({ media, showOriginal, onClick = () => {} }) {
|
||||||
} else if (type === 'gifv' || type === 'video') {
|
} else if (type === 'gifv' || type === 'video') {
|
||||||
// 20 seconds, treat as a gif
|
// 20 seconds, treat as a gif
|
||||||
const shortDuration = original.duration <= 20;
|
const shortDuration = original.duration <= 20;
|
||||||
const isGIF = type === 'gifv' || shortDuration;
|
const isGIFV = type === 'gifv';
|
||||||
|
const isGIF = isGIFV || shortDuration;
|
||||||
const loopable = original.duration <= 60;
|
const loopable = original.duration <= 60;
|
||||||
|
const formattedDuration = formatDuration(original.duration);
|
||||||
|
const hoverAnimate = !showOriginal && !autoAnimate && isGIF;
|
||||||
return (
|
return (
|
||||||
<div
|
<div
|
||||||
class={`media media-${isGIF ? 'gif' : 'video'}`}
|
class={`media media-${isGIF ? 'gif' : 'video'} ${
|
||||||
|
autoAnimate ? 'media-contain' : ''
|
||||||
|
}`}
|
||||||
|
data-formatted-duration={formattedDuration}
|
||||||
|
data-label={isGIF && !showOriginal && !autoAnimate ? 'GIF' : ''}
|
||||||
style={{
|
style={{
|
||||||
backgroundColor:
|
backgroundColor:
|
||||||
rgbAverageColor && `rgb(${rgbAverageColor.join(',')})`,
|
rgbAverageColor && `rgb(${rgbAverageColor.join(',')})`,
|
||||||
}}
|
}}
|
||||||
onClick={(e) => {
|
onClick={(e) => {
|
||||||
if (!showOriginal && isGIF) {
|
if (hoverAnimate) {
|
||||||
try {
|
try {
|
||||||
videoRef.current.pause();
|
videoRef.current.pause();
|
||||||
} catch (e) {}
|
} catch (e) {}
|
||||||
|
@ -780,37 +781,41 @@ function Media({ media, showOriginal, onClick = () => {} }) {
|
||||||
onClick(e);
|
onClick(e);
|
||||||
}}
|
}}
|
||||||
onMouseEnter={() => {
|
onMouseEnter={() => {
|
||||||
if (!showOriginal && isGIF) {
|
if (hoverAnimate) {
|
||||||
try {
|
try {
|
||||||
videoRef.current.play();
|
videoRef.current.play();
|
||||||
} catch (e) {}
|
} catch (e) {}
|
||||||
}
|
}
|
||||||
}}
|
}}
|
||||||
onMouseLeave={() => {
|
onMouseLeave={() => {
|
||||||
if (!showOriginal && isGIF) {
|
if (hoverAnimate) {
|
||||||
try {
|
try {
|
||||||
videoRef.current.pause();
|
videoRef.current.pause();
|
||||||
} catch (e) {}
|
} catch (e) {}
|
||||||
}
|
}
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
{showOriginal ? (
|
{showOriginal || autoAnimate ? (
|
||||||
<div
|
<div
|
||||||
|
style={{
|
||||||
|
width: '100%',
|
||||||
|
height: '100%',
|
||||||
|
}}
|
||||||
dangerouslySetInnerHTML={{
|
dangerouslySetInnerHTML={{
|
||||||
__html: `
|
__html: `
|
||||||
<video
|
<video
|
||||||
src="${url}"
|
src="${url}"
|
||||||
poster="${previewUrl}"
|
poster="${previewUrl}"
|
||||||
width="${width}"
|
width="${width}"
|
||||||
height="${height}"
|
height="${height}"
|
||||||
preload="auto"
|
preload="auto"
|
||||||
autoplay
|
autoplay
|
||||||
muted="${isGIF}"
|
muted="${isGIF}"
|
||||||
${isGIF ? '' : 'controls'}
|
${isGIFV ? '' : 'controls'}
|
||||||
playsinline
|
playsinline
|
||||||
loop="${loopable}"
|
loop="${loopable}"
|
||||||
></video>
|
></video>
|
||||||
`,
|
`,
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
) : isGIF ? (
|
) : isGIF ? (
|
||||||
|
@ -1130,9 +1135,7 @@ function Poll({ poll, lang, readOnly, onUpdate = () => {} }) {
|
||||||
</>
|
</>
|
||||||
)}{' '}
|
)}{' '}
|
||||||
• {expired ? 'Ended' : 'Ending'}{' '}
|
• {expired ? 'Ended' : 'Ending'}{' '}
|
||||||
{!!expiresAtDate && (
|
{!!expiresAtDate && <RelativeTime datetime={expiresAtDate} />}
|
||||||
<relative-time datetime={expiresAtDate.toISOString()} />
|
|
||||||
)}
|
|
||||||
</p>
|
</p>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
|
@ -1423,4 +1426,19 @@ function Carousel({ mediaAttachments, index = 0, onClose = () => {} }) {
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function formatDuration(time) {
|
||||||
|
if (!time) return;
|
||||||
|
let hours = Math.floor(time / 3600);
|
||||||
|
let minutes = Math.floor((time % 3600) / 60);
|
||||||
|
let seconds = Math.round(time % 60);
|
||||||
|
|
||||||
|
if (hours === 0) {
|
||||||
|
return `${minutes}:${seconds.toString().padStart(2, '0')}`;
|
||||||
|
} else {
|
||||||
|
return `${hours}:${minutes.toString().padStart(2, '0')}:${seconds
|
||||||
|
.toString()
|
||||||
|
.padStart(2, '0')}`;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
export default Status;
|
export default Status;
|
||||||
|
|
|
@ -2,7 +2,6 @@ import './index.css';
|
||||||
|
|
||||||
import './app.css';
|
import './app.css';
|
||||||
|
|
||||||
import '@github/relative-time-element';
|
|
||||||
import { login } from 'masto';
|
import { login } from 'masto';
|
||||||
import { render } from 'preact';
|
import { render } from 'preact';
|
||||||
import { useEffect, useState } from 'preact/hooks';
|
import { useEffect, useState } from 'preact/hooks';
|
||||||
|
|
|
@ -1,202 +1,435 @@
|
||||||
[
|
[
|
||||||
"mastodon.social",
|
"mastodon.social",
|
||||||
|
"mstdn.social",
|
||||||
"mastodon.world",
|
"mastodon.world",
|
||||||
"mas.to",
|
"mas.to",
|
||||||
"pawoo.net",
|
"pawoo.net",
|
||||||
"mastodon.online",
|
"mastodon.online",
|
||||||
|
"infosec.exchange",
|
||||||
"mstdn.jp",
|
"mstdn.jp",
|
||||||
|
"mastodonapp.uk",
|
||||||
|
"hachyderm.io",
|
||||||
|
"techhub.social",
|
||||||
|
"fosstodon.org",
|
||||||
"universeodon.com",
|
"universeodon.com",
|
||||||
"mastodon.lol",
|
"mastodon.lol",
|
||||||
"mastodonapp.uk",
|
|
||||||
"infosec.exchange",
|
|
||||||
"mastodon.uno",
|
|
||||||
"techhub.social",
|
|
||||||
"mastodon.sdf.org",
|
"mastodon.sdf.org",
|
||||||
"fosstodon.org",
|
|
||||||
"troet.cafe",
|
"troet.cafe",
|
||||||
"masto.ai",
|
"mastodon.uno",
|
||||||
|
"mastodon.nl",
|
||||||
"mstdn.party",
|
"mstdn.party",
|
||||||
"c.im",
|
"masto.ai",
|
||||||
"hachyderm.io",
|
|
||||||
"m.cmx.im",
|
|
||||||
"mstdn.ca",
|
"mstdn.ca",
|
||||||
"sfba.social",
|
"home.social",
|
||||||
|
"c.im",
|
||||||
"kolektiva.social",
|
"kolektiva.social",
|
||||||
"mastodon.scot",
|
"m.cmx.im",
|
||||||
"ohai.social",
|
"sfba.social",
|
||||||
"fedibird.com",
|
"fedibird.com",
|
||||||
"piaille.fr",
|
"piaille.fr",
|
||||||
"home.social",
|
|
||||||
"mindly.social",
|
|
||||||
"mastodon.nl",
|
|
||||||
"toot.community",
|
|
||||||
"aus.social",
|
|
||||||
"thu.closed.social",
|
|
||||||
"mastodon.gamedev.place",
|
"mastodon.gamedev.place",
|
||||||
"nerdculture.de",
|
"mastodon.scot",
|
||||||
|
"mindly.social",
|
||||||
|
"ohai.social",
|
||||||
"mastodon.cloud",
|
"mastodon.cloud",
|
||||||
"mastodon.ie",
|
"toot.community",
|
||||||
"det.social",
|
"det.social",
|
||||||
"mastodon.au",
|
"aus.social",
|
||||||
"nrw.social",
|
"nrw.social",
|
||||||
"mastodon.art",
|
"mastodon.art",
|
||||||
"chaos.social",
|
"chaos.social",
|
||||||
|
"social.vivaldi.net",
|
||||||
|
"mastodon.ie",
|
||||||
"norden.social",
|
"norden.social",
|
||||||
|
"sueden.social",
|
||||||
|
"mastodon.top",
|
||||||
|
"mastodon.au",
|
||||||
|
"mastodontech.de",
|
||||||
|
"mas.todon.de",
|
||||||
"ioc.exchange",
|
"ioc.exchange",
|
||||||
"alive.bar",
|
"alive.bar",
|
||||||
"tkz.one",
|
|
||||||
"sueden.social",
|
|
||||||
"mastodon.nu",
|
|
||||||
"mastodon.top",
|
|
||||||
"mastouille.fr",
|
|
||||||
"mastodontech.de",
|
|
||||||
"o3o.ca",
|
|
||||||
"social.tchncs.de",
|
"social.tchncs.de",
|
||||||
|
"mastodon.nu",
|
||||||
|
"social.cologne",
|
||||||
|
"mastouille.fr",
|
||||||
|
"o3o.ca",
|
||||||
|
"mathstodon.xyz",
|
||||||
"noagendasocial.com",
|
"noagendasocial.com",
|
||||||
"newsie.social",
|
"newsie.social",
|
||||||
"masto.es",
|
"sigmoid.social",
|
||||||
"planet.moe",
|
|
||||||
"social.vivaldi.net",
|
|
||||||
"ravenation.club",
|
|
||||||
"wxw.moe",
|
|
||||||
"mathstodon.xyz",
|
|
||||||
"social.cologne",
|
|
||||||
"mastodon.nz",
|
|
||||||
"qoto.org",
|
|
||||||
"hessen.social",
|
|
||||||
"mastodon.com.tr",
|
"mastodon.com.tr",
|
||||||
"ruhr.social",
|
"hessen.social",
|
||||||
"muenchen.social",
|
"muenchen.social",
|
||||||
"mamot.fr",
|
|
||||||
"twit.social",
|
|
||||||
"dice.camp",
|
|
||||||
"meow.social",
|
"meow.social",
|
||||||
"www.masto.pt",
|
"masto.es",
|
||||||
"social.anoxinon.de",
|
"masto.nu",
|
||||||
"www.sociale.network",
|
|
||||||
"tech.lgbt",
|
"tech.lgbt",
|
||||||
|
"ruhr.social",
|
||||||
|
"mastodon.green",
|
||||||
|
"mstdn.plus",
|
||||||
|
"wxw.moe",
|
||||||
|
"qoto.org",
|
||||||
|
"mamot.fr",
|
||||||
|
"tkz.one",
|
||||||
|
"dice.camp",
|
||||||
|
"social.anoxinon.de",
|
||||||
|
"mastodon.nz",
|
||||||
|
"twit.social",
|
||||||
|
"ravenation.club",
|
||||||
|
"planet.moe",
|
||||||
|
"mstdn.science",
|
||||||
|
"med-mastodon.com",
|
||||||
"econtwitter.net",
|
"econtwitter.net",
|
||||||
|
"fediscience.org",
|
||||||
|
"toot.io",
|
||||||
"masthead.social",
|
"masthead.social",
|
||||||
"glasgow.social",
|
"social.dev-wiki.de",
|
||||||
"ieji.de",
|
"mastodont.cat",
|
||||||
"toot.wales",
|
"toot.wales",
|
||||||
|
"ieji.de",
|
||||||
"ecoevo.social",
|
"ecoevo.social",
|
||||||
"ro-mastodon.puyo.jp",
|
"ro-mastodon.puyo.jp",
|
||||||
"noc.social",
|
|
||||||
"indieweb.social",
|
|
||||||
"zirk.us",
|
"zirk.us",
|
||||||
"twingyeo.kr",
|
"noc.social",
|
||||||
"social.linux.pizza",
|
"social.linux.pizza",
|
||||||
"mastodont.cat",
|
"cyberplace.social",
|
||||||
"social.dev-wiki.de",
|
"indieweb.social",
|
||||||
"mastodonczech.cz",
|
|
||||||
"climatejustice.social",
|
|
||||||
"eldritch.cafe",
|
|
||||||
"g0v.social",
|
|
||||||
"socel.net",
|
|
||||||
"dju.social",
|
|
||||||
"mastodontti.fi",
|
|
||||||
"101010.pl",
|
|
||||||
"framapiaf.org",
|
|
||||||
"wien.rocks",
|
|
||||||
"botsin.space",
|
|
||||||
"mastodon.bida.im",
|
|
||||||
"bildung.social",
|
|
||||||
"pouet.chapril.org",
|
|
||||||
"urbanists.social",
|
|
||||||
"wandering.shop",
|
|
||||||
"masto.pt",
|
|
||||||
"union.place",
|
|
||||||
"metalhead.club",
|
|
||||||
"ruby.social",
|
|
||||||
"hiveway.net",
|
|
||||||
"h4.io",
|
|
||||||
"genomic.social",
|
|
||||||
"mastodon-belgium.be",
|
|
||||||
"mastodon.xyz",
|
|
||||||
"octodon.social",
|
|
||||||
"pol.social",
|
|
||||||
"tooot.im",
|
|
||||||
"berlin.social",
|
|
||||||
"sciences.social",
|
|
||||||
"mstdn.guru",
|
|
||||||
"qdon.space",
|
|
||||||
"mastodon.radio",
|
|
||||||
"lile.cl",
|
|
||||||
"masto.nu",
|
|
||||||
"witches.live",
|
|
||||||
"mastodonners.nl",
|
"mastodonners.nl",
|
||||||
"muenster.im",
|
"convo.casa",
|
||||||
"lor.sh",
|
"twingyeo.kr",
|
||||||
"phpc.social",
|
"sself.co",
|
||||||
"pewtix.com",
|
"urbanists.social",
|
||||||
"social.librem.one",
|
"glasgow.social",
|
||||||
|
"botsin.space",
|
||||||
|
"eldritch.cafe",
|
||||||
|
"climatejustice.social",
|
||||||
|
"theblower.au",
|
||||||
|
"framapiaf.org",
|
||||||
|
"artsio.com",
|
||||||
|
"mastodon.iriseden.eu",
|
||||||
|
"socel.net",
|
||||||
|
"g0v.social",
|
||||||
|
"mastodonczech.cz",
|
||||||
|
"mastodontti.fi",
|
||||||
|
"wandering.shop",
|
||||||
|
"thu.closed.social",
|
||||||
|
"mastodon.bida.im",
|
||||||
|
"geekdom.social",
|
||||||
|
"stranger.social",
|
||||||
|
"cupoftea.social",
|
||||||
|
"bildung.social",
|
||||||
|
"awscommunity.social",
|
||||||
|
"mas.town",
|
||||||
|
"ruby.social",
|
||||||
|
"sciences.social",
|
||||||
|
"wien.rocks",
|
||||||
|
"respublicae.eu",
|
||||||
|
"metalhead.club",
|
||||||
|
"pouet.chapril.org",
|
||||||
|
"genomic.social",
|
||||||
|
"dju.social",
|
||||||
|
"101010.pl",
|
||||||
|
"graphics.social",
|
||||||
|
"defcon.social",
|
||||||
|
"mastodon.xyz",
|
||||||
|
"bark.lgbt",
|
||||||
|
"witches.live",
|
||||||
|
"climatejustice.rocks",
|
||||||
"rollenspiel.social",
|
"rollenspiel.social",
|
||||||
"peoplemaking.games",
|
"berlin.social",
|
||||||
|
"masto.pt",
|
||||||
|
"litmind.club",
|
||||||
|
"livellosegreto.it",
|
||||||
|
"mstdn.guru",
|
||||||
|
"nerdculture.de",
|
||||||
|
"journa.host",
|
||||||
|
"octodon.social",
|
||||||
|
"union.place",
|
||||||
|
"mastodon-belgium.be",
|
||||||
|
"mastodon.radio",
|
||||||
|
"pol.social",
|
||||||
|
"rheinneckar.social",
|
||||||
|
"hometech.social",
|
||||||
|
"androiddev.social",
|
||||||
|
"social.librem.one",
|
||||||
"kinky.business",
|
"kinky.business",
|
||||||
"mastodon.fun",
|
"phpc.social",
|
||||||
"me.ns.ci",
|
"mast.lat",
|
||||||
"mastodon.eus",
|
"muenster.im",
|
||||||
|
"mastodon.chasem.dev",
|
||||||
|
"tooot.im",
|
||||||
|
"musician.social",
|
||||||
"dresden.network",
|
"dresden.network",
|
||||||
"hostux.social",
|
"swiss.social",
|
||||||
"scholar.social",
|
"h4.io",
|
||||||
"freiburg.social",
|
|
||||||
"todon.eu",
|
|
||||||
"writing.exchange",
|
|
||||||
"toot.aquilenet.fr",
|
"toot.aquilenet.fr",
|
||||||
"digitalcourage.social",
|
"digitalcourage.social",
|
||||||
"rheinneckar.social",
|
"toad.social",
|
||||||
"discuss.systems",
|
"poweredbygay.social",
|
||||||
"defcon.social",
|
"hostux.social",
|
||||||
"snabelen.no",
|
|
||||||
"mastodon.se",
|
"mastodon.se",
|
||||||
|
"mastodon.me.uk",
|
||||||
"rubber.social",
|
"rubber.social",
|
||||||
"fulda.social",
|
"pewtix.com",
|
||||||
"vis.social",
|
"mastodon.berlin",
|
||||||
"toot.funami.tech",
|
"lor.sh",
|
||||||
"mast.dragon-fly.club",
|
"mastodon.fun",
|
||||||
|
"me.ns.ci",
|
||||||
|
"snabelen.no",
|
||||||
|
"freiburg.social",
|
||||||
"disabled.social",
|
"disabled.social",
|
||||||
"medibubble.org",
|
"spore.social",
|
||||||
"mastodon.technology",
|
"qdon.space",
|
||||||
|
"beta.qdon.space",
|
||||||
|
"scholar.social",
|
||||||
"vmst.io",
|
"vmst.io",
|
||||||
"mstdn.io",
|
"astrodon.social",
|
||||||
"equestria.social",
|
"masto.nobigtech.es",
|
||||||
"vocalodon.net",
|
"hci.social",
|
||||||
"mastodon.ml",
|
"mastodon.eus",
|
||||||
"libretooth.gr",
|
"todon.eu",
|
||||||
|
"discuss.systems",
|
||||||
"tooting.ch",
|
"tooting.ch",
|
||||||
"dizl.de",
|
"paquita.masto.host",
|
||||||
"best-friends.chat",
|
"fulda.social",
|
||||||
"romancelandia.club",
|
"lile.cl",
|
||||||
"queer.party",
|
"medibubble.org",
|
||||||
"tilde.zone",
|
"writing.exchange",
|
||||||
"xarxa.cloud",
|
"historians.social",
|
||||||
"abdl.link",
|
"vocalodon.net",
|
||||||
"bitcoinhackers.org",
|
"vis.social",
|
||||||
"photog.social",
|
|
||||||
"macaw.social",
|
|
||||||
"yiff.life",
|
"yiff.life",
|
||||||
"sociale.network",
|
"fur.lgbt",
|
||||||
|
"peoplemaking.games",
|
||||||
|
"hcommons.social",
|
||||||
|
"mstdn.io",
|
||||||
|
"libretooth.gr",
|
||||||
|
"m.sclo.nl",
|
||||||
|
"pettingzoo.co",
|
||||||
|
"mastodon.zaclys.com",
|
||||||
|
"equestria.social",
|
||||||
|
"best-friends.chat",
|
||||||
"ursal.zone",
|
"ursal.zone",
|
||||||
"eupolicy.social",
|
"bitcoinhackers.org",
|
||||||
"gruene.social",
|
"uiuxdev.social",
|
||||||
"artisan.chat",
|
"queer.party",
|
||||||
"graz.social",
|
"mastodon.ml",
|
||||||
|
"aethy.com",
|
||||||
|
"abdl.link",
|
||||||
|
"mastodon.com.py",
|
||||||
|
"mapstodon.space",
|
||||||
|
"typo.social",
|
||||||
|
"cryptodon.lol",
|
||||||
|
"tilde.zone",
|
||||||
|
"computerfairi.es",
|
||||||
"social.coop",
|
"social.coop",
|
||||||
"mstdn.id",
|
"mast.dragon-fly.club",
|
||||||
"social.sciences.re",
|
"dragon-fly.club",
|
||||||
"ludosphere.fr",
|
|
||||||
"social.politicaconciencia.org",
|
|
||||||
"oslo.town",
|
|
||||||
"scicomm.xyz",
|
|
||||||
"floss.social",
|
"floss.social",
|
||||||
"creators.social",
|
"photog.social",
|
||||||
"tabletop.social",
|
|
||||||
"bonn.social",
|
"bonn.social",
|
||||||
"openbiblio.social",
|
"sciencemastodon.com",
|
||||||
"mastodon.la",
|
"mastodon.coffee",
|
||||||
"halifaxsocial.ca",
|
"mastorol.es",
|
||||||
|
"federated.press",
|
||||||
|
"toot.funami.tech",
|
||||||
|
"mastodon.gal",
|
||||||
|
"tabletop.social",
|
||||||
|
"shakedown.social",
|
||||||
|
"dizl.de",
|
||||||
|
"romancelandia.club",
|
||||||
|
"oslo.town",
|
||||||
|
"graz.social",
|
||||||
|
"sociale.network",
|
||||||
|
"todon.nl",
|
||||||
|
"nofan.xyz",
|
||||||
|
"data-folks.masto.host",
|
||||||
|
"scicomm.xyz",
|
||||||
|
"layer8.space",
|
||||||
|
"artisan.chat",
|
||||||
"freeradical.zone",
|
"freeradical.zone",
|
||||||
|
"toot.cat",
|
||||||
|
"fandom.ink",
|
||||||
|
"twiukraine.com",
|
||||||
|
"eupolicy.social",
|
||||||
|
"xarxa.cloud",
|
||||||
|
"bsd.network",
|
||||||
|
"weirder.earth",
|
||||||
|
"linuxrocks.online",
|
||||||
|
"mastodon.cat",
|
||||||
|
"girlcock.club",
|
||||||
|
"bolha.us",
|
||||||
|
"zeroes.ca",
|
||||||
|
"douchi.space",
|
||||||
|
"cybre.space",
|
||||||
|
"mastodon.la",
|
||||||
|
"sunny.garden",
|
||||||
|
"bbq.snoot.com",
|
||||||
|
"liker.social",
|
||||||
|
"vulpine.club",
|
||||||
|
"imastodon.net",
|
||||||
|
"mstdn.maud.io",
|
||||||
|
"freeatlantis.com",
|
||||||
|
"is.nota.live",
|
||||||
|
"mastodon.org.uk",
|
||||||
|
"mastodon.arch-linux.cz",
|
||||||
|
"mona.do",
|
||||||
|
"tyrol.social",
|
||||||
|
"mstdn.id",
|
||||||
|
"mastodon.uy",
|
||||||
|
"mastodon.in.th",
|
||||||
|
"kurry.social",
|
||||||
|
"toot.cafe",
|
||||||
|
"shelter.moe",
|
||||||
|
"social.politicaconciencia.org",
|
||||||
|
"h-net.social",
|
||||||
|
"mstdn.mx",
|
||||||
|
"kopiti.am",
|
||||||
|
"mastodon.vlaanderen",
|
||||||
|
"mao.mastodonhub.com",
|
||||||
|
"cloud-native.social",
|
||||||
|
"mograph.social",
|
||||||
|
"oc.todon.fr",
|
||||||
|
"ura-mstdn.com",
|
||||||
|
"uri.life",
|
||||||
|
"liberdon.com",
|
||||||
|
"kinkyelephant.com",
|
||||||
|
"nojack.easydns.ca",
|
||||||
|
"mastodon.be",
|
||||||
|
"podcastindex.social",
|
||||||
|
"blacktwitter.io",
|
||||||
|
"awoo.space",
|
||||||
|
"woof.group",
|
||||||
|
"ani.work",
|
||||||
|
"colorid.es",
|
||||||
|
"seo.chat",
|
||||||
|
"mental.social",
|
||||||
|
"plural.cafe",
|
||||||
|
"ika.queloud.net",
|
||||||
|
"mastodon.com.br",
|
||||||
|
"mstdn.tokyocameraclub.com",
|
||||||
|
"donphan.social",
|
||||||
|
"gensokyo.town",
|
||||||
|
"ichiji.social",
|
||||||
|
"sunbeam.city",
|
||||||
|
"mstdn.kemono-friends.info",
|
||||||
|
"littlefo.rest",
|
||||||
|
"kirakiratter.com",
|
||||||
|
"uwu.social",
|
||||||
|
"elekk.xyz",
|
||||||
|
"hispagatos.space",
|
||||||
|
"hello.2heng.xin",
|
||||||
|
"the.fores.top",
|
||||||
|
"mstdn.fr",
|
||||||
|
"mastodon.mnetwork.co.kr",
|
||||||
|
"mastodon.gougere.fr",
|
||||||
|
"dobbs.town",
|
||||||
|
"gameliberty.club",
|
||||||
|
"gensokyo.social",
|
||||||
|
"mathtod.online",
|
||||||
|
"mastodon.cc",
|
||||||
|
"iztasocial.site",
|
||||||
|
"mastodon.pirateparty.be",
|
||||||
|
"dingdash.com",
|
||||||
|
"mastodon.partipirate.org",
|
||||||
|
"oulipo.social",
|
||||||
|
"anticapitalist.party",
|
||||||
|
"kemonodon.club",
|
||||||
|
"toot.turbo.chat",
|
||||||
|
"photodn.net",
|
||||||
|
"otogamer.me",
|
||||||
|
"bear.community",
|
||||||
|
"tablegame.mstdn.cloud",
|
||||||
|
"anarchism.space",
|
||||||
|
"ffxiv-mastodon.com",
|
||||||
|
"lgbt.io",
|
||||||
|
"lou.lt",
|
||||||
|
"social.chinwag.org",
|
||||||
|
"chinwag.org",
|
||||||
|
"aleph.land",
|
||||||
|
"social.slat.org",
|
||||||
|
"mastodon.juggler.jp",
|
||||||
|
"eigadon.net",
|
||||||
|
"vocalounge.cafe",
|
||||||
|
"acg.mn",
|
||||||
|
"acg.debula.ml",
|
||||||
|
"eletusk.club",
|
||||||
|
"otoya.space",
|
||||||
|
"social.coletivos.org",
|
||||||
|
"mastodon.cipherbliss.com",
|
||||||
|
"truthsocial.co.in",
|
||||||
|
"mstdn.osaka",
|
||||||
|
"social.targaryen.house",
|
||||||
|
"catdon.life",
|
||||||
|
"stereodon.social",
|
||||||
|
"social.opendesktop.org",
|
||||||
|
"nasface.cz",
|
||||||
|
"toot.site",
|
||||||
|
"fetswing.org",
|
||||||
|
"vipgirlfriend.xxx",
|
||||||
|
"mastodon.elte.hu",
|
||||||
|
"bgme.me",
|
||||||
|
"kinbaku.club",
|
||||||
|
"m.rthome.me",
|
||||||
|
"animalliberation.social",
|
||||||
|
"mastodon.librelabucm.org",
|
||||||
|
"mastodon.gza.jp",
|
||||||
|
"med-mammoth.com",
|
||||||
|
"hearthtodon.com",
|
||||||
|
"counter.social",
|
||||||
"kfem.cat",
|
"kfem.cat",
|
||||||
"federated.press"
|
"pet123.club",
|
||||||
|
"beta.woof.group",
|
||||||
|
"explosion.party",
|
||||||
|
"id.cc",
|
||||||
|
"freespeechextremist.com",
|
||||||
|
"cawfee.club",
|
||||||
|
"1234.as",
|
||||||
|
"fedi.absturztau.be",
|
||||||
|
"fsmi.social",
|
||||||
|
"go5.dev",
|
||||||
|
"poa.st",
|
||||||
|
"patriot.online",
|
||||||
|
"seaofog.com",
|
||||||
|
"libranet.de",
|
||||||
|
"tea.codes",
|
||||||
|
"pixelfed.social",
|
||||||
|
"shitposter.club",
|
||||||
|
"squeet.me",
|
||||||
|
"shared.graphics",
|
||||||
|
"glindr.org",
|
||||||
|
"pxlmo.com",
|
||||||
|
"pixel.tchncs.de",
|
||||||
|
"love.alicecomplex.com",
|
||||||
|
"friendica.eskimo.com",
|
||||||
|
"meatbag.app",
|
||||||
|
"fediverse.bbad.com",
|
||||||
|
"pix.toot.wales",
|
||||||
|
"fgc.network",
|
||||||
|
"bookrastinating.com",
|
||||||
|
"pixey.org",
|
||||||
|
"pixelfed.tokyo",
|
||||||
|
"chudbuds.lol",
|
||||||
|
"freeframe.masto.host",
|
||||||
|
"varishangout.net",
|
||||||
|
"friendica.vrije-mens.org",
|
||||||
|
"bae.st",
|
||||||
|
"brighteon.social",
|
||||||
|
"pixelfed.uno",
|
||||||
|
"helladoge.com",
|
||||||
|
"donotban.com",
|
||||||
|
"bookwyrm.social",
|
||||||
|
"spinster.xyz",
|
||||||
|
"pixelfed.de",
|
||||||
|
"metapixl.com",
|
||||||
|
"venera.social",
|
||||||
|
"blob.cat",
|
||||||
|
"onevery.ignorelist.com",
|
||||||
|
"cliq.buzz",
|
||||||
|
"pxl.roflcopter.fr",
|
||||||
|
"p.1069-3.com",
|
||||||
|
"www2.patriot.online",
|
||||||
|
"gc2.jp",
|
||||||
|
"soap.shitposter.club",
|
||||||
|
"www.mastodon.scot"
|
||||||
]
|
]
|
|
@ -3,7 +3,7 @@
|
||||||
|
|
||||||
--blue-color: royalblue;
|
--blue-color: royalblue;
|
||||||
--purple-color: blueviolet;
|
--purple-color: blueviolet;
|
||||||
--green-color: green;
|
--green-color: darkgreen;
|
||||||
--orange-color: darkorange;
|
--orange-color: darkorange;
|
||||||
--red-color: orangered;
|
--red-color: orangered;
|
||||||
--bg-color: #fff;
|
--bg-color: #fff;
|
||||||
|
@ -41,7 +41,7 @@
|
||||||
:root {
|
:root {
|
||||||
--blue-color: CornflowerBlue;
|
--blue-color: CornflowerBlue;
|
||||||
--purple-color: mediumpurple;
|
--purple-color: mediumpurple;
|
||||||
--green-color: limegreen;
|
--green-color: lightgreen;
|
||||||
--orange-color: orange;
|
--orange-color: orange;
|
||||||
--bg-color: #242526;
|
--bg-color: #242526;
|
||||||
--bg-faded-color: #18191a;
|
--bg-faded-color: #18191a;
|
||||||
|
@ -123,6 +123,7 @@ button,
|
||||||
line-height: 1;
|
line-height: 1;
|
||||||
vertical-align: middle;
|
vertical-align: middle;
|
||||||
text-decoration: none;
|
text-decoration: none;
|
||||||
|
user-select: none;
|
||||||
}
|
}
|
||||||
:is(button, .button) > * {
|
:is(button, .button) > * {
|
||||||
vertical-align: middle;
|
vertical-align: middle;
|
||||||
|
|
|
@ -1,6 +1,5 @@
|
||||||
import './index.css';
|
import './index.css';
|
||||||
|
|
||||||
import '@github/relative-time-element';
|
|
||||||
import { render } from 'preact';
|
import { render } from 'preact';
|
||||||
|
|
||||||
import { App } from './app';
|
import { App } from './app';
|
||||||
|
|
|
@ -8,6 +8,8 @@ import Icon from '../components/icon';
|
||||||
import Loader from '../components/loader';
|
import Loader from '../components/loader';
|
||||||
import Status from '../components/status';
|
import Status from '../components/status';
|
||||||
import states from '../utils/states';
|
import states from '../utils/states';
|
||||||
|
import useDebouncedCallback from '../utils/useDebouncedCallback';
|
||||||
|
import useScroll from '../utils/useScroll';
|
||||||
|
|
||||||
const LIMIT = 20;
|
const LIMIT = 20;
|
||||||
|
|
||||||
|
@ -27,6 +29,7 @@ function Home({ hidden }) {
|
||||||
homeIterator.current = masto.v1.timelines.listHome({
|
homeIterator.current = masto.v1.timelines.listHome({
|
||||||
limit: LIMIT,
|
limit: LIMIT,
|
||||||
});
|
});
|
||||||
|
states.homeNew = [];
|
||||||
}
|
}
|
||||||
const allStatuses = await homeIterator.current.next();
|
const allStatuses = await homeIterator.current.next();
|
||||||
if (allStatuses.value <= 0) {
|
if (allStatuses.value <= 0) {
|
||||||
|
@ -52,7 +55,10 @@ function Home({ hidden }) {
|
||||||
return allStatuses;
|
return allStatuses;
|
||||||
}
|
}
|
||||||
|
|
||||||
const loadStatuses = (firstLoad) => {
|
const loadingStatuses = useRef(false);
|
||||||
|
const loadStatuses = useDebouncedCallback((firstLoad) => {
|
||||||
|
if (loadingStatuses.current) return;
|
||||||
|
loadingStatuses.current = true;
|
||||||
setUIState('loading');
|
setUIState('loading');
|
||||||
(async () => {
|
(async () => {
|
||||||
try {
|
try {
|
||||||
|
@ -62,9 +68,11 @@ function Home({ hidden }) {
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
console.warn(e);
|
console.warn(e);
|
||||||
setUIState('error');
|
setUIState('error');
|
||||||
|
} finally {
|
||||||
|
loadingStatuses.current = false;
|
||||||
}
|
}
|
||||||
})();
|
})();
|
||||||
};
|
}, 1000);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
loadStatuses(true);
|
loadStatuses(true);
|
||||||
|
@ -154,6 +162,25 @@ function Home({ hidden }) {
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
const { scrollDirection, reachTop, nearReachTop, nearReachBottom } =
|
||||||
|
useScroll({
|
||||||
|
scrollableElement: scrollableRef.current,
|
||||||
|
distanceFromTop: 0.1,
|
||||||
|
distanceFromBottom: 0.15,
|
||||||
|
});
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (nearReachBottom && showMore) {
|
||||||
|
loadStatuses();
|
||||||
|
}
|
||||||
|
}, [nearReachBottom]);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (reachTop) {
|
||||||
|
loadStatuses(true);
|
||||||
|
}
|
||||||
|
}, [reachTop]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div
|
<div
|
||||||
id="home-page"
|
id="home-page"
|
||||||
|
@ -162,8 +189,27 @@ function Home({ hidden }) {
|
||||||
ref={scrollableRef}
|
ref={scrollableRef}
|
||||||
tabIndex="-1"
|
tabIndex="-1"
|
||||||
>
|
>
|
||||||
|
<button
|
||||||
|
hidden={scrollDirection === 'down' && !nearReachTop}
|
||||||
|
type="button"
|
||||||
|
id="compose-button"
|
||||||
|
onClick={(e) => {
|
||||||
|
if (e.shiftKey) {
|
||||||
|
const newWin = openCompose();
|
||||||
|
if (!newWin) {
|
||||||
|
alert('Looks like your browser is blocking popups.');
|
||||||
|
states.showCompose = true;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
states.showCompose = true;
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<Icon icon="quill" size="xxl" alt="Compose" />
|
||||||
|
</button>
|
||||||
<div class="timeline-deck deck">
|
<div class="timeline-deck deck">
|
||||||
<header
|
<header
|
||||||
|
hidden={scrollDirection === 'down' && !nearReachTop}
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
scrollableRef.current?.scrollTo({ top: 0, behavior: 'smooth' });
|
scrollableRef.current?.scrollTo({ top: 0, behavior: 'smooth' });
|
||||||
}}
|
}}
|
||||||
|
@ -200,27 +246,30 @@ function Home({ hidden }) {
|
||||||
</a>
|
</a>
|
||||||
</div>
|
</div>
|
||||||
</header>
|
</header>
|
||||||
{snapStates.homeNew.length > 0 && (
|
{snapStates.homeNew.length > 0 &&
|
||||||
<button
|
scrollDirection === 'up' &&
|
||||||
class="updates-button"
|
!nearReachTop &&
|
||||||
type="button"
|
!nearReachBottom && (
|
||||||
onClick={() => {
|
<button
|
||||||
const uniqueHomeNew = snapStates.homeNew.filter(
|
class="updates-button"
|
||||||
(status) => !states.home.some((s) => s.id === status.id),
|
type="button"
|
||||||
);
|
onClick={() => {
|
||||||
states.home.unshift(...uniqueHomeNew);
|
const uniqueHomeNew = snapStates.homeNew.filter(
|
||||||
loadStatuses(true);
|
(status) => !states.home.some((s) => s.id === status.id),
|
||||||
states.homeNew = [];
|
);
|
||||||
|
states.home.unshift(...uniqueHomeNew);
|
||||||
|
loadStatuses(true);
|
||||||
|
states.homeNew = [];
|
||||||
|
|
||||||
scrollableRef.current?.scrollTo({
|
scrollableRef.current?.scrollTo({
|
||||||
top: 0,
|
top: 0,
|
||||||
behavior: 'smooth',
|
behavior: 'smooth',
|
||||||
});
|
});
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<Icon icon="arrow-up" /> New posts
|
<Icon icon="arrow-up" /> New posts
|
||||||
</button>
|
</button>
|
||||||
)}
|
)}
|
||||||
{snapStates.home.length ? (
|
{snapStates.home.length ? (
|
||||||
<>
|
<>
|
||||||
<ul class="timeline">
|
<ul class="timeline">
|
||||||
|
@ -240,7 +289,7 @@ function Home({ hidden }) {
|
||||||
})}
|
})}
|
||||||
{showMore && (
|
{showMore && (
|
||||||
<>
|
<>
|
||||||
<InView
|
{/* <InView
|
||||||
as="li"
|
as="li"
|
||||||
style={{
|
style={{
|
||||||
height: '20vh',
|
height: '20vh',
|
||||||
|
@ -250,9 +299,15 @@ function Home({ hidden }) {
|
||||||
}}
|
}}
|
||||||
root={scrollableRef.current}
|
root={scrollableRef.current}
|
||||||
rootMargin="100px 0px"
|
rootMargin="100px 0px"
|
||||||
|
> */}
|
||||||
|
<li
|
||||||
|
style={{
|
||||||
|
height: '20vh',
|
||||||
|
}}
|
||||||
>
|
>
|
||||||
<Status skeleton />
|
<Status skeleton />
|
||||||
</InView>
|
</li>
|
||||||
|
{/* </InView> */}
|
||||||
<li
|
<li
|
||||||
style={{
|
style={{
|
||||||
height: '25vh',
|
height: '25vh',
|
||||||
|
|
|
@ -3,7 +3,7 @@ import './login.css';
|
||||||
import { useEffect, useRef, useState } from 'preact/hooks';
|
import { useEffect, useRef, useState } from 'preact/hooks';
|
||||||
|
|
||||||
import Loader from '../components/loader';
|
import Loader from '../components/loader';
|
||||||
import instancesList from '../data/instances.json';
|
import instancesListURL from '../data/instances.json?url';
|
||||||
import { getAuthorizationURL, registerApplication } from '../utils/auth';
|
import { getAuthorizationURL, registerApplication } from '../utils/auth';
|
||||||
import store from '../utils/store';
|
import store from '../utils/store';
|
||||||
import useTitle from '../utils/useTitle';
|
import useTitle from '../utils/useTitle';
|
||||||
|
@ -14,16 +14,30 @@ function Login() {
|
||||||
const cachedInstanceURL = store.local.get('instanceURL');
|
const cachedInstanceURL = store.local.get('instanceURL');
|
||||||
const [uiState, setUIState] = useState('default');
|
const [uiState, setUIState] = useState('default');
|
||||||
|
|
||||||
|
const [instancesList, setInstancesList] = useState([]);
|
||||||
|
useEffect(() => {
|
||||||
|
(async () => {
|
||||||
|
try {
|
||||||
|
const res = await fetch(instancesListURL);
|
||||||
|
const data = await res.json();
|
||||||
|
setInstancesList(data);
|
||||||
|
} catch (e) {
|
||||||
|
// Silently fail
|
||||||
|
console.error(e);
|
||||||
|
}
|
||||||
|
})();
|
||||||
|
}, []);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (cachedInstanceURL) {
|
if (cachedInstanceURL) {
|
||||||
instanceURLRef.current.value = cachedInstanceURL;
|
instanceURLRef.current.value = cachedInstanceURL.toLowerCase();
|
||||||
}
|
}
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
const onSubmit = (e) => {
|
const onSubmit = (e) => {
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
const { elements } = e.target;
|
const { elements } = e.target;
|
||||||
let instanceURL = elements.instanceURL.value;
|
let instanceURL = elements.instanceURL.value.toLowerCase();
|
||||||
// Remove protocol from instance URL
|
// Remove protocol from instance URL
|
||||||
instanceURL = instanceURL.replace(/(^\w+:|^)\/\//, '');
|
instanceURL = instanceURL.replace(/(^\w+:|^)\/\//, '');
|
||||||
store.local.set('instanceURL', instanceURL);
|
store.local.set('instanceURL', instanceURL);
|
||||||
|
@ -68,6 +82,10 @@ function Login() {
|
||||||
ref={instanceURLRef}
|
ref={instanceURLRef}
|
||||||
disabled={uiState === 'loading'}
|
disabled={uiState === 'loading'}
|
||||||
list="instances-list"
|
list="instances-list"
|
||||||
|
autocorrect="off"
|
||||||
|
autocapitalize="off"
|
||||||
|
autocomplete="off"
|
||||||
|
spellcheck="false"
|
||||||
/>
|
/>
|
||||||
<datalist id="instances-list">
|
<datalist id="instances-list">
|
||||||
{instancesList.map((instance) => (
|
{instancesList.map((instance) => (
|
||||||
|
|
|
@ -20,41 +20,56 @@
|
||||||
opacity: 0.75;
|
opacity: 0.75;
|
||||||
color: var(--text-insignificant-color);
|
color: var(--text-insignificant-color);
|
||||||
}
|
}
|
||||||
|
.notification-type.notification-mention {
|
||||||
|
color: var(--reply-to-color);
|
||||||
|
}
|
||||||
.notification-type.notification-favourite {
|
.notification-type.notification-favourite {
|
||||||
color: var(--favourite-color);
|
color: var(--favourite-color);
|
||||||
}
|
}
|
||||||
.notification-type.notification-reblog {
|
.notification-type.notification-reblog {
|
||||||
color: var(--reblog-color);
|
color: var(--reblog-color);
|
||||||
}
|
}
|
||||||
.notification-type.notification-poll,
|
.notification-type.notification-poll {
|
||||||
.notification-type.notification-mention {
|
|
||||||
color: var(--link-color);
|
color: var(--link-color);
|
||||||
}
|
}
|
||||||
|
|
||||||
.notification .status-link {
|
.notification .status-link {
|
||||||
border-radius: 8px 8px 0 0;
|
border-radius: 8px;
|
||||||
border: 1px solid var(--outline-color);
|
border: 1px solid var(--outline-color);
|
||||||
|
max-height: 160px;
|
||||||
|
overflow: hidden;
|
||||||
|
filter: saturate(0.25);
|
||||||
|
}
|
||||||
|
.notification .status-link:not(.status-type-mention) > .status {
|
||||||
max-height: 160px;
|
max-height: 160px;
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
/* fade out mask gradient bottom */
|
/* fade out mask gradient bottom */
|
||||||
mask-image: linear-gradient(
|
mask-image: linear-gradient(
|
||||||
rgba(0, 0, 0, 1),
|
rgba(0, 0, 0, 1) 130px,
|
||||||
rgba(0, 0, 0, 1) 50%,
|
rgba(0, 0, 0, 0.5) 145px,
|
||||||
transparent
|
transparent 159px
|
||||||
);
|
);
|
||||||
filter: saturate(0.25);
|
|
||||||
}
|
}
|
||||||
.notification .status-link.status-type-mention {
|
.notification .status-link.status-type-mention {
|
||||||
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);
|
margin-top: calc(-16px - 1px);
|
||||||
|
border-color: var(--reply-to-color);
|
||||||
|
box-shadow: 0 0 0 3px var(--reply-to-faded-color);
|
||||||
}
|
}
|
||||||
.notification .status-link:is(:hover, :focus) {
|
.notification .status-link:is(:hover, :focus) {
|
||||||
background-color: var(--bg-blur-color);
|
background-color: var(--bg-blur-color);
|
||||||
filter: saturate(1);
|
filter: saturate(1);
|
||||||
border-color: var(--outline-hover-color);
|
border-color: var(--outline-hover-color);
|
||||||
}
|
}
|
||||||
|
.notification .status-link.status-type-mention:is(:hover, :focus) {
|
||||||
|
border-color: var(--reply-to-color);
|
||||||
|
box-shadow: 0 0 5px var(--reply-to-color);
|
||||||
|
}
|
||||||
|
.notification .status-link:active {
|
||||||
|
filter: brightness(0.95);
|
||||||
|
}
|
||||||
.notification .status-link > * {
|
.notification .status-link > * {
|
||||||
pointer-events: none;
|
pointer-events: none;
|
||||||
}
|
}
|
||||||
|
|
|
@ -8,6 +8,7 @@ import Avatar from '../components/avatar';
|
||||||
import Icon from '../components/icon';
|
import Icon from '../components/icon';
|
||||||
import Loader from '../components/loader';
|
import Loader from '../components/loader';
|
||||||
import NameText from '../components/name-text';
|
import NameText from '../components/name-text';
|
||||||
|
import RelativeTime from '../components/relative-time';
|
||||||
import Status from '../components/status';
|
import Status from '../components/status';
|
||||||
import states from '../utils/states';
|
import states from '../utils/states';
|
||||||
import store from '../utils/store';
|
import store from '../utils/store';
|
||||||
|
@ -102,18 +103,16 @@ function Notification({ notification }) {
|
||||||
<span class="insignificant">
|
<span class="insignificant">
|
||||||
{' '}
|
{' '}
|
||||||
•{' '}
|
•{' '}
|
||||||
<relative-time
|
<RelativeTime
|
||||||
datetime={notification.createdAt}
|
datetime={notification.createdAt}
|
||||||
format="micro"
|
format="micro"
|
||||||
threshold="P1D"
|
|
||||||
prefix=""
|
|
||||||
/>
|
/>
|
||||||
</span>
|
</span>
|
||||||
)}
|
)}
|
||||||
</p>
|
</p>
|
||||||
)}
|
)}
|
||||||
{_accounts?.length > 1 && (
|
{_accounts?.length > 1 && (
|
||||||
<p>
|
<p class="avatars-stack">
|
||||||
{_accounts.map((account, i) => (
|
{_accounts.map((account, i) => (
|
||||||
<>
|
<>
|
||||||
<a
|
<a
|
||||||
|
@ -127,7 +126,7 @@ function Notification({ notification }) {
|
||||||
<Avatar
|
<Avatar
|
||||||
url={account.avatarStatic}
|
url={account.avatarStatic}
|
||||||
size={
|
size={
|
||||||
_accounts.length < 10
|
_accounts.length < 30
|
||||||
? 'xl'
|
? 'xl'
|
||||||
: _accounts.length < 100
|
: _accounts.length < 100
|
||||||
? 'l'
|
? 'l'
|
||||||
|
@ -164,28 +163,23 @@ function NotificationsList({ notifications, emptyCopy }) {
|
||||||
// Create new flat list of notifications
|
// Create new flat list of notifications
|
||||||
// Combine sibling notifications based on type and status id, ignore the id
|
// Combine sibling notifications based on type and status id, ignore the id
|
||||||
// Concat all notification.account into an array of _accounts
|
// Concat all notification.account into an array of _accounts
|
||||||
const cleanNotifications = [
|
const notificationsMap = {};
|
||||||
{
|
const cleanNotifications = [];
|
||||||
...notifications[0],
|
for (let i = 0, j = 0; i < notifications.length; i++) {
|
||||||
_accounts: [notifications[0].account],
|
|
||||||
},
|
|
||||||
];
|
|
||||||
for (let i = 1, j = 0; i < notifications.length; i++) {
|
|
||||||
const notification = notifications[i];
|
const notification = notifications[i];
|
||||||
const cleanNotification = cleanNotifications[j];
|
// const cleanNotification = cleanNotifications[j];
|
||||||
const { status, account, type } = notification;
|
const { status, account, type, created_at } = notification;
|
||||||
if (
|
const createdAt = new Date(created_at).toLocaleDateString();
|
||||||
account &&
|
const key = `${status?.id}-${type}-${createdAt}`;
|
||||||
cleanNotification?.account &&
|
const mappedNotification = notificationsMap[key];
|
||||||
cleanNotification?.status?.id === status?.id &&
|
if (mappedNotification?.account) {
|
||||||
cleanNotification?.type === type
|
mappedNotification._accounts.push(account);
|
||||||
) {
|
|
||||||
cleanNotification._accounts.push(account);
|
|
||||||
} else {
|
} else {
|
||||||
cleanNotifications[++j] = {
|
let n = (notificationsMap[key] = {
|
||||||
...notification,
|
...notification,
|
||||||
_accounts: [account],
|
_accounts: [account],
|
||||||
};
|
});
|
||||||
|
cleanNotifications[j++] = n;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// console.log({ notifications, cleanNotifications });
|
// console.log({ notifications, cleanNotifications });
|
||||||
|
@ -222,6 +216,7 @@ function Notifications() {
|
||||||
notificationsIterator.current = masto.v1.notifications.list({
|
notificationsIterator.current = masto.v1.notifications.list({
|
||||||
limit: LIMIT,
|
limit: LIMIT,
|
||||||
});
|
});
|
||||||
|
states.notificationsNew = [];
|
||||||
}
|
}
|
||||||
const allNotifications = await notificationsIterator.current.next();
|
const allNotifications = await notificationsIterator.current.next();
|
||||||
if (allNotifications.value <= 0) {
|
if (allNotifications.value <= 0) {
|
||||||
|
@ -257,7 +252,6 @@ function Notifications() {
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
loadNotifications(true);
|
loadNotifications(true);
|
||||||
states.notificationsNew = [];
|
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
const scrollableRef = useRef();
|
const scrollableRef = useRef();
|
||||||
|
|
|
@ -5,6 +5,7 @@ import { useRef, useState } from 'preact/hooks';
|
||||||
import Avatar from '../components/avatar';
|
import Avatar from '../components/avatar';
|
||||||
import Icon from '../components/icon';
|
import Icon from '../components/icon';
|
||||||
import NameText from '../components/name-text';
|
import NameText from '../components/name-text';
|
||||||
|
import RelativeTime from '../components/relative-time';
|
||||||
import states from '../utils/states';
|
import states from '../utils/states';
|
||||||
import store from '../utils/store';
|
import store from '../utils/store';
|
||||||
|
|
||||||
|
@ -196,8 +197,7 @@ function Settings({ onClose }) {
|
||||||
</p>
|
</p>
|
||||||
{__BUILD_TIME__ && (
|
{__BUILD_TIME__ && (
|
||||||
<p>
|
<p>
|
||||||
Last build:{' '}
|
Last build: <RelativeTime datetime={new Date(__BUILD_TIME__)} />{' '}
|
||||||
<relative-time datetime={new Date(__BUILD_TIME__).toISOString()} />{' '}
|
|
||||||
{__COMMIT_HASH__ && (
|
{__COMMIT_HASH__ && (
|
||||||
<>
|
<>
|
||||||
(
|
(
|
||||||
|
|
|
@ -17,6 +17,7 @@ import { useSnapshot } from 'valtio';
|
||||||
import Icon from '../components/icon';
|
import Icon from '../components/icon';
|
||||||
import Loader from '../components/loader';
|
import Loader from '../components/loader';
|
||||||
import NameText from '../components/name-text';
|
import NameText from '../components/name-text';
|
||||||
|
import RelativeTime from '../components/relative-time';
|
||||||
import Status from '../components/status';
|
import Status from '../components/status';
|
||||||
import htmlContentLength from '../utils/html-content-length';
|
import htmlContentLength from '../utils/html-content-length';
|
||||||
import shortenNumber from '../utils/shorten-number';
|
import shortenNumber from '../utils/shorten-number';
|
||||||
|
@ -54,7 +55,7 @@ function StatusPage({ id }) {
|
||||||
};
|
};
|
||||||
}, [id]);
|
}, [id]);
|
||||||
|
|
||||||
useEffect(() => {
|
const initContext = () => {
|
||||||
setUIState('loading');
|
setUIState('loading');
|
||||||
let heroTimer;
|
let heroTimer;
|
||||||
|
|
||||||
|
@ -173,7 +174,30 @@ function StatusPage({ id }) {
|
||||||
return () => {
|
return () => {
|
||||||
clearTimeout(heroTimer);
|
clearTimeout(heroTimer);
|
||||||
};
|
};
|
||||||
}, [id, snapStates.reloadStatusPage]);
|
};
|
||||||
|
|
||||||
|
useEffect(initContext, [id]);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
// Delete the cache for the context
|
||||||
|
(async () => {
|
||||||
|
try {
|
||||||
|
const accounts = store.local.getJSON('accounts') || [];
|
||||||
|
const currentAccount = store.session.get('currentAccount');
|
||||||
|
const account =
|
||||||
|
accounts.find((a) => a.info.id === currentAccount) || accounts[0];
|
||||||
|
const instanceURL = account.instanceURL;
|
||||||
|
const contextURL = `https://${instanceURL}/api/v1/statuses/${id}/context`;
|
||||||
|
console.log('Clear cache', contextURL);
|
||||||
|
const apiCache = await caches.open('api');
|
||||||
|
await apiCache.delete(contextURL, { ignoreVary: true });
|
||||||
|
|
||||||
|
return initContext();
|
||||||
|
} catch (e) {
|
||||||
|
console.error(e);
|
||||||
|
}
|
||||||
|
})();
|
||||||
|
}, [snapStates.reloadStatusPage]);
|
||||||
|
|
||||||
const firstLoad = useRef(true);
|
const firstLoad = useRef(true);
|
||||||
|
|
||||||
|
@ -280,7 +304,7 @@ function StatusPage({ id }) {
|
||||||
}, [heroInView]);
|
}, [heroInView]);
|
||||||
|
|
||||||
useHotkeys(['esc', 'backspace'], () => {
|
useHotkeys(['esc', 'backspace'], () => {
|
||||||
route(closeLink);
|
location.hash = closeLink;
|
||||||
});
|
});
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
@ -325,11 +349,9 @@ function StatusPage({ id }) {
|
||||||
<NameText showAvatar account={heroStatus.account} short />{' '}
|
<NameText showAvatar account={heroStatus.account} short />{' '}
|
||||||
<span class="insignificant">
|
<span class="insignificant">
|
||||||
•{' '}
|
•{' '}
|
||||||
<relative-time
|
<RelativeTime
|
||||||
datetime={heroStatus.createdAt}
|
datetime={heroStatus.createdAt}
|
||||||
format="micro"
|
format="micro"
|
||||||
threshold="P1D"
|
|
||||||
prefix=""
|
|
||||||
/>
|
/>
|
||||||
</span>
|
</span>
|
||||||
</span>
|
</span>
|
||||||
|
|
|
@ -18,4 +18,5 @@ export default proxy({
|
||||||
showCompose: false,
|
showCompose: false,
|
||||||
showSettings: false,
|
showSettings: false,
|
||||||
showAccount: false,
|
showAccount: false,
|
||||||
|
composeCharacterCount: 0,
|
||||||
});
|
});
|
||||||
|
|
43
src/utils/useScroll.js
Normal file
43
src/utils/useScroll.js
Normal file
|
@ -0,0 +1,43 @@
|
||||||
|
import { useEffect, useState } from 'preact/hooks';
|
||||||
|
|
||||||
|
export default function useScroll({
|
||||||
|
scrollableElement = window,
|
||||||
|
distanceFromTop = 0,
|
||||||
|
distanceFromBottom = 0,
|
||||||
|
scrollThreshold = 10,
|
||||||
|
} = {}) {
|
||||||
|
const [scrollDirection, setScrollDirection] = useState(null);
|
||||||
|
const [reachTop, setReachTop] = useState(false);
|
||||||
|
const [nearReachTop, setNearReachTop] = useState(false);
|
||||||
|
const [nearReachBottom, setNearReachBottom] = useState(false);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
let previousScrollTop = scrollableElement.scrollTop;
|
||||||
|
|
||||||
|
function onScroll() {
|
||||||
|
const { scrollTop, scrollHeight, clientHeight } = scrollableElement;
|
||||||
|
const scrollDistance = Math.abs(scrollTop - previousScrollTop);
|
||||||
|
const distanceFromTopPx =
|
||||||
|
scrollHeight * Math.min(1, Math.max(0, distanceFromTop));
|
||||||
|
const distanceFromBottomPx =
|
||||||
|
scrollHeight * Math.min(1, Math.max(0, distanceFromBottom));
|
||||||
|
|
||||||
|
if (scrollDistance >= scrollThreshold) {
|
||||||
|
setScrollDirection(previousScrollTop < scrollTop ? 'down' : 'up');
|
||||||
|
previousScrollTop = scrollTop;
|
||||||
|
}
|
||||||
|
|
||||||
|
setReachTop(scrollTop === 0);
|
||||||
|
setNearReachTop(scrollTop <= distanceFromTopPx);
|
||||||
|
setNearReachBottom(
|
||||||
|
scrollTop + clientHeight >= scrollHeight - distanceFromBottomPx,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
scrollableElement.addEventListener('scroll', onScroll, { passive: true });
|
||||||
|
|
||||||
|
return () => scrollableElement.removeEventListener('scroll', onScroll);
|
||||||
|
}, [scrollableElement, distanceFromTop, distanceFromBottom, scrollThreshold]);
|
||||||
|
|
||||||
|
return { scrollDirection, reachTop, nearReachTop, nearReachBottom };
|
||||||
|
}
|
|
@ -6,12 +6,11 @@ import { defineConfig, loadEnv, splitVendorChunkPlugin } from 'vite';
|
||||||
import htmlPlugin from 'vite-plugin-html-config';
|
import htmlPlugin from 'vite-plugin-html-config';
|
||||||
import VitePluginHtmlEnv from 'vite-plugin-html-env';
|
import VitePluginHtmlEnv from 'vite-plugin-html-env';
|
||||||
import { VitePWA } from 'vite-plugin-pwa';
|
import { VitePWA } from 'vite-plugin-pwa';
|
||||||
|
import removeConsole from 'vite-plugin-remove-console';
|
||||||
|
|
||||||
const {
|
const { NODE_ENV } = process.env;
|
||||||
VITE_CLIENT_NAME: CLIENT_NAME,
|
const { VITE_CLIENT_NAME: CLIENT_NAME, VITE_APP_ERROR_LOGGING: ERROR_LOGGING } =
|
||||||
NODE_ENV,
|
loadEnv('production', process.cwd());
|
||||||
VITE_APP_ERROR_LOGGING,
|
|
||||||
} = loadEnv('production', process.cwd());
|
|
||||||
|
|
||||||
const commitHash = execSync('git rev-parse --short HEAD').toString().trim();
|
const commitHash = execSync('git rev-parse --short HEAD').toString().trim();
|
||||||
|
|
||||||
|
@ -31,8 +30,9 @@ export default defineConfig({
|
||||||
preact(),
|
preact(),
|
||||||
splitVendorChunkPlugin(),
|
splitVendorChunkPlugin(),
|
||||||
VitePluginHtmlEnv(),
|
VitePluginHtmlEnv(),
|
||||||
|
removeConsole(),
|
||||||
htmlPlugin({
|
htmlPlugin({
|
||||||
headScripts: VITE_APP_ERROR_LOGGING ? [rollbarCode] : [],
|
headScripts: ERROR_LOGGING ? [rollbarCode] : [],
|
||||||
}),
|
}),
|
||||||
VitePWA({
|
VitePWA({
|
||||||
manifest: {
|
manifest: {
|
||||||
|
|
Loading…
Reference in a new issue