phanpy/src/pages/account-statuses.jsx

301 lines
8.8 KiB
React
Raw Normal View History

import { Menu, MenuItem } from '@szhsin/react-menu';
import { useEffect, useMemo, useRef, useState } from 'preact/hooks';
2023-04-03 05:36:31 +03:00
import { useParams, useSearchParams } from 'react-router-dom';
2023-02-03 16:08:08 +03:00
import { useSnapshot } from 'valtio';
import AccountInfo from '../components/account-info';
import EmojiText from '../components/emoji-text';
2023-04-03 05:36:31 +03:00
import Icon from '../components/icon';
import Link from '../components/link';
2023-06-13 12:46:37 +03:00
import Menu2 from '../components/menu2';
import Timeline from '../components/timeline';
import { api } from '../utils/api';
2023-04-08 20:01:36 +03:00
import showToast from '../utils/show-toast';
2023-01-31 14:08:10 +03:00
import states from '../utils/states';
import { saveStatus } from '../utils/states';
2023-02-03 16:08:08 +03:00
import useTitle from '../utils/useTitle';
const LIMIT = 20;
function AccountStatuses() {
2023-02-03 16:08:08 +03:00
const snapStates = useSnapshot(states);
const { id, ...params } = useParams();
2023-04-03 05:36:31 +03:00
const [searchParams, setSearchParams] = useSearchParams();
const excludeReplies = !searchParams.get('replies');
2023-04-04 06:01:53 +03:00
const excludeBoosts = !!searchParams.get('boosts');
2023-04-03 05:36:31 +03:00
const tagged = searchParams.get('tagged');
const media = !!searchParams.get('media');
const { masto, instance, authenticated } = api({ instance: params.instance });
const accountStatusesIterator = useRef();
async function fetchAccountStatuses(firstLoad) {
2023-02-17 05:12:59 +03:00
const results = [];
if (firstLoad) {
const { value: pinnedStatuses } = await masto.v1.accounts
.listStatuses(id, {
pinned: true,
})
.next();
2023-04-03 05:36:31 +03:00
if (pinnedStatuses?.length && !tagged && !media) {
2023-02-17 05:12:59 +03:00
pinnedStatuses.forEach((status) => {
status._pinned = true;
2023-03-18 17:25:02 +03:00
saveStatus(status, instance);
2023-02-17 05:12:59 +03:00
});
if (pinnedStatuses.length >= 3) {
const pinnedStatusesIds = pinnedStatuses.map((status) => status.id);
results.push({
id: pinnedStatusesIds,
items: pinnedStatuses,
type: 'pinned',
});
} else {
results.push(...pinnedStatuses);
}
2023-02-17 05:12:59 +03:00
}
}
if (firstLoad || !accountStatusesIterator.current) {
accountStatusesIterator.current = masto.v1.accounts.listStatuses(id, {
limit: LIMIT,
2023-04-03 05:36:31 +03:00
exclude_replies: excludeReplies,
2023-04-04 06:01:53 +03:00
exclude_reblogs: excludeBoosts,
2023-04-03 05:36:31 +03:00
only_media: media,
tagged,
});
}
2023-02-17 05:12:59 +03:00
const { value, done } = await accountStatusesIterator.current.next();
if (value?.length) {
results.push(...value);
value.forEach((item) => {
saveStatus(item, instance);
});
2023-02-17 05:12:59 +03:00
}
return {
value: results,
done,
};
}
const [account, setAccount] = useState();
2023-04-03 05:36:31 +03:00
const [featuredTags, setFeaturedTags] = useState([]);
useTitle(
2023-03-13 05:07:22 +03:00
`${account?.displayName ? account.displayName + ' ' : ''}@${
account?.acct ? account.acct : 'Account posts'
}`,
2023-02-11 11:27:40 +03:00
'/:instance?/a/:id',
);
useEffect(() => {
(async () => {
try {
const acc = await masto.v1.accounts.fetch(id);
console.log(acc);
setAccount(acc);
} catch (e) {
console.error(e);
}
2023-04-03 05:36:31 +03:00
try {
const featuredTags = await masto.v1.accounts.listFeaturedTags(id);
console.log({ featuredTags });
setFeaturedTags(featuredTags);
} catch (e) {
console.error(e);
}
})();
}, [id]);
const { displayName, acct, emojis } = account || {};
const filterBarRef = useRef();
const TimelineStart = useMemo(() => {
const cachedAccount = snapStates.accounts[`${id}@${instance}`];
2023-04-04 06:01:53 +03:00
const filtered = !excludeReplies || excludeBoosts || tagged || media;
return (
2023-04-03 05:36:31 +03:00
<>
<AccountInfo
instance={instance}
account={cachedAccount || id}
fetchAccount={() => masto.v1.accounts.fetch(id)}
authenticated={authenticated}
standalone
/>
<div class="filter-bar" ref={filterBarRef}>
{filtered ? (
<Link
to={`/${instance}/a/${id}`}
class="insignificant filter-clear"
title="Clear filters"
>
<Icon icon="x" size="l" />
</Link>
) : (
<Icon icon="filter" class="insignificant" size="l" />
)}
2023-04-03 05:36:31 +03:00
<Link
to={`/${instance}/a/${id}${excludeReplies ? '?replies=1' : ''}`}
2023-04-08 20:01:36 +03:00
onClick={() => {
if (excludeReplies) {
showToast('Showing post with replies');
}
}}
2023-04-03 05:36:31 +03:00
class={excludeReplies ? '' : 'is-active'}
>
+ Replies
</Link>
2023-04-04 06:01:53 +03:00
<Link
to={`/${instance}/a/${id}${excludeBoosts ? '' : '?boosts=0'}`}
2023-04-08 20:01:36 +03:00
onClick={() => {
if (!excludeBoosts) {
showToast('Showing posts without boosts');
}
}}
2023-04-04 06:01:53 +03:00
class={!excludeBoosts ? '' : 'is-active'}
>
- Boosts
</Link>
2023-04-03 05:36:31 +03:00
<Link
to={`/${instance}/a/${id}${media ? '' : '?media=1'}`}
2023-04-08 20:01:36 +03:00
onClick={() => {
if (!media) {
showToast('Showing posts with media');
}
}}
2023-04-03 05:36:31 +03:00
class={media ? 'is-active' : ''}
>
Media
</Link>
{featuredTags.map((tag) => (
<Link
to={`/${instance}/a/${id}${
tagged === tag.name
? ''
: `?tagged=${encodeURIComponent(tag.name)}`
}`}
2023-04-08 20:01:36 +03:00
onClick={() => {
if (tagged !== tag.name) {
showToast(`Showing posts tagged with #${tag.name}`);
}
}}
2023-04-03 05:36:31 +03:00
class={tagged === tag.name ? 'is-active' : ''}
>
<span>
<span class="more-insignificant">#</span>
{tag.name}
</span>
{
// The count differs based on instance 😅
}
{/* <span class="filter-count">{tag.statusesCount}</span> */}
</Link>
))}
</div>
</>
);
2023-04-03 05:36:31 +03:00
}, [
id,
instance,
authenticated,
excludeReplies,
2023-04-04 06:01:53 +03:00
excludeBoosts,
2023-04-03 05:36:31 +03:00
featuredTags,
tagged,
media,
]);
useEffect(() => {
// 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,
});
}
2023-04-04 06:01:53 +03:00
}, [featuredTags, tagged, media, excludeReplies, excludeBoosts]);
const accountInstance = useMemo(() => {
if (!account?.url) return null;
const domain = new URL(account.url).hostname;
return domain;
}, [account]);
const sameInstance = instance === accountInstance;
const allowSwitch = !!account && !sameInstance;
return (
<Timeline
key={id}
title={`${account?.acct ? '@' + account.acct : 'Posts'}`}
2023-01-31 14:08:10 +03:00
titleComponent={
<h1
class="header-account"
2023-02-17 05:14:44 +03:00
// onClick={() => {
// states.showAccount = {
// account,
// instance,
// };
// }}
2023-01-31 14:08:10 +03:00
>
<b>
<EmojiText text={displayName} emojis={emojis} />
</b>
2023-01-31 14:08:10 +03:00
<div>
<span>@{acct}</span>
2023-01-31 14:08:10 +03:00
</div>
</h1>
}
2023-02-27 18:59:41 +03:00
id="account-statuses"
instance={instance}
emptyText="Nothing to see here yet."
2023-04-29 17:22:07 +03:00
errorText="Unable to load posts"
fetchItems={fetchAccountStatuses}
useItemID
2023-02-03 16:08:08 +03:00
boostsCarousel={snapStates.settings.boostsCarousel}
timelineStart={TimelineStart}
2023-04-04 06:01:53 +03:00
refresh={excludeReplies + excludeBoosts + tagged + media}
headerEnd={
2023-06-13 12:46:37 +03:00
<Menu2
portal
// setDownOverflow
overflow="auto"
viewScroll="close"
position="anchor"
menuButton={
<button type="button" class="plain">
<Icon icon="more" size="l" />
</button>
}
>
<MenuItem
disabled={!allowSwitch}
onClick={() => {
(async () => {
try {
const { masto } = api({
instance: accountInstance,
});
const acc = await masto.v1.accounts.lookup({
acct: account.acct,
});
const { id } = acc;
location.hash = `/${accountInstance}/a/${id}`;
} catch (e) {
console.error(e);
alert('Unable to fetch account info');
}
})();
}}
>
<Icon icon="transfer" />{' '}
<small class="menu-double-lines">
Switch to account's instance (<b>{accountInstance}</b>)
</small>
</MenuItem>
2023-06-13 12:46:37 +03:00
</Menu2>
}
/>
);
}
export default AccountStatuses;