From 04b4101e55763fb4e620e7449690eff53d54deb6 Mon Sep 17 00:00:00 2001 From: Lim Chee Aun Date: Mon, 3 Apr 2023 10:05:57 +0800 Subject: [PATCH 01/28] Handle /notes/ url too --- src/pages/status.jsx | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/src/pages/status.jsx b/src/pages/status.jsx index be6aa0b8..922b25ff 100644 --- a/src/pages/status.jsx +++ b/src/pages/status.jsx @@ -955,14 +955,19 @@ function SubComments({ ); } +const statusRegex = /\/@([^@\/]+)@?([^\/]+)?\/([^\/]+)\/?$/i; +const statusNoteRegex = /\/notes\/([^\/]+)\/?$/i; function getInstanceStatusURL(url) { // Regex /:username/:id, where username = @username or @username@domain, id = anything - const statusRegex = /\/@([^@\/]+)@?([^\/]+)?\/([^\/]+)\/?$/i; const { hostname, pathname } = new URL(url); const [, username, domain, id] = pathname.match(statusRegex) || []; if (id) { return `/${hostname}/s/${id}`; } + const [, noteId] = pathname.match(statusNoteRegex) || []; + if (noteId) { + return `/${hostname}/s/${noteId}`; + } } export default StatusPage; From c7f4087ed2cfc204c7d9813b6474286875d3c96d Mon Sep 17 00:00:00 2001 From: Lim Chee Aun Date: Mon, 3 Apr 2023 10:36:31 +0800 Subject: [PATCH 02/28] Preliminary steps in adding filter bar --- src/app.css | 57 +++++++++++++++++++++++ src/components/icon.jsx | 1 + src/components/timeline.jsx | 4 ++ src/pages/account-statuses.jsx | 83 ++++++++++++++++++++++++++++++---- 4 files changed, 135 insertions(+), 10 deletions(-) diff --git a/src/app.css b/src/app.css index 1537beba..229ee341 100644 --- a/src/app.css +++ b/src/app.css @@ -1641,6 +1641,63 @@ ul.link-list li a .icon { } } +/* FILTER BAR */ + +.filter-bar { + padding: 8px 16px; + background-color: var(--bg-faded-color); + display: flex; + gap: 8px; + overflow-x: auto; + mask-image: linear-gradient( + to right, + transparent, + black 16px, + black calc(100% - 16px), + transparent + ); + align-items: center; +} +@media (min-width: 40em) { + .filter-bar { + background-color: transparent; + } +} +.filter-bar > a { + padding: 8px 16px; + border-radius: 999px; + background-color: var(--bg-color); + color: var(--link-color); + text-decoration: none; + white-space: nowrap; + border: 2px solid transparent; + transition: all 0.3s ease-out; + display: inline-flex; + align-items: center; + gap: 8px; +} +.filter-bar > a:is(:hover, :focus) { + border-color: var(--link-light-color); +} +.filter-bar > a > * { + vertical-align: middle; +} +.filter-bar > a.is-active { + border-color: var(--link-color); + box-shadow: inset 0 0 8px var(--link-faded-color); +} +.filter-bar > a > .filter-count { + font-size: 80%; + display: inline-block; + color: var(--text-insignificant-color); + min-width: 16px; + min-height: 16px; + padding: 4px; + margin: -4px -8px -4px 0; + background-color: var(--bg-faded-color); + border-radius: 999px; +} + /* OTHERS */ @media (min-width: 40em) { diff --git a/src/components/icon.jsx b/src/components/icon.jsx index 8d6fcb10..afc6e906 100644 --- a/src/components/icon.jsx +++ b/src/components/icon.jsx @@ -74,6 +74,7 @@ const ICONS = { time: 'mingcute:time-line', refresh: 'mingcute:refresh-2-line', emoji2: 'mingcute:emoji-2-line', + filter: 'mingcute:filter-2-line', }; const modules = import.meta.glob('/node_modules/@iconify-icons/mingcute/*.js'); diff --git a/src/components/timeline.jsx b/src/components/timeline.jsx index c07a5bc8..222ac0e6 100644 --- a/src/components/timeline.jsx +++ b/src/components/timeline.jsx @@ -33,6 +33,7 @@ function Timeline({ headerEnd, timelineStart, allowFilters, + refresh, }) { const [items, setItems] = useState([]); const [uiState, setUIState] = useState('default'); @@ -184,6 +185,9 @@ function Timeline({ scrollableRef.current?.scrollTo({ top: 0 }); loadItems(true); }, []); + useEffect(() => { + loadItems(true); + }, [refresh]); useEffect(() => { if (reachStart) { diff --git a/src/pages/account-statuses.jsx b/src/pages/account-statuses.jsx index 19b9cacb..0efe78be 100644 --- a/src/pages/account-statuses.jsx +++ b/src/pages/account-statuses.jsx @@ -1,8 +1,10 @@ import { useEffect, useMemo, useRef, useState } from 'preact/hooks'; -import { useParams } from 'react-router-dom'; +import { useParams, useSearchParams } from 'react-router-dom'; import { useSnapshot } from 'valtio'; import AccountInfo from '../components/account-info'; +import Icon from '../components/icon'; +import Link from '../components/link'; import Timeline from '../components/timeline'; import { api } from '../utils/api'; import emojifyText from '../utils/emojify-text'; @@ -15,6 +17,11 @@ const LIMIT = 20; function AccountStatuses() { const snapStates = useSnapshot(states); const { id, ...params } = useParams(); + const [searchParams, setSearchParams] = useSearchParams(); + const excludeReplies = !searchParams.get('replies'); + const tagged = searchParams.get('tagged'); + const media = !!searchParams.get('media'); + console.log({ excludeReplies }); const { masto, instance, authenticated } = api({ instance: params.instance }); const accountStatusesIterator = useRef(); async function fetchAccountStatuses(firstLoad) { @@ -25,7 +32,7 @@ function AccountStatuses() { pinned: true, }) .next(); - if (pinnedStatuses?.length) { + if (pinnedStatuses?.length && !tagged && !media) { pinnedStatuses.forEach((status) => { status._pinned = true; saveStatus(status, instance); @@ -45,6 +52,9 @@ function AccountStatuses() { if (firstLoad || !accountStatusesIterator.current) { accountStatusesIterator.current = masto.v1.accounts.listStatuses(id, { limit: LIMIT, + exclude_replies: excludeReplies, + only_media: media, + tagged, }); } const { value, done } = await accountStatusesIterator.current.next(); @@ -62,6 +72,7 @@ function AccountStatuses() { } const [account, setAccount] = useState(); + const [featuredTags, setFeaturedTags] = useState([]); useTitle( `${account?.displayName ? account.displayName + ' ' : ''}@${ account?.acct ? account.acct : 'Account posts' @@ -77,6 +88,13 @@ function AccountStatuses() { } catch (e) { console.error(e); } + try { + const featuredTags = await masto.v1.accounts.listFeaturedTags(id); + console.log({ featuredTags }); + setFeaturedTags(featuredTags); + } catch (e) { + console.error(e); + } })(); }, [id]); @@ -85,15 +103,59 @@ function AccountStatuses() { const TimelineStart = useMemo(() => { const cachedAccount = snapStates.accounts[`${id}@${instance}`]; return ( - masto.v1.accounts.fetch(id)} - authenticated={authenticated} - standalone - /> + <> + masto.v1.accounts.fetch(id)} + authenticated={authenticated} + standalone + /> +
+ + + + Replies + + + Media + + {featuredTags.map((tag) => ( + + + # + {tag.name} + + { + // The count differs based on instance 😅 + } + {/* {tag.statusesCount} */} + + ))} +
+ ); - }, [id, instance, authenticated]); + }, [ + id, + instance, + authenticated, + excludeReplies, + featuredTags, + tagged, + media, + ]); return ( ); } From b49f003605e941f90250251023777469d2758422 Mon Sep 17 00:00:00 2001 From: Lim Chee Aun Date: Mon, 3 Apr 2023 11:54:46 +0800 Subject: [PATCH 03/28] The faux video container also need special treatment --- src/components/media.jsx | 3 ++- src/components/status.css | 1 + 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/src/components/media.jsx b/src/components/media.jsx index fdf6b066..0e83fbdb 100644 --- a/src/components/media.jsx +++ b/src/components/media.jsx @@ -179,7 +179,7 @@ function Media({ media, showOriginal, autoAnimate, onClick = () => {} }) { }} > {showOriginal || autoGIFAnimate ? ( - isGIF ? ( + isGIF && showOriginal ? (
{} }) { ) : (
Date: Mon, 3 Apr 2023 13:17:22 +0800 Subject: [PATCH 04/28] Auto-scroll to filter & add clear filter "button" --- src/app.css | 2 +- src/pages/account-statuses.jsx | 31 ++++++++++++++++++++++++++++--- 2 files changed, 29 insertions(+), 4 deletions(-) diff --git a/src/app.css b/src/app.css index 229ee341..efdcae6b 100644 --- a/src/app.css +++ b/src/app.css @@ -1663,7 +1663,7 @@ ul.link-list li a .icon { background-color: transparent; } } -.filter-bar > a { +.filter-bar > a:not(.filter-clear) { padding: 8px 16px; border-radius: 999px; background-color: var(--bg-color); diff --git a/src/pages/account-statuses.jsx b/src/pages/account-statuses.jsx index 0efe78be..3db25f7d 100644 --- a/src/pages/account-statuses.jsx +++ b/src/pages/account-statuses.jsx @@ -21,7 +21,6 @@ function AccountStatuses() { const excludeReplies = !searchParams.get('replies'); const tagged = searchParams.get('tagged'); const media = !!searchParams.get('media'); - console.log({ excludeReplies }); const { masto, instance, authenticated } = api({ instance: params.instance }); const accountStatusesIterator = useRef(); async function fetchAccountStatuses(firstLoad) { @@ -100,8 +99,10 @@ function AccountStatuses() { const { displayName, acct, emojis } = account || {}; + const filterBarRef = useRef(); const TimelineStart = useMemo(() => { const cachedAccount = snapStates.accounts[`${id}@${instance}`]; + const filtered = !excludeReplies || tagged || media; return ( <> -
- +
+ {filtered ? ( + + + + ) : ( + + )} { + // Focus on .is-active + const active = filterBarRef.current?.querySelector('.is-active'); + if (active) { + console.log('active', active, active.offsetLeft); + filterBarRef.current.scrollTo({ + behavior: 'smooth', + left: + active.offsetLeft - + (filterBarRef.current.offsetWidth - active.offsetWidth) / 2, + }); + } + }, [featuredTags, tagged, media, excludeReplies]); + return ( Date: Tue, 4 Apr 2023 11:01:53 +0800 Subject: [PATCH 05/28] Add '- Boosts' filter --- src/pages/account-statuses.jsx | 15 ++++++++++++--- 1 file changed, 12 insertions(+), 3 deletions(-) diff --git a/src/pages/account-statuses.jsx b/src/pages/account-statuses.jsx index 3db25f7d..376fee6b 100644 --- a/src/pages/account-statuses.jsx +++ b/src/pages/account-statuses.jsx @@ -19,6 +19,7 @@ function AccountStatuses() { const { id, ...params } = useParams(); const [searchParams, setSearchParams] = useSearchParams(); const excludeReplies = !searchParams.get('replies'); + const excludeBoosts = !!searchParams.get('boosts'); const tagged = searchParams.get('tagged'); const media = !!searchParams.get('media'); const { masto, instance, authenticated } = api({ instance: params.instance }); @@ -52,6 +53,7 @@ function AccountStatuses() { accountStatusesIterator.current = masto.v1.accounts.listStatuses(id, { limit: LIMIT, exclude_replies: excludeReplies, + exclude_reblogs: excludeBoosts, only_media: media, tagged, }); @@ -102,7 +104,7 @@ function AccountStatuses() { const filterBarRef = useRef(); const TimelineStart = useMemo(() => { const cachedAccount = snapStates.accounts[`${id}@${instance}`]; - const filtered = !excludeReplies || tagged || media; + const filtered = !excludeReplies || excludeBoosts || tagged || media; return ( <> + Replies + + - Boosts + ); } From 18b00b2b7a2fb10ecd51233831b3401654172305 Mon Sep 17 00:00:00 2001 From: Lim Chee Aun Date: Tue, 4 Apr 2023 18:46:05 +0800 Subject: [PATCH 06/28] Fix toast showing even when canceling a boost/unboost confirmation --- src/components/status.jsx | 13 +++++++++---- 1 file changed, 9 insertions(+), 4 deletions(-) diff --git a/src/components/status.jsx b/src/components/status.jsx index f10f7ae3..032c2120 100644 --- a/src/components/status.jsx +++ b/src/components/status.jsx @@ -300,7 +300,8 @@ function Status({ const boostStatus = async () => { if (!sameInstance || !authenticated) { - return alert(unauthInteractionErrorMessage); + alert(unauthInteractionErrorMessage); + return false; } try { if (!reblogged) { @@ -314,7 +315,7 @@ function Status({ } const yes = confirm(confirmText); if (!yes) { - return; + return false; } } // Optimistic @@ -326,14 +327,17 @@ function Status({ if (reblogged) { const newStatus = await masto.v1.statuses.unreblog(id); saveStatus(newStatus, instance); + return true; } else { const newStatus = await masto.v1.statuses.reblog(id); saveStatus(newStatus, instance); + return true; } } catch (e) { console.error(e); // Revert optimistism states.statuses[sKey] = status; + return false; } }; @@ -450,9 +454,10 @@ function Status({ { try { - await boostStatus(); - if (!isSizeLarge) + const done = await boostStatus(); + if (!isSizeLarge && done) { showToast(reblogged ? 'Unboosted' : 'Boosted'); + } } catch (e) {} }} > From 4b4211874236f8ac5f58aab5da5318fcabf9887b Mon Sep 17 00:00:00 2001 From: Lim Chee Aun Date: Wed, 5 Apr 2023 18:52:15 +0800 Subject: [PATCH 07/28] Only show boosts carousel if there are >10 items --- src/utils/timeline-utils.jsx | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/utils/timeline-utils.jsx b/src/utils/timeline-utils.jsx index aa6600bb..40994a95 100644 --- a/src/utils/timeline-utils.jsx +++ b/src/utils/timeline-utils.jsx @@ -18,7 +18,10 @@ export function groupBoosts(values) { } // if boostStash is more than quarter of values // or if there are 3 or more boosts in a row - if (boostStash.length > values.length / 4 || serialBoosts >= 3) { + if ( + values.length > 10 && + (boostStash.length > values.length / 4 || serialBoosts >= 3) + ) { // if boostStash is more than 3 quarter of values const boostStashID = boostStash.map((status) => status.id); if (boostStash.length > (values.length * 3) / 4) { From ff1a9fa444f048922d82be89d403d5fe3f26ac14 Mon Sep 17 00:00:00 2001 From: Lim Chee Aun Date: Wed, 5 Apr 2023 23:30:26 +0800 Subject: [PATCH 08/28] Full CRUD for Lists --- public/sw.js | 2 +- src/components/account-info.css | 22 +++ src/components/account-info.jsx | 159 +++++++++++++++++++- src/components/list-add-edit.jsx | 133 +++++++++++++++++ src/index.css | 3 + src/pages/list.jsx | 245 ++++++++++++++++++++++++++++--- src/pages/lists.css | 33 +++++ src/pages/lists.jsx | 58 +++++++- 8 files changed, 621 insertions(+), 34 deletions(-) create mode 100644 src/components/list-add-edit.jsx create mode 100644 src/pages/lists.css diff --git a/public/sw.js b/public/sw.js index 14514039..b44a94b0 100644 --- a/public/sw.js +++ b/public/sw.js @@ -39,7 +39,7 @@ registerRoute(imageRoute); // - /api/v1/preferences // - /api/v1/lists/:id const apiExtendedRoute = new RegExpRoute( - /^https?:\/\/[^\/]+\/api\/v\d+\/(instance|custom_emojis|preferences|lists\/\d+)/, + /^https?:\/\/[^\/]+\/api\/v\d+\/(instance|custom_emojis|preferences|lists\/\d+)$/, new StaleWhileRevalidate({ cacheName: 'api-extended', plugins: [ diff --git a/src/components/account-info.css b/src/components/account-info.css index f14780bb..06bf133b 100644 --- a/src/components/account-info.css +++ b/src/components/account-info.css @@ -260,6 +260,28 @@ animation: shine 1s ease-in-out 1s; } +#list-add-remove-container .list-add-remove { + display: flex; + flex-direction: column; + gap: 8px; + margin: 0; + padding: 8px 0; + list-style: none; +} +#list-add-remove-container .list-add-remove button { + display: flex; + align-items: center; + gap: 8px; + width: 100%; +} +#list-add-remove-container .list-add-remove button .icon { + opacity: 0.15; +} +#list-add-remove-container .list-add-remove button.checked .icon { + opacity: 1; + color: var(--green-color); +} + @media (min-width: 40em) { .timeline-start .account-container { --item-radius: 16px; diff --git a/src/components/account-info.jsx b/src/components/account-info.jsx index a7fa85c2..0acc64b4 100644 --- a/src/components/account-info.jsx +++ b/src/components/account-info.jsx @@ -1,13 +1,7 @@ import './account-info.css'; -import { - Menu, - MenuDivider, - MenuHeader, - MenuItem, - SubMenu, -} from '@szhsin/react-menu'; -import { useEffect, useRef, useState } from 'preact/hooks'; +import { Menu, MenuDivider, MenuItem, SubMenu } from '@szhsin/react-menu'; +import { useEffect, useReducer, useRef, useState } from 'preact/hooks'; import { api } from '../utils/api'; import emojifyText from '../utils/emojify-text'; @@ -24,6 +18,8 @@ import AccountBlock from './account-block'; import Avatar from './avatar'; import Icon from './icon'; import Link from './link'; +import ListAddEdit from './list-add-edit'; +import Loader from './loader'; import Modal from './modal'; import TranslationBlock from './translation-block'; @@ -487,6 +483,7 @@ function RelatedActions({ info, instance, authenticated }) { const menuInstanceRef = useRef(null); const [showTranslatedBio, setShowTranslatedBio] = useState(false); + const [showAddRemoveLists, setShowAddRemoveLists] = useState(false); return ( <> @@ -583,6 +580,17 @@ function RelatedActions({ info, instance, authenticated }) { Translate bio + {/* Add/remove from lists is only possible if following the account */} + {following && ( + { + setShowAddRemoveLists(true); + }} + > + + Add/remove from Lists + + )} )} @@ -840,6 +848,18 @@ function RelatedActions({ info, instance, authenticated }) { )} + {!!showAddRemoveLists && ( + { + if (e.target === e.currentTarget) { + setShowAddRemoveLists(false); + } + }} + > + + + )} ); } @@ -900,4 +920,127 @@ function TranslatedBioSheet({ note, fields }) {
); } + +function AddRemoveListsSheet({ accountID }) { + const { masto } = api(); + const [uiState, setUiState] = useState('default'); + const [lists, setLists] = useState([]); + const [listsContainingAccount, setListsContainingAccount] = useState([]); + const [reloadCount, reload] = useReducer((c) => c + 1, 0); + + useEffect(() => { + setUiState('loading'); + (async () => { + try { + const lists = await masto.v1.lists.list(); + const listsContainingAccount = await masto.v1.accounts.listLists( + accountID, + ); + console.log({ lists, listsContainingAccount }); + setLists(lists); + setListsContainingAccount(listsContainingAccount); + setUiState('default'); + } catch (e) { + console.error(e); + setUiState('error'); + } + })(); + }, [reloadCount]); + + const [showListAddEditModal, setShowListAddEditModal] = useState(false); + + return ( +
+
+

Add/Remove from Lists

+
+
+ {lists.length > 0 ? ( +
    + {lists.map((list) => { + const inList = listsContainingAccount.some( + (l) => l.id === list.id, + ); + return ( +
  • + +
  • + ); + })} +
+ ) : uiState === 'loading' ? ( +

+ +

+ ) : uiState === 'error' ? ( +

Unable to load lists.

+ ) : ( +

No lists.

+ )} + +
+ {showListAddEditModal && ( + { + if (e.target === e.currentTarget) { + setShowListAddEditModal(false); + } + }} + > + { + if (result.state === 'success') { + reload(); + } + setShowListAddEditModal(false); + }} + /> + + )} +
+ ); +} + export default AccountInfo; diff --git a/src/components/list-add-edit.jsx b/src/components/list-add-edit.jsx new file mode 100644 index 00000000..a3fd8f3f --- /dev/null +++ b/src/components/list-add-edit.jsx @@ -0,0 +1,133 @@ +import { useEffect, useRef, useState } from 'preact/hooks'; + +import { api } from '../utils/api'; + +function ListAddEdit({ list, onClose = () => {} }) { + const { masto } = api(); + const [uiState, setUiState] = useState('default'); + const editMode = !!list; + const nameFieldRef = useRef(); + const repliesPolicyFieldRef = useRef(); + useEffect(() => { + if (editMode) { + nameFieldRef.current.value = list.title; + repliesPolicyFieldRef.current.value = list.repliesPolicy; + } + }, [editMode]); + return ( +
+
+

{editMode ? 'Edit list' : 'New list'}

+
+
+
{ + e.preventDefault(); // Get form values + + const formData = new FormData(e.target); + const title = formData.get('title'); + const repliesPolicy = formData.get('replies_policy'); + console.log({ + title, + repliesPolicy, + }); + setUiState('loading'); + + (async () => { + try { + let listResult; + + if (editMode) { + listResult = await masto.v1.lists.update(list.id, { + title, + replies_policy: repliesPolicy, + }); + } else { + listResult = await masto.v1.lists.create({ + title, + replies_policy: repliesPolicy, + }); + } + + console.log(listResult); + setUiState('default'); + onClose({ + state: 'success', + list: listResult, + }); + } catch (e) { + console.error(e); + setUiState('error'); + alert( + editMode ? 'Unable to edit list.' : 'Unable to create list.', + ); + } + })(); + }} + > +
+ +
+
+ +
+ +
+
+
+ ); +} + +export default ListAddEdit; diff --git a/src/index.css b/src/index.css index 339df71f..1a124fe5 100644 --- a/src/index.css +++ b/src/index.css @@ -194,6 +194,9 @@ button, color: var(--text-color); border: 1px solid var(--outline-color); } +:is(button, .button).light:not(:disabled, .disabled):is(:hover, :focus) { + border-color: var(--outline-hover-color); +} :is(button, .button).light.danger:not(:disabled, .disabled) { color: var(--red-color); } diff --git a/src/pages/list.jsx b/src/pages/list.jsx index 54c53c58..3b9155dc 100644 --- a/src/pages/list.jsx +++ b/src/pages/list.jsx @@ -1,8 +1,15 @@ -import { useEffect, useRef, useState } from 'preact/hooks'; -import { useParams } from 'react-router-dom'; +import './lists.css'; +import { Menu, MenuItem } from '@szhsin/react-menu'; +import { useEffect, useRef, useState } from 'preact/hooks'; +import { InView } from 'react-intersection-observer'; +import { useNavigate, useParams } from 'react-router-dom'; + +import AccountBlock from '../components/account-block'; import Icon from '../components/icon'; import Link from '../components/link'; +import ListAddEdit from '../components/list-add-edit'; +import Modal from '../components/modal'; import Timeline from '../components/timeline'; import { api } from '../utils/api'; import { filteredItems } from '../utils/filters'; @@ -14,7 +21,9 @@ const LIMIT = 20; function List(props) { const { masto, instance } = api(); const id = props?.id || useParams()?.id; + const navigate = useNavigate(); const latestItem = useRef(); + // const [reloadCount, reload] = useReducer((c) => c + 1, 0); const listIterator = useRef(); async function fetchList(firstLoad) { @@ -55,37 +64,231 @@ function List(props) { } } - const [title, setTitle] = useState(`List`); - useTitle(title, `/l/:id`); + const [list, setList] = useState({ title: 'List' }); + // const [title, setTitle] = useState(`List`); + useTitle(list.title, `/l/:id`); useEffect(() => { (async () => { try { const list = await masto.v1.lists.fetch(id); - setTitle(list.title); + setList(list); + // setTitle(list.title); } catch (e) { console.error(e); } })(); }, [id]); + const [showListAddEditModal, setShowListAddEditModal] = useState(false); + const [showManageMembersModal, setShowManageMembersModal] = useState(false); + return ( - - - + <> + + + + } + headerEnd={ + + + + } + > + + setShowListAddEditModal({ + list, + }) + } + > + + Edit + + setShowManageMembersModal(true)}> + + Manage members + + + } + /> + {showListAddEditModal && ( + { + if (e.target === e.currentTarget) { + setShowListAddEditModal(false); + } + }} + > + { + if (result.state === 'success' && result.list) { + setList(result.list); + // reload(); + } else if (result.state === 'deleted') { + navigate('/l'); + } + setShowListAddEditModal(false); + }} + /> + + )} + {showManageMembersModal && ( + { + if (e.target === e.currentTarget) { + setShowManageMembersModal(false); + } + }} + > + + + )} + + ); +} + +const MEMBERS_LIMIT = 10; +function ListManageMembers({ listID }) { + // Show list of members with [Remove] button + // API only returns 40 members at a time, so this need to be paginated with infinite scroll + // Show [Add] button after removing a member + const { masto, instance } = api(); + const [members, setMembers] = useState([]); + const [uiState, setUIState] = useState('default'); + const [showMore, setShowMore] = useState(false); + + const membersIterator = useRef(); + + async function fetchMembers(firstLoad) { + setShowMore(false); + setUIState('loading'); + (async () => { + try { + if (firstLoad || !membersIterator.current) { + membersIterator.current = masto.v1.lists.listAccounts(listID, { + limit: MEMBERS_LIMIT, + }); + } + const results = await membersIterator.current.next(); + let { done, value } = results; + if (value?.length) { + if (firstLoad) { + setMembers(value); + } else { + setMembers(members.concat(value)); + } + setShowMore(!done); + } else { + setShowMore(false); + } + setUIState('default'); + } catch (e) { + setUIState('error'); } - /> + })(); + } + + useEffect(() => { + fetchMembers(true); + }, []); + + return ( +
+
+

Manage members

+
+
+
    + {members.map((member) => ( +
  • + + +
  • + ))} + {showMore && uiState === 'default' && ( + inView && fetchMembers()}> + + + )} +
+
+
+ ); +} + +function RemoveAddButton({ account, listID }) { + const { masto } = api(); + const [uiState, setUIState] = useState('default'); + const [removed, setRemoved] = useState(false); + + return ( + ); } diff --git a/src/pages/lists.css b/src/pages/lists.css new file mode 100644 index 00000000..dcf5ffc6 --- /dev/null +++ b/src/pages/lists.css @@ -0,0 +1,33 @@ +.list-form { + padding: 8px 0; + display: flex; + gap: 8px; + flex-direction: column; +} + +.list-form-row :is(input, select) { + width: 100%; +} + +.list-form-footer { + display: flex; + gap: 8px; + justify-content: space-between; +} +.list-form-footer button[type='submit'] { + padding-inline: 24px; +} + +#list-manage-members-container ul { + display: block; + list-style: none; + padding: 8px 0; + margin: 0; +} +#list-manage-members-container ul li { + display: flex; + gap: 8px; + align-items: center; + justify-content: space-between; + padding: 8px 0; +} diff --git a/src/pages/lists.jsx b/src/pages/lists.jsx index 48ba6899..5aa3ceea 100644 --- a/src/pages/lists.jsx +++ b/src/pages/lists.jsx @@ -1,9 +1,13 @@ -import { useEffect, useState } from 'preact/hooks'; +import './lists.css'; + +import { useEffect, useReducer, useRef, useState } from 'preact/hooks'; import Icon from '../components/icon'; import Link from '../components/link'; +import ListAddEdit from '../components/list-add-edit'; import Loader from '../components/loader'; import Menu from '../components/menu'; +import Modal from '../components/modal'; import { api } from '../utils/api'; import useTitle from '../utils/useTitle'; @@ -12,6 +16,7 @@ function Lists() { useTitle(`Lists`, `/l`); const [uiState, setUiState] = useState('default'); + const [reloadCount, reload] = useReducer((c) => c + 1, 0); const [lists, setLists] = useState([]); useEffect(() => { setUiState('loading'); @@ -26,7 +31,9 @@ function Lists() { setUiState('error'); } })(); - }, []); + }, [reloadCount]); + + const [showListAddEditModal, setShowListAddEditModal] = useState(false); return (
@@ -40,7 +47,15 @@ function Lists() {

Lists

-
+
+ +
@@ -49,7 +64,22 @@ function Lists() { {lists.map((list) => (
  • - {list.title} + + {list.title} + + {/* */}
  • ))} @@ -65,6 +95,26 @@ function Lists() { )}
    + {showListAddEditModal && ( + { + if (e.target === e.currentTarget) { + setShowListAddEditModal(false); + } + }} + > + { + if (result.state === 'success') { + reload(); + } + setShowListAddEditModal(false); + }} + /> + + )}
    ); } From f1bb1454c3974251db1a13d9f4979b2c50ad3c60 Mon Sep 17 00:00:00 2001 From: Lim Chee Aun Date: Thu, 6 Apr 2023 00:49:32 +0800 Subject: [PATCH 09/28] Sort followed hashtags --- src/pages/followed-hashtags.jsx | 1 + 1 file changed, 1 insertion(+) diff --git a/src/pages/followed-hashtags.jsx b/src/pages/followed-hashtags.jsx index 21e0f290..5e07a53f 100644 --- a/src/pages/followed-hashtags.jsx +++ b/src/pages/followed-hashtags.jsx @@ -28,6 +28,7 @@ function FollowedHashtags() { if (done || value?.length === 0) break; tags.push(...value); } while (true); + tags.sort((a, b) => a.name.localeCompare(b.name)); console.log(tags); setFollowedHashtags(tags); setUiState('default'); From 913a352deefe67218556a457100c78ee624f5eca Mon Sep 17 00:00:00 2001 From: Lim Chee Aun Date: Thu, 6 Apr 2023 01:14:38 +0800 Subject: [PATCH 10/28] Add Trending page --- src/app.jsx | 2 + src/components/icon.jsx | 1 + src/components/menu.jsx | 3 + src/pages/trending.jsx | 127 ++++++++++++++++++++++++++++++++++++++++ 4 files changed, 133 insertions(+) create mode 100644 src/pages/trending.jsx diff --git a/src/app.jsx b/src/app.jsx index 8e4001aa..391e2e45 100644 --- a/src/app.jsx +++ b/src/app.jsx @@ -41,6 +41,7 @@ import Public from './pages/public'; import Search from './pages/search'; import Settings from './pages/settings'; import Status from './pages/status'; +import Trending from './pages/trending'; import Welcome from './pages/welcome'; import { api, @@ -238,6 +239,7 @@ function App() { } /> } /> + } /> } /> {/* } /> */} diff --git a/src/components/icon.jsx b/src/components/icon.jsx index afc6e906..0d37deb6 100644 --- a/src/components/icon.jsx +++ b/src/components/icon.jsx @@ -75,6 +75,7 @@ const ICONS = { refresh: 'mingcute:refresh-2-line', emoji2: 'mingcute:emoji-2-line', filter: 'mingcute:filter-2-line', + chart: 'mingcute:chart-line-line', }; const modules = import.meta.glob('/node_modules/@iconify-icons/mingcute/*.js'); diff --git a/src/components/menu.jsx b/src/components/menu.jsx index 7e36ca83..363e5219 100644 --- a/src/components/menu.jsx +++ b/src/components/menu.jsx @@ -137,6 +137,9 @@ function NavMenu(props) { Federated + + Trending + {authenticated && ( <> diff --git a/src/pages/trending.jsx b/src/pages/trending.jsx new file mode 100644 index 00000000..c92a7e11 --- /dev/null +++ b/src/pages/trending.jsx @@ -0,0 +1,127 @@ +import { Menu, MenuItem } from '@szhsin/react-menu'; +import { useRef } from 'preact/hooks'; +import { useNavigate, useParams } from 'react-router-dom'; +import { useSnapshot } from 'valtio'; + +import Icon from '../components/icon'; +import Timeline from '../components/timeline'; +import { api } from '../utils/api'; +import { filteredItems } from '../utils/filters'; +import states from '../utils/states'; +import { saveStatus } from '../utils/states'; +import useTitle from '../utils/useTitle'; + +const LIMIT = 20; + +function Trending(props) { + const snapStates = useSnapshot(states); + const params = useParams(); + const { masto, instance } = api({ + instance: props?.instance || params.instance, + }); + const title = `Trending (${instance})`; + useTitle(title, `/:instance?/trending`); + const navigate = useNavigate(); + const latestItem = useRef(); + + const trendIterator = useRef(); + async function fetchTrend(firstLoad) { + if (firstLoad || !trendIterator.current) { + trendIterator.current = masto.v1.trends.listStatuses({ + limit: LIMIT, + }); + } + const results = await trendIterator.current.next(); + let { value } = results; + if (value?.length) { + if (firstLoad) { + latestItem.current = value[0].id; + } + + value = filteredItems(value, 'public'); // Might not work here + value.forEach((item) => { + saveStatus(item, instance); + }); + } + return results; + } + + async function checkForUpdates() { + try { + const results = await masto.v1.trends + .listStatuses({ + limit: 1, + since_id: latestItem.current, + }) + .next(); + let { value } = results; + value = filteredItems(value, 'public'); + if (value?.length) { + return true; + } + return false; + } catch (e) { + return false; + } + } + + return ( +
    diff --git a/src/components/status.css b/src/components/status.css index 1258f324..3f583226 100644 --- a/src/components/status.css +++ b/src/components/status.css @@ -240,16 +240,18 @@ .status-reply-badge { display: inline-flex; - margin-left: 4px; + margin: 2px 0 2px 4px; gap: 4px; align-items: center; + vertical-align: middle; } .status-reply-badge .icon { color: var(--reply-to-color); } .status-thread-badge { + vertical-align: middle; display: inline-flex; - margin: 4px 0 0 0; + margin: 2px 0; gap: 4px; align-items: center; color: var(--reply-to-text-color); @@ -270,6 +272,24 @@ ); font-weight: bold; } +.status-direct-badge { + vertical-align: middle; + display: inline-flex; + margin: 2px 0; + gap: 4px; + align-items: center; + color: var(--reply-to-text-color); + background-color: var(--bg-color); + border: 1px solid var(--reply-to-text-color); + border-radius: 4px; + padding: 4px; + font-size: 10px; + line-height: 1; + text-transform: uppercase; + opacity: 0.75; + font-weight: bold; + box-shadow: inset 0 0 0 1px var(--reply-to-color); +} .status-filtered-badge { flex-shrink: 0; color: var(--text-insignificant-color); diff --git a/src/components/status.jsx b/src/components/status.jsx index ccf427fb..e3f5112c 100644 --- a/src/components/status.jsx +++ b/src/components/status.jsx @@ -62,7 +62,7 @@ const visibilityText = { public: 'Public', unlisted: 'Unlisted', private: 'Followers only', - direct: 'Direct', + direct: 'Private mention', }; function Status({ @@ -779,6 +779,11 @@ function Status({ ))} + {visibility === 'direct' && ( + <> +
    Private mention
    {' '} + + )} {!withinContext && ( <> {inReplyToAccountId === status.account?.id || From a75dd2d9c414bd4ebd8eea308e5f2955163aecd0 Mon Sep 17 00:00:00 2001 From: Lim Chee Aun Date: Thu, 6 Apr 2023 18:57:20 +0800 Subject: [PATCH 22/28] Better copy for "Public" --- src/components/shortcuts-settings.jsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/components/shortcuts-settings.jsx b/src/components/shortcuts-settings.jsx index 4ba5d95d..15a1bf6d 100644 --- a/src/components/shortcuts-settings.jsx +++ b/src/components/shortcuts-settings.jsx @@ -33,7 +33,7 @@ const TYPE_TEXT = { following: 'Home / Following', notifications: 'Notifications', list: 'List', - public: 'Public', + public: 'Public (Local / Federated)', search: 'Search', 'account-statuses': 'Account', bookmarks: 'Bookmarks', From 224a289a20859f6193aaaa662e0726d0b1c204f0 Mon Sep 17 00:00:00 2001 From: Lim Chee Aun Date: Thu, 6 Apr 2023 19:32:26 +0800 Subject: [PATCH 23/28] New Mentions page --- src/app.jsx | 2 + src/components/menu.jsx | 3 ++ src/components/shortcuts-settings.jsx | 12 ++++- src/pages/mentions.jsx | 76 +++++++++++++++++++++++++++ src/pages/notifications.jsx | 2 +- 5 files changed, 92 insertions(+), 3 deletions(-) create mode 100644 src/pages/mentions.jsx diff --git a/src/app.jsx b/src/app.jsx index 391e2e45..00039913 100644 --- a/src/app.jsx +++ b/src/app.jsx @@ -36,6 +36,7 @@ import Home from './pages/home'; import List from './pages/list'; import Lists from './pages/lists'; import Login from './pages/login'; +import Mentions from './pages/mentions'; import Notifications from './pages/notifications'; import Public from './pages/public'; import Search from './pages/search'; @@ -223,6 +224,7 @@ function App() { {isLoggedIn && ( } /> )} + {isLoggedIn && } />} {isLoggedIn && } />} {isLoggedIn && } />} {isLoggedIn && } />} diff --git a/src/components/menu.jsx b/src/components/menu.jsx index 363e5219..ca69f463 100644 --- a/src/components/menu.jsx +++ b/src/components/menu.jsx @@ -103,6 +103,9 @@ function NavMenu(props) { Following )} + + Mentions + Notifications {snapStates.notificationsShowNew && ( diff --git a/src/components/shortcuts-settings.jsx b/src/components/shortcuts-settings.jsx index 15a1bf6d..ed9e189a 100644 --- a/src/components/shortcuts-settings.jsx +++ b/src/components/shortcuts-settings.jsx @@ -18,16 +18,17 @@ const SHORTCUTS_LIMIT = 9; const TYPES = [ 'following', + 'mentions', 'notifications', 'list', 'public', + 'trending', // NOTE: Hide for now // 'search', // Search on Mastodon ain't great // 'account-statuses', // Need @acct search first + 'hashtag', 'bookmarks', 'favourites', - 'hashtag', - 'trending', ]; const TYPE_TEXT = { following: 'Home / Following', @@ -40,6 +41,7 @@ const TYPE_TEXT = { favourites: 'Favourites', hashtag: 'Hashtag', trending: 'Trending', + mentions: 'Mentions', }; const TYPE_PARAMS = { list: [ @@ -101,6 +103,12 @@ export const SHORTCUTS_META = { path: '/', icon: 'home', }, + mentions: { + id: 'mentions', + title: 'Mentions', + path: '/mentions', + icon: 'at', + }, notifications: { id: 'notifications', title: 'Notifications', diff --git a/src/pages/mentions.jsx b/src/pages/mentions.jsx new file mode 100644 index 00000000..7bde5099 --- /dev/null +++ b/src/pages/mentions.jsx @@ -0,0 +1,76 @@ +import { useRef } from 'preact/hooks'; + +import Timeline from '../components/timeline'; +import { api } from '../utils/api'; +import { saveStatus } from '../utils/states'; +import useTitle from '../utils/useTitle'; + +const LIMIT = 20; + +function Mentions() { + useTitle('Mentions', '/mentions'); + const { masto, instance } = api(); + const mentionsIterator = useRef(); + const latestItem = useRef(); + + async function fetchMentions(firstLoad) { + if (firstLoad || !mentionsIterator.current) { + mentionsIterator.current = masto.v1.notifications.list({ + limit: LIMIT, + types: ['mention'], + }); + } + const results = await mentionsIterator.current.next(); + let { value } = results; + if (value?.length) { + if (firstLoad) { + latestItem.current = value[0].id; + console.log('First load', latestItem.current); + } + + value.forEach(({ status: item }) => { + saveStatus(item, instance); + }); + } + return { + ...results, + value: value.map((item) => item.status), + }; + } + + async function checkForUpdates() { + try { + const results = await masto.v1.notifications + .list({ + limit: 1, + types: ['mention'], + since_id: latestItem.current, + }) + .next(); + let { value } = results; + console.log('checkForUpdates', latestItem.current, value); + if (value?.length) { + latestItem.current = value[0].id; + return true; + } + return false; + } catch (e) { + return false; + } + } + + return ( + + ); +} + +export default Mentions; diff --git a/src/pages/notifications.jsx b/src/pages/notifications.jsx index ad82177a..7b4dda1c 100644 --- a/src/pages/notifications.jsx +++ b/src/pages/notifications.jsx @@ -167,7 +167,7 @@ function Notifications() {
    - +

    Notifications

    From 2b26635e72ddaa941da01de484d0066c256d0560 Mon Sep 17 00:00:00 2001 From: Lim Chee Aun Date: Thu, 6 Apr 2023 22:51:48 +0800 Subject: [PATCH 24/28] New: Reactions Modal --- src/components/icon.jsx | 1 + src/components/status.css | 38 +++++++++ src/components/status.jsx | 164 ++++++++++++++++++++++++++++++++++++++ 3 files changed, 203 insertions(+) diff --git a/src/components/icon.jsx b/src/components/icon.jsx index 0d37deb6..196ca0f5 100644 --- a/src/components/icon.jsx +++ b/src/components/icon.jsx @@ -76,6 +76,7 @@ const ICONS = { emoji2: 'mingcute:emoji-2-line', filter: 'mingcute:filter-2-line', chart: 'mingcute:chart-line-line', + react: 'mingcute:react-line', }; const modules = import.meta.glob('/node_modules/@iconify-icons/mingcute/*.js'); diff --git a/src/components/status.css b/src/components/status.css index 3f583226..c1509ae9 100644 --- a/src/components/status.css +++ b/src/components/status.css @@ -1222,3 +1222,41 @@ a.card:is(:hover, :focus) { bottom: 8px; right: 8px; } + +/* REACTIONS */ + +#reactions-container main ul { + list-style: none; + margin: 0; + padding: 8px 0; + display: flex; + flex-wrap: wrap; + flex-direction: row; + column-gap: 1.5em; + row-gap: 16px; +} +#reactions-container main ul li { + display: flex; + flex-grow: 1; + flex-basis: 16em; + align-items: center; + margin: 0; + padding: 0; + gap: 8px; +} +#reactions-container main ul li .account-block-acct { + font-size: 80%; + color: var(--text-insignificant-color); + display: block; +} +#reactions-container .reactions-block { + display: flex; + flex-direction: column; + align-self: center; +} +#reactions-container .reactions-block .favourite-icon { + color: var(--favourite-color); +} +#reactions-container .reactions-block .reblog-icon { + color: var(--reblog-color); +} diff --git a/src/components/status.jsx b/src/components/status.jsx index e3f5112c..9dc0e570 100644 --- a/src/components/status.jsx +++ b/src/components/status.jsx @@ -14,11 +14,13 @@ import mem from 'mem'; import pThrottle from 'p-throttle'; import { memo } from 'preact/compat'; import { useEffect, useMemo, useRef, useState } from 'preact/hooks'; +import { InView } from 'react-intersection-observer'; import 'swiped-events'; import { useLongPress } from 'use-long-press'; import useResizeObserver from 'use-resize-observer'; import { useSnapshot } from 'valtio'; +import AccountBlock from '../components/account-block'; import Loader from '../components/loader'; import Modal from '../components/modal'; import NameText from '../components/name-text'; @@ -228,6 +230,7 @@ function Status({ if (!snapStates.settings.contentTranslation) enableTranslate = false; const [showEdited, setShowEdited] = useState(false); + const [showReactions, setShowReactions] = useState(false); const spoilerContentRef = useRef(null); useResizeObserver({ @@ -444,6 +447,14 @@ function Status({ )} {(!isSizeLarge || !!editedAt) && } + {isSizeLarge && ( + setShowReactions(true)}> + + + Boosted/Favourited by… + + + )} {!isSizeLarge && sameInstance && ( <> @@ -1123,6 +1134,18 @@ function Status({ /> )} + {showReactions && ( + { + if (e.target === e.currentTarget) { + setShowReactions(false); + } + }} + > + + + )} ); } @@ -1541,6 +1564,147 @@ function EditedAtModal({ ); } +const REACTIONS_LIMIT = 80; +function ReactionsModal({ statusID, instance }) { + const { masto } = api({ instance }); + const [uiState, setUIState] = useState('default'); + const [accounts, setAccounts] = useState([]); + const [showMore, setShowMore] = useState(false); + + const reblogIterator = useRef(); + const favouriteIterator = useRef(); + + async function fetchAccounts(firstLoad) { + setShowMore(false); + setUIState('loading'); + (async () => { + try { + if (firstLoad) { + reblogIterator.current = masto.v1.statuses.listRebloggedBy(statusID, { + limit: REACTIONS_LIMIT, + }); + favouriteIterator.current = masto.v1.statuses.listFavouritedBy( + statusID, + { + limit: REACTIONS_LIMIT, + }, + ); + } + const [{ value: reblogResults }, { value: favouriteResults }] = + await Promise.allSettled([ + reblogIterator.current.next(), + favouriteIterator.current.next(), + ]); + if (reblogResults.value?.length || favouriteResults.value?.length) { + if (reblogResults.value?.length) { + for (const account of reblogResults.value) { + const theAccount = accounts.find((a) => a.id === account.id); + if (!theAccount) { + accounts.push({ + ...account, + _types: ['reblog'], + }); + } else { + theAccount._types.push('reblog'); + } + } + } + if (favouriteResults.value?.length) { + for (const account of favouriteResults.value) { + const theAccount = accounts.find((a) => a.id === account.id); + if (!theAccount) { + accounts.push({ + ...account, + _types: ['favourite'], + }); + } else { + theAccount._types.push('favourite'); + } + } + } + setAccounts(accounts); + setShowMore(!reblogResults.done || !favouriteResults.done); + } else { + setShowMore(false); + } + setUIState('default'); + } catch (e) { + console.error(e); + setUIState('error'); + } + })(); + } + + useEffect(() => { + fetchAccounts(true); + }, []); + + return ( +
    +
    +

    Boosted/Favourited by…

    +
    +
    + {accounts.length > 0 ? ( + <> +
      + {accounts.map((account) => { + const { _types } = account; + return ( +
    • +
      + {_types.map((type) => ( + + ))} +
      + +
    • + ); + })} +
    + {uiState === 'default' && + (showMore ? ( + { + if (inView) { + fetchAccounts(); + } + }} + > + + + ) : ( +

    The end.

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

    + +

    + ) : uiState === 'error' ? ( +

    Unable to load accounts

    + ) : ( +

    No one yet.

    + )} +
    +
    + ); +} + function StatusButton({ checked, count, From 252b52ca7acf24b099ac3d4c0573d6aacc2b85ad Mon Sep 17 00:00:00 2001 From: Lim Chee Aun Date: Fri, 7 Apr 2023 19:44:49 +0800 Subject: [PATCH 25/28] Fix alt button background --- src/components/status.css | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/src/components/status.css b/src/components/status.css index c1509ae9..780666a7 100644 --- a/src/components/status.css +++ b/src/components/status.css @@ -689,14 +689,16 @@ body:has(#modal-container .carousel) .status .media img:hover { border-radius: 8px; color: var(--text-color); padding: 4px 8px; - border: 1px solid var(--outline-color); - box-shadow: 0 4px 16px var(--outline-color); + background-color: var(--bg-blur-color); + border: var(--hairline-width) solid var(--bg-blur-color); + box-shadow: 0 4px 16px var(--drop-shadow-color); max-width: min(var(--main-width), calc(100% - 32px)); display: flex; align-items: center; gap: 8px; font-size: 90%; z-index: 1; + text-shadow: 0 var(--hairline-width) var(--bg-color); } .carousel-item button.media-alt .media-alt-desc { overflow: hidden; From c8d1ab47489ee790b2abd3636bbb26c984dd7506 Mon Sep 17 00:00:00 2001 From: Lim Chee Aun Date: Fri, 7 Apr 2023 20:09:59 +0800 Subject: [PATCH 26/28] Need loader --- src/components/status.jsx | 13 ++++++++++--- 1 file changed, 10 insertions(+), 3 deletions(-) diff --git a/src/components/status.jsx b/src/components/status.jsx index 9dc0e570..240a381e 100644 --- a/src/components/status.jsx +++ b/src/components/status.jsx @@ -1670,8 +1670,8 @@ function ReactionsModal({ statusID, instance }) { ); })} - {uiState === 'default' && - (showMore ? ( + {uiState === 'default' ? ( + showMore ? ( { if (inView) { @@ -1689,7 +1689,14 @@ function ReactionsModal({ statusID, instance }) { ) : (

    The end.

    - ))} + ) + ) : ( + uiState === 'loading' && ( +

    + +

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

    From f357881e5ae3332a3f66152f62152a61a77bed89 Mon Sep 17 00:00:00 2001 From: Lim Chee Aun Date: Fri, 7 Apr 2023 20:22:32 +0800 Subject: [PATCH 27/28] Temporarily disable hard code focus on first column --- src/app.jsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/app.jsx b/src/app.jsx index 00039913..79dd9b04 100644 --- a/src/app.jsx +++ b/src/app.jsx @@ -146,7 +146,7 @@ function App() { const columns = document.getElementById('columns'); if (columns) { // Focus first column - columns.querySelector('.deck-container')?.focus?.(); + // columns.querySelector('.deck-container')?.focus?.(); } else { const backDrop = document.querySelector('.deck-backdrop'); if (backDrop) return; From b80bcdbf09deb5336f5ff5945b618640b23c4db0 Mon Sep 17 00:00:00 2001 From: Lim Chee Aun Date: Fri, 7 Apr 2023 21:55:10 +0800 Subject: [PATCH 28/28] Make added more prominent --- src/components/account-info.css | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/src/components/account-info.css b/src/components/account-info.css index 06bf133b..03be99c0 100644 --- a/src/components/account-info.css +++ b/src/components/account-info.css @@ -269,14 +269,20 @@ list-style: none; } #list-add-remove-container .list-add-remove button { + border-radius: 16px; display: flex; align-items: center; gap: 8px; width: 100%; + text-align: start; } #list-add-remove-container .list-add-remove button .icon { opacity: 0.15; } +#list-add-remove-container .list-add-remove button.checked { + border-color: var(--green-color); + font-weight: bold; +} #list-add-remove-container .list-add-remove button.checked .icon { opacity: 1; color: var(--green-color);