@@ -10,7 +10,7 @@ const SORT_OPTIONS = {
10
10
ascending: false // boolean for ascending/descending order, true = ascending
11
11
}
12
12
13
- const publications = ref ([])
13
+ const publications = ref ([]);
14
14
15
15
const sorting = ref ({
16
16
sort: " author" ,
@@ -22,22 +22,46 @@ const previousSort = ref("author")
22
22
const search = ref (" " )
23
23
const searchOpen = ref (false )
24
24
25
+ const selectedTags = ref (new Set ([]));
26
+ const tagsOpen = ref (false );
27
+
25
28
// helper
26
29
function sortByAuthor (a , b , order ) {
27
30
const aFirstAuthor = a .authors .split (' ,' )[0 ]
28
31
const bFirstAuthor = b .authors .split (' ,' )[0 ]
29
32
const aLastName = aFirstAuthor .split (' ' ).pop ()
30
33
const bLastName = bFirstAuthor .split (' ' ).pop ()
31
- return aLastName .localeCompare (bLastName) * order
34
+ return aLastName .localeCompare (bLastName) * order;
32
35
}
33
36
37
+ const tags = computed (() => {
38
+ const t = new Set ([]);
39
+ // get every unique tag from publications.value
40
+ publications .value .forEach ((p ) => {
41
+ if (p .tags ) {
42
+ // for every tag within the publication, add it to the Set object
43
+ p .tags .forEach ((tag ) => t .add (tag));
44
+ }
45
+ });
46
+
47
+ return t;
48
+ });
49
+
34
50
const sortedPublications = computed (() => {
35
51
// filter search string first
36
52
let filteredPubs = [... publications .value ]
37
53
if (search .value !== " " ) {
38
54
filteredPubs = filteredPubs .filter ((p ) => p .title .toLowerCase ().includes (search .value .toLowerCase ()))
39
55
}
40
56
57
+ // filter by tags
58
+ if (selectedTags .value .length > 0 ) {
59
+ filteredPubs = filteredPubs .filter ((p ) => {
60
+ if (! p .tags ) return false
61
+ return [... selectedTags .value ].every (tag => p .tags .includes (tag))
62
+ });
63
+ }
64
+
41
65
return filteredPubs .sort ((a , b ) => {
42
66
const sortKey = sorting .value .sort
43
67
const order = sorting .value .ascending ? - 1 : 1
@@ -74,8 +98,20 @@ function handleSortToggle(v) {
74
98
}
75
99
}
76
100
101
+ function handleTagSelect (tag ) {
102
+ if (selectedTags .value .has (tag)) {
103
+ selectedTags .value .delete (tag);
104
+ } else {
105
+ selectedTags .value .add (tag);
106
+ }
107
+ }
108
+
77
109
function openSearchBar () {
78
- searchOpen .value = true
110
+ searchOpen .value = true ;
111
+ }
112
+
113
+ function openTagsMenu () {
114
+ tagsOpen .value = true ;
79
115
}
80
116
81
117
onMounted (async () => {
@@ -125,6 +161,21 @@ onMounted(async () => {
125
161
height : 16px ; /* Height of the icon */
126
162
display : inline-block ;
127
163
}
164
+
165
+ .tags {
166
+ padding : 5px ;
167
+ max-width : 250px ;
168
+ }
169
+
170
+ .tag {
171
+ width : fit-content ;
172
+ }
173
+
174
+ .v-chip--selected {
175
+ background : var (--vp-c-brand-1 );
176
+ color : white ;
177
+ }
178
+
128
179
</style >
129
180
<span ></span >
130
181
<v-row
@@ -160,26 +211,45 @@ onMounted(async () => {
160
211
</v-icon>
161
212
<span v-else class="v-icon-placeholder"></span>
162
213
</v-btn>
163
- <v-menu
164
- :close-on-content-click="false"
165
- location="bottom"
166
- >
167
- <template v-slot:activator="{ props }">
168
- <v-btn v-bind="props" icon="mdi-magnify" @click="() => {
169
- sorting.sort = previousSort // reset sorting to previous to ignore the click action
170
- openSearchBar()
171
- }">
172
- </v-btn>
173
- </template>
174
- <v-card min-width="300">
175
- <v-text-field
176
- v-model="search"
177
- hide-details
178
- label="Search"
179
- >
180
- </v-text-field>
181
- </v-card>
182
- </v-menu>
214
+ <v-menu
215
+ :close-on-content-click="false"
216
+ location="bottom"
217
+ >
218
+ <template v-slot:activator="{ props }">
219
+ <v-btn v-bind="props" icon="mdi-tag-outline" @click="() => {
220
+ sorting.sort = previousSort // reset sorting to previous to ignore the click action
221
+ openTagsMenu()
222
+ }">
223
+ </v-btn>
224
+ </template>
225
+ <v-card class="tags">
226
+ <v-chip-group v-model="selectedTags" column multiple>
227
+ <v-chip v-for="tag in tags" :key="tag" class="tag" :value="tag">
228
+ {{tag}}
229
+ </v-chip>
230
+ </v-chip-group>
231
+ </v-card>
232
+ </v-menu>
233
+ <v-menu
234
+ :close-on-content-click="false"
235
+ location="bottom"
236
+ >
237
+ <template v-slot:activator="{ props }">
238
+ <v-btn v-bind="props" icon="mdi-magnify" @click="() => {
239
+ sorting.sort = previousSort // reset sorting to previous to ignore the click action
240
+ openSearchBar()
241
+ }">
242
+ </v-btn>
243
+ </template>
244
+ <v-card min-width="300">
245
+ <v-text-field
246
+ v-model="search"
247
+ hide-details
248
+ label="Search"
249
+ >
250
+ </v-text-field>
251
+ </v-card>
252
+ </v-menu>
183
253
</v-btn-toggle >
184
254
</v-row >
185
255
0 commit comments