Compare commits
44 commits
531df4cafa
...
a4240732d6
Author | SHA1 | Date | |
---|---|---|---|
|
a4240732d6 | ||
|
6d7eddc568 | ||
|
dac2af4334 | ||
|
2099953b68 | ||
|
5931ebb8fc | ||
|
adcb87679b | ||
|
5ead17a093 | ||
|
224cad4d7f | ||
|
e08817d611 | ||
|
1ffc1c257a | ||
|
098014a109 | ||
|
7546b42c7c | ||
|
f9a73777e7 | ||
|
d5584f8dd4 | ||
|
563b06e680 | ||
|
b6a64b66c7 | ||
|
0a4aae51b7 | ||
|
d16221e296 | ||
|
ed712d15f1 | ||
|
bd8817e61b | ||
|
ef712c62a9 | ||
|
9aa2bac685 | ||
|
34077e8467 | ||
|
b473061845 | ||
|
64c7b5b4f0 | ||
|
c11bbbb2b3 | ||
|
2c1a6c8cb5 | ||
|
67a85e1eef | ||
|
2e0ef6494b | ||
|
012b86d7ce | ||
|
0c45f515f0 | ||
|
9cc590be1b | ||
|
7589ec8803 | ||
|
cd17ca0b42 | ||
|
8aab997900 | ||
|
96c44ed485 | ||
|
7053fcc96a | ||
|
ad7cb46547 | ||
|
1b1af67064 | ||
|
bdd238de0e | ||
|
ced4dc86aa | ||
|
7be1e589ab | ||
|
7da1745cca | ||
|
025a5429cc |
|
@ -209,6 +209,7 @@ These are self-hosted by other wonderful folks.
|
||||||
- [phanpy.fulda.social](https://phanpy.fulda.social) by [@Ganneff@fulda.social](https://fulda.social/@Ganneff)
|
- [phanpy.fulda.social](https://phanpy.fulda.social) by [@Ganneff@fulda.social](https://fulda.social/@Ganneff)
|
||||||
- [phanpy.crmbl.uk](https://phanpy.crmbl.uk) by [@snail@crmbl.uk](https://mstdn.crmbl.uk/@snail)
|
- [phanpy.crmbl.uk](https://phanpy.crmbl.uk) by [@snail@crmbl.uk](https://mstdn.crmbl.uk/@snail)
|
||||||
- [halo.mookiesplace.com](https://halo.mookiesplace.com) by [@mookie@mookiesplace.com](https://mookiesplace.com/@mookie)
|
- [halo.mookiesplace.com](https://halo.mookiesplace.com) by [@mookie@mookiesplace.com](https://mookiesplace.com/@mookie)
|
||||||
|
- [social.qrk.one](https://social.qrk.one) by [@kev@fosstodon.org](https://fosstodon.org/@kev)
|
||||||
|
|
||||||
> Note: Add yours by creating a pull request.
|
> Note: Add yours by creating a pull request.
|
||||||
|
|
||||||
|
|
139
package-lock.json
generated
139
package-lock.json
generated
|
@ -9,9 +9,9 @@
|
||||||
"version": "0.1.0",
|
"version": "0.1.0",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@formatjs/intl-localematcher": "~0.5.4",
|
"@formatjs/intl-localematcher": "~0.5.4",
|
||||||
"@formatjs/intl-segmenter": "~11.5.5",
|
"@formatjs/intl-segmenter": "~11.5.7",
|
||||||
"@formkit/auto-animate": "~0.8.2",
|
"@formkit/auto-animate": "~0.8.2",
|
||||||
"@github/text-expander-element": "~2.6.1",
|
"@github/text-expander-element": "~2.7.1",
|
||||||
"@iconify-icons/mingcute": "~1.2.9",
|
"@iconify-icons/mingcute": "~1.2.9",
|
||||||
"@justinribeiro/lite-youtube": "~1.5.0",
|
"@justinribeiro/lite-youtube": "~1.5.0",
|
||||||
"@szhsin/react-menu": "~4.1.0",
|
"@szhsin/react-menu": "~4.1.0",
|
||||||
|
@ -30,7 +30,7 @@
|
||||||
"moize": "~6.1.6",
|
"moize": "~6.1.6",
|
||||||
"p-retry": "~6.2.0",
|
"p-retry": "~6.2.0",
|
||||||
"p-throttle": "~6.1.0",
|
"p-throttle": "~6.1.0",
|
||||||
"preact": "~10.21.0",
|
"preact": "~10.22.0",
|
||||||
"punycode": "~2.3.1",
|
"punycode": "~2.3.1",
|
||||||
"react-hotkeys-hook": "~4.5.0",
|
"react-hotkeys-hook": "~4.5.0",
|
||||||
"react-intersection-observer": "~9.10.2",
|
"react-intersection-observer": "~9.10.2",
|
||||||
|
@ -38,9 +38,10 @@
|
||||||
"react-router-dom": "6.6.2",
|
"react-router-dom": "6.6.2",
|
||||||
"string-length": "6.0.0",
|
"string-length": "6.0.0",
|
||||||
"swiped-events": "~1.2.0",
|
"swiped-events": "~1.2.0",
|
||||||
|
"tinyld": "~1.3.4",
|
||||||
"toastify-js": "~1.12.0",
|
"toastify-js": "~1.12.0",
|
||||||
"uid": "~2.0.2",
|
"uid": "~2.0.2",
|
||||||
"use-debounce": "~10.0.0",
|
"use-debounce": "~10.0.1",
|
||||||
"use-long-press": "~3.2.0",
|
"use-long-press": "~3.2.0",
|
||||||
"use-resize-observer": "~9.1.0",
|
"use-resize-observer": "~9.1.0",
|
||||||
"valtio": "1.13.2"
|
"valtio": "1.13.2"
|
||||||
|
@ -50,9 +51,9 @@
|
||||||
"@trivago/prettier-plugin-sort-imports": "~4.3.0",
|
"@trivago/prettier-plugin-sort-imports": "~4.3.0",
|
||||||
"postcss": "~8.4.38",
|
"postcss": "~8.4.38",
|
||||||
"postcss-dark-theme-class": "~1.3.0",
|
"postcss-dark-theme-class": "~1.3.0",
|
||||||
"postcss-preset-env": "~9.5.11",
|
"postcss-preset-env": "~9.5.14",
|
||||||
"twitter-text": "~3.1.0",
|
"twitter-text": "~3.1.0",
|
||||||
"vite": "~5.2.11",
|
"vite": "~5.2.12",
|
||||||
"vite-plugin-generate-file": "~0.1.1",
|
"vite-plugin-generate-file": "~0.1.1",
|
||||||
"vite-plugin-html-config": "~1.0.11",
|
"vite-plugin-html-config": "~1.0.11",
|
||||||
"vite-plugin-pwa": "~0.20.0",
|
"vite-plugin-pwa": "~0.20.0",
|
||||||
|
@ -2036,9 +2037,9 @@
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@csstools/postcss-cascade-layers": {
|
"node_modules/@csstools/postcss-cascade-layers": {
|
||||||
"version": "4.0.4",
|
"version": "4.0.6",
|
||||||
"resolved": "https://registry.npmjs.org/@csstools/postcss-cascade-layers/-/postcss-cascade-layers-4.0.4.tgz",
|
"resolved": "https://registry.npmjs.org/@csstools/postcss-cascade-layers/-/postcss-cascade-layers-4.0.6.tgz",
|
||||||
"integrity": "sha512-MKErv8lpEwVmAcAwidY1Kfd3oWrh2Q14kxHs9xn26XzjP/PrcdngWq63lJsZeMlBY7o+WlEOeE+FP6zPzeY2uw==",
|
"integrity": "sha512-Xt00qGAQyqAODFiFEJNkTpSUz5VfYqnDLECdlA/Vv17nl/OIV5QfTRHGAXrBGG5YcJyHpJ+GF9gF/RZvOQz4oA==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"funding": [
|
"funding": [
|
||||||
{
|
{
|
||||||
|
@ -2051,7 +2052,7 @@
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@csstools/selector-specificity": "^3.0.3",
|
"@csstools/selector-specificity": "^3.1.1",
|
||||||
"postcss-selector-parser": "^6.0.13"
|
"postcss-selector-parser": "^6.0.13"
|
||||||
},
|
},
|
||||||
"engines": {
|
"engines": {
|
||||||
|
@ -2307,9 +2308,9 @@
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@csstools/postcss-is-pseudo-class": {
|
"node_modules/@csstools/postcss-is-pseudo-class": {
|
||||||
"version": "4.0.6",
|
"version": "4.0.8",
|
||||||
"resolved": "https://registry.npmjs.org/@csstools/postcss-is-pseudo-class/-/postcss-is-pseudo-class-4.0.6.tgz",
|
"resolved": "https://registry.npmjs.org/@csstools/postcss-is-pseudo-class/-/postcss-is-pseudo-class-4.0.8.tgz",
|
||||||
"integrity": "sha512-HilOhAsMpFheMYkuaREZx+CGa4hsG6kQdzwXSsuqKDFzYz2eIMP213+3dH/vUbPXaWrzqLKr8m3i0dgYPoh7vg==",
|
"integrity": "sha512-0aj591yGlq5Qac+plaWCbn5cpjs5Sh0daovYUKJUOMjIp70prGH/XPLp7QjxtbFXz3CTvb0H9a35dpEuIuUi3Q==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"funding": [
|
"funding": [
|
||||||
{
|
{
|
||||||
|
@ -2322,7 +2323,7 @@
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@csstools/selector-specificity": "^3.0.3",
|
"@csstools/selector-specificity": "^3.1.1",
|
||||||
"postcss-selector-parser": "^6.0.13"
|
"postcss-selector-parser": "^6.0.13"
|
||||||
},
|
},
|
||||||
"engines": {
|
"engines": {
|
||||||
|
@ -2816,9 +2817,9 @@
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@csstools/selector-specificity": {
|
"node_modules/@csstools/selector-specificity": {
|
||||||
"version": "3.0.3",
|
"version": "3.1.1",
|
||||||
"resolved": "https://registry.npmjs.org/@csstools/selector-specificity/-/selector-specificity-3.0.3.tgz",
|
"resolved": "https://registry.npmjs.org/@csstools/selector-specificity/-/selector-specificity-3.1.1.tgz",
|
||||||
"integrity": "sha512-KEPNw4+WW5AVEIyzC80rTbWEUatTW2lXpN8+8ILC8PiPeWPjwUzrPZDIOZ2wwqDmeqOYTdSGyL3+vE5GC3FB3Q==",
|
"integrity": "sha512-a7cxGcJ2wIlMFLlh8z2ONm+715QkPHiyJcxwQlKOz/03GPw1COpfhcmC9wm4xlZfp//jWHNNMwzjtqHXVWU9KA==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"funding": [
|
"funding": [
|
||||||
{
|
{
|
||||||
|
@ -3228,9 +3229,9 @@
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@formatjs/ecma402-abstract": {
|
"node_modules/@formatjs/ecma402-abstract": {
|
||||||
"version": "1.18.2",
|
"version": "2.0.0",
|
||||||
"resolved": "https://registry.npmjs.org/@formatjs/ecma402-abstract/-/ecma402-abstract-1.18.2.tgz",
|
"resolved": "https://registry.npmjs.org/@formatjs/ecma402-abstract/-/ecma402-abstract-2.0.0.tgz",
|
||||||
"integrity": "sha512-+QoPW4csYALsQIl8GbN14igZzDbuwzcpWrku9nyMXlaqAlwRBgl5V+p0vWMGFqHOw37czNXaP/lEk4wbLgcmtA==",
|
"integrity": "sha512-rRqXOqdFmk7RYvj4khklyqzcfQl9vEL/usogncBHRZfZBDOwMGuSRNFl02fu5KGHXdbinju+YXyuR+Nk8xlr/g==",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@formatjs/intl-localematcher": "0.5.4",
|
"@formatjs/intl-localematcher": "0.5.4",
|
||||||
"tslib": "^2.4.0"
|
"tslib": "^2.4.0"
|
||||||
|
@ -3245,11 +3246,11 @@
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@formatjs/intl-segmenter": {
|
"node_modules/@formatjs/intl-segmenter": {
|
||||||
"version": "11.5.5",
|
"version": "11.5.7",
|
||||||
"resolved": "https://registry.npmjs.org/@formatjs/intl-segmenter/-/intl-segmenter-11.5.5.tgz",
|
"resolved": "https://registry.npmjs.org/@formatjs/intl-segmenter/-/intl-segmenter-11.5.7.tgz",
|
||||||
"integrity": "sha512-mMbJKFGzwYJBcwfL9EfqFje75Ce5WPar5rSi7wWvFtBPFY2Zi1cWIss7FSm2MNNM9l1BycBAsBQuXFt+Hd+0tQ==",
|
"integrity": "sha512-MPvUKOURPY1aHc/d3YtLKp4hamrJtdBRc/AZVt9zRitrNeRszSwpIIYDHka9chQJTRIJlIfS4S9FGMdA1PE3Xw==",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@formatjs/ecma402-abstract": "1.18.2",
|
"@formatjs/ecma402-abstract": "2.0.0",
|
||||||
"@formatjs/intl-localematcher": "0.5.4",
|
"@formatjs/intl-localematcher": "0.5.4",
|
||||||
"tslib": "^2.4.0"
|
"tslib": "^2.4.0"
|
||||||
}
|
}
|
||||||
|
@ -3266,12 +3267,12 @@
|
||||||
"license": "MIT"
|
"license": "MIT"
|
||||||
},
|
},
|
||||||
"node_modules/@github/text-expander-element": {
|
"node_modules/@github/text-expander-element": {
|
||||||
"version": "2.6.1",
|
"version": "2.7.1",
|
||||||
"resolved": "https://registry.npmjs.org/@github/text-expander-element/-/text-expander-element-2.6.1.tgz",
|
"resolved": "https://registry.npmjs.org/@github/text-expander-element/-/text-expander-element-2.7.1.tgz",
|
||||||
"integrity": "sha512-i6krPGXJRABfKXut0WArFd365Je4PT0MljtDoXUoCOEp+lGrmdosDMxmO0EfOYc97jBn+Hd2XO1mMsuI5+fwmQ==",
|
"integrity": "sha512-CWxfYxJRkeWVCUhJveproLs6pHsPrWtK8TsjL8ByYVcSCs8CJmNzF8b7ZawrUgfai0F2jb4aIdw2FoBTykj9XA==",
|
||||||
"license": "MIT",
|
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@github/combobox-nav": "^2.0.2"
|
"@github/combobox-nav": "^2.0.2",
|
||||||
|
"dom-input-range": "^1.1.6"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@iconify-icons/mingcute": {
|
"node_modules/@iconify-icons/mingcute": {
|
||||||
|
@ -4499,9 +4500,9 @@
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/css-has-pseudo": {
|
"node_modules/css-has-pseudo": {
|
||||||
"version": "6.0.3",
|
"version": "6.0.5",
|
||||||
"resolved": "https://registry.npmjs.org/css-has-pseudo/-/css-has-pseudo-6.0.3.tgz",
|
"resolved": "https://registry.npmjs.org/css-has-pseudo/-/css-has-pseudo-6.0.5.tgz",
|
||||||
"integrity": "sha512-qIsDxK/z0byH/mpNsv5hzQ5NOl8m1FRmOLgZpx4bG5uYHnOlO2XafeMI4mFIgNSViHwoUWcxSJZyyijaAmbs+A==",
|
"integrity": "sha512-ZTv6RlvJJZKp32jPYnAJVhowDCrRrHUTAxsYSuUPBEDJjzws6neMnzkRblxtgmv1RgcV5dhH2gn7E3wA9Wt6lw==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"funding": [
|
"funding": [
|
||||||
{
|
{
|
||||||
|
@ -4514,7 +4515,7 @@
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@csstools/selector-specificity": "^3.0.3",
|
"@csstools/selector-specificity": "^3.1.1",
|
||||||
"postcss-selector-parser": "^6.0.13",
|
"postcss-selector-parser": "^6.0.13",
|
||||||
"postcss-value-parser": "^4.2.0"
|
"postcss-value-parser": "^4.2.0"
|
||||||
},
|
},
|
||||||
|
@ -4737,6 +4738,14 @@
|
||||||
"valtio": "*"
|
"valtio": "*"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/dom-input-range": {
|
||||||
|
"version": "1.1.6",
|
||||||
|
"resolved": "https://registry.npmjs.org/dom-input-range/-/dom-input-range-1.1.6.tgz",
|
||||||
|
"integrity": "sha512-4o/SkTpscD0n81BeErrrtmE58lG8vTks++92vk//ld0NmkQTb4AVJ2rexh2yor6rtBf5IMte26u+fF3EgCppPQ==",
|
||||||
|
"workspaces": [
|
||||||
|
"demos"
|
||||||
|
]
|
||||||
|
},
|
||||||
"node_modules/dom-serializer": {
|
"node_modules/dom-serializer": {
|
||||||
"version": "2.0.0",
|
"version": "2.0.0",
|
||||||
"resolved": "https://registry.npmjs.org/dom-serializer/-/dom-serializer-2.0.0.tgz",
|
"resolved": "https://registry.npmjs.org/dom-serializer/-/dom-serializer-2.0.0.tgz",
|
||||||
|
@ -6925,9 +6934,9 @@
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/postcss-nesting": {
|
"node_modules/postcss-nesting": {
|
||||||
"version": "12.1.2",
|
"version": "12.1.5",
|
||||||
"resolved": "https://registry.npmjs.org/postcss-nesting/-/postcss-nesting-12.1.2.tgz",
|
"resolved": "https://registry.npmjs.org/postcss-nesting/-/postcss-nesting-12.1.5.tgz",
|
||||||
"integrity": "sha512-FUmTHGDNundodutB4PUBxt/EPuhgtpk8FJGRsBhOuy+6FnkR2A8RZWIsyyy6XmhvX2DZQQWIkvu+HB4IbJm+Ew==",
|
"integrity": "sha512-N1NgI1PDCiAGWPTYrwqm8wpjv0bgDmkYHH72pNsqTCv9CObxjxftdYu6AKtGN+pnJa7FQjMm3v4sp8QJbFsYdQ==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"funding": [
|
"funding": [
|
||||||
{
|
{
|
||||||
|
@ -6941,8 +6950,8 @@
|
||||||
],
|
],
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@csstools/selector-resolve-nested": "^1.1.0",
|
"@csstools/selector-resolve-nested": "^1.1.0",
|
||||||
"@csstools/selector-specificity": "^3.0.3",
|
"@csstools/selector-specificity": "^3.1.1",
|
||||||
"postcss-selector-parser": "^6.0.13"
|
"postcss-selector-parser": "^6.1.0"
|
||||||
},
|
},
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": "^14 || ^16 || >=18"
|
"node": "^14 || ^16 || >=18"
|
||||||
|
@ -7035,9 +7044,9 @@
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/postcss-preset-env": {
|
"node_modules/postcss-preset-env": {
|
||||||
"version": "9.5.11",
|
"version": "9.5.14",
|
||||||
"resolved": "https://registry.npmjs.org/postcss-preset-env/-/postcss-preset-env-9.5.11.tgz",
|
"resolved": "https://registry.npmjs.org/postcss-preset-env/-/postcss-preset-env-9.5.14.tgz",
|
||||||
"integrity": "sha512-rPFnftk1vQAaR45UmsuXhKd/IZrTj39dIc4usu8qbfxyNevHnG+FB8E50U7vs0v2OxBqBt5u0J5+cwb4newzGA==",
|
"integrity": "sha512-gTMi+3kENN/mN+K59aR+vEOjlkujTmmXJcM9rnAqGh9Y/euQ/ypdp9rd8mO1eoIjAD8vNS15+xbkBxoi+65BqQ==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"funding": [
|
"funding": [
|
||||||
{
|
{
|
||||||
|
@ -7050,7 +7059,7 @@
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@csstools/postcss-cascade-layers": "^4.0.4",
|
"@csstools/postcss-cascade-layers": "^4.0.6",
|
||||||
"@csstools/postcss-color-function": "^3.0.16",
|
"@csstools/postcss-color-function": "^3.0.16",
|
||||||
"@csstools/postcss-color-mix-function": "^2.0.16",
|
"@csstools/postcss-color-mix-function": "^2.0.16",
|
||||||
"@csstools/postcss-exponential-functions": "^1.0.7",
|
"@csstools/postcss-exponential-functions": "^1.0.7",
|
||||||
|
@ -7060,7 +7069,7 @@
|
||||||
"@csstools/postcss-hwb-function": "^3.0.15",
|
"@csstools/postcss-hwb-function": "^3.0.15",
|
||||||
"@csstools/postcss-ic-unit": "^3.0.6",
|
"@csstools/postcss-ic-unit": "^3.0.6",
|
||||||
"@csstools/postcss-initial": "^1.0.1",
|
"@csstools/postcss-initial": "^1.0.1",
|
||||||
"@csstools/postcss-is-pseudo-class": "^4.0.6",
|
"@csstools/postcss-is-pseudo-class": "^4.0.8",
|
||||||
"@csstools/postcss-light-dark-function": "^1.0.5",
|
"@csstools/postcss-light-dark-function": "^1.0.5",
|
||||||
"@csstools/postcss-logical-float-and-clear": "^2.0.1",
|
"@csstools/postcss-logical-float-and-clear": "^2.0.1",
|
||||||
"@csstools/postcss-logical-overflow": "^1.0.1",
|
"@csstools/postcss-logical-overflow": "^1.0.1",
|
||||||
|
@ -7082,7 +7091,7 @@
|
||||||
"autoprefixer": "^10.4.19",
|
"autoprefixer": "^10.4.19",
|
||||||
"browserslist": "^4.22.3",
|
"browserslist": "^4.22.3",
|
||||||
"css-blank-pseudo": "^6.0.2",
|
"css-blank-pseudo": "^6.0.2",
|
||||||
"css-has-pseudo": "^6.0.3",
|
"css-has-pseudo": "^6.0.5",
|
||||||
"css-prefers-color-scheme": "^9.0.1",
|
"css-prefers-color-scheme": "^9.0.1",
|
||||||
"cssdb": "^8.0.0",
|
"cssdb": "^8.0.0",
|
||||||
"postcss-attribute-case-insensitive": "^6.0.3",
|
"postcss-attribute-case-insensitive": "^6.0.3",
|
||||||
|
@ -7102,7 +7111,7 @@
|
||||||
"postcss-image-set-function": "^6.0.3",
|
"postcss-image-set-function": "^6.0.3",
|
||||||
"postcss-lab-function": "^6.0.16",
|
"postcss-lab-function": "^6.0.16",
|
||||||
"postcss-logical": "^7.0.1",
|
"postcss-logical": "^7.0.1",
|
||||||
"postcss-nesting": "^12.1.2",
|
"postcss-nesting": "^12.1.5",
|
||||||
"postcss-opacity-percentage": "^2.0.0",
|
"postcss-opacity-percentage": "^2.0.0",
|
||||||
"postcss-overflow-shorthand": "^5.0.1",
|
"postcss-overflow-shorthand": "^5.0.1",
|
||||||
"postcss-page-break": "^3.0.4",
|
"postcss-page-break": "^3.0.4",
|
||||||
|
@ -7179,9 +7188,9 @@
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/postcss-selector-parser": {
|
"node_modules/postcss-selector-parser": {
|
||||||
"version": "6.0.15",
|
"version": "6.1.0",
|
||||||
"resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-6.0.15.tgz",
|
"resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-6.1.0.tgz",
|
||||||
"integrity": "sha512-rEYkQOMUCEMhsKbK66tbEU9QVIxbhN18YiniAwA7XQYTVBqrBy+P2p5JcdqsHgKM2zWylp8d7J6eszocfds5Sw==",
|
"integrity": "sha512-UMz42UD0UY0EApS0ZL9o1XnLhSTtvvvLe5Dc2H2O56fvRZi+KulDyf5ctDhhtYJBGKStV2FL1fy6253cmLgqVQ==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"cssesc": "^3.0.0",
|
"cssesc": "^3.0.0",
|
||||||
|
@ -7199,9 +7208,9 @@
|
||||||
"license": "MIT"
|
"license": "MIT"
|
||||||
},
|
},
|
||||||
"node_modules/preact": {
|
"node_modules/preact": {
|
||||||
"version": "10.21.0",
|
"version": "10.22.0",
|
||||||
"resolved": "https://registry.npmjs.org/preact/-/preact-10.21.0.tgz",
|
"resolved": "https://registry.npmjs.org/preact/-/preact-10.22.0.tgz",
|
||||||
"integrity": "sha512-aQAIxtzWEwH8ou+OovWVSVNlFImL7xUCwJX3YMqA3U8iKCNC34999fFOnWjYNsylgfPgMexpbk7WYOLtKr/mxg==",
|
"integrity": "sha512-RRurnSjJPj4rp5K6XoP45Ui33ncb7e4H7WiOHVpjbkvqvA3U+N8Z6Qbo0AE6leGYBV66n8EhEaFixvIu3SkxFw==",
|
||||||
"funding": {
|
"funding": {
|
||||||
"type": "opencollective",
|
"type": "opencollective",
|
||||||
"url": "https://opencollective.com/preact"
|
"url": "https://opencollective.com/preact"
|
||||||
|
@ -8010,6 +8019,21 @@
|
||||||
"node": ">=10"
|
"node": ">=10"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/tinyld": {
|
||||||
|
"version": "1.3.4",
|
||||||
|
"resolved": "https://registry.npmjs.org/tinyld/-/tinyld-1.3.4.tgz",
|
||||||
|
"integrity": "sha512-u26CNoaInA4XpDU+8s/6Cq8xHc2T5M4fXB3ICfXPokUQoLzmPgSZU02TAkFwFMJCWTjk53gtkS8pETTreZwCqw==",
|
||||||
|
"bin": {
|
||||||
|
"tinyld": "bin/tinyld.js",
|
||||||
|
"tinyld-heavy": "bin/tinyld-heavy.js",
|
||||||
|
"tinyld-light": "bin/tinyld-light.js"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">= 12.10.0",
|
||||||
|
"npm": ">= 6.12.0",
|
||||||
|
"yarn": ">= 1.20.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/to-fast-properties": {
|
"node_modules/to-fast-properties": {
|
||||||
"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",
|
||||||
|
@ -8331,10 +8355,9 @@
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/use-debounce": {
|
"node_modules/use-debounce": {
|
||||||
"version": "10.0.0",
|
"version": "10.0.1",
|
||||||
"resolved": "https://registry.npmjs.org/use-debounce/-/use-debounce-10.0.0.tgz",
|
"resolved": "https://registry.npmjs.org/use-debounce/-/use-debounce-10.0.1.tgz",
|
||||||
"integrity": "sha512-XRjvlvCB46bah9IBXVnq/ACP2lxqXyZj0D9hj4K5OzNroMDpTEBg8Anuh1/UfRTRs7pLhQ+RiNxxwZu9+MVl1A==",
|
"integrity": "sha512-0uUXjOfm44e6z4LZ/woZvkM8FwV1wiuoB6xnrrOmeAEjRDDzTLQNRFtYHvqUsJdrz1X37j0rVGIVp144GLHGKg==",
|
||||||
"license": "MIT",
|
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">= 16.0.0"
|
"node": ">= 16.0.0"
|
||||||
},
|
},
|
||||||
|
@ -8405,9 +8428,9 @@
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/vite": {
|
"node_modules/vite": {
|
||||||
"version": "5.2.11",
|
"version": "5.2.12",
|
||||||
"resolved": "https://registry.npmjs.org/vite/-/vite-5.2.11.tgz",
|
"resolved": "https://registry.npmjs.org/vite/-/vite-5.2.12.tgz",
|
||||||
"integrity": "sha512-HndV31LWW05i1BLPMUCE1B9E9GFbOu1MbenhS58FuK6owSO5qHm7GiCotrNY1YE5rMeQSFBGmT5ZaLEjFizgiQ==",
|
"integrity": "sha512-/gC8GxzxMK5ntBwb48pR32GGhENnjtY30G4A0jemunsBkiEZFw60s8InGpN8gkhHEkjnRK1aSAxeQgwvFhUHAA==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"esbuild": "^0.20.1",
|
"esbuild": "^0.20.1",
|
||||||
|
|
13
package.json
13
package.json
|
@ -11,9 +11,9 @@
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@formatjs/intl-localematcher": "~0.5.4",
|
"@formatjs/intl-localematcher": "~0.5.4",
|
||||||
"@formatjs/intl-segmenter": "~11.5.5",
|
"@formatjs/intl-segmenter": "~11.5.7",
|
||||||
"@formkit/auto-animate": "~0.8.2",
|
"@formkit/auto-animate": "~0.8.2",
|
||||||
"@github/text-expander-element": "~2.6.1",
|
"@github/text-expander-element": "~2.7.1",
|
||||||
"@iconify-icons/mingcute": "~1.2.9",
|
"@iconify-icons/mingcute": "~1.2.9",
|
||||||
"@justinribeiro/lite-youtube": "~1.5.0",
|
"@justinribeiro/lite-youtube": "~1.5.0",
|
||||||
"@szhsin/react-menu": "~4.1.0",
|
"@szhsin/react-menu": "~4.1.0",
|
||||||
|
@ -32,7 +32,7 @@
|
||||||
"moize": "~6.1.6",
|
"moize": "~6.1.6",
|
||||||
"p-retry": "~6.2.0",
|
"p-retry": "~6.2.0",
|
||||||
"p-throttle": "~6.1.0",
|
"p-throttle": "~6.1.0",
|
||||||
"preact": "~10.21.0",
|
"preact": "~10.22.0",
|
||||||
"punycode": "~2.3.1",
|
"punycode": "~2.3.1",
|
||||||
"react-hotkeys-hook": "~4.5.0",
|
"react-hotkeys-hook": "~4.5.0",
|
||||||
"react-intersection-observer": "~9.10.2",
|
"react-intersection-observer": "~9.10.2",
|
||||||
|
@ -40,9 +40,10 @@
|
||||||
"react-router-dom": "6.6.2",
|
"react-router-dom": "6.6.2",
|
||||||
"string-length": "6.0.0",
|
"string-length": "6.0.0",
|
||||||
"swiped-events": "~1.2.0",
|
"swiped-events": "~1.2.0",
|
||||||
|
"tinyld": "~1.3.4",
|
||||||
"toastify-js": "~1.12.0",
|
"toastify-js": "~1.12.0",
|
||||||
"uid": "~2.0.2",
|
"uid": "~2.0.2",
|
||||||
"use-debounce": "~10.0.0",
|
"use-debounce": "~10.0.1",
|
||||||
"use-long-press": "~3.2.0",
|
"use-long-press": "~3.2.0",
|
||||||
"use-resize-observer": "~9.1.0",
|
"use-resize-observer": "~9.1.0",
|
||||||
"valtio": "1.13.2"
|
"valtio": "1.13.2"
|
||||||
|
@ -52,9 +53,9 @@
|
||||||
"@trivago/prettier-plugin-sort-imports": "~4.3.0",
|
"@trivago/prettier-plugin-sort-imports": "~4.3.0",
|
||||||
"postcss": "~8.4.38",
|
"postcss": "~8.4.38",
|
||||||
"postcss-dark-theme-class": "~1.3.0",
|
"postcss-dark-theme-class": "~1.3.0",
|
||||||
"postcss-preset-env": "~9.5.11",
|
"postcss-preset-env": "~9.5.14",
|
||||||
"twitter-text": "~3.1.0",
|
"twitter-text": "~3.1.0",
|
||||||
"vite": "~5.2.11",
|
"vite": "~5.2.12",
|
||||||
"vite-plugin-generate-file": "~0.1.1",
|
"vite-plugin-generate-file": "~0.1.1",
|
||||||
"vite-plugin-html-config": "~1.0.11",
|
"vite-plugin-html-config": "~1.0.11",
|
||||||
"vite-plugin-pwa": "~0.20.0",
|
"vite-plugin-pwa": "~0.20.0",
|
||||||
|
|
48
src/app.css
48
src/app.css
|
@ -1580,8 +1580,8 @@ body:has(.media-modal-container + .status-deck) .media-post-link {
|
||||||
0 10px 36px -4px var(--button-bg-blur-color);
|
0 10px 36px -4px var(--button-bg-blur-color);
|
||||||
transition: all 0.3s ease-in-out;
|
transition: all 0.3s ease-in-out;
|
||||||
}
|
}
|
||||||
.deck-container:has(header[hidden]) ~ #compose-button,
|
.deck-container:has(header[hidden]) ~ #compose-button:not(.loading),
|
||||||
#compose-button[hidden] {
|
#compose-button[hidden]:not(.loading) {
|
||||||
transform: translateY(200%);
|
transform: translateY(200%);
|
||||||
pointer-events: none;
|
pointer-events: none;
|
||||||
user-select: none;
|
user-select: none;
|
||||||
|
@ -1610,6 +1610,48 @@ body:has(.media-modal-container + .status-deck) .media-post-link {
|
||||||
bottom: calc(16px + env(safe-area-inset-bottom) + 52px);
|
bottom: calc(16px + env(safe-area-inset-bottom) + 52px);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
#compose-button {
|
||||||
|
&.min {
|
||||||
|
outline: 2px solid var(--button-text-color);
|
||||||
|
z-index: 1001; /* Higher than modal */
|
||||||
|
|
||||||
|
&:after {
|
||||||
|
content: '';
|
||||||
|
display: block;
|
||||||
|
position: absolute;
|
||||||
|
top: 0;
|
||||||
|
right: 0;
|
||||||
|
width: 14px;
|
||||||
|
height: 14px;
|
||||||
|
border-radius: 50%;
|
||||||
|
background-color: var(--button-bg-color);
|
||||||
|
border: 2px solid var(--button-text-color);
|
||||||
|
box-shadow: 0 2px 8px var(--drop-shadow-color);
|
||||||
|
opacity: 0;
|
||||||
|
transition: opacity 0.2s ease-out 0.5s;
|
||||||
|
opacity: 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
&.loading {
|
||||||
|
outline-color: var(--button-bg-blur-color);
|
||||||
|
|
||||||
|
&:before {
|
||||||
|
position: absolute;
|
||||||
|
inset: 0;
|
||||||
|
content: '';
|
||||||
|
border-radius: 50%;
|
||||||
|
animation: spin 5s linear infinite;
|
||||||
|
border: 2px dashed var(--button-text-color);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
&.error {
|
||||||
|
&:after {
|
||||||
|
background-color: var(--red-color);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/* SHEET */
|
/* SHEET */
|
||||||
|
|
||||||
|
@ -2184,6 +2226,8 @@ body > .szh-menu-container {
|
||||||
box-shadow: 0 3px 8px -1px var(--drop-shadow-color),
|
box-shadow: 0 3px 8px -1px var(--drop-shadow-color),
|
||||||
0 10px 36px -4px var(--button-bg-blur-color);
|
0 10px 36px -4px var(--button-bg-blur-color);
|
||||||
text-align: center;
|
text-align: center;
|
||||||
|
width: fit-content;
|
||||||
|
max-width: calc(100vw - 32px);
|
||||||
}
|
}
|
||||||
.toastify-bottom {
|
.toastify-bottom {
|
||||||
margin-bottom: env(safe-area-inset-bottom);
|
margin-bottom: env(safe-area-inset-bottom);
|
||||||
|
|
|
@ -126,13 +126,13 @@ setInterval(() => {
|
||||||
// Related: https://github.com/vitejs/vite/issues/10600
|
// Related: https://github.com/vitejs/vite/issues/10600
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
for (const icon in ICONS) {
|
for (const icon in ICONS) {
|
||||||
queueMicrotask(() => {
|
setTimeout(() => {
|
||||||
if (Array.isArray(ICONS[icon])) {
|
if (Array.isArray(ICONS[icon])) {
|
||||||
ICONS[icon][0]?.();
|
ICONS[icon][0]?.();
|
||||||
} else {
|
} else {
|
||||||
ICONS[icon]?.();
|
ICONS[icon]?.();
|
||||||
}
|
}
|
||||||
});
|
}, 1);
|
||||||
}
|
}
|
||||||
}, 5000);
|
}, 5000);
|
||||||
|
|
||||||
|
|
|
@ -108,4 +108,5 @@ export const ICONS = {
|
||||||
settings: () => import('@iconify-icons/mingcute/settings-6-line'),
|
settings: () => import('@iconify-icons/mingcute/settings-6-line'),
|
||||||
'heart-break': () => import('@iconify-icons/mingcute/heart-crack-line'),
|
'heart-break': () => import('@iconify-icons/mingcute/heart-crack-line'),
|
||||||
'user-x': () => import('@iconify-icons/mingcute/user-x-line'),
|
'user-x': () => import('@iconify-icons/mingcute/user-x-line'),
|
||||||
|
minimize: () => import('@iconify-icons/mingcute/arrows-down-line'),
|
||||||
};
|
};
|
||||||
|
|
|
@ -133,21 +133,18 @@ function AccountBlock({
|
||||||
)}
|
)}
|
||||||
</span>
|
</span>
|
||||||
{showActivity && (
|
{showActivity && (
|
||||||
<>
|
<div class="account-block-stats">
|
||||||
<br />
|
Posts: {shortenNumber(statusesCount)}
|
||||||
<small class="last-status-at insignificant">
|
{!!lastStatusAt && (
|
||||||
Posts: {statusesCount}
|
<>
|
||||||
{!!lastStatusAt && (
|
{' '}
|
||||||
<>
|
· Last posted:{' '}
|
||||||
{' '}
|
{niceDateTime(lastStatusAt, {
|
||||||
· Last posted:{' '}
|
hideTime: true,
|
||||||
{niceDateTime(lastStatusAt, {
|
})}
|
||||||
hideTime: true,
|
</>
|
||||||
})}
|
)}
|
||||||
</>
|
</div>
|
||||||
)}
|
|
||||||
</small>
|
|
||||||
</>
|
|
||||||
)}
|
)}
|
||||||
{showStats && (
|
{showStats && (
|
||||||
<div class="account-block-stats">
|
<div class="account-block-stats">
|
||||||
|
|
|
@ -19,6 +19,7 @@ import { getLists } from '../utils/lists';
|
||||||
import niceDateTime from '../utils/nice-date-time';
|
import niceDateTime from '../utils/nice-date-time';
|
||||||
import pmem from '../utils/pmem';
|
import pmem from '../utils/pmem';
|
||||||
import shortenNumber from '../utils/shorten-number';
|
import shortenNumber from '../utils/shorten-number';
|
||||||
|
import showCompose from '../utils/show-compose';
|
||||||
import showToast from '../utils/show-toast';
|
import showToast from '../utils/show-toast';
|
||||||
import states, { hideAllModals } from '../utils/states';
|
import states, { hideAllModals } from '../utils/states';
|
||||||
import store from '../utils/store';
|
import store from '../utils/store';
|
||||||
|
@ -1081,11 +1082,11 @@ function RelatedActions({
|
||||||
<>
|
<>
|
||||||
<MenuItem
|
<MenuItem
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
states.showCompose = {
|
showCompose({
|
||||||
draftStatus: {
|
draftStatus: {
|
||||||
status: `@${currentInfo?.acct || acct} `,
|
status: `@${currentInfo?.acct || acct} `,
|
||||||
},
|
},
|
||||||
};
|
});
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<Icon icon="at" />
|
<Icon icon="at" />
|
||||||
|
|
|
@ -63,7 +63,7 @@ function Avatar({ url, size, alt = '', squircle, ...props }) {
|
||||||
if (avatarRef.current) avatarRef.current.dataset.loaded = true;
|
if (avatarRef.current) avatarRef.current.dataset.loaded = true;
|
||||||
if (alphaCache[url] !== undefined) return;
|
if (alphaCache[url] !== undefined) return;
|
||||||
if (isMissing) return;
|
if (isMissing) return;
|
||||||
queueMicrotask(() => {
|
setTimeout(() => {
|
||||||
try {
|
try {
|
||||||
// Check if image has alpha channel
|
// Check if image has alpha channel
|
||||||
const { width, height } = e.target;
|
const { width, height } = e.target;
|
||||||
|
@ -88,7 +88,7 @@ function Avatar({ url, size, alt = '', squircle, ...props }) {
|
||||||
// Silent fail
|
// Silent fail
|
||||||
alphaCache[url] = false;
|
alphaCache[url] = false;
|
||||||
}
|
}
|
||||||
});
|
}, 1);
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
|
|
|
@ -1,4 +1,5 @@
|
||||||
import { useHotkeys } from 'react-hotkeys-hook';
|
import { useHotkeys } from 'react-hotkeys-hook';
|
||||||
|
import { useSnapshot } from 'valtio';
|
||||||
|
|
||||||
import openCompose from '../utils/open-compose';
|
import openCompose from '../utils/open-compose';
|
||||||
import openOSK from '../utils/open-osk';
|
import openOSK from '../utils/open-osk';
|
||||||
|
@ -7,7 +8,15 @@ import states from '../utils/states';
|
||||||
import Icon from './icon';
|
import Icon from './icon';
|
||||||
|
|
||||||
export default function ComposeButton() {
|
export default function ComposeButton() {
|
||||||
|
const snapStates = useSnapshot(states);
|
||||||
|
|
||||||
function handleButton(e) {
|
function handleButton(e) {
|
||||||
|
if (snapStates.composerState.minimized) {
|
||||||
|
states.composerState.minimized = false;
|
||||||
|
openOSK();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
if (e.shiftKey) {
|
if (e.shiftKey) {
|
||||||
const newWin = openCompose();
|
const newWin = openCompose();
|
||||||
|
|
||||||
|
@ -28,7 +37,14 @@ export default function ComposeButton() {
|
||||||
});
|
});
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<button type="button" id="compose-button" onClick={handleButton}>
|
<button
|
||||||
|
type="button"
|
||||||
|
id="compose-button"
|
||||||
|
onClick={handleButton}
|
||||||
|
class={`${snapStates.composerState.minimized ? 'min' : ''} ${
|
||||||
|
snapStates.composerState.publishing ? 'loading' : ''
|
||||||
|
} ${snapStates.composerState.publishingError ? 'error' : ''}`}
|
||||||
|
>
|
||||||
<Icon icon="quill" size="xl" alt="Compose" />
|
<Icon icon="quill" size="xl" alt="Compose" />
|
||||||
</button>
|
</button>
|
||||||
);
|
);
|
||||||
|
|
48
src/components/compose-suspense.jsx
Normal file
48
src/components/compose-suspense.jsx
Normal file
|
@ -0,0 +1,48 @@
|
||||||
|
import { shouldPolyfill } from '@formatjs/intl-segmenter/should-polyfill';
|
||||||
|
import { useEffect, useState } from 'preact/hooks';
|
||||||
|
|
||||||
|
import Loader from './loader';
|
||||||
|
|
||||||
|
const supportsIntlSegmenter = !shouldPolyfill();
|
||||||
|
|
||||||
|
function importIntlSegmenter() {
|
||||||
|
if (!supportsIntlSegmenter) {
|
||||||
|
return import('@formatjs/intl-segmenter/polyfill-force').catch(() => {});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function importCompose() {
|
||||||
|
return import('./compose');
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function preload() {
|
||||||
|
try {
|
||||||
|
await importIntlSegmenter();
|
||||||
|
importCompose();
|
||||||
|
} catch (e) {
|
||||||
|
console.error(e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export default function ComposeSuspense(props) {
|
||||||
|
const [Compose, setCompose] = useState(null);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
(async () => {
|
||||||
|
try {
|
||||||
|
if (supportsIntlSegmenter) {
|
||||||
|
const component = await importCompose();
|
||||||
|
setCompose(component);
|
||||||
|
} else {
|
||||||
|
await importIntlSegmenter();
|
||||||
|
const component = await importCompose();
|
||||||
|
setCompose(component);
|
||||||
|
}
|
||||||
|
} catch (e) {
|
||||||
|
console.error(e);
|
||||||
|
}
|
||||||
|
})();
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
return Compose?.default ? <Compose.default {...props} /> : <Loader />;
|
||||||
|
}
|
|
@ -298,14 +298,20 @@
|
||||||
height: 2.2em;
|
height: 2.2em;
|
||||||
}
|
}
|
||||||
#compose-container .text-expander-menu li:is(:hover, :focus, [aria-selected]) {
|
#compose-container .text-expander-menu li:is(:hover, :focus, [aria-selected]) {
|
||||||
color: var(--bg-color);
|
background-color: var(--link-bg-color);
|
||||||
background-color: var(--link-color);
|
|
||||||
}
|
|
||||||
#compose-container
|
|
||||||
.text-expander-menu:hover
|
|
||||||
li[aria-selected]:not(:hover, :focus) {
|
|
||||||
color: var(--text-color);
|
color: var(--text-color);
|
||||||
background-color: var(--bg-color);
|
}
|
||||||
|
#compose-container .text-expander-menu li[aria-selected] {
|
||||||
|
box-shadow: inset 4px 0 0 0 var(--button-bg-color);
|
||||||
|
}
|
||||||
|
#compose-container .text-expander-menu li[data-more] {
|
||||||
|
&:not(:hover, :focus, [aria-selected]) {
|
||||||
|
color: var(--text-insignificant-color);
|
||||||
|
background-color: var(--bg-faded-color);
|
||||||
|
}
|
||||||
|
|
||||||
|
font-size: 0.8em;
|
||||||
|
justify-content: center;
|
||||||
}
|
}
|
||||||
|
|
||||||
#compose-container .form-visibility-direct {
|
#compose-container .form-visibility-direct {
|
||||||
|
@ -334,6 +340,21 @@
|
||||||
display: flex;
|
display: flex;
|
||||||
gap: 8px;
|
gap: 8px;
|
||||||
align-items: stretch;
|
align-items: stretch;
|
||||||
|
|
||||||
|
.media-error {
|
||||||
|
padding: 2px;
|
||||||
|
color: var(--orange-fg-color);
|
||||||
|
background-color: transparent;
|
||||||
|
border: 1.5px dashed transparent;
|
||||||
|
line-height: 1;
|
||||||
|
border-radius: 4px;
|
||||||
|
display: flex;
|
||||||
|
|
||||||
|
&:is(:hover, :focus) {
|
||||||
|
background-color: var(--bg-color);
|
||||||
|
border-color: var(--orange-fg-color);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
#compose-container .media-preview {
|
#compose-container .media-preview {
|
||||||
flex-shrink: 0;
|
flex-shrink: 0;
|
||||||
|
@ -594,6 +615,75 @@
|
||||||
} */
|
} */
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#mention-sheet {
|
||||||
|
height: 50vh;
|
||||||
|
|
||||||
|
.accounts-list {
|
||||||
|
--list-gap: 1px;
|
||||||
|
list-style: none;
|
||||||
|
margin: 0;
|
||||||
|
padding: 8px 0;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
row-gap: var(--list-gap);
|
||||||
|
|
||||||
|
&.loading {
|
||||||
|
opacity: 0.5;
|
||||||
|
}
|
||||||
|
|
||||||
|
li {
|
||||||
|
display: flex;
|
||||||
|
flex-grow: 1;
|
||||||
|
/* align-items: center; */
|
||||||
|
margin: 0 -8px;
|
||||||
|
padding: 8px;
|
||||||
|
gap: 8px;
|
||||||
|
position: relative;
|
||||||
|
justify-content: space-between;
|
||||||
|
border-radius: 8px;
|
||||||
|
/* align-items: center; */
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
background-image: linear-gradient(
|
||||||
|
to right,
|
||||||
|
transparent 75%,
|
||||||
|
var(--link-bg-color)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
&.selected {
|
||||||
|
background-image: linear-gradient(
|
||||||
|
to right,
|
||||||
|
var(--bg-faded-color) 75%,
|
||||||
|
var(--link-bg-color)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
&:before {
|
||||||
|
content: '';
|
||||||
|
display: block;
|
||||||
|
border-top: var(--hairline-width) solid var(--divider-color);
|
||||||
|
position: absolute;
|
||||||
|
bottom: 0;
|
||||||
|
left: 58px;
|
||||||
|
right: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
&:has(+ li:is(.selected, :hover)):before,
|
||||||
|
&:is(.selected, :hover):before {
|
||||||
|
opacity: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
> button {
|
||||||
|
border-radius: 4px;
|
||||||
|
&:hover {
|
||||||
|
outline: 2px solid var(--button-bg-blur-color);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#custom-emojis-sheet {
|
#custom-emojis-sheet {
|
||||||
max-height: 50vh;
|
max-height: 50vh;
|
||||||
max-height: 50dvh;
|
max-height: 50dvh;
|
||||||
|
@ -609,7 +699,6 @@
|
||||||
input {
|
input {
|
||||||
width: 100%;
|
width: 100%;
|
||||||
min-width: 0;
|
min-width: 0;
|
||||||
font-size: 0.8em;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
File diff suppressed because it is too large
Load diff
|
@ -379,26 +379,26 @@ function Media({
|
||||||
const autoGIFAnimate = !showOriginal && autoAnimate && isGIF;
|
const autoGIFAnimate = !showOriginal && autoAnimate && isGIF;
|
||||||
const showProgress = original.duration > 5;
|
const showProgress = original.duration > 5;
|
||||||
|
|
||||||
const videoHTML = `
|
// This string is only for autoplay + muted to work on Mobile Safari
|
||||||
<video
|
const gifHTML = `
|
||||||
src="${url}"
|
<video
|
||||||
poster="${previewUrl}"
|
src="${url}"
|
||||||
width="${width}"
|
poster="${previewUrl}"
|
||||||
height="${height}"
|
width="${width}"
|
||||||
data-orientation="${orientation}"
|
height="${height}"
|
||||||
preload="auto"
|
data-orientation="${orientation}"
|
||||||
autoplay
|
preload="auto"
|
||||||
${isGIF ? 'muted' : ''}
|
autoplay
|
||||||
${isGIF ? '' : 'controls'}
|
muted
|
||||||
playsinline
|
playsinline
|
||||||
loop="${loopable}"
|
loop="${loopable}"
|
||||||
${isGIF ? 'ondblclick="this.paused ? this.play() : this.pause()"' : ''}
|
ondblclick="this.paused ? this.play() : this.pause()"
|
||||||
${
|
${
|
||||||
isGIF && showProgress
|
showProgress
|
||||||
? "ontimeupdate=\"this.closest('.media-gif') && this.closest('.media-gif').style.setProperty('--progress', `${~~((this.currentTime / this.duration) * 100)}%`)\""
|
? "ontimeupdate=\"this.closest('.media-gif') && this.closest('.media-gif').style.setProperty('--progress', `${~~((this.currentTime / this.duration) * 100)}%`)\""
|
||||||
: ''
|
: ''
|
||||||
}
|
}
|
||||||
></video>
|
></video>
|
||||||
`;
|
`;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
@ -461,17 +461,33 @@ function Media({
|
||||||
<div
|
<div
|
||||||
ref={mediaRef}
|
ref={mediaRef}
|
||||||
dangerouslySetInnerHTML={{
|
dangerouslySetInnerHTML={{
|
||||||
__html: videoHTML,
|
__html: gifHTML,
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
</QuickPinchZoom>
|
</QuickPinchZoom>
|
||||||
) : (
|
) : isGIF ? (
|
||||||
<div
|
<div
|
||||||
class="video-container"
|
class="video-container"
|
||||||
dangerouslySetInnerHTML={{
|
dangerouslySetInnerHTML={{
|
||||||
__html: videoHTML,
|
__html: gifHTML,
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
|
) : (
|
||||||
|
<div class="video-container">
|
||||||
|
<video
|
||||||
|
slot="media"
|
||||||
|
src={url}
|
||||||
|
poster={previewUrl}
|
||||||
|
width={width}
|
||||||
|
height={height}
|
||||||
|
data-orientation={orientation}
|
||||||
|
preload="auto"
|
||||||
|
autoplay
|
||||||
|
playsinline
|
||||||
|
loop={loopable}
|
||||||
|
controls
|
||||||
|
></video>
|
||||||
|
</div>
|
||||||
)
|
)
|
||||||
) : isGIF ? (
|
) : isGIF ? (
|
||||||
<video
|
<video
|
||||||
|
|
|
@ -10,17 +10,56 @@
|
||||||
align-items: center;
|
align-items: center;
|
||||||
background-color: var(--backdrop-color);
|
background-color: var(--backdrop-color);
|
||||||
animation: appear 0.5s var(--timing-function) both;
|
animation: appear 0.5s var(--timing-function) both;
|
||||||
|
transition: all 0.5s var(--timing-function);
|
||||||
|
|
||||||
&.solid {
|
&.solid {
|
||||||
background-color: var(--backdrop-solid-color);
|
background-color: var(--backdrop-solid-color);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
--compose-button-dimension: 56px;
|
||||||
|
--compose-button-dimension-half: calc(var(--compose-button-dimension) / 2);
|
||||||
|
--compose-button-dimension-margin: 16px;
|
||||||
|
|
||||||
|
&.min {
|
||||||
|
/* Minimized */
|
||||||
|
pointer-events: none;
|
||||||
|
user-select: none;
|
||||||
|
overflow: hidden;
|
||||||
|
transform: scale(0);
|
||||||
|
--right: max(
|
||||||
|
var(--compose-button-dimension-margin),
|
||||||
|
env(safe-area-inset-right)
|
||||||
|
);
|
||||||
|
--bottom: max(
|
||||||
|
var(--compose-button-dimension-margin),
|
||||||
|
env(safe-area-inset-bottom)
|
||||||
|
);
|
||||||
|
--origin-right: calc(
|
||||||
|
100% - var(--compose-button-dimension-half) - var(--right)
|
||||||
|
);
|
||||||
|
--origin-bottom: calc(
|
||||||
|
100% - var(--compose-button-dimension-half) - var(--bottom)
|
||||||
|
);
|
||||||
|
transform-origin: var(--origin-right) var(--origin-bottom);
|
||||||
|
}
|
||||||
|
|
||||||
.sheet {
|
.sheet {
|
||||||
transition: transform 0.3s var(--timing-function);
|
transition: transform 0.3s var(--timing-function);
|
||||||
transform-origin: center bottom;
|
transform-origin: 80% 80%;
|
||||||
}
|
}
|
||||||
|
|
||||||
&:has(~ div) .sheet {
|
&:has(~ div) .sheet {
|
||||||
transform: scale(0.975);
|
transform: scale(0.975);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@media (max-width: calc(40em - 1px)) {
|
||||||
|
#app[data-shortcuts-view-mode='tab-menu-bar'] ~ #modal-container > div.min {
|
||||||
|
border: 2px solid red;
|
||||||
|
|
||||||
|
--bottom: calc(
|
||||||
|
var(--compose-button-dimension-margin) + env(safe-area-inset-bottom) +
|
||||||
|
52px
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -8,7 +8,7 @@ import useCloseWatcher from '../utils/useCloseWatcher';
|
||||||
|
|
||||||
const $modalContainer = document.getElementById('modal-container');
|
const $modalContainer = document.getElementById('modal-container');
|
||||||
|
|
||||||
function Modal({ children, onClose, onClick, class: className }) {
|
function Modal({ children, onClose, onClick, class: className, minimized }) {
|
||||||
if (!children) return null;
|
if (!children) return null;
|
||||||
|
|
||||||
const modalRef = useRef();
|
const modalRef = useRef();
|
||||||
|
@ -41,6 +41,33 @@ function Modal({ children, onClose, onClick, class: className }) {
|
||||||
);
|
);
|
||||||
useCloseWatcher(onClose, [onClose]);
|
useCloseWatcher(onClose, [onClose]);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
const $deckContainers = document.querySelectorAll('.deck-container');
|
||||||
|
if (minimized) {
|
||||||
|
// Similar to focusDeck in focus-deck.jsx
|
||||||
|
// Focus last deck
|
||||||
|
const page = $deckContainers[$deckContainers.length - 1]; // last one
|
||||||
|
if (page && page.tabIndex === -1) {
|
||||||
|
page.focus();
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if (children) {
|
||||||
|
$deckContainers.forEach(($deckContainer) => {
|
||||||
|
$deckContainer.setAttribute('inert', '');
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
$deckContainers.forEach(($deckContainer) => {
|
||||||
|
$deckContainer.removeAttribute('inert');
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return () => {
|
||||||
|
$deckContainers.forEach(($deckContainer) => {
|
||||||
|
$deckContainer.removeAttribute('inert');
|
||||||
|
});
|
||||||
|
};
|
||||||
|
}, [children, minimized]);
|
||||||
|
|
||||||
const Modal = (
|
const Modal = (
|
||||||
<div
|
<div
|
||||||
ref={(node) => {
|
ref={(node) => {
|
||||||
|
@ -54,7 +81,8 @@ function Modal({ children, onClose, onClick, class: className }) {
|
||||||
onClose?.(e);
|
onClose?.(e);
|
||||||
}
|
}
|
||||||
}}
|
}}
|
||||||
tabIndex="-1"
|
tabIndex={minimized ? 0 : '-1'}
|
||||||
|
inert={minimized}
|
||||||
onFocus={(e) => {
|
onFocus={(e) => {
|
||||||
try {
|
try {
|
||||||
if (e.target === e.currentTarget) {
|
if (e.target === e.currentTarget) {
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
import { lazy } from 'preact/compat';
|
import { useEffect } from 'preact/hooks';
|
||||||
import { useLocation, useNavigate } from 'react-router-dom';
|
import { useLocation, useNavigate } from 'react-router-dom';
|
||||||
import { subscribe, useSnapshot } from 'valtio';
|
import { subscribe, useSnapshot } from 'valtio';
|
||||||
|
|
||||||
|
@ -9,19 +9,16 @@ import showToast from '../utils/show-toast';
|
||||||
import states from '../utils/states';
|
import states from '../utils/states';
|
||||||
|
|
||||||
import AccountSheet from './account-sheet';
|
import AccountSheet from './account-sheet';
|
||||||
// import Compose from './compose';
|
import ComposeSuspense, { preload } from './compose-suspense';
|
||||||
import Drafts from './drafts';
|
import Drafts from './drafts';
|
||||||
import EmbedModal from './embed-modal';
|
import EmbedModal from './embed-modal';
|
||||||
import GenericAccounts from './generic-accounts';
|
import GenericAccounts from './generic-accounts';
|
||||||
import IntlSegmenterSuspense from './intl-segmenter-suspense';
|
|
||||||
import MediaAltModal from './media-alt-modal';
|
import MediaAltModal from './media-alt-modal';
|
||||||
import MediaModal from './media-modal';
|
import MediaModal from './media-modal';
|
||||||
import Modal from './modal';
|
import Modal from './modal';
|
||||||
import ReportModal from './report-modal';
|
import ReportModal from './report-modal';
|
||||||
import ShortcutsSettings from './shortcuts-settings';
|
import ShortcutsSettings from './shortcuts-settings';
|
||||||
|
|
||||||
const Compose = lazy(() => import('./compose'));
|
|
||||||
|
|
||||||
subscribe(states, (changes) => {
|
subscribe(states, (changes) => {
|
||||||
for (const [action, path, value, prevValue] of changes) {
|
for (const [action, path, value, prevValue] of changes) {
|
||||||
// When closing modal, focus on deck
|
// When closing modal, focus on deck
|
||||||
|
@ -36,55 +33,60 @@ export default function Modals() {
|
||||||
const navigate = useNavigate();
|
const navigate = useNavigate();
|
||||||
const location = useLocation();
|
const location = useLocation();
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
setTimeout(preload, 1000);
|
||||||
|
}, []);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
{!!snapStates.showCompose && (
|
{!!snapStates.showCompose && (
|
||||||
<Modal class="solid">
|
<Modal
|
||||||
<IntlSegmenterSuspense>
|
class={`solid ${snapStates.composerState.minimized ? 'min' : ''}`}
|
||||||
<Compose
|
minimized={!!snapStates.composerState.minimized}
|
||||||
replyToStatus={
|
>
|
||||||
typeof snapStates.showCompose !== 'boolean'
|
<ComposeSuspense
|
||||||
? snapStates.showCompose.replyToStatus
|
replyToStatus={
|
||||||
: window.__COMPOSE__?.replyToStatus || null
|
typeof snapStates.showCompose !== 'boolean'
|
||||||
|
? snapStates.showCompose.replyToStatus
|
||||||
|
: window.__COMPOSE__?.replyToStatus || null
|
||||||
|
}
|
||||||
|
editStatus={
|
||||||
|
states.showCompose?.editStatus ||
|
||||||
|
window.__COMPOSE__?.editStatus ||
|
||||||
|
null
|
||||||
|
}
|
||||||
|
draftStatus={
|
||||||
|
states.showCompose?.draftStatus ||
|
||||||
|
window.__COMPOSE__?.draftStatus ||
|
||||||
|
null
|
||||||
|
}
|
||||||
|
onClose={(results) => {
|
||||||
|
const { newStatus, instance, type } = results || {};
|
||||||
|
states.showCompose = false;
|
||||||
|
window.__COMPOSE__ = null;
|
||||||
|
if (newStatus) {
|
||||||
|
states.reloadStatusPage++;
|
||||||
|
showToast({
|
||||||
|
text: {
|
||||||
|
post: 'Post published. Check it out.',
|
||||||
|
reply: 'Reply posted. Check it out.',
|
||||||
|
edit: 'Post updated. Check it out.',
|
||||||
|
}[type || 'post'],
|
||||||
|
delay: 1000,
|
||||||
|
duration: 10_000, // 10 seconds
|
||||||
|
onClick: (toast) => {
|
||||||
|
toast.hideToast();
|
||||||
|
states.prevLocation = location;
|
||||||
|
navigate(
|
||||||
|
instance
|
||||||
|
? `/${instance}/s/${newStatus.id}`
|
||||||
|
: `/s/${newStatus.id}`,
|
||||||
|
);
|
||||||
|
},
|
||||||
|
});
|
||||||
}
|
}
|
||||||
editStatus={
|
}}
|
||||||
states.showCompose?.editStatus ||
|
/>
|
||||||
window.__COMPOSE__?.editStatus ||
|
|
||||||
null
|
|
||||||
}
|
|
||||||
draftStatus={
|
|
||||||
states.showCompose?.draftStatus ||
|
|
||||||
window.__COMPOSE__?.draftStatus ||
|
|
||||||
null
|
|
||||||
}
|
|
||||||
onClose={(results) => {
|
|
||||||
const { newStatus, instance, type } = results || {};
|
|
||||||
states.showCompose = false;
|
|
||||||
window.__COMPOSE__ = null;
|
|
||||||
if (newStatus) {
|
|
||||||
states.reloadStatusPage++;
|
|
||||||
showToast({
|
|
||||||
text: {
|
|
||||||
post: 'Post published. Check it out.',
|
|
||||||
reply: 'Reply posted. Check it out.',
|
|
||||||
edit: 'Post updated. Check it out.',
|
|
||||||
}[type || 'post'],
|
|
||||||
delay: 1000,
|
|
||||||
duration: 10_000, // 10 seconds
|
|
||||||
onClick: (toast) => {
|
|
||||||
toast.hideToast();
|
|
||||||
states.prevLocation = location;
|
|
||||||
navigate(
|
|
||||||
instance
|
|
||||||
? `/${instance}/s/${newStatus.id}`
|
|
||||||
: `/s/${newStatus.id}`,
|
|
||||||
);
|
|
||||||
},
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
</IntlSegmenterSuspense>
|
|
||||||
</Modal>
|
</Modal>
|
||||||
)}
|
)}
|
||||||
{!!snapStates.showSettings && (
|
{!!snapStates.showSettings && (
|
||||||
|
@ -187,6 +189,7 @@ export default function Modals() {
|
||||||
}
|
}
|
||||||
postID={snapStates.showGenericAccounts.postID}
|
postID={snapStates.showGenericAccounts.postID}
|
||||||
onClose={() => (states.showGenericAccounts = false)}
|
onClose={() => (states.showGenericAccounts = false)}
|
||||||
|
blankCopy={snapStates.showGenericAccounts.blankCopy}
|
||||||
/>
|
/>
|
||||||
</Modal>
|
</Modal>
|
||||||
)}
|
)}
|
||||||
|
|
|
@ -31,16 +31,17 @@ function NameText({
|
||||||
.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(/\s+/g, ''); // E.g. "My name" === "myname"
|
||||||
const shortenedAlphaNumericDisplayName = shortenedDisplayName.replace(
|
const shortenedAlphaNumericDisplayName = shortenedDisplayName.replace(
|
||||||
/[^a-z0-9]/gi,
|
/[^a-z0-9@\.]/gi,
|
||||||
'',
|
'',
|
||||||
); // Remove non-alphanumeric characters
|
); // Remove non-alphanumeric characters
|
||||||
|
|
||||||
if (
|
if (
|
||||||
!short &&
|
(!short &&
|
||||||
(trimmedUsername === trimmedDisplayName ||
|
(trimmedUsername === trimmedDisplayName ||
|
||||||
trimmedUsername === shortenedDisplayName ||
|
trimmedUsername === shortenedDisplayName ||
|
||||||
trimmedUsername === shortenedAlphaNumericDisplayName ||
|
trimmedUsername === shortenedAlphaNumericDisplayName ||
|
||||||
nameCollator.compare(trimmedUsername, shortenedDisplayName) === 0)
|
nameCollator.compare(trimmedUsername, shortenedDisplayName) === 0)) ||
|
||||||
|
shortenedAlphaNumericDisplayName === acct.toLowerCase()
|
||||||
) {
|
) {
|
||||||
username = null;
|
username = null;
|
||||||
}
|
}
|
||||||
|
@ -57,9 +58,15 @@ function NameText({
|
||||||
}
|
}
|
||||||
onClick={(e) => {
|
onClick={(e) => {
|
||||||
if (external) return;
|
if (external) return;
|
||||||
|
if (e.shiftKey) return; // Save link? 🤷♂️
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
e.stopPropagation();
|
e.stopPropagation();
|
||||||
if (onClick) return onClick(e);
|
if (onClick) return onClick(e);
|
||||||
|
if (e.metaKey || e.ctrlKey || e.shiftKey || e.which === 2) {
|
||||||
|
const internalURL = `#/${instance}/a/${id}`;
|
||||||
|
window.open(internalURL, '_blank');
|
||||||
|
return;
|
||||||
|
}
|
||||||
states.showAccount = {
|
states.showAccount = {
|
||||||
account,
|
account,
|
||||||
instance,
|
instance,
|
||||||
|
|
|
@ -132,7 +132,7 @@ const MODERATION_WARNING_TEXT = {
|
||||||
suspend: 'Your account has been suspended.',
|
suspend: 'Your account has been suspended.',
|
||||||
};
|
};
|
||||||
|
|
||||||
const AVATARS_LIMIT = 50;
|
const AVATARS_LIMIT = 30;
|
||||||
|
|
||||||
function Notification({
|
function Notification({
|
||||||
notification,
|
notification,
|
||||||
|
@ -374,11 +374,7 @@ function Notification({
|
||||||
? 'xxl'
|
? 'xxl'
|
||||||
: _accounts.length < 20
|
: _accounts.length < 20
|
||||||
? 'xl'
|
? 'xl'
|
||||||
: _accounts.length < 30
|
: 'l'
|
||||||
? 'l'
|
|
||||||
: _accounts.length < 40
|
|
||||||
? 'm'
|
|
||||||
: 's' // My god, this person is popular!
|
|
||||||
}
|
}
|
||||||
key={account.id}
|
key={account.id}
|
||||||
alt={`${account.displayName} @${account.acct}`}
|
alt={`${account.displayName} @${account.acct}`}
|
||||||
|
|
|
@ -23,6 +23,7 @@ import {
|
||||||
} from 'preact/hooks';
|
} from 'preact/hooks';
|
||||||
import punycode from 'punycode';
|
import punycode from 'punycode';
|
||||||
import { useHotkeys } from 'react-hotkeys-hook';
|
import { useHotkeys } from 'react-hotkeys-hook';
|
||||||
|
import { detectAll } from 'tinyld/light';
|
||||||
import { useLongPress } from 'use-long-press';
|
import { useLongPress } from 'use-long-press';
|
||||||
import { useSnapshot } from 'valtio';
|
import { useSnapshot } from 'valtio';
|
||||||
|
|
||||||
|
@ -46,11 +47,13 @@ import handleContentLinks from '../utils/handle-content-links';
|
||||||
import htmlContentLength from '../utils/html-content-length';
|
import htmlContentLength from '../utils/html-content-length';
|
||||||
import isMastodonLinkMaybe from '../utils/isMastodonLinkMaybe';
|
import isMastodonLinkMaybe from '../utils/isMastodonLinkMaybe';
|
||||||
import localeMatch from '../utils/locale-match';
|
import localeMatch from '../utils/locale-match';
|
||||||
|
import mem from '../utils/mem';
|
||||||
import niceDateTime from '../utils/nice-date-time';
|
import niceDateTime from '../utils/nice-date-time';
|
||||||
import openCompose from '../utils/open-compose';
|
import openCompose from '../utils/open-compose';
|
||||||
import pmem from '../utils/pmem';
|
import pmem from '../utils/pmem';
|
||||||
import safeBoundingBoxPadding from '../utils/safe-bounding-box-padding';
|
import safeBoundingBoxPadding from '../utils/safe-bounding-box-padding';
|
||||||
import shortenNumber from '../utils/shorten-number';
|
import shortenNumber from '../utils/shorten-number';
|
||||||
|
import showCompose from '../utils/show-compose';
|
||||||
import showToast from '../utils/show-toast';
|
import showToast from '../utils/show-toast';
|
||||||
import { speak, supportsTTS } from '../utils/speech';
|
import { speak, supportsTTS } from '../utils/speech';
|
||||||
import states, { getStatus, saveStatus, statusKey } from '../utils/states';
|
import states, { getStatus, saveStatus, statusKey } from '../utils/states';
|
||||||
|
@ -157,6 +160,25 @@ const SIZE_CLASS = {
|
||||||
l: 'large',
|
l: 'large',
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const detectLang = mem((text) => {
|
||||||
|
text = text?.trim();
|
||||||
|
|
||||||
|
// Ref: https://github.com/komodojp/tinyld/blob/develop/docs/benchmark.md
|
||||||
|
// 500 should be enough for now, also the default max chars for Mastodon
|
||||||
|
if (text?.length > 500) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
const langs = detectAll(text);
|
||||||
|
const lang = langs[0];
|
||||||
|
if (lang?.lang && lang?.accuracy > 0.5) {
|
||||||
|
// If > 50% accurate, use it
|
||||||
|
// It can be accurate if < 50% but better be safe
|
||||||
|
// Though > 50% also can be inaccurate 🤷♂️
|
||||||
|
return lang.lang;
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
});
|
||||||
|
|
||||||
function Status({
|
function Status({
|
||||||
statusID,
|
statusID,
|
||||||
status,
|
status,
|
||||||
|
@ -241,7 +263,7 @@ function Status({
|
||||||
sensitive,
|
sensitive,
|
||||||
spoilerText,
|
spoilerText,
|
||||||
visibility, // public, unlisted, private, direct
|
visibility, // public, unlisted, private, direct
|
||||||
language,
|
language: _language,
|
||||||
editedAt,
|
editedAt,
|
||||||
filtered,
|
filtered,
|
||||||
card,
|
card,
|
||||||
|
@ -264,6 +286,42 @@ function Status({
|
||||||
emojiReactions,
|
emojiReactions,
|
||||||
} = status;
|
} = status;
|
||||||
|
|
||||||
|
const [languageAutoDetected, setLanguageAutoDetected] = useState(null);
|
||||||
|
useEffect(() => {
|
||||||
|
if (!content) return;
|
||||||
|
if (_language) return;
|
||||||
|
let timer;
|
||||||
|
timer = setTimeout(() => {
|
||||||
|
let detected = detectLang(
|
||||||
|
getHTMLText(content, {
|
||||||
|
preProcess: (dom) => {
|
||||||
|
// Remove anything that can skew the language detection
|
||||||
|
|
||||||
|
// Remove .mention, .hashtag, pre, code, a:has(.invisible)
|
||||||
|
dom
|
||||||
|
.querySelectorAll(
|
||||||
|
'.mention, .hashtag, pre, code, a:has(.invisible)',
|
||||||
|
)
|
||||||
|
.forEach((a) => {
|
||||||
|
a.remove();
|
||||||
|
});
|
||||||
|
|
||||||
|
// Remove links that contains text that starts with https?://
|
||||||
|
dom.querySelectorAll('a').forEach((a) => {
|
||||||
|
const text = a.innerText.trim();
|
||||||
|
if (text.startsWith('https://') || text.startsWith('http://')) {
|
||||||
|
a.remove();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
setLanguageAutoDetected(detected);
|
||||||
|
}, 1000);
|
||||||
|
return () => clearTimeout(timer);
|
||||||
|
}, [content, _language]);
|
||||||
|
const language = _language || languageAutoDetected;
|
||||||
|
|
||||||
// if (!mediaAttachments?.length) mediaFirst = false;
|
// if (!mediaAttachments?.length) mediaFirst = false;
|
||||||
const hasMediaAttachments = !!mediaAttachments?.length;
|
const hasMediaAttachments = !!mediaAttachments?.length;
|
||||||
if (mediaFirst && hasMediaAttachments) size = 's';
|
if (mediaFirst && hasMediaAttachments) size = 's';
|
||||||
|
@ -524,9 +582,9 @@ function Status({
|
||||||
});
|
});
|
||||||
if (newWin) return;
|
if (newWin) return;
|
||||||
}
|
}
|
||||||
states.showCompose = {
|
showCompose({
|
||||||
replyToStatus: status,
|
replyToStatus: status,
|
||||||
};
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
// Check if media has no descriptions
|
// Check if media has no descriptions
|
||||||
|
@ -771,11 +829,11 @@ function Status({
|
||||||
menuExtras={
|
menuExtras={
|
||||||
<MenuItem
|
<MenuItem
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
states.showCompose = {
|
showCompose({
|
||||||
draftStatus: {
|
draftStatus: {
|
||||||
status: `\n${url}`,
|
status: `\n${url}`,
|
||||||
},
|
},
|
||||||
};
|
});
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<Icon icon="quote" />
|
<Icon icon="quote" />
|
||||||
|
@ -1092,9 +1150,9 @@ function Status({
|
||||||
{supports('@mastodon/post-edit') && (
|
{supports('@mastodon/post-edit') && (
|
||||||
<MenuItem
|
<MenuItem
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
states.showCompose = {
|
showCompose({
|
||||||
editStatus: status,
|
editStatus: status,
|
||||||
};
|
});
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<Icon icon="pencil" />
|
<Icon icon="pencil" />
|
||||||
|
@ -1897,6 +1955,7 @@ function Status({
|
||||||
forceTranslate={forceTranslate || inlineTranslate}
|
forceTranslate={forceTranslate || inlineTranslate}
|
||||||
mini={!isSizeLarge && !withinContext}
|
mini={!isSizeLarge && !withinContext}
|
||||||
sourceLanguage={language}
|
sourceLanguage={language}
|
||||||
|
autoDetected={languageAutoDetected}
|
||||||
text={getPostText(status)}
|
text={getPostText(status)}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
|
@ -2125,11 +2184,11 @@ function Status({
|
||||||
menuExtras={
|
menuExtras={
|
||||||
<MenuItem
|
<MenuItem
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
states.showCompose = {
|
showCompose({
|
||||||
draftStatus: {
|
draftStatus: {
|
||||||
status: `\n${url}`,
|
status: `\n${url}`,
|
||||||
},
|
},
|
||||||
};
|
});
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<Icon icon="quote" />
|
<Icon icon="quote" />
|
||||||
|
@ -3124,7 +3183,7 @@ function StatusCompact({ sKey }) {
|
||||||
const {
|
const {
|
||||||
sensitive,
|
sensitive,
|
||||||
spoilerText,
|
spoilerText,
|
||||||
account: { avatar, avatarStatic, bot },
|
account: { avatar, avatarStatic, bot } = {},
|
||||||
visibility,
|
visibility,
|
||||||
content,
|
content,
|
||||||
language,
|
language,
|
||||||
|
|
|
@ -77,6 +77,7 @@ function TranslationBlock({
|
||||||
onTranslate,
|
onTranslate,
|
||||||
text = '',
|
text = '',
|
||||||
mini,
|
mini,
|
||||||
|
autoDetected,
|
||||||
}) {
|
}) {
|
||||||
const targetLang = getTranslateTargetLanguage(true);
|
const targetLang = getTranslateTargetLanguage(true);
|
||||||
const [uiState, setUIState] = useState('default');
|
const [uiState, setUIState] = useState('default');
|
||||||
|
@ -187,7 +188,9 @@ function TranslationBlock({
|
||||||
{uiState === 'loading'
|
{uiState === 'loading'
|
||||||
? 'Translating…'
|
? 'Translating…'
|
||||||
: sourceLanguage && sourceLangText && !detectedLang
|
: sourceLanguage && sourceLangText && !detectedLang
|
||||||
? `Translate from ${sourceLangText}`
|
? autoDetected
|
||||||
|
? `Translate from ${sourceLangText} (auto-detected)`
|
||||||
|
: `Translate from ${sourceLangText}`
|
||||||
: `Translate`}
|
: `Translate`}
|
||||||
</span>
|
</span>
|
||||||
</button>
|
</button>
|
||||||
|
|
|
@ -3,16 +3,12 @@ import './index.css';
|
||||||
import './app.css';
|
import './app.css';
|
||||||
|
|
||||||
import { render } from 'preact';
|
import { render } from 'preact';
|
||||||
import { lazy } from 'preact/compat';
|
|
||||||
import { useEffect, useState } from 'preact/hooks';
|
import { useEffect, useState } from 'preact/hooks';
|
||||||
|
|
||||||
import IntlSegmenterSuspense from './components/intl-segmenter-suspense';
|
import ComposeSuspense from './components/compose-suspense';
|
||||||
import { initStates } from './utils/states';
|
import { initStates } from './utils/states';
|
||||||
// import Compose from './components/compose';
|
|
||||||
import useTitle from './utils/useTitle';
|
import useTitle from './utils/useTitle';
|
||||||
|
|
||||||
const Compose = lazy(() => import('./components/compose'));
|
|
||||||
|
|
||||||
if (window.opener) {
|
if (window.opener) {
|
||||||
console = window.opener.console;
|
console = window.opener.console;
|
||||||
}
|
}
|
||||||
|
@ -66,25 +62,23 @@ function App() {
|
||||||
console.debug('OPEN COMPOSE');
|
console.debug('OPEN COMPOSE');
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<IntlSegmenterSuspense>
|
<ComposeSuspense
|
||||||
<Compose
|
editStatus={editStatus}
|
||||||
editStatus={editStatus}
|
replyToStatus={replyToStatus}
|
||||||
replyToStatus={replyToStatus}
|
draftStatus={draftStatus}
|
||||||
draftStatus={draftStatus}
|
standalone
|
||||||
standalone
|
hasOpener={window.opener}
|
||||||
hasOpener={window.opener}
|
onClose={(results) => {
|
||||||
onClose={(results) => {
|
const { newStatus, fn = () => {} } = results || {};
|
||||||
const { newStatus, fn = () => {} } = results || {};
|
try {
|
||||||
try {
|
if (newStatus) {
|
||||||
if (newStatus) {
|
window.opener.__STATES__.reloadStatusPage++;
|
||||||
window.opener.__STATES__.reloadStatusPage++;
|
}
|
||||||
}
|
fn();
|
||||||
fn();
|
setUIState('closed');
|
||||||
setUIState('closed');
|
} catch (e) {}
|
||||||
} catch (e) {}
|
}}
|
||||||
}}
|
/>
|
||||||
/>
|
|
||||||
</IntlSegmenterSuspense>
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1,5 +1,6 @@
|
||||||
{
|
{
|
||||||
"@mastodon/edit-media-attributes": ">=4.1",
|
"@mastodon/edit-media-attributes": ">=4.1",
|
||||||
"@mastodon/list-exclusive": ">=4.2",
|
"@mastodon/list-exclusive": ">=4.2",
|
||||||
"@mastodon/filtered-notifications": "~4.3 || >=4.3"
|
"@mastodon/filtered-notifications": "~4.3 || >=4.3",
|
||||||
|
"@mastodon/fetch-multiple-statuses": "~4.3 || >=4.3"
|
||||||
}
|
}
|
||||||
|
|
|
@ -547,3 +547,9 @@ kbd {
|
||||||
.shazam-container-horizontal[hidden] {
|
.shazam-container-horizontal[hidden] {
|
||||||
grid-template-columns: 0fr;
|
grid-template-columns: 0fr;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@keyframes spin {
|
||||||
|
to {
|
||||||
|
transform: rotate(360deg);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -41,6 +41,7 @@ import states, { statusKey } from '../utils/states';
|
||||||
import statusPeek from '../utils/status-peek';
|
import statusPeek from '../utils/status-peek';
|
||||||
import store from '../utils/store';
|
import store from '../utils/store';
|
||||||
import { getCurrentAccountID, getCurrentAccountNS } from '../utils/store-utils';
|
import { getCurrentAccountID, getCurrentAccountNS } from '../utils/store-utils';
|
||||||
|
import supports from '../utils/supports';
|
||||||
import { assignFollowedTags } from '../utils/timeline-utils';
|
import { assignFollowedTags } from '../utils/timeline-utils';
|
||||||
import useTitle from '../utils/useTitle';
|
import useTitle from '../utils/useTitle';
|
||||||
|
|
||||||
|
@ -116,6 +117,8 @@ function Catchup() {
|
||||||
}, []);
|
}, []);
|
||||||
const isSelf = (accountID) => accountID === currentAccount;
|
const isSelf = (accountID) => accountID === currentAccount;
|
||||||
|
|
||||||
|
const supportsPixelfed = supports('@pixelfed/home-include-reblogs');
|
||||||
|
|
||||||
async function fetchHome({ maxCreatedAt }) {
|
async function fetchHome({ maxCreatedAt }) {
|
||||||
const maxCreatedAtDate = maxCreatedAt ? new Date(maxCreatedAt) : null;
|
const maxCreatedAtDate = maxCreatedAt ? new Date(maxCreatedAt) : null;
|
||||||
console.debug('fetchHome', maxCreatedAtDate);
|
console.debug('fetchHome', maxCreatedAtDate);
|
||||||
|
@ -123,6 +126,13 @@ function Catchup() {
|
||||||
const homeIterator = masto.v1.timelines.home.list({ limit: 40 });
|
const homeIterator = masto.v1.timelines.home.list({ limit: 40 });
|
||||||
mainloop: while (true) {
|
mainloop: while (true) {
|
||||||
try {
|
try {
|
||||||
|
if (supportsPixelfed && homeIterator.nextParams) {
|
||||||
|
if (typeof homeIterator.nextParams === 'string') {
|
||||||
|
homeIterator.nextParams += '&include_reblogs=true';
|
||||||
|
} else {
|
||||||
|
homeIterator.nextParams.include_reblogs = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
const results = await homeIterator.next();
|
const results = await homeIterator.next();
|
||||||
const { value } = results;
|
const { value } = results;
|
||||||
if (value?.length) {
|
if (value?.length) {
|
||||||
|
@ -1677,63 +1687,70 @@ function PostPeek({ post, filterInfo }) {
|
||||||
} = post;
|
} = post;
|
||||||
const isThread =
|
const isThread =
|
||||||
(inReplyToId && inReplyToAccountId === account.id) || !!_thread;
|
(inReplyToId && inReplyToAccountId === account.id) || !!_thread;
|
||||||
const showMedia = !spoilerText && !sensitive;
|
|
||||||
|
const readingExpandSpoilers = useMemo(() => {
|
||||||
|
const prefs = store.account.get('preferences') || {};
|
||||||
|
return !!prefs['reading:expand:spoilers'];
|
||||||
|
}, []);
|
||||||
|
// const readingExpandSpoilers = true;
|
||||||
|
const showMedia = readingExpandSpoilers || (!spoilerText && !sensitive);
|
||||||
const postText = content ? statusPeek(post) : '';
|
const postText = content ? statusPeek(post) : '';
|
||||||
|
|
||||||
|
const showPostContent = !spoilerText || readingExpandSpoilers;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div class="post-peek" title={!spoilerText ? postText : ''}>
|
<div class="post-peek" title={!spoilerText ? postText : ''}>
|
||||||
<span class="post-peek-content">
|
<span class="post-peek-content">
|
||||||
|
{isThread && !showPostContent && (
|
||||||
|
<>
|
||||||
|
<span class="post-peek-tag post-peek-thread">Thread</span>{' '}
|
||||||
|
</>
|
||||||
|
)}
|
||||||
{!!filterInfo ? (
|
{!!filterInfo ? (
|
||||||
<>
|
<span class="post-peek-filtered">
|
||||||
{isThread && (
|
Filtered{filterInfo?.titlesStr ? `: ${filterInfo.titlesStr}` : ''}
|
||||||
<>
|
</span>
|
||||||
<span class="post-peek-tag post-peek-thread">Thread</span>{' '}
|
|
||||||
</>
|
|
||||||
)}
|
|
||||||
<span class="post-peek-filtered">
|
|
||||||
Filtered{filterInfo?.titlesStr ? `: ${filterInfo.titlesStr}` : ''}
|
|
||||||
</span>
|
|
||||||
</>
|
|
||||||
) : !!spoilerText ? (
|
|
||||||
<>
|
|
||||||
{isThread && (
|
|
||||||
<>
|
|
||||||
<span class="post-peek-tag post-peek-thread">Thread</span>{' '}
|
|
||||||
</>
|
|
||||||
)}
|
|
||||||
<span class="post-peek-spoiler">
|
|
||||||
<Icon icon="eye-close" /> {spoilerText}
|
|
||||||
</span>
|
|
||||||
</>
|
|
||||||
) : (
|
) : (
|
||||||
<div class="post-peek-html">
|
<>
|
||||||
{isThread && (
|
{!!spoilerText && (
|
||||||
<>
|
<span class="post-peek-spoiler">
|
||||||
<span class="post-peek-tag post-peek-thread">Thread</span>{' '}
|
<Icon
|
||||||
</>
|
icon={`${readingExpandSpoilers ? 'eye-open' : 'eye-close'}`}
|
||||||
|
/>{' '}
|
||||||
|
{spoilerText}
|
||||||
|
</span>
|
||||||
)}
|
)}
|
||||||
{!!content && (
|
{showPostContent && (
|
||||||
<div
|
<div class="post-peek-html">
|
||||||
dangerouslySetInnerHTML={{
|
{isThread && (
|
||||||
__html: emojifyText(content, emojis),
|
<>
|
||||||
}}
|
<span class="post-peek-tag post-peek-thread">Thread</span>{' '}
|
||||||
/>
|
</>
|
||||||
|
)}
|
||||||
|
{!!content && (
|
||||||
|
<div
|
||||||
|
dangerouslySetInnerHTML={{
|
||||||
|
__html: emojifyText(content, emojis),
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
{!!poll?.options?.length &&
|
||||||
|
poll.options.map((o) => (
|
||||||
|
<div>
|
||||||
|
{poll.multiple ? '▪️' : '•'} {o.title}
|
||||||
|
</div>
|
||||||
|
))}
|
||||||
|
{!content &&
|
||||||
|
mediaAttachments?.length === 1 &&
|
||||||
|
mediaAttachments[0].description && (
|
||||||
|
<>
|
||||||
|
<span class="post-peek-tag post-peek-alt">ALT</span>{' '}
|
||||||
|
<div>{mediaAttachments[0].description}</div>
|
||||||
|
</>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
)}
|
)}
|
||||||
{!!poll?.options?.length &&
|
</>
|
||||||
poll.options.map((o) => (
|
|
||||||
<div>
|
|
||||||
{poll.multiple ? '▪️' : '•'} {o.title}
|
|
||||||
</div>
|
|
||||||
))}
|
|
||||||
{!content &&
|
|
||||||
mediaAttachments?.length === 1 &&
|
|
||||||
mediaAttachments[0].description && (
|
|
||||||
<>
|
|
||||||
<span class="post-peek-tag post-peek-alt">ALT</span>{' '}
|
|
||||||
<div>{mediaAttachments[0].description}</div>
|
|
||||||
</>
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
)}
|
)}
|
||||||
</span>
|
</span>
|
||||||
{!filterInfo && (
|
{!filterInfo && (
|
||||||
|
|
|
@ -6,6 +6,7 @@ import { api } from '../utils/api';
|
||||||
import { filteredItems } from '../utils/filters';
|
import { filteredItems } from '../utils/filters';
|
||||||
import states from '../utils/states';
|
import states from '../utils/states';
|
||||||
import { getStatus, saveStatus } from '../utils/states';
|
import { getStatus, saveStatus } from '../utils/states';
|
||||||
|
import supports from '../utils/supports';
|
||||||
import {
|
import {
|
||||||
assignFollowedTags,
|
assignFollowedTags,
|
||||||
clearFollowedTagsState,
|
clearFollowedTagsState,
|
||||||
|
@ -23,11 +24,19 @@ function Following({ title, path, id, ...props }) {
|
||||||
const latestItem = useRef();
|
const latestItem = useRef();
|
||||||
|
|
||||||
console.debug('RENDER Following', title, id);
|
console.debug('RENDER Following', title, id);
|
||||||
|
const supportsPixelfed = supports('@pixelfed/home-include-reblogs');
|
||||||
|
|
||||||
async function fetchHome(firstLoad) {
|
async function fetchHome(firstLoad) {
|
||||||
if (firstLoad || !homeIterator.current) {
|
if (firstLoad || !homeIterator.current) {
|
||||||
homeIterator.current = masto.v1.timelines.home.list({ limit: LIMIT });
|
homeIterator.current = masto.v1.timelines.home.list({ limit: LIMIT });
|
||||||
}
|
}
|
||||||
|
if (supportsPixelfed && homeIterator.current?.nextParams) {
|
||||||
|
if (typeof homeIterator.current.nextParams === 'string') {
|
||||||
|
homeIterator.current.nextParams += '&include_reblogs=true';
|
||||||
|
} else {
|
||||||
|
homeIterator.current.nextParams.include_reblogs = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
const results = await homeIterator.current.next();
|
const results = await homeIterator.current.next();
|
||||||
let { value } = results;
|
let { value } = results;
|
||||||
if (value?.length) {
|
if (value?.length) {
|
||||||
|
@ -63,12 +72,14 @@ function Following({ title, path, id, ...props }) {
|
||||||
|
|
||||||
async function checkForUpdates() {
|
async function checkForUpdates() {
|
||||||
try {
|
try {
|
||||||
const results = await masto.v1.timelines.home
|
const opts = {
|
||||||
.list({
|
limit: 5,
|
||||||
limit: 5,
|
since_id: latestItem.current,
|
||||||
since_id: latestItem.current,
|
};
|
||||||
})
|
if (supports('@pixelfed/home-include-reblogs')) {
|
||||||
.next();
|
opts.include_reblogs = true;
|
||||||
|
}
|
||||||
|
const results = await masto.v1.timelines.home.list(opts).next();
|
||||||
let { value } = results;
|
let { value } = results;
|
||||||
console.log('checkForUpdates', latestItem.current, value);
|
console.log('checkForUpdates', latestItem.current, value);
|
||||||
const valueContainsLatestItem = value[0]?.id === latestItem.current; // since_id might not be supported
|
const valueContainsLatestItem = value[0]?.id === latestItem.current; // since_id might not be supported
|
||||||
|
|
|
@ -84,7 +84,7 @@ function NotificationsLink() {
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
const NOTIFICATIONS_LIMIT = 30;
|
const NOTIFICATIONS_LIMIT = 80;
|
||||||
const NOTIFICATIONS_DISPLAY_LIMIT = 5;
|
const NOTIFICATIONS_DISPLAY_LIMIT = 5;
|
||||||
function NotificationsMenu({ anchorRef, state, onClose }) {
|
function NotificationsMenu({ anchorRef, state, onClose }) {
|
||||||
const { masto, instance } = api();
|
const { masto, instance } = api();
|
||||||
|
|
|
@ -33,7 +33,7 @@ import usePageVisibility from '../utils/usePageVisibility';
|
||||||
import useScroll from '../utils/useScroll';
|
import useScroll from '../utils/useScroll';
|
||||||
import useTitle from '../utils/useTitle';
|
import useTitle from '../utils/useTitle';
|
||||||
|
|
||||||
const LIMIT = 30; // 30 is the maximum limit :(
|
const LIMIT = 80;
|
||||||
const emptySearchParams = new URLSearchParams();
|
const emptySearchParams = new URLSearchParams();
|
||||||
|
|
||||||
const scrollIntoViewOptions = {
|
const scrollIntoViewOptions = {
|
||||||
|
@ -292,8 +292,13 @@ function Notifications({ columnMode }) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
const firstLoad = useRef(true);
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
let unsub = subscribeKey(states, 'notificationsShowNew', (v) => {
|
let unsub = subscribeKey(states, 'notificationsShowNew', (v) => {
|
||||||
|
if (firstLoad.current) {
|
||||||
|
firstLoad.current = false;
|
||||||
|
return;
|
||||||
|
}
|
||||||
if (uiState === 'loading') return;
|
if (uiState === 'loading') return;
|
||||||
if (v) loadUpdates();
|
if (v) loadUpdates();
|
||||||
setShowNew(v);
|
setShowNew(v);
|
||||||
|
|
|
@ -23,12 +23,6 @@
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@keyframes spin {
|
|
||||||
to {
|
|
||||||
transform: rotate(360deg);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.hero-heading {
|
.hero-heading {
|
||||||
font-size: var(--text-size);
|
font-size: var(--text-size);
|
||||||
display: inline-block;
|
display: inline-block;
|
||||||
|
|
|
@ -153,6 +153,18 @@ function StatusPage(params) {
|
||||||
return () => clearTimeout(timer);
|
return () => clearTimeout(timer);
|
||||||
}, [showMediaOnly]);
|
}, [showMediaOnly]);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
const $deckContainers = document.querySelectorAll('.deck-container');
|
||||||
|
$deckContainers.forEach(($deckContainer) => {
|
||||||
|
$deckContainer.setAttribute('inert', '');
|
||||||
|
});
|
||||||
|
return () => {
|
||||||
|
$deckContainers.forEach(($deckContainer) => {
|
||||||
|
$deckContainer.removeAttribute('inert');
|
||||||
|
});
|
||||||
|
};
|
||||||
|
}, []);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div class="deck-backdrop">
|
<div class="deck-backdrop">
|
||||||
{showMedia ? (
|
{showMedia ? (
|
||||||
|
@ -972,6 +984,18 @@ function StatusThread({ id, closeLink = '/', instance: propInstance }) {
|
||||||
[statuses, limit, renderStatus],
|
[statuses, limit, renderStatus],
|
||||||
);
|
);
|
||||||
|
|
||||||
|
// If there's spoiler in hero status, auto-expand it
|
||||||
|
useEffect(() => {
|
||||||
|
let timer = setTimeout(() => {
|
||||||
|
if (!heroStatusRef.current) return;
|
||||||
|
const spoilerButton = heroStatusRef.current.querySelector(
|
||||||
|
'.spoiler-button:not(.spoiling), .spoiler-media-button:not(.spoiling)',
|
||||||
|
);
|
||||||
|
if (spoilerButton) spoilerButton.click();
|
||||||
|
}, 1000);
|
||||||
|
return () => clearTimeout(timer);
|
||||||
|
}, [id]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div
|
<div
|
||||||
tabIndex="-1"
|
tabIndex="-1"
|
||||||
|
|
|
@ -1,8 +1,10 @@
|
||||||
import mem from './mem';
|
import mem from './mem';
|
||||||
|
|
||||||
const div = document.createElement('div');
|
const div = document.createElement('div');
|
||||||
function getHTMLText(html) {
|
function getHTMLText(html, opts) {
|
||||||
if (!html) return '';
|
if (!html) return '';
|
||||||
|
const { preProcess } = opts || {};
|
||||||
|
|
||||||
div.innerHTML = html
|
div.innerHTML = html
|
||||||
.replace(/<\/p>/g, '</p>\n\n')
|
.replace(/<\/p>/g, '</p>\n\n')
|
||||||
.replace(/<\/li>/g, '</li>\n');
|
.replace(/<\/li>/g, '</li>\n');
|
||||||
|
@ -10,6 +12,8 @@ function getHTMLText(html) {
|
||||||
br.replaceWith('\n');
|
br.replaceWith('\n');
|
||||||
});
|
});
|
||||||
|
|
||||||
|
preProcess?.(div);
|
||||||
|
|
||||||
// MASTODON-SPECIFIC classes
|
// MASTODON-SPECIFIC classes
|
||||||
// Remove .invisible
|
// Remove .invisible
|
||||||
div.querySelectorAll('.invisible').forEach((el) => {
|
div.querySelectorAll('.invisible').forEach((el) => {
|
||||||
|
|
27
src/utils/show-compose.js
Normal file
27
src/utils/show-compose.js
Normal file
|
@ -0,0 +1,27 @@
|
||||||
|
import openOSK from './open-osk';
|
||||||
|
import showToast from './show-toast';
|
||||||
|
import states from './states';
|
||||||
|
|
||||||
|
const TOAST_DURATION = 5_000; // 5 seconds
|
||||||
|
|
||||||
|
export default function showCompose(opts) {
|
||||||
|
if (!opts) opts = true;
|
||||||
|
|
||||||
|
if (states.showCompose) {
|
||||||
|
if (states.composerState.minimized) {
|
||||||
|
showToast({
|
||||||
|
duration: TOAST_DURATION,
|
||||||
|
text: `A draft post is currently minimized. Post or discard it before creating a new one.`,
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
showToast({
|
||||||
|
duration: TOAST_DURATION,
|
||||||
|
text: `A post is currently open. Post or discard it before creating a new one.`,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
openOSK();
|
||||||
|
states.showCompose = opts;
|
||||||
|
}
|
|
@ -40,6 +40,7 @@ const states = proxy({
|
||||||
statusReply: {},
|
statusReply: {},
|
||||||
accounts: {},
|
accounts: {},
|
||||||
routeNotification: null,
|
routeNotification: null,
|
||||||
|
composerState: {},
|
||||||
// Modals
|
// Modals
|
||||||
showCompose: false,
|
showCompose: false,
|
||||||
showSettings: false,
|
showSettings: false,
|
||||||
|
|
|
@ -18,6 +18,7 @@ const platformFeatures = {
|
||||||
'@mastodon/profile-edit': notContainPixelfed,
|
'@mastodon/profile-edit': notContainPixelfed,
|
||||||
'@mastodon/profile-private-note': notContainPixelfed,
|
'@mastodon/profile-private-note': notContainPixelfed,
|
||||||
'@pixelfed/trending': containPixelfed,
|
'@pixelfed/trending': containPixelfed,
|
||||||
|
'@pixelfed/home-include-reblogs': containPixelfed,
|
||||||
};
|
};
|
||||||
const supportsCache = {};
|
const supportsCache = {};
|
||||||
|
|
||||||
|
|
|
@ -4,6 +4,7 @@ import pmem from './pmem';
|
||||||
import { fetchRelationships } from './relationships';
|
import { fetchRelationships } from './relationships';
|
||||||
import states, { saveStatus, statusKey } from './states';
|
import states, { saveStatus, statusKey } from './states';
|
||||||
import store from './store';
|
import store from './store';
|
||||||
|
import supports from './supports';
|
||||||
|
|
||||||
export function groupBoosts(values) {
|
export function groupBoosts(values) {
|
||||||
let newValues = [];
|
let newValues = [];
|
||||||
|
@ -149,6 +150,7 @@ export function groupContext(items, instance) {
|
||||||
|
|
||||||
const newItems = [];
|
const newItems = [];
|
||||||
const appliedContextIndices = [];
|
const appliedContextIndices = [];
|
||||||
|
const inReplyToIds = [];
|
||||||
items.forEach((item) => {
|
items.forEach((item) => {
|
||||||
if (item.reblog) {
|
if (item.reblog) {
|
||||||
newItems.push(item);
|
newItems.push(item);
|
||||||
|
@ -176,17 +178,53 @@ export function groupContext(items, instance) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// PREPARE FOR REPLY HINTS
|
||||||
if (item.inReplyToId && item.inReplyToAccountId !== item.account.id) {
|
if (item.inReplyToId && item.inReplyToAccountId !== item.account.id) {
|
||||||
const sKey = statusKey(item.id, instance);
|
const sKey = statusKey(item.id, instance);
|
||||||
if (!states.statusReply[sKey]) {
|
if (!states.statusReply[sKey]) {
|
||||||
// If it's a reply and not a thread
|
// If it's a reply and not a thread
|
||||||
queueMicrotask(async () => {
|
inReplyToIds.push({
|
||||||
|
sKey,
|
||||||
|
inReplyToId: item.inReplyToId,
|
||||||
|
});
|
||||||
|
// queueMicrotask(async () => {
|
||||||
|
// try {
|
||||||
|
// const { masto } = api({ instance });
|
||||||
|
// // const replyToStatus = await masto.v1.statuses
|
||||||
|
// // .$select(item.inReplyToId)
|
||||||
|
// // .fetch();
|
||||||
|
// const replyToStatus = await fetchStatus(item.inReplyToId, masto);
|
||||||
|
// saveStatus(replyToStatus, instance, {
|
||||||
|
// skipThreading: true,
|
||||||
|
// skipUnfurling: true,
|
||||||
|
// });
|
||||||
|
// states.statusReply[sKey] = {
|
||||||
|
// id: replyToStatus.id,
|
||||||
|
// instance,
|
||||||
|
// };
|
||||||
|
// } catch (e) {
|
||||||
|
// // Silently fail
|
||||||
|
// console.error(e);
|
||||||
|
// }
|
||||||
|
// });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
newItems.push(item);
|
||||||
|
});
|
||||||
|
|
||||||
|
// FETCH AND SHOW REPLY HINTS
|
||||||
|
if (inReplyToIds?.length) {
|
||||||
|
queueMicrotask(() => {
|
||||||
|
const { masto } = api({ instance });
|
||||||
|
console.log('REPLYHINT', inReplyToIds);
|
||||||
|
|
||||||
|
// Fallback if batch fetch fails or returns nothing or not supported
|
||||||
|
async function fallbackFetch() {
|
||||||
|
for (let i = 0; i < inReplyToIds.length; i++) {
|
||||||
|
const { sKey, inReplyToId } = inReplyToIds[i];
|
||||||
try {
|
try {
|
||||||
const { masto } = api({ instance });
|
const replyToStatus = await fetchStatus(inReplyToId, masto);
|
||||||
// const replyToStatus = await masto.v1.statuses
|
|
||||||
// .$select(item.inReplyToId)
|
|
||||||
// .fetch();
|
|
||||||
const replyToStatus = await fetchStatus(item.inReplyToId, masto);
|
|
||||||
saveStatus(replyToStatus, instance, {
|
saveStatus(replyToStatus, instance, {
|
||||||
skipThreading: true,
|
skipThreading: true,
|
||||||
skipUnfurling: true,
|
skipUnfurling: true,
|
||||||
|
@ -195,16 +233,52 @@ export function groupContext(items, instance) {
|
||||||
id: replyToStatus.id,
|
id: replyToStatus.id,
|
||||||
instance,
|
instance,
|
||||||
};
|
};
|
||||||
|
// Pause 1s
|
||||||
|
await new Promise((resolve) => setTimeout(resolve, 1000));
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
// Silently fail
|
// Silently fail
|
||||||
console.error(e);
|
console.error(e);
|
||||||
}
|
}
|
||||||
});
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
newItems.push(item);
|
if (supports('@mastodon/fetch-multiple-statuses')) {
|
||||||
});
|
// This is batch fetching yooo, woot
|
||||||
|
// Limit 20, returns 422 if exceeded https://github.com/mastodon/mastodon/pull/27871
|
||||||
|
const ids = inReplyToIds.map(({ inReplyToId }) => inReplyToId);
|
||||||
|
(async () => {
|
||||||
|
try {
|
||||||
|
const replyToStatuses = await masto.v1.statuses.list({ id: ids });
|
||||||
|
if (replyToStatuses?.length) {
|
||||||
|
for (const replyToStatus of replyToStatuses) {
|
||||||
|
saveStatus(replyToStatus, instance, {
|
||||||
|
skipThreading: true,
|
||||||
|
skipUnfurling: true,
|
||||||
|
});
|
||||||
|
const sKey = inReplyToIds.find(
|
||||||
|
({ inReplyToId }) => inReplyToId === replyToStatus.id,
|
||||||
|
)?.sKey;
|
||||||
|
if (sKey) {
|
||||||
|
states.statusReply[sKey] = {
|
||||||
|
id: replyToStatus.id,
|
||||||
|
instance,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
fallbackFetch();
|
||||||
|
}
|
||||||
|
} catch (e) {
|
||||||
|
// Silently fail
|
||||||
|
console.error(e);
|
||||||
|
fallbackFetch();
|
||||||
|
}
|
||||||
|
})();
|
||||||
|
} else {
|
||||||
|
fallbackFetch();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
return newItems;
|
return newItems;
|
||||||
}
|
}
|
||||||
|
|
|
@ -9,6 +9,20 @@ export const throttle = pThrottle({
|
||||||
interval: 1000,
|
interval: 1000,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
const STATUS_ID_REGEXES = [
|
||||||
|
/\/@[^@\/]+@?[^\/]+?\/(\d+)$/i, // Mastodon
|
||||||
|
/\/notice\/(\w+)$/i, // Pleroma
|
||||||
|
];
|
||||||
|
function getStatusID(path) {
|
||||||
|
for (let i = 0; i < STATUS_ID_REGEXES.length; i++) {
|
||||||
|
const statusMatchID = path.match(STATUS_ID_REGEXES[i])?.[1];
|
||||||
|
if (statusMatchID) {
|
||||||
|
return statusMatchID;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
const denylistDomains = /(twitter|github)\.com/i;
|
const denylistDomains = /(twitter|github)\.com/i;
|
||||||
const failedUnfurls = {};
|
const failedUnfurls = {};
|
||||||
function _unfurlMastodonLink(instance, url) {
|
function _unfurlMastodonLink(instance, url) {
|
||||||
|
@ -53,11 +67,11 @@ function _unfurlMastodonLink(instance, url) {
|
||||||
}
|
}
|
||||||
const domain = urlObj.hostname;
|
const domain = urlObj.hostname;
|
||||||
const path = urlObj.pathname;
|
const path = urlObj.pathname;
|
||||||
// Regex /:username/:id, where username = @username or @username@domain, id = number
|
// Regex /:username/:id, where username = @username or @username@domain, id = post ID
|
||||||
const statusRegex = /\/@([^@\/]+)@?([^\/]+)?\/(\d+)$/i;
|
let statusMatchID = getStatusID(path);
|
||||||
const statusMatch = statusRegex.exec(path);
|
|
||||||
if (statusMatch) {
|
if (statusMatchID) {
|
||||||
const id = statusMatch[3];
|
const id = statusMatchID;
|
||||||
const { masto } = api({ instance: domain });
|
const { masto } = api({ instance: domain });
|
||||||
remoteInstanceFetch = masto.v1.statuses
|
remoteInstanceFetch = masto.v1.statuses
|
||||||
.$select(id)
|
.$select(id)
|
||||||
|
|
Loading…
Reference in a new issue