From a362a9367fa7620eb697c529903f232ee69dbc25 Mon Sep 17 00:00:00 2001 From: Lim Chee Aun Date: Wed, 18 Jan 2023 00:56:35 +0800 Subject: [PATCH 001/108] Reduce width of boosts carousel Make it show roughly 3 boosts at a time --- src/app.css | 1 + 1 file changed, 1 insertion(+) diff --git a/src/app.css b/src/app.css index 38429db5..b7eb202b 100644 --- a/src/app.css +++ b/src/app.css @@ -1004,6 +1004,7 @@ meter.donut:is(.danger, .explode):after { } li:has(.boost-carousel) { width: 95vw; + max-width: calc(320px * 3.3); transform: translateX(calc(-50% + 20em)); } } From 1b6348fb08acdb59450dda90d66e8a72ffb95a51 Mon Sep 17 00:00:00 2001 From: Lim Chee Aun Date: Thu, 19 Jan 2023 15:51:54 +0800 Subject: [PATCH 002/108] Fix some links that are actually not user-links --- src/components/status.jsx | 20 ++++++++++++-------- 1 file changed, 12 insertions(+), 8 deletions(-) diff --git a/src/components/status.jsx b/src/components/status.jsx index e3d524b5..d66369b4 100644 --- a/src/components/status.jsx +++ b/src/components/status.jsx @@ -355,13 +355,10 @@ function Status({ target.tagName.toLowerCase() === 'a' && target.classList.contains('u-url') ) { - e.preventDefault(); - e.stopPropagation(); - const username = ( + const targetText = ( target.querySelector('span') || target - ).innerText - .trim() - .replace(/^@/, ''); + ).innerText.trim(); + const username = targetText.replace(/^@/, ''); const url = target.getAttribute('href'); const mention = mentions.find( (mention) => @@ -370,8 +367,13 @@ function Status({ mention.url === url, ); if (mention) { + e.preventDefault(); + e.stopPropagation(); states.showAccount = mention.acct; - } else { + } else if (!/^http/i.test(targetText)) { + console.log('mention not found', targetText); + e.preventDefault(); + e.stopPropagation(); const href = target.getAttribute('href'); states.showAccount = href; } @@ -385,7 +387,9 @@ function Status({ .querySelectorAll('a.u-url[target="_blank"]') .forEach((a) => { // Remove target="_blank" from links - a.removeAttribute('target'); + if (!/http/i.test(a.innerText.trim())) { + a.removeAttribute('target'); + } }); }, }), From baf139762c342986d805477fc9e29178275ec97b Mon Sep 17 00:00:00 2001 From: Lim Chee Aun Date: Fri, 20 Jan 2023 16:05:27 +0800 Subject: [PATCH 003/108] Scroll start logic should work when overscroll too --- src/utils/useScroll.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/utils/useScroll.js b/src/utils/useScroll.js index 4c82a852..de59b6fa 100644 --- a/src/utils/useScroll.js +++ b/src/utils/useScroll.js @@ -51,7 +51,7 @@ export default function useScroll({ previousScrollStart = scrollStart; } - setReachStart(scrollStart === 0); + setReachStart(scrollStart <= 0); setReachEnd(scrollStart + clientDimension >= scrollDimension); setNearReachStart(scrollStart <= distanceFromStartPx); setNearReachEnd( From 9bff95bcecbf43d747e8859feed16a00c5552f95 Mon Sep 17 00:00:00 2001 From: Lim Chee Aun Date: Sat, 21 Jan 2023 00:23:59 +0800 Subject: [PATCH 004/108] Replace preact-router with react-router MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Need more routing powers, hopefully things don't break 🤞 --- README.md | 1 + package-lock.json | 101 +++++--- package.json | 3 +- src/app.css | 88 +++++-- src/app.jsx | 120 ++++++---- src/components/link.jsx | 30 +++ src/components/status.jsx | 9 +- src/main.jsx | 8 +- src/pages/bookmarks.jsx | 144 ++++++++++++ src/pages/home.jsx | 449 ++++++++++++++++++------------------ src/pages/login.jsx | 3 +- src/pages/notifications.jsx | 34 +-- src/pages/settings.jsx | 5 +- src/pages/status.jsx | 24 +- src/pages/welcome.jsx | 5 +- 15 files changed, 662 insertions(+), 362 deletions(-) create mode 100644 src/components/link.jsx create mode 100644 src/pages/bookmarks.jsx diff --git a/README.md b/README.md index e24016ee..925bdafb 100644 --- a/README.md +++ b/README.md @@ -62,6 +62,7 @@ Prerequisites: Node.js 18+ - [Vite](https://vitejs.dev/) - Build tool - [Preact](https://preactjs.com/) - UI library - [Valtio](https://valtio.pmnd.rs/) - State management +- [React Router](https://reactrouter.com/) - Routing - [masto.js](https://github.com/neet/masto.js/) - Mastodon API client - [Iconify](https://iconify.design/) - Icon library - Vanilla CSS - *Yes, I'm old school.* diff --git a/package-lock.json b/package-lock.json index 7606458b..bae57c1b 100644 --- a/package-lock.json +++ b/package-lock.json @@ -14,15 +14,14 @@ "dayjs-twitter": "~0.5.0", "fast-blurhash": "~1.1.2", "fast-deep-equal": "~3.1.3", - "history": "~5.3.0", "idb-keyval": "~6.2.0", "just-debounce-it": "~3.2.0", "masto": "~5.5.0", "mem": "~9.0.2", "preact": "~10.11.3", - "preact-router": "~4.1.0", "react-hotkeys-hook": "~4.3.2", "react-intersection-observer": "~9.4.1", + "react-router-dom": "~6.7.0", "string-length": "~5.0.1", "swiped-events": "~1.1.7", "toastify-js": "~1.12.0", @@ -1653,6 +1652,7 @@ "version": "7.20.6", "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.20.6.tgz", "integrity": "sha512-Q+8MqP7TiHMWzSfwiJwXCjyf4GYA4Dgw3emg/7xmwsdLJOZUp+nMqcOwOzzYheuM1rhDu8FSj2l0aoMygEuXuA==", + "dev": true, "dependencies": { "regenerator-runtime": "^0.13.11" }, @@ -2276,6 +2276,14 @@ "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": { "version": "5.0.2", "resolved": "https://registry.npmjs.org/@rollup/plugin-replace/-/plugin-replace-5.0.2.tgz", @@ -3607,14 +3615,6 @@ "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": { "version": "7.1.1", "resolved": "https://registry.npmjs.org/idb/-/idb-7.1.1.tgz", @@ -4537,14 +4537,6 @@ "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": { "version": "2.8.0", "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" } }, + "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": { "version": "1.4.2", "resolved": "https://registry.npmjs.org/regenerate/-/regenerate-1.4.2.tgz", @@ -4693,7 +4715,8 @@ "node_modules/regenerator-runtime": { "version": "0.13.11", "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": { "version": "0.15.1", @@ -7013,6 +7036,7 @@ "version": "7.20.6", "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.20.6.tgz", "integrity": "sha512-Q+8MqP7TiHMWzSfwiJwXCjyf4GYA4Dgw3emg/7xmwsdLJOZUp+nMqcOwOzzYheuM1rhDu8FSj2l0aoMygEuXuA==", + "dev": true, "requires": { "regenerator-runtime": "^0.13.11" } @@ -7394,6 +7418,11 @@ "@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": { "version": "5.0.2", "resolved": "https://registry.npmjs.org/@rollup/plugin-replace/-/plugin-replace-5.0.2.tgz", @@ -8413,14 +8442,6 @@ "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": { "version": "7.1.1", "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", "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": { "version": "2.8.0", "resolved": "https://registry.npmjs.org/prettier/-/prettier-2.8.0.tgz", @@ -9181,6 +9196,23 @@ "integrity": "sha512-IXpIsPe6BleFOEHKzKh5UjwRUaz/JYS0lT/HPsupWEQou2hDqjhLMStc5zyE3eQVT4Fk3FufM8Fw33qW1uyeiw==", "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": { "version": "1.4.2", "resolved": "https://registry.npmjs.org/regenerate/-/regenerate-1.4.2.tgz", @@ -9199,7 +9231,8 @@ "regenerator-runtime": { "version": "0.13.11", "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": { "version": "0.15.1", diff --git a/package.json b/package.json index 6a45033c..0444dada 100644 --- a/package.json +++ b/package.json @@ -16,15 +16,14 @@ "dayjs-twitter": "~0.5.0", "fast-blurhash": "~1.1.2", "fast-deep-equal": "~3.1.3", - "history": "~5.3.0", "idb-keyval": "~6.2.0", "just-debounce-it": "~3.2.0", "masto": "~5.5.0", "mem": "~9.0.2", "preact": "~10.11.3", - "preact-router": "~4.1.0", "react-hotkeys-hook": "~4.3.2", "react-intersection-observer": "~9.4.1", + "react-router-dom": "~6.7.0", "string-length": "~5.0.1", "swiped-events": "~1.1.7", "toastify-js": "~1.12.0", diff --git a/src/app.css b/src/app.css index b7eb202b..690905a1 100644 --- a/src/app.css +++ b/src/app.css @@ -46,6 +46,7 @@ a[href^='http'][rel*='nofollow']:visited:not(:has(div)) { transition: opacity 0.1s ease-in-out; overscroll-behavior: contain; scroll-behavior: smooth; + background-color: var(--bg-color); } .deck-container[hidden] { display: block; @@ -61,6 +62,14 @@ a[href^='http'][rel*='nofollow']:visited:not(:has(div)) { 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 { min-height: 100vh; min-height: 100dvh; @@ -364,7 +373,7 @@ a[href^='http'][rel*='nofollow']:visited:not(:has(div)) { -webkit-tap-highlight-color: transparent; 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); outline-offset: -2px; } @@ -508,11 +517,6 @@ a[href^='http'][rel*='nofollow']:visited:not(:has(div)) { max-width: 40em; } -.decks { - flex-grow: 1; - width: 100%; -} - .deck-close { color: var(--text-insignificant-color) !important; } @@ -944,21 +948,63 @@ meter.donut:is(.danger, .explode):after { 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) { html, body { background-color: var(--bg-faded-color); } + .deck-container { + background-color: var(--bg-faded-color); + } #app { 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 { width: 50%; min-width: 40em; @@ -995,6 +1041,22 @@ meter.donut:is(.danger, .explode):after { border-radius: 16px; overflow: hidden; 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 { padding: 32px; diff --git a/src/app.jsx b/src/app.jsx index bd4f553c..340a1a45 100644 --- a/src/app.jsx +++ b/src/app.jsx @@ -1,19 +1,21 @@ import './app.css'; import 'toastify-js/src/toastify.css'; -import { createHashHistory } from 'history'; import debounce from 'just-debounce-it'; import { login } from 'masto'; -import Router, { route } from 'preact-router'; -import { useEffect, useLayoutEffect, useState } from 'preact/hooks'; +import { useEffect, useLayoutEffect, useMemo, useState } from 'preact/hooks'; +import { Route, Routes, useLocation, useNavigate } from 'react-router-dom'; import Toastify from 'toastify-js'; import { useSnapshot } from 'valtio'; import Account from './components/account'; import Compose from './components/compose'; import Drafts from './components/drafts'; +import Icon from './components/icon'; +import Link from './components/link'; import Loader from './components/loader'; import Modal from './components/modal'; +import Bookmarks from './pages/bookmarks'; import Home from './pages/home'; import Login from './pages/login'; import Notifications from './pages/notifications'; @@ -24,14 +26,13 @@ import { getAccessToken } from './utils/auth'; import states, { saveStatus } from './utils/states'; import store from './utils/store'; -const { VITE_CLIENT_NAME: CLIENT_NAME } = import.meta.env; - window.__STATES__ = states; function App() { const snapStates = useSnapshot(states); const [isLoggedIn, setIsLoggedIn] = useState(false); const [uiState, setUIState] = useState('loading'); + const navigate = useNavigate(); useLayoutEffect(() => { const theme = store.local.get('theme'); @@ -126,20 +127,22 @@ function App() { } }, []); - const [currentDeck, setCurrentDeck] = useState('home'); - const [currentModal, setCurrentModal] = useState(null); + let location = useLocation(); + const locationDeckMap = { + '/': 'home-page', + '/notifications': 'notifications-page', + }; const focusDeck = () => { - if (currentModal) return; let timer = setTimeout(() => { - const page = document.getElementById(`${currentDeck}-page`); - console.debug('FOCUS', currentDeck, page); + const page = document.getElementById(locationDeckMap[location.pathname]); + console.debug('FOCUS', location.pathname, page); if (page) { page.focus(); } }, 100); return () => clearTimeout(timer); }; - useEffect(focusDeck, [currentDeck, currentModal]); + useEffect(focusDeck, [location]); useEffect(() => { if ( !snapStates.showCompose && @@ -173,44 +176,66 @@ function App() { } }, [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 ( <> - {isLoggedIn && currentDeck && ( -
- {/* Home will never be unmounted */} -
- )} - {!isLoggedIn && uiState === 'loading' && } - { - 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); + + + ) : uiState === 'loading' ? ( + + ) : ( + + ) } - states.history.push(url); - }} - > - {!isLoggedIn && uiState !== 'loading' && } - - {isLoggedIn && } - - + /> + } /> + } /> + + + {isLoggedIn && ( + } /> + )} + {isLoggedIn && } />} + + + {isLoggedIn && } />} + + {!!snapStates.showCompose && ( { toast.hideToast(); - route(`/s/${newStatus.id}`); + states.prevLocation = location; + navigate(`/s/${newStatus.id}`); }, }); toast.showToast(); diff --git a/src/components/link.jsx b/src/components/link.jsx new file mode 100644 index 00000000..30659f46 --- /dev/null +++ b/src/components/link.jsx @@ -0,0 +1,30 @@ +import { useLocation } from 'react-router-dom'; + +import states from '../utils/states'; + +/* NOTES + ===== + Initially this uses from react-router-dom, but it doesn't work: + 1. It interferes with nested inside and it's difficult to preventDefault/stopPropagation from the nested + 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 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 ( + { + states.prevLocation = routerLocation; + }} + /> + ); +}; + +export default Link; diff --git a/src/components/status.jsx b/src/components/status.jsx index d66369b4..ac07f0ef 100644 --- a/src/components/status.jsx +++ b/src/components/status.jsx @@ -29,6 +29,7 @@ import visibilityIconsMap from '../utils/visibility-icons-map'; import Avatar from './avatar'; import Icon from './icon'; +import Link from './link'; import RelativeTime from './relative-time'; function fetchAccount(id) { @@ -251,18 +252,14 @@ function Status({ {/* */}{' '} {size !== 'l' && (uri ? ( - + {' '} - + ) : ( , document.getElementById('app')); +render( + + + , + document.getElementById('app'), +); // Clean up iconify localStorage // TODO: Remove this after few weeks? diff --git a/src/pages/bookmarks.jsx b/src/pages/bookmarks.jsx new file mode 100644 index 00000000..a6ee0ad8 --- /dev/null +++ b/src/pages/bookmarks.jsx @@ -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 ( +
+
+
{ + if (e.target === e.currentTarget) { + scrollableRef.current?.scrollTo({ + top: 0, + behavior: 'smooth', + }); + } + }} + > +
+ + + +
+

Bookmarks

+
{' '} +
+ {!!bookmarks.length ? ( + <> +
    + {bookmarks.map((status) => ( +
  • + + + +
  • + ))} +
+ + {showMore && ( + + )} + + ) : ( + uiState !== 'loading' && ( +

No bookmarks yet. Go bookmark something!

+ ) + )} + {uiState === 'loading' ? ( +
+ +
+ ) : uiState === 'error' ? ( +

+ Unable to load bookmarks. +
+
+ +

+ ) : ( + bookmarks.length && + !showMore &&

The end.

+ )} +
+
+ ); +} + +export default Bookmarks; diff --git a/src/pages/home.jsx b/src/pages/home.jsx index e4c333e8..f1bfafbf 100644 --- a/src/pages/home.jsx +++ b/src/pages/home.jsx @@ -1,10 +1,10 @@ -import { Link } from 'preact-router/match'; import { memo } from 'preact/compat'; import { useEffect, useRef, useState } from 'preact/hooks'; import { useHotkeys } from 'react-hotkeys-hook'; import { useSnapshot } from 'valtio'; import Icon from '../components/icon'; +import Link from '../components/link'; import Loader from '../components/loader'; import Status from '../components/status'; import db from '../utils/db'; @@ -36,82 +36,79 @@ function Home({ hidden }) { states.homeNew = []; } const allStatuses = await homeIterator.current.next(); - if (allStatuses.value <= 0) { - return { done: true }; - } - const homeValues = allStatuses.value.map((status) => { - saveStatus(status); - return { - id: status.id, - reblog: status.reblog?.id, - reply: !!status.inReplyToAccountId, - }; - }); + if (allStatuses.value?.length) { + const homeValues = allStatuses.value.map((status) => { + saveStatus(status); + return { + id: status.id, + reblog: status.reblog?.id, + reply: !!status.inReplyToAccountId, + }; + }); - // BOOSTS CAROUSEL - if (snapStates.settings.boostsCarousel) { - let specialHome = []; - let boostStash = []; - let serialBoosts = 0; - for (let i = 0; i < homeValues.length; i++) { - const status = homeValues[i]; - if (status.reblog) { - boostStash.push(status); - serialBoosts++; - } else { - specialHome.push(status); - if (serialBoosts < 3) { - serialBoosts = 0; + // BOOSTS CAROUSEL + if (snapStates.settings.boostsCarousel) { + let specialHome = []; + let boostStash = []; + let serialBoosts = 0; + for (let i = 0; i < homeValues.length; i++) { + const status = homeValues[i]; + if (status.reblog) { + boostStash.push(status); + serialBoosts++; + } else { + specialHome.push(status); + if (serialBoosts < 3) { + serialBoosts = 0; + } } } - } - // if boostStash is more than quarter of homeValues - // or if there are 3 or more boosts in a row - if (boostStash.length > homeValues.length / 4 || serialBoosts >= 3) { - // if boostStash is more than 3 quarter of homeValues - const boostStashID = boostStash.map((status) => status.id); - if (boostStash.length > (homeValues.length * 3) / 4) { - // insert boost array at the end of specialHome list - specialHome = [ - ...specialHome, - { id: boostStashID, boosts: boostStash }, - ]; + // if boostStash is more than quarter of homeValues + // or if there are 3 or more boosts in a row + if (boostStash.length > homeValues.length / 4 || serialBoosts >= 3) { + // if boostStash is more than 3 quarter of homeValues + const boostStashID = boostStash.map((status) => status.id); + if (boostStash.length > (homeValues.length * 3) / 4) { + // insert boost array at the end of specialHome list + specialHome = [ + ...specialHome, + { id: boostStashID, boosts: boostStash }, + ]; + } else { + // insert boosts array in the middle of specialHome list + const half = Math.floor(specialHome.length / 2); + specialHome = [ + ...specialHome.slice(0, half), + { + id: boostStashID, + boosts: boostStash, + }, + ...specialHome.slice(half), + ]; + } } else { - // insert boosts array in the middle of specialHome list - const half = Math.floor(specialHome.length / 2); - specialHome = [ - ...specialHome.slice(0, half), - { - id: boostStashID, - boosts: boostStash, - }, - ...specialHome.slice(half), - ]; + // Untouched, this is fine + specialHome = homeValues; + } + console.log({ + specialHome, + }); + if (firstLoad) { + states.home = specialHome; + } else { + states.home.push(...specialHome); } } else { - // Untouched, this is fine - specialHome = homeValues; - } - console.log({ - specialHome, - }); - if (firstLoad) { - states.home = specialHome; - } else { - states.home.push(...specialHome); - } - } else { - if (firstLoad) { - states.home = homeValues; - } else { - states.home.push(...homeValues); + if (firstLoad) { + states.home = homeValues; + } else { + states.home.push(...homeValues); + } } } states.homeLastFetchTime = Date.now(); - return { - done: false, - }; + return allStatuses; } const loadingStatuses = useRef(false); @@ -276,13 +273,161 @@ function Home({ hidden }) { }, []); return ( - + ); } @@ -504,9 +499,9 @@ function BoostsCarousel({ boosts }) { const actualStatusID = reblog || statusID; return (
  • - + - +
  • ); })} diff --git a/src/pages/login.jsx b/src/pages/login.jsx index d184ceaa..5bcc0ce6 100644 --- a/src/pages/login.jsx +++ b/src/pages/login.jsx @@ -2,6 +2,7 @@ import './login.css'; import { useEffect, useRef, useState } from 'preact/hooks'; +import Link from '../components/link'; import Loader from '../components/loader'; import instancesListURL from '../data/instances.json?url'; import { getAuthorizationURL, registerApplication } from '../utils/auth'; @@ -111,7 +112,7 @@ function Login() {

    - Go home + Go home

    diff --git a/src/pages/notifications.jsx b/src/pages/notifications.jsx index 738f6971..b90d6d45 100644 --- a/src/pages/notifications.jsx +++ b/src/pages/notifications.jsx @@ -1,17 +1,17 @@ import './notifications.css'; -import { Link } from 'preact-router/match'; import { memo } from 'preact/compat'; import { useEffect, useRef, useState } from 'preact/hooks'; import { useSnapshot } from 'valtio'; import Avatar from '../components/avatar'; import Icon from '../components/icon'; +import Link from '../components/link'; import Loader from '../components/loader'; import NameText from '../components/name-text'; import RelativeTime from '../components/relative-time'; import Status from '../components/status'; -import states from '../utils/states'; +import states, { saveStatus } from '../utils/states'; import store from '../utils/store'; import useTitle from '../utils/useTitle'; @@ -156,7 +156,7 @@ function Notification({ notification }) { {status && ( @@ -232,19 +232,19 @@ function Notifications() { states.notificationsNew = []; } const allNotifications = await notificationsIterator.current.next(); - if (allNotifications.value <= 0) { - return { done: true }; - } - const notificationsValues = allNotifications.value.map((notification) => { - if (notification.status) { - states.statuses[notification.status.id] = notification.status; + if (allNotifications.value?.length) { + const notificationsValues = allNotifications.value.map((notification) => { + saveStatus(notification.status, { + skipThreading: true, + override: false, + }); + return notification; + }); + if (firstLoad) { + states.notifications = notificationsValues; + } else { + states.notifications.push(...notificationsValues); } - return notification; - }); - if (firstLoad) { - states.notifications = notificationsValues; - } else { - states.notifications.push(...notificationsValues); } states.notificationsLastFetchTime = Date.now(); return allNotifications; @@ -310,9 +310,9 @@ function Notifications() { }} >
    - + - +

    Notifications

    diff --git a/src/pages/settings.jsx b/src/pages/settings.jsx index 60b55d38..b9aa6987 100644 --- a/src/pages/settings.jsx +++ b/src/pages/settings.jsx @@ -5,6 +5,7 @@ import { useSnapshot } from 'valtio'; import Avatar from '../components/avatar'; import Icon from '../components/icon'; +import Link from '../components/link'; import NameText from '../components/name-text'; import RelativeTime from '../components/relative-time'; import states from '../utils/states'; @@ -124,9 +125,9 @@ function Settings({ onClose }) {

    )}

    - + Add new account - +

    Settings

    diff --git a/src/pages/status.jsx b/src/pages/status.jsx index c0ce9e73..1c1719bb 100644 --- a/src/pages/status.jsx +++ b/src/pages/status.jsx @@ -1,13 +1,14 @@ import './status.css'; import debounce from 'just-debounce-it'; -import { Link } from 'preact-router/match'; import { useEffect, useMemo, useRef, useState } from 'preact/hooks'; import { useHotkeys } from 'react-hotkeys-hook'; import { InView } from 'react-intersection-observer'; +import { useLocation, useParams } from 'react-router-dom'; import { useSnapshot } from 'valtio'; import Icon from '../components/icon'; +import Link from '../components/link'; import Loader from '../components/loader'; import NameText from '../components/name-text'; import RelativeTime from '../components/relative-time'; @@ -23,7 +24,9 @@ import useTitle from '../utils/useTitle'; const LIMIT = 40; -function StatusPage({ id }) { +function StatusPage() { + const { id } = useParams(); + const location = useLocation(); const snapStates = useSnapshot(states); const [statuses, setStatuses] = useState([]); const [uiState, setUIState] = useState('default'); @@ -270,10 +273,11 @@ function StatusPage({ id }) { : 'Status', ); - const prevRoute = states.history.findLast((h) => { - return h === '/' || /notifications/i.test(h); - }); - const closeLink = `#${prevRoute || '/'}`; + const closeLink = useMemo(() => { + const pathname = snapStates.prevLocation?.pathname; + if (!pathname || pathname.startsWith('/s/')) return '/'; + return pathname; + }, []); const [limit, setLimit] = useState(LIMIT); const showMore = useMemo(() => { @@ -305,7 +309,7 @@ function StatusPage({ id }) { return (
    - +
    @@ -420,7 +424,7 @@ function StatusPage({ id }) { class=" status-link " - href={`#/s/${statusID}`} + to={`/s/${statusID}`} > diff --git a/src/pages/welcome.jsx b/src/pages/welcome.jsx index 4ba7e7c2..0f417a62 100644 --- a/src/pages/welcome.jsx +++ b/src/pages/welcome.jsx @@ -1,6 +1,7 @@ import './welcome.css'; import logo from '../assets/logo.svg'; +import Link from '../components/link'; import useTitle from '../utils/useTitle'; function Welcome() { @@ -28,9 +29,9 @@ function Welcome() {

    - + Log in - +

    From 4f91d9e4e96dbba135bcc49ec7924d52b2a2ad38 Mon Sep 17 00:00:00 2001 From: Lim Chee Aun Date: Sat, 21 Jan 2023 00:27:20 +0800 Subject: [PATCH 005/108] Upgrade masto --- package-lock.json | 14 +++++++------- package.json | 2 +- 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/package-lock.json b/package-lock.json index bae57c1b..9886c2ef 100644 --- a/package-lock.json +++ b/package-lock.json @@ -16,7 +16,7 @@ "fast-deep-equal": "~3.1.3", "idb-keyval": "~6.2.0", "just-debounce-it": "~3.2.0", - "masto": "~5.5.0", + "masto": "~5.5.1", "mem": "~9.0.2", "preact": "~10.11.3", "react-hotkeys-hook": "~4.3.2", @@ -4178,9 +4178,9 @@ } }, "node_modules/masto": { - "version": "5.5.0", - "resolved": "https://registry.npmjs.org/masto/-/masto-5.5.0.tgz", - "integrity": "sha512-EmAe76vYSR9tmUBiOqG7PwbrNFMVXaH7ce1LAr09MuXoS9RZfdEA4y7y3G0VhwTr4mGwnvWJu203CgAae7ZTEg==", + "version": "5.5.1", + "resolved": "https://registry.npmjs.org/masto/-/masto-5.5.1.tgz", + "integrity": "sha512-JU8cx9EtTkX+ZBIQ60yq0V39syZm5Jb7N7dfKDZHccelmBxbJ7uEol4g+nnRL3kk93NOycOUvTIOkDSvGmQAqQ==", "dependencies": { "@mastojs/ponyfills": "^1.0.4", "change-case": "^4.1.2", @@ -8858,9 +8858,9 @@ } }, "masto": { - "version": "5.5.0", - "resolved": "https://registry.npmjs.org/masto/-/masto-5.5.0.tgz", - "integrity": "sha512-EmAe76vYSR9tmUBiOqG7PwbrNFMVXaH7ce1LAr09MuXoS9RZfdEA4y7y3G0VhwTr4mGwnvWJu203CgAae7ZTEg==", + "version": "5.5.1", + "resolved": "https://registry.npmjs.org/masto/-/masto-5.5.1.tgz", + "integrity": "sha512-JU8cx9EtTkX+ZBIQ60yq0V39syZm5Jb7N7dfKDZHccelmBxbJ7uEol4g+nnRL3kk93NOycOUvTIOkDSvGmQAqQ==", "requires": { "@mastojs/ponyfills": "^1.0.4", "change-case": "^4.1.2", diff --git a/package.json b/package.json index 0444dada..444d2e4c 100644 --- a/package.json +++ b/package.json @@ -18,7 +18,7 @@ "fast-deep-equal": "~3.1.3", "idb-keyval": "~6.2.0", "just-debounce-it": "~3.2.0", - "masto": "~5.5.0", + "masto": "~5.5.1", "mem": "~9.0.2", "preact": "~10.11.3", "react-hotkeys-hook": "~4.3.2", From ddce5bb0ffe593ef40c90801950b4395d512dacf Mon Sep 17 00:00:00 2001 From: Lim Chee Aun Date: Sat, 21 Jan 2023 01:04:27 +0800 Subject: [PATCH 006/108] Fix background location need to persist --- src/app.jsx | 22 ++++++++++++++++------ 1 file changed, 16 insertions(+), 6 deletions(-) diff --git a/src/app.jsx b/src/app.jsx index 340a1a45..0cd00870 100644 --- a/src/app.jsx +++ b/src/app.jsx @@ -3,7 +3,13 @@ import 'toastify-js/src/toastify.css'; import debounce from 'just-debounce-it'; import { login } from 'masto'; -import { useEffect, useLayoutEffect, useMemo, useState } from 'preact/hooks'; +import { + useEffect, + useLayoutEffect, + useMemo, + useRef, + useState, +} from 'preact/hooks'; import { Route, Routes, useLocation, useNavigate } from 'react-router-dom'; import Toastify from 'toastify-js'; import { useSnapshot } from 'valtio'; @@ -176,15 +182,19 @@ function App() { } }, [isLoggedIn]); - const backgroundLocation = useMemo(() => { + const backgroundLocation = useRef(); + useEffect(() => { 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; + if (isModalPage) { + if (!backgroundLocation.current) + backgroundLocation.current = prevLocation; + } else { + backgroundLocation.current = null; + } }, [location]); const nonRootLocation = useMemo(() => { @@ -210,7 +220,7 @@ function App() { } /> } /> - + {isLoggedIn && ( } /> )} From d66d1bca12fef048a54ba2ca028126c4356df65a Mon Sep 17 00:00:00 2001 From: Lim Chee Aun Date: Sat, 21 Jan 2023 01:04:40 +0800 Subject: [PATCH 007/108] Stronger colors for dark mode --- src/index.css | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/index.css b/src/index.css index 747e79bf..dda1bc31 100644 --- a/src/index.css +++ b/src/index.css @@ -52,7 +52,6 @@ --purple-color: mediumpurple; --green-color: lightgreen; --orange-color: orange; - --reply-to-text-color: var(--reply-to-color); --bg-color: #242526; --bg-faded-color: #18191a; --bg-blur-color: #0009; @@ -62,6 +61,9 @@ --link-light-color: #6494ed99; --link-faded-color: #6494ed88; --link-bg-hover-color: #34353799; + --reblog-faded-color: #9370d844; + --reply-to-text-color: var(--reply-to-color); + --reply-to-faded-color: #ffa60033; --divider-color: rgba(255, 255, 255, 0.1); --bg-blur-color: #24252699; --backdrop-color: rgba(0, 0, 0, 0.5); From 37fc65b47ade272a8c8c8959af9769b0951f4e98 Mon Sep 17 00:00:00 2001 From: Lim Chee Aun Date: Sat, 21 Jan 2023 10:08:55 +0800 Subject: [PATCH 008/108] Don't need useEffect for backgroundLocation --- src/app.jsx | 26 ++++++++++++-------------- 1 file changed, 12 insertions(+), 14 deletions(-) diff --git a/src/app.jsx b/src/app.jsx index 0cd00870..2e5d0083 100644 --- a/src/app.jsx +++ b/src/app.jsx @@ -182,20 +182,18 @@ function App() { } }, [isLoggedIn]); - const backgroundLocation = useRef(); - useEffect(() => { - const { prevLocation } = snapStates; - const { pathname } = location; - const { pathname: prevPathname } = prevLocation || {}; - console.debug({ prevPathname, pathname }); - const isModalPage = /^\/s\//i.test(pathname); - if (isModalPage) { - if (!backgroundLocation.current) - backgroundLocation.current = prevLocation; - } else { - backgroundLocation.current = null; - } - }, [location]); + const { prevLocation } = snapStates; + const backgroundLocation = useRef(prevLocation || null); + const isModalPage = /^\/s\//i.test(location.pathname); + if (isModalPage) { + if (!backgroundLocation.current) backgroundLocation.current = prevLocation; + } else { + backgroundLocation.current = null; + } + console.debug({ + backgroundLocation: backgroundLocation.current, + location, + }); const nonRootLocation = useMemo(() => { const { pathname } = location; From e0d50168fd4bc708cd0b1541080adf81ada11646 Mon Sep 17 00:00:00 2001 From: Lim Chee Aun Date: Sat, 21 Jan 2023 13:21:57 +0800 Subject: [PATCH 009/108] Don't need useLocation when in compose pop-out --- src/components/link.jsx | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/src/components/link.jsx b/src/components/link.jsx index 30659f46..3dee227d 100644 --- a/src/components/link.jsx +++ b/src/components/link.jsx @@ -11,7 +11,10 @@ import states from '../utils/states'; */ const Link = (props) => { - const routerLocation = useLocation(); + let routerLocation; + try { + routerLocation = useLocation(); + } catch (e) {} let hash = (location.hash || '').replace(/^#/, '').trim(); if (hash === '') hash = '/'; const isActive = hash === props.to; @@ -21,7 +24,7 @@ const Link = (props) => { {...props} class={`${props.class || ''} ${isActive ? 'is-active' : ''}`} onClick={() => { - states.prevLocation = routerLocation; + if (routerLocation) states.prevLocation = routerLocation; }} /> ); From fa5a468005b0c8a3b4367b697e12bead55bab1f1 Mon Sep 17 00:00:00 2001 From: Lim Chee Aun Date: Sat, 21 Jan 2023 18:59:13 +0800 Subject: [PATCH 010/108] Don't really get how this becomes multi-line --- src/pages/status.jsx | 7 +------ 1 file changed, 1 insertion(+), 6 deletions(-) diff --git a/src/pages/status.jsx b/src/pages/status.jsx index 1c1719bb..66980424 100644 --- a/src/pages/status.jsx +++ b/src/pages/status.jsx @@ -420,12 +420,7 @@ function StatusPage() { ) : ( - + Date: Sat, 21 Jan 2023 19:52:51 +0800 Subject: [PATCH 011/108] When clicked, don't use cached scroll position --- src/components/link.jsx | 1 + src/pages/status.jsx | 23 ++++++++++++++++------- 2 files changed, 17 insertions(+), 7 deletions(-) diff --git a/src/components/link.jsx b/src/components/link.jsx index 3dee227d..39698d26 100644 --- a/src/components/link.jsx +++ b/src/components/link.jsx @@ -25,6 +25,7 @@ const Link = (props) => { class={`${props.class || ''} ${isActive ? 'is-active' : ''}`} onClick={() => { if (routerLocation) states.prevLocation = routerLocation; + props.onClick?.(); }} /> ); diff --git a/src/pages/status.jsx b/src/pages/status.jsx index 66980424..a98b1afc 100644 --- a/src/pages/status.jsx +++ b/src/pages/status.jsx @@ -24,6 +24,10 @@ import useTitle from '../utils/useTitle'; const LIMIT = 40; +function resetScrollPosition(id) { + delete states.scrollPositions[id]; +} + function StatusPage() { const { id } = useParams(); const location = useLocation(); @@ -190,6 +194,7 @@ function StatusPage() { console.debug('scrollPosition', scrollPosition); if (!!scrollPosition) { console.debug('Case 1', { + id, scrollPosition, }); scrollableRef.current.scrollTop = scrollPosition; @@ -420,7 +425,13 @@ function StatusPage() { ) : ( - + { + resetScrollPosition(statusID); + }} + > {}, -}) { +function SubComments({ hasManyStatuses, replies }) { // If less than or 2 replies and total number of characters of content from replies is less than 500 let isBrief = false; if (replies.length <= 2) { @@ -551,7 +558,9 @@ function SubComments({ { + resetScrollPosition(r.id); + }} > {r.repliesCount > 0 && ( From 6f4a5553eca2de3bd69587dfa9971c67f3eab70c Mon Sep 17 00:00:00 2001 From: Lim Chee Aun Date: Sat, 21 Jan 2023 20:21:16 +0800 Subject: [PATCH 012/108] Simplify and robustify --- src/pages/bookmarks.jsx | 36 +++++++++++++++++------------------- 1 file changed, 17 insertions(+), 19 deletions(-) diff --git a/src/pages/bookmarks.jsx b/src/pages/bookmarks.jsx index a6ee0ad8..bdd4e660 100644 --- a/src/pages/bookmarks.jsx +++ b/src/pages/bookmarks.jsx @@ -4,7 +4,6 @@ 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; @@ -15,21 +14,14 @@ function Bookmarks() { const [uiState, setUIState] = useState('default'); const [showMore, setShowMore] = useState(false); - const bookmarksIterator = useRef(masto.v1.bookmarks.list({ limit: LIMIT })); + const bookmarksIterator = useRef(); async function fetchBookmarks(firstLoad) { - console.log('fetchBookmarks', firstLoad); - if (firstLoad) { + if (firstLoad || !bookmarksIterator.current) { 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; - }); + const bookmarksValue = allBookmarks.value; + if (bookmarksValue?.length) { if (firstLoad) { setBookmarks(bookmarksValue); } else { @@ -43,9 +35,7 @@ function Bookmarks() { setUIState('loading'); (async () => { try { - console.log('loadBookmarks', firstLoad); const { done } = await fetchBookmarks(firstLoad); - console.log('loadBookmarks', firstLoad); setShowMore(!done); setUIState('default'); } catch (e) { @@ -78,6 +68,9 @@ function Bookmarks() { }); } }} + onDblClick={(e) => { + loadBookmarks(true); + }} >
    @@ -85,7 +78,9 @@ function Bookmarks() {

    Bookmarks

    -
    {' '} +
    +
    {!!bookmarks.length ? ( <> @@ -98,7 +93,6 @@ function Bookmarks() { ))} - {showMore && (
    +
      + {Array.from({ length: 5 }).map((_, i) => ( +
    • + +
    • + ))} +
    ) : uiState === 'error' ? (

    Unable to load bookmarks. From 952f0489130eaff05ae3fc6f631969172e1d556b Mon Sep 17 00:00:00 2001 From: Lim Chee Aun Date: Sat, 21 Jan 2023 23:36:31 +0800 Subject: [PATCH 013/108] Fix disabling Boosts Carousel not working --- src/utils/states.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/utils/states.js b/src/utils/states.js index 9726e008..faf1f87b 100644 --- a/src/utils/states.js +++ b/src/utils/states.js @@ -25,7 +25,7 @@ const states = proxy({ composeCharacterCount: 0, settings: { boostsCarousel: store.local.get('settings:boostsCarousel') - ? store.local.get('settings:boostsCarousel') + ? store.local.get('settings:boostsCarousel') === '1' : true, }, }); From 4760efe837eae9ed1cad2e5dcd758132aa3b5512 Mon Sep 17 00:00:00 2001 From: Lim Chee Aun Date: Sat, 21 Jan 2023 23:43:39 +0800 Subject: [PATCH 014/108] Need to pass the 'e' too --- src/components/link.jsx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/components/link.jsx b/src/components/link.jsx index 39698d26..5c1276eb 100644 --- a/src/components/link.jsx +++ b/src/components/link.jsx @@ -23,9 +23,9 @@ const Link = (props) => { href={`#${props.to}`} {...props} class={`${props.class || ''} ${isActive ? 'is-active' : ''}`} - onClick={() => { + onClick={(e) => { if (routerLocation) states.prevLocation = routerLocation; - props.onClick?.(); + props.onClick?.(e); }} /> ); From 2a44f3a670ac321b311d08adfb27a85900978ed2 Mon Sep 17 00:00:00 2001 From: Lim Chee Aun Date: Sun, 22 Jan 2023 00:37:46 +0800 Subject: [PATCH 015/108] Hidden way to update the account info Usually when avatar or name changes --- src/components/avatar.jsx | 3 ++- src/pages/settings.jsx | 24 +++++++++++++++++++++--- 2 files changed, 23 insertions(+), 4 deletions(-) diff --git a/src/components/avatar.jsx b/src/components/avatar.jsx index f8e1ca29..1ceb7fe4 100644 --- a/src/components/avatar.jsx +++ b/src/components/avatar.jsx @@ -9,7 +9,7 @@ const SIZES = { xxxl: 64, }; -function Avatar({ url, size, alt = '' }) { +function Avatar({ url, size, alt = '', ...props }) { size = SIZES[size] || size || SIZES.m; return ( {!!url && ( {alt} diff --git a/src/pages/settings.jsx b/src/pages/settings.jsx index b9aa6987..524256ce 100644 --- a/src/pages/settings.jsx +++ b/src/pages/settings.jsx @@ -1,6 +1,6 @@ import './settings.css'; -import { useRef, useState } from 'preact/hooks'; +import { useReducer, useRef, useState } from 'preact/hooks'; import { useSnapshot } from 'valtio'; import Avatar from '../components/avatar'; @@ -27,6 +27,8 @@ function Settings({ onClose }) { const moreThanOneAccount = accounts.length > 1; const [currentDefault, setCurrentDefault] = useState(0); + const [_, reload] = useReducer((x) => x + 1, 0); + return (

    @@ -40,14 +42,30 @@ function Settings({ onClose }) { const isCurrent = account.info.id === currentAccount; const isDefault = i === (currentDefault || 0); return ( -
  • +
  • {moreThanOneAccount && ( )} - + { + if (isCurrent) { + try { + const info = await masto.v1.accounts.fetch( + account.info.id, + ); + console.log('fetched account info', info); + account.info = info; + store.local.setJSON('accounts', accounts); + reload(); + } catch (e) {} + } + }} + /> Date: Sun, 22 Jan 2023 16:27:00 +0800 Subject: [PATCH 016/108] New feature: ALT badge in image carousel Adjusted the layout and fix some styles as well --- src/app.css | 21 ++++----- src/components/status.css | 54 +++++++++++++++++++++ src/components/status.jsx | 99 +++++++++++++++++++++++++++------------ 3 files changed, 132 insertions(+), 42 deletions(-) diff --git a/src/app.css b/src/app.css index 690905a1..24c8fade 100644 --- a/src/app.css +++ b/src/app.css @@ -625,10 +625,6 @@ a[href^='http'][rel*='nofollow']:visited:not(:has(div)) { top: 0; top: env(safe-area-inset-top, 0); } -.carousel-controls { - bottom: 0; - bottom: env(safe-area-inset-bottom, 0); -} :is(.carousel-top-controls, .carousel-controls) { position: fixed; left: 0; @@ -670,14 +666,15 @@ button.carousel-dot { font-weight: bold; backdrop-filter: none !important; } -button.carousel-dot:is(:hover, :focus) button.carousel-dot.active, -button.carousel-dot[disabled].active { +button.carousel-dot[disabled] { + pointer-events: none; +} +button.carousel-dot:is(:hover, :focus, .active, [disabled].active) { color: var(--link-color) !important; } -button.carousel-dot.active, -button.carousel-dot[disabled].active { +button.carousel-dot:is(.active, [disabled].active) { opacity: 1; - transform: scale(2) translateY(-0.5px); + transform: scale(2.2) translateY(-0.5px); } @media (hover: hover) { .carousel-top-controls { @@ -685,7 +682,7 @@ button.carousel-dot[disabled].active { transition: transform 0.2s ease-in-out; } .carousel-controls { - transform: translateY(100%); + transform: scaleX(200%); transition: transform 0.2s ease-in-out; } :is(.carousel-top-controls, .carousel-controls)[hidden] { @@ -1061,9 +1058,9 @@ meter.donut:is(.danger, .explode):after { .box { padding: 32px; } - :is(.carousel-top-controls, .carousel-controls) { + /* :is(.carousel-top-controls, .carousel-controls) { padding: 32px; - } + } */ li:has(.boost-carousel) { width: 95vw; max-width: calc(320px * 3.3); diff --git a/src/components/status.css b/src/components/status.css index e921d0bf..489b08b0 100644 --- a/src/components/status.css +++ b/src/components/status.css @@ -488,6 +488,60 @@ background-blend-mode: multiply; } +.carousel-item { + position: relative; +} +.carousel-item button.media-alt { + position: absolute; + bottom: 16px; + bottom: calc(16px + env(safe-area-inset-bottom)); + left: 16px; + left: calc(16px + env(safe-area-inset-left)); + text-align: left; + border-radius: 8px; + color: var(--text-color); + padding: 4px 8px 4px 4px; + border: 1px solid var(--outline-color); + box-shadow: 0 4px 16px var(--outline-color); + max-width: min(40em, calc(100% - 32px)); + display: flex; + align-items: center; + gap: 4px; + font-size: 90%; +} +.carousel-item button.media-alt .media-alt-desc { + white-space: nowrap; + overflow: hidden; + text-overflow: ellipsis; +} +@media (min-width: 40em) { + .carousel-item button.media-alt .media-alt-desc { + white-space: normal; + display: -webkit-box; + display: box; + -webkit-box-orient: vertical; + box-orient: vertical; + -webkit-line-clamp: 2; + line-clamp: 2; + line-height: 1.4; + } +} +.carousel-item button.media-alt[hidden] { + opacity: 0; +} +@media (hover: hover) { + .carousel-item button.media-alt { + transform: translateY(200%); + transition: transform 0.2s ease-in-out; + } + .carousel-item:hover button.media-alt { + transform: translateY(0); + } + .carousel-item button.media-alt[hidden] { + opacity: 1; + } +} + /* CARD */ .card { diff --git a/src/components/status.jsx b/src/components/status.jsx index ac07f0ef..cbdd0e90 100644 --- a/src/components/status.jsx +++ b/src/components/status.jsx @@ -1320,6 +1320,8 @@ function Carousel({ mediaAttachments, index = 0, onClose = () => {} }) { useHotkeys('esc', onClose, [onClose]); + const [showMediaAlt, setShowMediaAlt] = useState(false); + return ( <>
    {} }) { } }} > + {!!media.description && ( + + )} ); })}
    - {mediaAttachments?.length > 1 && ( - + {mediaAttachments?.length > 1 && ( + )} + {!!showMediaAlt && ( + { + if (e.target === e.currentTarget) { + setShowMediaAlt(false); + } + }} + > +
    +
    +

    Media description

    +
    +
    +

    {showMediaAlt}

    +
    +
    +
    + )} ); } From d093bddf435cf0116f4283fe45a8e412423b79a8 Mon Sep 17 00:00:00 2001 From: Lim Chee Aun Date: Sun, 22 Jan 2023 16:32:45 +0800 Subject: [PATCH 017/108] Lighten the purple further for dark mode --- src/index.css | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/index.css b/src/index.css index dda1bc31..80adfbe6 100644 --- a/src/index.css +++ b/src/index.css @@ -49,7 +49,7 @@ @media (prefers-color-scheme: dark) { :root { --blue-color: CornflowerBlue; - --purple-color: mediumpurple; + --purple-color: #b190f1; --green-color: lightgreen; --orange-color: orange; --bg-color: #242526; @@ -61,7 +61,7 @@ --link-light-color: #6494ed99; --link-faded-color: #6494ed88; --link-bg-hover-color: #34353799; - --reblog-faded-color: #9370d844; + --reblog-faded-color: #b190f155; --reply-to-text-color: var(--reply-to-color); --reply-to-faded-color: #ffa60033; --divider-color: rgba(255, 255, 255, 0.1); From 78839913ed1adb338c6b71ba769bce3409022f2a Mon Sep 17 00:00:00 2001 From: Lim Chee Aun Date: Sun, 22 Jan 2023 16:39:04 +0800 Subject: [PATCH 018/108] s/Icon/icon --- src/pages/bookmarks.jsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/pages/bookmarks.jsx b/src/pages/bookmarks.jsx index bdd4e660..72d40efa 100644 --- a/src/pages/bookmarks.jsx +++ b/src/pages/bookmarks.jsx @@ -1,6 +1,6 @@ import { useEffect, useRef, useState } from 'preact/hooks'; -import Icon from '../components/Icon'; +import Icon from '../components/icon'; import Link from '../components/link'; import Loader from '../components/Loader'; import Status from '../components/status'; From 0ecd7f572f626fa9fa0d8660b0fb000d57ca93da Mon Sep 17 00:00:00 2001 From: Lim Chee Aun Date: Sun, 22 Jan 2023 16:40:10 +0800 Subject: [PATCH 019/108] s/Loader/loader --- src/pages/bookmarks.jsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/pages/bookmarks.jsx b/src/pages/bookmarks.jsx index 72d40efa..e5b7a941 100644 --- a/src/pages/bookmarks.jsx +++ b/src/pages/bookmarks.jsx @@ -2,7 +2,7 @@ import { useEffect, useRef, useState } from 'preact/hooks'; 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 useTitle from '../utils/useTitle'; From 7d409bde5b6c40f39bbb8dee76cb5bf8814ef679 Mon Sep 17 00:00:00 2001 From: Lim Chee Aun Date: Sun, 22 Jan 2023 16:49:58 +0800 Subject: [PATCH 020/108] Downgrade react-router Latest version changed something with hashrouter --- package-lock.json | 50 +++++++++++++++++++++++------------------------ package.json | 2 +- 2 files changed, 26 insertions(+), 26 deletions(-) diff --git a/package-lock.json b/package-lock.json index 9886c2ef..86775d23 100644 --- a/package-lock.json +++ b/package-lock.json @@ -21,7 +21,7 @@ "preact": "~10.11.3", "react-hotkeys-hook": "~4.3.2", "react-intersection-observer": "~9.4.1", - "react-router-dom": "~6.7.0", + "react-router-dom": "6.6.2", "string-length": "~5.0.1", "swiped-events": "~1.1.7", "toastify-js": "~1.12.0", @@ -2277,9 +2277,9 @@ } }, "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==", + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/@remix-run/router/-/router-1.2.1.tgz", + "integrity": "sha512-XiY0IsyHR+DXYS5vBxpoBe/8veTeoRpMHP+vDosLZxL5bnpetzI0igkxkLZS235ldLzyfkxF+2divEwWHP3vMQ==", "engines": { "node": ">=14" } @@ -4665,11 +4665,11 @@ } }, "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==", + "version": "6.6.2", + "resolved": "https://registry.npmjs.org/react-router/-/react-router-6.6.2.tgz", + "integrity": "sha512-uJPG55Pek3orClbURDvfljhqFvMgJRo59Pktywkk8hUUkTY2aRfza8Yhl/vZQXs+TNQyr6tu+uqz/fLxPICOGQ==", "dependencies": { - "@remix-run/router": "1.3.0" + "@remix-run/router": "1.2.1" }, "engines": { "node": ">=14" @@ -4679,12 +4679,12 @@ } }, "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==", + "version": "6.6.2", + "resolved": "https://registry.npmjs.org/react-router-dom/-/react-router-dom-6.6.2.tgz", + "integrity": "sha512-6SCDXxRQqW5af8ImOqKza7icmQ47/EMbz572uFjzvcArg3lZ+04PxSPp8qGs+p2Y+q+b+S/AjXv8m8dyLndIIA==", "dependencies": { - "@remix-run/router": "1.3.0", - "react-router": "6.7.0" + "@remix-run/router": "1.2.1", + "react-router": "6.6.2" }, "engines": { "node": ">=14" @@ -7419,9 +7419,9 @@ } }, "@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==" + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/@remix-run/router/-/router-1.2.1.tgz", + "integrity": "sha512-XiY0IsyHR+DXYS5vBxpoBe/8veTeoRpMHP+vDosLZxL5bnpetzI0igkxkLZS235ldLzyfkxF+2divEwWHP3vMQ==" }, "@rollup/plugin-replace": { "version": "5.0.2", @@ -9197,20 +9197,20 @@ "requires": {} }, "react-router": { - "version": "6.7.0", - "resolved": "https://registry.npmjs.org/react-router/-/react-router-6.7.0.tgz", - "integrity": "sha512-KNWlG622ddq29MAM159uUsNMdbX8USruoKnwMMQcs/QWZgFUayICSn2oB7reHce1zPj6CG18kfkZIunSSRyGHg==", + "version": "6.6.2", + "resolved": "https://registry.npmjs.org/react-router/-/react-router-6.6.2.tgz", + "integrity": "sha512-uJPG55Pek3orClbURDvfljhqFvMgJRo59Pktywkk8hUUkTY2aRfza8Yhl/vZQXs+TNQyr6tu+uqz/fLxPICOGQ==", "requires": { - "@remix-run/router": "1.3.0" + "@remix-run/router": "1.2.1" } }, "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==", + "version": "6.6.2", + "resolved": "https://registry.npmjs.org/react-router-dom/-/react-router-dom-6.6.2.tgz", + "integrity": "sha512-6SCDXxRQqW5af8ImOqKza7icmQ47/EMbz572uFjzvcArg3lZ+04PxSPp8qGs+p2Y+q+b+S/AjXv8m8dyLndIIA==", "requires": { - "@remix-run/router": "1.3.0", - "react-router": "6.7.0" + "@remix-run/router": "1.2.1", + "react-router": "6.6.2" } }, "regenerate": { diff --git a/package.json b/package.json index 444d2e4c..4020465e 100644 --- a/package.json +++ b/package.json @@ -23,7 +23,7 @@ "preact": "~10.11.3", "react-hotkeys-hook": "~4.3.2", "react-intersection-observer": "~9.4.1", - "react-router-dom": "~6.7.0", + "react-router-dom": "6.6.2", "string-length": "~5.0.1", "swiped-events": "~1.1.7", "toastify-js": "~1.12.0", From a495e1fa686e181d8a62da8b3f68c8a90511baec Mon Sep 17 00:00:00 2001 From: Lim Chee Aun Date: Sun, 22 Jan 2023 17:19:37 +0800 Subject: [PATCH 021/108] Only do transition for large screens --- src/app.css | 15 +++++++-------- 1 file changed, 7 insertions(+), 8 deletions(-) diff --git a/src/app.css b/src/app.css index 24c8fade..468cf266 100644 --- a/src/app.css +++ b/src/app.css @@ -62,14 +62,6 @@ a[href^='http'][rel*='nofollow']:visited:not(:has(div)) { 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 { min-height: 100vh; min-height: 100dvh; @@ -1002,6 +994,13 @@ meter.donut:is(.danger, .explode):after { #app { display: flex; } + .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-backdrop .deck { width: 50%; min-width: 40em; From e7dffecfe056e02d870685680b9f5e7965536130 Mon Sep 17 00:00:00 2001 From: Lim Chee Aun Date: Sun, 22 Jan 2023 19:21:24 +0800 Subject: [PATCH 022/108] Need to reset cachedStatusesMap too --- src/pages/status.jsx | 1 + 1 file changed, 1 insertion(+) diff --git a/src/pages/status.jsx b/src/pages/status.jsx index a98b1afc..04a32d71 100644 --- a/src/pages/status.jsx +++ b/src/pages/status.jsx @@ -25,6 +25,7 @@ import useTitle from '../utils/useTitle'; const LIMIT = 40; function resetScrollPosition(id) { + delete cachedStatusesMap.current[id]; delete states.scrollPositions[id]; } From a522511e0e34f947445825ecbab69379c077f182 Mon Sep 17 00:00:00 2001 From: Lim Chee Aun Date: Sun, 22 Jan 2023 19:33:45 +0800 Subject: [PATCH 023/108] Add "Mark media as sensitive" checkbox It does the same thing as spoiler text toggle. --- src/components/compose.css | 10 ++++++++++ src/components/compose.jsx | 14 ++++++++++++++ 2 files changed, 24 insertions(+) diff --git a/src/components/compose.css b/src/components/compose.css index 0d69efd1..3f2349c8 100644 --- a/src/components/compose.css +++ b/src/components/compose.css @@ -324,6 +324,16 @@ margin-bottom: 4px; } +#compose-container .media-sensitive { + padding: 8px; + background-color: var(--bg-blur-color); + border-radius: 8px; + cursor: pointer; +} +#compose-container .media-sensitive > * { + vertical-align: middle; +} + #compose-container form .poll { background-color: var(--bg-faded-color); border-radius: 8px; diff --git a/src/components/compose.jsx b/src/components/compose.jsx index 78453357..919fb7f6 100644 --- a/src/components/compose.jsx +++ b/src/components/compose.jsx @@ -876,6 +876,20 @@ function Compose({ /> ); })} +
    )} {!!poll && ( From 4c05692ef57be4053a23272d223f31ed22446072 Mon Sep 17 00:00:00 2001 From: Lim Chee Aun Date: Sun, 22 Jan 2023 20:29:48 +0800 Subject: [PATCH 024/108] This account resolving thingie is getting ridiculous --- src/components/account.jsx | 25 ++++++++++++++++--- src/components/status.jsx | 35 ++------------------------- src/utils/handle-account-links.js | 40 +++++++++++++++++++++++++++++++ 3 files changed, 64 insertions(+), 36 deletions(-) create mode 100644 src/utils/handle-account-links.js diff --git a/src/components/account.jsx b/src/components/account.jsx index 075421fb..7554df00 100644 --- a/src/components/account.jsx +++ b/src/components/account.jsx @@ -3,6 +3,7 @@ import './account.css'; import { useEffect, useState } from 'preact/hooks'; import enhanceContent from '../utils/enhance-content'; +import handleAccountLinks from '../utils/handle-account-links'; import shortenNumber from '../utils/shorten-number'; import store from '../utils/store'; @@ -27,12 +28,29 @@ function Account({ account }) { setInfo(info); setUIState('default'); } catch (e) { - alert(e); - setUIState('error'); + try { + const result = await masto.v2.search({ + q: account, + type: 'accounts', + limit: 1, + resolve: true, + }); + if (result.accounts.length) { + setInfo(result.accounts[0]); + setUIState('default'); + return; + } + alert('Account not found'); + setUIState('error'); + } catch (err) { + alert(err); + console.error(err); + setUIState('error'); + } } })(); } - }, []); + }, [account]); const { acct, @@ -138,6 +156,7 @@ function Account({ account }) { )}
    { - let { target } = e; - if (target.parentNode.tagName.toLowerCase() === 'a') { - target = target.parentNode; - } - if ( - target.tagName.toLowerCase() === 'a' && - target.classList.contains('u-url') - ) { - const targetText = ( - target.querySelector('span') || target - ).innerText.trim(); - const username = targetText.replace(/^@/, ''); - const url = target.getAttribute('href'); - const mention = mentions.find( - (mention) => - mention.username === username || - mention.acct === username || - mention.url === url, - ); - if (mention) { - e.preventDefault(); - e.stopPropagation(); - states.showAccount = mention.acct; - } else if (!/^http/i.test(targetText)) { - console.log('mention not found', targetText); - e.preventDefault(); - e.stopPropagation(); - const href = target.getAttribute('href'); - states.showAccount = href; - } - } - }} + onClick={handleAccountLinks({ mentions })} dangerouslySetInnerHTML={{ __html: enhanceContent(content, { emojis, diff --git a/src/utils/handle-account-links.js b/src/utils/handle-account-links.js new file mode 100644 index 00000000..b7156ffc --- /dev/null +++ b/src/utils/handle-account-links.js @@ -0,0 +1,40 @@ +import states from './states'; + +function handleAccountLinks(opts) { + const { mentions = [] } = opts || {}; + return (e) => { + let { target } = e; + if (target.parentNode.tagName.toLowerCase() === 'a') { + target = target.parentNode; + } + if ( + target.tagName.toLowerCase() === 'a' && + target.classList.contains('u-url') + ) { + const targetText = ( + target.querySelector('span') || target + ).innerText.trim(); + const username = targetText.replace(/^@/, ''); + const url = target.getAttribute('href'); + const mention = mentions.find( + (mention) => + mention.username === username || + mention.acct === username || + mention.url === url, + ); + if (mention) { + e.preventDefault(); + e.stopPropagation(); + states.showAccount = mention.acct; + } else if (!/^http/i.test(targetText)) { + console.log('mention not found', targetText); + e.preventDefault(); + e.stopPropagation(); + const href = target.getAttribute('href'); + states.showAccount = href; + } + } + }; +} + +export default handleAccountLinks; From b72f683a970c29c98266d1eb47ed181ea8693d53 Mon Sep 17 00:00:00 2001 From: Lim Chee Aun Date: Sun, 22 Jan 2023 20:50:11 +0800 Subject: [PATCH 025/108] Add "12 hours" option for poll duration --- src/components/compose.jsx | 1 + 1 file changed, 1 insertion(+) diff --git a/src/components/compose.jsx b/src/components/compose.jsx index 919fb7f6..31f2a29b 100644 --- a/src/components/compose.jsx +++ b/src/components/compose.jsx @@ -45,6 +45,7 @@ const expiryOptions = { '30 minutes': 30 * 60, '1 hour': 60 * 60, '6 hours': 6 * 60 * 60, + '12 hours': 12 * 60 * 60, '1 day': 24 * 60 * 60, '3 days': 3 * 24 * 60 * 60, '7 days': 7 * 24 * 60 * 60, From 3213e8503e0314133a87120ba920c2e378a5fcb6 Mon Sep 17 00:00:00 2001 From: Lim Chee Aun Date: Sun, 22 Jan 2023 23:57:43 +0800 Subject: [PATCH 026/108] Fix alt tag text got chopped off at the bottom --- src/components/status.css | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/components/status.css b/src/components/status.css index 489b08b0..d49c9f1f 100644 --- a/src/components/status.css +++ b/src/components/status.css @@ -513,6 +513,7 @@ white-space: nowrap; overflow: hidden; text-overflow: ellipsis; + line-height: 1.4; } @media (min-width: 40em) { .carousel-item button.media-alt .media-alt-desc { @@ -523,7 +524,6 @@ box-orient: vertical; -webkit-line-clamp: 2; line-clamp: 2; - line-height: 1.4; } } .carousel-item button.media-alt[hidden] { From 348e7a52c905bf23b44af86eaeee90cb81e6f3a8 Mon Sep 17 00:00:00 2001 From: Lim Chee Aun Date: Mon, 23 Jan 2023 17:58:33 +0800 Subject: [PATCH 027/108] Fix stupid mistake --- src/pages/status.jsx | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) diff --git a/src/pages/status.jsx b/src/pages/status.jsx index 04a32d71..933bed5a 100644 --- a/src/pages/status.jsx +++ b/src/pages/status.jsx @@ -16,7 +16,6 @@ import Status from '../components/status'; import htmlContentLength from '../utils/html-content-length'; import shortenNumber from '../utils/shorten-number'; import states, { saveStatus, threadifyStatus } from '../utils/states'; -import store from '../utils/store'; import { getCurrentAccount } from '../utils/store-utils'; import useDebouncedCallback from '../utils/useDebouncedCallback'; import useScroll from '../utils/useScroll'; @@ -24,8 +23,9 @@ import useTitle from '../utils/useTitle'; const LIMIT = 40; +let cachedStatusesMap = {}; function resetScrollPosition(id) { - delete cachedStatusesMap.current[id]; + delete cachedStatusesMap[id]; delete states.scrollPositions[id]; } @@ -61,13 +61,12 @@ function StatusPage() { }, [id, uiState !== 'loading']); const scrollOffsets = useRef(); - const cachedStatusesMap = useRef({}); const initContext = () => { console.debug('initContext', id); setUIState('loading'); let heroTimer; - const cachedStatuses = cachedStatusesMap.current[id]; + const cachedStatuses = cachedStatusesMap[id]; if (cachedStatuses) { // Case 1: It's cached, let's restore them to make it snappy const reallyCachedStatuses = cachedStatuses.filter( @@ -170,7 +169,7 @@ function StatusPage() { }; console.log({ allStatuses }); setStatuses(allStatuses); - cachedStatusesMap.current[id] = allStatuses; + cachedStatusesMap[id] = allStatuses; // Let's threadify this one // Note that all non-hero statuses will trigger saveStatus which will threadify them too @@ -242,7 +241,7 @@ function StatusPage() { // RESET states.scrollPositions = {}; states.reloadStatusPage = 0; - cachedStatusesMap.current = {}; + cachedStatusesMap = {}; }; }, []); From 56b6552d65838bda3582bf7cc111a81e096731b1 Mon Sep 17 00:00:00 2001 From: Lim Chee Aun Date: Mon, 23 Jan 2023 20:34:53 +0800 Subject: [PATCH 028/108] Show thread counter for small too --- src/components/status.jsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/components/status.jsx b/src/components/status.jsx index 15ceb352..73ea1e27 100644 --- a/src/components/status.jsx +++ b/src/components/status.jsx @@ -272,7 +272,7 @@ function Status({ ))}
    - {!withinContext && size !== 's' && ( + {!withinContext && ( <> {inReplyToAccountId === status.account?.id || !!snapStates.statusThreadNumber[id] ? ( From cdb5435796b3d471060b680fd8deffe4b5c1d04e Mon Sep 17 00:00:00 2001 From: Lim Chee Aun Date: Mon, 23 Jan 2023 20:35:15 +0800 Subject: [PATCH 029/108] Nicer radius for multi-media container --- src/components/status.css | 30 +++++++++++++++++++++++++++++- src/components/status.jsx | 2 +- 2 files changed, 30 insertions(+), 2 deletions(-) diff --git a/src/components/status.css b/src/components/status.css index d49c9f1f..f601066d 100644 --- a/src/components/status.css +++ b/src/components/status.css @@ -325,12 +325,40 @@ min-height: 160px; max-height: 60vh; } -.status .media { +.status .media-container .media { border-radius: 8px; overflow: hidden; min-height: 80px; border: 1px solid var(--outline-color); } +/* Special media borders */ +.status .media-container.media-eq2 .media:first-of-type { + border-radius: 8px 0 0 8px; +} +.status .media-container.media-eq2 .media:last-of-type { + border-radius: 0 8px 8px 0; +} +.status .media-container.media-eq3 .media:first-of-type { + border-radius: 8px 0 0 8px; +} +.status .media-container.media-eq3 .media:nth-of-type(2) { + border-radius: 0 8px 0 0; +} +.status .media-container.media-eq3 .media:last-of-type { + border-radius: 0 0 8px 0; +} +.status .media-container.media-eq4 .media:first-of-type { + border-radius: 8px 0 0 0; +} +.status .media-container.media-eq4 .media:nth-of-type(2) { + border-radius: 0 8px 0 0; +} +.status .media-container.media-eq4 .media:nth-of-type(3) { + border-radius: 0 0 0 8px; +} +.status .media-container.media-eq4 .media:last-of-type { + border-radius: 0 0 8px 0; +} .status .media:only-child { grid-area: span 2 / span 2; } diff --git a/src/components/status.jsx b/src/components/status.jsx index 73ea1e27..8bfa0a01 100644 --- a/src/components/status.jsx +++ b/src/components/status.jsx @@ -391,7 +391,7 @@ function Status({ )} {!!mediaAttachments.length && (
    2 ? 'media-gt2' : '' } ${mediaAttachments.length > 4 ? 'media-gt4' : ''}`} > From 51eaf4f1f2e6df0ce52db7897f98bda5ad026182 Mon Sep 17 00:00:00 2001 From: Lim Chee Aun Date: Tue, 24 Jan 2023 11:32:33 +0800 Subject: [PATCH 030/108] Replace blue-text buttons on carousel modal --- src/components/status.jsx | 8 ++++---- src/index.css | 5 +++++ 2 files changed, 9 insertions(+), 4 deletions(-) diff --git a/src/components/status.jsx b/src/components/status.jsx index 8bfa0a01..d9044d85 100644 --- a/src/components/status.jsx +++ b/src/components/status.jsx @@ -1355,7 +1355,7 @@ function Carousel({ mediaAttachments, index = 0, onClose = () => {} }) { )} -
    + {!isDefault && moreThanOneAccount && (
    +
  • ); From 19ee95d188522eb4ff5fd8edd48597242a51fb58 Mon Sep 17 00:00:00 2001 From: Lim Chee Aun Date: Tue, 24 Jan 2023 20:55:04 +0800 Subject: [PATCH 035/108] Add a key, just in case --- src/pages/home.jsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/pages/home.jsx b/src/pages/home.jsx index 32b2bee9..8f77bd7c 100644 --- a/src/pages/home.jsx +++ b/src/pages/home.jsx @@ -498,7 +498,7 @@ function BoostsCarousel({ boosts }) { const { id: statusID, reblog } = boost; const actualStatusID = reblog || statusID; return ( -
  • +
  • From 28281bb75289198aae1e2c1f930a3a80abc6d7a9 Mon Sep 17 00:00:00 2001 From: Lim Chee Aun Date: Tue, 24 Jan 2023 20:56:43 +0800 Subject: [PATCH 036/108] New component: Menu It's time to do this menu thing the right way instead of hacky CSS --- package-lock.json | 83 ++++++++++++++++++++++++++++++++++++++- package.json | 1 + src/app.css | 51 ++++++------------------ src/components/status.jsx | 51 +++++++++++++----------- src/main.jsx | 2 + src/pages/settings.css | 14 +++---- src/pages/settings.jsx | 69 ++++++++++++++++---------------- 7 files changed, 166 insertions(+), 105 deletions(-) diff --git a/package-lock.json b/package-lock.json index 86775d23..afe92294 100644 --- a/package-lock.json +++ b/package-lock.json @@ -10,6 +10,7 @@ "dependencies": { "@github/text-expander-element": "~2.3.0", "@iconify-icons/mingcute": "~1.2.3", + "@szhsin/react-menu": "~3.3.1", "dayjs": "~1.11.7", "dayjs-twitter": "~0.5.0", "fast-blurhash": "~1.1.2", @@ -2364,6 +2365,19 @@ "string.prototype.matchall": "^4.0.6" } }, + "node_modules/@szhsin/react-menu": { + "version": "3.3.1", + "resolved": "https://registry.npmjs.org/@szhsin/react-menu/-/react-menu-3.3.1.tgz", + "integrity": "sha512-e8vK+N1YWwTdYXElvRRf5GIImtcDecqTCzpAa0DkGAknKwfQwtQtUnBn+DECodwsWi5H5ONKTU+kn0qJ70hEYQ==", + "dependencies": { + "prop-types": "^15.7.2", + "react-transition-state": "^1.1.5" + }, + "peerDependencies": { + "react": ">=16.14.0", + "react-dom": ">=16.14.0" + } + }, "node_modules/@trivago/prettier-plugin-sort-imports": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/@trivago/prettier-plugin-sort-imports/-/prettier-plugin-sort-imports-4.0.0.tgz", @@ -4130,7 +4144,6 @@ "version": "1.4.0", "resolved": "https://registry.npmjs.org/loose-envify/-/loose-envify-1.4.0.tgz", "integrity": "sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q==", - "peer": true, "dependencies": { "js-tokens": "^3.0.0 || ^4.0.0" }, @@ -4370,6 +4383,14 @@ "node": ">=0.10.0" } }, + "node_modules/object-assign": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", + "integrity": "sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==", + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/object-inspect": { "version": "1.12.2", "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.12.2.tgz", @@ -4565,6 +4586,16 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/prop-types": { + "version": "15.8.1", + "resolved": "https://registry.npmjs.org/prop-types/-/prop-types-15.8.1.tgz", + "integrity": "sha512-oj87CgZICdulUohogVAR7AjlC0327U4el4L6eAvOqCeudMDVU0NThNaV+b9Df4dXgSP1gXMTnPdhfe/2qDH5cg==", + "dependencies": { + "loose-envify": "^1.4.0", + "object-assign": "^4.1.1", + "react-is": "^16.13.1" + } + }, "node_modules/proxy-compare": { "version": "2.4.0", "resolved": "https://registry.npmjs.org/proxy-compare/-/proxy-compare-2.4.0.tgz", @@ -4664,6 +4695,11 @@ "react": "^15.0.0 || ^16.0.0 || ^17.0.0 || ^18.0.0" } }, + "node_modules/react-is": { + "version": "16.13.1", + "resolved": "https://registry.npmjs.org/react-is/-/react-is-16.13.1.tgz", + "integrity": "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==" + }, "node_modules/react-router": { "version": "6.6.2", "resolved": "https://registry.npmjs.org/react-router/-/react-router-6.6.2.tgz", @@ -4694,6 +4730,15 @@ "react-dom": ">=16.8" } }, + "node_modules/react-transition-state": { + "version": "1.1.5", + "resolved": "https://registry.npmjs.org/react-transition-state/-/react-transition-state-1.1.5.tgz", + "integrity": "sha512-ITY2mZqc2dWG2eitJkYNdcSFW8aKeOlkL2A/vowRrLL8GH3J6Re/SpD/BLvQzrVOTqjsP0b5S9N10vgNNzwMUQ==", + "peerDependencies": { + "react": ">=16.8.0", + "react-dom": ">=16.8.0" + } + }, "node_modules/regenerate": { "version": "1.4.2", "resolved": "https://registry.npmjs.org/regenerate/-/regenerate-1.4.2.tgz", @@ -7477,6 +7522,15 @@ "string.prototype.matchall": "^4.0.6" } }, + "@szhsin/react-menu": { + "version": "3.3.1", + "resolved": "https://registry.npmjs.org/@szhsin/react-menu/-/react-menu-3.3.1.tgz", + "integrity": "sha512-e8vK+N1YWwTdYXElvRRf5GIImtcDecqTCzpAa0DkGAknKwfQwtQtUnBn+DECodwsWi5H5ONKTU+kn0qJ70hEYQ==", + "requires": { + "prop-types": "^15.7.2", + "react-transition-state": "^1.1.5" + } + }, "@trivago/prettier-plugin-sort-imports": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/@trivago/prettier-plugin-sort-imports/-/prettier-plugin-sort-imports-4.0.0.tgz", @@ -8819,7 +8873,6 @@ "version": "1.4.0", "resolved": "https://registry.npmjs.org/loose-envify/-/loose-envify-1.4.0.tgz", "integrity": "sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q==", - "peer": true, "requires": { "js-tokens": "^3.0.0 || ^4.0.0" } @@ -9001,6 +9054,11 @@ "integrity": "sha512-bdok/XvKII3nUpklnV6P2hxtMNrCboOjAcyBuQnWEhO665FwrSNRxU+AqpsyvO6LgGYPspN+lu5CLtw4jPRKNA==", "dev": true }, + "object-assign": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", + "integrity": "sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==" + }, "object-inspect": { "version": "1.12.2", "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.12.2.tgz", @@ -9131,6 +9189,16 @@ "integrity": "sha512-6UqkYefdogmzqAZWzJ7laYeJnaXDy2/J+ZqiiMtS7t7OfpXWTlaeGMwX8U6EFvPV/YWWEKRkS8hKS4k60WHTOg==", "dev": true }, + "prop-types": { + "version": "15.8.1", + "resolved": "https://registry.npmjs.org/prop-types/-/prop-types-15.8.1.tgz", + "integrity": "sha512-oj87CgZICdulUohogVAR7AjlC0327U4el4L6eAvOqCeudMDVU0NThNaV+b9Df4dXgSP1gXMTnPdhfe/2qDH5cg==", + "requires": { + "loose-envify": "^1.4.0", + "object-assign": "^4.1.1", + "react-is": "^16.13.1" + } + }, "proxy-compare": { "version": "2.4.0", "resolved": "https://registry.npmjs.org/proxy-compare/-/proxy-compare-2.4.0.tgz", @@ -9196,6 +9264,11 @@ "integrity": "sha512-IXpIsPe6BleFOEHKzKh5UjwRUaz/JYS0lT/HPsupWEQou2hDqjhLMStc5zyE3eQVT4Fk3FufM8Fw33qW1uyeiw==", "requires": {} }, + "react-is": { + "version": "16.13.1", + "resolved": "https://registry.npmjs.org/react-is/-/react-is-16.13.1.tgz", + "integrity": "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==" + }, "react-router": { "version": "6.6.2", "resolved": "https://registry.npmjs.org/react-router/-/react-router-6.6.2.tgz", @@ -9213,6 +9286,12 @@ "react-router": "6.6.2" } }, + "react-transition-state": { + "version": "1.1.5", + "resolved": "https://registry.npmjs.org/react-transition-state/-/react-transition-state-1.1.5.tgz", + "integrity": "sha512-ITY2mZqc2dWG2eitJkYNdcSFW8aKeOlkL2A/vowRrLL8GH3J6Re/SpD/BLvQzrVOTqjsP0b5S9N10vgNNzwMUQ==", + "requires": {} + }, "regenerate": { "version": "1.4.2", "resolved": "https://registry.npmjs.org/regenerate/-/regenerate-1.4.2.tgz", diff --git a/package.json b/package.json index 4020465e..a8f291f1 100644 --- a/package.json +++ b/package.json @@ -12,6 +12,7 @@ "dependencies": { "@github/text-expander-element": "~2.3.0", "@iconify-icons/mingcute": "~1.2.3", + "@szhsin/react-menu": "~3.3.1", "dayjs": "~1.11.7", "dayjs-twitter": "~0.5.0", "fast-blurhash": "~1.1.2", diff --git a/src/app.css b/src/app.css index de025319..bd76a0c4 100644 --- a/src/app.css +++ b/src/app.css @@ -805,54 +805,27 @@ button.carousel-dot:is(.active, [disabled].active) { /* MENU POPUP */ -.menu-container { - position: relative; -} -.menu-container button { - color: inherit !important; -} -.menu-container button:is(:hover, :active, :focus) { - background-color: var(--button-plain-bg-hover-color); -} -.menu-container menu { - position: absolute; - right: 0; - top: 0; - transform: translateY(-100%); - opacity: 0; - pointer-events: none; - padding: 8px 0; +.szh-menu { + padding: 8px 0 !important; margin: 0; font-size: 16px; background-color: var(--bg-color); - width: 10em; - list-style: none; - z-index: 100; border: 1px solid var(--outline-color); border-radius: 8px; - transition: all 0.2s ease-in-out; box-shadow: 0 0 8px var(--bg-faded-color), 0 4px 8px var(--bg-faded-color), 0 2px 4px var(--bg-faded-color); -} -.menu-container menu li { - margin: 0; - padding: 0; - list-style: none; -} -.menu-container > button:is(:hover, :active, :focus) + menu, -.menu-container menu:is(:hover, :active) { - opacity: 1; - pointer-events: auto; -} -.menu-container menu button { - width: 100%; text-align: left; - color: var(--text-color) !important; - border-radius: 0; } -.menu-container menu button:is(:hover, :focus) { - color: var(--bg-color) !important; - background-color: var(--link-color); +.szh-menu .szh-menu__item { + padding: 8px 16px !important; +} +.szh-menu + .szh-menu__item:not(.szh-menu__item--disabled, .szh-menu__item--hover) { + color: var(--text-color); +} +.szh-menu .szh-menu__item--hover { + color: var(--button-text-color); + background-color: var(--button-bg-color); } /* DONUT METER */ diff --git a/src/components/status.jsx b/src/components/status.jsx index d9044d85..d4a20aba 100644 --- a/src/components/status.jsx +++ b/src/components/status.jsx @@ -1,5 +1,6 @@ import './status.css'; +import { Menu, MenuItem } from '@szhsin/react-menu'; import { getBlurHashAverageColor } from 'fast-blurhash'; import mem from 'mem'; import { memo } from 'preact/compat'; @@ -587,30 +588,32 @@ function Status({ />
  • {isSelf && ( - - - - {isSelf && ( -
  • - -
  • - )} -
    -
    + + +
    + } + > + {isSelf && ( + { + states.showCompose = { + editStatus: status, + }; + }} + > + Edit… + + )} + )}
    diff --git a/src/main.jsx b/src/main.jsx index f1fd4def..bee7d956 100644 --- a/src/main.jsx +++ b/src/main.jsx @@ -1,5 +1,7 @@ import './index.css'; +import '@szhsin/react-menu/dist/core.css'; + import { render } from 'preact'; import { HashRouter } from 'react-router-dom'; diff --git a/src/pages/settings.css b/src/pages/settings.css index 2b940da9..54e14f34 100644 --- a/src/pages/settings.css +++ b/src/pages/settings.css @@ -29,7 +29,7 @@ padding: 0; list-style: none; } -#settings-container ul li { +#settings-container ul:not([role='menu']) > li { padding: 8px 0 16px; display: flex; justify-content: space-between; @@ -37,26 +37,26 @@ flex-wrap: wrap; border-bottom: var(--hairline-width) solid var(--outline-color); } -#settings-container ul li .current { +#settings-container ul:not([role='menu']) > li .current { margin-right: 8px; color: var(--green-color); opacity: 0.1; } -#settings-container ul li .current.is-current { +#settings-container ul:not([role='menu']) > li .current.is-current { opacity: 1; } -#settings-container ul li .current.is-current + .avatar { +#settings-container ul:not([role='menu']) > li .current.is-current + .avatar { box-shadow: 0 0 0 1.5px var(--green-color), 0 0 8px var(--green-color); } -#settings-container ul li > div { +#settings-container ul:not([role='menu']) > li > div { flex-grow: 1; max-width: 100%; } -#settings-container ul li > div.actions { +#settings-container ul:not([role='menu']) > li > div.actions { flex-basis: fit-content; margin-top: 8px; } -#settings-container ul li > div:last-child { +#settings-container ul:not([role='menu']) > li > div:last-child { text-align: right; } #settings-container div, diff --git a/src/pages/settings.jsx b/src/pages/settings.jsx index 8b086384..d0b5468e 100644 --- a/src/pages/settings.jsx +++ b/src/pages/settings.jsx @@ -1,5 +1,6 @@ import './settings.css'; +import { Menu, MenuItem } from '@szhsin/react-menu'; import { useReducer, useRef, useState } from 'preact/hooks'; import { useSnapshot } from 'valtio'; @@ -92,43 +93,45 @@ function Settings({ onClose }) { Switch )} - - {!isDefault && moreThanOneAccount && ( + { - // Move account to the top of the list - accounts.splice(i, 1); - accounts.unshift(account); - store.local.setJSON('accounts', accounts); - setCurrentDefault(i); - }} + title="More" + class="plain more-button" > - Set as default + - )} - {isCurrent && ( - <> - {' '} - - - )} - + } + > + { + // Move account to the top of the list + accounts.splice(i, 1); + accounts.unshift(account); + store.local.setJSON('accounts', accounts); + setCurrentDefault(i); + }} + > + Set as default + + { + const yes = confirm( + 'Are you sure you want to log out?', + ); + if (!yes) return; + accounts.splice(i, 1); + store.local.setJSON('accounts', accounts); + location.reload(); + }} + > + Log out + + ); From f16c29097c1aa411370da208f5f6a928aaec33aa Mon Sep 17 00:00:00 2001 From: Lim Chee Aun Date: Tue, 24 Jan 2023 21:10:44 +0800 Subject: [PATCH 037/108] Experiment: more radius for media --- src/components/status.css | 21 +++++++++++---------- 1 file changed, 11 insertions(+), 10 deletions(-) diff --git a/src/components/status.css b/src/components/status.css index f601066d..cf16b879 100644 --- a/src/components/status.css +++ b/src/components/status.css @@ -326,38 +326,39 @@ max-height: 60vh; } .status .media-container .media { - border-radius: 8px; + --media-radius: 16px; + border-radius: var(--media-radius); overflow: hidden; min-height: 80px; border: 1px solid var(--outline-color); } /* Special media borders */ .status .media-container.media-eq2 .media:first-of-type { - border-radius: 8px 0 0 8px; + border-radius: var(--media-radius) 0 0 var(--media-radius); } .status .media-container.media-eq2 .media:last-of-type { - border-radius: 0 8px 8px 0; + border-radius: 0 var(--media-radius) var(--media-radius) 0; } .status .media-container.media-eq3 .media:first-of-type { - border-radius: 8px 0 0 8px; + border-radius: var(--media-radius) 0 0 var(--media-radius); } .status .media-container.media-eq3 .media:nth-of-type(2) { - border-radius: 0 8px 0 0; + border-radius: 0 var(--media-radius) 0 0; } .status .media-container.media-eq3 .media:last-of-type { - border-radius: 0 0 8px 0; + border-radius: 0 0 var(--media-radius) 0; } .status .media-container.media-eq4 .media:first-of-type { - border-radius: 8px 0 0 0; + border-radius: var(--media-radius) 0 0 0; } .status .media-container.media-eq4 .media:nth-of-type(2) { - border-radius: 0 8px 0 0; + border-radius: 0 var(--media-radius) 0 0; } .status .media-container.media-eq4 .media:nth-of-type(3) { - border-radius: 0 0 0 8px; + border-radius: 0 0 0 var(--media-radius); } .status .media-container.media-eq4 .media:last-of-type { - border-radius: 0 0 8px 0; + border-radius: 0 0 var(--media-radius) 0; } .status .media:only-child { grid-area: span 2 / span 2; From 5f89c0673a02b6d8aa8532ad924e8346ec30f3b3 Mon Sep 17 00:00:00 2001 From: Lim Chee Aun Date: Tue, 24 Jan 2023 22:00:50 +0800 Subject: [PATCH 038/108] Fix wrong colors for menu on Mobile Safari --- src/app.css | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/app.css b/src/app.css index bd76a0c4..df202fd8 100644 --- a/src/app.css +++ b/src/app.css @@ -809,8 +809,8 @@ button.carousel-dot:is(.active, [disabled].active) { padding: 8px 0 !important; margin: 0; font-size: 16px; - background-color: var(--bg-color); - border: 1px solid var(--outline-color); + background-color: var(--bg-color) !important; + border: 1px solid var(--outline-color) !important; border-radius: 8px; box-shadow: 0 0 8px var(--bg-faded-color), 0 4px 8px var(--bg-faded-color), 0 2px 4px var(--bg-faded-color); From 0ea65b2cfdd4a1e5f127aa80853443fae24d20ba Mon Sep 17 00:00:00 2001 From: Lim Chee Aun Date: Tue, 24 Jan 2023 22:21:04 +0800 Subject: [PATCH 039/108] Remove old spoiler effect This was fun but sadly perf is really bad --- index.html | 57 --------------------------------------- src/components/status.css | 11 -------- 2 files changed, 68 deletions(-) diff --git a/index.html b/index.html index 4b81bc0e..a18f8685 100644 --- a/index.html +++ b/index.html @@ -45,62 +45,5 @@
    - - - - - - - - - - - - diff --git a/src/components/status.css b/src/components/status.css index cf16b879..3cf11e34 100644 --- a/src/components/status.css +++ b/src/components/status.css @@ -188,11 +188,9 @@ ~ *:not(.media-container, .card), .status .content-container.has-spoiler .spoiler ~ .card .meta-container { filter: blur(5px) invert(0.5); - /* filter: url(#spoiler); */ text-rendering: optimizeSpeed; image-rendering: crisp-edges; image-rendering: pixelated; - /* transform: translate3d(-5px, -5px, 0); */ pointer-events: none; user-select: none; contain: layout; @@ -206,15 +204,6 @@ image-rendering: pixelated; animation: none !important; } -/* @media (prefers-color-scheme: dark) { - .status - .content-container.has-spoiler - .spoiler - ~ *:not(.media-container, .card), - .status .content-container.has-spoiler .spoiler ~ .card .meta-container { - filter: url(#spoiler-dark); - } -} */ .status .content-container.show-spoiler .spoiler { border-style: dotted; } From a18659ee27410b88f17d5e4782cb740f8ce78a6a Mon Sep 17 00:00:00 2001 From: Lim Chee Aun Date: Tue, 24 Jan 2023 22:21:27 +0800 Subject: [PATCH 040/108] Test: Disable user-selection in carousel --- src/app.css | 1 + 1 file changed, 1 insertion(+) diff --git a/src/app.css b/src/app.css index df202fd8..e7fec66c 100644 --- a/src/app.css +++ b/src/app.css @@ -589,6 +589,7 @@ a[href^='http'][rel*='nofollow']:visited:not(:has(div)) { scrollbar-width: none; overscroll-behavior: contain; touch-action: pan-x; + user-select: none; } .carousel::-webkit-scrollbar { display: none; From 7f9742b50a8dff23de1db4a1c705ee8afe7407e5 Mon Sep 17 00:00:00 2001 From: Lim Chee Aun Date: Wed, 25 Jan 2023 00:26:47 +0800 Subject: [PATCH 041/108] Animate skeleton --- src/components/status.css | 16 +++++++++++++++- 1 file changed, 15 insertions(+), 1 deletion(-) diff --git a/src/components/status.css b/src/components/status.css index 3cf11e34..a988219a 100644 --- a/src/components/status.css +++ b/src/components/status.css @@ -79,12 +79,26 @@ .status.large.visibility-direct { background-image: var(--fade-in-out-bg), var(--yellow-stripes); } + +@keyframes skeleton-breathe { + 0% { + opacity: 1; + } + 40% { + opacity: 0.4; + } + 100% { + opacity: 1; + } +} .status.skeleton { color: var(--outline-color); + animation: skeleton-breathe 6s linear infinite; user-select: none; pointer-events: none; + contain: layout; + text-rendering: optimizeSpeed; } - .status.skeleton > .avatar { background-color: var(--outline-color); } From 5fb123f228372d0034c3849ab557c8d072bdaf97 Mon Sep 17 00:00:00 2001 From: Lim Chee Aun Date: Wed, 25 Jan 2023 00:40:05 +0800 Subject: [PATCH 042/108] Need to preserve white space in media descriptions --- src/components/status.jsx | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/src/components/status.jsx b/src/components/status.jsx index d4a20aba..b8f4a068 100644 --- a/src/components/status.jsx +++ b/src/components/status.jsx @@ -1446,7 +1446,13 @@ function Carousel({ mediaAttachments, index = 0, onClose = () => {} }) {

    Media description

    -

    {showMediaAlt}

    +

    + {showMediaAlt} +

    From 20b0a80c45f7979607a0805507975e5c6e09bb80 Mon Sep 17 00:00:00 2001 From: Lim Chee Aun Date: Wed, 25 Jan 2023 01:01:04 +0800 Subject: [PATCH 043/108] Delicate adjustments to the gradient hints --- src/components/status.css | 12 ++++++------ src/index.css | 6 +++--- 2 files changed, 9 insertions(+), 9 deletions(-) diff --git a/src/components/status.css b/src/components/status.css index a988219a..0ac1aafe 100644 --- a/src/components/status.css +++ b/src/components/status.css @@ -2,23 +2,23 @@ .status-reblog { background: linear-gradient( - to bottom right, + 160deg, var(--reblog-faded-color), - transparent 160px + transparent min(160px, 50%) ); } .status-reply-to { background: linear-gradient( - to bottom right, + 160deg, var(--reply-to-faded-color), - transparent 160px + transparent min(160px, 50%) ); } .status-reblog .status-reply-to { background: linear-gradient( - to top left, + -20deg, var(--reply-to-faded-color), - transparent 160px + transparent min(160px, 50%) ); } .visibility-direct { diff --git a/src/index.css b/src/index.css index 35657fb6..e1f96bd3 100644 --- a/src/index.css +++ b/src/index.css @@ -28,7 +28,7 @@ --reply-to-color: var(--orange-color); --reply-to-text-color: #b36200; --favourite-color: var(--red-color); - --reply-to-faded-color: #ffa6001a; + --reply-to-faded-color: #ffa60030; --outline-color: rgba(128, 128, 128, 0.2); --outline-hover-color: rgba(128, 128, 128, 0.7); --divider-color: rgba(0, 0, 0, 0.1); @@ -61,9 +61,9 @@ --link-light-color: #6494ed99; --link-faded-color: #6494ed88; --link-bg-hover-color: #34353799; - --reblog-faded-color: #b190f155; + --reblog-faded-color: #b190f141; --reply-to-text-color: var(--reply-to-color); - --reply-to-faded-color: #ffa60033; + --reply-to-faded-color: #ffa60027; --divider-color: rgba(255, 255, 255, 0.1); --bg-blur-color: #24252699; --backdrop-color: rgba(0, 0, 0, 0.5); From 7c4bda105bcfd3cc6b62f22696a98049cb15828c Mon Sep 17 00:00:00 2001 From: Lim Chee Aun Date: Wed, 25 Jan 2023 16:25:23 +0800 Subject: [PATCH 044/108] Fix profile metadata labels and values could have shortcode emojis --- src/components/account.jsx | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/src/components/account.jsx b/src/components/account.jsx index 7554df00..10c0b6a8 100644 --- a/src/components/account.jsx +++ b/src/components/account.jsx @@ -2,6 +2,7 @@ import './account.css'; import { useEffect, useState } from 'preact/hooks'; +import emojifyText from '../utils/emojify-text'; import enhanceContent from '../utils/enhance-content'; import handleAccountLinks from '../utils/handle-account-links'; import shortenNumber from '../utils/shorten-number'; @@ -171,12 +172,16 @@ function Account({ account }) { key={name} > - {name}{' '} + {' '} {!!verifiedAt && }

    From 5b8657a2ab08879f7f5a38e67d518c770810b158 Mon Sep 17 00:00:00 2001 From: Lim Chee Aun Date: Wed, 25 Jan 2023 16:39:57 +0800 Subject: [PATCH 045/108] Subtle fade in for menus --- src/app.css | 1 + 1 file changed, 1 insertion(+) diff --git a/src/app.css b/src/app.css index e7fec66c..a067b0b7 100644 --- a/src/app.css +++ b/src/app.css @@ -816,6 +816,7 @@ button.carousel-dot:is(.active, [disabled].active) { box-shadow: 0 0 8px var(--bg-faded-color), 0 4px 8px var(--bg-faded-color), 0 2px 4px var(--bg-faded-color); text-align: left; + animation: appear 0.15s ease-in-out; } .szh-menu .szh-menu__item { padding: 8px 16px !important; From c24a3ef251b3bca186609479d0ac0adbcaaafe5c Mon Sep 17 00:00:00 2001 From: Lim Chee Aun Date: Wed, 25 Jan 2023 16:41:28 +0800 Subject: [PATCH 046/108] Add fetch retries in status page --- package-lock.json | 48 ++++++++++++++++++++++++++++++++++++++++++++ package.json | 1 + src/pages/status.jsx | 10 +++++++-- 3 files changed, 57 insertions(+), 2 deletions(-) diff --git a/package-lock.json b/package-lock.json index afe92294..c49a8bfd 100644 --- a/package-lock.json +++ b/package-lock.json @@ -19,6 +19,7 @@ "just-debounce-it": "~3.2.0", "masto": "~5.5.1", "mem": "~9.0.2", + "p-retry": "~5.1.2", "preact": "~10.11.3", "react-hotkeys-hook": "~4.3.2", "react-intersection-observer": "~9.4.1", @@ -2538,6 +2539,11 @@ "@types/node": "*" } }, + "node_modules/@types/retry": { + "version": "0.12.1", + "resolved": "https://registry.npmjs.org/@types/retry/-/retry-0.12.1.tgz", + "integrity": "sha512-xoDlM2S4ortawSWORYqsdU+2rxdh4LRW9ytc3zmT37RIKQh6IHyKwwtKhKis9ah8ol07DCkZxPt8BBvPjC6v4g==" + }, "node_modules/@types/trusted-types": { "version": "2.0.2", "resolved": "https://registry.npmjs.org/@types/trusted-types/-/trusted-types-2.0.2.tgz", @@ -4443,6 +4449,21 @@ "node": ">=4" } }, + "node_modules/p-retry": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/p-retry/-/p-retry-5.1.2.tgz", + "integrity": "sha512-couX95waDu98NfNZV+i/iLt+fdVxmI7CbrrdC2uDWfPdUAApyxT4wmDlyOtR5KtTDmkDO0zDScDjDou9YHhd9g==", + "dependencies": { + "@types/retry": "0.12.1", + "retry": "^0.13.1" + }, + "engines": { + "node": "^12.20.0 || ^14.13.1 || >=16.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/param-case": { "version": "3.0.4", "resolved": "https://registry.npmjs.org/param-case/-/param-case-3.0.4.tgz", @@ -4859,6 +4880,14 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/retry": { + "version": "0.13.1", + "resolved": "https://registry.npmjs.org/retry/-/retry-0.13.1.tgz", + "integrity": "sha512-XQBQ3I8W1Cge0Seh+6gjj03LbmRFWuoszgK9ooCpwYIrhhoO80pfq4cUkU5DkknwfOfFteRwlZ56PYOGYyFWdg==", + "engines": { + "node": ">= 4" + } + }, "node_modules/reusify": { "version": "1.0.4", "resolved": "https://registry.npmjs.org/reusify/-/reusify-1.0.4.tgz", @@ -7663,6 +7692,11 @@ "@types/node": "*" } }, + "@types/retry": { + "version": "0.12.1", + "resolved": "https://registry.npmjs.org/@types/retry/-/retry-0.12.1.tgz", + "integrity": "sha512-xoDlM2S4ortawSWORYqsdU+2rxdh4LRW9ytc3zmT37RIKQh6IHyKwwtKhKis9ah8ol07DCkZxPt8BBvPjC6v4g==" + }, "@types/trusted-types": { "version": "2.0.2", "resolved": "https://registry.npmjs.org/@types/trusted-types/-/trusted-types-2.0.2.tgz", @@ -9096,6 +9130,15 @@ "resolved": "https://registry.npmjs.org/p-defer/-/p-defer-1.0.0.tgz", "integrity": "sha512-wB3wfAxZpk2AzOfUMJNL+d36xothRSyj8EXOa4f6GMqYDN9BJaaSISbsk+wS9abmnebVw95C2Kb5t85UmpCxuw==" }, + "p-retry": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/p-retry/-/p-retry-5.1.2.tgz", + "integrity": "sha512-couX95waDu98NfNZV+i/iLt+fdVxmI7CbrrdC2uDWfPdUAApyxT4wmDlyOtR5KtTDmkDO0zDScDjDou9YHhd9g==", + "requires": { + "@types/retry": "0.12.1", + "retry": "^0.13.1" + } + }, "param-case": { "version": "3.0.4", "resolved": "https://registry.npmjs.org/param-case/-/param-case-3.0.4.tgz", @@ -9387,6 +9430,11 @@ "supports-preserve-symlinks-flag": "^1.0.0" } }, + "retry": { + "version": "0.13.1", + "resolved": "https://registry.npmjs.org/retry/-/retry-0.13.1.tgz", + "integrity": "sha512-XQBQ3I8W1Cge0Seh+6gjj03LbmRFWuoszgK9ooCpwYIrhhoO80pfq4cUkU5DkknwfOfFteRwlZ56PYOGYyFWdg==" + }, "reusify": { "version": "1.0.4", "resolved": "https://registry.npmjs.org/reusify/-/reusify-1.0.4.tgz", diff --git a/package.json b/package.json index a8f291f1..265492bd 100644 --- a/package.json +++ b/package.json @@ -21,6 +21,7 @@ "just-debounce-it": "~3.2.0", "masto": "~5.5.1", "mem": "~9.0.2", + "p-retry": "~5.1.2", "preact": "~10.11.3", "react-hotkeys-hook": "~4.3.2", "react-intersection-observer": "~9.4.1", diff --git a/src/pages/status.jsx b/src/pages/status.jsx index 933bed5a..61c97a4b 100644 --- a/src/pages/status.jsx +++ b/src/pages/status.jsx @@ -1,6 +1,7 @@ import './status.css'; import debounce from 'just-debounce-it'; +import pRetry from 'p-retry'; import { useEffect, useMemo, useRef, useState } from 'preact/hooks'; import { useHotkeys } from 'react-hotkeys-hook'; import { InView } from 'react-intersection-observer'; @@ -87,8 +88,13 @@ function StatusPage() { } (async () => { - const heroFetch = () => masto.v1.statuses.fetch(id); - const contextFetch = masto.v1.statuses.fetchContext(id); + const heroFetch = () => + pRetry(() => masto.v1.statuses.fetch(id), { + retries: 3, + }); + const contextFetch = pRetry(() => masto.v1.statuses.fetchContext(id), { + retries: 2, + }); const hasStatus = !!snapStates.statuses[id]; let heroStatus = snapStates.statuses[id]; From d2c820ce5a0585888d5ff1fdb809b1712af274a5 Mon Sep 17 00:00:00 2001 From: Lim Chee Aun Date: Wed, 25 Jan 2023 16:42:01 +0800 Subject: [PATCH 047/108] Only show 'Set as default' if has more than 1 account --- src/pages/settings.jsx | 26 ++++++++++++++------------ 1 file changed, 14 insertions(+), 12 deletions(-) diff --git a/src/pages/settings.jsx b/src/pages/settings.jsx index d0b5468e..ba27dbb5 100644 --- a/src/pages/settings.jsx +++ b/src/pages/settings.jsx @@ -105,18 +105,20 @@ function Settings({ onClose }) { } > - { - // Move account to the top of the list - accounts.splice(i, 1); - accounts.unshift(account); - store.local.setJSON('accounts', accounts); - setCurrentDefault(i); - }} - > - Set as default - + {moreThanOneAccount && ( + { + // Move account to the top of the list + accounts.splice(i, 1); + accounts.unshift(account); + store.local.setJSON('accounts', accounts); + setCurrentDefault(i); + }} + > + Set as default + + )} { From 3392f57462fbd330ccac4e172a1d0560ee358dc9 Mon Sep 17 00:00:00 2001 From: Lim Chee Aun Date: Wed, 25 Jan 2023 20:37:48 +0800 Subject: [PATCH 048/108] Rewrite scrolling logic in carousel Just normal scroll event is enough, don't need intersection observer --- src/app.css | 1 + src/components/status.jsx | 60 ++++++++++++++++++++++----------------- 2 files changed, 35 insertions(+), 26 deletions(-) diff --git a/src/app.css b/src/app.css index a067b0b7..27fc40c1 100644 --- a/src/app.css +++ b/src/app.css @@ -664,6 +664,7 @@ button.carousel-dot { color: var(--text-insignificant-color) !important; font-weight: bold; backdrop-filter: none !important; + transition: all 0.2s; } button.carousel-dot[disabled] { pointer-events: none; diff --git a/src/components/status.jsx b/src/components/status.jsx index b8f4a068..339a9b2d 100644 --- a/src/components/status.jsx +++ b/src/components/status.jsx @@ -12,7 +12,6 @@ import { useState, } from 'preact/hooks'; import { useHotkeys } from 'react-hotkeys-hook'; -import { InView } from 'react-intersection-observer'; import 'swiped-events'; import useResizeObserver from 'use-resize-observer'; import { useSnapshot } from 'valtio'; @@ -26,7 +25,6 @@ import htmlContentLength from '../utils/html-content-length'; import shortenNumber from '../utils/shorten-number'; import states, { saveStatus } from '../utils/states'; import store from '../utils/store'; -import useDebouncedCallback from '../utils/useDebouncedCallback'; import visibilityIconsMap from '../utils/visibility-icons-map'; import Avatar from './avatar'; @@ -1260,19 +1258,8 @@ function Carousel({ mediaAttachments, index = 0, onClose = () => {} }) { const [currentIndex, setCurrentIndex] = useState(index); const carouselFocusItem = useRef(null); useLayoutEffect(() => { - carouselFocusItem.current?.node?.scrollIntoView(); + carouselFocusItem.current?.scrollIntoView(); }, []); - useLayoutEffect(() => { - carouselFocusItem.current?.node?.scrollIntoView({ - behavior: 'smooth', - }); - }, [currentIndex]); - - const onSnap = useDebouncedCallback((inView, i) => { - if (inView) { - setCurrentIndex(i); - } - }, 100); const [showControls, setShowControls] = useState(true); @@ -1294,6 +1281,24 @@ function Carousel({ mediaAttachments, index = 0, onClose = () => {} }) { const [showMediaAlt, setShowMediaAlt] = useState(false); + useEffect(() => { + let handleScroll = () => { + const { clientWidth, scrollLeft } = carouselRef.current; + const index = Math.round(scrollLeft / clientWidth); + setCurrentIndex(index); + }; + if (carouselRef.current) { + carouselRef.current.addEventListener('scroll', handleScroll, { + passive: true, + }); + } + return () => { + if (carouselRef.current) { + carouselRef.current.removeEventListener('scroll', handleScroll); + } + }; + }, []); + return ( <>

    {} }) { ? getBlurHashAverageColor(blurhash) : null; return ( - {} }) { }} tabindex="0" key={media.id} - ref={i === currentIndex ? carouselFocusItem : null} // InView options - root={carouselRef.current} - threshold={1} - onChange={(inView) => onSnap(inView, i)} + ref={i === currentIndex ? carouselFocusItem : null} onClick={(e) => { if (e.target !== e.currentTarget) { setShowControls(!showControls); @@ -1350,7 +1352,7 @@ function Carousel({ mediaAttachments, index = 0, onClose = () => {} }) { )} - +
    ); })} @@ -1377,7 +1379,10 @@ function Carousel({ mediaAttachments, index = 0, onClose = () => {} }) { onClick={(e) => { e.preventDefault(); e.stopPropagation(); - setCurrentIndex(i); + carouselRef.current.scrollTo({ + left: carouselRef.current.clientWidth * i, + behavior: 'smooth', + }); }} > • @@ -1410,10 +1415,10 @@ function Carousel({ mediaAttachments, index = 0, onClose = () => {} }) { onClick={(e) => { e.preventDefault(); e.stopPropagation(); - setCurrentIndex( - (currentIndex - 1 + mediaAttachments.length) % - mediaAttachments.length, - ); + carouselRef.current.scrollTo({ + left: carouselRef.current.clientWidth * (currentIndex - 1), + behavior: 'smooth', + }); }} > @@ -1425,7 +1430,10 @@ function Carousel({ mediaAttachments, index = 0, onClose = () => {} }) { onClick={(e) => { e.preventDefault(); e.stopPropagation(); - setCurrentIndex((currentIndex + 1) % mediaAttachments.length); + carouselRef.current.scrollTo({ + left: carouselRef.current.clientWidth * (currentIndex + 1), + behavior: 'smooth', + }); }} > From 9a898437f947dc02b96706cde88cdf139317888c Mon Sep 17 00:00:00 2001 From: Lim Chee Aun Date: Wed, 25 Jan 2023 21:53:43 +0800 Subject: [PATCH 049/108] Remove unused code --- src/pages/home.jsx | 12 ------------ 1 file changed, 12 deletions(-) diff --git a/src/pages/home.jsx b/src/pages/home.jsx index 8f77bd7c..5fec0550 100644 --- a/src/pages/home.jsx +++ b/src/pages/home.jsx @@ -368,17 +368,6 @@ function Home({ hidden }) { })} {showMore && ( <> - {/* { - if (inView) loadStatuses(); - }} - root={scrollableRef.current} - rootMargin="100px 0px" - > */} - {/* */}
  • Date: Thu, 26 Jan 2023 00:32:56 +0800 Subject: [PATCH 050/108] s/Spoiler text/Content warning --- src/components/compose.jsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/components/compose.jsx b/src/components/compose.jsx index 31f2a29b..01a52b83 100644 --- a/src/components/compose.jsx +++ b/src/components/compose.jsx @@ -780,7 +780,7 @@ function Compose({ ref={spoilerTextRef} type="text" name="spoilerText" - placeholder="Spoiler text" + placeholder="Content warning" disabled={uiState === 'loading'} class="spoiler-text-field" style={{ From a1e2207e96c6dcfb44fc8aee35981477552fa018 Mon Sep 17 00:00:00 2001 From: Lim Chee Aun Date: Thu, 26 Jan 2023 00:34:00 +0800 Subject: [PATCH 051/108] Reset input[type=file] after media is added Bug: Add file A, remove fie A, add file A = nothing happens --- src/components/compose.jsx | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/components/compose.jsx b/src/components/compose.jsx index 01a52b83..368d8c93 100644 --- a/src/components/compose.jsx +++ b/src/components/compose.jsx @@ -949,6 +949,8 @@ function Compose({ return attachments.concat(mediaFiles); }); } + // Reset + e.target.value = ''; }} /> From f2d50b0bac59f2eab1336722c86cef3c0a8a52a8 Mon Sep 17 00:00:00 2001 From: Lim Chee Aun Date: Thu, 26 Jan 2023 00:34:52 +0800 Subject: [PATCH 052/108] Add `lang` to all fields based on chosen language Reference: https://github.com/mastodon/mastodon/issues/19858 --- src/components/compose.jsx | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/src/components/compose.jsx b/src/components/compose.jsx index 368d8c93..0c544f0b 100644 --- a/src/components/compose.jsx +++ b/src/components/compose.jsx @@ -783,6 +783,7 @@ function Compose({ placeholder="Content warning" disabled={uiState === 'loading'} class="spoiler-text-field" + lang={language} style={{ opacity: sensitive ? 1 : 0, pointerEvents: sensitive ? 'auto' : 'none', @@ -847,6 +848,7 @@ function Compose({ } required={mediaAttachments.length === 0} disabled={uiState === 'loading'} + lang={language} onInput={() => { updateCharCount(); }} @@ -862,6 +864,7 @@ function Compose({ key={id || fileID || i} attachment={attachment} disabled={uiState === 'loading'} + lang={language} onDescriptionChange={(value) => { setMediaAttachments((attachments) => { const newAttachments = [...attachments]; @@ -895,6 +898,7 @@ function Compose({ )} {!!poll && ( {}, onRemove = () => {}, }) { @@ -1274,6 +1279,7 @@ function MediaAttachment({