From c7f4087ed2cfc204c7d9813b6474286875d3c96d Mon Sep 17 00:00:00 2001 From: Lim Chee Aun Date: Mon, 3 Apr 2023 10:36:31 +0800 Subject: [PATCH] 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 ( ); }