diff --git a/src/app.css b/src/app.css index 0e2fd50a..2da3904d 100644 --- a/src/app.css +++ b/src/app.css @@ -1358,6 +1358,9 @@ body:has(.media-modal-container + .status-deck) .media-post-link { .tag.collapsed { margin: 0; } +.tag.danger { + background-color: var(--red-color); +} /* MENU POPUP */ diff --git a/src/components/account-info.jsx b/src/components/account-info.jsx index 91fd45af..af30aaca 100644 --- a/src/components/account-info.jsx +++ b/src/components/account-info.jsx @@ -12,6 +12,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 AccountBlock from './account-block'; import Avatar from './avatar'; @@ -483,6 +484,12 @@ function RelatedActions({ info, instance, authenticated }) { } }, [info, authenticated]); + useEffect(() => { + if (info && isSelf) { + updateAccount(info); + } + }, [info, isSelf]); + const loading = relationshipUIState === 'loading'; const menuInstanceRef = useRef(null); @@ -524,18 +531,22 @@ function RelatedActions({ info, instance, authenticated }) {

- {followedBy ? ( - Following you - ) : !!lastStatusAt ? ( - - Last post:{' '} - {niceDateTime(lastStatusAt, { - hideTime: true, - })} - - ) : ( - - )}{' '} + + {followedBy ? ( + Following you + ) : !!lastStatusAt ? ( + + Last post:{' '} + {niceDateTime(lastStatusAt, { + hideTime: true, + })} + + ) : ( + + )} + {muting && Muted} + {blocking && Blocked} + {' '}

{} }) { if (isImage) { // Note: type: unknown might not have width/height quickPinchZoomProps.containerProps.style.display = 'inherit'; + + useLayoutEffect(() => { + if (!isSafari) return; + if (!showOriginal) return; + (async () => { + try { + await fetch(mediaURL, { mode: 'no-cors' }); + mediaRef.current.src = mediaURL; + } catch (e) { + // Ignore + } + })(); + }, [mediaURL]); + return ( {} }) { }} onLoad={(e) => { e.target.closest('.media-image').style.backgroundImage = ''; + e.target.dataset.loaded = true; }} onError={(e) => { const { src } = e.target; diff --git a/src/pages/search.jsx b/src/pages/search.jsx index 5b451e3b..bb507f1a 100644 --- a/src/pages/search.jsx +++ b/src/pages/search.jsx @@ -1,7 +1,14 @@ import './search.css'; import { forwardRef } from 'preact/compat'; -import { useEffect, useImperativeHandle, useRef, useState } from 'preact/hooks'; +import { + useEffect, + useImperativeHandle, + useLayoutEffect, + useRef, + useState, +} from 'preact/hooks'; +import { InView } from 'react-intersection-observer'; import { useParams, useSearchParams } from 'react-router-dom'; import AccountBlock from '../components/account-block'; @@ -13,6 +20,9 @@ import Status from '../components/status'; import { api } from '../utils/api'; import useTitle from '../utils/useTitle'; +const SHORT_LIMIT = 5; +const LIMIT = 40; + function Search(props) { const params = useParams(); const { masto, instance, authenticated } = api({ @@ -40,35 +50,78 @@ function Search(props) { `/search`, ); + const [showMore, setShowMore] = useState(false); + const offsetRef = useRef(0); + useEffect(() => { + offsetRef.current = 0; + }, [type]); + + const scrollableRef = useRef(); + useLayoutEffect(() => { + scrollableRef.current?.scrollTo?.(0, 0); + }, [q, type]); + const [statusResults, setStatusResults] = useState([]); const [accountResults, setAccountResults] = useState([]); const [hashtagResults, setHashtagResults] = useState([]); + + function loadResults(firstLoad) { + setUiState('loading'); + if (firstLoad && !type) { + setStatusResults(statusResults.slice(0, SHORT_LIMIT)); + setAccountResults(accountResults.slice(0, SHORT_LIMIT)); + setHashtagResults(hashtagResults.slice(0, SHORT_LIMIT)); + } + + (async () => { + const params = { + q, + resolve: authenticated, + limit: SHORT_LIMIT, + }; + if (type) { + params.limit = LIMIT; + params.type = type; + params.offset = offsetRef.current; + } + try { + const results = await masto.v2.search(params); + console.log(results); + if (type && !firstLoad) { + if (type === 'statuses') { + setStatusResults((prev) => [...prev, ...results.statuses]); + } else if (type === 'accounts') { + setAccountResults((prev) => [...prev, ...results.accounts]); + } else if (type === 'hashtags') { + setHashtagResults((prev) => [...prev, ...results.hashtags]); + } + offsetRef.current = offsetRef.current + LIMIT; + setShowMore(!!results[type]?.length); + } else { + setStatusResults(results.statuses); + setAccountResults(results.accounts); + setHashtagResults(results.hashtags); + } + setUiState('default'); + } catch (err) { + console.error(err); + setUiState('error'); + } + })(); + } + useEffect(() => { // searchFieldRef.current?.focus?.(); // searchFormRef.current?.focus?.(); if (q) { // searchFieldRef.current.value = q; searchFormRef.current?.setValue?.(q); - - setUiState('loading'); - (async () => { - const results = await masto.v2.search({ - q, - limit: type ? 40 : 5, - resolve: authenticated, - type, - }); - console.log(results); - setStatusResults(results.statuses); - setAccountResults(results.accounts); - setHashtagResults(results.hashtags); - setUiState('default'); - })(); + loadResults(true); } }, [q, type, instance]); return ( -
+
@@ -110,7 +163,7 @@ function Search(props) { ))}
)} - {!!q && uiState !== 'loading' ? ( + {!!q ? ( <> {(!type || type === 'accounts') && ( <> @@ -121,7 +174,7 @@ function Search(props) { <>
    {accountResults.map((account) => ( -
  • +
  • ) : ( -

    No accounts found.

    + !type && + (uiState === 'loading' ? ( +

    + +

    + ) : ( +

    No accounts found.

    + )) )} )} @@ -154,7 +214,7 @@ function Search(props) { <>