Compare commits
61 commits
6229acea20
...
c6824b9b0d
Author | SHA1 | Date | |
---|---|---|---|
|
c6824b9b0d | ||
|
36f38230c4 | ||
|
a66a4e238e | ||
|
aa7fb4441f | ||
|
f1dbb9ec42 | ||
|
a59668ea9a | ||
|
6581bc2881 | ||
|
28bb66f185 | ||
|
46d7cba1ea | ||
|
ff35c458c3 | ||
|
26d445af7d | ||
|
3470b9adec | ||
|
f3d77dd04e | ||
|
14f5c37721 | ||
|
94c59c47d1 | ||
|
a66307b757 | ||
|
9792700f30 | ||
|
36e852bebb | ||
|
6075542071 | ||
|
0386357688 | ||
|
3f0b933654 | ||
|
9cac63c37d | ||
|
5cfcfdc98b | ||
|
a2d995ec07 | ||
|
4ca9a802e3 | ||
|
990f2b2e29 | ||
|
725da37063 | ||
|
1b41d39032 | ||
|
23dd7f5a7a | ||
|
7d95c50c7a | ||
|
a352f94c2c | ||
|
38e2b176bc | ||
|
6b4c1c8505 | ||
|
46dfd9aab0 | ||
|
59d0138ca8 | ||
|
3fbd5b8622 | ||
|
b6c4045cb4 | ||
|
37c784dad2 | ||
|
04d431cf71 | ||
|
97458b66eb | ||
|
fadfc6052d | ||
|
0ca92e7509 | ||
|
b8484eff79 | ||
|
1017d1d270 | ||
|
04179340f6 | ||
|
9b0889fe23 | ||
|
79e87b7d89 | ||
|
0ebc0fa64c | ||
|
35974cc89c | ||
|
00675c827f | ||
|
2b3f65f28c | ||
|
2912d2e2e6 | ||
|
500f877d4b | ||
|
4b9ff0ca5b | ||
|
07f927d4ff | ||
|
8c6563a671 | ||
|
ffabd6188d | ||
|
d71b1a7e36 | ||
|
c47687e2e4 | ||
|
5b0d6dd58b | ||
|
ecd5c7b91e |
211
package-lock.json
generated
211
package-lock.json
generated
|
@ -8,7 +8,7 @@
|
|||
"name": "phanpy",
|
||||
"version": "0.1.0",
|
||||
"dependencies": {
|
||||
"@formatjs/intl-localematcher": "~0.5.2",
|
||||
"@formatjs/intl-localematcher": "~0.5.4",
|
||||
"@formkit/auto-animate": "~0.8.1",
|
||||
"@github/text-expander-element": "~2.6.1",
|
||||
"@iconify-icons/mingcute": "~1.2.9",
|
||||
|
@ -23,12 +23,12 @@
|
|||
"idb-keyval": "~6.2.1",
|
||||
"just-debounce-it": "~3.2.0",
|
||||
"lz-string": "~1.5.0",
|
||||
"masto": "~6.5.1",
|
||||
"masto": "~6.5.2",
|
||||
"moize": "~6.1.6",
|
||||
"p-retry": "~6.2.0",
|
||||
"p-throttle": "~6.1.0",
|
||||
"preact": "~10.19.3",
|
||||
"react-hotkeys-hook": "~4.4.1",
|
||||
"react-hotkeys-hook": "~4.4.4",
|
||||
"react-intersection-observer": "~9.5.3",
|
||||
"react-quick-pinch-zoom": "~5.1.0",
|
||||
"react-router-dom": "6.6.2",
|
||||
|
@ -43,16 +43,16 @@
|
|||
"valtio": "1.9.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@preact/preset-vite": "~2.7.0",
|
||||
"@preact/preset-vite": "~2.8.1",
|
||||
"@trivago/prettier-plugin-sort-imports": "~4.3.0",
|
||||
"postcss": "~8.4.32",
|
||||
"postcss": "~8.4.33",
|
||||
"postcss-dark-theme-class": "~1.1.0",
|
||||
"postcss-preset-env": "~9.3.0",
|
||||
"twitter-text": "~3.1.0",
|
||||
"vite": "~5.0.10",
|
||||
"vite": "~5.0.12",
|
||||
"vite-plugin-generate-file": "~0.1.1",
|
||||
"vite-plugin-html-config": "~1.0.11",
|
||||
"vite-plugin-pwa": "~0.17.4",
|
||||
"vite-plugin-pwa": "~0.17.5",
|
||||
"vite-plugin-remove-console": "~2.2.0",
|
||||
"workbox-cacheable-response": "~7.0.0",
|
||||
"workbox-expiration": "~7.0.0",
|
||||
|
@ -2899,10 +2899,9 @@
|
|||
}
|
||||
},
|
||||
"node_modules/@formatjs/intl-localematcher": {
|
||||
"version": "0.5.2",
|
||||
"resolved": "https://registry.npmjs.org/@formatjs/intl-localematcher/-/intl-localematcher-0.5.2.tgz",
|
||||
"integrity": "sha512-txaaE2fiBMagLrR4jYhxzFO6wEdEG4TPMqrzBAcbr4HFUYzH/YC+lg6OIzKCHm8WgDdyQevxbAAV1OgcXctuGw==",
|
||||
"license": "MIT",
|
||||
"version": "0.5.4",
|
||||
"resolved": "https://registry.npmjs.org/@formatjs/intl-localematcher/-/intl-localematcher-0.5.4.tgz",
|
||||
"integrity": "sha512-zTwEpWOzZ2CiKcB93BLngUX59hQkuZjT2+SAQEscSm52peDW/getsawMcWF1rGRpMCX6D7nSJA3CzJ8gn13N/g==",
|
||||
"dependencies": {
|
||||
"tslib": "^2.4.0"
|
||||
}
|
||||
|
@ -3081,11 +3080,10 @@
|
|||
}
|
||||
},
|
||||
"node_modules/@preact/preset-vite": {
|
||||
"version": "2.7.0",
|
||||
"resolved": "https://registry.npmjs.org/@preact/preset-vite/-/preset-vite-2.7.0.tgz",
|
||||
"integrity": "sha512-m5N0FVtxbCCDxNk55NGhsRpKJChYcupcuQHzMJc/Bll07IKZKn8amwYciyKFS9haU6AgzDAJ/ewvApr6Qg1DHw==",
|
||||
"version": "2.8.1",
|
||||
"resolved": "https://registry.npmjs.org/@preact/preset-vite/-/preset-vite-2.8.1.tgz",
|
||||
"integrity": "sha512-a9KV4opdj17X2gOFuGup0aE+sXYABX/tJi/QDptOrleX4FlnoZgDWvz45tHOdVfrZX+3uvVsIYPHxRsTerkDNA==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@babel/plugin-transform-react-jsx": "^7.22.15",
|
||||
"@babel/plugin-transform-react-jsx-development": "^7.22.5",
|
||||
|
@ -3094,6 +3092,8 @@
|
|||
"babel-plugin-transform-hook-names": "^1.0.2",
|
||||
"debug": "^4.3.4",
|
||||
"kolorist": "^1.8.0",
|
||||
"magic-string": "0.30.5",
|
||||
"node-html-parser": "^6.1.10",
|
||||
"resolve": "^1.22.8"
|
||||
},
|
||||
"peerDependencies": {
|
||||
|
@ -3101,6 +3101,24 @@
|
|||
"vite": "2.x || 3.x || 4.x || 5.x"
|
||||
}
|
||||
},
|
||||
"node_modules/@preact/preset-vite/node_modules/@jridgewell/sourcemap-codec": {
|
||||
"version": "1.4.15",
|
||||
"resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.4.15.tgz",
|
||||
"integrity": "sha512-eF2rxCRulEKXHTRiDrDy6erMYWqNw4LPdQ8UQA4huuxaQsVeRPFl2oM8oDGxMFhJUWZf9McpLtJasDDZb/Bpeg==",
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/@preact/preset-vite/node_modules/magic-string": {
|
||||
"version": "0.30.5",
|
||||
"resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.30.5.tgz",
|
||||
"integrity": "sha512-7xlpfBaQaP/T6Vh8MO/EqXSW5En6INHEvEXQiuff7Gku0PWjU3uf6w/j9o7O+SpB5fOAkrI5HeoNgwjEO0pFsA==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"@jridgewell/sourcemap-codec": "^1.4.15"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=12"
|
||||
}
|
||||
},
|
||||
"node_modules/@prefresh/babel-plugin": {
|
||||
"version": "0.5.0",
|
||||
"resolved": "https://registry.npmjs.org/@prefresh/babel-plugin/-/babel-plugin-0.5.0.tgz",
|
||||
|
@ -3752,6 +3770,12 @@
|
|||
"dev": true,
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/boolbase": {
|
||||
"version": "1.0.0",
|
||||
"resolved": "https://registry.npmjs.org/boolbase/-/boolbase-1.0.0.tgz",
|
||||
"integrity": "sha512-JZOSA7Mo9sNGB8+UjSgzdLtokWAky1zbztM3WRLCbZ70/3cTANmQmOdR7y2g+J0e2WXywy1yS468tY+IruqEww==",
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/brace-expansion": {
|
||||
"version": "1.1.11",
|
||||
"resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz",
|
||||
|
@ -4103,6 +4127,34 @@
|
|||
"postcss": "^8.4"
|
||||
}
|
||||
},
|
||||
"node_modules/css-select": {
|
||||
"version": "5.1.0",
|
||||
"resolved": "https://registry.npmjs.org/css-select/-/css-select-5.1.0.tgz",
|
||||
"integrity": "sha512-nwoRF1rvRRnnCqqY7updORDsuqKzqYJ28+oSMaJMMgOauh3fvwHqMS7EZpIPqK8GL+g9mKxF1vP/ZjSeNjEVHg==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"boolbase": "^1.0.0",
|
||||
"css-what": "^6.1.0",
|
||||
"domhandler": "^5.0.2",
|
||||
"domutils": "^3.0.1",
|
||||
"nth-check": "^2.0.1"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/fb55"
|
||||
}
|
||||
},
|
||||
"node_modules/css-what": {
|
||||
"version": "6.1.0",
|
||||
"resolved": "https://registry.npmjs.org/css-what/-/css-what-6.1.0.tgz",
|
||||
"integrity": "sha512-HTUrgRJ7r4dsZKU6GjmpfRK1O76h97Z8MfS1G0FozR+oF2kG6Vfe8JE6zwrkbxigziPHinCJ+gCPjA9EaBDtRw==",
|
||||
"dev": true,
|
||||
"engines": {
|
||||
"node": ">= 6"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/fb55"
|
||||
}
|
||||
},
|
||||
"node_modules/cssdb": {
|
||||
"version": "7.9.0",
|
||||
"resolved": "https://registry.npmjs.org/cssdb/-/cssdb-7.9.0.tgz",
|
||||
|
@ -4193,6 +4245,61 @@
|
|||
"url": "https://github.com/sponsors/ljharb"
|
||||
}
|
||||
},
|
||||
"node_modules/dom-serializer": {
|
||||
"version": "2.0.0",
|
||||
"resolved": "https://registry.npmjs.org/dom-serializer/-/dom-serializer-2.0.0.tgz",
|
||||
"integrity": "sha512-wIkAryiqt/nV5EQKqQpo3SToSOV9J0DnbJqwK7Wv/Trc92zIAYZ4FlMu+JPFW1DfGFt81ZTCGgDEabffXeLyJg==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"domelementtype": "^2.3.0",
|
||||
"domhandler": "^5.0.2",
|
||||
"entities": "^4.2.0"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/cheeriojs/dom-serializer?sponsor=1"
|
||||
}
|
||||
},
|
||||
"node_modules/domelementtype": {
|
||||
"version": "2.3.0",
|
||||
"resolved": "https://registry.npmjs.org/domelementtype/-/domelementtype-2.3.0.tgz",
|
||||
"integrity": "sha512-OLETBj6w0OsagBwdXnPdN0cnMfF9opN69co+7ZrbfPGrdpPVNBUj02spi6B1N7wChLQiPn4CSH/zJvXw56gmHw==",
|
||||
"dev": true,
|
||||
"funding": [
|
||||
{
|
||||
"type": "github",
|
||||
"url": "https://github.com/sponsors/fb55"
|
||||
}
|
||||
]
|
||||
},
|
||||
"node_modules/domhandler": {
|
||||
"version": "5.0.3",
|
||||
"resolved": "https://registry.npmjs.org/domhandler/-/domhandler-5.0.3.tgz",
|
||||
"integrity": "sha512-cgwlv/1iFQiFnU96XXgROh8xTeetsnJiDsTc7TYCLFd9+/WNkIqPTxiM/8pSd8VIrhXGTf1Ny1q1hquVqDJB5w==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"domelementtype": "^2.3.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 4"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/fb55/domhandler?sponsor=1"
|
||||
}
|
||||
},
|
||||
"node_modules/domutils": {
|
||||
"version": "3.1.0",
|
||||
"resolved": "https://registry.npmjs.org/domutils/-/domutils-3.1.0.tgz",
|
||||
"integrity": "sha512-H78uMmQtI2AhgDJjWeQmHwJJ2bLPD3GMmO7Zja/ZZh84wkm+4ut+IUnUdRa8uCGX88DiVx1j6FRe1XfxEgjEZA==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"dom-serializer": "^2.0.0",
|
||||
"domelementtype": "^2.3.0",
|
||||
"domhandler": "^5.0.3"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/fb55/domutils?sponsor=1"
|
||||
}
|
||||
},
|
||||
"node_modules/dot-case": {
|
||||
"version": "3.0.4",
|
||||
"resolved": "https://registry.npmjs.org/dot-case/-/dot-case-3.0.4.tgz",
|
||||
|
@ -4232,6 +4339,18 @@
|
|||
"dev": true,
|
||||
"license": "ISC"
|
||||
},
|
||||
"node_modules/entities": {
|
||||
"version": "4.5.0",
|
||||
"resolved": "https://registry.npmjs.org/entities/-/entities-4.5.0.tgz",
|
||||
"integrity": "sha512-V0hjH4dGPh9Ao5p0MoRY6BVqtwCjhz6vI5LT8AJ55H+4g9/4vbHx1I54fS0XuclLhDHArPQCiMjDxjaL8fPxhw==",
|
||||
"dev": true,
|
||||
"engines": {
|
||||
"node": ">=0.12"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/fb55/entities?sponsor=1"
|
||||
}
|
||||
},
|
||||
"node_modules/es-abstract": {
|
||||
"version": "1.21.2",
|
||||
"resolved": "https://registry.npmjs.org/es-abstract/-/es-abstract-1.21.2.tgz",
|
||||
|
@ -4811,6 +4930,15 @@
|
|||
"url": "https://github.com/sponsors/ljharb"
|
||||
}
|
||||
},
|
||||
"node_modules/he": {
|
||||
"version": "1.2.0",
|
||||
"resolved": "https://registry.npmjs.org/he/-/he-1.2.0.tgz",
|
||||
"integrity": "sha512-F/1DnUGPopORZi0ni+CvrCgHQ5FyEAHRLSApuYWMmrbSwoN2Mn/7k+Gl38gJnR7yyDZk6WLXwiGod1JOWNDKGw==",
|
||||
"dev": true,
|
||||
"bin": {
|
||||
"he": "bin/he"
|
||||
}
|
||||
},
|
||||
"node_modules/header-case": {
|
||||
"version": "2.0.4",
|
||||
"resolved": "https://registry.npmjs.org/header-case/-/header-case-2.0.4.tgz",
|
||||
|
@ -5479,9 +5607,9 @@
|
|||
}
|
||||
},
|
||||
"node_modules/masto": {
|
||||
"version": "6.5.1",
|
||||
"resolved": "https://registry.npmjs.org/masto/-/masto-6.5.1.tgz",
|
||||
"integrity": "sha512-jQTWSNmwtKPQ/H9gW6dIvX4cYIQZE5tKwFFwv6/hcuwqHuYaNHMMU51Qt9pqC1y9NZshivwsMijC9QKUKIiHhg==",
|
||||
"version": "6.5.2",
|
||||
"resolved": "https://registry.npmjs.org/masto/-/masto-6.5.2.tgz",
|
||||
"integrity": "sha512-JfnG7MSQmhszWnLsvdBuxXc2tcVUyCvlTxnSH/5S+In4dU1tvc1wGhFR87kO+YW8gfDsDw9CHh+AD/z+DbTTfQ==",
|
||||
"dependencies": {
|
||||
"change-case": "^4.1.2",
|
||||
"events-to-async": "^2.0.1",
|
||||
|
@ -5613,6 +5741,16 @@
|
|||
"tslib": "^2.0.3"
|
||||
}
|
||||
},
|
||||
"node_modules/node-html-parser": {
|
||||
"version": "6.1.12",
|
||||
"resolved": "https://registry.npmjs.org/node-html-parser/-/node-html-parser-6.1.12.tgz",
|
||||
"integrity": "sha512-/bT/Ncmv+fbMGX96XG9g05vFt43m/+SYKIs9oAemQVYyVcZmDAI2Xq/SbNcpOA35eF0Zk2av3Ksf+Xk8Vt8abA==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"css-select": "^5.1.0",
|
||||
"he": "1.2.0"
|
||||
}
|
||||
},
|
||||
"node_modules/node-releases": {
|
||||
"version": "2.0.13",
|
||||
"resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.13.tgz",
|
||||
|
@ -5630,6 +5768,18 @@
|
|||
"node": ">=0.10.0"
|
||||
}
|
||||
},
|
||||
"node_modules/nth-check": {
|
||||
"version": "2.1.1",
|
||||
"resolved": "https://registry.npmjs.org/nth-check/-/nth-check-2.1.1.tgz",
|
||||
"integrity": "sha512-lqjrjmaOoAnWfMmBPL+XNnynZh2+swxiX3WUE0s4yEHI6m+AwrK2UZOimIRl3X/4QctVqS8AiZjFqyOGrMXb/w==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"boolbase": "^1.0.0"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/fb55/nth-check?sponsor=1"
|
||||
}
|
||||
},
|
||||
"node_modules/object-assign": {
|
||||
"version": "4.1.1",
|
||||
"resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz",
|
||||
|
@ -5783,9 +5933,9 @@
|
|||
}
|
||||
},
|
||||
"node_modules/postcss": {
|
||||
"version": "8.4.32",
|
||||
"resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.32.tgz",
|
||||
"integrity": "sha512-D/kj5JNu6oo2EIy+XL/26JEDTlIbB8hw85G8StOE6L74RQAVVP5rej6wxCNqyMbR4RkPfqvezVbPw81Ngd6Kcw==",
|
||||
"version": "8.4.33",
|
||||
"resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.33.tgz",
|
||||
"integrity": "sha512-Kkpbhhdjw2qQs2O2DGX+8m5OVqEcbB9HRBvuYM9pgrjEFUg30A9LmXNlTAUj4S9kgtGyrMbTzVjH7E+s5Re2yg==",
|
||||
"dev": true,
|
||||
"funding": [
|
||||
{
|
||||
|
@ -6646,10 +6796,9 @@
|
|||
}
|
||||
},
|
||||
"node_modules/react-hotkeys-hook": {
|
||||
"version": "4.4.1",
|
||||
"resolved": "https://registry.npmjs.org/react-hotkeys-hook/-/react-hotkeys-hook-4.4.1.tgz",
|
||||
"integrity": "sha512-sClBMBioFEgFGYLTWWRKvhxcCx1DRznd+wkFHwQZspnRBkHTgruKIHptlK/U/2DPX8BhHoRGzpMVWUXMmdZlmw==",
|
||||
"license": "MIT",
|
||||
"version": "4.4.4",
|
||||
"resolved": "https://registry.npmjs.org/react-hotkeys-hook/-/react-hotkeys-hook-4.4.4.tgz",
|
||||
"integrity": "sha512-wzZmqb/Obr0ds9Myc1sIFPJ52GA/Eeg/vXBWV0HA1LvHlVAW5Va3KB0q6EZNlNSHQWscWZ2K8+6w0GYSie2o7A==",
|
||||
"peerDependencies": {
|
||||
"react": ">=16.8.1",
|
||||
"react-dom": ">=16.8.1"
|
||||
|
@ -7624,9 +7773,9 @@
|
|||
}
|
||||
},
|
||||
"node_modules/vite": {
|
||||
"version": "5.0.10",
|
||||
"resolved": "https://registry.npmjs.org/vite/-/vite-5.0.10.tgz",
|
||||
"integrity": "sha512-2P8J7WWgmc355HUMlFrwofacvr98DAjoE52BfdbwQtyLH06XKwaL/FMnmKM2crF0iX4MpmMKoDlNCB1ok7zHCw==",
|
||||
"version": "5.0.12",
|
||||
"resolved": "https://registry.npmjs.org/vite/-/vite-5.0.12.tgz",
|
||||
"integrity": "sha512-4hsnEkG3q0N4Tzf1+t6NdN9dg/L3BM+q8SWgbSPnJvrgH2kgdyzfVJwbR1ic69/4uMJJ/3dqDZZE5/WwqW8U1w==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"esbuild": "^0.19.3",
|
||||
|
@ -7705,9 +7854,9 @@
|
|||
}
|
||||
},
|
||||
"node_modules/vite-plugin-pwa": {
|
||||
"version": "0.17.4",
|
||||
"resolved": "https://registry.npmjs.org/vite-plugin-pwa/-/vite-plugin-pwa-0.17.4.tgz",
|
||||
"integrity": "sha512-j9iiyinFOYyof4Zk3Q+DtmYyDVBDAi6PuMGNGq6uGI0pw7E+LNm9e+nQ2ep9obMP/kjdWwzilqUrlfVRj9OobA==",
|
||||
"version": "0.17.5",
|
||||
"resolved": "https://registry.npmjs.org/vite-plugin-pwa/-/vite-plugin-pwa-0.17.5.tgz",
|
||||
"integrity": "sha512-UxRNPiJBzh4tqU/vc8G2TxmrUTzT6BqvSzhszLk62uKsf+npXdvLxGDz9C675f4BJi6MbD2tPnJhi5txlMzxbQ==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"debug": "^4.3.4",
|
||||
|
|
14
package.json
14
package.json
|
@ -10,7 +10,7 @@
|
|||
"sourcemap": "npx source-map-explorer dist/assets/*.js"
|
||||
},
|
||||
"dependencies": {
|
||||
"@formatjs/intl-localematcher": "~0.5.2",
|
||||
"@formatjs/intl-localematcher": "~0.5.4",
|
||||
"@formkit/auto-animate": "~0.8.1",
|
||||
"@github/text-expander-element": "~2.6.1",
|
||||
"@iconify-icons/mingcute": "~1.2.9",
|
||||
|
@ -25,12 +25,12 @@
|
|||
"idb-keyval": "~6.2.1",
|
||||
"just-debounce-it": "~3.2.0",
|
||||
"lz-string": "~1.5.0",
|
||||
"masto": "~6.5.1",
|
||||
"masto": "~6.5.2",
|
||||
"moize": "~6.1.6",
|
||||
"p-retry": "~6.2.0",
|
||||
"p-throttle": "~6.1.0",
|
||||
"preact": "~10.19.3",
|
||||
"react-hotkeys-hook": "~4.4.1",
|
||||
"react-hotkeys-hook": "~4.4.4",
|
||||
"react-intersection-observer": "~9.5.3",
|
||||
"react-quick-pinch-zoom": "~5.1.0",
|
||||
"react-router-dom": "6.6.2",
|
||||
|
@ -45,16 +45,16 @@
|
|||
"valtio": "1.9.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@preact/preset-vite": "~2.7.0",
|
||||
"@preact/preset-vite": "~2.8.1",
|
||||
"@trivago/prettier-plugin-sort-imports": "~4.3.0",
|
||||
"postcss": "~8.4.32",
|
||||
"postcss": "~8.4.33",
|
||||
"postcss-dark-theme-class": "~1.1.0",
|
||||
"postcss-preset-env": "~9.3.0",
|
||||
"twitter-text": "~3.1.0",
|
||||
"vite": "~5.0.10",
|
||||
"vite": "~5.0.12",
|
||||
"vite-plugin-generate-file": "~0.1.1",
|
||||
"vite-plugin-html-config": "~1.0.11",
|
||||
"vite-plugin-pwa": "~0.17.4",
|
||||
"vite-plugin-pwa": "~0.17.5",
|
||||
"vite-plugin-remove-console": "~2.2.0",
|
||||
"workbox-cacheable-response": "~7.0.0",
|
||||
"workbox-expiration": "~7.0.0",
|
||||
|
|
24
src/app.css
24
src/app.css
|
@ -645,6 +645,16 @@ a[href^='http'][rel*='nofollow']:visited:not(:has(div)) {
|
|||
var(--bg-faded-color)
|
||||
);
|
||||
}
|
||||
@keyframes summary-fade {
|
||||
0% {
|
||||
opacity: 0;
|
||||
transform: translateY(-8px);
|
||||
}
|
||||
100% {
|
||||
opacity: 1;
|
||||
transform: translateY(0);
|
||||
}
|
||||
}
|
||||
.timeline.contextual > li .replies[open] > .replies-summary {
|
||||
border-bottom-left-radius: 0;
|
||||
|
||||
|
@ -659,6 +669,10 @@ a[href^='http'][rel*='nofollow']:visited:not(:has(div)) {
|
|||
.replies-summary-chevron {
|
||||
transform: rotate(180deg);
|
||||
}
|
||||
|
||||
+ * {
|
||||
animation: summary-fade 0.3s ease-out both;
|
||||
}
|
||||
}
|
||||
.timeline.contextual > li .replies .replies-summary[hidden] {
|
||||
display: none;
|
||||
|
@ -933,7 +947,7 @@ a[href^='http'][rel*='nofollow']:visited:not(:has(div)) {
|
|||
background: linear-gradient(
|
||||
to bottom right,
|
||||
var(--carousel-faded-color),
|
||||
transparent 150%
|
||||
transparent
|
||||
);
|
||||
position: relative;
|
||||
container-type: inline-size;
|
||||
|
@ -948,7 +962,7 @@ a[href^='http'][rel*='nofollow']:visited:not(:has(div)) {
|
|||
var(--carousel-faded-color),
|
||||
transparent
|
||||
),
|
||||
linear-gradient(to top, var(--bg-color), transparent 64px);
|
||||
linear-gradient(to top, var(--bg-color) 8px, transparent 64px);
|
||||
background-repeat: no-repeat;
|
||||
background-position: bottom center;
|
||||
}
|
||||
|
@ -1059,6 +1073,10 @@ a[href^='http'][rel*='nofollow']:visited:not(:has(div)) {
|
|||
.ui-state {
|
||||
padding: 16px;
|
||||
text-align: center;
|
||||
|
||||
.icon {
|
||||
vertical-align: middle;
|
||||
}
|
||||
}
|
||||
|
||||
.status-carousel-link {
|
||||
|
@ -1659,6 +1677,8 @@ body:has(.media-modal-container + .status-deck) .media-post-link {
|
|||
|
||||
svg {
|
||||
contain: none;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -14,7 +14,7 @@ import { subscribe } from 'valtio';
|
|||
|
||||
import BackgroundService from './components/background-service';
|
||||
import ComposeButton from './components/compose-button';
|
||||
import { ICONS } from './components/icon';
|
||||
import { ICONS } from './components/ICONS';
|
||||
import KeyboardShortcutsHelp from './components/keyboard-shortcuts-help';
|
||||
import Loader from './components/loader';
|
||||
import Modals from './components/modals';
|
||||
|
|
103
src/components/ICONS.jsx
Normal file
103
src/components/ICONS.jsx
Normal file
|
@ -0,0 +1,103 @@
|
|||
export const ICONS = {
|
||||
x: () => import('@iconify-icons/mingcute/close-line'),
|
||||
heart: () => import('@iconify-icons/mingcute/heart-line'),
|
||||
bookmark: () => import('@iconify-icons/mingcute/bookmark-line'),
|
||||
'check-circle': () => import('@iconify-icons/mingcute/check-circle-line'),
|
||||
'x-circle': () => import('@iconify-icons/mingcute/close-circle-line'),
|
||||
transfer: () => import('@iconify-icons/mingcute/transfer-4-line'),
|
||||
rocket: () => import('@iconify-icons/mingcute/rocket-line'),
|
||||
'arrow-left': () => import('@iconify-icons/mingcute/arrow-left-line'),
|
||||
'arrow-right': () => import('@iconify-icons/mingcute/arrow-right-line'),
|
||||
'arrow-up': () => import('@iconify-icons/mingcute/arrow-up-line'),
|
||||
'arrow-down': () => import('@iconify-icons/mingcute/arrow-down-line'),
|
||||
earth: () => import('@iconify-icons/mingcute/earth-line'),
|
||||
lock: () => import('@iconify-icons/mingcute/lock-line'),
|
||||
unlock: () => import('@iconify-icons/mingcute/unlock-line'),
|
||||
'eye-close': () => import('@iconify-icons/mingcute/eye-close-line'),
|
||||
'eye-open': () => import('@iconify-icons/mingcute/eye-2-line'),
|
||||
message: () => import('@iconify-icons/mingcute/mail-line'),
|
||||
comment: () => import('@iconify-icons/mingcute/chat-3-line'),
|
||||
comment2: () => import('@iconify-icons/mingcute/comment-2-line'),
|
||||
home: () => import('@iconify-icons/mingcute/home-3-line'),
|
||||
notification: () => import('@iconify-icons/mingcute/notification-line'),
|
||||
follow: () => import('@iconify-icons/mingcute/user-follow-line'),
|
||||
'follow-add': () => import('@iconify-icons/mingcute/user-add-line'),
|
||||
poll: [() => import('@iconify-icons/mingcute/chart-bar-line'), '90deg'],
|
||||
pencil: () => import('@iconify-icons/mingcute/pencil-line'),
|
||||
quill: () => import('@iconify-icons/mingcute/quill-pen-line'),
|
||||
at: () => import('@iconify-icons/mingcute/at-line'),
|
||||
attachment: () => import('@iconify-icons/mingcute/attachment-line'),
|
||||
upload: () => import('@iconify-icons/mingcute/upload-3-line'),
|
||||
gear: () => import('@iconify-icons/mingcute/settings-3-line'),
|
||||
more: () => import('@iconify-icons/mingcute/more-3-line'),
|
||||
more2: () => import('@iconify-icons/mingcute/more-1-fill'),
|
||||
external: () => import('@iconify-icons/mingcute/external-link-line'),
|
||||
popout: () => import('@iconify-icons/mingcute/external-link-line'),
|
||||
popin: [() => import('@iconify-icons/mingcute/external-link-line'), '180deg'],
|
||||
plus: () => import('@iconify-icons/mingcute/add-circle-line'),
|
||||
'chevron-left': () => import('@iconify-icons/mingcute/left-line'),
|
||||
'chevron-right': () => import('@iconify-icons/mingcute/right-line'),
|
||||
'chevron-down': () => import('@iconify-icons/mingcute/down-line'),
|
||||
reply: [
|
||||
() => import('@iconify-icons/mingcute/share-forward-line'),
|
||||
'180deg',
|
||||
'horizontal',
|
||||
],
|
||||
thread: () => import('@iconify-icons/mingcute/route-line'),
|
||||
group: () => import('@iconify-icons/mingcute/group-line'),
|
||||
bot: () => import('@iconify-icons/mingcute/android-2-line'),
|
||||
menu: () => import('@iconify-icons/mingcute/rows-4-line'),
|
||||
list: () => import('@iconify-icons/mingcute/list-check-line'),
|
||||
search: () => import('@iconify-icons/mingcute/search-2-line'),
|
||||
hashtag: () => import('@iconify-icons/mingcute/hashtag-line'),
|
||||
info: () => import('@iconify-icons/mingcute/information-line'),
|
||||
shortcut: () => import('@iconify-icons/mingcute/lightning-line'),
|
||||
user: () => import('@iconify-icons/mingcute/user-4-line'),
|
||||
following: () => import('@iconify-icons/mingcute/walk-line'),
|
||||
pin: () => import('@iconify-icons/mingcute/pin-line'),
|
||||
unpin: [() => import('@iconify-icons/mingcute/pin-line'), '180deg'],
|
||||
bus: () => import('@iconify-icons/mingcute/bus-2-line'),
|
||||
link: () => import('@iconify-icons/mingcute/link-2-line'),
|
||||
history: () => import('@iconify-icons/mingcute/history-line'),
|
||||
share: () => import('@iconify-icons/mingcute/share-2-line'),
|
||||
sparkles: () => import('@iconify-icons/mingcute/sparkles-line'),
|
||||
sparkles2: () => import('@iconify-icons/mingcute/sparkles-2-line'),
|
||||
exit: () => import('@iconify-icons/mingcute/exit-line'),
|
||||
translate: () => import('@iconify-icons/mingcute/translate-line'),
|
||||
play: () => import('@iconify-icons/mingcute/play-fill'),
|
||||
trash: () => import('@iconify-icons/mingcute/delete-2-line'),
|
||||
mute: () => import('@iconify-icons/mingcute/volume-mute-line'),
|
||||
unmute: () => import('@iconify-icons/mingcute/volume-line'),
|
||||
block: () => import('@iconify-icons/mingcute/forbid-circle-line'),
|
||||
unblock: [
|
||||
() => import('@iconify-icons/mingcute/forbid-circle-line'),
|
||||
'180deg',
|
||||
],
|
||||
flag: () => import('@iconify-icons/mingcute/flag-4-line'),
|
||||
time: () => import('@iconify-icons/mingcute/time-line'),
|
||||
refresh: () => import('@iconify-icons/mingcute/refresh-2-line'),
|
||||
emoji2: () => import('@iconify-icons/mingcute/emoji-2-line'),
|
||||
filter: () => import('@iconify-icons/mingcute/filter-2-line'),
|
||||
chart: () => import('@iconify-icons/mingcute/chart-line-line'),
|
||||
react: () => import('@iconify-icons/mingcute/react-line'),
|
||||
layout4: () => import('@iconify-icons/mingcute/layout-4-line'),
|
||||
layout5: () => import('@iconify-icons/mingcute/layout-5-line'),
|
||||
announce: () => import('@iconify-icons/mingcute/announcement-line'),
|
||||
alert: () => import('@iconify-icons/mingcute/alert-line'),
|
||||
round: () => import('@iconify-icons/mingcute/round-fill'),
|
||||
'arrow-up-circle': () =>
|
||||
import('@iconify-icons/mingcute/arrow-up-circle-line'),
|
||||
'arrow-down-circle': () =>
|
||||
import('@iconify-icons/mingcute/arrow-down-circle-line'),
|
||||
clipboard: () => import('@iconify-icons/mingcute/clipboard-line'),
|
||||
'account-edit': () => import('@iconify-icons/mingcute/user-edit-line'),
|
||||
'account-warning': () => import('@iconify-icons/mingcute/user-warning-line'),
|
||||
keyboard: () => import('@iconify-icons/mingcute/keyboard-line'),
|
||||
cloud: () => import('@iconify-icons/mingcute/cloud-line'),
|
||||
month: () => import('@iconify-icons/mingcute/calendar-month-line'),
|
||||
media: () => import('@iconify-icons/mingcute/photo-album-line'),
|
||||
speak: () => import('@iconify-icons/mingcute/radar-line'),
|
||||
building: () => import('@iconify-icons/mingcute/building-5-line'),
|
||||
history: () => import('@iconify-icons/mingcute/history-2-line'),
|
||||
document: () => import('@iconify-icons/mingcute/document-line'),
|
||||
};
|
|
@ -61,6 +61,7 @@ function AccountBlock({
|
|||
note,
|
||||
group,
|
||||
followersCount,
|
||||
createdAt,
|
||||
} = account;
|
||||
let [_, acct1, acct2] = acct.match(/([^@]+)(@.+)/i) || [, acct];
|
||||
if (accountInstance) {
|
||||
|
@ -188,6 +189,21 @@ function AccountBlock({
|
|||
/>
|
||||
</span>
|
||||
)}
|
||||
{!bot &&
|
||||
!group &&
|
||||
!hasRelationship &&
|
||||
!followersCount &&
|
||||
!verifiedField &&
|
||||
!!createdAt && (
|
||||
<span class="created-at">
|
||||
Joined{' '}
|
||||
<time datetime={createdAt}>
|
||||
{niceDateTime(createdAt, {
|
||||
hideTime: true,
|
||||
})}
|
||||
</time>
|
||||
</span>
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
</span>
|
||||
|
|
|
@ -59,7 +59,11 @@ function AccountSheet({ account, instance: propInstance, onClose }) {
|
|||
return result.accounts[0];
|
||||
} else if (/https?:\/\/[^/]+\/@/.test(account)) {
|
||||
const accountURL = new URL(account);
|
||||
const acct = accountURL.pathname.replace(/^\//, '');
|
||||
const { hostname, pathname } = accountURL;
|
||||
const acct =
|
||||
pathname.replace(/^\//, '').replace(/\/$/, '') +
|
||||
'@' +
|
||||
hostname;
|
||||
const result = await masto.v2.search.fetch({
|
||||
q: acct,
|
||||
type: 'accounts',
|
||||
|
|
|
@ -133,7 +133,14 @@ const SCAN_RE = new RegExp(
|
|||
|
||||
function highlightText(text, { maxCharacters = Infinity }) {
|
||||
// Accept text string, return formatted HTML string
|
||||
let html = text;
|
||||
// Escape all HTML special characters
|
||||
let html = text
|
||||
.replace(/&/g, '&')
|
||||
.replace(/</g, '<')
|
||||
.replace(/>/g, '>')
|
||||
.replace(/"/g, '"')
|
||||
.replace(/'/g, ''');
|
||||
|
||||
// Exceeded characters limit
|
||||
const { composerCharacterCount } = states;
|
||||
let leftoverHTML = '';
|
||||
|
|
|
@ -22,6 +22,10 @@
|
|||
iframe {
|
||||
pointer-events: auto;
|
||||
max-width: 100%;
|
||||
max-height: 100%;
|
||||
width: max(var(--width), 480px);
|
||||
height: auto;
|
||||
aspect-ratio: var(--aspect-ratio);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -2,7 +2,7 @@ import './embed-modal.css';
|
|||
|
||||
import Icon from './icon';
|
||||
|
||||
function EmbedModal({ html, url, onClose = () => {} }) {
|
||||
function EmbedModal({ html, url, width, height, onClose = () => {} }) {
|
||||
return (
|
||||
<div class="embed-modal-container">
|
||||
<div class="top-controls">
|
||||
|
@ -20,7 +20,15 @@ function EmbedModal({ html, url, onClose = () => {} }) {
|
|||
</a>
|
||||
)}
|
||||
</div>
|
||||
<div class="embed-content" dangerouslySetInnerHTML={{ __html: html }} />
|
||||
<div
|
||||
class="embed-content"
|
||||
dangerouslySetInnerHTML={{ __html: html }}
|
||||
style={{
|
||||
'--width': width + 'px',
|
||||
'--height': height + 'px',
|
||||
'--aspect-ratio': `${width}/${height}`,
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
|
|
@ -1,3 +1,5 @@
|
|||
import { memo } from 'preact/compat';
|
||||
|
||||
function EmojiText({ text, emojis }) {
|
||||
if (!text) return '';
|
||||
if (!emojis?.length) return text;
|
||||
|
@ -31,4 +33,9 @@ function EmojiText({ text, emojis }) {
|
|||
return elements;
|
||||
}
|
||||
|
||||
export default EmojiText;
|
||||
export default memo(
|
||||
EmojiText,
|
||||
(oldProps, newProps) =>
|
||||
oldProps.text === newProps.text &&
|
||||
oldProps.emojis?.length === newProps.emojis?.length,
|
||||
);
|
||||
|
|
|
@ -1,6 +1,8 @@
|
|||
import moize from 'moize';
|
||||
import { useEffect, useRef, useState } from 'preact/hooks';
|
||||
|
||||
import { ICONS } from './ICONS';
|
||||
|
||||
const SIZES = {
|
||||
s: 12,
|
||||
m: 16,
|
||||
|
@ -9,115 +11,13 @@ const SIZES = {
|
|||
xxl: 32,
|
||||
};
|
||||
|
||||
export const ICONS = {
|
||||
x: () => import('@iconify-icons/mingcute/close-line'),
|
||||
heart: () => import('@iconify-icons/mingcute/heart-line'),
|
||||
bookmark: () => import('@iconify-icons/mingcute/bookmark-line'),
|
||||
'check-circle': () => import('@iconify-icons/mingcute/check-circle-line'),
|
||||
'x-circle': () => import('@iconify-icons/mingcute/close-circle-line'),
|
||||
transfer: () => import('@iconify-icons/mingcute/transfer-4-line'),
|
||||
rocket: () => import('@iconify-icons/mingcute/rocket-line'),
|
||||
'arrow-left': () => import('@iconify-icons/mingcute/arrow-left-line'),
|
||||
'arrow-right': () => import('@iconify-icons/mingcute/arrow-right-line'),
|
||||
'arrow-up': () => import('@iconify-icons/mingcute/arrow-up-line'),
|
||||
'arrow-down': () => import('@iconify-icons/mingcute/arrow-down-line'),
|
||||
earth: () => import('@iconify-icons/mingcute/earth-line'),
|
||||
lock: () => import('@iconify-icons/mingcute/lock-line'),
|
||||
unlock: () => import('@iconify-icons/mingcute/unlock-line'),
|
||||
'eye-close': () => import('@iconify-icons/mingcute/eye-close-line'),
|
||||
'eye-open': () => import('@iconify-icons/mingcute/eye-2-line'),
|
||||
message: () => import('@iconify-icons/mingcute/mail-line'),
|
||||
comment: () => import('@iconify-icons/mingcute/chat-3-line'),
|
||||
comment2: () => import('@iconify-icons/mingcute/comment-2-line'),
|
||||
home: () => import('@iconify-icons/mingcute/home-3-line'),
|
||||
notification: () => import('@iconify-icons/mingcute/notification-line'),
|
||||
follow: () => import('@iconify-icons/mingcute/user-follow-line'),
|
||||
'follow-add': () => import('@iconify-icons/mingcute/user-add-line'),
|
||||
poll: [() => import('@iconify-icons/mingcute/chart-bar-line'), '90deg'],
|
||||
pencil: () => import('@iconify-icons/mingcute/pencil-line'),
|
||||
quill: () => import('@iconify-icons/mingcute/quill-pen-line'),
|
||||
at: () => import('@iconify-icons/mingcute/at-line'),
|
||||
attachment: () => import('@iconify-icons/mingcute/attachment-line'),
|
||||
upload: () => import('@iconify-icons/mingcute/upload-3-line'),
|
||||
gear: () => import('@iconify-icons/mingcute/settings-3-line'),
|
||||
more: () => import('@iconify-icons/mingcute/more-3-line'),
|
||||
external: () => import('@iconify-icons/mingcute/external-link-line'),
|
||||
popout: () => import('@iconify-icons/mingcute/external-link-line'),
|
||||
popin: [() => import('@iconify-icons/mingcute/external-link-line'), '180deg'],
|
||||
plus: () => import('@iconify-icons/mingcute/add-circle-line'),
|
||||
'chevron-left': () => import('@iconify-icons/mingcute/left-line'),
|
||||
'chevron-right': () => import('@iconify-icons/mingcute/right-line'),
|
||||
'chevron-down': () => import('@iconify-icons/mingcute/down-line'),
|
||||
reply: [
|
||||
() => import('@iconify-icons/mingcute/share-forward-line'),
|
||||
'180deg',
|
||||
'horizontal',
|
||||
],
|
||||
thread: () => import('@iconify-icons/mingcute/route-line'),
|
||||
group: () => import('@iconify-icons/mingcute/group-line'),
|
||||
bot: () => import('@iconify-icons/mingcute/android-2-line'),
|
||||
menu: () => import('@iconify-icons/mingcute/rows-4-line'),
|
||||
list: () => import('@iconify-icons/mingcute/list-check-line'),
|
||||
search: () => import('@iconify-icons/mingcute/search-2-line'),
|
||||
hashtag: () => import('@iconify-icons/mingcute/hashtag-line'),
|
||||
info: () => import('@iconify-icons/mingcute/information-line'),
|
||||
shortcut: () => import('@iconify-icons/mingcute/lightning-line'),
|
||||
user: () => import('@iconify-icons/mingcute/user-4-line'),
|
||||
following: () => import('@iconify-icons/mingcute/walk-line'),
|
||||
pin: () => import('@iconify-icons/mingcute/pin-line'),
|
||||
bus: () => import('@iconify-icons/mingcute/bus-2-line'),
|
||||
link: () => import('@iconify-icons/mingcute/link-2-line'),
|
||||
history: () => import('@iconify-icons/mingcute/history-line'),
|
||||
share: () => import('@iconify-icons/mingcute/share-2-line'),
|
||||
sparkles: () => import('@iconify-icons/mingcute/sparkles-line'),
|
||||
sparkles2: () => import('@iconify-icons/mingcute/sparkles-2-line'),
|
||||
exit: () => import('@iconify-icons/mingcute/exit-line'),
|
||||
translate: () => import('@iconify-icons/mingcute/translate-line'),
|
||||
play: () => import('@iconify-icons/mingcute/play-fill'),
|
||||
trash: () => import('@iconify-icons/mingcute/delete-2-line'),
|
||||
mute: () => import('@iconify-icons/mingcute/volume-mute-line'),
|
||||
unmute: () => import('@iconify-icons/mingcute/volume-line'),
|
||||
block: () => import('@iconify-icons/mingcute/forbid-circle-line'),
|
||||
unblock: [
|
||||
() => import('@iconify-icons/mingcute/forbid-circle-line'),
|
||||
'180deg',
|
||||
],
|
||||
flag: () => import('@iconify-icons/mingcute/flag-4-line'),
|
||||
time: () => import('@iconify-icons/mingcute/time-line'),
|
||||
refresh: () => import('@iconify-icons/mingcute/refresh-2-line'),
|
||||
emoji2: () => import('@iconify-icons/mingcute/emoji-2-line'),
|
||||
filter: () => import('@iconify-icons/mingcute/filter-2-line'),
|
||||
chart: () => import('@iconify-icons/mingcute/chart-line-line'),
|
||||
react: () => import('@iconify-icons/mingcute/react-line'),
|
||||
layout4: () => import('@iconify-icons/mingcute/layout-4-line'),
|
||||
layout5: () => import('@iconify-icons/mingcute/layout-5-line'),
|
||||
announce: () => import('@iconify-icons/mingcute/announcement-line'),
|
||||
alert: () => import('@iconify-icons/mingcute/alert-line'),
|
||||
round: () => import('@iconify-icons/mingcute/round-fill'),
|
||||
'arrow-up-circle': () =>
|
||||
import('@iconify-icons/mingcute/arrow-up-circle-line'),
|
||||
'arrow-down-circle': () =>
|
||||
import('@iconify-icons/mingcute/arrow-down-circle-line'),
|
||||
clipboard: () => import('@iconify-icons/mingcute/clipboard-line'),
|
||||
'account-edit': () => import('@iconify-icons/mingcute/user-edit-line'),
|
||||
'account-warning': () => import('@iconify-icons/mingcute/user-warning-line'),
|
||||
keyboard: () => import('@iconify-icons/mingcute/keyboard-line'),
|
||||
cloud: () => import('@iconify-icons/mingcute/cloud-line'),
|
||||
month: () => import('@iconify-icons/mingcute/calendar-month-line'),
|
||||
media: () => import('@iconify-icons/mingcute/photo-album-line'),
|
||||
speak: () => import('@iconify-icons/mingcute/radar-line'),
|
||||
building: () => import('@iconify-icons/mingcute/building-5-line'),
|
||||
};
|
||||
|
||||
const ICONDATA = {};
|
||||
|
||||
// Memoize the dangerouslySetInnerHTML of the SVGs
|
||||
const SVGICon = moize(
|
||||
function ({ size, width, height, body, rotate, flip }) {
|
||||
function ({ width, height, body, rotate, flip }) {
|
||||
return (
|
||||
<svg
|
||||
width={size}
|
||||
height={size}
|
||||
viewBox={`0 0 ${width} ${height}`}
|
||||
dangerouslySetInnerHTML={{ __html: body }}
|
||||
style={{
|
||||
|
@ -131,6 +31,8 @@ const SVGICon = moize(
|
|||
{
|
||||
isShallowEqual: true,
|
||||
maxSize: Object.keys(ICONS).length,
|
||||
matchesArg: (cacheKeyArg, keyArg) =>
|
||||
cacheKeyArg.icon === keyArg.icon && cacheKeyArg.body === keyArg.body,
|
||||
},
|
||||
);
|
||||
|
||||
|
@ -191,7 +93,7 @@ function Icon({
|
|||
// }}
|
||||
// />
|
||||
<SVGICon
|
||||
size={iconSize}
|
||||
icon={icon}
|
||||
width={iconData.width}
|
||||
height={iconData.height}
|
||||
body={iconData.body}
|
||||
|
|
|
@ -17,7 +17,7 @@ export default memo(function KeyboardShortcutsHelp() {
|
|||
}
|
||||
|
||||
useHotkeys(
|
||||
'?, shift+?',
|
||||
'?, shift+?, shift+slash',
|
||||
(e) => {
|
||||
console.log('help');
|
||||
states.showKeyboardShortcutsHelp = true;
|
||||
|
@ -71,6 +71,10 @@ export default memo(function KeyboardShortcutsHelp() {
|
|||
</>
|
||||
),
|
||||
},
|
||||
{
|
||||
action: 'Load new posts',
|
||||
keys: <kbd>.</kbd>,
|
||||
},
|
||||
{
|
||||
action: 'Open post details',
|
||||
keys: (
|
||||
|
|
|
@ -273,7 +273,7 @@ function MediaModal({
|
|||
<span>
|
||||
<Menu2
|
||||
overflow="auto"
|
||||
align="end"
|
||||
align="center"
|
||||
position="anchor"
|
||||
gap={4}
|
||||
menuClassName="glass-menu"
|
||||
|
|
|
@ -151,11 +151,18 @@ function Media({
|
|||
[to],
|
||||
);
|
||||
|
||||
const remoteMediaURLObj = remoteMediaURL ? new URL(remoteMediaURL) : null;
|
||||
const isVideoMaybe =
|
||||
type === 'unknown' &&
|
||||
/\.(mp4|m4a|m4p|m4b|m4r|m4v|mov|webm)$/i.test(remoteMediaURL);
|
||||
remoteMediaURLObj &&
|
||||
/\.(mp4|m4r|m4v|mov|webm)$/i.test(remoteMediaURLObj.pathname);
|
||||
const isAudioMaybe =
|
||||
type === 'unknown' &&
|
||||
remoteMediaURLObj &&
|
||||
/\.(mp3|ogg|wav|m4a|m4p|m4b)$/i.test(remoteMediaURLObj.pathname);
|
||||
const isImage =
|
||||
type === 'image' || (type === 'unknown' && previewUrl && !isVideoMaybe);
|
||||
type === 'image' ||
|
||||
(type === 'unknown' && previewUrl && !isVideoMaybe && !isAudioMaybe);
|
||||
|
||||
const parentRef = useRef();
|
||||
const [imageSmallerThanParent, setImageSmallerThanParent] = useState(false);
|
||||
|
@ -476,7 +483,7 @@ function Media({
|
|||
</Parent>
|
||||
</Figure>
|
||||
);
|
||||
} else if (type === 'audio') {
|
||||
} else if (type === 'audio' || isAudioMaybe) {
|
||||
const formattedDuration = formatDuration(original.duration);
|
||||
return (
|
||||
<Figure>
|
||||
|
@ -499,6 +506,12 @@ function Media({
|
|||
height={height}
|
||||
data-orientation={orientation}
|
||||
loading="lazy"
|
||||
onError={(e) => {
|
||||
try {
|
||||
// Remove self if broken
|
||||
e.target?.remove?.();
|
||||
} catch (e) {}
|
||||
}}
|
||||
/>
|
||||
) : null}
|
||||
{!showOriginal && (
|
||||
|
|
|
@ -210,6 +210,8 @@ export default function Modals() {
|
|||
<EmbedModal
|
||||
html={snapStates.showEmbedModal.html}
|
||||
url={snapStates.showEmbedModal.url}
|
||||
width={snapStates.showEmbedModal.width}
|
||||
height={snapStates.showEmbedModal.height}
|
||||
onClose={() => {
|
||||
states.showEmbedModal = false;
|
||||
}}
|
||||
|
|
|
@ -97,4 +97,9 @@ function NameText({
|
|||
);
|
||||
}
|
||||
|
||||
export default memo(NameText);
|
||||
export default memo(NameText, (oldProps, newProps) => {
|
||||
// Only care about account.id, the other props usually don't change
|
||||
const { account } = oldProps;
|
||||
const { account: newAccount } = newProps;
|
||||
return account?.acct === newAccount?.acct;
|
||||
});
|
||||
|
|
|
@ -292,7 +292,12 @@ function Notification({
|
|||
instance ? `/${instance}/s/${status.id}` : `/s/${status.id}`
|
||||
}
|
||||
>
|
||||
<Status status={status} size="s" />
|
||||
<Status
|
||||
status={status}
|
||||
size="s"
|
||||
previewMode
|
||||
allowContextMenu
|
||||
/>
|
||||
</TruncatedLink>
|
||||
</li>
|
||||
))}
|
||||
|
@ -326,9 +331,19 @@ function Notification({
|
|||
}
|
||||
>
|
||||
{isStatic ? (
|
||||
<Status status={actualStatus} size="s" />
|
||||
<Status
|
||||
status={actualStatus}
|
||||
size="s"
|
||||
previewMode
|
||||
allowContextMenu
|
||||
/>
|
||||
) : (
|
||||
<Status statusID={actualStatusID} size="s" />
|
||||
<Status
|
||||
statusID={actualStatusID}
|
||||
size="s"
|
||||
previewMode
|
||||
allowContextMenu
|
||||
/>
|
||||
)}
|
||||
</TruncatedLink>
|
||||
)}
|
||||
|
|
|
@ -8,6 +8,7 @@ import dayjs from 'dayjs';
|
|||
import dayjsTwitter from 'dayjs-twitter';
|
||||
import localizedFormat from 'dayjs/plugin/localizedFormat';
|
||||
import relativeTime from 'dayjs/plugin/relativeTime';
|
||||
import { useMemo } from 'preact/hooks';
|
||||
|
||||
dayjs.extend(dayjsTwitter);
|
||||
dayjs.extend(localizedFormat);
|
||||
|
@ -17,23 +18,25 @@ const dtf = new Intl.DateTimeFormat();
|
|||
|
||||
export default function RelativeTime({ datetime, format }) {
|
||||
if (!datetime) return null;
|
||||
const date = dayjs(datetime);
|
||||
let dateStr;
|
||||
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()) {
|
||||
dateStr = date.twitter();
|
||||
} else {
|
||||
dateStr = dtf.format(date.toDate());
|
||||
const date = useMemo(() => dayjs(datetime), [datetime]);
|
||||
const dateStr = useMemo(() => {
|
||||
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()) {
|
||||
return date.twitter();
|
||||
} else {
|
||||
return dtf.format(date.toDate());
|
||||
}
|
||||
}
|
||||
} else {
|
||||
dateStr = date.fromNow();
|
||||
}
|
||||
return date.fromNow();
|
||||
}, [date, format]);
|
||||
const dt = useMemo(() => date.toISOString(), [date]);
|
||||
const title = useMemo(() => date.format('LLLL'), [date]);
|
||||
|
||||
return (
|
||||
<time datetime={date.toISOString()} title={date.format('LLLL')}>
|
||||
<time datetime={dt} title={title}>
|
||||
{dateStr}
|
||||
</time>
|
||||
);
|
||||
|
|
|
@ -11,7 +11,7 @@ export default memo(function SearchCommand({ onClose = () => {} }) {
|
|||
const searchFormRef = useRef(null);
|
||||
|
||||
useHotkeys(
|
||||
'/',
|
||||
['Slash', '/'],
|
||||
(e) => {
|
||||
setShowSearch(true);
|
||||
setTimeout(() => {
|
||||
|
|
|
@ -73,6 +73,7 @@ const SearchForm = forwardRef((props, ref) => {
|
|||
autocomplete="off"
|
||||
autocorrect="off"
|
||||
autocapitalize="off"
|
||||
spellcheck="false"
|
||||
onSearch={(e) => {
|
||||
if (!e.target.value) {
|
||||
setSearchParams({});
|
||||
|
@ -84,6 +85,9 @@ const SearchForm = forwardRef((props, ref) => {
|
|||
}}
|
||||
onFocus={() => {
|
||||
setSearchMenuOpen(true);
|
||||
formRef.current
|
||||
?.querySelector('.search-popover-item')
|
||||
?.classList.add('focus');
|
||||
}}
|
||||
onBlur={() => {
|
||||
setTimeout(() => {
|
||||
|
@ -178,8 +182,33 @@ const SearchForm = forwardRef((props, ref) => {
|
|||
}}
|
||||
/>
|
||||
<div class="search-popover" hidden={!searchMenuOpen || !query}>
|
||||
{/* {!!query && (
|
||||
<Link
|
||||
to={`/search?q=${encodeURIComponent(query)}`}
|
||||
class="search-popover-item focus"
|
||||
onClick={(e) => {
|
||||
props?.onSubmit?.(e);
|
||||
}}
|
||||
>
|
||||
<Icon icon="search" />
|
||||
<span>{query}</span>
|
||||
</Link>
|
||||
)} */}
|
||||
{!!query &&
|
||||
[
|
||||
{
|
||||
label: (
|
||||
<>
|
||||
{query}{' '}
|
||||
<small class="insignificant">
|
||||
‒ accounts, hashtags & posts
|
||||
</small>
|
||||
</>
|
||||
),
|
||||
to: `/search?q=${encodeURIComponent(query)}`,
|
||||
top: !type && !/\s/.test(query),
|
||||
hidden: !!type,
|
||||
},
|
||||
{
|
||||
label: (
|
||||
<>
|
||||
|
@ -188,6 +217,8 @@ const SearchForm = forwardRef((props, ref) => {
|
|||
),
|
||||
to: `/search?q=${encodeURIComponent(query)}&type=statuses`,
|
||||
hidden: /^https?:/.test(query),
|
||||
top: /\s/.test(query),
|
||||
icon: 'document',
|
||||
},
|
||||
{
|
||||
label: (
|
||||
|
@ -200,6 +231,7 @@ const SearchForm = forwardRef((props, ref) => {
|
|||
/^@/.test(query) || /^https?:/.test(query) || /\s/.test(query),
|
||||
top: /^#/.test(query),
|
||||
type: 'link',
|
||||
icon: 'hashtag',
|
||||
},
|
||||
{
|
||||
label: (
|
||||
|
@ -219,6 +251,7 @@ const SearchForm = forwardRef((props, ref) => {
|
|||
</>
|
||||
),
|
||||
to: `/search?q=${encodeURIComponent(query)}&type=accounts`,
|
||||
icon: 'group',
|
||||
},
|
||||
]
|
||||
.sort((a, b) => {
|
||||
|
@ -226,17 +259,18 @@ const SearchForm = forwardRef((props, ref) => {
|
|||
if (!a.top && b.top) return 1;
|
||||
return 0;
|
||||
})
|
||||
.map(({ label, to, hidden, type }) => (
|
||||
.filter(({ hidden }) => !hidden)
|
||||
.map(({ label, to, icon, type }, i) => (
|
||||
<Link
|
||||
to={to}
|
||||
class="search-popover-item"
|
||||
hidden={hidden}
|
||||
class={`search-popover-item ${i === 0 ? 'focus' : ''}`}
|
||||
// hidden={hidden}
|
||||
onClick={(e) => {
|
||||
props?.onSubmit?.(e);
|
||||
}}
|
||||
>
|
||||
<Icon
|
||||
icon={type === 'link' ? 'arrow-right' : 'search'}
|
||||
icon={icon || (type === 'link' ? 'arrow-right' : 'search')}
|
||||
class="more-insignificant"
|
||||
/>
|
||||
<span>{label}</span>{' '}
|
||||
|
|
|
@ -36,7 +36,7 @@
|
|||
|
||||
#shortcuts-settings-container .shortcuts-view-mode {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
align-items: stretch;
|
||||
gap: 2px;
|
||||
margin: 8px 0 0;
|
||||
}
|
||||
|
@ -52,6 +52,7 @@
|
|||
gap: 8px;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
}
|
||||
#shortcuts-settings-container .shortcuts-view-mode label:first-child {
|
||||
border-top-left-radius: 16px;
|
||||
|
|
|
@ -170,7 +170,7 @@ export const SHORTCUTS_META = {
|
|||
},
|
||||
search: {
|
||||
id: 'search',
|
||||
title: ({ query }) => (query ? `"${query}"` : 'Search'),
|
||||
title: ({ query }) => (query ? `“${query}”` : 'Search'),
|
||||
path: ({ query }) =>
|
||||
query
|
||||
? `/search?q=${encodeURIComponent(query)}&type=statuses`
|
||||
|
@ -279,92 +279,93 @@ function ShortcutsSettings({ onClose }) {
|
|||
})}
|
||||
</div>
|
||||
{shortcuts.length > 0 ? (
|
||||
<ol class="shortcuts-list" ref={shortcutsListParent}>
|
||||
{shortcuts.filter(Boolean).map((shortcut, i) => {
|
||||
// const key = i + Object.values(shortcut);
|
||||
const key = Object.values(shortcut).join('-');
|
||||
const { type } = shortcut;
|
||||
if (!SHORTCUTS_META[type]) return null;
|
||||
let { icon, title, subtitle, excludeViewMode } =
|
||||
SHORTCUTS_META[type];
|
||||
if (typeof title === 'function') {
|
||||
title = title(shortcut, i);
|
||||
}
|
||||
if (typeof subtitle === 'function') {
|
||||
subtitle = subtitle(shortcut, i);
|
||||
}
|
||||
if (typeof icon === 'function') {
|
||||
icon = icon(shortcut, i);
|
||||
}
|
||||
if (typeof excludeViewMode === 'function') {
|
||||
excludeViewMode = excludeViewMode(shortcut, i);
|
||||
}
|
||||
const excludedViewMode = excludeViewMode?.includes(
|
||||
snapStates.settings.shortcutsViewMode,
|
||||
);
|
||||
return (
|
||||
<li key={key}>
|
||||
<Icon icon={icon} />
|
||||
<span class="shortcut-text">
|
||||
<AsyncText>{title}</AsyncText>
|
||||
{subtitle && (
|
||||
<>
|
||||
{' '}
|
||||
<small class="ib insignificant">{subtitle}</small>
|
||||
</>
|
||||
)}
|
||||
{excludedViewMode && (
|
||||
<span class="tag">
|
||||
Not available in current view mode
|
||||
</span>
|
||||
)}
|
||||
</span>
|
||||
<span class="shortcut-actions">
|
||||
<button
|
||||
type="button"
|
||||
class="plain small"
|
||||
disabled={i === 0}
|
||||
onClick={() => {
|
||||
const shortcutsArr = Array.from(states.shortcuts);
|
||||
if (i > 0) {
|
||||
const temp = states.shortcuts[i - 1];
|
||||
shortcutsArr[i - 1] = shortcut;
|
||||
shortcutsArr[i] = temp;
|
||||
states.shortcuts = shortcutsArr;
|
||||
}
|
||||
}}
|
||||
>
|
||||
<Icon icon="arrow-up" alt="Move up" />
|
||||
</button>
|
||||
<button
|
||||
type="button"
|
||||
class="plain small"
|
||||
disabled={i === shortcuts.length - 1}
|
||||
onClick={() => {
|
||||
const shortcutsArr = Array.from(states.shortcuts);
|
||||
if (i < states.shortcuts.length - 1) {
|
||||
const temp = states.shortcuts[i + 1];
|
||||
shortcutsArr[i + 1] = shortcut;
|
||||
shortcutsArr[i] = temp;
|
||||
states.shortcuts = shortcutsArr;
|
||||
}
|
||||
}}
|
||||
>
|
||||
<Icon icon="arrow-down" alt="Move down" />
|
||||
</button>
|
||||
<button
|
||||
type="button"
|
||||
class="plain small"
|
||||
onClick={() => {
|
||||
setShowForm({
|
||||
shortcut,
|
||||
shortcutIndex: i,
|
||||
});
|
||||
}}
|
||||
>
|
||||
<Icon icon="pencil" alt="Edit" />
|
||||
</button>
|
||||
{/* <button
|
||||
<>
|
||||
<ol class="shortcuts-list" ref={shortcutsListParent}>
|
||||
{shortcuts.filter(Boolean).map((shortcut, i) => {
|
||||
// const key = i + Object.values(shortcut);
|
||||
const key = Object.values(shortcut).join('-');
|
||||
const { type } = shortcut;
|
||||
if (!SHORTCUTS_META[type]) return null;
|
||||
let { icon, title, subtitle, excludeViewMode } =
|
||||
SHORTCUTS_META[type];
|
||||
if (typeof title === 'function') {
|
||||
title = title(shortcut, i);
|
||||
}
|
||||
if (typeof subtitle === 'function') {
|
||||
subtitle = subtitle(shortcut, i);
|
||||
}
|
||||
if (typeof icon === 'function') {
|
||||
icon = icon(shortcut, i);
|
||||
}
|
||||
if (typeof excludeViewMode === 'function') {
|
||||
excludeViewMode = excludeViewMode(shortcut, i);
|
||||
}
|
||||
const excludedViewMode = excludeViewMode?.includes(
|
||||
snapStates.settings.shortcutsViewMode,
|
||||
);
|
||||
return (
|
||||
<li key={key}>
|
||||
<Icon icon={icon} />
|
||||
<span class="shortcut-text">
|
||||
<AsyncText>{title}</AsyncText>
|
||||
{subtitle && (
|
||||
<>
|
||||
{' '}
|
||||
<small class="ib insignificant">{subtitle}</small>
|
||||
</>
|
||||
)}
|
||||
{excludedViewMode && (
|
||||
<span class="tag">
|
||||
Not available in current view mode
|
||||
</span>
|
||||
)}
|
||||
</span>
|
||||
<span class="shortcut-actions">
|
||||
<button
|
||||
type="button"
|
||||
class="plain small"
|
||||
disabled={i === 0}
|
||||
onClick={() => {
|
||||
const shortcutsArr = Array.from(states.shortcuts);
|
||||
if (i > 0) {
|
||||
const temp = states.shortcuts[i - 1];
|
||||
shortcutsArr[i - 1] = shortcut;
|
||||
shortcutsArr[i] = temp;
|
||||
states.shortcuts = shortcutsArr;
|
||||
}
|
||||
}}
|
||||
>
|
||||
<Icon icon="arrow-up" alt="Move up" />
|
||||
</button>
|
||||
<button
|
||||
type="button"
|
||||
class="plain small"
|
||||
disabled={i === shortcuts.length - 1}
|
||||
onClick={() => {
|
||||
const shortcutsArr = Array.from(states.shortcuts);
|
||||
if (i < states.shortcuts.length - 1) {
|
||||
const temp = states.shortcuts[i + 1];
|
||||
shortcutsArr[i + 1] = shortcut;
|
||||
shortcutsArr[i] = temp;
|
||||
states.shortcuts = shortcutsArr;
|
||||
}
|
||||
}}
|
||||
>
|
||||
<Icon icon="arrow-down" alt="Move down" />
|
||||
</button>
|
||||
<button
|
||||
type="button"
|
||||
class="plain small"
|
||||
onClick={() => {
|
||||
setShowForm({
|
||||
shortcut,
|
||||
shortcutIndex: i,
|
||||
});
|
||||
}}
|
||||
>
|
||||
<Icon icon="pencil" alt="Edit" />
|
||||
</button>
|
||||
{/* <button
|
||||
type="button"
|
||||
class="plain small"
|
||||
onClick={() => {
|
||||
|
@ -373,11 +374,21 @@ function ShortcutsSettings({ onClose }) {
|
|||
>
|
||||
<Icon icon="x" alt="Remove" />
|
||||
</button> */}
|
||||
</span>
|
||||
</li>
|
||||
);
|
||||
})}
|
||||
</ol>
|
||||
</span>
|
||||
</li>
|
||||
);
|
||||
})}
|
||||
</ol>
|
||||
{shortcuts.length === 1 &&
|
||||
snapStates.settings.shortcutsViewMode !== 'float-button' && (
|
||||
<div class="ui-state insignificant">
|
||||
<Icon icon="info" />{' '}
|
||||
<small>
|
||||
Add more than one shortcut/column to make this work.
|
||||
</small>
|
||||
</div>
|
||||
)}
|
||||
</>
|
||||
) : (
|
||||
<div class="ui-state insignificant">
|
||||
<p>No shortcuts yet. Tap on the Add shortcut button.</p>
|
||||
|
@ -428,7 +439,12 @@ function ShortcutsSettings({ onClose }) {
|
|||
disabled={shortcuts.length >= SHORTCUTS_LIMIT}
|
||||
onClick={() => setShowForm(true)}
|
||||
>
|
||||
<Icon icon="plus" /> <span>Add shortcut</span>
|
||||
<Icon icon="plus" />{' '}
|
||||
<span>
|
||||
{snapStates.settings.shortcutsViewMode === 'multi-column'
|
||||
? 'Add column…'
|
||||
: 'Add shortcut…'}
|
||||
</span>
|
||||
</button>
|
||||
</p>
|
||||
</main>
|
||||
|
|
|
@ -206,7 +206,7 @@
|
|||
.status-card:not(.status-carousel .status)
|
||||
:is(.content, .poll, .media-container) {
|
||||
max-height: 160px !important;
|
||||
overflow: clip;
|
||||
overflow: hidden;
|
||||
}
|
||||
.status.small:not(.status-carousel .status, .status.large .status)
|
||||
.status-card
|
||||
|
@ -290,7 +290,7 @@
|
|||
transition: all 0.2s ease-out;
|
||||
}
|
||||
.status.filtered:hover :is(.status-filtered-info-1, .status-filtered-info-2) {
|
||||
transition-delay: 0.5s;
|
||||
transition-delay: 1.5s;
|
||||
}
|
||||
.status.filtered .status-filtered-info-1 {
|
||||
opacity: 0.5;
|
||||
|
@ -330,6 +330,68 @@
|
|||
font-size: 90%;
|
||||
}
|
||||
|
||||
.status.compact-reply {
|
||||
--avatar-size: 20px;
|
||||
--line-start: 40px;
|
||||
--line-width: 3px;
|
||||
--line-end: calc(var(--line-start) + var(--line-width));
|
||||
|
||||
display: flex;
|
||||
gap: 12px;
|
||||
--top-padding: 16px;
|
||||
padding-top: var(--top-padding);
|
||||
padding-bottom: 0;
|
||||
margin-bottom: calc(-1 * var(--top-padding) / 2);
|
||||
background-image: linear-gradient(
|
||||
160deg,
|
||||
transparent 2.5%,
|
||||
var(--reply-to-faded-color) 10%,
|
||||
transparent
|
||||
);
|
||||
background-repeat: no-repeat;
|
||||
background-size: 100% calc(100% - var(--top-padding) / 2);
|
||||
|
||||
> * {
|
||||
opacity: 0.65;
|
||||
transition: opacity 1s ease-out;
|
||||
}
|
||||
.status-link:hover & > * {
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
&:before {
|
||||
content: '';
|
||||
position: absolute;
|
||||
top: calc(var(--top-padding) + var(--avatar-size));
|
||||
left: var(--line-start);
|
||||
width: var(--line-width);
|
||||
height: calc(
|
||||
100% - var(--top-padding) - var(--avatar-size) + (var(--top-padding) / 2)
|
||||
);
|
||||
background-color: var(--comment-line-color);
|
||||
z-index: 0;
|
||||
mask-image: linear-gradient(to bottom, #000 8px, transparent);
|
||||
}
|
||||
|
||||
.avatar {
|
||||
margin-left: calc((50px - var(--avatar-size)) / 2);
|
||||
justify-self: center;
|
||||
z-index: 1;
|
||||
}
|
||||
|
||||
.content-compact {
|
||||
overflow: hidden;
|
||||
display: -webkit-box;
|
||||
display: box;
|
||||
-webkit-box-orient: vertical;
|
||||
box-orient: vertical;
|
||||
-webkit-line-clamp: 2;
|
||||
line-clamp: 2;
|
||||
font-size: 90%;
|
||||
line-height: var(--avatar-size);
|
||||
}
|
||||
}
|
||||
|
||||
.status .container {
|
||||
flex-grow: 1;
|
||||
min-width: 0;
|
||||
|
@ -364,9 +426,8 @@
|
|||
vertical-align: middle;
|
||||
}
|
||||
.status > .container > .meta :is(.time, .edited) {
|
||||
color: inherit;
|
||||
color: var(--text-insignificant-color);
|
||||
text-align: end;
|
||||
opacity: 0.5;
|
||||
text-decoration: none;
|
||||
flex-shrink: 0;
|
||||
margin-left: 4px;
|
||||
|
@ -375,9 +436,21 @@
|
|||
.status > .container > .meta a.time {
|
||||
position: relative;
|
||||
overflow: visible;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 2px;
|
||||
font-size: 90%;
|
||||
|
||||
.more {
|
||||
margin-left: 4px;
|
||||
transition: transform 0.2s ease-out;
|
||||
}
|
||||
}
|
||||
.status > .container > .meta a.time:is(:hover, :focus) {
|
||||
text-decoration: underline;
|
||||
.more {
|
||||
transform: scale(1.2);
|
||||
color: var(--link-color);
|
||||
}
|
||||
}
|
||||
.status > .container > .meta a.time:active,
|
||||
.status > .container > .meta a.time.is-open {
|
||||
|
@ -643,6 +716,10 @@
|
|||
display: flex;
|
||||
gap: 4px;
|
||||
align-items: center;
|
||||
|
||||
.timeline-item-container & {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
|
||||
.status.compact-thread .spoiler-badge {
|
||||
|
@ -1232,6 +1309,22 @@ body:has(#modal-container .carousel) .status .media img:hover {
|
|||
-webkit-box-orient: vertical;
|
||||
white-space: normal;
|
||||
}
|
||||
/* Collapse possible hashtag stuffing */
|
||||
/* If >= 9 hashtags, collapse */
|
||||
/* TODO: lower the threshold one day */
|
||||
.status:not(.large, .contextual .status)
|
||||
p:not(.hashtag-stuffing):has(.hashtag:nth-of-type(1)):has(
|
||||
.hashtag:nth-of-type(2)
|
||||
):has(.hashtag:nth-of-type(3)):has(.hashtag:nth-of-type(4)):has(
|
||||
.hashtag:nth-of-type(5)
|
||||
):has(.hashtag:nth-of-type(6)):has(.hashtag:nth-of-type(7)):has(
|
||||
.hashtag:nth-of-type(8)
|
||||
):has(.hashtag:nth-of-type(9)) {
|
||||
overflow: hidden;
|
||||
display: -webkit-box;
|
||||
-webkit-line-clamp: 3;
|
||||
-webkit-box-orient: vertical;
|
||||
}
|
||||
|
||||
.media-figure-multiple {
|
||||
margin: 0;
|
||||
|
@ -1755,6 +1848,87 @@ a.card:is(:hover, :focus):visited {
|
|||
color: var(--green-color);
|
||||
}
|
||||
|
||||
/* ACTIONS */
|
||||
|
||||
.status-actions {
|
||||
display: flex;
|
||||
position: absolute;
|
||||
top: 4px;
|
||||
right: 4px;
|
||||
background-color: var(--bg-color);
|
||||
border-radius: 8px;
|
||||
z-index: 1;
|
||||
border: 1px solid var(--outline-color);
|
||||
box-shadow: 0 2px 6px -3px var(--drop-shadow-color);
|
||||
overflow: clip;
|
||||
opacity: 0;
|
||||
pointer-events: none;
|
||||
transform: translateX(8px);
|
||||
transform-origin: right center;
|
||||
transition: all 0.15s ease-out 0.3s, border-color 0.3s ease-out;
|
||||
|
||||
button.plain {
|
||||
color: var(--text-insignificant-color);
|
||||
backdrop-filter: none;
|
||||
padding: 10px;
|
||||
border-radius: 8px;
|
||||
outline-offset: -5px;
|
||||
outline: 1px solid transparent;
|
||||
|
||||
&:is(:hover, :focus) {
|
||||
color: var(--text-color);
|
||||
background-color: var(--bg-faded-color);
|
||||
filter: none !important;
|
||||
box-shadow: inset 0 0 0 2px var(--bg-color);
|
||||
}
|
||||
|
||||
&.reblog-button.checked {
|
||||
color: var(--reblog-color);
|
||||
outline-color: var(--reblog-color);
|
||||
}
|
||||
|
||||
&.favourite-button.checked {
|
||||
color: var(--favourite-color);
|
||||
outline-color: var(--favourite-color);
|
||||
}
|
||||
|
||||
&.bookmark-button.checked {
|
||||
color: var(--link-color);
|
||||
outline-color: var(--link-color);
|
||||
}
|
||||
}
|
||||
|
||||
&:hover {
|
||||
border-color: var(--outline-hover-color);
|
||||
}
|
||||
|
||||
&:hover,
|
||||
.status:hover &:not(:hover),
|
||||
&.open {
|
||||
opacity: 1;
|
||||
pointer-events: auto;
|
||||
transform: translateX(0);
|
||||
}
|
||||
@media (pointer: coarse) {
|
||||
.status:has(&):hover {
|
||||
transition: background-color 0.1s ease-out 0.3s;
|
||||
background-color: var(--bg-faded-blur-color);
|
||||
}
|
||||
}
|
||||
|
||||
&.open {
|
||||
button.more-button {
|
||||
color: var(--text-color);
|
||||
background-color: var(--outline-color);
|
||||
box-shadow: inset 0 0 0 2px var(--bg-color);
|
||||
}
|
||||
|
||||
button:not(.more-button) {
|
||||
opacity: 0.3;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* BADGE */
|
||||
|
||||
.status-badge {
|
||||
|
@ -1853,6 +2027,12 @@ a.card:is(:hover, :focus):visited {
|
|||
font-size: 80%;
|
||||
}
|
||||
|
||||
/* MENU OPEN */
|
||||
|
||||
.status-menu-open {
|
||||
background-color: var(--link-bg-hover-color) !important;
|
||||
}
|
||||
|
||||
/* FILTERED */
|
||||
|
||||
#filtered-status-peek {
|
||||
|
|
File diff suppressed because it is too large
Load diff
|
@ -46,6 +46,7 @@ function Timeline({
|
|||
view,
|
||||
filterContext,
|
||||
showFollowedTags,
|
||||
showReplyParent,
|
||||
}) {
|
||||
const snapStates = useSnapshot(states);
|
||||
const [items, setItems] = useState([]);
|
||||
|
@ -84,7 +85,7 @@ function Timeline({
|
|||
if (boostsCarousel) {
|
||||
value = groupBoosts(value);
|
||||
}
|
||||
value = groupContext(value);
|
||||
value = groupContext(value, instance);
|
||||
}
|
||||
if (pinnedPosts.length) {
|
||||
value = pinnedPosts.concat(value);
|
||||
|
@ -204,6 +205,21 @@ function Timeline({
|
|||
}
|
||||
});
|
||||
|
||||
const showNewPostsIndicator =
|
||||
items.length > 0 && uiState !== 'loading' && showNew;
|
||||
const handleLoadNewPosts = useCallback(() => {
|
||||
loadItems(true);
|
||||
scrollableRef.current?.scrollTo({
|
||||
top: 0,
|
||||
behavior: 'smooth',
|
||||
});
|
||||
}, [loadItems]);
|
||||
const dotRef = useHotkeys('.', () => {
|
||||
if (showNewPostsIndicator) {
|
||||
handleLoadNewPosts();
|
||||
}
|
||||
});
|
||||
|
||||
// const {
|
||||
// scrollDirection,
|
||||
// nearReachStart,
|
||||
|
@ -387,24 +403,15 @@ function Timeline({
|
|||
{!!headerEnd && headerEnd}
|
||||
</div>
|
||||
</div>
|
||||
{items.length > 0 &&
|
||||
uiState !== 'loading' &&
|
||||
// !hiddenUI &&
|
||||
showNew && (
|
||||
<button
|
||||
class="updates-button shiny-pill"
|
||||
type="button"
|
||||
onClick={() => {
|
||||
loadItems(true);
|
||||
scrollableRef.current?.scrollTo({
|
||||
top: 0,
|
||||
behavior: 'smooth',
|
||||
});
|
||||
}}
|
||||
>
|
||||
<Icon icon="arrow-up" /> New posts
|
||||
</button>
|
||||
)}
|
||||
{showNewPostsIndicator && (
|
||||
<button
|
||||
class="updates-button shiny-pill"
|
||||
type="button"
|
||||
onClick={handleLoadNewPosts}
|
||||
>
|
||||
<Icon icon="arrow-up" /> New posts
|
||||
</button>
|
||||
)}
|
||||
</header>
|
||||
{!!timelineStart && (
|
||||
<div
|
||||
|
@ -426,6 +433,7 @@ function Timeline({
|
|||
key={status.id + status?._pinned + view}
|
||||
view={view}
|
||||
showFollowedTags={showFollowedTags}
|
||||
showReplyParent={showReplyParent}
|
||||
/>
|
||||
))}
|
||||
{showMore &&
|
||||
|
@ -516,6 +524,7 @@ function TimelineItem({
|
|||
filterContext,
|
||||
view,
|
||||
showFollowedTags,
|
||||
showReplyParent,
|
||||
}) {
|
||||
const { id: statusID, reblog, items, type, _pinned } = status;
|
||||
if (_pinned) useItemID = false;
|
||||
|
@ -674,6 +683,7 @@ function TimelineItem({
|
|||
instance={instance}
|
||||
enableCommentHint
|
||||
showFollowedTags={showFollowedTags}
|
||||
showReplyParent={showReplyParent}
|
||||
// allowFilters={allowFilters}
|
||||
/>
|
||||
) : (
|
||||
|
@ -682,6 +692,7 @@ function TimelineItem({
|
|||
instance={instance}
|
||||
enableCommentHint
|
||||
showFollowedTags={showFollowedTags}
|
||||
showReplyParent={showReplyParent}
|
||||
// allowFilters={allowFilters}
|
||||
/>
|
||||
)}
|
||||
|
@ -779,7 +790,7 @@ function StatusCarousel({ title, class: className, children }) {
|
|||
|
||||
function TimelineStatusCompact({ status, instance }) {
|
||||
const snapStates = useSnapshot(states);
|
||||
const { id, visibility } = status;
|
||||
const { id, visibility, language } = status;
|
||||
const statusPeekText = statusPeek(status);
|
||||
const sKey = statusKey(id, instance);
|
||||
return (
|
||||
|
@ -801,7 +812,12 @@ function TimelineStatusCompact({ status, instance }) {
|
|||
<Icon icon="thread" size="s" />
|
||||
</div>
|
||||
)}
|
||||
<div class="content-compact" title={statusPeekText}>
|
||||
<div
|
||||
class="content-compact"
|
||||
title={statusPeekText}
|
||||
lang={language}
|
||||
dir="auto"
|
||||
>
|
||||
{statusPeekText}
|
||||
{status.sensitive && status.spoilerText && (
|
||||
<>
|
||||
|
|
|
@ -959,11 +959,6 @@
|
|||
"Kabyle",
|
||||
"Taqbaylit"
|
||||
],
|
||||
[
|
||||
"kmr",
|
||||
"Kurmanji (Kurdish)",
|
||||
"Kurmancî"
|
||||
],
|
||||
[
|
||||
"ldn",
|
||||
"Láadan",
|
||||
|
|
|
@ -45,19 +45,31 @@ function FollowedHashtags() {
|
|||
</header>
|
||||
<main>
|
||||
{followedHashtags.length > 0 ? (
|
||||
<ul class="link-list">
|
||||
{followedHashtags.map((tag) => (
|
||||
<li>
|
||||
<Link
|
||||
to={
|
||||
instance ? `/${instance}/t/${tag.name}` : `/t/${tag.name}`
|
||||
}
|
||||
>
|
||||
<Icon icon="hashtag" /> <span>{tag.name}</span>
|
||||
</Link>
|
||||
</li>
|
||||
))}
|
||||
</ul>
|
||||
<>
|
||||
<ul class="link-list">
|
||||
{followedHashtags.map((tag) => (
|
||||
<li>
|
||||
<Link
|
||||
to={
|
||||
instance
|
||||
? `/${instance}/t/${tag.name}`
|
||||
: `/t/${tag.name}`
|
||||
}
|
||||
>
|
||||
<Icon icon="hashtag" /> <span>{tag.name}</span>
|
||||
</Link>
|
||||
</li>
|
||||
))}
|
||||
</ul>
|
||||
{followedHashtags.length > 1 && (
|
||||
<footer class="ui-state">
|
||||
<small class="insignificant">
|
||||
{followedHashtags.length} hashtag
|
||||
{followedHashtags.length === 1 ? '' : 's'}
|
||||
</small>
|
||||
</footer>
|
||||
)}
|
||||
</>
|
||||
) : uiState === 'loading' ? (
|
||||
<p class="ui-state">
|
||||
<Loader abrupt />
|
||||
|
|
|
@ -129,6 +129,7 @@ function Following({ title, path, id, ...props }) {
|
|||
// allowFilters
|
||||
filterContext="home"
|
||||
showFollowedTags
|
||||
showReplyParent
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
|
|
@ -104,6 +104,7 @@ function List(props) {
|
|||
boostsCarousel={snapStates.settings.boostsCarousel}
|
||||
// allowFilters
|
||||
filterContext="home"
|
||||
showReplyParent
|
||||
// refresh={reloadCount}
|
||||
headerStart={
|
||||
<Link to="/l" class="button plain">
|
||||
|
|
|
@ -61,14 +61,15 @@ function Lists() {
|
|||
</header>
|
||||
<main>
|
||||
{lists.length > 0 ? (
|
||||
<ul class="link-list">
|
||||
{lists.map((list) => (
|
||||
<li>
|
||||
<Link to={`/l/${list.id}`}>
|
||||
<span>
|
||||
<Icon icon="list" /> <span>{list.title}</span>
|
||||
</span>
|
||||
{/* <button
|
||||
<>
|
||||
<ul class="link-list">
|
||||
{lists.map((list) => (
|
||||
<li>
|
||||
<Link to={`/l/${list.id}`}>
|
||||
<span>
|
||||
<Icon icon="list" /> <span>{list.title}</span>
|
||||
</span>
|
||||
{/* <button
|
||||
type="button"
|
||||
class="plain"
|
||||
onClick={(e) => {
|
||||
|
@ -81,10 +82,19 @@ function Lists() {
|
|||
>
|
||||
<Icon icon="pencil" />
|
||||
</button> */}
|
||||
</Link>
|
||||
</li>
|
||||
))}
|
||||
</ul>
|
||||
</Link>
|
||||
</li>
|
||||
))}
|
||||
</ul>
|
||||
{lists.length > 1 && (
|
||||
<footer class="ui-state">
|
||||
<small class="insignificant">
|
||||
{lists.length} list
|
||||
{lists.length === 1 ? '' : 's'}
|
||||
</small>
|
||||
</footer>
|
||||
)}
|
||||
</>
|
||||
) : uiState === 'loading' ? (
|
||||
<p class="ui-state">
|
||||
<Loader />
|
||||
|
|
|
@ -44,6 +44,18 @@
|
|||
}
|
||||
}
|
||||
|
||||
#search-page h2 {
|
||||
a {
|
||||
.icon {
|
||||
vertical-align: middle;
|
||||
transition: transform 0.2s;
|
||||
}
|
||||
&:hover .icon {
|
||||
transform: translateX(4px);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#search-page ul.accounts-list {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
|
|
|
@ -174,7 +174,7 @@ function Search({ columnMode, ...props }) {
|
|||
}, [q, type, instance]);
|
||||
|
||||
useHotkeys(
|
||||
'/',
|
||||
['/', 'Slash'],
|
||||
(e) => {
|
||||
searchFormRef.current?.focus?.();
|
||||
},
|
||||
|
@ -253,7 +253,14 @@ function Search({ columnMode, ...props }) {
|
|||
{(!type || type === 'accounts') && (
|
||||
<>
|
||||
{type !== 'accounts' && (
|
||||
<h2 class="timeline-header">Accounts</h2>
|
||||
<h2 class="timeline-header">
|
||||
Accounts{' '}
|
||||
<Link
|
||||
to={`/search?q=${encodeURIComponent(q)}&type=accounts`}
|
||||
>
|
||||
<Icon icon="arrow-right" size="l" />
|
||||
</Link>
|
||||
</h2>
|
||||
)}
|
||||
{accountResults.length > 0 ? (
|
||||
<>
|
||||
|
@ -273,7 +280,9 @@ function Search({ columnMode, ...props }) {
|
|||
<div class="ui-state">
|
||||
<Link
|
||||
class="plain button"
|
||||
to={`/search?q=${q}&type=accounts`}
|
||||
to={`/search?q=${encodeURIComponent(
|
||||
q,
|
||||
)}&type=accounts`}
|
||||
>
|
||||
See more accounts <Icon icon="arrow-right" />
|
||||
</Link>
|
||||
|
@ -295,7 +304,14 @@ function Search({ columnMode, ...props }) {
|
|||
{(!type || type === 'hashtags') && (
|
||||
<>
|
||||
{type !== 'hashtags' && (
|
||||
<h2 class="timeline-header">Hashtags</h2>
|
||||
<h2 class="timeline-header">
|
||||
Hashtags{' '}
|
||||
<Link
|
||||
to={`/search?q=${encodeURIComponent(q)}&type=hashtags`}
|
||||
>
|
||||
<Icon icon="arrow-right" size="l" />
|
||||
</Link>
|
||||
</h2>
|
||||
)}
|
||||
{hashtagResults.length > 0 ? (
|
||||
<>
|
||||
|
@ -331,7 +347,9 @@ function Search({ columnMode, ...props }) {
|
|||
<div class="ui-state">
|
||||
<Link
|
||||
class="plain button"
|
||||
to={`/search?q=${q}&type=hashtags`}
|
||||
to={`/search?q=${encodeURIComponent(
|
||||
q,
|
||||
)}&type=hashtags`}
|
||||
>
|
||||
See more hashtags <Icon icon="arrow-right" />
|
||||
</Link>
|
||||
|
@ -353,7 +371,14 @@ function Search({ columnMode, ...props }) {
|
|||
{(!type || type === 'statuses') && (
|
||||
<>
|
||||
{type !== 'statuses' && (
|
||||
<h2 class="timeline-header">Posts</h2>
|
||||
<h2 class="timeline-header">
|
||||
Posts{' '}
|
||||
<Link
|
||||
to={`/search?q=${encodeURIComponent(q)}&type=statuses`}
|
||||
>
|
||||
<Icon icon="arrow-right" size="l" />
|
||||
</Link>
|
||||
</h2>
|
||||
)}
|
||||
{statusResults.length > 0 ? (
|
||||
<>
|
||||
|
@ -377,7 +402,9 @@ function Search({ columnMode, ...props }) {
|
|||
<div class="ui-state">
|
||||
<Link
|
||||
class="plain button"
|
||||
to={`/search?q=${q}&type=statuses`}
|
||||
to={`/search?q=${encodeURIComponent(
|
||||
q,
|
||||
)}&type=statuses`}
|
||||
>
|
||||
See more posts <Icon icon="arrow-right" />
|
||||
</Link>
|
||||
|
|
|
@ -643,7 +643,7 @@ function PushNotificationsSection({ onClose }) {
|
|||
const { instance } = api();
|
||||
const [uiState, setUIState] = useState('default');
|
||||
const pushFormRef = useRef();
|
||||
const [allowNofitications, setAllowNotifications] = useState(false);
|
||||
const [allowNotifications, setAllowNotifications] = useState(false);
|
||||
const [needRelogin, setNeedRelogin] = useState(false);
|
||||
const previousPolicyRef = useRef();
|
||||
useEffect(() => {
|
||||
|
@ -689,7 +689,7 @@ function PushNotificationsSection({ onClose }) {
|
|||
ref={pushFormRef}
|
||||
onChange={() => {
|
||||
const values = Object.fromEntries(new FormData(pushFormRef.current));
|
||||
const allowNofitications = !!values['policy-allow'];
|
||||
const allowNotifications = !!values['policy-allow'];
|
||||
const params = {
|
||||
policy: values.policy,
|
||||
data: {
|
||||
|
@ -718,9 +718,13 @@ function PushNotificationsSection({ onClose }) {
|
|||
});
|
||||
const policyChanged = previousPolicyRef.current !== params.policy;
|
||||
|
||||
console.log('PN Form', { values, allowNofitications, params });
|
||||
console.log('PN Form', {
|
||||
values,
|
||||
allowNotifications: allowNotifications,
|
||||
params,
|
||||
});
|
||||
|
||||
if (allowNofitications && alertsCount > 0) {
|
||||
if (allowNotifications && alertsCount > 0) {
|
||||
if (policyChanged) {
|
||||
console.debug('Policy changed.');
|
||||
removeSubscription()
|
||||
|
@ -754,7 +758,7 @@ function PushNotificationsSection({ onClose }) {
|
|||
type="checkbox"
|
||||
disabled={isLoading || needRelogin}
|
||||
name="policy-allow"
|
||||
checked={allowNofitications}
|
||||
checked={allowNotifications}
|
||||
onChange={async (e) => {
|
||||
const { checked } = e.target;
|
||||
if (checked) {
|
||||
|
@ -778,7 +782,7 @@ function PushNotificationsSection({ onClose }) {
|
|||
Allow from{' '}
|
||||
<select
|
||||
name="policy"
|
||||
disabled={isLoading || needRelogin || !allowNofitications}
|
||||
disabled={isLoading || needRelogin || !allowNotifications}
|
||||
>
|
||||
{[
|
||||
{
|
||||
|
@ -803,7 +807,7 @@ function PushNotificationsSection({ onClose }) {
|
|||
style={{
|
||||
width: '100%',
|
||||
}}
|
||||
hidden={!allowNofitications}
|
||||
hidden={!allowNotifications}
|
||||
>
|
||||
<div class="shazam-container-inner">
|
||||
<div class="sub-section">
|
||||
|
|
|
@ -1,16 +1,32 @@
|
|||
.status-deck header {
|
||||
white-space: nowrap;
|
||||
.status-deck {
|
||||
header {
|
||||
white-space: nowrap;
|
||||
}
|
||||
header h1 {
|
||||
min-width: 0;
|
||||
flex-grow: 1;
|
||||
text-overflow: ellipsis;
|
||||
overflow: hidden;
|
||||
white-space: nowrap;
|
||||
align-self: stretch;
|
||||
}
|
||||
header h1 .deck-back {
|
||||
margin-left: -16px;
|
||||
}
|
||||
|
||||
.button-refresh .icon {
|
||||
animation: spin 1s linear;
|
||||
}
|
||||
.button-refresh:is(:hover, :focus) .icon {
|
||||
transition: transform 1s linear;
|
||||
transform: rotate(360deg);
|
||||
}
|
||||
}
|
||||
.status-deck header h1 {
|
||||
min-width: 0;
|
||||
flex-grow: 1;
|
||||
text-overflow: ellipsis;
|
||||
overflow: hidden;
|
||||
white-space: nowrap;
|
||||
align-self: stretch;
|
||||
}
|
||||
.status-deck header h1 .deck-back {
|
||||
margin-left: -16px;
|
||||
|
||||
@keyframes spin {
|
||||
to {
|
||||
transform: rotate(360deg);
|
||||
}
|
||||
}
|
||||
|
||||
.hero-heading {
|
||||
|
|
|
@ -245,6 +245,7 @@ function StatusThread({ id, closeLink = '/', instance: propInstance }) {
|
|||
}, [id, uiState !== 'loading']);
|
||||
|
||||
const scrollOffsets = useRef();
|
||||
const lastInitContextTS = useRef();
|
||||
const initContext = ({ reloadHero } = {}) => {
|
||||
console.debug('initContext', id);
|
||||
setUIState('loading');
|
||||
|
@ -432,12 +433,31 @@ function StatusThread({ id, closeLink = '/', instance: propInstance }) {
|
|||
}
|
||||
})();
|
||||
|
||||
lastInitContextTS.current = Date.now();
|
||||
|
||||
return () => {
|
||||
clearTimeout(heroTimer);
|
||||
};
|
||||
};
|
||||
|
||||
useEffect(initContext, [id, masto]);
|
||||
|
||||
const [showRefresh, setShowRefresh] = useState(false);
|
||||
useEffect(() => {
|
||||
let interval = setInterval(() => {
|
||||
const now = Date.now();
|
||||
if (
|
||||
lastInitContextTS.current &&
|
||||
now - lastInitContextTS.current >= 60_000
|
||||
) {
|
||||
setShowRefresh(true);
|
||||
}
|
||||
}, 60_000); // 1 minute
|
||||
return () => {
|
||||
clearInterval(interval);
|
||||
};
|
||||
}, []);
|
||||
|
||||
useLayoutEffect(() => {
|
||||
if (!statuses.length) return;
|
||||
console.debug('STATUSES', statuses);
|
||||
|
@ -845,6 +865,7 @@ function StatusThread({ id, closeLink = '/', instance: propInstance }) {
|
|||
enableTranslate
|
||||
onMediaClick={handleMediaClick}
|
||||
onStatusLinkClick={handleStatusLinkClick}
|
||||
showActionsBar={!!descendant}
|
||||
/>
|
||||
)}
|
||||
{ancestor && repliesCount > 1 && (
|
||||
|
@ -1094,6 +1115,18 @@ function StatusThread({ id, closeLink = '/', instance: propInstance }) {
|
|||
>
|
||||
<Icon icon="layout4" size="l" />
|
||||
</button>
|
||||
{showRefresh && (
|
||||
<button
|
||||
type="button"
|
||||
class="plain button-refresh"
|
||||
onClick={() => {
|
||||
states.reloadStatusPage++;
|
||||
setShowRefresh(false);
|
||||
}}
|
||||
>
|
||||
<Icon icon="refresh" size="l" />
|
||||
</button>
|
||||
)}
|
||||
<Menu2
|
||||
align="end"
|
||||
portal={{
|
||||
|
@ -1400,6 +1433,7 @@ function SubComments({
|
|||
size="s"
|
||||
enableTranslate
|
||||
onMediaClick={handleMediaClick}
|
||||
showActionsBar
|
||||
/>
|
||||
{!r.replies?.length && r.repliesCount > 0 && (
|
||||
<div class="replies-link">
|
||||
|
|
|
@ -37,6 +37,7 @@ function _enhanceContent(content, opts = {}) {
|
|||
links.forEach((link) => {
|
||||
if (/^https?:\/\//i.test(link.textContent.trim())) {
|
||||
link.classList.add('has-url-text');
|
||||
shortenLink(link);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
@ -287,6 +288,30 @@ const defaultRejectFilter = [
|
|||
const defaultRejectFilterMap = Object.fromEntries(
|
||||
defaultRejectFilter.map((nodeName) => [nodeName, true]),
|
||||
);
|
||||
|
||||
const URL_PREFIX_REGEX = /^(https?:\/\/(www\.)?|xmpp:)/;
|
||||
const URL_DISPLAY_LENGTH = 30;
|
||||
// Similar to https://github.com/mastodon/mastodon/blob/1666b1955992e16f4605b414c6563ca25b3a3f18/app/lib/text_formatter.rb#L54-L69
|
||||
function shortenLink(link) {
|
||||
if (!link || link.querySelector?.('*')) {
|
||||
return;
|
||||
}
|
||||
try {
|
||||
const url = link.innerText.trim();
|
||||
const prefix = (url.match(URL_PREFIX_REGEX) || [])[0] || '';
|
||||
if (!prefix) return;
|
||||
const displayURL = url.slice(
|
||||
prefix.length,
|
||||
prefix.length + URL_DISPLAY_LENGTH,
|
||||
);
|
||||
const suffix = url.slice(prefix.length + URL_DISPLAY_LENGTH);
|
||||
const cutoff = url.slice(prefix.length).length > URL_DISPLAY_LENGTH;
|
||||
link.innerHTML = `<span class="invisible">${prefix}</span><span class=${
|
||||
cutoff ? 'ellipsis' : ''
|
||||
}>${displayURL}</span><span class="invisible">${suffix}</span>`;
|
||||
} catch (e) {}
|
||||
}
|
||||
|
||||
function extractTextNodes(dom, opts = {}) {
|
||||
const textNodes = [];
|
||||
const rejectFilterMap = Object.assign(
|
||||
|
|
|
@ -9,6 +9,17 @@ function getHTMLText(html) {
|
|||
div.querySelectorAll('br').forEach((br) => {
|
||||
br.replaceWith('\n');
|
||||
});
|
||||
|
||||
// MASTODON-SPECIFIC classes
|
||||
// Remove .invisible
|
||||
div.querySelectorAll('.invisible').forEach((el) => {
|
||||
el.remove();
|
||||
});
|
||||
// Add … at end of .ellipsis
|
||||
div.querySelectorAll('.ellipsis').forEach((el) => {
|
||||
el.append('...');
|
||||
});
|
||||
|
||||
return div.innerText.replace(/[\r\n]{3,}/g, '\n\n').trim();
|
||||
}
|
||||
|
||||
|
|
|
@ -37,6 +37,7 @@ const states = proxy({
|
|||
unfurledLinks: {},
|
||||
statusQuotes: {},
|
||||
statusFollowedTags: {},
|
||||
statusReply: {},
|
||||
accounts: {},
|
||||
routeNotification: null,
|
||||
// Modals
|
||||
|
@ -187,9 +188,19 @@ export function saveStatus(status, instance, opts) {
|
|||
if (oldStatus?._pinned) status._pinned = oldStatus._pinned;
|
||||
// if (oldStatus?._filtered) status._filtered = oldStatus._filtered;
|
||||
states.statuses[key] = status;
|
||||
if (status.reblog) {
|
||||
const key = statusKey(status.reblog.id, instance);
|
||||
states.statuses[key] = status.reblog;
|
||||
if (status.reblog?.id) {
|
||||
const srKey = statusKey(status.reblog.id, instance);
|
||||
states.statuses[srKey] = status.reblog;
|
||||
}
|
||||
if (status.quote?.id) {
|
||||
const sKey = statusKey(status.quote.id, instance);
|
||||
states.statuses[sKey] = status.quote;
|
||||
states.statusQuotes[key] = [
|
||||
{
|
||||
id: status.quote.id,
|
||||
instance,
|
||||
},
|
||||
];
|
||||
}
|
||||
});
|
||||
|
||||
|
|
|
@ -1,6 +1,8 @@
|
|||
import { api } from './api';
|
||||
import { extractTagsFromStatus, getFollowedTags } from './followed-tags';
|
||||
import pmem from './pmem';
|
||||
import { fetchRelationships } from './relationships';
|
||||
import states, { statusKey } from './states';
|
||||
import states, { saveStatus, statusKey } from './states';
|
||||
import store from './store';
|
||||
|
||||
export function groupBoosts(values) {
|
||||
|
@ -81,7 +83,7 @@ export function dedupeBoosts(items, instance) {
|
|||
return filteredItems;
|
||||
}
|
||||
|
||||
export function groupContext(items) {
|
||||
export function groupContext(items, instance) {
|
||||
const contexts = [];
|
||||
let contextIndex = 0;
|
||||
items.forEach((item) => {
|
||||
|
@ -173,12 +175,44 @@ export function groupContext(items) {
|
|||
return;
|
||||
}
|
||||
}
|
||||
|
||||
if (item.inReplyToId && item.inReplyToAccountId !== item.account.id) {
|
||||
const sKey = statusKey(item.id, instance);
|
||||
if (!states.statusReply[sKey]) {
|
||||
// If it's a reply and not a thread
|
||||
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);
|
||||
});
|
||||
|
||||
return newItems;
|
||||
}
|
||||
|
||||
const fetchStatus = pmem((statusID, masto) => {
|
||||
return masto.v1.statuses.$select(statusID).fetch();
|
||||
});
|
||||
|
||||
export async function assignFollowedTags(items, instance) {
|
||||
const followedTags = await getFollowedTags(); // [{name: 'tag'}, {...}]
|
||||
if (!followedTags.length) return;
|
||||
|
@ -219,7 +253,7 @@ export async function assignFollowedTags(items, instance) {
|
|||
statusWithFollowedTags.forEach((s) => {
|
||||
const { item, sKey, followedTags } = s;
|
||||
const r = relationships[item.account.id];
|
||||
if (!r.following) {
|
||||
if (r && !r.following) {
|
||||
statusFollowedTags[sKey] = followedTags;
|
||||
}
|
||||
});
|
||||
|
|
Loading…
Reference in a new issue