Skip to content

Commit

Permalink
Experiment: month filter for account statuses
Browse files Browse the repository at this point in the history
  • Loading branch information
cheeaun committed Oct 20, 2023
1 parent d1aedca commit ab7df0f
Show file tree
Hide file tree
Showing 2 changed files with 259 additions and 7 deletions.
42 changes: 42 additions & 0 deletions src/app.css
Original file line number Diff line number Diff line change
Expand Up @@ -2127,6 +2127,48 @@ ul.link-list li a .icon {
pointer-events: none;
opacity: 0.5;
}

.filter-field {
flex-shrink: 0;
padding: 8px 16px;
border-radius: 999px;
color: var(--text-color);
background-color: var(--bg-color);
border: 2px solid transparent;
margin: 0;
appearance: none;
line-height: 1;
font-size: 90%;

&:placeholder-shown {
color: var(--text-insignificant-color);
}

&:is(:hover, :focus-visible) {
border-color: var(--link-light-color);
}
&:focus {
outline-color: var(--link-light-color);
}
&.is-active {
border-color: var(--link-color);
box-shadow: inset 0 0 8px var(--link-faded-color);
}

:is(input, select) {
background-color: transparent;
border: 0;
padding: 0;
margin: 0;
color: inherit;
font-size: inherit;
line-height: inherit;
appearance: none;
border-radius: 0;
box-shadow: none;
outline: none;
}
}
}
.filter-bar.centered {
justify-content: center;
Expand Down
224 changes: 217 additions & 7 deletions src/pages/account-statuses.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -17,17 +17,119 @@ import useTitle from '../utils/useTitle';

const LIMIT = 20;

const supportsInputMonth = (() => {
try {
const input = document.createElement('input');
input.setAttribute('type', 'month');
return input.type === 'month';
} catch (e) {
return false;
}
})();

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 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 === api().instance,
[instance],
);
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 results = await masto.v2.search.fetch({
q: `from:${account?.acct}`,
type: 'statuses',
limit: 1,
});
setSearchEnabled(!!results?.statuses?.length);
})();
}, [sameCurrentInstance, account?.acct]);

async function fetchAccountStatuses(firstLoad) {
if (/^\d{4}-[01]\d$/.test(month)) {
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: pinnedStatuses } = await masto.v1.accounts
Expand Down Expand Up @@ -78,7 +180,6 @@ function AccountStatuses() {
};
}

const [account, setAccount] = useState();
const [featuredTags, setFeaturedTags] = useState([]);
useTitle(
`${account?.displayName ? account.displayName + ' ' : ''}@${
Expand Down Expand Up @@ -112,7 +213,8 @@ function AccountStatuses() {
const filterBarRef = useRef();
const TimelineStart = useMemo(() => {
const cachedAccount = snapStates.accounts[`${id}@${instance}`];
const filtered = !excludeReplies || excludeBoosts || tagged || media;
const filtered =
!excludeReplies || excludeBoosts || tagged || media || !!month;
return (
<>
<AccountInfo
Expand Down Expand Up @@ -170,6 +272,7 @@ function AccountStatuses() {
</Link>
{featuredTags.map((tag) => (
<Link
key={tag.id}
to={`/${instance}/a/${id}${
tagged === tag.name
? ''
Expand All @@ -192,18 +295,56 @@ function AccountStatuses() {
{/* <span class="filter-count">{tag.statusesCount}</span> */}
</Link>
))}
{searchEnabled &&
(supportsInputMonth ? (
<input
type="month"
class={`filter-field ${month ? 'is-active' : ''}`}
disabled={!account?.acct}
value={month || ''}
min="1983-01" // Birth of the Internet
max={new Date().toISOString().slice(0, 7)}
onInput={(e) => {
const { value } = e.currentTarget;
setSearchParams(
value
? {
month: value,
}
: {},
);
}}
/>
) : (
// Fallback to <select> for month and <input type="number"> for year
<MonthPicker
class={`filter-field ${month ? 'is-active' : ''}`}
disabled={!account?.acct}
value={month || ''}
min="1983-01" // Birth of the Internet
max={new Date().toISOString().slice(0, 7)}
onInput={(e) => {
const { value } = e;
setSearchParams(
value
? {
month: value,
}
: {},
);
}}
/>
))}
</div>
</>
);
}, [
id,
instance,
authenticated,
excludeReplies,
excludeBoosts,
featuredTags,
tagged,
media,
searchEnabled,
...allSearchParams,
]);

useEffect(() => {
Expand Down Expand Up @@ -258,7 +399,13 @@ function AccountStatuses() {
useItemID
boostsCarousel={snapStates.settings.boostsCarousel}
timelineStart={TimelineStart}
refresh={[excludeReplies, excludeBoosts, tagged, media].toString()}
refresh={[
excludeReplies,
excludeBoosts,
tagged,
media,
month + account?.acct,
].toString()}
headerEnd={
<Menu2
portal
Expand Down Expand Up @@ -303,4 +450,67 @@ function AccountStatuses() {
);
}

function MonthPicker(props) {
const {
class: className,
disabled,
value,
min,
max,
onInput = () => {},
} = props;
const [_year, _month] = value?.split('-') || [];
const monthFieldRef = useRef();
const yearFieldRef = useRef();

return (
<div class={className}>
<select
ref={monthFieldRef}
disabled={disabled}
value={_month || ''}
onInput={(e) => {
const { value } = e.currentTarget;
onInput({
value: value ? `${yearFieldRef.current.value}-${value}` : '',
});
}}
>
<option value="">Month</option>
<option disabled>-----</option>
{Array.from({ length: 12 }, (_, i) => (
<option
value={
// Month is 1-indexed
(i + 1).toString().padStart(2, '0')
}
key={i}
>
{new Date(0, i).toLocaleString('default', {
month: 'long',
})}
</option>
))}
</select>{' '}
<input
ref={yearFieldRef}
type="number"
disabled={disabled}
value={_year || new Date().getFullYear()}
min={min?.slice(0, 4) || '1983'}
max={max?.slice(0, 4) || new Date().getFullYear()}
onInput={(e) => {
const { value } = e.currentTarget;
onInput({
value: value ? `${value}-${monthFieldRef.current.value}` : '',
});
}}
style={{
width: '4.5em',
}}
/>
</div>
);
}

export default AccountStatuses;

0 comments on commit ab7df0f

Please sign in to comment.