@paris-ias/list 1.1.18 → 1.1.20
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/README.md +41 -7
- package/dist/module.json +1 -1
- package/dist/runtime/components/fellowships/DenseItem.vue +3 -2
- package/dist/runtime/components/list/atoms/SortMenu.vue +8 -4
- package/dist/runtime/components/list/molecules/Filters.vue +14 -5
- package/dist/runtime/components/news/View.vue +5 -2
- package/dist/runtime/components/people/Badges.vue +47 -4
- package/dist/runtime/components/people/DenseItem.vue +151 -40
- package/dist/runtime/components/people/View.vue +89 -6
- package/dist/runtime/translations/en.json +2 -0
- package/dist/runtime/translations/fr.json +5 -2
- package/package.json +2 -2
package/README.md
CHANGED
|
@@ -17,12 +17,13 @@ Find and replace all on all files (CMD+SHIFT+F):
|
|
|
17
17
|
My new Nuxt module for doing amazing things.
|
|
18
18
|
|
|
19
19
|
- [✨ Release Notes](/CHANGELOG.md)
|
|
20
|
-
<!-- - [🏀 Online playground](https://stackblitz.com/github/your-org/my-module?file=playground%2Fapp.vue) -->
|
|
21
|
-
<!-- - [📖 Documentation](https://example.com) -->
|
|
20
|
+
<!-- - [🏀 Online playground](https://stackblitz.com/github/your-org/my-module?file=playground%2Fapp.vue) -->
|
|
21
|
+
<!-- - [📖 Documentation](https://example.com) -->
|
|
22
22
|
|
|
23
23
|
## Features
|
|
24
24
|
|
|
25
25
|
<!-- Highlight some of the features your module provide here -->
|
|
26
|
+
|
|
26
27
|
- ⛰ Foo
|
|
27
28
|
- 🚠 Bar
|
|
28
29
|
- 🌲 Baz
|
|
@@ -37,7 +38,6 @@ npx nuxi module add my-module
|
|
|
37
38
|
|
|
38
39
|
That's it! You can now use My Module in your Nuxt app ✨
|
|
39
40
|
|
|
40
|
-
|
|
41
41
|
## Contribution
|
|
42
42
|
|
|
43
43
|
<details>
|
|
@@ -69,16 +69,50 @@ That's it! You can now use My Module in your Nuxt app ✨
|
|
|
69
69
|
|
|
70
70
|
</details>
|
|
71
71
|
|
|
72
|
-
|
|
73
72
|
<!-- Badges -->
|
|
73
|
+
|
|
74
74
|
[npm-version-src]: https://img.shields.io/npm/v/my-module/latest.svg?style=flat&colorA=020420&colorB=00DC82
|
|
75
75
|
[npm-version-href]: https://npmjs.com/package/my-module
|
|
76
|
-
|
|
77
76
|
[npm-downloads-src]: https://img.shields.io/npm/dm/my-module.svg?style=flat&colorA=020420&colorB=00DC82
|
|
78
77
|
[npm-downloads-href]: https://npm.chart.dev/my-module
|
|
79
|
-
|
|
80
78
|
[license-src]: https://img.shields.io/npm/l/my-module.svg?style=flat&colorA=020420&colorB=00DC82
|
|
81
79
|
[license-href]: https://npmjs.com/package/my-module
|
|
82
|
-
|
|
83
80
|
[nuxt-src]: https://img.shields.io/badge/Nuxt-020420?logo=nuxt.js
|
|
84
81
|
[nuxt-href]: https://nuxt.com
|
|
82
|
+
|
|
83
|
+
| archeology | ARCHAEOLOGY |
|
|
84
|
+
| ------------------------------------------- | -------------------------------------- |
|
|
85
|
+
| architecture-and-spatial-planning | ARCHITECTURE_AND_URBAN_PLANNING |
|
|
86
|
+
| art-and-art-history | ART_AND_HISTORY_OF_ART |
|
|
87
|
+
| | BIOLOGY |
|
|
88
|
+
| | CHEMISTRY |
|
|
89
|
+
| classical-studies | CLASSICAL_STUDIES |
|
|
90
|
+
| | COMPUTER_SCIENCE |
|
|
91
|
+
| | DEMOGRAPHY |
|
|
92
|
+
| digital-humanities | DIGITAL_HUMANITIES |
|
|
93
|
+
| economics-and-finance | ECONOMICS |
|
|
94
|
+
| education | EDUCATION_SCIENCES |
|
|
95
|
+
| environment | ENVIRONMENTAL_SCIENCES |
|
|
96
|
+
| gender-studies | SOCIOLOGY |
|
|
97
|
+
| geography | GEOGRAPHY |
|
|
98
|
+
| history-philosophy-and-sociology-of-science | STUDIES_IN_SCIENCE_AND_TECHNOLOGY |
|
|
99
|
+
| history | HISTORY |
|
|
100
|
+
| information-and-communication-studies | INFORMATION_AND_COMMUNICATION_SCIENCES |
|
|
101
|
+
| | INTERNATIONAL_RELATIONS |
|
|
102
|
+
| law | LAW |
|
|
103
|
+
| linguistics | LINGUISTICS |
|
|
104
|
+
| literature | LITERATURE |
|
|
105
|
+
| | MANAGEMENT_AND_PUBLIC_ADMINISTRATION |
|
|
106
|
+
| medicine-pharmacy | MEDICINE |
|
|
107
|
+
| music-musicology-and-performance-arts | ART_AND_HISTORY_OF_ART |
|
|
108
|
+
| neuroscience | NEUROSCIENCES_AND_COGNITIVE_SCIENCES |
|
|
109
|
+
| others | OTHERS |
|
|
110
|
+
| philosophy | PHILOSOPHY |
|
|
111
|
+
| physics-and-mathematics | PHYSICS_MATHEMATICS_AND_ENGINEERING |
|
|
112
|
+
| political-science | POLITICAL_SCIENCE |
|
|
113
|
+
| psychology | PSYCHOLOGY |
|
|
114
|
+
| | STUDIES_IN_SCIENCE_AND_TECHNOLOGY |
|
|
115
|
+
| | THEOLOGY |
|
|
116
|
+
| sciences-of-the-universe | PHYSICS_MATHEMATICS_AND_ENGINEERING |
|
|
117
|
+
| social-anthropology-and-ethnology | ANTHROPOLOGY_AND_ETHNOLOGY |
|
|
118
|
+
| sociology | SOCIOLOGY |
|
package/dist/module.json
CHANGED
|
@@ -64,8 +64,9 @@
|
|
|
64
64
|
v-else
|
|
65
65
|
:items="[
|
|
66
66
|
$t('list.filters.fellowships.fellowshipType.' + item.fellowshipType),
|
|
67
|
-
...(props.item?.disciplines
|
|
68
|
-
|
|
67
|
+
...(props.item?.disciplines.map((discipline) =>
|
|
68
|
+
$t('list.filters.disciplines.' + discipline),
|
|
69
|
+
) ?? []),
|
|
69
70
|
]"
|
|
70
71
|
class="mt-2"
|
|
71
72
|
/>
|
|
@@ -14,9 +14,13 @@
|
|
|
14
14
|
width="60"
|
|
15
15
|
height="60"
|
|
16
16
|
/>
|
|
17
|
-
<v-btn
|
|
18
|
-
|
|
19
|
-
|
|
17
|
+
<v-btn
|
|
18
|
+
v-else
|
|
19
|
+
x-large
|
|
20
|
+
tile
|
|
21
|
+
flat
|
|
22
|
+
:icon="'mdi-' + $stores[type].sort[currentSort]?.icon"
|
|
23
|
+
/>
|
|
20
24
|
</span>
|
|
21
25
|
</template>
|
|
22
26
|
|
|
@@ -29,7 +33,7 @@
|
|
|
29
33
|
</v-tooltip>
|
|
30
34
|
</template>
|
|
31
35
|
|
|
32
|
-
|
|
36
|
+
<v-list density="compact">
|
|
33
37
|
<v-list-item
|
|
34
38
|
v-for="(item, key) in $stores[type].sort"
|
|
35
39
|
:key="key"
|
|
@@ -127,9 +127,13 @@ const computeVisibility = (filterKey) => {
|
|
|
127
127
|
const filters = $stores[props.type].filters
|
|
128
128
|
const show = filters?.[filterKey]?.show
|
|
129
129
|
|
|
130
|
+
// No show config -> always visible.
|
|
130
131
|
if (!show) return true
|
|
131
|
-
|
|
132
|
-
|
|
132
|
+
|
|
133
|
+
const def = show.default === true
|
|
134
|
+
|
|
135
|
+
// Without switch rules, visibility is simply the default.
|
|
136
|
+
if (!Array.isArray(show.switchIf) || show.switchIf.length === 0) return def
|
|
133
137
|
|
|
134
138
|
const checkRule = (rule) =>
|
|
135
139
|
Object.entries(rule).every(([depKey, expected]) => {
|
|
@@ -146,9 +150,14 @@ const computeVisibility = (filterKey) => {
|
|
|
146
150
|
return Array.isArray(expected) ? expected.includes(cur) : cur === expected
|
|
147
151
|
})
|
|
148
152
|
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
153
|
+
// disjonctive (default true) -> any rule matches; otherwise all rules match.
|
|
154
|
+
const matched =
|
|
155
|
+
show.disjonctive !== false
|
|
156
|
+
? show.switchIf.some(checkRule)
|
|
157
|
+
: show.switchIf.every(checkRule)
|
|
158
|
+
|
|
159
|
+
// A matched switch flips the default; otherwise keep the default.
|
|
160
|
+
return matched ? !def : def
|
|
152
161
|
}
|
|
153
162
|
</script>
|
|
154
163
|
|
|
@@ -122,7 +122,10 @@
|
|
|
122
122
|
]
|
|
123
123
|
"
|
|
124
124
|
/>
|
|
125
|
-
<div
|
|
125
|
+
<div
|
|
126
|
+
v-if="item && item.description"
|
|
127
|
+
class="mt-md-n2 mx-4 mx-sm-8 mx-md-0"
|
|
128
|
+
>
|
|
126
129
|
<MDC :value="item.description" />
|
|
127
130
|
</div>
|
|
128
131
|
</v-col>
|
|
@@ -181,5 +184,5 @@ const today = new Date()
|
|
|
181
184
|
)
|
|
182
185
|
|
|
183
186
|
) */
|
|
184
|
-
$stores.
|
|
187
|
+
$stores.publications.loading = false
|
|
185
188
|
</script>
|
|
@@ -7,16 +7,20 @@
|
|
|
7
7
|
<template v-else-if="item.groups">
|
|
8
8
|
<template v-for="(value, key, index) in item.groups" :key="key + index">
|
|
9
9
|
<template v-if="value && key === 'vintage'">
|
|
10
|
+
<!-- One pill per calendar year a vintage spans: an entry running
|
|
11
|
+
from start to stop across a year boundary yields a pill for
|
|
12
|
+
each year (e.g. 2025, 2026). Entries without dates fall back
|
|
13
|
+
to their single `year`. -->
|
|
10
14
|
<v-chip
|
|
11
|
-
v-for="
|
|
12
|
-
:key="
|
|
15
|
+
v-for="year in vintageYears"
|
|
16
|
+
:key="year"
|
|
13
17
|
size="small"
|
|
14
18
|
class="mt-3 mr-1 mr-md-3"
|
|
15
19
|
variant="outlined"
|
|
16
20
|
tile
|
|
17
21
|
style="background-color: white; color: black"
|
|
18
22
|
>
|
|
19
|
-
{{ $t("vintage", [
|
|
23
|
+
{{ $t("vintage", [year]) }}
|
|
20
24
|
</v-chip>
|
|
21
25
|
</template>
|
|
22
26
|
|
|
@@ -59,7 +63,7 @@
|
|
|
59
63
|
|
|
60
64
|
<script setup>
|
|
61
65
|
import { useRootStore } from "../../stores/root"
|
|
62
|
-
import { useNuxtApp } from "#imports"
|
|
66
|
+
import { computed, useNuxtApp } from "#imports"
|
|
63
67
|
import { useDisplay } from "vuetify"
|
|
64
68
|
const { mdAndUp } = useDisplay()
|
|
65
69
|
const rootStore = useRootStore()
|
|
@@ -71,6 +75,45 @@ const props = defineProps({
|
|
|
71
75
|
default: () => ({}),
|
|
72
76
|
},
|
|
73
77
|
})
|
|
78
|
+
|
|
79
|
+
// Expand a start→stop span into the list of calendar years it covers.
|
|
80
|
+
// e.g. 2024-09-01 → 2025-07-30 yields [2024, 2025]. Returns [] if either
|
|
81
|
+
// bound is missing or unparseable.
|
|
82
|
+
const spanYears = (start, stop) => {
|
|
83
|
+
if (!start || !stop) return []
|
|
84
|
+
const from = new Date(start).getFullYear()
|
|
85
|
+
const to = new Date(stop).getFullYear()
|
|
86
|
+
if (Number.isNaN(from) || Number.isNaN(to) || to < from) return []
|
|
87
|
+
const years = []
|
|
88
|
+
for (let y = from; y <= to; y++) years.push(y)
|
|
89
|
+
return years
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
// Years to render as vintage pills, deduped and sorted ascending.
|
|
93
|
+
//
|
|
94
|
+
// `groups.vintage[]` is the authoritative list of fellowships (a fellow may
|
|
95
|
+
// have several) but is queried without start/stop, so it only contributes its
|
|
96
|
+
// single `year`. `latest` carries the actual start/stop, so its span is
|
|
97
|
+
// expanded into one year per calendar year — this is what turns a cross-year
|
|
98
|
+
// fellowship into two pills (e.g. 2025, 2026).
|
|
99
|
+
const vintageYears = computed(() => {
|
|
100
|
+
const years = new Set()
|
|
101
|
+
|
|
102
|
+
const latest = props.item?.latest
|
|
103
|
+
// latest is the Vintage|Position union; only a Vintage carries a fellowship
|
|
104
|
+
// span. Position (staff) has start/stop too but no `year`, so guard on year.
|
|
105
|
+
if (latest?.year != null) {
|
|
106
|
+
const span = spanYears(latest.start, latest.stop)
|
|
107
|
+
if (span.length) span.forEach((y) => years.add(y))
|
|
108
|
+
else years.add(latest.year)
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
for (const v of props.item?.groups?.vintage ?? []) {
|
|
112
|
+
if (v?.year != null) years.add(v.year)
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
return [...years].sort((a, b) => a - b)
|
|
116
|
+
})
|
|
74
117
|
</script>
|
|
75
118
|
|
|
76
119
|
<style lang="scss" scoped></style>
|
|
@@ -17,53 +17,92 @@
|
|
|
17
17
|
/>
|
|
18
18
|
</v-col>
|
|
19
19
|
<v-col align-self="start" class="dense ml-md-4">
|
|
20
|
+
<!-- NAME ROW -->
|
|
20
21
|
<v-skeleton-loader v-if="loading" type="heading" />
|
|
21
22
|
<div
|
|
22
23
|
v-else
|
|
23
24
|
class="d-flex justify-space-between text-title text-h5 align-center pt-md-2"
|
|
24
25
|
>
|
|
25
|
-
<
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
26
|
+
<div class="d-flex align-center flex-wrap" style="gap: 8px">
|
|
27
|
+
<span
|
|
28
|
+
v-html="
|
|
29
|
+
searchQuery.length
|
|
30
|
+
? highlightAndTruncate(
|
|
31
|
+
300,
|
|
32
|
+
item.firstname + ' ' + item.lastname,
|
|
33
|
+
searchQuery.split(' '),
|
|
34
|
+
)
|
|
35
|
+
: item.firstname + ' ' + item.lastname
|
|
36
|
+
"
|
|
37
|
+
/>
|
|
38
|
+
<span v-if="isPresent" class="present-pill">
|
|
39
|
+
<span class="present-dot" />
|
|
40
|
+
{{ $t("present") }}
|
|
41
|
+
</span>
|
|
42
|
+
</div>
|
|
43
|
+
|
|
36
44
|
<v-spacer />
|
|
37
|
-
|
|
45
|
+
|
|
46
|
+
<!-- year badges: one pill per calendar year the fellowship spans, so a
|
|
47
|
+
cross-year vintage (start→stop over a boundary) shows e.g. 2025 and
|
|
48
|
+
2026. Falls back to the single `year` when dates are absent. -->
|
|
49
|
+
<div class="d-flex flex-wrap justify-end" style="gap: 4px">
|
|
50
|
+
<v-chip
|
|
51
|
+
v-for="year in vintageYears"
|
|
52
|
+
:key="year"
|
|
53
|
+
size="small"
|
|
54
|
+
variant="outlined"
|
|
55
|
+
tile
|
|
56
|
+
style="background-color: white; color: black"
|
|
57
|
+
>
|
|
58
|
+
{{ $t("vintage", [year]) }}
|
|
59
|
+
</v-chip>
|
|
60
|
+
</div>
|
|
38
61
|
</div>
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
v-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
300,
|
|
46
|
-
item.groups.vintage[0].theme,
|
|
47
|
-
searchQuery.split(' '),
|
|
48
|
-
)
|
|
49
|
-
: item.groups.vintage[0].theme
|
|
50
|
-
"
|
|
62
|
+
|
|
63
|
+
<!-- DISCIPLINES (translated, capped at 3 + overflow) -->
|
|
64
|
+
<v-skeleton-loader
|
|
65
|
+
v-if="loading"
|
|
66
|
+
type="chip@3"
|
|
67
|
+
class="discipline-skeleton mt-2"
|
|
51
68
|
/>
|
|
52
|
-
<
|
|
69
|
+
<div
|
|
70
|
+
v-else-if="disciplines.length"
|
|
71
|
+
class="d-flex flex-wrap mt-2"
|
|
72
|
+
style="gap: 6px"
|
|
73
|
+
>
|
|
74
|
+
<v-chip
|
|
75
|
+
v-for="(d, i) in shownDisciplines"
|
|
76
|
+
:key="d + i"
|
|
77
|
+
size="small"
|
|
78
|
+
tile
|
|
79
|
+
variant="flat"
|
|
80
|
+
color="grey-lighten-3"
|
|
81
|
+
class="discipline-chip"
|
|
82
|
+
>
|
|
83
|
+
{{ disciplineLabel(d) }}
|
|
84
|
+
</v-chip>
|
|
85
|
+
<v-chip
|
|
86
|
+
v-if="extraDisciplines"
|
|
87
|
+
size="small"
|
|
88
|
+
variant="flat"
|
|
89
|
+
color="grey-lighten-3"
|
|
90
|
+
class="discipline-chip"
|
|
91
|
+
>
|
|
92
|
+
+{{ extraDisciplines }}
|
|
93
|
+
</v-chip>
|
|
94
|
+
</div>
|
|
53
95
|
|
|
96
|
+
<!-- THEME (Vintage only; non-fellows show nothing) -->
|
|
97
|
+
<v-skeleton-loader v-if="loading" type="text" />
|
|
54
98
|
<div
|
|
55
|
-
v-else-if="
|
|
56
|
-
class="
|
|
57
|
-
:style="
|
|
58
|
-
'-webkit-line-clamp:' +
|
|
59
|
-
[1, 1, 1, 2, 3, 3][
|
|
60
|
-
['xs', 'sm', 'md', 'lg', 'xl', 'xxl'].indexOf(displayName || 'md')
|
|
61
|
-
]
|
|
62
|
-
"
|
|
99
|
+
v-else-if="latestTheme"
|
|
100
|
+
class="theme-label mt-2"
|
|
101
|
+
:style="'-webkit-line-clamp:' + themeLineClamp"
|
|
63
102
|
v-html="
|
|
64
103
|
searchQuery.length
|
|
65
|
-
? highlightAndTruncate(
|
|
66
|
-
:
|
|
104
|
+
? highlightAndTruncate(300, latestTheme, searchQuery.split(' '))
|
|
105
|
+
: latestTheme
|
|
67
106
|
"
|
|
68
107
|
/>
|
|
69
108
|
</v-col>
|
|
@@ -73,16 +112,19 @@
|
|
|
73
112
|
<script setup>
|
|
74
113
|
import { useRootStore } from "../../stores/root"
|
|
75
114
|
import { highlightAndTruncate } from "../../composables/useUtils"
|
|
76
|
-
import { computed, useRoute, useNuxtApp } from "#imports"
|
|
115
|
+
import { computed, useRoute, useNuxtApp, useI18n } from "#imports"
|
|
77
116
|
import { useDisplay } from "vuetify"
|
|
78
|
-
const { name } = useRoute()
|
|
79
117
|
|
|
118
|
+
const { name } = useRoute()
|
|
80
119
|
const { mdAndUp, name: displayName } = useDisplay()
|
|
120
|
+
const { t } = useI18n()
|
|
81
121
|
const rootStore = useRootStore()
|
|
82
122
|
const { $stores } = useNuxtApp()
|
|
123
|
+
|
|
83
124
|
const searchQuery = computed(() =>
|
|
84
125
|
name.startsWith("search") ? rootStore.search : $stores["people"].search || "",
|
|
85
126
|
)
|
|
127
|
+
|
|
86
128
|
const props = defineProps({
|
|
87
129
|
item: {
|
|
88
130
|
type: Object,
|
|
@@ -92,14 +134,83 @@ const props = defineProps({
|
|
|
92
134
|
type: Number,
|
|
93
135
|
required: true,
|
|
94
136
|
},
|
|
95
|
-
|
|
96
137
|
loading: {
|
|
97
138
|
type: Boolean,
|
|
98
139
|
required: false,
|
|
99
140
|
default: false,
|
|
100
141
|
},
|
|
101
142
|
})
|
|
143
|
+
|
|
144
|
+
// latest union (field-presence based; __typename not selected in the query)
|
|
145
|
+
const latest = computed(() => props.item?.latest ?? null)
|
|
146
|
+
const latestTheme = computed(() => latest.value?.theme ?? null) // Vintage only
|
|
147
|
+
|
|
148
|
+
// Expand a start→stop span into the calendar years it covers, e.g.
|
|
149
|
+
// 2024-09-01 → 2025-07-30 yields [2024, 2025]. [] if a bound is missing/invalid.
|
|
150
|
+
const spanYears = (start, stop) => {
|
|
151
|
+
if (!start || !stop) return []
|
|
152
|
+
const from = new Date(start).getFullYear()
|
|
153
|
+
const to = new Date(stop).getFullYear()
|
|
154
|
+
if (Number.isNaN(from) || Number.isNaN(to) || to < from) return []
|
|
155
|
+
const years = []
|
|
156
|
+
for (let y = from; y <= to; y++) years.push(y)
|
|
157
|
+
return years
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
// Vintage year pills (deduped, ascending). Mirrors PeopleBadges: `latest`
|
|
161
|
+
// carries start/stop so its span expands to one year per calendar year (a
|
|
162
|
+
// cross-year fellowship → two pills); `groups.vintage[]` is queried without
|
|
163
|
+
// dates so each only contributes its single `year`. Falls back to `year` when
|
|
164
|
+
// no span is available.
|
|
165
|
+
const vintageYears = computed(() => {
|
|
166
|
+
const years = new Set()
|
|
167
|
+
|
|
168
|
+
const l = latest.value
|
|
169
|
+
if (l?.year != null) {
|
|
170
|
+
const span = spanYears(l.start, l.stop)
|
|
171
|
+
if (span.length) span.forEach((y) => years.add(y))
|
|
172
|
+
else years.add(l.year)
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
for (const v of props.item?.groups?.vintage ?? []) {
|
|
176
|
+
if (v?.year != null) years.add(v.year)
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
return [...years].sort((a, b) => a - b)
|
|
180
|
+
})
|
|
181
|
+
|
|
182
|
+
// "present" = currently in residence/post: started on or before today and not
|
|
183
|
+
// yet ended. Works for both Vintage (fellowship period) and Position (staff role)
|
|
184
|
+
// once latest.start/latest.stop are fetched. Degrades to false when absent.
|
|
185
|
+
const isPresent = computed(() => {
|
|
186
|
+
const l = latest.value
|
|
187
|
+
if (!l?.start) return false
|
|
188
|
+
const now = Date.now()
|
|
189
|
+
if (new Date(l.start).getTime() > now) return false
|
|
190
|
+
if (!l.stop) return true
|
|
191
|
+
return new Date(l.stop).getTime() >= now
|
|
192
|
+
})
|
|
193
|
+
|
|
194
|
+
// disciplines: scalar enum array → translated labels, capped at 3
|
|
195
|
+
const MAX_DISCIPLINES = 3
|
|
196
|
+
const disciplines = computed(() =>
|
|
197
|
+
Array.isArray(props.item?.disciplines) ? props.item.disciplines : [],
|
|
198
|
+
)
|
|
199
|
+
const shownDisciplines = computed(() =>
|
|
200
|
+
disciplines.value.slice(0, MAX_DISCIPLINES),
|
|
201
|
+
)
|
|
202
|
+
const extraDisciplines = computed(() =>
|
|
203
|
+
Math.max(0, disciplines.value.length - MAX_DISCIPLINES),
|
|
204
|
+
)
|
|
205
|
+
const disciplineLabel = (d) => t("list.filters.disciplines." + d)
|
|
206
|
+
|
|
207
|
+
// responsive line-clamp for the theme (mirrors ExpandableDenseItem)
|
|
208
|
+
const breakpointIndex = computed(() =>
|
|
209
|
+
["xs", "sm", "md", "lg", "xl", "xxl"].indexOf(displayName.value || "md"),
|
|
210
|
+
)
|
|
211
|
+
const themeLineClamp = computed(() => [1, 1, 2, 2, 3, 3][breakpointIndex.value])
|
|
102
212
|
</script>
|
|
103
|
-
|
|
104
|
-
|
|
213
|
+
|
|
214
|
+
<style scoped>
|
|
215
|
+
.theme-label{display:-webkit-box;font-size:.875rem;font-style:italic;line-height:1.4;max-width:83ch;opacity:.7;-webkit-box-orient:vertical;overflow:hidden}.discipline-chip{border-radius:6px;color:rgba(0,0,0,.78);font-weight:500}.discipline-skeleton :deep(.v-skeleton-loader__chip){margin:0 6px 0 0}.present-pill{align-items:center;background-color:rgba(0,0,0,.06);border-radius:999px;color:rgba(0,0,0,.7);display:inline-flex;font-size:.625rem;font-weight:500;gap:5px;letter-spacing:.04em;line-height:1;padding:3px 8px;text-transform:uppercase}.present-dot{background-color:#4caf50;border-radius:50%;height:6px;width:6px}
|
|
105
216
|
</style>
|
|
@@ -10,10 +10,12 @@
|
|
|
10
10
|
>
|
|
11
11
|
<!-- PEOPLE IMAGE -->
|
|
12
12
|
<MiscAtomsImageContainer
|
|
13
|
-
v-if="mdAndUp
|
|
13
|
+
v-if="mdAndUp"
|
|
14
14
|
cover
|
|
15
15
|
:loading="loading"
|
|
16
|
-
:src="
|
|
16
|
+
:src="
|
|
17
|
+
item && item.image && item.image.url ? item.image : '/default.png'
|
|
18
|
+
"
|
|
17
19
|
:ratio="1 / 1"
|
|
18
20
|
:width="
|
|
19
21
|
[200, 250, 250, 300][
|
|
@@ -47,11 +49,19 @@
|
|
|
47
49
|
<template v-else>
|
|
48
50
|
<div
|
|
49
51
|
v-if="item && item.firstname && item.lastname"
|
|
50
|
-
class="
|
|
52
|
+
class="mt-8 mb-2 text-h3 align-self-center text-wrap"
|
|
51
53
|
>
|
|
52
54
|
{{ item.firstname + " " + item.lastname
|
|
53
55
|
}}<!-- TODO : call a composable to format people names (multiple, initials, capped & al. ) -->
|
|
54
56
|
</div>
|
|
57
|
+
<!-- PRESENT -->
|
|
58
|
+
<div v-if="isPresent" class="mb-6 d-flex justify-center">
|
|
59
|
+
<span class="present-pill">
|
|
60
|
+
<span class="present-dot" />
|
|
61
|
+
{{ $t("present") }}
|
|
62
|
+
</span>
|
|
63
|
+
</div>
|
|
64
|
+
<div v-else class="mb-6" />
|
|
55
65
|
<!-- SOCIALS -->
|
|
56
66
|
<div class="text-center">
|
|
57
67
|
<MiscAtomsSocials
|
|
@@ -63,6 +73,41 @@
|
|
|
63
73
|
<div class="mt-6 align-self-center">
|
|
64
74
|
<PeopleBadges v-if="item && item.groups" :item="item" />
|
|
65
75
|
</div>
|
|
76
|
+
|
|
77
|
+
<!-- FELLOWSHIP -->
|
|
78
|
+
<div v-if="fellowName || fellowTheme || fellowDates" class="mt-6">
|
|
79
|
+
<div v-if="fellowName" class="text-h6 font-weight-regular">
|
|
80
|
+
{{ fellowName }}
|
|
81
|
+
</div>
|
|
82
|
+
|
|
83
|
+
<!-- DISCIPLINES -->
|
|
84
|
+
<div
|
|
85
|
+
v-if="disciplines.length"
|
|
86
|
+
class="mt-4 d-flex flex-wrap justify-center"
|
|
87
|
+
style="gap: 6px"
|
|
88
|
+
>
|
|
89
|
+
<v-chip
|
|
90
|
+
v-for="(d, i) in disciplines"
|
|
91
|
+
:key="d + i"
|
|
92
|
+
size="small"
|
|
93
|
+
variant="flat"
|
|
94
|
+
tile
|
|
95
|
+
color="grey-lighten-3"
|
|
96
|
+
class="discipline-chip"
|
|
97
|
+
>
|
|
98
|
+
{{ disciplineLabel(d) }}
|
|
99
|
+
</v-chip>
|
|
100
|
+
</div>
|
|
101
|
+
<div v-if="fellowTheme" class="text-body-1 mt-1 font-italic">
|
|
102
|
+
{{ fellowTheme }}
|
|
103
|
+
</div>
|
|
104
|
+
<div
|
|
105
|
+
v-if="fellowDates"
|
|
106
|
+
class="text-body-2 mt-1 text-medium-emphasis"
|
|
107
|
+
>
|
|
108
|
+
{{ fellowDates }}
|
|
109
|
+
</div>
|
|
110
|
+
</div>
|
|
66
111
|
</template>
|
|
67
112
|
</v-col>
|
|
68
113
|
</v-row>
|
|
@@ -165,9 +210,10 @@
|
|
|
165
210
|
|
|
166
211
|
<script setup>
|
|
167
212
|
import { useDisplay } from "vuetify"
|
|
168
|
-
import { useNuxtApp, useI18n } from "#imports"
|
|
213
|
+
import { computed, useNuxtApp, useI18n } from "#imports"
|
|
214
|
+
import { formatDate } from "../../composables/useUtils"
|
|
169
215
|
|
|
170
|
-
const { locale } = useI18n()
|
|
216
|
+
const { t, locale } = useI18n()
|
|
171
217
|
const { $stores } = useNuxtApp()
|
|
172
218
|
const { name, mdAndUp } = useDisplay()
|
|
173
219
|
const props = defineProps({
|
|
@@ -176,5 +222,42 @@ const props = defineProps({
|
|
|
176
222
|
})
|
|
177
223
|
$stores.people.loading = false
|
|
178
224
|
|
|
179
|
-
|
|
225
|
+
// disciplines: scalar enum array → translated labels
|
|
226
|
+
const disciplines = computed(() =>
|
|
227
|
+
Array.isArray(props.item?.disciplines) ? props.item.disciplines : [],
|
|
228
|
+
)
|
|
229
|
+
const disciplineLabel = (d) => t("list.filters.disciplines." + d)
|
|
230
|
+
|
|
231
|
+
// latest is a union Vintage | Position; the fellowship program lives on the
|
|
232
|
+
// Vintage branch (name = program name, theme = fellowship theme) and carries the
|
|
233
|
+
// arrival/departure dates (start/stop). Guard by field presence.
|
|
234
|
+
const latest = computed(() => props.item?.latest ?? null)
|
|
235
|
+
const fellowName = computed(() => latest.value?.name ?? null)
|
|
236
|
+
const fellowTheme = computed(() => latest.value?.theme ?? null)
|
|
237
|
+
|
|
238
|
+
// "present" = currently in residence/post: started on or before today and not
|
|
239
|
+
// yet ended. Works for both Vintage and Position; degrades to false if start/stop
|
|
240
|
+
// are absent (e.g. before the trees query change is published).
|
|
241
|
+
const isPresent = computed(() => {
|
|
242
|
+
const l = latest.value
|
|
243
|
+
if (!l?.start) return false
|
|
244
|
+
const now = Date.now()
|
|
245
|
+
if (new Date(l.start).getTime() > now) return false
|
|
246
|
+
if (!l.stop) return true
|
|
247
|
+
return new Date(l.stop).getTime() >= now
|
|
248
|
+
})
|
|
249
|
+
|
|
250
|
+
// Arrival/departure dates straight from latest.start/latest.stop.
|
|
251
|
+
const fellowDates = computed(() => {
|
|
252
|
+
const l = latest.value
|
|
253
|
+
if (!l?.start) return null
|
|
254
|
+
return t("from {0} to {1}", [
|
|
255
|
+
formatDate(l.start, locale.value),
|
|
256
|
+
(l.stop && formatDate(l.stop, locale.value)) || t("present"),
|
|
257
|
+
])
|
|
258
|
+
})
|
|
180
259
|
</script>
|
|
260
|
+
|
|
261
|
+
<style scoped>
|
|
262
|
+
.discipline-chip{border-radius:6px;color:rgba(0,0,0,.78);font-weight:500}.present-pill{align-items:center;background-color:rgba(0,0,0,.06);border-radius:999px;color:rgba(0,0,0,.7);display:inline-flex;font-size:.6875rem;font-weight:500;gap:5px;letter-spacing:.04em;line-height:1;padding:4px 10px;text-transform:uppercase}.present-dot{background-color:#4caf50;border-radius:50%;height:6px;width:6px}
|
|
263
|
+
</style>
|
|
@@ -286,6 +286,7 @@
|
|
|
286
286
|
},
|
|
287
287
|
"disciplines": {
|
|
288
288
|
"label": "Discipline",
|
|
289
|
+
"ALL": "All disciplines",
|
|
289
290
|
"ANTHROPOLOGY_AND_ETHNOLOGY": "Anthropology and Ethnology",
|
|
290
291
|
"ARCHITECTURE_AND_URBAN_PLANNING": "Architecture and Urban Planning",
|
|
291
292
|
"ARCHAEOLOGY": "Archaeology",
|
|
@@ -311,6 +312,7 @@
|
|
|
311
312
|
"SOCIOLOGY": "Sociology",
|
|
312
313
|
"STUDIES_IN_SCIENCE_AND_TECHNOLOGY": "Studies in science and technology",
|
|
313
314
|
"THEOLOGY": "Theology",
|
|
315
|
+
"OTHERS": "Others",
|
|
314
316
|
"BIOLOGY": "Biology",
|
|
315
317
|
"CHEMISTRY": "Chemistry",
|
|
316
318
|
"COMPUTER_SCIENCE": "Computer science",
|
|
@@ -13,7 +13,7 @@
|
|
|
13
13
|
"see-more": "Voir plus d'événements"
|
|
14
14
|
},
|
|
15
15
|
"filters": "filtre | filtre | Filtres",
|
|
16
|
-
"from {0} to {1}": "Du {0}
|
|
16
|
+
"from {0} to {1}": "Du {0} au {1}",
|
|
17
17
|
"gallery": "Galerie",
|
|
18
18
|
"hybrid-event": "Évènement hybride",
|
|
19
19
|
"inscription-gratuite-et-obligatoire": "Enregistrement gratuit et obligatoire",
|
|
@@ -275,6 +275,7 @@
|
|
|
275
275
|
}
|
|
276
276
|
},
|
|
277
277
|
"disciplines": {
|
|
278
|
+
"ALL": "Toutes les disciplines",
|
|
278
279
|
"label": "Discipline",
|
|
279
280
|
"ANTHROPOLOGY_AND_ETHNOLOGY": "Anthropologie et ethnologie",
|
|
280
281
|
"ARCHITECTURE_AND_URBAN_PLANNING": "Architecture et urbanisme",
|
|
@@ -301,6 +302,7 @@
|
|
|
301
302
|
"SOCIOLOGY": "Sociologie",
|
|
302
303
|
"STUDIES_IN_SCIENCE_AND_TECHNOLOGY": "Études en sciences et technologies",
|
|
303
304
|
"THEOLOGY": "Théologie",
|
|
305
|
+
"OTHERS": "Autres",
|
|
304
306
|
"BIOLOGY": "Biologie",
|
|
305
307
|
"CHEMISTRY": "Chimie",
|
|
306
308
|
"COMPUTER_SCIENCE": "Informatique",
|
|
@@ -322,6 +324,7 @@
|
|
|
322
324
|
"IDENTITIES_GENDER_AND_SEXUALITIES": "Identités, genre et sexualités",
|
|
323
325
|
"INEQUALITIES_INCLUSION_SOCIAL_INNOVATION": "Inégalités, inclusion, innovation sociale",
|
|
324
326
|
"MIGRATIONS": "Migrations",
|
|
327
|
+
"OTHERS": "Autres",
|
|
325
328
|
"OCEANS": "Océans",
|
|
326
329
|
"PUBLIC_POLICIES": "Politiques publiques",
|
|
327
330
|
"RADICALISATION_VIOLENCE_EXTREMISM": "Radicalisations, violences, extrémismes",
|
|
@@ -439,7 +442,7 @@
|
|
|
439
442
|
"visit": "Nous rendre visite",
|
|
440
443
|
"visit-the-project-website": "Visiter le site web",
|
|
441
444
|
"groups": "Catégorie",
|
|
442
|
-
"present": "
|
|
445
|
+
"present": "Présent",
|
|
443
446
|
"visit-this-project-website": "Visitez le site Web du projet",
|
|
444
447
|
"visit-this-publications-website": "Visitez la page Web de cette publication",
|
|
445
448
|
"no-biography": "Aucune biographie disponible",
|
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.1.
|
|
4
|
+
"version": "1.1.20",
|
|
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.14"
|
|
12
12
|
},
|
|
13
13
|
"description": "Paris IAS List Module",
|
|
14
14
|
"peerDependencies": {
|