From f7c69e56e99312af3d9b53c7ea0caa57648c37c2 Mon Sep 17 00:00:00 2001 From: steve mookie kong Date: Thu, 11 Apr 2024 21:28:38 -0700 Subject: [PATCH 01/37] Adding new self-hosted instance of Phanpy Added new self-hosted instance of Phanpy, halo.mookiesplace.com --- README.md | 1 + 1 file changed, 1 insertion(+) diff --git a/README.md b/README.md index fea2c6a..e893ece 100644 --- a/README.md +++ b/README.md @@ -205,6 +205,7 @@ These are self-hosted by other wonderful folks. - [phanpy.hear-me.social](https://phanpy.hear-me.social) by [@admin@hear-me.social](https://hear-me.social/@admin) - [phanpy.fulda.social](https://phanpy.fulda.social) by [@Ganneff@fulda.social](https://fulda.social/@Ganneff) - [phanpy.crmbl.uk](https://phanpy.crmbl.uk) by [@snail@crmbl.uk](https://mstdn.crmbl.uk/@snail) +- [halo.mookiesplace.com](https://halo.mookiesplace.com) by [@mookie@mookiesplace.com](https://mookiesplace.com/@mookie) > Note: Add yours by creating a pull request. From 9285a0ba9ad7f39b7ef3bc1d8e0a3af6ac97cc95 Mon Sep 17 00:00:00 2001 From: Lim Chee Aun Date: Fri, 12 Apr 2024 23:56:34 +0800 Subject: [PATCH 02/37] Test upgrade react-hotkeys-hook for the keys fix --- package-lock.json | 8 ++++---- package.json | 2 +- src/components/background-service.jsx | 2 +- src/components/columns.jsx | 2 +- src/components/compose-button.jsx | 3 +-- src/components/compose.jsx | 2 +- src/components/keyboard-shortcuts-help.jsx | 2 +- src/components/media-modal.jsx | 2 +- src/components/modal.jsx | 2 +- src/components/search-command.jsx | 3 ++- src/components/shortcuts.jsx | 2 +- src/components/status.jsx | 2 +- src/components/timeline.jsx | 2 +- src/pages/catchup.jsx | 2 +- src/pages/notifications.jsx | 2 +- src/pages/search.jsx | 2 +- src/pages/status.jsx | 2 +- src/utils/useHotkeys.js | 16 ++++++++++++++++ 18 files changed, 37 insertions(+), 21 deletions(-) create mode 100644 src/utils/useHotkeys.js diff --git a/package-lock.json b/package-lock.json index c7328de..e3e10c9 100644 --- a/package-lock.json +++ b/package-lock.json @@ -31,7 +31,7 @@ "p-throttle": "~6.1.0", "preact": "~10.20.1", "punycode": "~2.3.1", - "react-hotkeys-hook": "~4.5.0", + "react-hotkeys-hook": "~5.0.0-1", "react-intersection-observer": "~9.8.1", "react-quick-pinch-zoom": "~5.1.0", "react-router-dom": "6.6.2", @@ -7221,9 +7221,9 @@ } }, "node_modules/react-hotkeys-hook": { - "version": "4.5.0", - "resolved": "https://registry.npmjs.org/react-hotkeys-hook/-/react-hotkeys-hook-4.5.0.tgz", - "integrity": "sha512-Samb85GSgAWFQNvVt3PS90LPPGSf9mkH/r4au81ZP1yOIFayLC3QAvqTgGtJ8YEDMXtPmaVBs6NgipHO6h4Mug==", + "version": "5.0.0-1", + "resolved": "https://registry.npmjs.org/react-hotkeys-hook/-/react-hotkeys-hook-5.0.0-1.tgz", + "integrity": "sha512-nb8WD8IBrlEn3O2nlMoaBrWCxg2/vjgK3XFquDMM50qMbRzlOrR/p4PUlvh0rfuOMqEtKTGaL5BwkCh3rZ3T1w==", "peerDependencies": { "react": ">=16.8.1", "react-dom": ">=16.8.1" diff --git a/package.json b/package.json index a6f2497..90f940e 100644 --- a/package.json +++ b/package.json @@ -33,7 +33,7 @@ "p-throttle": "~6.1.0", "preact": "~10.20.1", "punycode": "~2.3.1", - "react-hotkeys-hook": "~4.5.0", + "react-hotkeys-hook": "~5.0.0-1", "react-intersection-observer": "~9.8.1", "react-quick-pinch-zoom": "~5.1.0", "react-router-dom": "6.6.2", diff --git a/src/components/background-service.jsx b/src/components/background-service.jsx index 15647fe..08026d6 100644 --- a/src/components/background-service.jsx +++ b/src/components/background-service.jsx @@ -1,10 +1,10 @@ import { memo } from 'preact/compat'; import { useEffect, useRef, useState } from 'preact/hooks'; -import { useHotkeys } from 'react-hotkeys-hook'; import { api } from '../utils/api'; import showToast from '../utils/show-toast'; import states, { saveStatus } from '../utils/states'; +import useHotkeys from '../utils/useHotkeys'; import useInterval from '../utils/useInterval'; import usePageVisibility from '../utils/usePageVisibility'; diff --git a/src/components/columns.jsx b/src/components/columns.jsx index f21e116..5d57b75 100644 --- a/src/components/columns.jsx +++ b/src/components/columns.jsx @@ -1,4 +1,3 @@ -import { useHotkeys } from 'react-hotkeys-hook'; import { useSnapshot } from 'valtio'; import Bookmarks from '../pages/bookmarks'; @@ -12,6 +11,7 @@ import Public from '../pages/public'; import Search from '../pages/search'; import Trending from '../pages/trending'; import states from '../utils/states'; +import useHotkeys from '../utils/useHotkeys'; import useTitle from '../utils/useTitle'; function Columns() { diff --git a/src/components/compose-button.jsx b/src/components/compose-button.jsx index ef64adf..a9c21f8 100644 --- a/src/components/compose-button.jsx +++ b/src/components/compose-button.jsx @@ -1,8 +1,7 @@ -import { useHotkeys } from 'react-hotkeys-hook'; - import openCompose from '../utils/open-compose'; import openOSK from '../utils/open-osk'; import states from '../utils/states'; +import useHotkeys from '../utils/useHotkeys'; import Icon from './icon'; diff --git a/src/components/compose.jsx b/src/components/compose.jsx index de3177a..a589fc3 100644 --- a/src/components/compose.jsx +++ b/src/components/compose.jsx @@ -5,7 +5,6 @@ import { MenuItem } from '@szhsin/react-menu'; import { deepEqual } from 'fast-equals'; import { forwardRef } from 'preact/compat'; import { useEffect, useMemo, useRef, useState } from 'preact/hooks'; -import { useHotkeys } from 'react-hotkeys-hook'; import stringLength from 'string-length'; import { uid } from 'uid/single'; import { useDebouncedCallback, useThrottledCallback } from 'use-debounce'; @@ -33,6 +32,7 @@ import { } from '../utils/store-utils'; import supports from '../utils/supports'; import useCloseWatcher from '../utils/useCloseWatcher'; +import useHotkeys from '../utils/useHotkeys'; import useInterval from '../utils/useInterval'; import visibilityIconsMap from '../utils/visibility-icons-map'; diff --git a/src/components/keyboard-shortcuts-help.jsx b/src/components/keyboard-shortcuts-help.jsx index a3925f2..b2bc290 100644 --- a/src/components/keyboard-shortcuts-help.jsx +++ b/src/components/keyboard-shortcuts-help.jsx @@ -1,10 +1,10 @@ import './keyboard-shortcuts-help.css'; import { memo } from 'preact/compat'; -import { useHotkeys } from 'react-hotkeys-hook'; import { useSnapshot } from 'valtio'; import states from '../utils/states'; +import useHotkeys from '../utils/useHotkeys'; import Icon from './icon'; import Modal from './modal'; diff --git a/src/components/media-modal.jsx b/src/components/media-modal.jsx index 968988b..e4bd93c 100644 --- a/src/components/media-modal.jsx +++ b/src/components/media-modal.jsx @@ -7,11 +7,11 @@ import { useRef, useState, } from 'preact/hooks'; -import { useHotkeys } from 'react-hotkeys-hook'; import { oklab2rgb, rgb2oklab } from '../utils/color-utils'; import showToast from '../utils/show-toast'; import states from '../utils/states'; +import useHotkeys from '../utils/useHotkeys'; import Icon from './icon'; import Link from './link'; diff --git a/src/components/modal.jsx b/src/components/modal.jsx index f1aaaf0..3cdc4d2 100644 --- a/src/components/modal.jsx +++ b/src/components/modal.jsx @@ -2,9 +2,9 @@ import './modal.css'; import { createPortal } from 'preact/compat'; import { useEffect, useRef } from 'preact/hooks'; -import { useHotkeys } from 'react-hotkeys-hook'; import useCloseWatcher from '../utils/useCloseWatcher'; +import useHotkeys from '../utils/useHotkeys'; const $modalContainer = document.getElementById('modal-container'); diff --git a/src/components/search-command.jsx b/src/components/search-command.jsx index c40a92f..cba31be 100644 --- a/src/components/search-command.jsx +++ b/src/components/search-command.jsx @@ -2,7 +2,8 @@ import './search-command.css'; import { memo } from 'preact/compat'; import { useRef, useState } from 'preact/hooks'; -import { useHotkeys } from 'react-hotkeys-hook'; + +import useHotkeys from '../utils/useHotkeys'; import SearchForm from './search-form'; diff --git a/src/components/shortcuts.jsx b/src/components/shortcuts.jsx index ddbe9cd..f043a94 100644 --- a/src/components/shortcuts.jsx +++ b/src/components/shortcuts.jsx @@ -3,7 +3,6 @@ import './shortcuts.css'; import { MenuDivider } from '@szhsin/react-menu'; import { memo } from 'preact/compat'; import { useRef, useState } from 'preact/hooks'; -import { useHotkeys } from 'react-hotkeys-hook'; import { useNavigate } from 'react-router-dom'; import { useSnapshot } from 'valtio'; @@ -11,6 +10,7 @@ import { SHORTCUTS_META } from '../components/shortcuts-settings'; import { api } from '../utils/api'; import { getLists } from '../utils/lists'; import states from '../utils/states'; +import useHotkeys from '../utils/useHotkeys'; import AsyncText from './AsyncText'; import Icon from './icon'; diff --git a/src/components/status.jsx b/src/components/status.jsx index 9c09314..a3f8fad 100644 --- a/src/components/status.jsx +++ b/src/components/status.jsx @@ -21,7 +21,6 @@ import { useState, } from 'preact/hooks'; import punycode from 'punycode'; -import { useHotkeys } from 'react-hotkeys-hook'; import { useLongPress } from 'use-long-press'; import { useSnapshot } from 'valtio'; @@ -56,6 +55,7 @@ import states, { getStatus, saveStatus, statusKey } from '../utils/states'; import statusPeek from '../utils/status-peek'; import store from '../utils/store'; import unfurlMastodonLink from '../utils/unfurl-link'; +import useHotkeys from '../utils/useHotkeys'; import useTruncated from '../utils/useTruncated'; import visibilityIconsMap from '../utils/visibility-icons-map'; diff --git a/src/components/timeline.jsx b/src/components/timeline.jsx index 6fe1273..33cdc9e 100644 --- a/src/components/timeline.jsx +++ b/src/components/timeline.jsx @@ -6,7 +6,6 @@ import { useRef, useState, } from 'preact/hooks'; -import { useHotkeys } from 'react-hotkeys-hook'; import { InView } from 'react-intersection-observer'; import { useDebouncedCallback } from 'use-debounce'; import { useSnapshot } from 'valtio'; @@ -17,6 +16,7 @@ import states, { statusKey } from '../utils/states'; import statusPeek from '../utils/status-peek'; import { isMediaFirstInstance } from '../utils/store-utils'; import { groupBoosts, groupContext } from '../utils/timeline-utils'; +import useHotkeys from '../utils/useHotkeys'; import useInterval from '../utils/useInterval'; import usePageVisibility from '../utils/usePageVisibility'; import useScroll from '../utils/useScroll'; diff --git a/src/pages/catchup.jsx b/src/pages/catchup.jsx index 9a5f318..6801681 100644 --- a/src/pages/catchup.jsx +++ b/src/pages/catchup.jsx @@ -14,7 +14,6 @@ import { useState, } from 'preact/hooks'; import punycode from 'punycode'; -import { useHotkeys } from 'react-hotkeys-hook'; import { useSearchParams } from 'react-router-dom'; import { uid } from 'uid/single'; @@ -42,6 +41,7 @@ import statusPeek from '../utils/status-peek'; import store from '../utils/store'; import { getCurrentAccountNS } from '../utils/store-utils'; import { assignFollowedTags } from '../utils/timeline-utils'; +import useHotkeys from '../utils/useHotkeys'; import useTitle from '../utils/useTitle'; const FILTER_CONTEXT = 'home'; diff --git a/src/pages/notifications.jsx b/src/pages/notifications.jsx index 2c46a43..b226978 100644 --- a/src/pages/notifications.jsx +++ b/src/pages/notifications.jsx @@ -3,7 +3,6 @@ import './notifications.css'; import { Fragment } from 'preact'; import { memo } from 'preact/compat'; import { useCallback, useEffect, useRef, useState } from 'preact/hooks'; -import { useHotkeys } from 'react-hotkeys-hook'; import { InView } from 'react-intersection-observer'; import { useSearchParams } from 'react-router-dom'; import { useSnapshot } from 'valtio'; @@ -29,6 +28,7 @@ import showToast from '../utils/show-toast'; import states, { saveStatus } from '../utils/states'; import { getCurrentInstance } from '../utils/store-utils'; import supports from '../utils/supports'; +import useHotkeys from '../utils/useHotkeys'; import usePageVisibility from '../utils/usePageVisibility'; import useScroll from '../utils/useScroll'; import useTitle from '../utils/useTitle'; diff --git a/src/pages/search.jsx b/src/pages/search.jsx index 37b30e4..315bc41 100644 --- a/src/pages/search.jsx +++ b/src/pages/search.jsx @@ -2,7 +2,6 @@ import './search.css'; import { useAutoAnimate } from '@formkit/auto-animate/preact'; import { useEffect, useLayoutEffect, useRef, useState } from 'preact/hooks'; -import { useHotkeys } from 'react-hotkeys-hook'; import { InView } from 'react-intersection-observer'; import { useParams, useSearchParams } from 'react-router-dom'; @@ -16,6 +15,7 @@ import Status from '../components/status'; import { api } from '../utils/api'; import { fetchRelationships } from '../utils/relationships'; import shortenNumber from '../utils/shorten-number'; +import useHotkeys from '../utils/useHotkeys'; import usePageVisibility from '../utils/usePageVisibility'; import useTitle from '../utils/useTitle'; diff --git a/src/pages/status.jsx b/src/pages/status.jsx index 50c3b30..25a9708 100644 --- a/src/pages/status.jsx +++ b/src/pages/status.jsx @@ -13,7 +13,6 @@ import { useState, } from 'preact/hooks'; import punycode from 'punycode'; -import { useHotkeys } from 'react-hotkeys-hook'; import { InView } from 'react-intersection-observer'; import { matchPath, useSearchParams } from 'react-router-dom'; import { useSnapshot } from 'valtio'; @@ -38,6 +37,7 @@ import states, { } from '../utils/states'; import statusPeek from '../utils/status-peek'; import { getCurrentAccount } from '../utils/store-utils'; +import useHotkeys from '../utils/useHotkeys'; import useScroll from '../utils/useScroll'; import useTitle from '../utils/useTitle'; diff --git a/src/utils/useHotkeys.js b/src/utils/useHotkeys.js new file mode 100644 index 0000000..d3a78a2 --- /dev/null +++ b/src/utils/useHotkeys.js @@ -0,0 +1,16 @@ +import { useHotkeys } from 'react-hotkeys-hook'; + +// Patch useHotKeys to add additional option +// E.g. useHotkeys('!', callback, {useKey: true}) + +export default function (keys, callback, options, deps) { + return useHotkeys( + keys, + callback, + { + useKey: true, + ...options, + }, + deps, + ); +} From aefda31c2a9a7f90d5e2273c782925966551eeca Mon Sep 17 00:00:00 2001 From: Lim Chee Aun Date: Sat, 13 Apr 2024 00:03:28 +0800 Subject: [PATCH 03/37] Temporary quick fix, remove dash from hashtag regex --- 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 a589fc3..b926f79 100644 --- a/src/components/compose.jsx +++ b/src/components/compose.jsx @@ -124,7 +124,7 @@ const MENTION_RE = new RegExp( // AI-generated, all other regexes are too complicated const HASHTAG_RE = new RegExp( - `(^|[^=\\/\\w])(#[a-z0-9_]+([a-z0-9_.-]+[a-z0-9_]+)?)(?![\\/\\w])`, + `(^|[^=\\/\\w])(#[a-z0-9_]+([a-z0-9_.]+[a-z0-9_]+)?)(?![\\/\\w])`, 'ig', ); From e782cc0dde5470e2ce6df972177ac55444bae64c Mon Sep 17 00:00:00 2001 From: Lim Chee Aun Date: Sat, 13 Apr 2024 00:06:34 +0800 Subject: [PATCH 04/37] Refactor set/get current account ID And add fallback for standalone mode where session storage is not enough --- src/app.jsx | 4 ++-- src/components/account-info.jsx | 9 +++----- src/components/media-post.jsx | 3 ++- src/components/nav-menu.jsx | 6 +++--- src/components/notification.jsx | 3 ++- src/components/shortcuts-settings.jsx | 5 +++-- src/components/status.jsx | 3 ++- src/pages/accounts.jsx | 5 +++-- src/pages/catchup.jsx | 4 ++-- src/utils/api.js | 3 ++- src/utils/filters.jsx | 4 ++-- src/utils/relationships.js | 4 ++-- src/utils/store-utils.js | 31 +++++++++++++++++++++++++-- 13 files changed, 57 insertions(+), 27 deletions(-) diff --git a/src/app.jsx b/src/app.jsx index 1b18093..e74246b 100644 --- a/src/app.jsx +++ b/src/app.jsx @@ -53,7 +53,7 @@ import { getAccessToken } from './utils/auth'; import focusDeck from './utils/focus-deck'; import states, { initStates, statusKey } from './utils/states'; import store from './utils/store'; -import { getCurrentAccount } from './utils/store-utils'; +import { getCurrentAccount, setCurrentAccountID } from './utils/store-utils'; import './utils/toast-alert'; window.__STATES__ = states; @@ -338,7 +338,7 @@ function App() { window.__IGNORE_GET_ACCOUNT_ERROR__ = true; const account = getCurrentAccount(); if (account) { - store.session.set('currentAccount', account.info.id); + setCurrentAccountID(account.info.id); const { client } = api({ account }); const { instance } = client; // console.log('masto', masto); diff --git a/src/components/account-info.jsx b/src/components/account-info.jsx index f93fcca..35ea89e 100644 --- a/src/components/account-info.jsx +++ b/src/components/account-info.jsx @@ -22,7 +22,7 @@ import shortenNumber from '../utils/shorten-number'; import showToast from '../utils/show-toast'; import states, { hideAllModals } from '../utils/states'; import store from '../utils/store'; -import { updateAccount } from '../utils/store-utils'; +import { getCurrentAccountID, updateAccount } from '../utils/store-utils'; import AccountBlock from './account-block'; import Avatar from './avatar'; @@ -198,10 +198,7 @@ function AccountInfo({ } } - const isSelf = useMemo( - () => id === store.session.get('currentAccount'), - [id], - ); + const isSelf = useMemo(() => id === getCurrentAccountID(), [id]); useEffect(() => { const infoHasEssentials = !!( @@ -920,7 +917,7 @@ function RelatedActions({ useEffect(() => { if (info) { - const currentAccount = store.session.get('currentAccount'); + const currentAccount = getCurrentAccountID(); let currentID; (async () => { if (sameInstance && authenticated) { diff --git a/src/components/media-post.jsx b/src/components/media-post.jsx index d5d09f5..91ed67d 100644 --- a/src/components/media-post.jsx +++ b/src/components/media-post.jsx @@ -8,6 +8,7 @@ import FilterContext from '../utils/filter-context'; import { isFiltered } from '../utils/filters'; import states, { statusKey } from '../utils/states'; import store from '../utils/store'; +import { getCurrentAccountID } from '../utils/store-utils'; import Media from './media'; @@ -88,7 +89,7 @@ function MediaPost({ }; const currentAccount = useMemo(() => { - return store.session.get('currentAccount'); + return getCurrentAccountID(); }, []); const isSelf = useMemo(() => { return currentAccount && currentAccount === accountId; diff --git a/src/components/nav-menu.jsx b/src/components/nav-menu.jsx index 2b622fe..769fb84 100644 --- a/src/components/nav-menu.jsx +++ b/src/components/nav-menu.jsx @@ -11,6 +11,7 @@ import { getLists } from '../utils/lists'; import safeBoundingBoxPadding from '../utils/safe-bounding-box-padding'; import states from '../utils/states'; import store from '../utils/store'; +import { getCurrentAccountID } from '../utils/store-utils'; import Avatar from './avatar'; import Icon from './icon'; @@ -24,9 +25,8 @@ function NavMenu(props) { const [currentAccount, moreThanOneAccount] = useMemo(() => { const accounts = store.local.getJSON('accounts') || []; const acc = - accounts.find( - (account) => account.info.id === store.session.get('currentAccount'), - ) || accounts[0]; + accounts.find((account) => account.info.id === getCurrentAccountID()) || + accounts[0]; return [acc, accounts.length > 1]; }, []); diff --git a/src/components/notification.jsx b/src/components/notification.jsx index f892fe8..beb2a20 100644 --- a/src/components/notification.jsx +++ b/src/components/notification.jsx @@ -4,6 +4,7 @@ import { memo } from 'preact/compat'; import shortenNumber from '../utils/shorten-number'; import states, { statusKey } from '../utils/states'; import store from '../utils/store'; +import { getCurrentAccountID } from '../utils/store-utils'; import useTruncated from '../utils/useTruncated'; import Avatar from './avatar'; @@ -132,7 +133,7 @@ function Notification({ const actualStatus = status?.reblog || status; const actualStatusID = actualStatus?.id; - const currentAccount = store.session.get('currentAccount'); + const currentAccount = getCurrentAccountID(); const isSelf = currentAccount === account?.id; const isVoted = status?.poll?.voted; const isReplyToOthers = diff --git a/src/components/shortcuts-settings.jsx b/src/components/shortcuts-settings.jsx index 5ccfdb4..ad8aca5 100644 --- a/src/components/shortcuts-settings.jsx +++ b/src/components/shortcuts-settings.jsx @@ -19,6 +19,7 @@ import pmem from '../utils/pmem'; import showToast from '../utils/show-toast'; import states from '../utils/states'; import store from '../utils/store'; +import { getCurrentAccountID } from '../utils/store-utils'; import AsyncText from './AsyncText'; import Icon from './icon'; @@ -787,7 +788,7 @@ function ImportExport({ shortcuts, onClose }) { disabled={importUIState === 'cloud-downloading'} onClick={async () => { setImportUIState('cloud-downloading'); - const currentAccount = store.session.get('currentAccount'); + const currentAccount = getCurrentAccountID(); showToast( 'Downloading saved shortcuts from instance server…', ); @@ -1043,7 +1044,7 @@ function ImportExport({ shortcuts, onClose }) { disabled={importUIState === 'cloud-uploading'} onClick={async () => { setImportUIState('cloud-uploading'); - const currentAccount = store.session.get('currentAccount'); + const currentAccount = getCurrentAccountID(); try { const relationships = await masto.v1.accounts.relationships.fetch({ diff --git a/src/components/status.jsx b/src/components/status.jsx index a3f8fad..41aace8 100644 --- a/src/components/status.jsx +++ b/src/components/status.jsx @@ -54,6 +54,7 @@ import { speak, supportsTTS } from '../utils/speech'; import states, { getStatus, saveStatus, statusKey } from '../utils/states'; import statusPeek from '../utils/status-peek'; import store from '../utils/store'; +import { getCurrentAccountID } from '../utils/store-utils'; import unfurlMastodonLink from '../utils/unfurl-link'; import useHotkeys from '../utils/useHotkeys'; import useTruncated from '../utils/useTruncated'; @@ -256,7 +257,7 @@ function Status({ if (mediaFirst && hasMediaAttachments) size = 's'; const currentAccount = useMemo(() => { - return store.session.get('currentAccount'); + return getCurrentAccountID(); }, []); const isSelf = useMemo(() => { return currentAccount && currentAccount === accountId; diff --git a/src/pages/accounts.jsx b/src/pages/accounts.jsx index 5d57ba2..efa38a1 100644 --- a/src/pages/accounts.jsx +++ b/src/pages/accounts.jsx @@ -13,12 +13,13 @@ import NameText from '../components/name-text'; import { api } from '../utils/api'; import states from '../utils/states'; import store from '../utils/store'; +import { getCurrentAccountID, setCurrentAccountID } from '../utils/store-utils'; function Accounts({ onClose }) { const { masto } = api(); // Accounts const accounts = store.local.getJSON('accounts'); - const currentAccount = store.session.get('currentAccount'); + const currentAccount = getCurrentAccountID(); const moreThanOneAccount = accounts.length > 1; const [_, reload] = useReducer((x) => x + 1, 0); @@ -81,7 +82,7 @@ function Accounts({ onClose }) { if (isCurrent) { states.showAccount = `${account.info.username}@${account.instanceURL}`; } else { - store.session.set('currentAccount', account.info.id); + setCurrentAccountID(account.info.id); location.reload(); } }} diff --git a/src/pages/catchup.jsx b/src/pages/catchup.jsx index 6801681..e416d2e 100644 --- a/src/pages/catchup.jsx +++ b/src/pages/catchup.jsx @@ -39,7 +39,7 @@ import showToast from '../utils/show-toast'; import states, { statusKey } from '../utils/states'; import statusPeek from '../utils/status-peek'; import store from '../utils/store'; -import { getCurrentAccountNS } from '../utils/store-utils'; +import { getCurrentAccountID, getCurrentAccountNS } from '../utils/store-utils'; import { assignFollowedTags } from '../utils/timeline-utils'; import useHotkeys from '../utils/useHotkeys'; import useTitle from '../utils/useTitle'; @@ -112,7 +112,7 @@ function Catchup() { const [showTopLinks, setShowTopLinks] = useState(false); const currentAccount = useMemo(() => { - return store.session.get('currentAccount'); + return getCurrentAccountID(); }, []); const isSelf = (accountID) => accountID === currentAccount; diff --git a/src/utils/api.js b/src/utils/api.js index d1c9981..78674a2 100644 --- a/src/utils/api.js +++ b/src/utils/api.js @@ -7,6 +7,7 @@ import { getAccountByInstance, getCurrentAccount, saveAccount, + setCurrentAccountID, } from './store-utils'; // Default *fallback* instance @@ -118,7 +119,7 @@ export async function initAccount(client, instance, accessToken, vapidKey) { const mastoAccount = await masto.v1.accounts.verifyCredentials(); console.log('CURRENTACCOUNT SET', mastoAccount.id); - store.session.set('currentAccount', mastoAccount.id); + setCurrentAccountID(mastoAccount.id); saveAccount({ info: mastoAccount, diff --git a/src/utils/filters.jsx b/src/utils/filters.jsx index 5d51bc0..794d617 100644 --- a/src/utils/filters.jsx +++ b/src/utils/filters.jsx @@ -1,5 +1,5 @@ import mem from './mem'; -import store from './store'; +import { getCurrentAccountID } from './store-utils'; function _isFiltered(filtered, filterContext) { if (!filtered?.length) return false; @@ -43,7 +43,7 @@ export function filteredItem(item, filterContext, currentAccountID) { export function filteredItems(items, filterContext) { if (!items?.length) return []; if (!filterContext) return items; - const currentAccountID = store.session.get('currentAccount'); + const currentAccountID = getCurrentAccountID(); return items.filter((item) => filteredItem(item, filterContext, currentAccountID), ); diff --git a/src/utils/relationships.js b/src/utils/relationships.js index eb80da5..868a8b9 100644 --- a/src/utils/relationships.js +++ b/src/utils/relationships.js @@ -1,11 +1,11 @@ import { api } from './api'; -import store from './store'; +import { getCurrentAccountID } from './store-utils'; export async function fetchRelationships(accounts, relationshipsMap = {}) { if (!accounts?.length) return; const { masto } = api(); - const currentAccount = store.session.get('currentAccount'); + const currentAccount = getCurrentAccountID(); const uniqueAccountIds = accounts.reduce((acc, a) => { // 1. Ignore duplicate accounts // 2. Ignore accounts that are already inside relationshipsMap diff --git a/src/utils/store-utils.js b/src/utils/store-utils.js index aff33e4..46054ed 100644 --- a/src/utils/store-utils.js +++ b/src/utils/store-utils.js @@ -16,13 +16,40 @@ export function getAccountByInstance(instance) { return accounts.find((a) => a.instanceURL === instance); } +const standaloneMQ = window.matchMedia('(display-mode: standalone)'); + +export function getCurrentAccountID() { + try { + const id = store.session.get('currentAccount'); + if (id) return id; + } catch (e) {} + if (standaloneMQ.matches) { + try { + const id = store.local.get('currentAccount'); + if (id) return id; + } catch (e) {} + } + return null; +} + +export function setCurrentAccountID(id) { + try { + store.session.set('currentAccount', id); + } catch (e) {} + if (standaloneMQ.matches) { + try { + store.local.set('currentAccount', id); + } catch (e) {} + } +} + export function getCurrentAccount() { if (!window.__IGNORE_GET_ACCOUNT_ERROR__) { // Track down getCurrentAccount() calls before account-based states are initialized console.error('getCurrentAccount() called before states are initialized'); if (import.meta.env.DEV) console.trace(); } - const currentAccount = store.session.get('currentAccount'); + const currentAccount = getCurrentAccountID(); const account = getAccount(currentAccount); return account; } @@ -48,7 +75,7 @@ export function saveAccount(account) { accounts.push(account); } store.local.setJSON('accounts', accounts); - store.session.set('currentAccount', account.info.id); + setCurrentAccountID(account.info.id); } export function updateAccount(accountInfo) { From 501e43207b8ff1ae24747cae46708ae6323f7cae Mon Sep 17 00:00:00 2001 From: Lim Chee Aun Date: Sat, 13 Apr 2024 00:07:48 +0800 Subject: [PATCH 05/37] Don't set onlyMedia if not set This defaults to false for Mastodon, but true for Pixelfed --- src/pages/hashtag.jsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/pages/hashtag.jsx b/src/pages/hashtag.jsx index ec7809a..6fe4e1d 100644 --- a/src/pages/hashtag.jsx +++ b/src/pages/hashtag.jsx @@ -73,7 +73,7 @@ function Hashtags({ media: mediaView, columnMode, ...props }) { limit: LIMIT, any: hashtags.slice(1), maxId: firstLoad ? undefined : maxID.current, - onlyMedia: media, + onlyMedia: media ? true : undefined, }) .next(); let { value } = results; From 2faf9b4c20df329cfce767e3d71bdb81d2a9fc51 Mon Sep 17 00:00:00 2001 From: Lim Chee Aun Date: Sat, 13 Apr 2024 00:08:09 +0800 Subject: [PATCH 06/37] Pixelfed needs remote which is opposite of local lol --- src/pages/public.jsx | 1 + 1 file changed, 1 insertion(+) diff --git a/src/pages/public.jsx b/src/pages/public.jsx index f4e10b3..7b90f37 100644 --- a/src/pages/public.jsx +++ b/src/pages/public.jsx @@ -33,6 +33,7 @@ function Public({ local, columnMode, ...props }) { publicIterator.current = masto.v1.timelines.public.list({ limit: LIMIT, local: isLocal, + remote: !isLocal, // Pixelfed }); } const results = await publicIterator.current.next(); From 6f22ec38422f537cdafe02f157faa18e606828e6 Mon Sep 17 00:00:00 2001 From: Lim Chee Aun Date: Sat, 13 Apr 2024 17:07:28 +0800 Subject: [PATCH 07/37] Fix missing idempotency key --- src/components/compose.jsx | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/src/components/compose.jsx b/src/components/compose.jsx index b926f79..0f93a28 100644 --- a/src/components/compose.jsx +++ b/src/components/compose.jsx @@ -988,7 +988,11 @@ function Compose({ } else { try { newStatus = await masto.v1.statuses.create(params, { - idempotencyKey: UID.current, + requestInit: { + headers: { + 'Idempotency-Key': UID.current, + }, + }, }); } catch (_) { // If idempotency key fails, try again without it From ec65163c89d07f9a1b6df3898840ad61e542f1ea Mon Sep 17 00:00:00 2001 From: Lim Chee Aun Date: Sat, 13 Apr 2024 17:08:39 +0800 Subject: [PATCH 08/37] More breathing space --- src/app.css | 1 + 1 file changed, 1 insertion(+) diff --git a/src/app.css b/src/app.css index ecd56ad..4553483 100644 --- a/src/app.css +++ b/src/app.css @@ -312,6 +312,7 @@ a[href^='http'][rel*='nofollow']:visited:not(:has(div)) { box-shadow: none !important; max-width: min(480px, 100%); margin-inline: auto !important; + margin-block: 32px; &:has(.skeleton) { width: 100%; From df3aca70fade11e7e4273bfa500249f8799c3692 Mon Sep 17 00:00:00 2001 From: Lim Chee Aun Date: Sat, 13 Apr 2024 17:09:00 +0800 Subject: [PATCH 09/37] Open media + post view for wider viewports --- 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 41aace8..21a423d 100644 --- a/src/components/status.jsx +++ b/src/components/status.jsx @@ -2287,7 +2287,7 @@ function MediaFirstContainer(props) { ))} From 7be620808f0cf4f46902ddc5637c7d75791a3582 Mon Sep 17 00:00:00 2001 From: Lim Chee Aun Date: Sat, 13 Apr 2024 17:09:56 +0800 Subject: [PATCH 10/37] Fix notifications for Pixelfed --- src/pages/notifications.jsx | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/src/pages/notifications.jsx b/src/pages/notifications.jsx index b226978..702dbe0 100644 --- a/src/pages/notifications.jsx +++ b/src/pages/notifications.jsx @@ -72,6 +72,13 @@ function Notifications({ columnMode }) { excludeTypes: ['follow_request'], }); } + if (/max_id=($|&)/i.test(notificationsIterator.current?.nextParams)) { + // Pixelfed returns next paginationed link with empty max_id + // I assume, it's done (end of list) + return { + done: true, + }; + } const allNotifications = await notificationsIterator.current.next(); const notifications = allNotifications.value; From 260bb8746d77890cd40abe604947d858a4838f8c Mon Sep 17 00:00:00 2001 From: Lim Chee Aun Date: Sat, 13 Apr 2024 17:10:13 +0800 Subject: [PATCH 11/37] More media-first adjustments --- src/pages/account-statuses.jsx | 251 +++++++++++++++++---------------- src/pages/hashtag.jsx | 47 +++--- 2 files changed, 157 insertions(+), 141 deletions(-) diff --git a/src/pages/account-statuses.jsx b/src/pages/account-statuses.jsx index 75ff6e3..05e7c60 100644 --- a/src/pages/account-statuses.jsx +++ b/src/pages/account-statuses.jsx @@ -21,6 +21,7 @@ import pmem from '../utils/pmem'; import showToast from '../utils/show-toast'; import states from '../utils/states'; import { saveStatus } from '../utils/states'; +import { isMediaFirstInstance } from '../utils/store-utils'; import useTitle from '../utils/useTitle'; const LIMIT = 20; @@ -68,6 +69,8 @@ function AccountStatuses() { searchOffsetRef.current = 0; }, allSearchParams); + const mediaFirst = useMemo(() => isMediaFirstInstance(), []); + const sameCurrentInstance = useMemo( () => instance === currentInstance, [instance, currentInstance], @@ -186,7 +189,7 @@ function AccountStatuses() { limit: LIMIT, exclude_replies: excludeReplies, exclude_reblogs: excludeBoosts, - only_media: media, + only_media: media || undefined, tagged, }); } @@ -270,17 +273,21 @@ function AccountStatuses() { } catch (e) { console.error(e); } - try { - const featuredTags = await masto.v1.accounts - .$select(id) - .featuredTags.list(); - console.log({ featuredTags }); - setFeaturedTags(featuredTags); - } catch (e) { - console.error(e); + // No need, because the whole filter bar is hidden + // TODO: Revisit this + if (!mediaFirst) { + try { + const featuredTags = await masto.v1.accounts + .$select(id) + .featuredTags.list(); + console.log({ featuredTags }); + setFeaturedTags(featuredTags); + } catch (e) { + console.error(e); + } } })(); - }, [id]); + }, [id, mediaFirst]); const { displayName, acct, emojis } = account || {}; @@ -299,95 +306,126 @@ function AccountStatuses() { authenticated={authenticated} standalone /> -
- {filtered ? ( + {!mediaFirst && ( +
+ {filtered ? ( + + + + ) : ( + + )} - - - ) : ( - - )} - { - if (excludeReplies) { - showToast('Showing post with replies'); - } - }} - class={excludeReplies ? '' : 'is-active'} - > - + Replies - - { - if (!excludeBoosts) { - showToast('Showing posts without boosts'); - } - }} - class={!excludeBoosts ? '' : 'is-active'} - > - - Boosts - - { - if (!media) { - showToast('Showing posts with media'); - } - }} - class={media ? 'is-active' : ''} - > - Media - - {featuredTags.map((tag) => ( - { - if (tagged !== tag.name) { - showToast(`Showing posts tagged with #${tag.name}`); + if (excludeReplies) { + showToast('Showing post with replies'); } }} - class={tagged === tag.name ? 'is-active' : ''} + class={excludeReplies ? '' : 'is-active'} > - - # - {tag.name} - - { - // The count differs based on instance 😅 - } - {/* {tag.statusesCount} */} + + Replies - ))} - {searchEnabled && - (supportsInputMonth ? ( - - ) : ( - // Fallback to for year - { - const { value, validity } = e; - if (!validity.valid) return; - setSearchParams( - value - ? { - month: value, - } - : {}, - ); - }} - /> - ))} -
+ ))} +
+ )} ); }, [ @@ -492,7 +501,7 @@ function AccountStatuses() { errorText="Unable to load posts" fetchItems={fetchAccountStatuses} useItemID - view={media ? 'media' : undefined} + view={media || mediaFirst ? 'media' : undefined} boostsCarousel={snapStates.settings.boostsCarousel} timelineStart={TimelineStart} refresh={[ diff --git a/src/pages/hashtag.jsx b/src/pages/hashtag.jsx index 6fe4e1d..7cd31d2 100644 --- a/src/pages/hashtag.jsx +++ b/src/pages/hashtag.jsx @@ -5,7 +5,7 @@ import { MenuHeader, MenuItem, } from '@szhsin/react-menu'; -import { useEffect, useRef, useState } from 'preact/hooks'; +import { useEffect, useMemo, useRef, useState } from 'preact/hooks'; import { useNavigate, useParams, useSearchParams } from 'react-router-dom'; import Icon from '../components/icon'; @@ -18,6 +18,7 @@ import { filteredItems } from '../utils/filters'; import showToast from '../utils/show-toast'; import states from '../utils/states'; import { saveStatus } from '../utils/states'; +import { isMediaFirstInstance } from '../utils/store-utils'; import useTitle from '../utils/useTitle'; const LIMIT = 20; @@ -55,6 +56,8 @@ function Hashtags({ media: mediaView, columnMode, ...props }) { useTitle(title, `/:instance?/t/:hashtag`); const latestItem = useRef(); + const mediaFirst = useMemo(() => isMediaFirstInstance(), []); + // const hashtagsIterator = useRef(); const maxID = useRef(undefined); async function fetchHashtags(firstLoad) { @@ -85,7 +88,7 @@ function Hashtags({ media: mediaView, columnMode, ...props }) { // value = filteredItems(value, 'public'); value.forEach((item) => { saveStatus(item, instance, { - skipThreading: media, // If media view, no need to form threads + skipThreading: media || mediaFirst, // If media view, no need to form threads }); }); @@ -156,7 +159,7 @@ function Hashtags({ media: mediaView, columnMode, ...props }) { fetchItems={fetchHashtags} checkForUpdates={checkForUpdates} useItemID - view={media ? 'media' : undefined} + view={media || mediaFirst ? 'media' : undefined} refresh={media} // allowFilters filterContext="public" @@ -233,23 +236,27 @@ function Hashtags({ media: mediaView, columnMode, ...props }) { )} - Filters - { - if (media) { - searchParams.delete('media'); - } else { - searchParams.set('media', '1'); - } - setSearchParams(searchParams); - }} - > - {' '} - Media only - - + {!mediaFirst && ( + <> + Filters + { + if (media) { + searchParams.delete('media'); + } else { + searchParams.set('media', '1'); + } + setSearchParams(searchParams); + }} + > + {' '} + Media only + + + + )} {({ ref }) => (
Date: Sat, 13 Apr 2024 19:21:20 +0800 Subject: [PATCH 12/37] Fix width/height not set --- src/components/media.jsx | 31 +++++++++++++++++++------------ 1 file changed, 19 insertions(+), 12 deletions(-) diff --git a/src/components/media.jsx b/src/components/media.jsx index b12a6e9..235bbb5 100644 --- a/src/components/media.jsx +++ b/src/components/media.jsx @@ -341,13 +341,15 @@ function Media({ if (!hasDimensions) { const $media = e.target.closest('.media'); if ($media) { + const { naturalWidth, naturalHeight } = e.target; $media.dataset.orientation = - e.target.naturalWidth > e.target.naturalHeight - ? 'landscape' - : 'portrait'; - $media.style['--width'] = `${e.target.naturalWidth}px`; - $media.style['--height'] = `${e.target.naturalHeight}px`; - $media.style.aspectRatio = `${e.target.naturalWidth}/${e.target.naturalHeight}`; + naturalWidth > naturalHeight ? 'landscape' : 'portrait'; + $media.style.setProperty('--width', `${naturalWidth}px`); + $media.style.setProperty( + '--height', + `${naturalHeight}px`, + ); + $media.style.aspectRatio = `${naturalWidth}/${naturalHeight}`; } } }} @@ -515,15 +517,20 @@ function Media({ if (!hasDimensions) { const $media = e.target.closest('.media'); if ($media) { + const { naturalHeight, naturalWidth } = e.target; $media.dataset.orientation = - e.target.naturalWidth > e.target.naturalHeight + naturalWidth > naturalHeight ? 'landscape' : 'portrait'; - $media.style['--width'] = `${e.target.naturalWidth}px`; - $media.style[ - '--height' - ] = `${e.target.naturalHeight}px`; - $media.style.aspectRatio = `${e.target.naturalWidth}/${e.target.naturalHeight}`; + $media.style.setProperty( + '--width', + `${naturalWidth}px`, + ); + $media.style.setProperty( + '--height', + `${naturalHeight}px`, + ); + $media.style.aspectRatio = `${naturalWidth}/${naturalHeight}`; } } }} From c286562ee81e40a7e15ba64606413fd7925309ff Mon Sep 17 00:00:00 2001 From: Lim Chee Aun Date: Sat, 13 Apr 2024 19:21:48 +0800 Subject: [PATCH 13/37] Media-first style adjustments --- src/app.css | 5 ++++- src/components/status.css | 4 +++- 2 files changed, 7 insertions(+), 2 deletions(-) diff --git a/src/app.css b/src/app.css index 4553483..9b88bd4 100644 --- a/src/app.css +++ b/src/app.css @@ -312,7 +312,10 @@ a[href^='http'][rel*='nofollow']:visited:not(:has(div)) { box-shadow: none !important; max-width: min(480px, 100%); margin-inline: auto !important; - margin-block: 32px; + + &:not(:first-child) { + margin-block: 32px; + } &:has(.skeleton) { width: 100%; diff --git a/src/components/status.css b/src/components/status.css index eea4c28..e4dde17 100644 --- a/src/components/status.css +++ b/src/components/status.css @@ -1325,6 +1325,8 @@ body:has(#modal-container .carousel) .status .media img:hover { } .status-media-first { + animation: appear-smooth 1s ease-out; + .meta-name { opacity: 0.65; transition: opacity 0.5s ease-in-out; @@ -1398,7 +1400,7 @@ body:has(#modal-container .carousel) .status .media img:hover { .media { /* background-color: var(--average-color, var(--bg-faded-color)); */ - width: var(--width); + width: var(--width, 100%); max-width: 100%; max-height: 100%; min-height: var(--min-dimension); From 94996d098ee34a978d0a0dcf25b824afdd031e46 Mon Sep 17 00:00:00 2001 From: Lim Chee Aun Date: Sat, 13 Apr 2024 23:08:25 +0800 Subject: [PATCH 14/37] Fix width issue --- src/app.css | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/src/app.css b/src/app.css index 9b88bd4..ca8ca8b 100644 --- a/src/app.css +++ b/src/app.css @@ -306,11 +306,14 @@ a[href^='http'][rel*='nofollow']:visited:not(:has(div)) { .timeline { > li:not(.timeline-item-carousel, .timeline-item-container) { &:has(.status-media-first) { - width: fit-content; + @media (min-width: 40em) { + width: fit-content; + max-width: min(480px, 100%); + } + background-color: transparent !important; border: 0 !important; box-shadow: none !important; - max-width: min(480px, 100%); margin-inline: auto !important; &:not(:first-child) { From 342ff20986622c4c600fa85927b31391b58b3c7a Mon Sep 17 00:00:00 2001 From: Lim Chee Aun Date: Sun, 14 Apr 2024 08:14:34 +0800 Subject: [PATCH 15/37] Document `PHANPY_IMG_ALT_API_URL` --- README.md | 3 +++ 1 file changed, 3 insertions(+) diff --git a/README.md b/README.md index e893ece..8f5c328 100644 --- a/README.md +++ b/README.md @@ -179,6 +179,9 @@ Available variables: - May specify a self-hosted Lingva instance, powered by either [lingva-translate](https://github.com/thedaviddelta/lingva-translate) or [lingva-api](https://github.com/cheeaun/lingva-api) - List of fallback instances hard-coded in `/.env` - [↗️ List of lingva-translate instances](https://github.com/thedaviddelta/lingva-translate?tab=readme-ov-file#instances) +- `PHANPY_IMG_ALT_API_URL` (optional, no defaults): + - API endpoint for self-hosted instance of [img-alt-api](https://github.com/cheeaun/img-alt-api). + - If provided, a setting will appear for users to enable the image description generator in the composer. Disabled by default. - `PHANPY_GIPHY_API_KEY` (optional, no defaults): - API key for [GIPHY](https://developers.giphy.com/). See [API docs](https://developers.giphy.com/docs/api/). - If provided, a setting will appear for users to enable the GIF picker in the composer. Disabled by default. From 6f8f3e4fd0448116ec5ffb0e0b029252c29b344e Mon Sep 17 00:00:00 2001 From: Lim Chee Aun Date: Sun, 14 Apr 2024 14:08:50 +0800 Subject: [PATCH 16/37] Change -35deg to 145deg prevents stripes animation When dynamically changing dimension (height), repeating linear gradient seems to animate. This prevents it. https://stackoverflow.com/a/76285775/20838 --- src/components/compose.css | 10 +++++----- src/components/status.css | 2 +- src/pages/catchup.css | 2 +- 3 files changed, 7 insertions(+), 7 deletions(-) diff --git a/src/components/compose.css b/src/components/compose.css index cfa4cdd..9e2c3f0 100644 --- a/src/components/compose.css +++ b/src/components/compose.css @@ -310,7 +310,7 @@ #compose-container .form-visibility-direct { --yellow-stripes: repeating-linear-gradient( - -45deg, + 135deg, var(--reply-to-faded-color), var(--reply-to-faded-color) 10px, var(--reply-to-faded-color) 10px, @@ -348,9 +348,9 @@ var(--img-bg-color) 25%, transparent 25% ), - linear-gradient(-45deg, var(--img-bg-color) 25%, transparent 25%), + linear-gradient(135deg, var(--img-bg-color) 25%, transparent 25%), linear-gradient(45deg, transparent 75%, var(--img-bg-color) 75%), - linear-gradient(-45deg, transparent 75%, var(--img-bg-color) 75%); + linear-gradient(135deg, transparent 75%, var(--img-bg-color) 75%); background-size: 10px 10px; background-position: 0 0, 0 5px, 5px -5px, -5px 0px; } @@ -562,9 +562,9 @@ var(--img-bg-color) 25%, transparent 25% ), - linear-gradient(-45deg, var(--img-bg-color) 25%, transparent 25%), + linear-gradient(135deg, var(--img-bg-color) 25%, transparent 25%), linear-gradient(45deg, transparent 75%, var(--img-bg-color) 75%), - linear-gradient(-45deg, transparent 75%, var(--img-bg-color) 75%); + linear-gradient(135deg, transparent 75%, var(--img-bg-color) 75%); background-size: 20px 20px; background-position: 0 0, 0 10px, 10px -10px, -10px 0px; flex: 0.8; diff --git a/src/components/status.css b/src/components/status.css index e4dde17..0122108 100644 --- a/src/components/status.css +++ b/src/components/status.css @@ -47,7 +47,7 @@ } .visibility-direct { --yellow-stripes: repeating-linear-gradient( - -45deg, + 135deg, var(--reply-to-faded-color), var(--reply-to-faded-color) 10px, var(--reply-to-faded-color) 10px, diff --git a/src/pages/catchup.css b/src/pages/catchup.css index dcfbf94..4f53c6d 100644 --- a/src/pages/catchup.css +++ b/src/pages/catchup.css @@ -614,7 +614,7 @@ } &.visibility-direct { --yellow-stripes: repeating-linear-gradient( - -45deg, + 135deg, var(--reply-to-faded-color), var(--reply-to-faded-color) 10px, var(--reply-to-faded-color) 10px, From afdfdb86dad1607322b347eae99759ee30e78217 Mon Sep 17 00:00:00 2001 From: Lim Chee Aun Date: Sun, 14 Apr 2024 17:18:52 +0800 Subject: [PATCH 17/37] Media-first style adjustments --- src/components/status.css | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/src/components/status.css b/src/components/status.css index 0122108..8bcf2b8 100644 --- a/src/components/status.css +++ b/src/components/status.css @@ -1320,13 +1320,11 @@ body:has(#modal-container .carousel) .status .media img:hover { } .status.skeleton .media-first-container { - min-height: 3em; + min-height: 320px; background-color: var(--outline-color); } .status-media-first { - animation: appear-smooth 1s ease-out; - .meta-name { opacity: 0.65; transition: opacity 0.5s ease-in-out; @@ -1406,6 +1404,10 @@ body:has(#modal-container .carousel) .status .media img:hover { min-height: var(--min-dimension); /* max-height: min(var(--height), 80vh); */ + &:has(img:not([data-loaded='true'])) { + min-height: 320px; + } + &:active { transform: none; } From 06c6360cae77178591aeb972e4745dd52ad6c5e4 Mon Sep 17 00:00:00 2001 From: Lim Chee Aun Date: Sun, 14 Apr 2024 17:20:18 +0800 Subject: [PATCH 18/37] More support for Pixelfed --- src/app.css | 3 +- src/components/nav-menu.jsx | 31 +++++++++----- src/components/status.jsx | 80 ++++++++++++++++++++++--------------- src/pages/trending.jsx | 57 ++++++++++++++++---------- src/utils/supports.js | 19 +++++++++ 5 files changed, 124 insertions(+), 66 deletions(-) diff --git a/src/app.css b/src/app.css index ca8ca8b..b149534 100644 --- a/src/app.css +++ b/src/app.css @@ -1917,7 +1917,8 @@ body > .szh-menu-container { /* two columns only */ grid-template-columns: repeat(2, 1fr); } -.szh-menu .menu-horizontal:has(> .szh-menu__item:only-child) { +.szh-menu .menu-horizontal:has(> .szh-menu__item:only-child), +.szh-menu .menu-horizontal:has(> .szh-menu__submenu:only-child) { grid-template-columns: 1fr; } .szh-menu .menu-horizontal > .szh-menu__item:not(:only-child):first-child, diff --git a/src/components/nav-menu.jsx b/src/components/nav-menu.jsx index 769fb84..74f2567 100644 --- a/src/components/nav-menu.jsx +++ b/src/components/nav-menu.jsx @@ -12,6 +12,7 @@ import safeBoundingBoxPadding from '../utils/safe-bounding-box-padding'; import states from '../utils/states'; import store from '../utils/store'; import { getCurrentAccountID } from '../utils/store-utils'; +import supports from '../utils/supports'; import Avatar from './avatar'; import Icon from './icon'; @@ -83,8 +84,10 @@ function NavMenu(props) { return results; } + const supportsLists = supports('@mastodon/lists'); const [lists, setLists] = useState([]); useEffect(() => { + if (!supportsLists) return; if (menuState === 'open') { getLists().then(setLists); } @@ -186,9 +189,11 @@ function NavMenu(props) { Catch-up - - Mentions - + {supports('@mastodon/mentions') && ( + + Mentions + + )} Notifications {snapStates.notificationsShowNew && ( @@ -232,10 +237,12 @@ function NavMenu(props) { )} ) : ( - - - Lists - + supportsLists && ( + + + Lists + + ) )} Bookmarks @@ -260,10 +267,12 @@ function NavMenu(props) { Followed Hashtags - - - Filters - + {supports('@mastodon/filters') && ( + + + Filters + + )} { states.showGenericAccounts = { diff --git a/src/components/status.jsx b/src/components/status.jsx index 21a423d..455cd4e 100644 --- a/src/components/status.jsx +++ b/src/components/status.jsx @@ -55,6 +55,7 @@ import states, { getStatus, saveStatus, statusKey } from '../utils/states'; import statusPeek from '../utils/status-peek'; import store from '../utils/store'; import { getCurrentAccountID } from '../utils/store-utils'; +import supports from '../utils/supports'; import unfurlMastodonLink from '../utils/unfurl-link'; import useHotkeys from '../utils/useHotkeys'; import useTruncated from '../utils/useTruncated'; @@ -149,6 +150,12 @@ const PostContent = memo( }, ); +const SIZE_CLASS = { + s: 'small', + m: 'medium', + l: 'large', +}; + function Status({ statusID, status, @@ -174,7 +181,11 @@ function Status({ }) { if (skeleton) { return ( -
+
{!mediaFirst && }
@@ -640,6 +651,7 @@ function Status({ }; const bookmarkStatus = async () => { + if (!supports('@mastodon/post-bookmark')) return; if (!sameInstance || !authenticated) { alert(unauthInteractionErrorMessage); return false; @@ -827,13 +839,15 @@ function Status({ : 'Like'} - - - {bookmarked ? 'Unbookmark' : 'Bookmark'} - + {supports('@mastodon/post-bookmark') && ( + + + {bookmarked ? 'Unbookmark' : 'Bookmark'} + + )}
)} @@ -1077,16 +1091,18 @@ function Status({ )} {isSelf && ( -
- -
+ {supports('@mastodon/post-bookmark') && ( +
+ +
+ )} link.type === 'link'); - console.log('links', links); - if (links?.length) { - setLinks(links); + if (supports('@mastodon/trending-links')) { + try { + const { value } = await fetchLinks(masto, instance); + // 4 types available: link, photo, video, rich + // Only want links for now + const links = value?.filter?.((link) => link.type === 'link'); + console.log('links', links); + if (links?.length) { + setLinks(links); + } + } catch (e) { + console.error(e); } - } catch (e) { - console.error(e); } } const results = await trendIterator.current.next(); diff --git a/src/utils/supports.js b/src/utils/supports.js index 02b1a49..4733cd9 100644 --- a/src/utils/supports.js +++ b/src/utils/supports.js @@ -4,6 +4,20 @@ import features from '../data/features.json'; import { getCurrentInstance } from './store-utils'; +// Non-semver(?) UA string detection +// Can't put this inside features.json due to regex +const containPixelfed = /pixelfed/i; +const notContainPixelfed = /^(?!.*pixelfed).*$/i; +const platformFeatures = { + '@mastodon/lists': notContainPixelfed, + '@mastodon/filters': notContainPixelfed, + '@mastodon/mentions': notContainPixelfed, + '@mastodon/trending-hashtags': notContainPixelfed, + '@mastodon/trending-links': notContainPixelfed, + '@mastodon/post-bookmark': notContainPixelfed, + '@mastodon/post-edit': notContainPixelfed, + '@pixelfed/trending': containPixelfed, +}; const supportsCache = {}; function supports(feature) { @@ -11,6 +25,11 @@ function supports(feature) { const { version, domain } = getCurrentInstance(); const key = `${domain}-${feature}`; if (supportsCache[key]) return supportsCache[key]; + + if (platformFeatures[feature]) { + return (supportsCache[key] = platformFeatures[feature].test(version)); + } + const range = features[feature]; if (!range) return false; return (supportsCache[key] = satisfies(version, range, { From cd5920114fe762560dd70fa699c8088c94456368 Mon Sep 17 00:00:00 2001 From: Lim Chee Aun Date: Mon, 15 Apr 2024 07:26:45 +0800 Subject: [PATCH 19/37] Undo back to -45deg, not everything need 135deg --- src/components/compose.css | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/components/compose.css b/src/components/compose.css index 9e2c3f0..407f172 100644 --- a/src/components/compose.css +++ b/src/components/compose.css @@ -348,9 +348,9 @@ var(--img-bg-color) 25%, transparent 25% ), - linear-gradient(135deg, var(--img-bg-color) 25%, transparent 25%), + linear-gradient(-45deg, var(--img-bg-color) 25%, transparent 25%), linear-gradient(45deg, transparent 75%, var(--img-bg-color) 75%), - linear-gradient(135deg, transparent 75%, var(--img-bg-color) 75%); + linear-gradient(-45deg, transparent 75%, var(--img-bg-color) 75%); background-size: 10px 10px; background-position: 0 0, 0 5px, 5px -5px, -5px 0px; } @@ -562,9 +562,9 @@ var(--img-bg-color) 25%, transparent 25% ), - linear-gradient(135deg, var(--img-bg-color) 25%, transparent 25%), + linear-gradient(-45deg, var(--img-bg-color) 25%, transparent 25%), linear-gradient(45deg, transparent 75%, var(--img-bg-color) 75%), - linear-gradient(135deg, transparent 75%, var(--img-bg-color) 75%); + linear-gradient(-45deg, transparent 75%, var(--img-bg-color) 75%); background-size: 20px 20px; background-position: 0 0, 0 10px, 10px -10px, -10px 0px; flex: 0.8; From 57390a291b20aa268513a40c60954a3bae5237d2 Mon Sep 17 00:00:00 2001 From: Lim Chee Aun Date: Mon, 15 Apr 2024 10:10:49 +0800 Subject: [PATCH 20/37] No need background if there's pre-meta before it --- src/components/status.css | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/components/status.css b/src/components/status.css index 8bcf2b8..4398c90 100644 --- a/src/components/status.css +++ b/src/components/status.css @@ -365,6 +365,10 @@ background-image: var(--yellow-stripes); } + .status-pre-meta + & { + background-image: none; + } + > * { opacity: 0.65; transition: opacity 1s ease-out; From 304ce5a3e80dfe2269e18d13cc96e3185c37185b Mon Sep 17 00:00:00 2001 From: Lim Chee Aun Date: Mon, 15 Apr 2024 17:06:44 +0800 Subject: [PATCH 21/37] Experiment dynamic change of parent This might prevent double renders --- src/components/status.jsx | 60 ++++++++++++++++++--------------------- 1 file changed, 27 insertions(+), 33 deletions(-) diff --git a/src/components/status.jsx b/src/components/status.jsx index 455cd4e..0d42e60 100644 --- a/src/components/status.jsx +++ b/src/components/status.jsx @@ -11,6 +11,7 @@ import { import { decodeBlurHash, getBlurHashAverageColor } from 'fast-blurhash'; import { shallowEqual } from 'fast-equals'; import prettify from 'html-prettify'; +import { Fragment } from 'preact'; import { memo } from 'preact/compat'; import { useCallback, @@ -406,38 +407,31 @@ function Status({ } // Check followedTags - if (showFollowedTags && !!snapStates.statusFollowedTags[sKey]?.length) { - return ( -
-
- {' '} - {snapStates.statusFollowedTags[sKey].slice(0, 3).map((tag) => ( - - {tag} - - ))} -
- + const FollowedTagsParent = ({ children }) => ( +
+
+ {' '} + {snapStates.statusFollowedTags[sKey].slice(0, 3).map((tag) => ( + + {tag} + + ))}
- ); - } + {children} +
+ ); + const StatusParent = + showFollowedTags && !!snapStates.statusFollowedTags[sKey]?.length + ? FollowedTagsParent + : Fragment; const isSizeLarge = size === 'l'; @@ -1383,7 +1377,7 @@ function Status({ ]); return ( - <> + {showReplyParent && !!(inReplyToId && inReplyToAccountId) && ( )} @@ -2251,7 +2245,7 @@ function Status({ )} - + ); } From 294ab2bf00941b76e0cad9d6222ff2df7e3de94a Mon Sep 17 00:00:00 2001 From: Lim Chee Aun Date: Mon, 15 Apr 2024 17:07:20 +0800 Subject: [PATCH 22/37] Just put in this commented test notification Good for reference in the future --- src/pages/notifications.jsx | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/src/pages/notifications.jsx b/src/pages/notifications.jsx index 702dbe0..a8434c9 100644 --- a/src/pages/notifications.jsx +++ b/src/pages/notifications.jsx @@ -89,6 +89,21 @@ function Notifications({ columnMode }) { }); }); + // TEST: Slot in a fake notification to test 'severed_relationships' + // notifications.unshift({ + // id: '123123', + // type: 'severed_relationships', + // createdAt: '2024-03-22T19:20:08.316Z', + // event: { + // type: 'account_suspension', + // targetName: 'mastodon.dev', + // followersCount: 0, + // followingCount: 0, + // }, + // }); + + // console.log({ notifications }); + const groupedNotifications = groupNotifications(notifications); if (firstLoad) { From 701b9e99b33361b64d0ea08b252a3732ad456f54 Mon Sep 17 00:00:00 2001 From: Lim Chee Aun Date: Mon, 15 Apr 2024 17:07:34 +0800 Subject: [PATCH 23/37] More media-first styling changes --- src/components/status.css | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/components/status.css b/src/components/status.css index 4398c90..cac20f6 100644 --- a/src/components/status.css +++ b/src/components/status.css @@ -1375,11 +1375,13 @@ body:has(#modal-container .carousel) .status .media img:hover { /* border: var(--hairline-width) solid var(--outline-color); border-inline-width: 0; background-color: var(--bg-faded-color); */ + box-shadow: 0 0 0 var(--hairline-width) var(--outline-color); @media (min-width: 40em) { margin-inline: 0; /* border-radius: 4px; */ border-inline-width: var(--hairline-width); + box-shadow: none; } &::-webkit-scrollbar { @@ -1397,7 +1399,7 @@ body:has(#modal-container .carousel) .status .media img:hover { &:not(:only-child) { background-color: var(--bg-blur-color); - box-shadow: inset 0 0 0 var(--hairline-width) var(--outline-color); + /* box-shadow: inset 0 0 0 var(--hairline-width) var(--outline-color); */ } .media { @@ -1414,6 +1416,7 @@ body:has(#modal-container .carousel) .status .media img:hover { &:active { transform: none; + filter: none; } img, From e2f39596f048cb240ccd33f6bef663355f8ec535 Mon Sep 17 00:00:00 2001 From: Lim Chee Aun Date: Mon, 15 Apr 2024 19:58:59 +0800 Subject: [PATCH 24/37] Might as well add more supports --- src/components/account-info.jsx | 52 ++++++++++++++++++--------------- src/utils/supports.js | 3 +- 2 files changed, 31 insertions(+), 24 deletions(-) diff --git a/src/components/account-info.jsx b/src/components/account-info.jsx index 35ea89e..49cb427 100644 --- a/src/components/account-info.jsx +++ b/src/components/account-info.jsx @@ -23,6 +23,7 @@ import showToast from '../utils/show-toast'; import states, { hideAllModals } from '../utils/states'; import store from '../utils/store'; import { getCurrentAccountID, updateAccount } from '../utils/store-utils'; +import supports from '../utils/supports'; import AccountBlock from './account-block'; import Avatar from './avatar'; @@ -1091,16 +1092,18 @@ function RelatedActions({ Translate bio - { - setShowPrivateNoteModal(true); - }} - > - - - {privateNote ? 'Edit private note' : 'Add private note'} - - + {supports('@mastodon/profile-private-note') && ( + { + setShowPrivateNoteModal(true); + }} + > + + + {privateNote ? 'Edit private note' : 'Add private note'} + + + )} {following && !!relationship && ( <> )} - {currentAuthenticated && isSelf && standalone && ( - <> - - { - setShowEditProfile(true); - }} - > - - Edit profile - - - )} + {currentAuthenticated && + isSelf && + standalone && + supports('@mastodon/profile-edit') && ( + <> + + { + setShowEditProfile(true); + }} + > + + Edit profile + + + )} {import.meta.env.DEV && currentAuthenticated && isSelf && ( <> diff --git a/src/utils/supports.js b/src/utils/supports.js index 4733cd9..6b94126 100644 --- a/src/utils/supports.js +++ b/src/utils/supports.js @@ -5,7 +5,6 @@ import features from '../data/features.json'; import { getCurrentInstance } from './store-utils'; // Non-semver(?) UA string detection -// Can't put this inside features.json due to regex const containPixelfed = /pixelfed/i; const notContainPixelfed = /^(?!.*pixelfed).*$/i; const platformFeatures = { @@ -16,6 +15,8 @@ const platformFeatures = { '@mastodon/trending-links': notContainPixelfed, '@mastodon/post-bookmark': notContainPixelfed, '@mastodon/post-edit': notContainPixelfed, + '@mastodon/profile-edit': notContainPixelfed, + '@mastodon/profile-private-note': notContainPixelfed, '@pixelfed/trending': containPixelfed, }; const supportsCache = {}; From 9a6364a674f2658d8acf6690f4a4966fcfc9ce07 Mon Sep 17 00:00:00 2001 From: Lim Chee Aun Date: Mon, 15 Apr 2024 19:59:57 +0800 Subject: [PATCH 25/37] Obviously got to flex my scroll-driven animation CSSkillz --- src/components/status.css | 18 ++++++++++++++++++ src/components/status.jsx | 7 ++++++- 2 files changed, 24 insertions(+), 1 deletion(-) diff --git a/src/components/status.css b/src/components/status.css index cac20f6..b607c01 100644 --- a/src/components/status.css +++ b/src/components/status.css @@ -1328,7 +1328,18 @@ body:has(#modal-container .carousel) .status .media img:hover { background-color: var(--outline-color); } +@keyframes media-carousel-slide { + 0% { + transform: translateX(calc(var(--dots-count, 1) * 5px)); + } + 100% { + transform: translateX(calc(var(--dots-count, 1) * -5px)); + } +} + .status-media-first { + timeline-scope: --media-carousel; + .meta-name { opacity: 0.65; transition: opacity 0.5s ease-in-out; @@ -1376,6 +1387,7 @@ body:has(#modal-container .carousel) .status .media img:hover { border-inline-width: 0; background-color: var(--bg-faded-color); */ box-shadow: 0 0 0 var(--hairline-width) var(--outline-color); + scroll-timeline: --media-carousel x; @media (min-width: 40em) { margin-inline: 0; @@ -1500,6 +1512,11 @@ body:has(#modal-container .carousel) .status .media img:hover { margin-top: 8px; padding: 8px; + @supports (animation-timeline: scroll()) { + animation: auto media-carousel-slide linear both; + animation-timeline: --media-carousel; + } + .carousel-dot { display: inline-block; width: 5px; @@ -1508,6 +1525,7 @@ body:has(#modal-container .carousel) .status .media img:hover { background-color: var(--text-color); transition: all 0.3s ease-in-out; opacity: 0.3; + flex-shrink: 0; &.active { opacity: 1; diff --git a/src/components/status.jsx b/src/components/status.jsx index 0d42e60..a72182c 100644 --- a/src/components/status.jsx +++ b/src/components/status.jsx @@ -2344,7 +2344,12 @@ function MediaFirstContainer(props) { )}
{moreThanOne && ( -