refactor: Simplify announcement filtering and improve archived announcements handling

This commit is contained in:
Ushie
2025-06-14 05:40:35 +03:00
parent e2798140fc
commit 69b412ecc8

View File

@@ -22,52 +22,51 @@
import ChevronDown from 'svelte-material-icons/ChevronDown.svelte';
import Create from 'svelte-material-icons/Plus.svelte';
let searchParams: Readable<URLSearchParams>;
let expanded = false;
if (building) searchParams = readable(new URLSearchParams());
else searchParams = derived(page, ($page) => $page.url.searchParams);
const searchParams: Readable<URLSearchParams> = building
? readable(new URLSearchParams())
: derived(page, ($page) => $page.url.searchParams);
let searchTerm = $searchParams.get('s') || '';
let displayedTerm = '';
$: query = createQuery(queries.announcements());
$: tagsQuery = createQuery(queries.announcementTags());
$: selectedTags = $searchParams.getAll('tag');
let expanded = false;
function filterAnnouncements(
announcements: Iterable<ResponseAnnouncement>,
search: string,
selectedTags: string[]
): ResponseAnnouncement[] {
const announcementFilter = createFilter(Array.from(announcements), {
searcherOptions: {
keys: ['title', 'content']
},
additionalFilter: (announcement: ResponseAnnouncement, tags: string[]): boolean => {
return (
tags.length === 0 ||
tags.some((tag) => announcement.tags && announcement.tags.includes(tag))
);
}
});
return announcementFilter(selectedTags, search);
}
// Make sure we don't have to filter the announcements after every key press
let displayedTerm = '';
const update = () => {
displayedTerm = searchTerm;
const url = new URL(window.location.href);
url.pathname = '/announcements';
if (searchTerm) url.searchParams.set('s', searchTerm);
else url.searchParams.delete('s');
searchTerm ? url.searchParams.set('s', searchTerm) : url.searchParams.delete('s');
};
onMount(update);
const archivedAnnouncements = (announcements: ResponseAnnouncement[]) =>
announcements.filter((a) => a.archived_at && moment(a.archived_at).isBefore(moment()));
const activeAnnouncements = (announcements: ResponseAnnouncement[]) =>
announcements.filter((a) => !a.archived_at || moment(a.archived_at).isAfter(moment()));
const filterAnnouncements = (
announcements: ResponseAnnouncement[],
search: string,
tags: string[]
): ResponseAnnouncement[] => {
const announcementFilter = createFilter(announcements, {
searcherOptions: { keys: ['title', 'content'] },
additionalFilter: (a: ResponseAnnouncement, tags: string[]) =>
tags.length === 0 || tags.some((tag) => a.tags?.includes(tag))
});
return announcementFilter(tags, search);
};
onMount(() => {
debounce(update)();
});
</script>
<div class="search">
@@ -92,60 +91,61 @@
</Query>
<Query {query} let:data>
<div class="cards">
{#each filterAnnouncements(data.announcements, displayedTerm, selectedTags) as announcement}
{#if !announcement.archived_at || moment(announcement.archived_at).isAfter(moment())}
{#key selectedTags || displayedTerm}
<div in:fly={{ y: 10, easing: quintOut, duration: 750 }}>
<AnnouncementCard {announcement} />
</div>
{/key}
{/if}
{/each}
</div>
<div
role="button"
class="expand-archived"
aria-expanded={expanded}
class:closed={!expanded}
on:click={() => (expanded = !expanded)}
on:keypress={() => (expanded = !expanded)}
tabindex="0"
>
<h4>Archived announcements</h4>
<div id="arrow" style:transform={expanded ? 'rotate(0deg)' : 'rotate(-180deg)'}>
<ChevronDown size="24px" color="var(--surface-six)" />
</div>
</div>
{#if expanded}
<div
class="cards"
in:slide={{ easing: quintIn, duration: 250 }}
out:slide={{ easing: quintOut, duration: 250 }}
>
{#each filterAnnouncements(data.announcements, displayedTerm, selectedTags) as announcement}
{#if announcement.archived_at && moment(announcement.archived_at).isBefore(moment())}
{#key selectedTags || displayedTerm}
<AnnouncementCard {announcement} />
{/key}
{/if}
{#if activeAnnouncements(filterAnnouncements(data.announcements, displayedTerm, selectedTags)).length}
<div class="cards">
{#each activeAnnouncements(filterAnnouncements(data.announcements, displayedTerm, selectedTags)) as announcement}
<div in:fly={{ y: 10, easing: quintOut, duration: 750 }}>
<AnnouncementCard {announcement} />
</div>
{/each}
</div>
{/if}
{#if archivedAnnouncements(filterAnnouncements(data.announcements, displayedTerm, selectedTags)).length}
<div
role="button"
class="expand-archived"
aria-expanded={expanded}
on:click={() => (expanded = !expanded)}
on:keypress={() => (expanded = !expanded)}
tabindex="0"
>
<h4>Archived announcements</h4>
<div id="arrow" style:transform={expanded ? 'rotate(-180deg)' : 'rotate(0deg)'}>
<ChevronDown size="24px" color="var(--surface-six)" />
</div>
</div>
{#if expanded}
<div
class="cards"
in:slide={{ easing: quintIn, duration: 250 }}
out:slide={{ easing: quintOut, duration: 250 }}
>
{#each archivedAnnouncements(filterAnnouncements(data.announcements, displayedTerm, selectedTags)) as announcement}
<AnnouncementCard {announcement} />
{/each}
</div>
{/if}
{/if}
</Query>
</main>
<style lang="scss">
main {
display: flex;
flex-direction: column;
gap: 1rem;
}
.expand-archived {
display: flex;
align-items: center;
justify-content: space-between;
cursor: pointer;
user-select: none;
padding: 0rem 0.25rem;
padding-inline: 0.25rem;
#arrow {
height: 1.5rem;
@@ -174,7 +174,6 @@
.cards {
display: grid;
grid-template-columns: repeat(3, 1fr);
padding: 16px 0;
width: 100%;
gap: 16px;