@paris-ias/list 1.0.185 → 1.0.187

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 CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@paris-ias/list",
3
3
  "configKey": "list",
4
- "version": "1.0.185",
4
+ "version": "1.0.187",
5
5
  "builder": {
6
6
  "@nuxt/module-builder": "0.8.4",
7
7
  "unbuild": "2.0.0"
@@ -6,19 +6,24 @@
6
6
  :loading
7
7
  :date-start="item.start"
8
8
  :date-stop="item.stop"
9
- class="pr-4 mt-md-2"
9
+ class="pr-4"
10
10
  />
11
11
  </v-col>
12
- <v-col v-if="mdAndUp" cols="1">
13
- <MiscAtomsImageContainer
14
- cover
15
- :loading
16
- :src="
17
- item && item.image && item.image.url ? item.image.url : '/default.png'
18
- "
19
- :ratio="1 / 1"
20
- :width="70"
21
- />
12
+ <v-col v-if="mdAndUp" cols="auto">
13
+ <div style="width: 80px; height: 80px; flex-shrink: 0">
14
+ <MiscAtomsImageContainer
15
+ cover
16
+ :loading
17
+ :src="
18
+ item && item.image && item.image.url
19
+ ? item.image.url
20
+ : '/default.png'
21
+ "
22
+ :ratio="1 / 1"
23
+ :width="80"
24
+ style="width: 80px; height: 80px"
25
+ />
26
+ </div>
22
27
  </v-col>
23
28
 
24
29
  <v-col align-self="start" class="px-2">
@@ -61,8 +66,8 @@
61
66
  v-else
62
67
  class="text-h5 dense paragraph mt-2"
63
68
  v-html="
64
- $rootStore.search.length
65
- ? highlightAndTruncate(300, item.name, $rootStore.search.split(' '))
69
+ searchQuery.length
70
+ ? highlightAndTruncate(300, item.name, searchQuery.split(' '))
66
71
  : item.name
67
72
  "
68
73
  />
@@ -73,7 +78,7 @@
73
78
  :value="`${highlightAndTruncate(
74
79
  85,
75
80
  item.summary,
76
- $rootStore.search.split(' '),
81
+ searchQuery.split(' '),
77
82
  )}`"
78
83
  />
79
84
  </v-col>
@@ -88,15 +93,21 @@
88
93
  </template>
89
94
 
90
95
  <script setup>
91
- import { useI18n, useNuxtApp, computed } from "#imports"
96
+ import { useI18n, useNuxtApp, useRoute, computed } from "#imports"
92
97
  import { useDisplay } from "vuetify"
93
98
  import { useRootStore } from "../../stores/root"
94
99
  import { highlightAndTruncate } from "../../composables/useUtils"
95
- const { $rootStore } = useNuxtApp()
100
+ const { $rootStore, $stores } = useNuxtApp()
96
101
  const { smAndDown, mdAndUp } = useDisplay()
97
102
 
98
103
  const { locale } = useI18n()
99
104
  const rootStore = useRootStore()
105
+ const { name: routeName } = useRoute()
106
+ const searchQuery = computed(() =>
107
+ routeName.startsWith("search")
108
+ ? $rootStore.search
109
+ : $stores["events"].search || "",
110
+ )
100
111
  const props = defineProps({
101
112
  item: {
102
113
  type: Object,
@@ -29,9 +29,15 @@
29
29
  />
30
30
 
31
31
  <div v-else>
32
- <div v-if="item.name" class="text-h4 text-black text-wrap mt-4">
33
- {{ item.name }}
34
- </div>
32
+ <div
33
+ v-if="item.name"
34
+ class="text-h4 text-black text-wrap mt-4"
35
+ v-html="
36
+ searchQuery.length
37
+ ? highlightAndTruncate(300, item.name, searchQuery.split(' '))
38
+ : item.name
39
+ "
40
+ />
35
41
  <div class="mt-2 text-h6 text-overline font-weight-black">
36
42
  {{ $t("list.filters.events.category." + item.category) }}
37
43
  </div>
@@ -45,7 +51,7 @@
45
51
  ]
46
52
  "
47
53
  >
48
- <MDC v-if="item.summary" :value="item.summary" />
54
+ <MDC v-if="item.summary" :value="searchQuery.length ? highlightAndTruncate(500, item.summary, searchQuery.split(' ')) : item.summary" />
49
55
  </p>
50
56
 
51
57
  <div v-if="lgAndUp" class="d-flex flex-row align-center flex-wrap">
@@ -85,12 +91,17 @@
85
91
  <script setup>
86
92
  import { useDisplay } from "vuetify"
87
93
  import { useRootStore } from "../../stores/root"
88
- import { useNuxtApp, computed } from "#imports"
94
+ import { useNuxtApp, useRoute, computed } from "#imports"
95
+ import { highlightAndTruncate } from "../../composables/useUtils"
89
96
 
90
97
  const { name, mdAndDown, lgAndUp } = useDisplay()
91
98
 
92
99
  const rootStore = useRootStore()
93
100
  const { $stores } = useNuxtApp()
101
+ const { name: routeName } = useRoute()
102
+ const searchQuery = computed(() =>
103
+ routeName.startsWith('search') ? rootStore.search : ($stores['events'].search || '')
104
+ )
94
105
  const props = defineProps({
95
106
  item: {
96
107
  type: Object,
@@ -6,7 +6,7 @@
6
6
  :date-stop="item.stop"
7
7
  :loading="loading"
8
8
  class="mr-4 mb-6 mb-md-0"
9
- style="min-width: 70px"
9
+ style="min-width: 80px"
10
10
  />
11
11
  <div
12
12
  class="d-flex flex-column"
@@ -4,7 +4,12 @@
4
4
  no-gutters
5
5
  class="cursor-pointer highlight-on-hover pt-2 pl-2"
6
6
  >
7
- <v-col align-self="center" cols="8" class="text-h5 dense">
7
+ <v-col
8
+ align-self="center"
9
+ cols="8"
10
+ class="text-h5 dense"
11
+ :offset="name.startsWith('search') ? 1 : 0"
12
+ >
8
13
  <v-skeleton-loader
9
14
  v-if="loading"
10
15
  type="heading"
@@ -14,14 +19,14 @@
14
19
  <div
15
20
  v-else
16
21
  v-html="
17
- $rootStore.search.length
18
- ? highlightAndTruncate(300, item.name, $rootStore.search.split(' '))
22
+ searchQuery.length
23
+ ? highlightAndTruncate(300, item.name, searchQuery.split(' '))
19
24
  : item.name
20
25
  "
21
26
  />
22
27
  <FellowshipsBadges :item="item" :loading="loading" />
23
28
  </v-col>
24
- <v-col align-self="center" cols="4">
29
+ <v-col align-self="center" cols="auto">
25
30
  <v-skeleton-loader v-if="loading" type="chip" width="260" class="mt-2" />
26
31
  <MiscMoleculesChipContainer
27
32
  v-else
@@ -38,10 +43,13 @@
38
43
  </template>
39
44
 
40
45
  <script setup>
41
- import { useNuxtApp } from "#imports"
46
+ import { useNuxtApp, useRoute, computed } from "#imports"
42
47
  import { highlightAndTruncate } from "../../composables/useUtils"
43
- const { $rootStore } = useNuxtApp()
44
-
48
+ const { $rootStore, $stores } = useNuxtApp()
49
+ const { name } = useRoute()
50
+ const searchQuery = computed(() =>
51
+ name.startsWith('search') ? $rootStore.search : ($stores['fellowships'].search || '')
52
+ )
45
53
  const props = defineProps({
46
54
  item: {
47
55
  type: Object,
@@ -13,9 +13,14 @@
13
13
  <template v-else>
14
14
  <FellowshipsBadges :item :loading="loading" />
15
15
 
16
- <div class="text-h4 text-black text-wrap mt-4 pb-4">
17
- {{ item.name }}
18
- </div>
16
+ <div
17
+ class="text-h4 text-black text-wrap mt-4 pb-4"
18
+ v-html="
19
+ searchQuery.length
20
+ ? highlightAndTruncate(300, item.name, searchQuery.split(' '))
21
+ : item.name
22
+ "
23
+ />
19
24
 
20
25
  <div
21
26
  v-if="item.summary"
@@ -27,7 +32,7 @@
27
32
  ]
28
33
  "
29
34
  >
30
- <MDC :value="item.summary" />
35
+ <MDC :value="searchQuery.length ? highlightAndTruncate(500, item.summary, searchQuery.split(' ')) : item.summary" />
31
36
  </div>
32
37
 
33
38
  <MiscMoleculesChipContainer
@@ -50,8 +55,17 @@
50
55
 
51
56
  <script setup>
52
57
  import { useDisplay } from "vuetify"
58
+ import { computed, useRoute, useNuxtApp } from "#imports"
59
+ import { useRootStore } from "../../stores/root"
60
+ import { highlightAndTruncate } from "../../composables/useUtils"
53
61
 
54
62
  const { name } = useDisplay()
63
+ const rootStore = useRootStore()
64
+ const { $stores } = useNuxtApp()
65
+ const { name: routeName } = useRoute()
66
+ const searchQuery = computed(() =>
67
+ routeName.startsWith('search') ? rootStore.search : ($stores['fellowships'].search || '')
68
+ )
55
69
 
56
70
  const props = defineProps({
57
71
  item: {
@@ -33,7 +33,9 @@
33
33
  />
34
34
 
35
35
  <div v-else class="d-flex align-center flex-column mt-12">
36
- <div class="d-flex text-center text-wrap text-h3 text-black">
36
+ <div
37
+ class="d-flex text-center text-wrap text-h4 text-md-h3 text-black"
38
+ >
37
39
  {{ item.name }}
38
40
  </div>
39
41
  <v-divider width="154px" class="mb-1 mt-6" />
@@ -57,7 +59,7 @@
57
59
  <FellowshipsBadges :item="item" :view="view" :loading="loading" />
58
60
  </div>
59
61
  </div>
60
- <div class="mx-6">
62
+ <div>
61
63
  <!-- DIVIDERS -->
62
64
  <v-responsive class="mx-auto my-9" width="120">
63
65
  <v-divider class="mb-1" />
@@ -49,9 +49,13 @@
49
49
  :id="`global-search-input-${type}`"
50
50
  v-model.trim="search"
51
51
  :placeholder="
52
- type === 'all'
53
- ? t('search')
54
- : $t('list.search-type', [$t('items.' + type, 2)])
52
+ routeName.startsWith('search')
53
+ ? mdAndUp
54
+ ? $t('type-a-search-term-to-find-results-across-all-categories')
55
+ : $t('search')
56
+ : type === 'all'
57
+ ? $t('search')
58
+ : $t('list.search-type', [$t('items.' + type, 2)])
55
59
  "
56
60
  single-line
57
61
  class="transition-swing flex-grow-1"
@@ -66,11 +70,18 @@
66
70
  >
67
71
  <template v-if="!search" #label>
68
72
  <div class="searchLabel">
69
- {{ $t("search") }}
73
+ {{
74
+ routeName.startsWith("search")
75
+ ? mdAndUp
76
+ ? $t("type-a-search-term-to-find-results-across-all-categories")
77
+ : $t("search")
78
+ : type === "all"
79
+ ? $t("search")
80
+ : $t("list.search-type", [$t("items." + type, 2)])
81
+ }}
70
82
  </div>
71
83
  </template>
72
84
  </v-text-field>
73
-
74
85
  <v-btn
75
86
  :rounded="0"
76
87
  variant="outlined"
@@ -90,11 +101,21 @@
90
101
  <script setup>
91
102
  import { useDebounceFn } from "@vueuse/core"
92
103
  import { useRootStore } from "../../../stores/root"
93
- import { computed, useI18n, ref, useLocalePath, useRouter, navigateTo } from "#imports"
104
+ import { useDisplay } from "vuetify"
105
+ import {
106
+ computed,
107
+ useI18n,
108
+ ref,
109
+ useLocalePath,
110
+ useRoute,
111
+ useRouter,
112
+ navigateTo,
113
+ } from "#imports"
114
+ const { mdAndUp } = useDisplay()
94
115
  const localePath = useLocalePath()
95
116
  const { locale, t } = useI18n()
96
117
  const rootStore = useRootStore()
97
-
118
+ const { name: routeName } = useRoute()
98
119
  // Utility function to capitalize first letter
99
120
  const capitalize = (str) => str.charAt(0).toUpperCase() + str.slice(1)
100
121
  const emit = defineEmits(["filter-change"])
@@ -151,7 +172,11 @@ const toggleFilter = (option) => {
151
172
  const textFieldRef = ref(null)
152
173
 
153
174
  const navigateToSearch = () => {
154
- const term = (textFieldRef.value?.$el?.querySelector("input")?.value ?? rootStore.search ?? "").trim()
175
+ const term = (
176
+ textFieldRef.value?.$el?.querySelector("input")?.value ??
177
+ rootStore.search ??
178
+ ""
179
+ ).trim()
155
180
  rootStore.search = term
156
181
  navigateTo({
157
182
  path: localePath("/search"),
@@ -15,8 +15,7 @@
15
15
  class="d-flex flex-column cursor-pointer"
16
16
  @click="$emit('toggle', type)"
17
17
  >
18
- <div
19
- class="text-h5 font-weight-medium"
18
+ <h3
20
19
  :class="
21
20
  $rootStore.results[type] && $rootStore.results[type].total > 0
22
21
  ? 'black'
@@ -24,7 +23,7 @@
24
23
  "
25
24
  >
26
25
  {{ capitalize($t("items." + props.type, 2)) }}
27
- </div>
26
+ </h3>
28
27
  <div class="text-overline">
29
28
  {{
30
29
  feminine
@@ -11,10 +11,17 @@
11
11
  <ListAtomsLogoPlaceholder idle />
12
12
  </div>
13
13
  <template v-else>
14
- <div v-if="loading" class="search-pending">
15
- <ListAtomsLogoPlaceholder />
14
+ <div v-if="pending" class="search-pending">
15
+ <div class="search-pending__inner">
16
+ <ListAtomsLogoPlaceholder class="loader-logo loader-logo--active" />
17
+ <div class="search-pending__dots">
18
+ <span class="search-pending__dot" />
19
+ <span class="search-pending__dot" />
20
+ <span class="search-pending__dot" />
21
+ </div>
22
+ </div>
16
23
  </div>
17
- <template v-else-if="!loading">
24
+ <template v-else>
18
25
  <ListMoleculesResultsContainer
19
26
  v-for="type in filteredSortedModules"
20
27
  :key="type"
@@ -48,7 +55,6 @@ import {
48
55
  useI18n,
49
56
  useAppConfig,
50
57
  useRoute,
51
- ref,
52
58
  reactive,
53
59
  useAsyncQuery,
54
60
  computed,
@@ -64,8 +70,6 @@ if (route.query.search) {
64
70
  $rootStore.search = route.query.search
65
71
  }
66
72
 
67
- const loading = ref(true)
68
-
69
73
  const open = reactive(
70
74
  appConfig.list.modules.reduce((acc, type) => {
71
75
  acc[type] = false
@@ -73,10 +77,14 @@ const open = reactive(
73
77
  }, {}),
74
78
  )
75
79
 
76
- const selectedCategories = ref([...appConfig.list.modules])
80
+ const selectedCategories = reactive([...appConfig.list.modules])
77
81
 
78
82
  const handleFilterChange = (filterData) => {
79
- selectedCategories.value = filterData.categories
83
+ selectedCategories.splice(
84
+ 0,
85
+ selectedCategories.length,
86
+ ...filterData.categories,
87
+ )
80
88
  }
81
89
 
82
90
  const sortedModules = computed(() => {
@@ -93,9 +101,7 @@ const sortedModules = computed(() => {
93
101
  })
94
102
  })
95
103
  const filteredSortedModules = computed(() => {
96
- return sortedModules.value.filter((type) =>
97
- selectedCategories.value.includes(type),
98
- )
104
+ return sortedModules.value.filter((type) => selectedCategories.includes(type))
99
105
  })
100
106
 
101
107
  const searchTerm = computed(() => $rootStore.search || "")
@@ -113,13 +119,8 @@ const { data, pending, error } = useAsyncQuery(
113
119
  enabled: computed(() => searchTerm.value.length > 0),
114
120
  },
115
121
  )
116
- if (error.value) {
117
- console.error("GraphQL query error: ", error.value)
118
- } else {
119
- /* console.log("Query result data: ", data.value.items?.length) */
120
- }
121
122
 
122
- const applyData = (newData) => {
123
+ watch(data, (newData) => {
123
124
  if (!newData) return
124
125
  $rootStore.applyListResult("all", newData)
125
126
  appConfig.list.modules.forEach((type) => {
@@ -127,18 +128,364 @@ const applyData = (newData) => {
127
128
  open[type] = true
128
129
  }
129
130
  })
130
- loading.value = false
131
- }
132
-
133
- watch(data, applyData, { immediate: true })
131
+ })
134
132
 
135
133
  watch(error, (err) => {
136
- if (err) {
137
- console.error("GraphQL query error:", err)
138
- loading.value = false
139
- }
134
+ if (err) console.error("GraphQL query error:", err)
140
135
  })
141
136
  </script>
137
+
142
138
  <style scoped>
143
- .results-container{display:flex;flex-direction:column;gap:8px;margin-left:8px}.search-empty{min-height:100vh;opacity:.8}.search-empty,.search-pending{display:flex;justify-content:center}.search-pending{padding:48px 0}
139
+ @charset "UTF-8";
140
+ .results-container {
141
+ margin-left: 8px;
142
+ display: flex;
143
+ flex-direction: column;
144
+ gap: 8px;
145
+ }
146
+
147
+ .search-empty {
148
+ min-height: 100vh;
149
+ display: flex;
150
+ justify-content: center;
151
+ opacity: 0.8;
152
+ }
153
+ .search-empty svg {
154
+ width: min(32vh, 32vw);
155
+ height: min(32vh, 32vw);
156
+ }
157
+
158
+ .search-pending {
159
+ display: flex;
160
+ justify-content: center;
161
+ padding: 48px 0;
162
+ }
163
+ .search-pending svg {
164
+ width: min(32vh, 32vw);
165
+ height: min(32vh, 32vw);
166
+ }
167
+
168
+ .search-pending__inner {
169
+ display: flex;
170
+ flex-direction: column;
171
+ align-items: center;
172
+ gap: 24px;
173
+ }
174
+
175
+ .loader-logo {
176
+ filter: drop-shadow(0 4px 12px rgba(0, 0, 0, 0.1));
177
+ width: 200px;
178
+ height: auto;
179
+ }
180
+
181
+ /* Drawing animation — mirrors ListOrganismsLoader exactly */
182
+ .loader-logo--active :deep([class^="IEA_"]) {
183
+ fill: transparent;
184
+ stroke: #000;
185
+ stroke-opacity: 1;
186
+ fill-opacity: 1;
187
+ fill-rule: nonzero;
188
+ }
189
+
190
+ .loader-logo--active :deep(.IEA_0) {
191
+ stroke-dasharray: 1223 1225;
192
+ stroke-dashoffset: 1224;
193
+ animation: IEA_draw_0 3141ms ease 0ms infinite;
194
+ }
195
+
196
+ .loader-logo--active :deep(.IEA_5) {
197
+ stroke-dasharray: 335 337;
198
+ stroke-dashoffset: 336;
199
+ animation: IEA_draw_5 3141ms ease 0ms infinite;
200
+ }
201
+
202
+ .loader-logo--active :deep(.IEA_1) {
203
+ stroke-dasharray: 1351 1353;
204
+ stroke-dashoffset: 1352;
205
+ animation: IEA_draw_1 3141ms ease 0ms infinite, stroke-fade 3141ms linear 0ms infinite, fill-standard 3141ms ease 0ms infinite;
206
+ }
207
+
208
+ .loader-logo--active :deep(.IEA_2) {
209
+ stroke-dasharray: 1311 1313;
210
+ stroke-dashoffset: 1312;
211
+ animation: IEA_draw_2 3141ms ease 0ms infinite, stroke-fade 3141ms linear 0ms infinite, fill-standard 3141ms ease 0ms infinite;
212
+ }
213
+
214
+ .loader-logo--active :deep(.IEA_3),
215
+ .loader-logo--active :deep(.IEA_4) {
216
+ stroke-dasharray: 13 15;
217
+ stroke-dashoffset: 14;
218
+ }
219
+
220
+ .loader-logo--active :deep(.IEA_3) {
221
+ animation: IEA_draw_3 3141ms ease 0ms infinite, stroke-fade 3141ms linear 0ms infinite, fill-standard 3141ms ease 0ms infinite;
222
+ }
223
+
224
+ .loader-logo--active :deep(.IEA_4) {
225
+ animation: IEA_draw_4 3141ms ease 0ms infinite, stroke-fade 3141ms linear 0ms infinite, fill-standard 3141ms ease 0ms infinite;
226
+ }
227
+
228
+ .loader-logo--active :deep(.IEA_6),
229
+ .loader-logo--active :deep(.IEA_7),
230
+ .loader-logo--active :deep(.IEA_8) {
231
+ stroke-dasharray: 102 104;
232
+ stroke-dashoffset: 103;
233
+ }
234
+
235
+ .loader-logo--active :deep(.IEA_6) {
236
+ animation: IEA_draw_6 3141ms ease 0ms infinite, stroke-fade 3141ms linear 0ms infinite, fill-standard 3141ms ease 0ms infinite;
237
+ }
238
+
239
+ .loader-logo--active :deep(.IEA_7) {
240
+ animation: IEA_draw_7 3141ms ease 0ms infinite, stroke-fade 3141ms linear 0ms infinite, fill-standard 3141ms ease 0ms infinite;
241
+ }
242
+
243
+ .loader-logo--active :deep(.IEA_8) {
244
+ animation: IEA_draw_8 3141ms ease 0ms infinite, stroke-fade 3141ms linear 0ms infinite, fill-standard 3141ms ease 0ms infinite;
245
+ }
246
+
247
+ .loader-logo--active :deep(.IEA_9) {
248
+ stroke-dasharray: 372 374;
249
+ stroke-dashoffset: 373;
250
+ animation: IEA_draw_9 3141ms ease 0ms infinite, stroke-fade 3141ms linear 0ms infinite, fill-standard 3141ms ease 0ms infinite;
251
+ }
252
+
253
+ .loader-logo--active :deep(.IEA_10) {
254
+ stroke-dasharray: 96 98;
255
+ stroke-dashoffset: 97;
256
+ animation: IEA_draw_10 3141ms ease 0ms infinite, stroke-fade 3141ms linear 0ms infinite, fill-standard 3141ms ease 0ms infinite;
257
+ }
258
+
259
+ .loader-logo--active :deep(.IEA_11) {
260
+ stroke-dasharray: 184 186;
261
+ stroke-dashoffset: 185;
262
+ animation: IEA_draw_11 3141ms ease 0ms infinite, stroke-fade 3141ms linear 0ms infinite, fill-standard 3141ms ease 0ms infinite;
263
+ }
264
+
265
+ .loader-logo--active :deep(.IEA_12) {
266
+ stroke-dasharray: 169 171;
267
+ stroke-dashoffset: 170;
268
+ animation: IEA_draw_12 3141ms ease 0ms infinite, stroke-fade 3141ms linear 0ms infinite, fill-standard 3141ms ease 0ms infinite;
269
+ }
270
+
271
+ .loader-logo--active :deep(.IEA_13) {
272
+ stroke-dasharray: 1020 1022;
273
+ stroke-dashoffset: 1021;
274
+ animation: IEA_draw_13 3141ms ease 0ms infinite, stroke-fade 3141ms linear 0ms infinite, fill-circles 3141ms ease 0ms infinite;
275
+ }
276
+
277
+ .loader-logo--active :deep(.IEA_14) {
278
+ stroke-dasharray: 939 941;
279
+ stroke-dashoffset: 940;
280
+ animation: IEA_draw_14 3141ms ease 0ms infinite, stroke-fade 3141ms linear 0ms infinite, fill-circles 3141ms ease 0ms infinite;
281
+ }
282
+
283
+ .loader-logo--active :deep(.IEA_15) {
284
+ stroke-dasharray: 326 328;
285
+ stroke-dashoffset: 327;
286
+ animation: IEA_draw_15 3141ms ease 0ms infinite, stroke-fade 3141ms linear 0ms infinite, fill-standard 3141ms ease 0ms infinite;
287
+ }
288
+
289
+ @keyframes fill-standard {
290
+ 0%, 35% {
291
+ fill: transparent;
292
+ }
293
+ 60% {
294
+ fill: rgba(0, 0, 0, 0.3);
295
+ }
296
+ 80% {
297
+ fill: rgba(0, 0, 0, 0.7);
298
+ }
299
+ 100% {
300
+ fill: #000;
301
+ }
302
+ }
303
+ @keyframes fill-circles {
304
+ 0%, 35% {
305
+ fill: transparent;
306
+ }
307
+ 60% {
308
+ fill: rgba(0, 0, 0, 0.2);
309
+ }
310
+ 80% {
311
+ fill: rgba(0, 0, 0, 0.5);
312
+ }
313
+ 100% {
314
+ fill: #000;
315
+ }
316
+ }
317
+ @keyframes stroke-fade {
318
+ 0%, 65% {
319
+ stroke-opacity: 1;
320
+ }
321
+ 80% {
322
+ stroke-opacity: 0.5;
323
+ }
324
+ 95% {
325
+ stroke-opacity: 0.2;
326
+ }
327
+ 100% {
328
+ stroke-opacity: 0;
329
+ }
330
+ }
331
+ @keyframes IEA_draw_0 {
332
+ 5% {
333
+ stroke-dashoffset: 1224;
334
+ }
335
+ 30%, 100% {
336
+ stroke-dashoffset: 0;
337
+ }
338
+ }
339
+ @keyframes IEA_draw_1 {
340
+ 8% {
341
+ stroke-dashoffset: 1352;
342
+ }
343
+ 35%, 100% {
344
+ stroke-dashoffset: 0;
345
+ }
346
+ }
347
+ @keyframes IEA_draw_2 {
348
+ 10% {
349
+ stroke-dashoffset: 1312;
350
+ }
351
+ 40%, 100% {
352
+ stroke-dashoffset: 0;
353
+ }
354
+ }
355
+ @keyframes IEA_draw_3 {
356
+ 12% {
357
+ stroke-dashoffset: 14;
358
+ }
359
+ 45%, 100% {
360
+ stroke-dashoffset: 0;
361
+ }
362
+ }
363
+ @keyframes IEA_draw_4 {
364
+ 15% {
365
+ stroke-dashoffset: 14;
366
+ }
367
+ 50%, 100% {
368
+ stroke-dashoffset: 0;
369
+ }
370
+ }
371
+ @keyframes IEA_draw_5 {
372
+ 15.74% {
373
+ stroke-dashoffset: 336;
374
+ }
375
+ 43.52%, 100% {
376
+ stroke-dashoffset: 0;
377
+ }
378
+ }
379
+ @keyframes IEA_draw_6 {
380
+ 16.67% {
381
+ stroke-dashoffset: 103;
382
+ }
383
+ 44.44%, 100% {
384
+ stroke-dashoffset: 0;
385
+ }
386
+ }
387
+ @keyframes IEA_draw_7 {
388
+ 17.59% {
389
+ stroke-dashoffset: 103;
390
+ }
391
+ 45.37%, 100% {
392
+ stroke-dashoffset: 0;
393
+ }
394
+ }
395
+ @keyframes IEA_draw_8 {
396
+ 18.52% {
397
+ stroke-dashoffset: 103;
398
+ }
399
+ 46.3%, 100% {
400
+ stroke-dashoffset: 0;
401
+ }
402
+ }
403
+ @keyframes IEA_draw_9 {
404
+ 19.44% {
405
+ stroke-dashoffset: 373;
406
+ }
407
+ 47.22%, 100% {
408
+ stroke-dashoffset: 0;
409
+ }
410
+ }
411
+ @keyframes IEA_draw_10 {
412
+ 20.37% {
413
+ stroke-dashoffset: 97;
414
+ }
415
+ 48.15%, 100% {
416
+ stroke-dashoffset: 0;
417
+ }
418
+ }
419
+ @keyframes IEA_draw_11 {
420
+ 21.3% {
421
+ stroke-dashoffset: 185;
422
+ }
423
+ 49.07%, 100% {
424
+ stroke-dashoffset: 0;
425
+ }
426
+ }
427
+ @keyframes IEA_draw_12 {
428
+ 22.22% {
429
+ stroke-dashoffset: 170;
430
+ }
431
+ 50%, 100% {
432
+ stroke-dashoffset: 0;
433
+ }
434
+ }
435
+ @keyframes IEA_draw_13 {
436
+ 23.15% {
437
+ stroke-dashoffset: 1021;
438
+ }
439
+ 50.93%, 100% {
440
+ stroke-dashoffset: 0;
441
+ }
442
+ }
443
+ @keyframes IEA_draw_14 {
444
+ 24.07% {
445
+ stroke-dashoffset: 940;
446
+ }
447
+ 51.85%, 100% {
448
+ stroke-dashoffset: 0;
449
+ }
450
+ }
451
+ @keyframes IEA_draw_15 {
452
+ 25% {
453
+ stroke-dashoffset: 327;
454
+ }
455
+ 52.78%, 100% {
456
+ stroke-dashoffset: 0;
457
+ }
458
+ }
459
+ /* Minimalist three-dot indicator */
460
+ .search-pending__dots {
461
+ display: flex;
462
+ gap: 6px;
463
+ align-items: center;
464
+ }
465
+
466
+ .search-pending__dot {
467
+ display: block;
468
+ width: 5px;
469
+ height: 5px;
470
+ border-radius: 50%;
471
+ background: #000;
472
+ animation: dot-bounce 1.2s ease-in-out infinite;
473
+ }
474
+ .search-pending__dot:nth-child(2) {
475
+ animation-delay: 0.2s;
476
+ }
477
+ .search-pending__dot:nth-child(3) {
478
+ animation-delay: 0.4s;
479
+ }
480
+
481
+ @keyframes dot-bounce {
482
+ 0%, 80%, 100% {
483
+ opacity: 0.15;
484
+ transform: translateY(0);
485
+ }
486
+ 40% {
487
+ opacity: 0.7;
488
+ transform: translateY(-4px);
489
+ }
490
+ }
144
491
  </style>
@@ -8,7 +8,10 @@
8
8
  <v-skeleton-loader v-if="loading" height="100%" type="image" />
9
9
 
10
10
  <template v-else>
11
- <div class="overflow-hidden mw-100">
11
+ <div
12
+ class="overflow-hidden"
13
+ :style="width ? `width: ${width}px; height: ${Math.round(width / ratio)}px; max-width: 100%` : 'max-width: 100%'"
14
+ >
12
15
  <!-- TODO debug why the picture is not displaying/sizing properly -->
13
16
  <v-img
14
17
  v-if="src && computedSrc"
@@ -1,5 +1,13 @@
1
1
  <template>
2
2
  <v-row v-ripple no-gutters class="cursor-pointer highlight-on-hover pa-2">
3
+ <v-col v-if="mdAndUp" cols="1">
4
+ <MiscAtomsDateStamp
5
+ v-if="item.date"
6
+ :loading
7
+ :date-start="item.date"
8
+ class="pr-4"
9
+ />
10
+ </v-col>
3
11
  <v-col v-if="mdAndUp" align-self="center" cols="1">
4
12
  <MiscAtomsImageContainer
5
13
  cover
@@ -8,7 +16,7 @@
8
16
  item && item.image && item.image.url ? item.image.url : '/default.png'
9
17
  "
10
18
  :ratio="1 / 1"
11
- :width="70"
19
+ :width="80"
12
20
  />
13
21
  </v-col>
14
22
  <v-col align-self="start" class="text-h5 dense mx-2 paragraph">
@@ -36,12 +44,8 @@
36
44
  </template>
37
45
  <div
38
46
  v-html="
39
- rootStore.search.length
40
- ? highlightAndTruncate(
41
- 300,
42
- item.name,
43
- rootStore.search.split(' '),
44
- )
47
+ searchQuery.length
48
+ ? highlightAndTruncate(300, item.name, searchQuery.split(' '))
45
49
  : item.name
46
50
  "
47
51
  />
@@ -54,10 +58,17 @@
54
58
  import { useDisplay } from "vuetify"
55
59
  import { useRootStore } from "../../stores/root"
56
60
  import { highlightAndTruncate } from "../../composables/useUtils"
57
- import { computed } from "#imports"
61
+ import { computed, useRoute, useNuxtApp } from "#imports"
58
62
 
59
63
  const { name, mdAndUp } = useDisplay()
60
64
  const rootStore = useRootStore()
65
+ const { name: routeName } = useRoute()
66
+ const { $stores } = useNuxtApp()
67
+ const searchQuery = computed(() =>
68
+ routeName.startsWith("search")
69
+ ? rootStore.search
70
+ : $stores["news"].search || "",
71
+ )
61
72
 
62
73
  const props = defineProps({
63
74
  item: {
@@ -36,9 +36,14 @@
36
36
  <br />
37
37
  </template>
38
38
 
39
- <div class="text-wrap text-h5 text-md-h4 text-black">
40
- {{ item.name }}
41
- </div>
39
+ <div
40
+ class="text-wrap text-h5 text-md-h4 text-black"
41
+ v-html="
42
+ searchQuery.length
43
+ ? highlightAndTruncate(300, item.name, searchQuery.split(' '))
44
+ : item.name
45
+ "
46
+ />
42
47
  <div class="tex-overline mt-3">
43
48
  {{ formatDateValue(item.date, locale) }}
44
49
  </div>
@@ -104,11 +109,19 @@
104
109
  <script setup>
105
110
  import { useDisplay } from "vuetify"
106
111
  import { useRootStore } from "../../stores/root"
107
- import { useI18n, computed } from "#imports"
112
+ import { useI18n, computed, useRoute, useNuxtApp } from "#imports"
113
+ import { highlightAndTruncate } from "../../composables/useUtils"
108
114
 
109
115
  const { locale } = useI18n()
110
116
  const rootStore = useRootStore()
111
117
  const { name, smAndDown, mdAndDown, mdAndUp, lgAndUp } = useDisplay()
118
+ const { $stores } = useNuxtApp()
119
+ const { name: routeName } = useRoute()
120
+ const searchQuery = computed(() =>
121
+ routeName.startsWith("search")
122
+ ? rootStore.search
123
+ : $stores["news"].search || "",
124
+ )
112
125
  const eventCategory = computed(() => {
113
126
  if (props.item.category) {
114
127
  return "list.filters.news.category." + props.item.category
@@ -138,8 +151,10 @@ const props = defineProps({
138
151
 
139
152
  const processedSummary = computed(() => {
140
153
  const raw = props.item.summary || ""
141
-
142
- return replaceMarkdownLinksWithSlug(raw, props.path)
154
+ const linked = replaceMarkdownLinksWithSlug(raw, props.path)
155
+ return searchQuery.value.length
156
+ ? highlightAndTruncate(500, linked, searchQuery.value.split(" "))
157
+ : linked
143
158
  })
144
159
 
145
160
  function replaceMarkdownLinksWithSlug(markdownText, slugPath) {
@@ -11,7 +11,7 @@
11
11
  v-for="(vintage, index2) in item.groups.vintage"
12
12
  :key="index2"
13
13
  size="small"
14
- class="mt-3 mr-3"
14
+ class="mt-3 mr-1 mr-md-3"
15
15
  variant="outlined"
16
16
  tile
17
17
  style="background-color: white; color: black"
@@ -1,6 +1,6 @@
1
1
  <template>
2
2
  <v-row v-ripple no-gutters class="cursor-pointer highlight-on-hover">
3
- <v-col v-if="mdAndUp" cols="1">
3
+ <v-col v-if="mdAndUp" cols="1" :offset="name.startsWith('search') ? 1 : 0">
4
4
  <MiscAtomsImageContainer
5
5
  cover
6
6
  :loading="loading"
@@ -8,20 +8,23 @@
8
8
  item && item.image && item.image.url ? item.image.url : '/default.png'
9
9
  "
10
10
  :ratio="1 / 1"
11
- :width="70"
11
+ :width="80"
12
12
  class="ma-1"
13
13
  />
14
14
  </v-col>
15
- <v-col align-self="start" class="text-h6 dense px-2">
15
+ <v-col align-self="start" class="text-sm-h6 dense">
16
16
  <v-skeleton-loader v-if="loading" type="heading" />
17
- <div v-else class="d-flex text-h5 align-center">
17
+ <div
18
+ v-else
19
+ class="d-flex justify-space-between text-title text-md-h5 align-center"
20
+ >
18
21
  <span
19
22
  v-html="
20
- rootStore.search.length
23
+ searchQuery.length
21
24
  ? highlightAndTruncate(
22
25
  300,
23
26
  item.firstname + ' ' + item.lastname,
24
- rootStore.search.split(' '),
27
+ searchQuery.split(' '),
25
28
  )
26
29
  : item.firstname + ' ' + item.lastname
27
30
  "
@@ -33,15 +36,30 @@
33
36
  v-if="item.group && item.groups.vintage && item.groups.vintage[0].theme"
34
37
  class="text-body-1 font-weight-light paragraph"
35
38
  v-html="
36
- rootStore.search.length
39
+ searchQuery.length
37
40
  ? highlightAndTruncate(
38
41
  300,
39
42
  item.groups.vintage[0].theme,
40
- rootStore.search.split(' '),
43
+ searchQuery.split(' '),
41
44
  )
42
45
  : item.groups.vintage[0].theme
43
46
  "
44
47
  />
48
+ <div
49
+ v-if="item.summary && mdAndUp"
50
+ class="text-body-1 font-weight-light paragraph"
51
+ :style="
52
+ '-webkit-line-clamp:' +
53
+ [1, 1, 1, 2, 3, 3][
54
+ ['xs', 'sm', 'md', 'lg', 'xl', 'xxl'].indexOf(displayName || 'md')
55
+ ]
56
+ "
57
+ v-html="
58
+ searchQuery.length
59
+ ? highlightAndTruncate(100, item.summary, searchQuery.split(' '))
60
+ : item.summary
61
+ "
62
+ />
45
63
  </v-col>
46
64
  </v-row>
47
65
  </template>
@@ -49,11 +67,16 @@
49
67
  <script setup>
50
68
  import { useRootStore } from "../../stores/root"
51
69
  import { highlightAndTruncate } from "../../composables/useUtils"
52
- import { computed } from "#imports"
70
+ import { computed, useRoute, useNuxtApp } from "#imports"
53
71
  import { useDisplay } from "vuetify"
72
+ const { name } = useRoute()
54
73
 
55
- const { mdAndUp } = useDisplay()
74
+ const { mdAndUp, name: displayName } = useDisplay()
56
75
  const rootStore = useRootStore()
76
+ const { $stores } = useNuxtApp()
77
+ const searchQuery = computed(() =>
78
+ name.startsWith("search") ? rootStore.search : $stores["people"].search || "",
79
+ )
57
80
  const props = defineProps({
58
81
  item: {
59
82
  type: Object,
@@ -72,5 +95,5 @@ const props = defineProps({
72
95
  })
73
96
  </script>
74
97
  <style>
75
- .paragraph{max-width:83ch!important}
98
+ .paragraph{display:-webkit-box;max-width:83ch!important;-webkit-box-orient:vertical;overflow:hidden}
76
99
  </style>
@@ -26,17 +26,22 @@
26
26
  />
27
27
 
28
28
  <div v-else class="ml-md-8">
29
- <div class="text-wrap text-h5 text-md-h4 text-black">
30
- {{ item.firstname + " " + item.lastname }}
31
- </div>
29
+ <div
30
+ class="text-wrap text-h5 text-md-h4 text-black"
31
+ v-html="
32
+ searchQuery.length
33
+ ? highlightAndTruncate(300, item.firstname + ' ' + item.lastname, searchQuery.split(' '))
34
+ : item.firstname + ' ' + item.lastname
35
+ "
36
+ />
32
37
  <MiscAtomsSocials v-if="item.socials" :socials="item.socials" />
33
38
  <PeoplepBadges :item="item" />
34
39
  <div
35
- v-if="item.biography && item.biography.length > 0"
40
+ v-if="item.summary && item.summary.length > 0"
36
41
  class="text-wrap clamped-text text-black"
37
42
  :style="'-webkit-line-clamp:' + lineClamp"
38
43
  >
39
- <MDC :value="item.biography" />
44
+ <MDC :value="searchQuery.length ? highlightAndTruncate(500, item.summary, searchQuery.split(' ')) : item.summary" />
40
45
  </div>
41
46
 
42
47
  <div v-else class="text-body-2">
@@ -49,9 +54,17 @@
49
54
 
50
55
  <script setup>
51
56
  import { useDisplay } from "vuetify"
52
- import { computed } from "#imports"
57
+ import { computed, useRoute, useNuxtApp } from "#imports"
58
+ import { useRootStore } from "../../stores/root"
59
+ import { highlightAndTruncate } from "../../composables/useUtils"
53
60
 
54
61
  const { name, mdAndUp } = useDisplay()
62
+ const rootStore = useRootStore()
63
+ const { $stores } = useNuxtApp()
64
+ const { name: routeName } = useRoute()
65
+ const searchQuery = computed(() =>
66
+ routeName.startsWith('search') ? rootStore.search : ($stores['people'].search || '')
67
+ )
55
68
 
56
69
  const props = defineProps({
57
70
  item: {
@@ -1,6 +1,11 @@
1
1
  <template>
2
- <v-row v-ripple no-gutters class="cursor-pointer highlight-on-hover px-4">
3
- <v-col v-if="mdAndUp" align-self="center" cols="1">
2
+ <v-row v-ripple no-gutters class="cursor-pointer highlight-on-hover">
3
+ <v-col
4
+ v-if="mdAndUp"
5
+ align-self="center"
6
+ cols="1"
7
+ :offset="name.startsWith('search') ? 1 : 0"
8
+ >
4
9
  <MiscAtomsImageContainer
5
10
  cover
6
11
  :loading="loading"
@@ -8,16 +13,16 @@
8
13
  item && item.image && item.image.url ? item.image.url : '/default.png'
9
14
  "
10
15
  :ratio="1 / 1"
11
- :width="70"
16
+ :width="80"
12
17
  />
13
18
  </v-col>
14
- <v-col align-self="center" class="text-h5 dense pl-4 pt-2">
19
+ <v-col align-self="center" class="text-h5 dense pt-2">
15
20
  <v-skeleton-loader v-if="loading" type="heading" width="50%" />
16
21
  <span
17
22
  v-else
18
23
  v-html="
19
- rootStore.search.length
20
- ? highlightAndTruncate(300, item.name, rootStore.search.split(' '))
24
+ searchQuery.length
25
+ ? highlightAndTruncate(300, item.name, searchQuery.split(' '))
21
26
  : item.name
22
27
  "
23
28
  />
@@ -36,7 +41,7 @@
36
41
  :value="`${highlightAndTruncate(
37
42
  150,
38
43
  item.summary,
39
- rootStore.search.split(' '),
44
+ searchQuery.split(' '),
40
45
  )}`"
41
46
  />
42
47
  </v-col>
@@ -47,10 +52,17 @@
47
52
  import { useDisplay } from "vuetify"
48
53
  import { useRootStore } from "../../stores/root"
49
54
  import { highlightAndTruncate } from "../../composables/useUtils"
50
-
55
+ import { useRoute, useNuxtApp, computed } from "#imports"
51
56
  const { mdAndUp } = useDisplay()
57
+ const { name } = useRoute()
52
58
 
53
59
  const rootStore = useRootStore()
60
+ const { $stores } = useNuxtApp()
61
+ const searchQuery = computed(() =>
62
+ name.startsWith("search")
63
+ ? rootStore.search
64
+ : $stores["projects"].search || "",
65
+ )
54
66
  const props = defineProps({
55
67
  item: {
56
68
  type: Object,
@@ -23,9 +23,14 @@
23
23
  />
24
24
 
25
25
  <template v-else>
26
- <div class="text-h5 text-sm-h3 text-md-h4 text-md-h4 my-6">
27
- {{ item.name }}
28
- </div>
26
+ <div
27
+ class="text-h5 text-sm-h3 text-md-h4 text-md-h4 my-6"
28
+ v-html="
29
+ searchQuery.length
30
+ ? highlightAndTruncate(300, item.name, searchQuery.split(' '))
31
+ : item.name
32
+ "
33
+ />
29
34
  <div
30
35
  v-if="item.summary"
31
36
  class="mt-n3 text-wrap clamped-text"
@@ -36,7 +41,17 @@
36
41
  ]
37
42
  "
38
43
  >
39
- <MDC :value="item.summary" />
44
+ <MDC
45
+ :value="
46
+ searchQuery.length
47
+ ? highlightAndTruncate(
48
+ 500,
49
+ item.summary,
50
+ searchQuery.split(' '),
51
+ )
52
+ : item.summary
53
+ "
54
+ />
40
55
  </div>
41
56
 
42
57
  <v-btn
@@ -54,7 +69,7 @@
54
69
  <v-btn
55
70
  variant="outlined"
56
71
  tile
57
- class="mt-4 ml-4"
72
+ class="mt-4 ml-0 ml-md-4"
58
73
  prepend-icon="mdi-web"
59
74
  :size="
60
75
  ['small', 'small', 'small', 'default', 'default', 'large'][
@@ -67,13 +82,24 @@
67
82
  </template>
68
83
  </v-col>
69
84
  </v-row>
70
- <v-divider />
85
+ <v-divider class="my-6" />
71
86
  </template>
72
87
 
73
88
  <script setup>
74
89
  import { useDisplay } from "vuetify"
90
+ import { computed, useRoute, useNuxtApp } from "#imports"
91
+ import { useRootStore } from "../../stores/root"
92
+ import { highlightAndTruncate } from "../../composables/useUtils"
75
93
 
76
94
  const { name } = useDisplay()
95
+ const rootStore = useRootStore()
96
+ const { $stores } = useNuxtApp()
97
+ const { name: routeName } = useRoute()
98
+ const searchQuery = computed(() =>
99
+ routeName.startsWith("search")
100
+ ? rootStore.search
101
+ : $stores["projects"].search || "",
102
+ )
77
103
 
78
104
  const props = defineProps({
79
105
  item: {
@@ -80,7 +80,7 @@
80
80
  ]
81
81
  "
82
82
  />
83
- <div v-if="item && item.description" class="mt-md-n2 mx-10 mx-md-0">
83
+ <div v-if="item && item.description" class="mt-md-n2">
84
84
  <MDC :value="item.description" />
85
85
  </div>
86
86
  </v-col>
@@ -1,6 +1,11 @@
1
1
  <template>
2
2
  <v-row v-ripple no-gutters class="cursor-pointer highlight-on-hover my-2">
3
- <v-col v-if="mdAndUp" cols="1" class="align-center">
3
+ <v-col
4
+ v-if="mdAndUp"
5
+ cols="1"
6
+ class="align-center"
7
+ :offset="name.startsWith('search') ? 1 : 0"
8
+ >
4
9
  <MiscAtomsImageContainer
5
10
  cover
6
11
  :loading="loading"
@@ -8,7 +13,7 @@
8
13
  item && item.image && item.image.url ? item.image.url : '/default.png'
9
14
  "
10
15
  :ratio="1 / 1"
11
- :width="70"
16
+ :width="80"
12
17
  />
13
18
  </v-col>
14
19
  <v-col class="pl-2">
@@ -44,12 +49,8 @@
44
49
  v-else
45
50
  class="text-h5 dense paragraph"
46
51
  v-html="
47
- rootStore.search.length
48
- ? highlightAndTruncate(
49
- 300,
50
- item.name,
51
- rootStore.search.split(' '),
52
- )
52
+ searchQuery.length
53
+ ? highlightAndTruncate(300, item.name, searchQuery.split(' '))
53
54
  : item.name
54
55
  "
55
56
  />
@@ -61,7 +62,7 @@
61
62
  :value="`${highlightAndTruncate(
62
63
  150,
63
64
  item.summary,
64
- rootStore.search.split(' '),
65
+ searchQuery.split(' '),
65
66
  )}`"
66
67
  />
67
68
  </div> </v-col
@@ -72,9 +73,15 @@
72
73
  import { useDisplay } from "vuetify"
73
74
  import { useRootStore } from "../../stores/root"
74
75
  import { highlightAndTruncate } from "../../composables/useUtils"
75
- import { computed } from "#imports"
76
+ import { computed, useRoute, useNuxtApp } from "#imports"
76
77
  const rootStore = useRootStore()
77
-
78
+ const { name } = useRoute()
79
+ const { $stores } = useNuxtApp()
80
+ const searchQuery = computed(() =>
81
+ name.startsWith("search")
82
+ ? rootStore.search
83
+ : $stores["publications"].search || "",
84
+ )
78
85
  const { mdAndUp } = useDisplay()
79
86
  const props = defineProps({
80
87
  item: {
@@ -25,9 +25,14 @@
25
25
 
26
26
  <template v-else>
27
27
  <div v-if="item.type" class="text-overline">{{ $t(item.type) }}</div>
28
- <div class="text-h5 text-sm-h3 text-md-h4 text-md-h4 my-6">
29
- {{ item.name }}
30
- </div>
28
+ <div
29
+ class="text-h5 text-sm-h3 text-md-h4 text-md-h4 my-6"
30
+ v-html="
31
+ searchQuery.length
32
+ ? highlightAndTruncate(300, item.name, searchQuery.split(' '))
33
+ : item.name
34
+ "
35
+ />
31
36
  <div
32
37
  v-if="item.summary"
33
38
  class="text-wrap clamped-text"
@@ -38,7 +43,7 @@
38
43
  ]
39
44
  "
40
45
  >
41
- <MDC :value="item.summary" />
46
+ <MDC :value="searchQuery.length ? highlightAndTruncate(500, item.summary, searchQuery.split(' ')) : item.summary" />
42
47
  </div>
43
48
  <v-btn
44
49
  class="mt-4"
@@ -77,8 +82,17 @@
77
82
 
78
83
  <script setup>
79
84
  import { useDisplay } from "vuetify"
85
+ import { computed, useRoute, useNuxtApp } from "#imports"
86
+ import { useRootStore } from "../../stores/root"
87
+ import { highlightAndTruncate } from "../../composables/useUtils"
80
88
 
81
89
  const { name } = useDisplay()
90
+ const rootStore = useRootStore()
91
+ const { $stores } = useNuxtApp()
92
+ const { name: routeName } = useRoute()
93
+ const searchQuery = computed(() =>
94
+ routeName.startsWith('search') ? rootStore.search : ($stores['publications'].search || '')
95
+ )
82
96
 
83
97
  const props = defineProps({
84
98
  item: {
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "license": "AGPL-3.0-only",
3
3
  "main": "./dist/module.mjs",
4
- "version": "1.0.185",
4
+ "version": "1.0.187",
5
5
  "name": "@paris-ias/list",
6
6
  "repository": {
7
7
  "url": "git+https://github.com/IEA-Paris/list.git",