@paris-ias/list 1.3.0 → 1.3.3

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.3.0",
4
+ "version": "1.3.3",
5
5
  "builder": {
6
6
  "@nuxt/module-builder": "0.8.4",
7
7
  "unbuild": "2.0.0"
@@ -23,17 +23,20 @@
23
23
 
24
24
  <v-col align-self="start" class="pl-md-4">
25
25
  <v-skeleton-loader v-if="loading" type="chip" class="mr-3" width="120" />
26
- <v-chip
27
- v-else
28
- class="mr-3"
29
- color="black"
30
- size="small"
31
- style="background-color: white; color: black"
32
- tile
33
- variant="outlined"
34
- >
35
- {{ $t("list.filters.events.category." + item.category) }}
36
- </v-chip>
26
+ <span v-else>
27
+ <v-chip
28
+ class="mr-3"
29
+ color="black"
30
+ size="small"
31
+ style="background-color: white; color: black"
32
+ tile
33
+ variant="outlined"
34
+ >
35
+ {{ $t("list.filters.events.category." + item.category) }}
36
+ </v-chip>
37
+ <!-- DISCIPLINES -->
38
+ <MiscMoleculesDisciplinesTags :disciplines="item.disciplines" inline />
39
+ </span>
37
40
  <v-skeleton-loader
38
41
  v-if="loading && smAndDown"
39
42
  type="text"
@@ -51,8 +51,9 @@
51
51
  $t(
52
52
  'list.filters.fellowships.fellowshipType.' + item.fellowshipType,
53
53
  ),
54
- ...(props.item?.disciplines?.map((discipline) => discipline.name) ??
55
- []),
54
+ ...(props.item?.disciplines?.map((discipline) =>
55
+ $t('list.filters.disciplines.' + discipline),
56
+ ) ?? []),
56
57
  ]"
57
58
  />
58
59
  </template>
@@ -2,7 +2,7 @@
2
2
  <v-row>
3
3
  <template v-for="(filterItem, index) in Object.keys($stores[type].filters)">
4
4
  <v-col
5
- v-if="computeVisibility(filterItem)"
5
+ v-if="appAllowed(filterItem) && computeVisibility(filterItem)"
6
6
  :key="type + index + filterItem"
7
7
  cols="12"
8
8
  sm="6"
@@ -44,7 +44,7 @@
44
44
  <script setup>
45
45
  import { useDisplay } from "vuetify"
46
46
  import { capitalize } from "../../../composables/useUtils"
47
- import { useNuxtApp, resolveComponent, useI18n } from "#imports"
47
+ import { useNuxtApp, resolveComponent, useI18n, useAppConfig } from "#imports"
48
48
 
49
49
  const { smAndDown } = useDisplay()
50
50
  const i18n = useI18n()
@@ -52,6 +52,18 @@ const { locale, messages } = useI18n()
52
52
  const { $stores, $filters } = useNuxtApp()
53
53
  const props = defineProps(["type", "expanded"])
54
54
 
