mirror of
https://github.com/cheeaun/phanpy.git
synced 2024-11-28 20:28:52 +03:00
Replace preact-router with react-router
Need more routing powers, hopefully things don't break 🤞
This commit is contained in:
parent
baf139762c
commit
9bff95bcec
15 changed files with 662 additions and 362 deletions
|
@ -62,6 +62,7 @@ Prerequisites: Node.js 18+
|
||||||
- [Vite](https://vitejs.dev/) - Build tool
|
- [Vite](https://vitejs.dev/) - Build tool
|
||||||
- [Preact](https://preactjs.com/) - UI library
|
- [Preact](https://preactjs.com/) - UI library
|
||||||
- [Valtio](https://valtio.pmnd.rs/) - State management
|
- [Valtio](https://valtio.pmnd.rs/) - State management
|
||||||
|
- [React Router](https://reactrouter.com/) - Routing
|
||||||
- [masto.js](https://github.com/neet/masto.js/) - Mastodon API client
|
- [masto.js](https://github.com/neet/masto.js/) - Mastodon API client
|
||||||
- [Iconify](https://iconify.design/) - Icon library
|
- [Iconify](https://iconify.design/) - Icon library
|
||||||
- Vanilla CSS - *Yes, I'm old school.*
|
- Vanilla CSS - *Yes, I'm old school.*
|
||||||
|
|
101
package-lock.json
generated
101
package-lock.json
generated
|
@ -14,15 +14,14 @@
|
||||||
"dayjs-twitter": "~0.5.0",
|
"dayjs-twitter": "~0.5.0",
|
||||||
"fast-blurhash": "~1.1.2",
|
"fast-blurhash": "~1.1.2",
|
||||||
"fast-deep-equal": "~3.1.3",
|
"fast-deep-equal": "~3.1.3",
|
||||||
"history": "~5.3.0",
|
|
||||||
"idb-keyval": "~6.2.0",
|
"idb-keyval": "~6.2.0",
|
||||||
"just-debounce-it": "~3.2.0",
|
"just-debounce-it": "~3.2.0",
|
||||||
"masto": "~5.5.0",
|
"masto": "~5.5.0",
|
||||||
"mem": "~9.0.2",
|
"mem": "~9.0.2",
|
||||||
"preact": "~10.11.3",
|
"preact": "~10.11.3",
|
||||||
"preact-router": "~4.1.0",
|
|
||||||
"react-hotkeys-hook": "~4.3.2",
|
"react-hotkeys-hook": "~4.3.2",
|
||||||
"react-intersection-observer": "~9.4.1",
|
"react-intersection-observer": "~9.4.1",
|
||||||
|
"react-router-dom": "~6.7.0",
|
||||||
"string-length": "~5.0.1",
|
"string-length": "~5.0.1",
|
||||||
"swiped-events": "~1.1.7",
|
"swiped-events": "~1.1.7",
|
||||||
"toastify-js": "~1.12.0",
|
"toastify-js": "~1.12.0",
|
||||||
|
@ -1653,6 +1652,7 @@
|
||||||
"version": "7.20.6",
|
"version": "7.20.6",
|
||||||
"resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.20.6.tgz",
|
"resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.20.6.tgz",
|
||||||
"integrity": "sha512-Q+8MqP7TiHMWzSfwiJwXCjyf4GYA4Dgw3emg/7xmwsdLJOZUp+nMqcOwOzzYheuM1rhDu8FSj2l0aoMygEuXuA==",
|
"integrity": "sha512-Q+8MqP7TiHMWzSfwiJwXCjyf4GYA4Dgw3emg/7xmwsdLJOZUp+nMqcOwOzzYheuM1rhDu8FSj2l0aoMygEuXuA==",
|
||||||
|
"dev": true,
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"regenerator-runtime": "^0.13.11"
|
"regenerator-runtime": "^0.13.11"
|
||||||
},
|
},
|
||||||
|
@ -2276,6 +2276,14 @@
|
||||||
"vite": ">=2.0.0-beta.3"
|
"vite": ">=2.0.0-beta.3"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/@remix-run/router": {
|
||||||
|
"version": "1.3.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/@remix-run/router/-/router-1.3.0.tgz",
|
||||||
|
"integrity": "sha512-nwQoYb3m4DDpHTeOwpJEuDt8lWVcujhYYSFGLluC+9es2PyLjm+jjq3IeRBQbwBtPLJE/lkuHuGHr8uQLgmJRA==",
|
||||||
|
"engines": {
|
||||||
|
"node": ">=14"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/@rollup/plugin-replace": {
|
"node_modules/@rollup/plugin-replace": {
|
||||||
"version": "5.0.2",
|
"version": "5.0.2",
|
||||||
"resolved": "https://registry.npmjs.org/@rollup/plugin-replace/-/plugin-replace-5.0.2.tgz",
|
"resolved": "https://registry.npmjs.org/@rollup/plugin-replace/-/plugin-replace-5.0.2.tgz",
|
||||||
|
@ -3607,14 +3615,6 @@
|
||||||
"tslib": "^2.0.3"
|
"tslib": "^2.0.3"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/history": {
|
|
||||||
"version": "5.3.0",
|
|
||||||
"resolved": "https://registry.npmjs.org/history/-/history-5.3.0.tgz",
|
|
||||||
"integrity": "sha512-ZqaKwjjrAYUYfLG+htGaIIZ4nioX2L70ZUMIFysS3xvBsSG4x/n1V6TXV3N8ZYNuFGlDirFg32T7B6WOUPDYcQ==",
|
|
||||||
"dependencies": {
|
|
||||||
"@babel/runtime": "^7.7.6"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"node_modules/idb": {
|
"node_modules/idb": {
|
||||||
"version": "7.1.1",
|
"version": "7.1.1",
|
||||||
"resolved": "https://registry.npmjs.org/idb/-/idb-7.1.1.tgz",
|
"resolved": "https://registry.npmjs.org/idb/-/idb-7.1.1.tgz",
|
||||||
|
@ -4537,14 +4537,6 @@
|
||||||
"url": "https://opencollective.com/preact"
|
"url": "https://opencollective.com/preact"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/preact-router": {
|
|
||||||
"version": "4.1.0",
|
|
||||||
"resolved": "https://registry.npmjs.org/preact-router/-/preact-router-4.1.0.tgz",
|
|
||||||
"integrity": "sha512-y1w2YvVpKAju9FMV+fAVR1NpH4MW5q07BZrziMZeg6F/rGJ9KvLUZtjOqsy2I8fDYiX36AM1AQTXIIK3jigBhA==",
|
|
||||||
"peerDependencies": {
|
|
||||||
"preact": ">=10"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"node_modules/prettier": {
|
"node_modules/prettier": {
|
||||||
"version": "2.8.0",
|
"version": "2.8.0",
|
||||||
"resolved": "https://registry.npmjs.org/prettier/-/prettier-2.8.0.tgz",
|
"resolved": "https://registry.npmjs.org/prettier/-/prettier-2.8.0.tgz",
|
||||||
|
@ -4672,6 +4664,36 @@
|
||||||
"react": "^15.0.0 || ^16.0.0 || ^17.0.0 || ^18.0.0"
|
"react": "^15.0.0 || ^16.0.0 || ^17.0.0 || ^18.0.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/react-router": {
|
||||||
|
"version": "6.7.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/react-router/-/react-router-6.7.0.tgz",
|
||||||
|
"integrity": "sha512-KNWlG622ddq29MAM159uUsNMdbX8USruoKnwMMQcs/QWZgFUayICSn2oB7reHce1zPj6CG18kfkZIunSSRyGHg==",
|
||||||
|
"dependencies": {
|
||||||
|
"@remix-run/router": "1.3.0"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=14"
|
||||||
|
},
|
||||||
|
"peerDependencies": {
|
||||||
|
"react": ">=16.8"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/react-router-dom": {
|
||||||
|
"version": "6.7.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/react-router-dom/-/react-router-dom-6.7.0.tgz",
|
||||||
|
"integrity": "sha512-jQtXUJyhso3kFw430+0SPCbmCmY1/kJv8iRffGHwHy3CkoomGxeYzMkmeSPYo6Egzh3FKJZRAL22yg5p2tXtfg==",
|
||||||
|
"dependencies": {
|
||||||
|
"@remix-run/router": "1.3.0",
|
||||||
|
"react-router": "6.7.0"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=14"
|
||||||
|
},
|
||||||
|
"peerDependencies": {
|
||||||
|
"react": ">=16.8",
|
||||||
|
"react-dom": ">=16.8"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/regenerate": {
|
"node_modules/regenerate": {
|
||||||
"version": "1.4.2",
|
"version": "1.4.2",
|
||||||
"resolved": "https://registry.npmjs.org/regenerate/-/regenerate-1.4.2.tgz",
|
"resolved": "https://registry.npmjs.org/regenerate/-/regenerate-1.4.2.tgz",
|
||||||
|
@ -4693,7 +4715,8 @@
|
||||||
"node_modules/regenerator-runtime": {
|
"node_modules/regenerator-runtime": {
|
||||||
"version": "0.13.11",
|
"version": "0.13.11",
|
||||||
"resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.13.11.tgz",
|
"resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.13.11.tgz",
|
||||||
"integrity": "sha512-kY1AZVr2Ra+t+piVaJ4gxaFaReZVH40AKNo7UCX6W+dEwBo/2oZJzqfuN1qLq1oL45o56cPaTXELwrTh8Fpggg=="
|
"integrity": "sha512-kY1AZVr2Ra+t+piVaJ4gxaFaReZVH40AKNo7UCX6W+dEwBo/2oZJzqfuN1qLq1oL45o56cPaTXELwrTh8Fpggg==",
|
||||||
|
"dev": true
|
||||||
},
|
},
|
||||||
"node_modules/regenerator-transform": {
|
"node_modules/regenerator-transform": {
|
||||||
"version": "0.15.1",
|
"version": "0.15.1",
|
||||||
|
@ -7013,6 +7036,7 @@
|
||||||
"version": "7.20.6",
|
"version": "7.20.6",
|
||||||
"resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.20.6.tgz",
|
"resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.20.6.tgz",
|
||||||
"integrity": "sha512-Q+8MqP7TiHMWzSfwiJwXCjyf4GYA4Dgw3emg/7xmwsdLJOZUp+nMqcOwOzzYheuM1rhDu8FSj2l0aoMygEuXuA==",
|
"integrity": "sha512-Q+8MqP7TiHMWzSfwiJwXCjyf4GYA4Dgw3emg/7xmwsdLJOZUp+nMqcOwOzzYheuM1rhDu8FSj2l0aoMygEuXuA==",
|
||||||
|
"dev": true,
|
||||||
"requires": {
|
"requires": {
|
||||||
"regenerator-runtime": "^0.13.11"
|
"regenerator-runtime": "^0.13.11"
|
||||||
}
|
}
|
||||||
|
@ -7394,6 +7418,11 @@
|
||||||
"@rollup/pluginutils": "^4.1.0"
|
"@rollup/pluginutils": "^4.1.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"@remix-run/router": {
|
||||||
|
"version": "1.3.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/@remix-run/router/-/router-1.3.0.tgz",
|
||||||
|
"integrity": "sha512-nwQoYb3m4DDpHTeOwpJEuDt8lWVcujhYYSFGLluC+9es2PyLjm+jjq3IeRBQbwBtPLJE/lkuHuGHr8uQLgmJRA=="
|
||||||
|
},
|
||||||
"@rollup/plugin-replace": {
|
"@rollup/plugin-replace": {
|
||||||
"version": "5.0.2",
|
"version": "5.0.2",
|
||||||
"resolved": "https://registry.npmjs.org/@rollup/plugin-replace/-/plugin-replace-5.0.2.tgz",
|
"resolved": "https://registry.npmjs.org/@rollup/plugin-replace/-/plugin-replace-5.0.2.tgz",
|
||||||
|
@ -8413,14 +8442,6 @@
|
||||||
"tslib": "^2.0.3"
|
"tslib": "^2.0.3"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"history": {
|
|
||||||
"version": "5.3.0",
|
|
||||||
"resolved": "https://registry.npmjs.org/history/-/history-5.3.0.tgz",
|
|
||||||
"integrity": "sha512-ZqaKwjjrAYUYfLG+htGaIIZ4nioX2L70ZUMIFysS3xvBsSG4x/n1V6TXV3N8ZYNuFGlDirFg32T7B6WOUPDYcQ==",
|
|
||||||
"requires": {
|
|
||||||
"@babel/runtime": "^7.7.6"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"idb": {
|
"idb": {
|
||||||
"version": "7.1.1",
|
"version": "7.1.1",
|
||||||
"resolved": "https://registry.npmjs.org/idb/-/idb-7.1.1.tgz",
|
"resolved": "https://registry.npmjs.org/idb/-/idb-7.1.1.tgz",
|
||||||
|
@ -9097,12 +9118,6 @@
|
||||||
"resolved": "https://registry.npmjs.org/preact/-/preact-10.11.3.tgz",
|
"resolved": "https://registry.npmjs.org/preact/-/preact-10.11.3.tgz",
|
||||||
"integrity": "sha512-eY93IVpod/zG3uMF22Unl8h9KkrcKIRs2EGar8hwLZZDU1lkjph303V9HZBwufh2s736U6VXuhD109LYqPoffg=="
|
"integrity": "sha512-eY93IVpod/zG3uMF22Unl8h9KkrcKIRs2EGar8hwLZZDU1lkjph303V9HZBwufh2s736U6VXuhD109LYqPoffg=="
|
||||||
},
|
},
|
||||||
"preact-router": {
|
|
||||||
"version": "4.1.0",
|
|
||||||
"resolved": "https://registry.npmjs.org/preact-router/-/preact-router-4.1.0.tgz",
|
|
||||||
"integrity": "sha512-y1w2YvVpKAju9FMV+fAVR1NpH4MW5q07BZrziMZeg6F/rGJ9KvLUZtjOqsy2I8fDYiX36AM1AQTXIIK3jigBhA==",
|
|
||||||
"requires": {}
|
|
||||||
},
|
|
||||||
"prettier": {
|
"prettier": {
|
||||||
"version": "2.8.0",
|
"version": "2.8.0",
|
||||||
"resolved": "https://registry.npmjs.org/prettier/-/prettier-2.8.0.tgz",
|
"resolved": "https://registry.npmjs.org/prettier/-/prettier-2.8.0.tgz",
|
||||||
|
@ -9181,6 +9196,23 @@
|
||||||
"integrity": "sha512-IXpIsPe6BleFOEHKzKh5UjwRUaz/JYS0lT/HPsupWEQou2hDqjhLMStc5zyE3eQVT4Fk3FufM8Fw33qW1uyeiw==",
|
"integrity": "sha512-IXpIsPe6BleFOEHKzKh5UjwRUaz/JYS0lT/HPsupWEQou2hDqjhLMStc5zyE3eQVT4Fk3FufM8Fw33qW1uyeiw==",
|
||||||
"requires": {}
|
"requires": {}
|
||||||
},
|
},
|
||||||
|
"react-router": {
|
||||||
|
"version": "6.7.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/react-router/-/react-router-6.7.0.tgz",
|
||||||
|
"integrity": "sha512-KNWlG622ddq29MAM159uUsNMdbX8USruoKnwMMQcs/QWZgFUayICSn2oB7reHce1zPj6CG18kfkZIunSSRyGHg==",
|
||||||
|
"requires": {
|
||||||
|
"@remix-run/router": "1.3.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"react-router-dom": {
|
||||||
|
"version": "6.7.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/react-router-dom/-/react-router-dom-6.7.0.tgz",
|
||||||
|
"integrity": "sha512-jQtXUJyhso3kFw430+0SPCbmCmY1/kJv8iRffGHwHy3CkoomGxeYzMkmeSPYo6Egzh3FKJZRAL22yg5p2tXtfg==",
|
||||||
|
"requires": {
|
||||||
|
"@remix-run/router": "1.3.0",
|
||||||
|
"react-router": "6.7.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
"regenerate": {
|
"regenerate": {
|
||||||
"version": "1.4.2",
|
"version": "1.4.2",
|
||||||
"resolved": "https://registry.npmjs.org/regenerate/-/regenerate-1.4.2.tgz",
|
"resolved": "https://registry.npmjs.org/regenerate/-/regenerate-1.4.2.tgz",
|
||||||
|
@ -9199,7 +9231,8 @@
|
||||||
"regenerator-runtime": {
|
"regenerator-runtime": {
|
||||||
"version": "0.13.11",
|
"version": "0.13.11",
|
||||||
"resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.13.11.tgz",
|
"resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.13.11.tgz",
|
||||||
"integrity": "sha512-kY1AZVr2Ra+t+piVaJ4gxaFaReZVH40AKNo7UCX6W+dEwBo/2oZJzqfuN1qLq1oL45o56cPaTXELwrTh8Fpggg=="
|
"integrity": "sha512-kY1AZVr2Ra+t+piVaJ4gxaFaReZVH40AKNo7UCX6W+dEwBo/2oZJzqfuN1qLq1oL45o56cPaTXELwrTh8Fpggg==",
|
||||||
|
"dev": true
|
||||||
},
|
},
|
||||||
"regenerator-transform": {
|
"regenerator-transform": {
|
||||||
"version": "0.15.1",
|
"version": "0.15.1",
|
||||||
|
|
|
@ -16,15 +16,14 @@
|
||||||
"dayjs-twitter": "~0.5.0",
|
"dayjs-twitter": "~0.5.0",
|
||||||
"fast-blurhash": "~1.1.2",
|
"fast-blurhash": "~1.1.2",
|
||||||
"fast-deep-equal": "~3.1.3",
|
"fast-deep-equal": "~3.1.3",
|
||||||
"history": "~5.3.0",
|
|
||||||
"idb-keyval": "~6.2.0",
|
"idb-keyval": "~6.2.0",
|
||||||
"just-debounce-it": "~3.2.0",
|
"just-debounce-it": "~3.2.0",
|
||||||
"masto": "~5.5.0",
|
"masto": "~5.5.0",
|
||||||
"mem": "~9.0.2",
|
"mem": "~9.0.2",
|
||||||
"preact": "~10.11.3",
|
"preact": "~10.11.3",
|
||||||
"preact-router": "~4.1.0",
|
|
||||||
"react-hotkeys-hook": "~4.3.2",
|
"react-hotkeys-hook": "~4.3.2",
|
||||||
"react-intersection-observer": "~9.4.1",
|
"react-intersection-observer": "~9.4.1",
|
||||||
|
"react-router-dom": "~6.7.0",
|
||||||
"string-length": "~5.0.1",
|
"string-length": "~5.0.1",
|
||||||
"swiped-events": "~1.1.7",
|
"swiped-events": "~1.1.7",
|
||||||
"toastify-js": "~1.12.0",
|
"toastify-js": "~1.12.0",
|
||||||
|
|
88
src/app.css
88
src/app.css
|
@ -46,6 +46,7 @@ a[href^='http'][rel*='nofollow']:visited:not(:has(div)) {
|
||||||
transition: opacity 0.1s ease-in-out;
|
transition: opacity 0.1s ease-in-out;
|
||||||
overscroll-behavior: contain;
|
overscroll-behavior: contain;
|
||||||
scroll-behavior: smooth;
|
scroll-behavior: smooth;
|
||||||
|
background-color: var(--bg-color);
|
||||||
}
|
}
|
||||||
.deck-container[hidden] {
|
.deck-container[hidden] {
|
||||||
display: block;
|
display: block;
|
||||||
|
@ -61,6 +62,14 @@ a[href^='http'][rel*='nofollow']:visited:not(:has(div)) {
|
||||||
scroll-padding-top: 3em;
|
scroll-padding-top: 3em;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.deck-container {
|
||||||
|
transition: transform 0.4s var(--timing-function);
|
||||||
|
}
|
||||||
|
.deck-container:has(~ .deck-backdrop) {
|
||||||
|
transition: transform 0.4s ease-out;
|
||||||
|
transform: translate3d(-5vw, 0, 0);
|
||||||
|
}
|
||||||
|
|
||||||
.deck {
|
.deck {
|
||||||
min-height: 100vh;
|
min-height: 100vh;
|
||||||
min-height: 100dvh;
|
min-height: 100dvh;
|
||||||
|
@ -364,7 +373,7 @@ a[href^='http'][rel*='nofollow']:visited:not(:has(div)) {
|
||||||
-webkit-tap-highlight-color: transparent;
|
-webkit-tap-highlight-color: transparent;
|
||||||
animation: appear 0.2s ease-out;
|
animation: appear 0.2s ease-out;
|
||||||
}
|
}
|
||||||
.status-link:is(:hover, :focus) {
|
.status-link:is(:hover, :focus, .is-active) {
|
||||||
background-color: var(--link-bg-hover-color);
|
background-color: var(--link-bg-hover-color);
|
||||||
outline-offset: -2px;
|
outline-offset: -2px;
|
||||||
}
|
}
|
||||||
|
@ -508,11 +517,6 @@ a[href^='http'][rel*='nofollow']:visited:not(:has(div)) {
|
||||||
max-width: 40em;
|
max-width: 40em;
|
||||||
}
|
}
|
||||||
|
|
||||||
.decks {
|
|
||||||
flex-grow: 1;
|
|
||||||
width: 100%;
|
|
||||||
}
|
|
||||||
|
|
||||||
.deck-close {
|
.deck-close {
|
||||||
color: var(--text-insignificant-color) !important;
|
color: var(--text-insignificant-color) !important;
|
||||||
}
|
}
|
||||||
|
@ -944,21 +948,63 @@ meter.donut:is(.danger, .explode):after {
|
||||||
gap: 4px;
|
gap: 4px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.deck-container {
|
||||||
|
width: 100%;
|
||||||
|
flex-grow: 1;
|
||||||
|
}
|
||||||
|
#home-page ~ .deck-container {
|
||||||
|
z-index: 10;
|
||||||
|
position: fixed;
|
||||||
|
inset: 0;
|
||||||
|
}
|
||||||
|
#home-page:has(~ .deck-container) {
|
||||||
|
display: block;
|
||||||
|
position: absolute;
|
||||||
|
user-select: none;
|
||||||
|
pointer-events: none;
|
||||||
|
opacity: 0;
|
||||||
|
content-visibility: hidden;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* TAB BAR */
|
||||||
|
|
||||||
|
#tab-bar:not([hidden]) {
|
||||||
|
position: fixed;
|
||||||
|
bottom: 16px;
|
||||||
|
bottom: max(16px, env(safe-area-inset-bottom));
|
||||||
|
width: calc(100% - 32px);
|
||||||
|
max-width: calc(40em - 32px);
|
||||||
|
z-index: 100;
|
||||||
|
display: flex;
|
||||||
|
background-color: var(--bg-blur-color);
|
||||||
|
backdrop-filter: blur(16px) saturate(3);
|
||||||
|
border: var(--hairline-width) solid var(--outline-color);
|
||||||
|
border-radius: 16px;
|
||||||
|
box-shadow: 0 8px 32px var(--outline-color);
|
||||||
|
}
|
||||||
|
#tab-bar li {
|
||||||
|
flex-grow: 1;
|
||||||
|
margin: 0;
|
||||||
|
padding: 0;
|
||||||
|
list-style: none;
|
||||||
|
}
|
||||||
|
#tab-bar li a {
|
||||||
|
text-align: center;
|
||||||
|
padding: 16px 0;
|
||||||
|
display: block;
|
||||||
|
}
|
||||||
|
|
||||||
@media (min-width: 40em) {
|
@media (min-width: 40em) {
|
||||||
html,
|
html,
|
||||||
body {
|
body {
|
||||||
background-color: var(--bg-faded-color);
|
background-color: var(--bg-faded-color);
|
||||||
}
|
}
|
||||||
|
.deck-container {
|
||||||
|
background-color: var(--bg-faded-color);
|
||||||
|
}
|
||||||
#app {
|
#app {
|
||||||
display: flex;
|
display: flex;
|
||||||
}
|
}
|
||||||
.decks {
|
|
||||||
transition: transform 0.4s var(--timing-function);
|
|
||||||
}
|
|
||||||
.decks:has(~ .deck-backdrop) {
|
|
||||||
transition: transform 0.4s ease-out;
|
|
||||||
transform: translate3d(-5vw, 0, 0);
|
|
||||||
}
|
|
||||||
.deck-backdrop .deck {
|
.deck-backdrop .deck {
|
||||||
width: 50%;
|
width: 50%;
|
||||||
min-width: 40em;
|
min-width: 40em;
|
||||||
|
@ -995,6 +1041,22 @@ meter.donut:is(.danger, .explode):after {
|
||||||
border-radius: 16px;
|
border-radius: 16px;
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
box-shadow: 0px 1px var(--bg-blur-color);
|
box-shadow: 0px 1px var(--bg-blur-color);
|
||||||
|
transition: transform 0.4s var(--timing-function);
|
||||||
|
--back-transition: transform 0.4s ease-out;
|
||||||
|
}
|
||||||
|
.timeline-deck .timeline:not(.flat) > li:has(.status-link.is-active) {
|
||||||
|
transition: var(--back-transition);
|
||||||
|
transform: translate3d(-2.5vw, 0, 0);
|
||||||
|
}
|
||||||
|
.timeline-deck
|
||||||
|
.timeline:not(.flat)
|
||||||
|
> li:not(:has(.boost-carousel)):has(+ li .status-link.is-active),
|
||||||
|
.timeline-deck
|
||||||
|
.timeline:not(.flat)
|
||||||
|
> li:not(:has(.boost-carousel)):has(.status-link.is-active)
|
||||||
|
+ li {
|
||||||
|
transition: var(--back-transition);
|
||||||
|
transform: translate3d(-1.25vw, 0, 0);
|
||||||
}
|
}
|
||||||
.box {
|
.box {
|
||||||
padding: 32px;
|
padding: 32px;
|
||||||
|
|
120
src/app.jsx
120
src/app.jsx
|
@ -1,19 +1,21 @@
|
||||||
import './app.css';
|
import './app.css';
|
||||||
import 'toastify-js/src/toastify.css';
|
import 'toastify-js/src/toastify.css';
|
||||||
|
|
||||||
import { createHashHistory } from 'history';
|
|
||||||
import debounce from 'just-debounce-it';
|
import debounce from 'just-debounce-it';
|
||||||
import { login } from 'masto';
|
import { login } from 'masto';
|
||||||
import Router, { route } from 'preact-router';
|
import { useEffect, useLayoutEffect, useMemo, useState } from 'preact/hooks';
|
||||||
import { useEffect, useLayoutEffect, useState } from 'preact/hooks';
|
import { Route, Routes, useLocation, useNavigate } from 'react-router-dom';
|
||||||
import Toastify from 'toastify-js';
|
import Toastify from 'toastify-js';
|
||||||
import { useSnapshot } from 'valtio';
|
import { useSnapshot } from 'valtio';
|
||||||
|
|
||||||
import Account from './components/account';
|
import Account from './components/account';
|
||||||
import Compose from './components/compose';
|
import Compose from './components/compose';
|
||||||
import Drafts from './components/drafts';
|
import Drafts from './components/drafts';
|
||||||
|
import Icon from './components/icon';
|
||||||
|
import Link from './components/link';
|
||||||
import Loader from './components/loader';
|
import Loader from './components/loader';
|
||||||
import Modal from './components/modal';
|
import Modal from './components/modal';
|
||||||
|
import Bookmarks from './pages/bookmarks';
|
||||||
import Home from './pages/home';
|
import Home from './pages/home';
|
||||||
import Login from './pages/login';
|
import Login from './pages/login';
|
||||||
import Notifications from './pages/notifications';
|
import Notifications from './pages/notifications';
|
||||||
|
@ -24,14 +26,13 @@ import { getAccessToken } from './utils/auth';
|
||||||
import states, { saveStatus } from './utils/states';
|
import states, { saveStatus } from './utils/states';
|
||||||
import store from './utils/store';
|
import store from './utils/store';
|
||||||
|
|
||||||
const { VITE_CLIENT_NAME: CLIENT_NAME } = import.meta.env;
|
|
||||||
|
|
||||||
window.__STATES__ = states;
|
window.__STATES__ = states;
|
||||||
|
|
||||||
function App() {
|
function App() {
|
||||||
const snapStates = useSnapshot(states);
|
const snapStates = useSnapshot(states);
|
||||||
const [isLoggedIn, setIsLoggedIn] = useState(false);
|
const [isLoggedIn, setIsLoggedIn] = useState(false);
|
||||||
const [uiState, setUIState] = useState('loading');
|
const [uiState, setUIState] = useState('loading');
|
||||||
|
const navigate = useNavigate();
|
||||||
|
|
||||||
useLayoutEffect(() => {
|
useLayoutEffect(() => {
|
||||||
const theme = store.local.get('theme');
|
const theme = store.local.get('theme');
|
||||||
|
@ -126,20 +127,22 @@ function App() {
|
||||||
}
|
}
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
const [currentDeck, setCurrentDeck] = useState('home');
|
let location = useLocation();
|
||||||
const [currentModal, setCurrentModal] = useState(null);
|
const locationDeckMap = {
|
||||||
|
'/': 'home-page',
|
||||||
|
'/notifications': 'notifications-page',
|
||||||
|
};
|
||||||
const focusDeck = () => {
|
const focusDeck = () => {
|
||||||
if (currentModal) return;
|
|
||||||
let timer = setTimeout(() => {
|
let timer = setTimeout(() => {
|
||||||
const page = document.getElementById(`${currentDeck}-page`);
|
const page = document.getElementById(locationDeckMap[location.pathname]);
|
||||||
console.debug('FOCUS', currentDeck, page);
|
console.debug('FOCUS', location.pathname, page);
|
||||||
if (page) {
|
if (page) {
|
||||||
page.focus();
|
page.focus();
|
||||||
}
|
}
|
||||||
}, 100);
|
}, 100);
|
||||||
return () => clearTimeout(timer);
|
return () => clearTimeout(timer);
|
||||||
};
|
};
|
||||||
useEffect(focusDeck, [currentDeck, currentModal]);
|
useEffect(focusDeck, [location]);
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (
|
if (
|
||||||
!snapStates.showCompose &&
|
!snapStates.showCompose &&
|
||||||
|
@ -173,44 +176,66 @@ function App() {
|
||||||
}
|
}
|
||||||
}, [isLoggedIn]);
|
}, [isLoggedIn]);
|
||||||
|
|
||||||
|
const backgroundLocation = useMemo(() => {
|
||||||
|
const { prevLocation } = snapStates;
|
||||||
|
|
||||||
|
console.debug({ location, prevLocation });
|
||||||
|
const { pathname } = location;
|
||||||
|
const { pathname: prevPathname } = prevLocation || {};
|
||||||
|
console.debug({ prevPathname, pathname });
|
||||||
|
const isModalPage = /^\/s\//i.test(pathname);
|
||||||
|
return isModalPage ? prevLocation : null;
|
||||||
|
}, [location]);
|
||||||
|
|
||||||
|
const nonRootLocation = useMemo(() => {
|
||||||
|
const { pathname } = location;
|
||||||
|
return !/\/(login|welcome)$/.test(pathname);
|
||||||
|
}, [location]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
{isLoggedIn && currentDeck && (
|
<Routes location={nonRootLocation || location}>
|
||||||
<div class="decks">
|
<Route
|
||||||
{/* Home will never be unmounted */}
|
path="/"
|
||||||
<Home hidden={currentDeck !== 'home'} />
|
element={
|
||||||
{/* Notifications can be unmounted */}
|
isLoggedIn ? (
|
||||||
{currentDeck === 'notifications' && <Notifications />}
|
<Home />
|
||||||
</div>
|
) : uiState === 'loading' ? (
|
||||||
)}
|
<Loader />
|
||||||
{!isLoggedIn && uiState === 'loading' && <Loader />}
|
) : (
|
||||||
<Router
|
<Welcome />
|
||||||
history={createHashHistory()}
|
)
|
||||||
onChange={(e) => {
|
|
||||||
console.debug('ROUTER onChange', e);
|
|
||||||
// Special handling for Home and Notifications
|
|
||||||
const { url } = e;
|
|
||||||
if (/notifications/i.test(url)) {
|
|
||||||
setCurrentDeck('notifications');
|
|
||||||
setCurrentModal(null);
|
|
||||||
} else if (url === '/') {
|
|
||||||
setCurrentDeck('home');
|
|
||||||
document.title = `Home / ${CLIENT_NAME}`;
|
|
||||||
setCurrentModal(null);
|
|
||||||
} else if (/^\/s\//i.test(url)) {
|
|
||||||
setCurrentModal('status');
|
|
||||||
} else {
|
|
||||||
setCurrentModal(null);
|
|
||||||
setCurrentDeck(null);
|
|
||||||
}
|
}
|
||||||
states.history.push(url);
|
/>
|
||||||
}}
|
<Route path="/login" element={<Login />} />
|
||||||
>
|
<Route path="/welcome" element={<Welcome />} />
|
||||||
{!isLoggedIn && uiState !== 'loading' && <Welcome path="/" />}
|
</Routes>
|
||||||
<Welcome path="/welcome" />
|
<Routes location={backgroundLocation || location}>
|
||||||
{isLoggedIn && <Status path="/s/:id" />}
|
{isLoggedIn && (
|
||||||
<Login path="/login" />
|
<Route path="/notifications" element={<Notifications />} />
|
||||||
</Router>
|
)}
|
||||||
|
{isLoggedIn && <Route path="/bookmarks" element={<Bookmarks />} />}
|
||||||
|
</Routes>
|
||||||
|
<Routes>
|
||||||
|
{isLoggedIn && <Route path="/s/:id" element={<Status />} />}
|
||||||
|
</Routes>
|
||||||
|
<nav id="tab-bar" hidden>
|
||||||
|
<li>
|
||||||
|
<Link to="/">
|
||||||
|
<Icon icon="home" alt="Home" size="xl" />
|
||||||
|
</Link>
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
<Link to="/notifications">
|
||||||
|
<Icon icon="notification" alt="Notifications" size="xl" />
|
||||||
|
</Link>
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
<Link to="/bookmarks">
|
||||||
|
<Icon icon="bookmark" alt="Bookmarks" size="xl" />
|
||||||
|
</Link>
|
||||||
|
</li>
|
||||||
|
</nav>
|
||||||
{!!snapStates.showCompose && (
|
{!!snapStates.showCompose && (
|
||||||
<Modal>
|
<Modal>
|
||||||
<Compose
|
<Compose
|
||||||
|
@ -244,7 +269,8 @@ function App() {
|
||||||
// destination: `/#/s/${newStatus.id}`,
|
// destination: `/#/s/${newStatus.id}`,
|
||||||
onClick: () => {
|
onClick: () => {
|
||||||
toast.hideToast();
|
toast.hideToast();
|
||||||
route(`/s/${newStatus.id}`);
|
states.prevLocation = location;
|
||||||
|
navigate(`/s/${newStatus.id}`);
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
toast.showToast();
|
toast.showToast();
|
||||||
|
|
30
src/components/link.jsx
Normal file
30
src/components/link.jsx
Normal file
|
@ -0,0 +1,30 @@
|
||||||
|
import { useLocation } from 'react-router-dom';
|
||||||
|
|
||||||
|
import states from '../utils/states';
|
||||||
|
|
||||||
|
/* NOTES
|
||||||
|
=====
|
||||||
|
Initially this uses <NavLink> from react-router-dom, but it doesn't work:
|
||||||
|
1. It interferes with nested <a> inside <a> and it's difficult to preventDefault/stopPropagation from the nested <a>
|
||||||
|
2. isActive doesn't work properly with the weird routes that's set up in this app, due to the faux "location" to make the modals work and prevent unmounting
|
||||||
|
3. Not using <Link state/> because it modifies history.state that *persists* across page reloads. I don't need that, so using valtio's states instead.
|
||||||
|
*/
|
||||||
|
|
||||||
|
const Link = (props) => {
|
||||||
|
const routerLocation = useLocation();
|
||||||
|
let hash = (location.hash || '').replace(/^#/, '').trim();
|
||||||
|
if (hash === '') hash = '/';
|
||||||
|
const isActive = hash === props.to;
|
||||||
|
return (
|
||||||
|
<a
|
||||||
|
href={`#${props.to}`}
|
||||||
|
{...props}
|
||||||
|
class={`${props.class || ''} ${isActive ? 'is-active' : ''}`}
|
||||||
|
onClick={() => {
|
||||||
|
states.prevLocation = routerLocation;
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default Link;
|
|
@ -29,6 +29,7 @@ import visibilityIconsMap from '../utils/visibility-icons-map';
|
||||||
|
|
||||||
import Avatar from './avatar';
|
import Avatar from './avatar';
|
||||||
import Icon from './icon';
|
import Icon from './icon';
|
||||||
|
import Link from './link';
|
||||||
import RelativeTime from './relative-time';
|
import RelativeTime from './relative-time';
|
||||||
|
|
||||||
function fetchAccount(id) {
|
function fetchAccount(id) {
|
||||||
|
@ -251,18 +252,14 @@ function Status({
|
||||||
{/* </span> */}{' '}
|
{/* </span> */}{' '}
|
||||||
{size !== 'l' &&
|
{size !== 'l' &&
|
||||||
(uri ? (
|
(uri ? (
|
||||||
<a
|
<Link to={`/s/${id}`} class="time">
|
||||||
href={`#/s/${id}
|
|
||||||
`}
|
|
||||||
class="time"
|
|
||||||
>
|
|
||||||
<Icon
|
<Icon
|
||||||
icon={visibilityIconsMap[visibility]}
|
icon={visibilityIconsMap[visibility]}
|
||||||
alt={visibility}
|
alt={visibility}
|
||||||
size="s"
|
size="s"
|
||||||
/>{' '}
|
/>{' '}
|
||||||
<RelativeTime datetime={createdAtDate} format="micro" />
|
<RelativeTime datetime={createdAtDate} format="micro" />
|
||||||
</a>
|
</Link>
|
||||||
) : (
|
) : (
|
||||||
<span class="time">
|
<span class="time">
|
||||||
<Icon
|
<Icon
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
import './index.css';
|
import './index.css';
|
||||||
|
|
||||||
import { render } from 'preact';
|
import { render } from 'preact';
|
||||||
|
import { HashRouter } from 'react-router-dom';
|
||||||
|
|
||||||
import { App } from './app';
|
import { App } from './app';
|
||||||
|
|
||||||
|
@ -8,7 +9,12 @@ if (import.meta.env.DEV) {
|
||||||
import('preact/debug');
|
import('preact/debug');
|
||||||
}
|
}
|
||||||
|
|
||||||
render(<App />, document.getElementById('app'));
|
render(
|
||||||
|
<HashRouter>
|
||||||
|
<App />
|
||||||
|
</HashRouter>,
|
||||||
|
document.getElementById('app'),
|
||||||
|
);
|
||||||
|
|
||||||
// Clean up iconify localStorage
|
// Clean up iconify localStorage
|
||||||
// TODO: Remove this after few weeks?
|
// TODO: Remove this after few weeks?
|
||||||
|
|
144
src/pages/bookmarks.jsx
Normal file
144
src/pages/bookmarks.jsx
Normal file
|
@ -0,0 +1,144 @@
|
||||||
|
import { useEffect, useRef, useState } from 'preact/hooks';
|
||||||
|
|
||||||
|
import Icon from '../components/Icon';
|
||||||
|
import Link from '../components/link';
|
||||||
|
import Loader from '../components/Loader';
|
||||||
|
import Status from '../components/status';
|
||||||
|
import { saveStatus } from '../utils/states';
|
||||||
|
import useTitle from '../utils/useTitle';
|
||||||
|
|
||||||
|
const LIMIT = 40;
|
||||||
|
|
||||||
|
function Bookmarks() {
|
||||||
|
useTitle('Bookmarks');
|
||||||
|
const [bookmarks, setBookmarks] = useState([]);
|
||||||
|
const [uiState, setUIState] = useState('default');
|
||||||
|
const [showMore, setShowMore] = useState(false);
|
||||||
|
|
||||||
|
const bookmarksIterator = useRef(masto.v1.bookmarks.list({ limit: LIMIT }));
|
||||||
|
async function fetchBookmarks(firstLoad) {
|
||||||
|
console.log('fetchBookmarks', firstLoad);
|
||||||
|
if (firstLoad) {
|
||||||
|
bookmarksIterator.current = masto.v1.bookmarks.list({ limit: LIMIT });
|
||||||
|
}
|
||||||
|
const allBookmarks = await bookmarksIterator.current.next();
|
||||||
|
if (allBookmarks.value?.length) {
|
||||||
|
const bookmarksValue = allBookmarks.value.map((status) => {
|
||||||
|
saveStatus(status, {
|
||||||
|
skipThreading: true,
|
||||||
|
override: false,
|
||||||
|
});
|
||||||
|
return status;
|
||||||
|
});
|
||||||
|
if (firstLoad) {
|
||||||
|
setBookmarks(bookmarksValue);
|
||||||
|
} else {
|
||||||
|
setBookmarks([...bookmarks, ...bookmarksValue]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return allBookmarks;
|
||||||
|
}
|
||||||
|
|
||||||
|
const loadBookmarks = (firstLoad) => {
|
||||||
|
setUIState('loading');
|
||||||
|
(async () => {
|
||||||
|
try {
|
||||||
|
console.log('loadBookmarks', firstLoad);
|
||||||
|
const { done } = await fetchBookmarks(firstLoad);
|
||||||
|
console.log('loadBookmarks', firstLoad);
|
||||||
|
setShowMore(!done);
|
||||||
|
setUIState('default');
|
||||||
|
} catch (e) {
|
||||||
|
console.error(e);
|
||||||
|
setUIState('error');
|
||||||
|
}
|
||||||
|
})();
|
||||||
|
};
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
loadBookmarks(true);
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
const scrollableRef = useRef(null);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div
|
||||||
|
id="bookmarks-page"
|
||||||
|
class="deck-container"
|
||||||
|
ref={scrollableRef}
|
||||||
|
tabIndex="-1"
|
||||||
|
>
|
||||||
|
<div class="timeline-deck deck">
|
||||||
|
<header
|
||||||
|
onClick={(e) => {
|
||||||
|
if (e.target === e.currentTarget) {
|
||||||
|
scrollableRef.current?.scrollTo({
|
||||||
|
top: 0,
|
||||||
|
behavior: 'smooth',
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<div class="header-side">
|
||||||
|
<Link to="/" class="button plain">
|
||||||
|
<Icon icon="home" size="l" />
|
||||||
|
</Link>
|
||||||
|
</div>
|
||||||
|
<h1>Bookmarks</h1>
|
||||||
|
<div class="header-side"></div>{' '}
|
||||||
|
</header>
|
||||||
|
{!!bookmarks.length ? (
|
||||||
|
<>
|
||||||
|
<ul class="timeline">
|
||||||
|
{bookmarks.map((status) => (
|
||||||
|
<li key={`bookmark-${status.id}`}>
|
||||||
|
<Link class="status-link" to={`/s/${status.id}`}>
|
||||||
|
<Status status={status} />
|
||||||
|
</Link>
|
||||||
|
</li>
|
||||||
|
))}
|
||||||
|
</ul>
|
||||||
|
|
||||||
|
{showMore && (
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
class="plain block"
|
||||||
|
disabled={uiState === 'loading'}
|
||||||
|
onClick={() => loadBookmarks()}
|
||||||
|
style={{ marginBlockEnd: '6em' }}
|
||||||
|
>
|
||||||
|
{uiState === 'loading' ? <Loader /> : <>Show more…</>}
|
||||||
|
</button>
|
||||||
|
)}
|
||||||
|
</>
|
||||||
|
) : (
|
||||||
|
uiState !== 'loading' && (
|
||||||
|
<p class="ui-state">No bookmarks yet. Go bookmark something!</p>
|
||||||
|
)
|
||||||
|
)}
|
||||||
|
{uiState === 'loading' ? (
|
||||||
|
<div class="ui-state">
|
||||||
|
<Loader />
|
||||||
|
</div>
|
||||||
|
) : uiState === 'error' ? (
|
||||||
|
<p class="ui-state">
|
||||||
|
Unable to load bookmarks.
|
||||||
|
<br />
|
||||||
|
<br />
|
||||||
|
<button
|
||||||
|
class="button plain"
|
||||||
|
onClick={() => loadBookmarks(!bookmarks.length)}
|
||||||
|
>
|
||||||
|
Try again
|
||||||
|
</button>
|
||||||
|
</p>
|
||||||
|
) : (
|
||||||
|
bookmarks.length &&
|
||||||
|
!showMore && <p class="ui-state insignificant">The end.</p>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export default Bookmarks;
|
|
@ -1,10 +1,10 @@
|
||||||
import { Link } from 'preact-router/match';
|
|
||||||
import { memo } from 'preact/compat';
|
import { memo } from 'preact/compat';
|
||||||
import { useEffect, useRef, useState } from 'preact/hooks';
|
import { useEffect, useRef, useState } from 'preact/hooks';
|
||||||
import { useHotkeys } from 'react-hotkeys-hook';
|
import { useHotkeys } from 'react-hotkeys-hook';
|
||||||
import { useSnapshot } from 'valtio';
|
import { useSnapshot } from 'valtio';
|
||||||
|
|
||||||
import Icon from '../components/icon';
|
import Icon from '../components/icon';
|
||||||
|
import Link from '../components/link';
|
||||||
import Loader from '../components/loader';
|
import Loader from '../components/loader';
|
||||||
import Status from '../components/status';
|
import Status from '../components/status';
|
||||||
import db from '../utils/db';
|
import db from '../utils/db';
|
||||||
|
@ -36,9 +36,7 @@ function Home({ hidden }) {
|
||||||
states.homeNew = [];
|
states.homeNew = [];
|
||||||
}
|
}
|
||||||
const allStatuses = await homeIterator.current.next();
|
const allStatuses = await homeIterator.current.next();
|
||||||
if (allStatuses.value <= 0) {
|
if (allStatuses.value?.length) {
|
||||||
return { done: true };
|
|
||||||
}
|
|
||||||
const homeValues = allStatuses.value.map((status) => {
|
const homeValues = allStatuses.value.map((status) => {
|
||||||
saveStatus(status);
|
saveStatus(status);
|
||||||
return {
|
return {
|
||||||
|
@ -107,11 +105,10 @@ function Home({ hidden }) {
|
||||||
states.home.push(...homeValues);
|
states.home.push(...homeValues);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
states.homeLastFetchTime = Date.now();
|
states.homeLastFetchTime = Date.now();
|
||||||
return {
|
return allStatuses;
|
||||||
done: false,
|
|
||||||
};
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const loadingStatuses = useRef(false);
|
const loadingStatuses = useRef(false);
|
||||||
|
@ -276,6 +273,7 @@ function Home({ hidden }) {
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
<>
|
||||||
<div
|
<div
|
||||||
id="home-page"
|
id="home-page"
|
||||||
class="deck-container"
|
class="deck-container"
|
||||||
|
@ -283,24 +281,6 @@ function Home({ hidden }) {
|
||||||
ref={scrollableRef}
|
ref={scrollableRef}
|
||||||
tabIndex="-1"
|
tabIndex="-1"
|
||||||
>
|
>
|
||||||
<button
|
|
||||||
hidden={scrollDirection === 'end' && !nearReachStart}
|
|
||||||
type="button"
|
|
||||||
id="compose-button"
|
|
||||||
onClick={(e) => {
|
|
||||||
if (e.shiftKey) {
|
|
||||||
const newWin = openCompose();
|
|
||||||
if (!newWin) {
|
|
||||||
alert('Looks like your browser is blocking popups.');
|
|
||||||
states.showCompose = true;
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
states.showCompose = true;
|
|
||||||
}
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<Icon icon="quill" size="xxl" alt="Compose" />
|
|
||||||
</button>
|
|
||||||
<div class="timeline-deck deck">
|
<div class="timeline-deck deck">
|
||||||
<header
|
<header
|
||||||
hidden={scrollDirection === 'end' && !nearReachStart}
|
hidden={scrollDirection === 'end' && !nearReachStart}
|
||||||
|
@ -327,8 +307,8 @@ function Home({ hidden }) {
|
||||||
<h1>Home</h1>
|
<h1>Home</h1>
|
||||||
<div class="header-side">
|
<div class="header-side">
|
||||||
<Loader hidden={uiState !== 'loading'} />{' '}
|
<Loader hidden={uiState !== 'loading'} />{' '}
|
||||||
<a
|
<Link
|
||||||
href="#/notifications"
|
to="/notifications"
|
||||||
class={`button plain ${
|
class={`button plain ${
|
||||||
snapStates.notificationsNew.length > 0 ? 'has-badge' : ''
|
snapStates.notificationsNew.length > 0 ? 'has-badge' : ''
|
||||||
}`}
|
}`}
|
||||||
|
@ -337,7 +317,7 @@ function Home({ hidden }) {
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<Icon icon="notification" size="l" alt="Notifications" />
|
<Icon icon="notification" size="l" alt="Notifications" />
|
||||||
</a>
|
</Link>
|
||||||
</div>
|
</div>
|
||||||
</header>
|
</header>
|
||||||
{snapStates.homeNew.length > 0 &&
|
{snapStates.homeNew.length > 0 &&
|
||||||
|
@ -380,11 +360,7 @@ function Home({ hidden }) {
|
||||||
}
|
}
|
||||||
return (
|
return (
|
||||||
<li key={statusID}>
|
<li key={statusID}>
|
||||||
<Link
|
<Link class="status-link" to={`/s/${actualStatusID}`}>
|
||||||
activeClassName="active"
|
|
||||||
class="status-link"
|
|
||||||
href={`#/s/${actualStatusID}`}
|
|
||||||
>
|
|
||||||
<Status statusID={statusID} />
|
<Status statusID={statusID} />
|
||||||
</Link>
|
</Link>
|
||||||
</li>
|
</li>
|
||||||
|
@ -452,6 +428,25 @@ function Home({ hidden }) {
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
<button
|
||||||
|
hidden={scrollDirection === 'end' && !nearReachStart}
|
||||||
|
type="button"
|
||||||
|
id="compose-button"
|
||||||
|
onClick={(e) => {
|
||||||
|
if (e.shiftKey) {
|
||||||
|
const newWin = openCompose();
|
||||||
|
if (!newWin) {
|
||||||
|
alert('Looks like your browser is blocking popups.');
|
||||||
|
states.showCompose = true;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
states.showCompose = true;
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<Icon icon="quill" size="xxl" alt="Compose" />
|
||||||
|
</button>
|
||||||
|
</>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -504,9 +499,9 @@ function BoostsCarousel({ boosts }) {
|
||||||
const actualStatusID = reblog || statusID;
|
const actualStatusID = reblog || statusID;
|
||||||
return (
|
return (
|
||||||
<li>
|
<li>
|
||||||
<a class="status-boost-link" href={`#/s/${actualStatusID}`}>
|
<Link class="status-boost-link" to={`/s/${actualStatusID}`}>
|
||||||
<Status statusID={statusID} size="s" />
|
<Status statusID={statusID} size="s" />
|
||||||
</a>
|
</Link>
|
||||||
</li>
|
</li>
|
||||||
);
|
);
|
||||||
})}
|
})}
|
||||||
|
|
|
@ -2,6 +2,7 @@ import './login.css';
|
||||||
|
|
||||||
import { useEffect, useRef, useState } from 'preact/hooks';
|
import { useEffect, useRef, useState } from 'preact/hooks';
|
||||||
|
|
||||||
|
import Link from '../components/link';
|
||||||
import Loader from '../components/loader';
|
import Loader from '../components/loader';
|
||||||
import instancesListURL from '../data/instances.json?url';
|
import instancesListURL from '../data/instances.json?url';
|
||||||
import { getAuthorizationURL, registerApplication } from '../utils/auth';
|
import { getAuthorizationURL, registerApplication } from '../utils/auth';
|
||||||
|
@ -111,7 +112,7 @@ function Login() {
|
||||||
</a>
|
</a>
|
||||||
</p>
|
</p>
|
||||||
<p>
|
<p>
|
||||||
<a href="/#">Go home</a>
|
<Link to="/">Go home</Link>
|
||||||
</p>
|
</p>
|
||||||
</form>
|
</form>
|
||||||
</main>
|
</main>
|
||||||
|
|
|
@ -1,17 +1,17 @@
|
||||||
import './notifications.css';
|
import './notifications.css';
|
||||||
|
|
||||||
import { Link } from 'preact-router/match';
|
|
||||||
import { memo } from 'preact/compat';
|
import { memo } from 'preact/compat';
|
||||||
import { useEffect, useRef, useState } from 'preact/hooks';
|
import { useEffect, useRef, useState } from 'preact/hooks';
|
||||||
import { useSnapshot } from 'valtio';
|
import { useSnapshot } from 'valtio';
|
||||||
|
|
||||||
import Avatar from '../components/avatar';
|
import Avatar from '../components/avatar';
|
||||||
import Icon from '../components/icon';
|
import Icon from '../components/icon';
|
||||||
|
import Link from '../components/link';
|
||||||
import Loader from '../components/loader';
|
import Loader from '../components/loader';
|
||||||
import NameText from '../components/name-text';
|
import NameText from '../components/name-text';
|
||||||
import RelativeTime from '../components/relative-time';
|
import RelativeTime from '../components/relative-time';
|
||||||
import Status from '../components/status';
|
import Status from '../components/status';
|
||||||
import states from '../utils/states';
|
import states, { saveStatus } from '../utils/states';
|
||||||
import store from '../utils/store';
|
import store from '../utils/store';
|
||||||
import useTitle from '../utils/useTitle';
|
import useTitle from '../utils/useTitle';
|
||||||
|
|
||||||
|
@ -156,7 +156,7 @@ function Notification({ notification }) {
|
||||||
{status && (
|
{status && (
|
||||||
<Link
|
<Link
|
||||||
class={`status-link status-type-${type}`}
|
class={`status-link status-type-${type}`}
|
||||||
href={`#/s/${actualStatusID}`}
|
to={`/s/${actualStatusID}`}
|
||||||
>
|
>
|
||||||
<Status status={status} size="s" />
|
<Status status={status} size="s" />
|
||||||
</Link>
|
</Link>
|
||||||
|
@ -232,13 +232,12 @@ function Notifications() {
|
||||||
states.notificationsNew = [];
|
states.notificationsNew = [];
|
||||||
}
|
}
|
||||||
const allNotifications = await notificationsIterator.current.next();
|
const allNotifications = await notificationsIterator.current.next();
|
||||||
if (allNotifications.value <= 0) {
|
if (allNotifications.value?.length) {
|
||||||
return { done: true };
|
|
||||||
}
|
|
||||||
const notificationsValues = allNotifications.value.map((notification) => {
|
const notificationsValues = allNotifications.value.map((notification) => {
|
||||||
if (notification.status) {
|
saveStatus(notification.status, {
|
||||||
states.statuses[notification.status.id] = notification.status;
|
skipThreading: true,
|
||||||
}
|
override: false,
|
||||||
|
});
|
||||||
return notification;
|
return notification;
|
||||||
});
|
});
|
||||||
if (firstLoad) {
|
if (firstLoad) {
|
||||||
|
@ -246,6 +245,7 @@ function Notifications() {
|
||||||
} else {
|
} else {
|
||||||
states.notifications.push(...notificationsValues);
|
states.notifications.push(...notificationsValues);
|
||||||
}
|
}
|
||||||
|
}
|
||||||
states.notificationsLastFetchTime = Date.now();
|
states.notificationsLastFetchTime = Date.now();
|
||||||
return allNotifications;
|
return allNotifications;
|
||||||
}
|
}
|
||||||
|
@ -310,9 +310,9 @@ function Notifications() {
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<div class="header-side">
|
<div class="header-side">
|
||||||
<a href="#" class="button plain">
|
<Link to="/" class="button plain">
|
||||||
<Icon icon="home" size="l" />
|
<Icon icon="home" size="l" />
|
||||||
</a>
|
</Link>
|
||||||
</div>
|
</div>
|
||||||
<h1>Notifications</h1>
|
<h1>Notifications</h1>
|
||||||
<div class="header-side">
|
<div class="header-side">
|
||||||
|
|
|
@ -5,6 +5,7 @@ import { useSnapshot } from 'valtio';
|
||||||
|
|
||||||
import Avatar from '../components/avatar';
|
import Avatar from '../components/avatar';
|
||||||
import Icon from '../components/icon';
|
import Icon from '../components/icon';
|
||||||
|
import Link from '../components/link';
|
||||||
import NameText from '../components/name-text';
|
import NameText from '../components/name-text';
|
||||||
import RelativeTime from '../components/relative-time';
|
import RelativeTime from '../components/relative-time';
|
||||||
import states from '../utils/states';
|
import states from '../utils/states';
|
||||||
|
@ -124,9 +125,9 @@ function Settings({ onClose }) {
|
||||||
</p>
|
</p>
|
||||||
)}
|
)}
|
||||||
<p style={{ textAlign: 'end' }}>
|
<p style={{ textAlign: 'end' }}>
|
||||||
<a href="/#/login" class="button" onClick={onClose}>
|
<Link to="/login" class="button" onClick={onClose}>
|
||||||
Add new account
|
Add new account
|
||||||
</a>
|
</Link>
|
||||||
</p>
|
</p>
|
||||||
</section>
|
</section>
|
||||||
<h2>Settings</h2>
|
<h2>Settings</h2>
|
||||||
|
|
|
@ -1,13 +1,14 @@
|
||||||
import './status.css';
|
import './status.css';
|
||||||
|
|
||||||
import debounce from 'just-debounce-it';
|
import debounce from 'just-debounce-it';
|
||||||
import { Link } from 'preact-router/match';
|
|
||||||
import { useEffect, useMemo, useRef, useState } from 'preact/hooks';
|
import { useEffect, useMemo, useRef, useState } from 'preact/hooks';
|
||||||
import { useHotkeys } from 'react-hotkeys-hook';
|
import { useHotkeys } from 'react-hotkeys-hook';
|
||||||
import { InView } from 'react-intersection-observer';
|
import { InView } from 'react-intersection-observer';
|
||||||
|
import { useLocation, useParams } from 'react-router-dom';
|
||||||
import { useSnapshot } from 'valtio';
|
import { useSnapshot } from 'valtio';
|
||||||
|
|
||||||
import Icon from '../components/icon';
|
import Icon from '../components/icon';
|
||||||
|
import Link from '../components/link';
|
||||||
import Loader from '../components/loader';
|
import Loader from '../components/loader';
|
||||||
import NameText from '../components/name-text';
|
import NameText from '../components/name-text';
|
||||||
import RelativeTime from '../components/relative-time';
|
import RelativeTime from '../components/relative-time';
|
||||||
|
@ -23,7 +24,9 @@ import useTitle from '../utils/useTitle';
|
||||||
|
|
||||||
const LIMIT = 40;
|
const LIMIT = 40;
|
||||||
|
|
||||||
function StatusPage({ id }) {
|
function StatusPage() {
|
||||||
|
const { id } = useParams();
|
||||||
|
const location = useLocation();
|
||||||
const snapStates = useSnapshot(states);
|
const snapStates = useSnapshot(states);
|
||||||
const [statuses, setStatuses] = useState([]);
|
const [statuses, setStatuses] = useState([]);
|
||||||
const [uiState, setUIState] = useState('default');
|
const [uiState, setUIState] = useState('default');
|
||||||
|
@ -270,10 +273,11 @@ function StatusPage({ id }) {
|
||||||
: 'Status',
|
: 'Status',
|
||||||
);
|
);
|
||||||
|
|
||||||
const prevRoute = states.history.findLast((h) => {
|
const closeLink = useMemo(() => {
|
||||||
return h === '/' || /notifications/i.test(h);
|
const pathname = snapStates.prevLocation?.pathname;
|
||||||
});
|
if (!pathname || pathname.startsWith('/s/')) return '/';
|
||||||
const closeLink = `#${prevRoute || '/'}`;
|
return pathname;
|
||||||
|
}, []);
|
||||||
|
|
||||||
const [limit, setLimit] = useState(LIMIT);
|
const [limit, setLimit] = useState(LIMIT);
|
||||||
const showMore = useMemo(() => {
|
const showMore = useMemo(() => {
|
||||||
|
@ -305,7 +309,7 @@ function StatusPage({ id }) {
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div class="deck-backdrop">
|
<div class="deck-backdrop">
|
||||||
<Link href={closeLink}></Link>
|
<Link to={closeLink}></Link>
|
||||||
<div
|
<div
|
||||||
tabIndex="-1"
|
tabIndex="-1"
|
||||||
ref={scrollableRef}
|
ref={scrollableRef}
|
||||||
|
@ -383,7 +387,7 @@ function StatusPage({ id }) {
|
||||||
</h1>
|
</h1>
|
||||||
<div class="header-side">
|
<div class="header-side">
|
||||||
<Loader hidden={uiState !== 'loading'} />
|
<Loader hidden={uiState !== 'loading'} />
|
||||||
<Link class="button plain deck-close" href={closeLink}>
|
<Link class="button plain deck-close" to={closeLink}>
|
||||||
<Icon icon="x" size="xl" />
|
<Icon icon="x" size="xl" />
|
||||||
</Link>
|
</Link>
|
||||||
</div>
|
</div>
|
||||||
|
@ -420,7 +424,7 @@ function StatusPage({ id }) {
|
||||||
class="
|
class="
|
||||||
status-link
|
status-link
|
||||||
"
|
"
|
||||||
href={`#/s/${statusID}`}
|
to={`/s/${statusID}`}
|
||||||
>
|
>
|
||||||
<Status
|
<Status
|
||||||
statusID={statusID}
|
statusID={statusID}
|
||||||
|
@ -551,7 +555,7 @@ function SubComments({
|
||||||
<li key={r.id}>
|
<li key={r.id}>
|
||||||
<Link
|
<Link
|
||||||
class="status-link"
|
class="status-link"
|
||||||
href={`#/s/${r.id}`}
|
to={`/s/${r.id}`}
|
||||||
onClick={onStatusLinkClick}
|
onClick={onStatusLinkClick}
|
||||||
>
|
>
|
||||||
<Status statusID={r.id} withinContext size="s" />
|
<Status statusID={r.id} withinContext size="s" />
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
import './welcome.css';
|
import './welcome.css';
|
||||||
|
|
||||||
import logo from '../assets/logo.svg';
|
import logo from '../assets/logo.svg';
|
||||||
|
import Link from '../components/link';
|
||||||
import useTitle from '../utils/useTitle';
|
import useTitle from '../utils/useTitle';
|
||||||
|
|
||||||
function Welcome() {
|
function Welcome() {
|
||||||
|
@ -28,9 +29,9 @@ function Welcome() {
|
||||||
<p>
|
<p>
|
||||||
<big>
|
<big>
|
||||||
<b>
|
<b>
|
||||||
<a href="#/login" class="button">
|
<Link to="/login" class="button">
|
||||||
Log in
|
Log in
|
||||||
</a>
|
</Link>
|
||||||
</b>
|
</b>
|
||||||
</big>
|
</big>
|
||||||
</p>
|
</p>
|
||||||
|
|
Loading…
Reference in a new issue