@paris-ias/list 1.3.0 → 1.3.2
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/events/DenseItem.vue +14 -11
- package/dist/runtime/components/list/molecules/Filters.vue +37 -2
- package/dist/runtime/components/list/organisms/List.vue +4 -0
- package/dist/runtime/components/misc/molecules/DisciplinesTags.vue +12 -2
- package/dist/runtime/components/news/DenseItem.vue +6 -0
- package/dist/runtime/components/publications/DenseItem.vue +8 -2
- package/dist/runtime/stores/root.d.ts +1 -0
- package/dist/runtime/stores/root.js +7 -0
- package/package.json +2 -2
package/dist/module.json
CHANGED
|
@@ -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-
|
|
27
|
-
v-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
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"
|
|
@@ -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="
|
|
10
|
-
:class="
|
|
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
|
-
<
|
|
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 }) {
|
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.
|
|
4
|
+
"version": "1.3.2",
|
|
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.18"
|
|
12
12
|
},
|
|
13
13
|
"description": "Paris IAS List Module",
|
|
14
14
|
"peerDependencies": {
|