55
+ // Consuming app id. Filters may declare an `appId` array of the consumers
56
+ // allowed to render them; this is the id we match against. "all" is a wildcard.
57
+ const appId = useAppConfig().list?.appId ?? "iea"
58
+
59
+ // A filter is shown only on the consumers it opts into. No appId -> shown
60
+ // everywhere (backward compatible). "all" in the list matches any consumer.
61
+ const appAllowed = (filterKey) => {
62
+ const allowed = $stores[props.type].filters[filterKey].appId
63
+ if (!Array.isArray(allowed) || allowed.length === 0) return true
64
+ return allowed.includes("all") || allowed.includes(appId)
65
+ }
66
+
55
67
  const ComponentName = (name) => {
56
68
  return resolveComponent(
57
69
  "ListInputs" + capitalize($stores[props.type].filters[name].type),
@@ -76,6 +88,19 @@ const getItems = (name) => {
76
88
  .sort((a, b) => a.title.localeCompare(b.title))
77
89
  }
78
90
 
91
+ // Prefer the enum declared on the model filter (filters[name].items) over the
92
+ // values baked into filters.json. The model is the source of truth for enum
93
+ // options (e.g. publicationType), so filters.json never has to stay in sync.
94
+ const modelItems = $stores[props.type].filters[name].items
95
+ if (modelItems && typeof modelItems === "object") {
96
+ return Object.values(modelItems)
97
+ .filter((item) => item !== "label")
98
+ .map((item) => ({
99
+ title: i18n.t(`list.filters.${props.type}.${name}.${item}`),
100
+ value: item,
101
+ }))
102
+ }
103
+
79
104
  if ($filters?.[props.type]?.[name]) {
80
105
  return $filters[props.type][name]
81
106
  .filter((key) => key !== "label")
@@ -140,6 +165,16 @@ const computeVisibility = (filterKey) => {
140
165
 
141
166
  const checkRule = (rule) =>
142
167
  Object.entries(rule).every(([depKey, expected]) => {
168
+ // Reserved key: match against the active view modifier rather than a
169
+ // sibling filter's value. On routes like /resources/media the `type` is
170
+ // narrowed server-side via the modifier, so the modifier is what changes.
171
+ if (depKey === "modifier") {
172
+ const cur = $stores[props.type].modifier
173
+ return Array.isArray(expected)
174
+ ? expected.includes(cur)
175
+ : cur === expected
176
+ }
177
+
143
178
  const dep = filters?.[depKey]
144
179
  if (!dep) return false
145
180
  const cur = dep.value
@@ -117,6 +117,10 @@ const props = defineProps({
117
117
  /* console.log("start llocal loading from setup") */
118
118
  rootStore.setLoading(true, props.type)
119
119
 
120
+ // Mirror the route's fixed modifier into the store so the filter UI can branch
121
+ // its conditional visibility on it (the query still reads props.modifier directly).
122
+ rootStore.setModifier(props.type, props.modifier)
123
+
120
124
  // Initial route -> store (single source-of-truth on first load)
121
125
  rootStore.loadRouteQuery(props.type)
122
126
 
@@ -6,8 +6,11 @@
6
6
  />
7
7
  <div
8
8
  v-else-if="disciplines.length"
9
- class="d-flex flex-wrap"
10
- :class="justify === 'center' ? 'justify-center' : ''"
9
+ class="flex-wrap"
10
+ :class="[
11
+ inline ? 'd-inline-flex align-center' : 'd-flex',
12
+ justify === 'center' ? 'justify-center' : '',
13
+ ]"
11
14
  style="gap: 6px"
12
15
  >
13
16
  <v-chip
@@ -59,6 +62,13 @@ const props = defineProps({
59
62
  type: String,
60
63
  default: "start",
61
64
  },
65
+ // render as inline-flex so the chips flow on the same line as preceding
66
+ // content (e.g. the category chip in the events dense item) instead of
67
+ // forcing a block-level line break
68
+ inline: {
69
+ type: Boolean,
70
+ default: false,
71
+ },
62
72
  })
63
73
 
64
74
  // normalise to an array regardless of what the caller passes
@@ -41,6 +41,12 @@
41
41
  {{ $t(eventCategory) }}
42
42
  </v-chip>
43
43
  <MiscMoleculesChipContainer :items="item.tags || []" size="small" />
44
+ <MiscMoleculesDisciplinesTags
45
+ v-if="item.disciplines && item.disciplines.length"
46
+ :disciplines="item.disciplines"
47
+ inline
48
+ class="mt-2"
49
+ />
44
50
  </template>
45
51
  <div
46
52
  v-html="
@@ -46,11 +46,17 @@
46
46
  :items="item.tags || []"
47
47
  size="small"
48
48
  />
49
+ <MiscMoleculesDisciplinesTags
50
+ v-if="item.disciplines && item.disciplines.length"
51
+ :disciplines="item.disciplines"
52
+ inline
53
+ class="mt-2"
54
+ />
49
55
  </template>
50
56
  <v-skeleton-loader v-if="loading" type="heading" />
51
- <span
57
+ <div
52
58
  v-else
53
- class="text-h5 dense paragraph"
59
+ class="text-h5 dense paragraph mt-2"
54
60
  v-html="
55
61
  searchQuery.length
56
62
  ? highlightAndTruncate(300, item.name, searchQuery.split(' '))
@@ -37,6 +37,7 @@ export declare const useRootStore: import("pinia").StoreDefinition<"rootStore",
37
37
  loadRouteQuery(type: string): void;
38
38
  setFiltersCount(type: string): void;
39
39
  updateRouteQuery(type: string): void;
40
+ setModifier(type: string, modifier?: string): void;
40
41
  resetState(type: string, lang?: string): void;
41
42
  updateSort({ type, sortKey }: {
42
43
  type: string;
@@ -113,12 +113,19 @@ export const useRootStore = defineStore("rootStore", {
113
113
  };
114
114
  router.replace({ query: routeQuery });
115
115
  },
116
+ setModifier(type, modifier) {
117
+ const { $stores } = useNuxtApp();
118
+ if ($stores[type]) {
119
+ $stores[type].modifier = modifier;
120
+ }
121
+ },
116
122
  resetState(type, lang = "en") {
117
123
  const { $stores, $models } = useNuxtApp();
118
124
  const model = structuredClone($models[type]);
119
125
  $stores[type].filters = model?.filters;
120
126
  $stores[type].search = "";
121
127
  $stores[type].page = 1;
128
+ $stores[type].modifier = void 0;
122
129
  $stores[type].sortKey = null;
123
130
  },
124
131
  updateSort({ type, sortKey }) {
@@ -133,19 +133,18 @@
133
133
  "paris-1": "Paris 1",
134
134
  "paris-2": "Paris 2"
135
135
  },
136
+ "status": {
137
+ "label": "Status",
138
+ "PLANNED": "Upcoming",
139
+ "ONGOING": "Ongoing",
140
+ "FINISHED": "Finished"
141
+ },
136
142
  "fellowshipType": {
137
143
  "label": "Type of fellowship",
138
144
  "IN_GROUP": "Collective",
139
145
  "LONG_STAY": "Long stay",
140
146
  "SHORT_STAY": "Short stay"
141
147
  },
142
- "status": {
143
- "label": "Status",
144
- "CANCELLED": "Cancelled",
145
- "FINISHED": "Finished",
146
- "ONGOING": "Ongoing",
147
- "PLANNED": "Upcoming"
148
- },
149
148
  "disciplines": {
150
149
  "label": "Disciplines"
151
150
  }
@@ -113,7 +113,7 @@
113
113
  "status": {
114
114
  "label": "Statut",
115
115
  "CANCELLED": "Annulé",
116
- "DONE": "terminé",
116
+ "DONE": "Terminé",
117
117
  "POSTPONED": "Reporté",
118
118
  "PUBLISHED": "Publié",
119
119
  "RESCHEDULED": "Reprogrammé"
@@ -133,10 +133,9 @@
133
133
  },
134
134
  "status": {
135
135
  "label": "Statut",
136
- "CANCELLED": "Annulé",
137
- "PAST": "Terminé",
138
- "IN_PROGRESS": "En cours",
139
- "UPCOMING": "À venir"
136
+ "PLANNED": "À venir",
137
+ "ONGOING": "En cours",
138
+ "FINISHED": "Terminé"
140
139
  },
141
140
  "disciplines": {
142
141
  "label": "Discipline"
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.3.0",
4
+ "version": "1.3.3",
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.17"
11
+ "@paris-ias/trees": "^2.2.18"
12
12
  },
13
13
  "description": "Paris IAS List Module",
14
14
  "peerDependencies": {