import { MenuItem } from '@szhsin/react-menu';
import {
useCallback,
useEffect,
useMemo,
useRef,
useState,
} from 'preact/hooks';
import { useParams, useSearchParams } from 'react-router-dom';
import { useSnapshot } from 'valtio';
import AccountInfo from '../components/account-info';
import EmojiText from '../components/emoji-text';
import Icon from '../components/icon';
import Link from '../components/link';
import Menu2 from '../components/menu2';
import Timeline from '../components/timeline';
import { api } from '../utils/api';
import pmem from '../utils/pmem';
import showToast from '../utils/show-toast';
import states from '../utils/states';
import { saveStatus } from '../utils/states';
import useTitle from '../utils/useTitle';
const LIMIT = 20;
const MIN_YEAR = 1983;
const MIN_YEAR_MONTH = `${MIN_YEAR}-01`; // Birth of the Internet
const supportsInputMonth = (() => {
try {
const input = document.createElement('input');
input.setAttribute('type', 'month');
return input.type === 'month';
} catch (e) {
return false;
}
})();
async function _isSearchEnabled(instance) {
const { masto } = api({ instance });
const results = await masto.v2.search.fetch({
q: 'from:me',
type: 'statuses',
limit: 1,
});
return !!results?.statuses?.length;
}
const isSearchEnabled = pmem(_isSearchEnabled);
function AccountStatuses() {
const snapStates = useSnapshot(states);
const { id, ...params } = useParams();
const [searchParams, setSearchParams] = useSearchParams();
const month = searchParams.get('month');
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 });
const { masto: currentMasto, instance: currentInstance } = api();
const accountStatusesIterator = useRef();
const allSearchParams = [month, excludeReplies, excludeBoosts, tagged, media];
const [account, setAccount] = useState();
const searchOffsetRef = useRef(0);
useEffect(() => {
searchOffsetRef.current = 0;
}, allSearchParams);
const sameCurrentInstance = useMemo(
() => instance === currentInstance,
[instance, currentInstance],
);
const [searchEnabled, setSearchEnabled] = useState(false);
useEffect(() => {
// Only enable for current logged-in instance
// Most remote instances don't allow unauthenticated searches
if (!sameCurrentInstance) return;
if (!account?.acct) return;
(async () => {
const enabled = await isSearchEnabled(instance);
console.log({ enabled });
setSearchEnabled(enabled);
})();
}, [instance, sameCurrentInstance, account?.acct]);
async function fetchAccountStatuses(firstLoad) {
const isValidMonth = /^\d{4}-[01]\d$/.test(month);
const isValidYear = month?.split?.('-')?.[0] >= MIN_YEAR;
if (isValidMonth && isValidYear) {
if (!account) {
return {
value: [],
done: true,
};
}
const [_year, _month] = month.split('-');
const monthIndex = parseInt(_month, 10) - 1;
// YYYY-MM (no day)
// Search options:
// - from:account
// - after:YYYY-MM-DD (non-inclusive)
// - before:YYYY-MM-DD (non-inclusive)
// Last day of previous month
const after = new Date(_year, monthIndex, 0);
const afterStr = `${after.getFullYear()}-${(after.getMonth() + 1)
.toString()
.padStart(2, '0')}-${after.getDate().toString().padStart(2, '0')}`;
// First day of next month
const before = new Date(_year, monthIndex + 1, 1);
const beforeStr = `${before.getFullYear()}-${(before.getMonth() + 1)
.toString()
.padStart(2, '0')}-${before.getDate().toString().padStart(2, '0')}`;
console.log({
month,
_year,
_month,
monthIndex,
after,
before,
afterStr,
beforeStr,
});
let limit;
if (firstLoad) {
limit = LIMIT + 1;
searchOffsetRef.current = 0;
} else {
limit = LIMIT + searchOffsetRef.current + 1;
searchOffsetRef.current += LIMIT;
}
const searchResults = await masto.v2.search.fetch({
q: `from:${account.acct} after:${afterStr} before:${beforeStr}`,
type: 'statuses',
limit,
offset: searchOffsetRef.current,
});
if (searchResults?.statuses?.length) {
const value = searchResults.statuses.slice(0, LIMIT);
value.forEach((item) => {
saveStatus(item, instance);
});
const done = searchResults.statuses.length <= LIMIT;
return { value, done };
} else {
return { value: [], done: true };
}
}
const results = [];
if (firstLoad) {
const { value } = await masto.v1.accounts
.$select(id)
.statuses.list({
pinned: true,
})
.next();
if (value?.length && !tagged && !media) {
const pinnedStatuses = value.map((status) => {
saveStatus(status, instance);
return {
...status,
_pinned: true,
};
});
if (pinnedStatuses.length >= 3) {
const pinnedStatusesIds = pinnedStatuses.map((status) => status.id);
results.push({
id: pinnedStatusesIds,
items: pinnedStatuses,
type: 'pinned',
});
} else {
results.push(...pinnedStatuses);
}
}
}
if (firstLoad || !accountStatusesIterator.current) {
accountStatusesIterator.current = masto.v1.accounts
.$select(id)
.statuses.list({
limit: LIMIT,
exclude_replies: excludeReplies,
exclude_reblogs: excludeBoosts,
only_media: media,
tagged,
});
}
const { value, done } = await accountStatusesIterator.current.next();
if (value?.length) {
results.push(...value);
value.forEach((item) => {
saveStatus(item, instance);
});
}
return {
value: results,
done,
};
}
const [featuredTags, setFeaturedTags] = useState([]);
useTitle(
account?.acct
? `${account?.displayName ? account.displayName + ' ' : ''}@${
account.acct
}${
!excludeReplies
? ' (+ Replies)'
: excludeBoosts
? ' (- Boosts)'
: tagged
? ` (#${tagged})`
: media
? ' (Media)'
: month
? ` (${new Date(month).toLocaleString('default', {
month: 'long',
year: 'numeric',
})})`
: ''
}`
: 'Account posts',
'/:instance?/a/:id',
);
const fetchAccountPromiseRef = useRef();
const fetchAccount = useCallback(() => {
const fetchPromise =
fetchAccountPromiseRef.current || masto.v1.accounts.$select(id).fetch();
fetchAccountPromiseRef.current = fetchPromise;
return fetchPromise;
}, [id, masto]);
useEffect(() => {
(async () => {
try {
const acc = await fetchAccount();
console.log(acc);
setAccount(acc);
} 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);
}
})();
}, [id]);
const { displayName, acct, emojis } = account || {};
const accountInfoMemo = useMemo(() => {
const cachedAccount = snapStates.accounts[`${id}@${instance}`];
return (