@paris-ias/list 1.1.20 → 1.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/module.json +1 -1
- package/dist/runtime/components/list/atoms/ResultsList.vue +12 -2
- package/dist/runtime/components/list/molecules/GlobalSearchInput.vue +67 -203
- package/dist/runtime/components/list/molecules/ResultsContainer.vue +40 -49
- package/dist/runtime/components/list/organisms/Results.vue +152 -79
- package/dist/runtime/composables/useSearchGroups.d.ts +39 -0
- package/dist/runtime/composables/useSearchGroups.js +128 -0
- package/dist/runtime/stores/root.d.ts +9 -0
- package/dist/runtime/stores/root.js +11 -1
- package/dist/runtime/translations/en.json +16 -1
- package/dist/runtime/translations/fr.json +16 -1
- package/package.json +2 -2
package/dist/module.json
CHANGED
|
@@ -26,17 +26,27 @@ const props = defineProps({
|
|
|
26
26
|
type: String,
|
|
27
27
|
default: "people",
|
|
28
28
|
},
|
|
29
|
+
// The underlying list module that owns the item card + store config. For
|
|
30
|
+
// modifier sub-categories (e.g. team, tools, media) this differs from
|
|
31
|
+
// `type`, which is only a result bucket key. Defaults to `type` so plain
|
|
32
|
+
// per-module list views keep working unchanged.
|
|
33
|
+
parentType: {
|
|
34
|
+
type: String,
|
|
35
|
+
default: "",
|
|
36
|
+
},
|
|
29
37
|
pathPrefix: {
|
|
30
38
|
type: String,
|
|
31
39
|
default: "people-slug",
|
|
32
40
|
},
|
|
33
41
|
})
|
|
34
42
|
|
|
43
|
+
const resolvedParentType = computed(() => props.parentType || props.type)
|
|
44
|
+
|
|
35
45
|
const itemTemplate = computed(() =>
|
|
36
46
|
resolveComponent(
|
|
37
47
|
(
|
|
38
|
-
capitalize(
|
|
39
|
-
capitalize($stores[
|
|
48
|
+
capitalize(resolvedParentType.value) +
|
|
49
|
+
capitalize($stores[resolvedParentType.value].view.name) +
|
|
40
50
|
"Item"
|
|
41
51
|
).toString(),
|
|
42
52
|
),
|
|
@@ -1,108 +1,65 @@
|
|
|
1
1
|
<template>
|
|
2
|
-
<div
|
|
3
|
-
<
|
|
4
|
-
v-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
>
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
</div>
|
|
52
|
-
<v-divider />
|
|
53
|
-
<v-list density="compact">
|
|
54
|
-
<v-list-item
|
|
55
|
-
v-for="option in filterOptions"
|
|
56
|
-
:key="option.value"
|
|
57
|
-
@click="toggleFilter(option)"
|
|
58
|
-
>
|
|
59
|
-
<template #prepend>
|
|
60
|
-
<v-checkbox
|
|
61
|
-
hide-details
|
|
62
|
-
:model-value="categories.includes(option.value)"
|
|
63
|
-
@update:model-value="toggleFilter(option)"
|
|
64
|
-
/>
|
|
65
|
-
</template>
|
|
66
|
-
<v-list-item-title>{{ option.label }}</v-list-item-title>
|
|
67
|
-
</v-list-item>
|
|
68
|
-
</v-list>
|
|
69
|
-
</v-card>
|
|
70
|
-
</v-menu>
|
|
71
|
-
<v-text-field
|
|
72
|
-
ref="textFieldRef"
|
|
73
|
-
:id="`global-search-input-${type}`"
|
|
74
|
-
v-model.trim="search"
|
|
75
|
-
:placeholder="placeholderText"
|
|
76
|
-
single-line
|
|
77
|
-
class="transition-swing flex-grow-1"
|
|
2
|
+
<div>
|
|
3
|
+
<div class="d-flex align-center">
|
|
4
|
+
<v-text-field
|
|
5
|
+
ref="textFieldRef"
|
|
6
|
+
:id="`global-search-input-${type}`"
|
|
7
|
+
v-model.trim="search"
|
|
8
|
+
:placeholder="placeholderText"
|
|
9
|
+
single-line
|
|
10
|
+
class="transition-swing flex-grow-1"
|
|
11
|
+
variant="outlined"
|
|
12
|
+
hide-details
|
|
13
|
+
clearable
|
|
14
|
+
tile
|
|
15
|
+
type="search"
|
|
16
|
+
:loading="rootStore.loading"
|
|
17
|
+
@keyup.enter="navigateToSearch"
|
|
18
|
+
@click:append="navigateToSearch"
|
|
19
|
+
>
|
|
20
|
+
<template v-if="!search" #label>
|
|
21
|
+
<div class="searchLabel">
|
|
22
|
+
{{ placeholderText }}
|
|
23
|
+
</div>
|
|
24
|
+
</template>
|
|
25
|
+
</v-text-field>
|
|
26
|
+
<v-btn
|
|
27
|
+
:rounded="0"
|
|
28
|
+
variant="outlined"
|
|
29
|
+
size="large"
|
|
30
|
+
height="56"
|
|
31
|
+
:aria-label="$t('click-here-to-search')"
|
|
32
|
+
@keyup.enter="navigateToSearch"
|
|
33
|
+
@click="navigateToSearch"
|
|
34
|
+
>
|
|
35
|
+
<v-icon size="large">mdi-magnify</v-icon>
|
|
36
|
+
<v-tooltip activator="parent" location="start">{{
|
|
37
|
+
$t("click-here-to-search")
|
|
38
|
+
}}</v-tooltip>
|
|
39
|
+
</v-btn>
|
|
40
|
+
</div>
|
|
41
|
+
|
|
42
|
+
<!-- Disciplines filter: full-width, available on every breakpoint. -->
|
|
43
|
+
<v-autocomplete
|
|
44
|
+
v-if="disciplineOptions.length"
|
|
45
|
+
:model-value="disciplines"
|
|
46
|
+
:items="disciplineOptions"
|
|
47
|
+
item-title="title"
|
|
48
|
+
item-value="value"
|
|
49
|
+
:label="$t('search.filter-by-discipline')"
|
|
50
|
+
:placeholder="$t('search.disciplines')"
|
|
78
51
|
variant="outlined"
|
|
79
|
-
|
|
80
|
-
clearable
|
|
52
|
+
density="comfortable"
|
|
81
53
|
tile
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
</template>
|
|
92
|
-
</v-text-field>
|
|
93
|
-
<v-btn
|
|
94
|
-
:rounded="0"
|
|
95
|
-
variant="outlined"
|
|
96
|
-
size="large"
|
|
97
|
-
height="56"
|
|
98
|
-
@keyup.enter="navigateToSearch"
|
|
99
|
-
@click="navigateToSearch"
|
|
100
|
-
>
|
|
101
|
-
<v-icon size="large">mdi-magnify</v-icon>
|
|
102
|
-
<v-tooltip activator="parent" location="start">{{
|
|
103
|
-
$t("click-here-to-search")
|
|
104
|
-
}}</v-tooltip>
|
|
105
|
-
</v-btn>
|
|
54
|
+
multiple
|
|
55
|
+
chips
|
|
56
|
+
closable-chips
|
|
57
|
+
clearable
|
|
58
|
+
hide-details
|
|
59
|
+
class="mt-3"
|
|
60
|
+
prepend-inner-icon="mdi-filter-variant"
|
|
61
|
+
@update:model-value="onDisciplinesChange"
|
|
62
|
+
/>
|
|
106
63
|
</div>
|
|
107
64
|
</template>
|
|
108
65
|
|
|
@@ -128,9 +85,7 @@ const { locale, t } = useI18n()
|
|
|
128
85
|
const rootStore = useRootStore()
|
|
129
86
|
const route = useRoute()
|
|
130
87
|
const routeName = computed(() => route.name?.toString() ?? "")
|
|
131
|
-
|
|
132
|
-
const capitalize = (str) => str.charAt(0).toUpperCase() + str.slice(1)
|
|
133
|
-
const emit = defineEmits(["filter-change"])
|
|
88
|
+
const emit = defineEmits(["disciplines-change"])
|
|
134
89
|
|
|
135
90
|
// Rotating placeholder examples (only used for the global "all" search).
|
|
136
91
|
// Examples live in i18n; keys may be absent in some locales — we filter those.
|
|
@@ -192,15 +147,13 @@ const props = defineProps({
|
|
|
192
147
|
type: Boolean,
|
|
193
148
|
default: false,
|
|
194
149
|
},
|
|
195
|
-
|
|
150
|
+
// Selected discipline slugs (global search only).
|
|
151
|
+
disciplines: {
|
|
196
152
|
type: Array,
|
|
197
153
|
default: () => [],
|
|
198
154
|
},
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
default: false,
|
|
202
|
-
},
|
|
203
|
-
filterOrder: {
|
|
155
|
+
// Available discipline options: [{ value, title }].
|
|
156
|
+
disciplineOptions: {
|
|
204
157
|
type: Array,
|
|
205
158
|
default: () => [],
|
|
206
159
|
},
|
|
@@ -219,82 +172,8 @@ onMounted(() => {
|
|
|
219
172
|
})
|
|
220
173
|
})
|
|
221
174
|
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
// Filter options
|
|
226
|
-
const allFilterOptions = computed(() => ({
|
|
227
|
-
people: { value: "people", label: capitalize(t("items.people", 2)) },
|
|
228
|
-
events: { value: "events", label: capitalize(t("items.events", 2)) },
|
|
229
|
-
news: { value: "news", label: capitalize(t("items.news", 2)) },
|
|
230
|
-
publications: {
|
|
231
|
-
value: "publications",
|
|
232
|
-
label: capitalize(t("items.publications", 2)),
|
|
233
|
-
},
|
|
234
|
-
fellowships: {
|
|
235
|
-
value: "fellowships",
|
|
236
|
-
label: capitalize(t("items.fellowships", 2)),
|
|
237
|
-
},
|
|
238
|
-
projects: { value: "projects", label: capitalize(t("items.projects", 2)) },
|
|
239
|
-
}))
|
|
240
|
-
|
|
241
|
-
const filterOptions = computed(() => {
|
|
242
|
-
const map = allFilterOptions.value
|
|
243
|
-
if (props.filterOrder.length > 0) {
|
|
244
|
-
const ordered = props.filterOrder.filter((k) => map[k]).map((k) => map[k])
|
|
245
|
-
const rest = Object.values(map).filter(
|
|
246
|
-
(o) => !props.filterOrder.includes(o.value),
|
|
247
|
-
)
|
|
248
|
-
return [...ordered, ...rest]
|
|
249
|
-
}
|
|
250
|
-
return Object.values(map)
|
|
251
|
-
})
|
|
252
|
-
|
|
253
|
-
const allValues = computed(() => filterOptions.value.map((o) => o.value))
|
|
254
|
-
|
|
255
|
-
const allSelected = computed(
|
|
256
|
-
() =>
|
|
257
|
-
props.categories.length === allValues.value.length &&
|
|
258
|
-
allValues.value.every((v) => props.categories.includes(v)),
|
|
259
|
-
)
|
|
260
|
-
const noneSelected = computed(() => props.categories.length === 0)
|
|
261
|
-
// Show the count badge only when the user has narrowed the selection.
|
|
262
|
-
const hasActiveFilter = computed(
|
|
263
|
-
() => !allSelected.value && !noneSelected.value,
|
|
264
|
-
)
|
|
265
|
-
|
|
266
|
-
const selectAllFilters = () => {
|
|
267
|
-
emit("filter-change", {
|
|
268
|
-
name: "__all__",
|
|
269
|
-
value: true,
|
|
270
|
-
categories: [...allValues.value],
|
|
271
|
-
})
|
|
272
|
-
}
|
|
273
|
-
|
|
274
|
-
const clearFilters = () => {
|
|
275
|
-
emit("filter-change", {
|
|
276
|
-
name: "__none__",
|
|
277
|
-
value: false,
|
|
278
|
-
categories: [],
|
|
279
|
-
})
|
|
280
|
-
}
|
|
281
|
-
|
|
282
|
-
// Toggle filter selection
|
|
283
|
-
const toggleFilter = (option) => {
|
|
284
|
-
const currentCategories = [...props.categories]
|
|
285
|
-
const index = currentCategories.indexOf(option.value)
|
|
286
|
-
|
|
287
|
-
if (index > -1) {
|
|
288
|
-
currentCategories.splice(index, 1)
|
|
289
|
-
} else {
|
|
290
|
-
currentCategories.push(option.value)
|
|
291
|
-
}
|
|
292
|
-
|
|
293
|
-
emit("filter-change", {
|
|
294
|
-
name: option.value,
|
|
295
|
-
value: currentCategories.includes(option.value),
|
|
296
|
-
categories: currentCategories,
|
|
297
|
-
})
|
|
175
|
+
const onDisciplinesChange = (next) => {
|
|
176
|
+
emit("disciplines-change", Array.isArray(next) ? next : [])
|
|
298
177
|
}
|
|
299
178
|
|
|
300
179
|
const textFieldRef = ref(null)
|
|
@@ -334,19 +213,4 @@ const search = computed({
|
|
|
334
213
|
})
|
|
335
214
|
</script>
|
|
336
215
|
|
|
337
|
-
<style scoped>
|
|
338
|
-
.global-search__filter-count {
|
|
339
|
-
display: inline-flex;
|
|
340
|
-
align-items: center;
|
|
341
|
-
justify-content: center;
|
|
342
|
-
min-width: 18px;
|
|
343
|
-
height: 18px;
|
|
344
|
-
padding: 0 5px;
|
|
345
|
-
border-radius: 9px;
|
|
346
|
-
background: #000;
|
|
347
|
-
color: #fff;
|
|
348
|
-
font-size: 0.7rem;
|
|
349
|
-
font-weight: 600;
|
|
350
|
-
line-height: 1;
|
|
351
|
-
}
|
|
352
|
-
</style>
|
|
216
|
+
<style lang="scss" scoped></style>
|
|
@@ -6,53 +6,28 @@
|
|
|
6
6
|
variant="text"
|
|
7
7
|
class="mr-2"
|
|
8
8
|
@click="$emit('toggle', type)"
|
|
9
|
-
:disabled="
|
|
10
|
-
|
|
11
|
-
"
|
|
9
|
+
:disabled="total === 0"
|
|
10
|
+
:aria-expanded="open ? 'true' : 'false'"
|
|
11
|
+
:aria-label="$t(open ? 'show-less' : 'show-more')"
|
|
12
12
|
>
|
|
13
13
|
<v-icon>{{ open ? "mdi-chevron-down" : "mdi-chevron-right" }}</v-icon>
|
|
14
14
|
</v-btn>
|
|
15
15
|
<div
|
|
16
16
|
class="d-flex flex-column cursor-pointer"
|
|
17
|
+
role="button"
|
|
18
|
+
:aria-expanded="open ? 'true' : 'false'"
|
|
17
19
|
@click="$emit('toggle', type)"
|
|
18
20
|
>
|
|
19
|
-
<
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
? 'black'
|
|
24
|
-
: 'text-grey darken-2'
|
|
25
|
-
"
|
|
26
|
-
>
|
|
27
|
-
{{ capitalize($t("items." + props.type, 2)) }}
|
|
28
|
-
</h3>
|
|
29
|
-
<div class="text-overline mb-3">
|
|
21
|
+
<h4 class="mt-2 mb-0" :class="total > 0 ? 'black' : 'text-grey darken-2'">
|
|
22
|
+
{{ label }}
|
|
23
|
+
</h4>
|
|
24
|
+
<div class="text-overline mb-2">
|
|
30
25
|
{{
|
|
31
|
-
|
|
32
|
-
?
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
$t(
|
|
37
|
-
"items." + props.type,
|
|
38
|
-
$rootStore.results[type] &&
|
|
39
|
-
$rootStore.results[type].total,
|
|
40
|
-
),
|
|
41
|
-
],
|
|
42
|
-
$rootStore.results[type] && $rootStore.results[type].total,
|
|
43
|
-
)
|
|
44
|
-
: $t(
|
|
45
|
-
"list.0-items-found",
|
|
46
|
-
[
|
|
47
|
-
$rootStore.results[type] && $rootStore.results[type].total,
|
|
48
|
-
$t(
|
|
49
|
-
"items." + props.type,
|
|
50
|
-
$rootStore.results[type] &&
|
|
51
|
-
$rootStore.results[type].total,
|
|
52
|
-
),
|
|
53
|
-
],
|
|
54
|
-
$rootStore.results[type] && $rootStore.results[type].total,
|
|
55
|
-
)
|
|
26
|
+
$t(
|
|
27
|
+
feminine ? "list.0-items-found-f" : "list.0-items-found",
|
|
28
|
+
[total, $t("items." + props.type, total)],
|
|
29
|
+
total,
|
|
30
|
+
)
|
|
56
31
|
}}
|
|
57
32
|
</div>
|
|
58
33
|
</div>
|
|
@@ -62,21 +37,15 @@
|
|
|
62
37
|
variant="text"
|
|
63
38
|
size="small"
|
|
64
39
|
rounded="0"
|
|
65
|
-
v-if="
|
|
40
|
+
v-if="total > 3"
|
|
66
41
|
:to="
|
|
67
42
|
localePath({
|
|
68
|
-
path:
|
|
43
|
+
path: viewMore,
|
|
69
44
|
query: { search: $rootStore.search },
|
|
70
45
|
})
|
|
71
46
|
"
|
|
72
47
|
>
|
|
73
|
-
{{
|
|
74
|
-
$t(
|
|
75
|
-
"list.pls-x-more",
|
|
76
|
-
[$rootStore.results[type] && $rootStore.results[type].total - 3],
|
|
77
|
-
$rootStore.results[type] && $rootStore.results[type].total - 3,
|
|
78
|
-
)
|
|
79
|
-
}}
|
|
48
|
+
{{ $t('list.pls-x-more', [total - 3], total - 3) }}
|
|
80
49
|
</v-btn>
|
|
81
50
|
</div>
|
|
82
51
|
<slot />
|
|
@@ -84,9 +53,10 @@
|
|
|
84
53
|
<v-divider></v-divider>
|
|
85
54
|
</template>
|
|
86
55
|
<script setup>
|
|
87
|
-
import { useNuxtApp, useLocalePath } from "#imports"
|
|
56
|
+
import { useNuxtApp, useLocalePath, useI18n, computed } from "#imports"
|
|
88
57
|
import { useDisplay } from "vuetify"
|
|
89
58
|
const localePath = useLocalePath()
|
|
59
|
+
const { t } = useI18n()
|
|
90
60
|
|
|
91
61
|
const { mdAndUp } = useDisplay()
|
|
92
62
|
|
|
@@ -112,5 +82,26 @@ const props = defineProps({
|
|
|
112
82
|
required: false,
|
|
113
83
|
default: true,
|
|
114
84
|
},
|
|
85
|
+
// Optional i18n key override for the heading (people groups live outside the
|
|
86
|
+
// `items.*` namespace). Falls back to `items.<type>` (pluralised).
|
|
87
|
+
labelKey: {
|
|
88
|
+
type: String,
|
|
89
|
+
required: false,
|
|
90
|
+
default: "",
|
|
91
|
+
},
|
|
92
|
+
// Path the "view more" link routes to (the sub-category landing page).
|
|
93
|
+
viewMore: {
|
|
94
|
+
type: String,
|
|
95
|
+
required: false,
|
|
96
|
+
default: "",
|
|
97
|
+
},
|
|
115
98
|
})
|
|
99
|
+
|
|
100
|
+
const total = computed(() => $rootStore.results[props.type]?.total ?? 0)
|
|
101
|
+
|
|
102
|
+
const label = computed(() =>
|
|
103
|
+
props.labelKey
|
|
104
|
+
? t(props.labelKey)
|
|
105
|
+
: capitalize(t("items." + props.type, 2)),
|
|
106
|
+
)
|
|
116
107
|
</script>
|
|
@@ -2,18 +2,17 @@
|
|
|
2
2
|
<ListMoleculesGlobalSearchInput
|
|
3
3
|
type="all"
|
|
4
4
|
:placeholder="$t('search')"
|
|
5
|
-
:
|
|
6
|
-
:
|
|
7
|
-
filter
|
|
5
|
+
:disciplines="selectedDisciplines"
|
|
6
|
+
:discipline-options="disciplineOptions"
|
|
8
7
|
autofocus
|
|
9
|
-
@
|
|
8
|
+
@disciplines-change="handleDisciplinesChange"
|
|
10
9
|
class="mb-6"
|
|
11
10
|
/>
|
|
12
11
|
<div v-if="searchTerm.length === 0" class="search-empty">
|
|
13
12
|
<ListAtomsLogoPlaceholder idle />
|
|
14
13
|
</div>
|
|
15
14
|
<template v-else>
|
|
16
|
-
<div v-if="pending" class="search-pending">
|
|
15
|
+
<div v-if="pending" class="search-pending" aria-busy="true">
|
|
17
16
|
<div class="search-pending__inner">
|
|
18
17
|
<ListAtomsLogoPlaceholder class="loader-logo loader-logo--active" />
|
|
19
18
|
<div class="search-pending__dots">
|
|
@@ -24,29 +23,39 @@
|
|
|
24
23
|
</div>
|
|
25
24
|
</div>
|
|
26
25
|
<template v-else>
|
|
27
|
-
<
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
26
|
+
<p class="visually-hidden" aria-live="polite">
|
|
27
|
+
{{ resultsAnnouncement }}
|
|
28
|
+
</p>
|
|
29
|
+
<div v-if="!hasAnyResult" class="search-no-results">
|
|
30
|
+
{{ $t("search.no-results", [searchTerm]) }}
|
|
31
|
+
</div>
|
|
32
|
+
<section
|
|
33
|
+
v-for="group in visibleGroups"
|
|
34
|
+
:key="group.key"
|
|
35
|
+
class="search-group"
|
|
36
36
|
>
|
|
37
|
-
<
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
37
|
+
<h3 class="search-group__title">{{ $t(group.labelKey) }}</h3>
|
|
38
|
+
<ListMoleculesResultsContainer
|
|
39
|
+
v-for="sub in group.visibleSubCategories"
|
|
40
|
+
:key="sub.type"
|
|
41
|
+
:type="sub.type"
|
|
42
|
+
:label-key="sub.labelKey"
|
|
43
|
+
:view-more="sub.viewMore"
|
|
44
|
+
:feminine="isFeminine(sub.type)"
|
|
45
|
+
:open="open[sub.type] ?? false"
|
|
46
|
+
@toggle="(t) => (open[t] = !open[t])"
|
|
47
|
+
>
|
|
48
|
+
<v-expand-transition class="results-container">
|
|
49
|
+
<div v-show="open[sub.type]">
|
|
50
|
+
<ListAtomsResultsList
|
|
51
|
+
:type="sub.type"
|
|
52
|
+
:parent-type="sub.parentType"
|
|
53
|
+
:path-prefix="sub.pathPrefix"
|
|
54
|
+
/>
|
|
55
|
+
</div>
|
|
56
|
+
</v-expand-transition>
|
|
57
|
+
</ListMoleculesResultsContainer>
|
|
58
|
+
</section>
|
|
50
59
|
</template>
|
|
51
60
|
</template>
|
|
52
61
|
</template>
|
|
@@ -55,7 +64,6 @@
|
|
|
55
64
|
import {
|
|
56
65
|
useNuxtApp,
|
|
57
66
|
useI18n,
|
|
58
|
-
useAppConfig,
|
|
59
67
|
useRoute,
|
|
60
68
|
useRouter,
|
|
61
69
|
reactive,
|
|
@@ -64,85 +72,67 @@ import {
|
|
|
64
72
|
watch,
|
|
65
73
|
} from "#imports"
|
|
66
74
|
import { useRootStore } from "../../../stores/root"
|
|
75
|
+
import {
|
|
76
|
+
SEARCH_GROUPS,
|
|
77
|
+
SEARCH_SUBCATEGORY_TYPES,
|
|
78
|
+
FEMININE_SUBCATEGORIES,
|
|
79
|
+
} from "../../../composables/useSearchGroups"
|
|
67
80
|
import SEARCH from "@paris-ias/trees/dist/graphql/client/misc/query.search.all.gql"
|
|
68
81
|
|
|
69
82
|
const { $rootStore } = useNuxtApp()
|
|
70
83
|
const rootStore = useRootStore()
|
|
71
|
-
const
|
|
72
|
-
const { locale } = useI18n()
|
|
84
|
+
const { locale, t, messages } = useI18n()
|
|
73
85
|
const route = useRoute()
|
|
74
86
|
const router = useRouter()
|
|
75
87
|
if (route.query.search) {
|
|
76
88
|
rootStore.search = String(route.query.search)
|
|
77
89
|
}
|
|
78
90
|
|
|
91
|
+
// Per-sub-category expanded state.
|
|
79
92
|
const open = reactive(
|
|
80
|
-
|
|
93
|
+
SEARCH_SUBCATEGORY_TYPES.reduce((acc, type) => {
|
|
81
94
|
acc[type] = false
|
|
82
95
|
return acc
|
|
83
96
|
}, {}),
|
|
84
97
|
)
|
|
85
98
|
|
|
86
|
-
//
|
|
87
|
-
//
|
|
88
|
-
//
|
|
89
|
-
const
|
|
90
|
-
|
|
99
|
+
// --- Disciplines filter ----------------------------------------------------
|
|
100
|
+
// Options reuse the same source the per-module filters use: the global
|
|
101
|
+
// `list.filters.disciplines` i18n namespace (keys minus the `label` entry).
|
|
102
|
+
const disciplineOptions = computed(() => {
|
|
103
|
+
const dict = messages.value?.[locale.value]?.list?.filters?.disciplines || {}
|
|
104
|
+
return Object.keys(dict)
|
|
105
|
+
.filter((key) => key !== "label")
|
|
106
|
+
.map((key) => ({ value: key, title: t("list.filters.disciplines." + key) }))
|
|
107
|
+
.sort((a, b) => a.title.localeCompare(b.title))
|
|
108
|
+
})
|
|
109
|
+
|
|
110
|
+
const parseDisciplinesQuery = (raw) => {
|
|
111
|
+
if (!raw) return []
|
|
91
112
|
const value = Array.isArray(raw) ? raw[0] : raw
|
|
92
|
-
if (typeof value !== "string" || value.trim() === "") return
|
|
93
|
-
const known = new Set(
|
|
94
|
-
|
|
113
|
+
if (typeof value !== "string" || value.trim() === "") return []
|
|
114
|
+
const known = new Set(disciplineOptions.value.map((o) => o.value))
|
|
115
|
+
return value
|
|
95
116
|
.split(",")
|
|
96
117
|
.map((s) => s.trim())
|
|
97
118
|
.filter((s) => known.has(s))
|
|
98
|
-
return parsed.length ? parsed : null
|
|
99
119
|
}
|
|
100
120
|
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
const selectedCategories = reactive(initialCategories)
|
|
121
|
+
rootStore.searchDisciplines = parseDisciplinesQuery(route.query.disciplines)
|
|
122
|
+
const selectedDisciplines = computed(() => rootStore.searchDisciplines)
|
|
104
123
|
|
|
105
|
-
const
|
|
106
|
-
|
|
107
|
-
selectedCategories.length === appConfig.list.modules.length &&
|
|
108
|
-
appConfig.list.modules.every((t) => selectedCategories.includes(t))
|
|
124
|
+
const handleDisciplinesChange = (next) => {
|
|
125
|
+
rootStore.searchDisciplines = [...next]
|
|
109
126
|
const nextQuery = { ...route.query }
|
|
110
|
-
if (
|
|
111
|
-
|
|
127
|
+
if (next.length) {
|
|
128
|
+
nextQuery.disciplines = [...next].sort().join(",")
|
|
112
129
|
} else {
|
|
113
|
-
|
|
114
|
-
// regardless of click order in the dropdown.
|
|
115
|
-
nextQuery.type = [...selectedCategories].sort().join(",")
|
|
130
|
+
delete nextQuery.disciplines
|
|
116
131
|
}
|
|
117
132
|
router.replace({ query: nextQuery })
|
|
118
133
|
}
|
|
119
134
|
|
|
120
|
-
|
|
121
|
-
selectedCategories.splice(
|
|
122
|
-
0,
|
|
123
|
-
selectedCategories.length,
|
|
124
|
-
...filterData.categories,
|
|
125
|
-
)
|
|
126
|
-
syncCategoriesToUrl()
|
|
127
|
-
}
|
|
128
|
-
|
|
129
|
-
const sortedModules = computed(() => {
|
|
130
|
-
return appConfig.list.modules.slice().sort((a, b) => {
|
|
131
|
-
const aMaxScore = Math.max(
|
|
132
|
-
...($rootStore.results[a]?.items || []).map((i) => i.score ?? 0),
|
|
133
|
-
0,
|
|
134
|
-
)
|
|
135
|
-
const bMaxScore = Math.max(
|
|
136
|
-
...($rootStore.results[b]?.items || []).map((i) => i.score ?? 0),
|
|
137
|
-
0,
|
|
138
|
-
)
|
|
139
|
-
return bMaxScore - aMaxScore
|
|
140
|
-
})
|
|
141
|
-
})
|
|
142
|
-
const filteredSortedModules = computed(() => {
|
|
143
|
-
return sortedModules.value.filter((type) => selectedCategories.includes(type))
|
|
144
|
-
})
|
|
145
|
-
|
|
135
|
+
// --- Search query ----------------------------------------------------------
|
|
146
136
|
const searchTerm = computed(() => $rootStore.search || "")
|
|
147
137
|
const currentLocale = computed(() => locale.value)
|
|
148
138
|
|
|
@@ -153,6 +143,7 @@ const { data, pending, error } = useAsyncQuery(
|
|
|
153
143
|
search: searchTerm.value,
|
|
154
144
|
appId: "iea",
|
|
155
145
|
lang: currentLocale.value,
|
|
146
|
+
disciplines: selectedDisciplines.value,
|
|
156
147
|
})),
|
|
157
148
|
},
|
|
158
149
|
{ server: false },
|
|
@@ -161,7 +152,7 @@ const { data, pending, error } = useAsyncQuery(
|
|
|
161
152
|
watch(data, (newData) => {
|
|
162
153
|
if (!newData) return
|
|
163
154
|
$rootStore.applyListResult("all", newData)
|
|
164
|
-
|
|
155
|
+
SEARCH_SUBCATEGORY_TYPES.forEach((type) => {
|
|
165
156
|
if (newData.search?.[type]?.total > 0) {
|
|
166
157
|
open[type] = true
|
|
167
158
|
}
|
|
@@ -171,6 +162,44 @@ watch(data, (newData) => {
|
|
|
171
162
|
watch(error, (err) => {
|
|
172
163
|
if (err) console.error("GraphQL query error:", err)
|
|
173
164
|
})
|
|
165
|
+
|
|
166
|
+
// --- Result dispatch -------------------------------------------------------
|
|
167
|
+
const totalFor = (type) => $rootStore.results[type]?.total ?? 0
|
|
168
|
+
const maxScoreFor = (type) =>
|
|
169
|
+
Math.max(...($rootStore.results[type]?.items || []).map((i) => i.score ?? 0), 0)
|
|
170
|
+
|
|
171
|
+
const isFeminine = (type) => FEMININE_SUBCATEGORIES.has(type)
|
|
172
|
+
|
|
173
|
+
// Groups, with empty sub-categories hidden and groups ordered by their best
|
|
174
|
+
// matching score so the most relevant group surfaces first.
|
|
175
|
+
const visibleGroups = computed(() => {
|
|
176
|
+
return SEARCH_GROUPS.map((group) => {
|
|
177
|
+
const visibleSubCategories = group.subCategories.filter(
|
|
178
|
+
(sub) => totalFor(sub.type) > 0,
|
|
179
|
+
)
|
|
180
|
+
const groupScore = Math.max(
|
|
181
|
+
...group.subCategories.map((sub) => maxScoreFor(sub.type)),
|
|
182
|
+
0,
|
|
183
|
+
)
|
|
184
|
+
return { ...group, visibleSubCategories, groupScore }
|
|
185
|
+
})
|
|
186
|
+
.filter((group) => group.visibleSubCategories.length > 0)
|
|
187
|
+
.sort((a, b) => b.groupScore - a.groupScore)
|
|
188
|
+
})
|
|
189
|
+
|
|
190
|
+
const hasAnyResult = computed(() =>
|
|
191
|
+
SEARCH_SUBCATEGORY_TYPES.some((type) => totalFor(type) > 0),
|
|
192
|
+
)
|
|
193
|
+
|
|
194
|
+
const resultsAnnouncement = computed(() => {
|
|
195
|
+
const total = SEARCH_SUBCATEGORY_TYPES.reduce(
|
|
196
|
+
(sum, type) => sum + totalFor(type),
|
|
197
|
+
0,
|
|
198
|
+
)
|
|
199
|
+
return hasAnyResult.value
|
|
200
|
+
? t("list.0-items-found", [total, t("search")], total)
|
|
201
|
+
: t("search.no-results-plain")
|
|
202
|
+
})
|
|
174
203
|
</script>
|
|
175
204
|
|
|
176
205
|
<style scoped>
|
|
@@ -182,6 +211,50 @@ watch(error, (err) => {
|
|
|
182
211
|
gap: 8px;
|
|
183
212
|
}
|
|
184
213
|
|
|
214
|
+
.search-group {
|
|
215
|
+
margin-bottom: 24px;
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
.search-group__title {
|
|
219
|
+
margin: 0 0 4px;
|
|
220
|
+
font-size: 1.35rem;
|
|
221
|
+
font-weight: 700;
|
|
222
|
+
letter-spacing: 0.01em;
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
/* Indent the sub-category containers under their group heading. Tightened on
|
|
226
|
+
mobile so nested content does not crowd narrow screens. */
|
|
227
|
+
.search-group :deep(.results-container) {
|
|
228
|
+
margin-left: 16px;
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
@media (max-width: 600px) {
|
|
232
|
+
.search-group__title {
|
|
233
|
+
font-size: 1.15rem;
|
|
234
|
+
}
|
|
235
|
+
.search-group :deep(.results-container) {
|
|
236
|
+
margin-left: 8px;
|
|
237
|
+
}
|
|
238
|
+
}
|
|
239
|
+
.search-no-results {
|
|
240
|
+
padding: 32px 0;
|
|
241
|
+
text-align: center;
|
|
242
|
+
color: rgba(0, 0, 0, 0.6);
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
/* Screen-reader-only live region for result-count announcements. */
|
|
246
|
+
.visually-hidden {
|
|
247
|
+
position: absolute;
|
|
248
|
+
width: 1px;
|
|
249
|
+
height: 1px;
|
|
250
|
+
padding: 0;
|
|
251
|
+
margin: -1px;
|
|
252
|
+
overflow: hidden;
|
|
253
|
+
clip: rect(0, 0, 0, 0);
|
|
254
|
+
white-space: nowrap;
|
|
255
|
+
border: 0;
|
|
256
|
+
}
|
|
257
|
+
|
|
185
258
|
.search-empty {
|
|
186
259
|
min-height: 100vh;
|
|
187
260
|
display: flex;
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Global-search group model.
|
|
3
|
+
*
|
|
4
|
+
* The global search resolver dispatches results into modifier-based
|
|
5
|
+
* sub-categories (see query.search.all.gql / the Apex search resolver). The UI
|
|
6
|
+
* presents them under their renamed top-level groups, e.g. People details into
|
|
7
|
+
* team / sab / board / ethics / fellows, while Projects becomes "Collective
|
|
8
|
+
* Intelligence" and Publications becomes "Resources".
|
|
9
|
+
*
|
|
10
|
+
* For each sub-category we keep:
|
|
11
|
+
* - `type`: the result key in `rootStore.results` (also the i18n
|
|
12
|
+
* `items.<type>` label key for the count line)
|
|
13
|
+
* - `parentType`: the underlying list module, used to resolve the item card
|
|
14
|
+
* component and reuse module-level behaviour
|
|
15
|
+
* - `labelKey`: optional i18n override when the sub-category label does not
|
|
16
|
+
* live under `items.<type>` (people groups live under
|
|
17
|
+
* `list.filters.people.groups.*`)
|
|
18
|
+
* - `pathPrefix`: route name for an individual item's detail page
|
|
19
|
+
* - `viewMore`: path for the "view more" link (the sub-category landing page)
|
|
20
|
+
*/
|
|
21
|
+
export interface SearchSubCategory {
|
|
22
|
+
type: string;
|
|
23
|
+
parentType: string;
|
|
24
|
+
labelKey?: string;
|
|
25
|
+
pathPrefix: string;
|
|
26
|
+
viewMore: string;
|
|
27
|
+
}
|
|
28
|
+
export interface SearchGroup {
|
|
29
|
+
/** i18n key for the group heading (under the list namespace). */
|
|
30
|
+
labelKey: string;
|
|
31
|
+
/** Stable key for v-for / open-state tracking. */
|
|
32
|
+
key: string;
|
|
33
|
+
subCategories: SearchSubCategory[];
|
|
34
|
+
}
|
|
35
|
+
export declare const SEARCH_GROUPS: SearchGroup[];
|
|
36
|
+
/** Every sub-category type, flattened — handy for store iteration. */
|
|
37
|
+
export declare const SEARCH_SUBCATEGORY_TYPES: string[];
|
|
38
|
+
/** Feminine grammatical gender per sub-category (drives the count i18n form). */
|
|
39
|
+
export declare const FEMININE_SUBCATEGORIES: Set<string>;
|
|
@@ -0,0 +1,128 @@
|
|
|
1
|
+
const PEOPLE_DETAIL = "fellows-slug";
|
|
2
|
+
const ORGANISATION = "/about/organisation";
|
|
3
|
+
export const SEARCH_GROUPS = [
|
|
4
|
+
{
|
|
5
|
+
key: "people",
|
|
6
|
+
labelKey: "search.groups.people",
|
|
7
|
+
subCategories: [
|
|
8
|
+
// People share one detail route (/fellows/[slug]); the four governance
|
|
9
|
+
// groups all land on the organisation page, fellows on the fellows page.
|
|
10
|
+
{
|
|
11
|
+
type: "team",
|
|
12
|
+
parentType: "people",
|
|
13
|
+
labelKey: "list.filters.people.groups.team",
|
|
14
|
+
pathPrefix: PEOPLE_DETAIL,
|
|
15
|
+
viewMore: ORGANISATION
|
|
16
|
+
},
|
|
17
|
+
{
|
|
18
|
+
type: "sab",
|
|
19
|
+
parentType: "people",
|
|
20
|
+
labelKey: "list.filters.people.groups.sab",
|
|
21
|
+
pathPrefix: PEOPLE_DETAIL,
|
|
22
|
+
viewMore: ORGANISATION
|
|
23
|
+
},
|
|
24
|
+
{
|
|
25
|
+
type: "board",
|
|
26
|
+
parentType: "people",
|
|
27
|
+
labelKey: "list.filters.people.groups.board",
|
|
28
|
+
pathPrefix: PEOPLE_DETAIL,
|
|
29
|
+
viewMore: ORGANISATION
|
|
30
|
+
},
|
|
31
|
+
{
|
|
32
|
+
type: "ethics",
|
|
33
|
+
parentType: "people",
|
|
34
|
+
labelKey: "list.filters.people.groups.ethics",
|
|
35
|
+
pathPrefix: PEOPLE_DETAIL,
|
|
36
|
+
viewMore: ORGANISATION
|
|
37
|
+
},
|
|
38
|
+
{
|
|
39
|
+
type: "fellows",
|
|
40
|
+
parentType: "people",
|
|
41
|
+
labelKey: "list.filters.people.groups.fellows",
|
|
42
|
+
pathPrefix: PEOPLE_DETAIL,
|
|
43
|
+
viewMore: "/fellows"
|
|
44
|
+
}
|
|
45
|
+
]
|
|
46
|
+
},
|
|
47
|
+
{
|
|
48
|
+
key: "collective-intelligence",
|
|
49
|
+
labelKey: "search.groups.collective-intelligence",
|
|
50
|
+
subCategories: [
|
|
51
|
+
{
|
|
52
|
+
type: "initiatives",
|
|
53
|
+
parentType: "projects",
|
|
54
|
+
pathPrefix: "programs-collective-intelligence-initiatives-slug",
|
|
55
|
+
viewMore: "/programs/collective-intelligence/initiatives"
|
|
56
|
+
},
|
|
57
|
+
{
|
|
58
|
+
type: "projects",
|
|
59
|
+
parentType: "projects",
|
|
60
|
+
pathPrefix: "programs-collective-intelligence-projects-slug",
|
|
61
|
+
viewMore: "/programs/collective-intelligence/projects"
|
|
62
|
+
},
|
|
63
|
+
{
|
|
64
|
+
type: "tools",
|
|
65
|
+
parentType: "projects",
|
|
66
|
+
pathPrefix: "programs-collective-intelligence-tools-slug",
|
|
67
|
+
viewMore: "/programs/collective-intelligence/tools"
|
|
68
|
+
}
|
|
69
|
+
]
|
|
70
|
+
},
|
|
71
|
+
{
|
|
72
|
+
key: "events",
|
|
73
|
+
labelKey: "search.groups.events",
|
|
74
|
+
subCategories: [
|
|
75
|
+
{
|
|
76
|
+
type: "events",
|
|
77
|
+
parentType: "events",
|
|
78
|
+
pathPrefix: "events-slug",
|
|
79
|
+
viewMore: "/events"
|
|
80
|
+
}
|
|
81
|
+
]
|
|
82
|
+
},
|
|
83
|
+
{
|
|
84
|
+
key: "fellowships",
|
|
85
|
+
labelKey: "search.groups.fellowships",
|
|
86
|
+
subCategories: [
|
|
87
|
+
{
|
|
88
|
+
type: "fellowships",
|
|
89
|
+
parentType: "fellowships",
|
|
90
|
+
pathPrefix: "programs-fellowships-slug",
|
|
91
|
+
viewMore: "/programs/fellowships"
|
|
92
|
+
}
|
|
93
|
+
]
|
|
94
|
+
},
|
|
95
|
+
{
|
|
96
|
+
key: "resources",
|
|
97
|
+
labelKey: "search.groups.resources",
|
|
98
|
+
subCategories: [
|
|
99
|
+
{
|
|
100
|
+
type: "media",
|
|
101
|
+
parentType: "publications",
|
|
102
|
+
pathPrefix: "resources-media-slug",
|
|
103
|
+
viewMore: "/resources/media"
|
|
104
|
+
},
|
|
105
|
+
{
|
|
106
|
+
type: "publications",
|
|
107
|
+
parentType: "publications",
|
|
108
|
+
pathPrefix: "resources-publications-slug",
|
|
109
|
+
viewMore: "/resources/publications"
|
|
110
|
+
},
|
|
111
|
+
{
|
|
112
|
+
type: "news",
|
|
113
|
+
parentType: "publications",
|
|
114
|
+
pathPrefix: "resources-news-slug",
|
|
115
|
+
viewMore: "/resources/news"
|
|
116
|
+
}
|
|
117
|
+
]
|
|
118
|
+
}
|
|
119
|
+
];
|
|
120
|
+
export const SEARCH_SUBCATEGORY_TYPES = SEARCH_GROUPS.flatMap(
|
|
121
|
+
(g) => g.subCategories.map((s) => s.type)
|
|
122
|
+
);
|
|
123
|
+
export const FEMININE_SUBCATEGORIES = /* @__PURE__ */ new Set([
|
|
124
|
+
"team",
|
|
125
|
+
"media",
|
|
126
|
+
"news",
|
|
127
|
+
"publications"
|
|
128
|
+
]);
|
|
@@ -11,6 +11,14 @@ interface SearchResults {
|
|
|
11
11
|
files: Record<string, unknown>;
|
|
12
12
|
mailing: Record<string, unknown>;
|
|
13
13
|
tags: Record<string, unknown>;
|
|
14
|
+
team: Record<string, unknown>;
|
|
15
|
+
sab: Record<string, unknown>;
|
|
16
|
+
board: Record<string, unknown>;
|
|
17
|
+
ethics: Record<string, unknown>;
|
|
18
|
+
fellows: Record<string, unknown>;
|
|
19
|
+
initiatives: Record<string, unknown>;
|
|
20
|
+
tools: Record<string, unknown>;
|
|
21
|
+
media: Record<string, unknown>;
|
|
14
22
|
}
|
|
15
23
|
interface RootStoreState {
|
|
16
24
|
scrolled: boolean;
|
|
@@ -20,6 +28,7 @@ interface RootStoreState {
|
|
|
20
28
|
page: number;
|
|
21
29
|
numberOfPages: number;
|
|
22
30
|
search: string;
|
|
31
|
+
searchDisciplines: string[];
|
|
23
32
|
results: SearchResults;
|
|
24
33
|
}
|
|
25
34
|
export declare const useRootStore: import("pinia").StoreDefinition<"rootStore", RootStoreState, {}, {
|
|
@@ -9,6 +9,7 @@ export const useRootStore = defineStore("rootStore", {
|
|
|
9
9
|
page: 1,
|
|
10
10
|
numberOfPages: 0,
|
|
11
11
|
search: "",
|
|
12
|
+
searchDisciplines: [],
|
|
12
13
|
results: {
|
|
13
14
|
events: {},
|
|
14
15
|
news: {},
|
|
@@ -21,7 +22,16 @@ export const useRootStore = defineStore("rootStore", {
|
|
|
21
22
|
disciplines: {},
|
|
22
23
|
files: {},
|
|
23
24
|
mailing: {},
|
|
24
|
-
tags: {}
|
|
25
|
+
tags: {},
|
|
26
|
+
// Global-search sub-categories
|
|
27
|
+
team: {},
|
|
28
|
+
sab: {},
|
|
29
|
+
board: {},
|
|
30
|
+
ethics: {},
|
|
31
|
+
fellows: {},
|
|
32
|
+
initiatives: {},
|
|
33
|
+
tools: {},
|
|
34
|
+
media: {}
|
|
25
35
|
}
|
|
26
36
|
}),
|
|
27
37
|
actions: {
|
|
@@ -23,9 +23,12 @@
|
|
|
23
23
|
"affiliations": "affiliation | affiliation | affiliations",
|
|
24
24
|
"all": "all | all | all",
|
|
25
25
|
"app": "application | application | applications",
|
|
26
|
+
"board": "board member | board member | board members",
|
|
26
27
|
"disciplines": "discipline | discipline | disciplines",
|
|
28
|
+
"ethics": "committee member | committee member | committee members",
|
|
27
29
|
"events": "event | event | events",
|
|
28
30
|
"fellow": "fellow | fellow | fellows",
|
|
31
|
+
"fellows": "fellow | fellow | fellows",
|
|
29
32
|
"fellowships": "fellowship | fellowship | fellowships",
|
|
30
33
|
"file": "document | document | documents",
|
|
31
34
|
"initiatives": "initiative | initiative | initiatives",
|
|
@@ -33,6 +36,8 @@
|
|
|
33
36
|
"media": "media | media | media",
|
|
34
37
|
"news": "news | news | news",
|
|
35
38
|
"people": "fellow | fellow | fellows",
|
|
39
|
+
"sab": "SAB member | SAB member | SAB members",
|
|
40
|
+
"team": "team member | team member | team members",
|
|
36
41
|
"tools": "tool | tool | tools",
|
|
37
42
|
"projects": "project | project | projects",
|
|
38
43
|
"publications": "publication | publication | publications",
|
|
@@ -471,5 +476,15 @@
|
|
|
471
476
|
"search.example.1": "Hannah Arendt",
|
|
472
477
|
"search.example.2": "climate policy",
|
|
473
478
|
"search.example.3": "fellowships 2024",
|
|
474
|
-
"search.example.4": "social science"
|
|
479
|
+
"search.example.4": "social science",
|
|
480
|
+
"search.groups.people": "People",
|
|
481
|
+
"search.groups.collective-intelligence": "Collective Intelligence",
|
|
482
|
+
"search.groups.events": "Events",
|
|
483
|
+
"search.groups.fellowships": "Fellowship",
|
|
484
|
+
"search.groups.resources": "Resources",
|
|
485
|
+
"search.filter-by-discipline": "Filter by discipline",
|
|
486
|
+
"search.disciplines": "Disciplines",
|
|
487
|
+
"search.clear-disciplines": "Clear disciplines",
|
|
488
|
+
"search.no-results": "No results found for \"{0}\".",
|
|
489
|
+
"search.no-results-plain": "No results found."
|
|
475
490
|
}
|
|
@@ -23,9 +23,12 @@
|
|
|
23
23
|
"affiliations": "affiliation | affiliation | affiliations",
|
|
24
24
|
"all": "Tout",
|
|
25
25
|
"app": "application | application | applications",
|
|
26
|
+
"board": "membre du conseil | membre du conseil | membres du conseil",
|
|
26
27
|
"disciplines": "discipline | discipline | disciplines",
|
|
28
|
+
"ethics": "membre du comité | membre du comité | membres du comité",
|
|
27
29
|
"events": "événement |événement | événements",
|
|
28
30
|
"fellow": "résident | résident | résidents",
|
|
31
|
+
"fellows": "résident | résident | résidents",
|
|
29
32
|
"fellowships": "programme d'accueil | programme d'accueil | programmes d'accueil",
|
|
30
33
|
"file": "document | document | documents",
|
|
31
34
|
"initiatives": "initiative | initiative | initiatives",
|
|
@@ -33,6 +36,8 @@
|
|
|
33
36
|
"media": "média | média | médias",
|
|
34
37
|
"news": "actualité | actualités | actualités",
|
|
35
38
|
"people": "résident | résident | résidents",
|
|
39
|
+
"sab": "membre du conseil scientifique | membre du conseil scientifique | membres du conseil scientifique",
|
|
40
|
+
"team": "membre de l'équipe | membre de l'équipe | membres de l'équipe",
|
|
36
41
|
"tools": "outil | outil | outils",
|
|
37
42
|
"projects": "projet | projet | projets",
|
|
38
43
|
"publications": "publication | publications | publications",
|
|
@@ -462,5 +467,15 @@
|
|
|
462
467
|
"search.example.1": "Hannah Arendt",
|
|
463
468
|
"search.example.2": "politique climatique",
|
|
464
469
|
"search.example.3": "résidences 2024",
|
|
465
|
-
"search.example.4": "sciences sociales"
|
|
470
|
+
"search.example.4": "sciences sociales",
|
|
471
|
+
"search.groups.people": "Personnes",
|
|
472
|
+
"search.groups.collective-intelligence": "Intelligence collective",
|
|
473
|
+
"search.groups.events": "Événements",
|
|
474
|
+
"search.groups.fellowships": "Résidence",
|
|
475
|
+
"search.groups.resources": "Ressources",
|
|
476
|
+
"search.filter-by-discipline": "Filtrer par discipline",
|
|
477
|
+
"search.disciplines": "Disciplines",
|
|
478
|
+
"search.clear-disciplines": "Effacer les disciplines",
|
|
479
|
+
"search.no-results": "Aucun résultat pour « {0} ».",
|
|
480
|
+
"search.no-results-plain": "Aucun résultat."
|
|
466
481
|
}
|
package/package.json
CHANGED
|
@@ -1,14 +1,14 @@
|
|
|
1
1
|
{
|
|
2
2
|
"license": "AGPL-3.0-only",
|
|
3
3
|
"main": "./dist/module.mjs",
|
|
4
|
-
"version": "1.
|
|
4
|
+
"version": "1.2.0",
|
|
5
5
|
"name": "@paris-ias/list",
|
|
6
6
|
"repository": {
|
|
7
7
|
"url": "git+https://github.com/IEA-Paris/list.git",
|
|
8
8
|
"type": "git"
|
|
9
9
|
},
|
|
10
10
|
"dependencies": {
|
|
11
|
-
"@paris-ias/trees": "^2.2.
|
|
11
|
+
"@paris-ias/trees": "^2.2.15"
|
|
12
12
|
},
|
|
13
13
|
"description": "Paris IAS List Module",
|
|
14
14
|
"peerDependencies": {
|