|
22 | 22 | import ChevronDown from 'svelte-material-icons/ChevronDown.svelte'; |
23 | 23 | import Create from 'svelte-material-icons/Plus.svelte'; |
24 | 24 |
|
25 | | - let searchParams: Readable<URLSearchParams>; |
| 25 | + let expanded = false; |
26 | 26 |
|
27 | | - if (building) searchParams = readable(new URLSearchParams()); |
28 | | - else searchParams = derived(page, ($page) => $page.url.searchParams); |
| 27 | + const searchParams: Readable<URLSearchParams> = building |
| 28 | + ? readable(new URLSearchParams()) |
| 29 | + : derived(page, ($page) => $page.url.searchParams); |
29 | 30 |
|
30 | 31 | let searchTerm = $searchParams.get('s') || ''; |
| 32 | + let displayedTerm = ''; |
31 | 33 |
|
32 | 34 | $: query = createQuery(queries.announcements()); |
33 | 35 | $: tagsQuery = createQuery(queries.announcementTags()); |
34 | 36 | $: selectedTags = $searchParams.getAll('tag'); |
35 | 37 |
|
36 | | - let expanded = false; |
37 | | -
|
38 | | - function filterAnnouncements( |
39 | | - announcements: Iterable<ResponseAnnouncement>, |
40 | | - search: string, |
41 | | - selectedTags: string[] |
42 | | - ): ResponseAnnouncement[] { |
43 | | - const announcementFilter = createFilter(Array.from(announcements), { |
44 | | - searcherOptions: { |
45 | | - keys: ['title', 'content'] |
46 | | - }, |
47 | | - additionalFilter: (announcement: ResponseAnnouncement, tags: string[]): boolean => { |
48 | | - return ( |
49 | | - tags.length === 0 || |
50 | | - tags.some((tag) => announcement.tags && announcement.tags.includes(tag)) |
51 | | - ); |
52 | | - } |
53 | | - }); |
54 | | -
|
55 | | - return announcementFilter(selectedTags, search); |
56 | | - } |
57 | | -
|
58 | | - // Make sure we don't have to filter the announcements after every key press |
59 | | - let displayedTerm = ''; |
60 | 38 | const update = () => { |
61 | 39 | displayedTerm = searchTerm; |
62 | 40 |
|
63 | 41 | const url = new URL(window.location.href); |
64 | 42 | url.pathname = '/announcements'; |
65 | 43 |
|
66 | | - if (searchTerm) url.searchParams.set('s', searchTerm); |
67 | | - else url.searchParams.delete('s'); |
| 44 | + searchTerm ? url.searchParams.set('s', searchTerm) : url.searchParams.delete('s'); |
68 | 45 | }; |
69 | 46 |
|
70 | | - onMount(update); |
| 47 | + const archivedAnnouncements = (announcements: ResponseAnnouncement[]) => |
| 48 | + announcements.filter((a) => a.archived_at && moment(a.archived_at).isBefore(moment())); |
| 49 | + const activeAnnouncements = (announcements: ResponseAnnouncement[]) => |
| 50 | + announcements.filter((a) => !a.archived_at || moment(a.archived_at).isAfter(moment())); |
| 51 | +
|
| 52 | + const filterAnnouncements = ( |
| 53 | + announcements: ResponseAnnouncement[], |
| 54 | + search: string, |
| 55 | + tags: string[] |
| 56 | + ): ResponseAnnouncement[] => { |
| 57 | + const announcementFilter = createFilter(announcements, { |
| 58 | + searcherOptions: { keys: ['title', 'content'] }, |
| 59 | +
|
| 60 | + additionalFilter: (a: ResponseAnnouncement, tags: string[]) => |
| 61 | + tags.length === 0 || tags.some((tag) => a.tags?.includes(tag)) |
| 62 | + }); |
| 63 | +
|
| 64 | + return announcementFilter(tags, search); |
| 65 | + }; |
| 66 | +
|
| 67 | + onMount(() => { |
| 68 | + debounce(update)(); |
| 69 | + }); |
71 | 70 | </script> |
72 | 71 |
|
73 | 72 | <div class="search"> |
|
92 | 91 | </Query> |
93 | 92 |
|
94 | 93 | <Query {query} let:data> |
95 | | - <div class="cards"> |
96 | | - {#each filterAnnouncements(data.announcements, displayedTerm, selectedTags) as announcement} |
97 | | - {#if !announcement.archived_at || moment(announcement.archived_at).isAfter(moment())} |
98 | | - {#key selectedTags || displayedTerm} |
99 | | - <div in:fly={{ y: 10, easing: quintOut, duration: 750 }}> |
100 | | - <AnnouncementCard {announcement} /> |
101 | | - </div> |
102 | | - {/key} |
103 | | - {/if} |
104 | | - {/each} |
105 | | - </div> |
106 | | - |
107 | | - <div |
108 | | - role="button" |
109 | | - class="expand-archived" |
110 | | - aria-expanded={expanded} |
111 | | - class:closed={!expanded} |
112 | | - on:click={() => (expanded = !expanded)} |
113 | | - on:keypress={() => (expanded = !expanded)} |
114 | | - tabindex="0" |
115 | | - > |
116 | | - <h4>Archived announcements</h4> |
117 | | - |
118 | | - <div id="arrow" style:transform={expanded ? 'rotate(0deg)' : 'rotate(-180deg)'}> |
119 | | - <ChevronDown size="24px" color="var(--surface-six)" /> |
| 94 | + {#if activeAnnouncements(filterAnnouncements(data.announcements, displayedTerm, selectedTags)).length} |
| 95 | + <div class="cards"> |
| 96 | + {#each activeAnnouncements(filterAnnouncements(data.announcements, displayedTerm, selectedTags)) as announcement} |
| 97 | + <div in:fly={{ y: 10, easing: quintOut, duration: 750 }}> |
| 98 | + <AnnouncementCard {announcement} /> |
| 99 | + </div> |
| 100 | + {/each} |
120 | 101 | </div> |
121 | | - </div> |
| 102 | + {/if} |
122 | 103 |
|
123 | | - {#if expanded} |
| 104 | + {#if archivedAnnouncements(filterAnnouncements(data.announcements, displayedTerm, selectedTags)).length} |
124 | 105 | <div |
125 | | - class="cards" |
126 | | - in:slide={{ easing: quintIn, duration: 250 }} |
127 | | - out:slide={{ easing: quintOut, duration: 250 }} |
| 106 | + role="button" |
| 107 | + class="expand-archived" |
| 108 | + aria-expanded={expanded} |
| 109 | + on:click={() => (expanded = !expanded)} |
| 110 | + on:keypress={() => (expanded = !expanded)} |
| 111 | + tabindex="0" |
128 | 112 | > |
129 | | - {#each filterAnnouncements(data.announcements, displayedTerm, selectedTags) as announcement} |
130 | | - {#if announcement.archived_at && moment(announcement.archived_at).isBefore(moment())} |
131 | | - {#key selectedTags || displayedTerm} |
132 | | - <AnnouncementCard {announcement} /> |
133 | | - {/key} |
134 | | - {/if} |
135 | | - {/each} |
| 113 | + <h4>Archived announcements</h4> |
| 114 | + |
| 115 | + <div id="arrow" style:transform={expanded ? 'rotate(-180deg)' : 'rotate(0deg)'}> |
| 116 | + <ChevronDown size="24px" color="var(--surface-six)" /> |
| 117 | + </div> |
136 | 118 | </div> |
| 119 | + |
| 120 | + {#if expanded} |
| 121 | + <div |
| 122 | + class="cards" |
| 123 | + in:slide={{ easing: quintIn, duration: 250 }} |
| 124 | + out:slide={{ easing: quintOut, duration: 250 }} |
| 125 | + > |
| 126 | + {#each archivedAnnouncements(filterAnnouncements(data.announcements, displayedTerm, selectedTags)) as announcement} |
| 127 | + <AnnouncementCard {announcement} /> |
| 128 | + {/each} |
| 129 | + </div> |
| 130 | + {/if} |
137 | 131 | {/if} |
138 | 132 | </Query> |
139 | 133 | </main> |
140 | 134 |
|
141 | 135 | <style lang="scss"> |
| 136 | + main { |
| 137 | + display: flex; |
| 138 | + flex-direction: column; |
| 139 | + gap: 1rem; |
| 140 | + } |
| 141 | +
|
142 | 142 | .expand-archived { |
143 | 143 | display: flex; |
144 | 144 | align-items: center; |
145 | 145 | justify-content: space-between; |
146 | 146 | cursor: pointer; |
147 | 147 | user-select: none; |
148 | | - padding: 0rem 0.25rem; |
| 148 | + padding-inline: 0.25rem; |
149 | 149 |
|
150 | 150 | #arrow { |
151 | 151 | height: 1.5rem; |
|
0 commit comments