@datagouv/components-next 0.0.32 → 0.1.1
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 +5 -15
- package/assets/labels/hvd.svg +15 -0
- package/assets/labels/inspire.svg +20 -0
- package/assets/labels/sl.svg +5 -0
- package/assets/labels/spd.svg +5 -0
- package/assets/labels/sr.svg +5 -0
- package/dist/JsonPreview.client-nkpNPvZb.js +92 -0
- package/dist/{MapContainer.client-Dhfz-YU8.js → MapContainer.client-CrTw-ai8.js} +17587 -6826
- package/dist/{PdfPreview.client--W0FK7CN.js → PdfPreview.client-JpAWf0A2.js} +47 -48
- package/dist/{Pmtiles.client-B6vRTwrm.js → Pmtiles.client-B83l4Ft5.js} +7609 -7503
- package/dist/Swagger.client-D1TfRQjI.js +4 -0
- package/dist/XmlPreview.client-DV8N1S9Y.js +84 -0
- package/dist/components-next.css +6 -6
- package/dist/components-next.js +95 -80
- package/dist/components.css +2 -2
- package/dist/en-CuSmdvir.js +30 -0
- package/dist/hvd-DYeke1vM.js +4 -0
- package/dist/inspire-BLXeJvob.js +4 -0
- package/dist/{main-yWiuApVL.js → main-CN6IuSUA.js} +53400 -55985
- package/dist/{pdf-vue3-Dm2ZCc3P.js → pdf-vue3-IkJO65RH.js} +2 -2
- package/dist/{pdf.min-f72cfa08-DAetWL3M.js → pdf.min-f72cfa08-CdgJTooZ.js} +78 -78
- package/dist/sl-VR8Tb1_u.js +4 -0
- package/dist/spd-BJ-Omhgt.js +4 -0
- package/dist/sr-DjSF-8xW.js +4 -0
- package/dist/{text-clamp.esm-Mb7Qdtu9.js → text-clamp.esm-B5kW_XMt.js} +54 -55
- package/dist/{vue3-json-viewer-B1fiyuLU.js → vue3-json-viewer-BXwup7nO.js} +88 -93
- package/dist/{vue3-xml-viewer.common-1QyofKqS.js → vue3-xml-viewer.common-9hga4rGF.js} +54 -54
- package/package.json +12 -11
- package/src/components/ActivityList/ActivityList.vue +159 -0
- package/src/components/ActivityList/UserActivityList.vue +30 -0
- package/src/components/AppLink.vue +3 -3
- package/src/components/Avatar.vue +1 -0
- package/src/components/DataserviceCard.vue +3 -3
- package/src/components/DatasetCard.vue +19 -18
- package/src/components/DatasetInformationPanel.vue +16 -16
- package/src/components/DatasetLabelTag.vue +40 -0
- package/src/components/DatasetQuality.vue +13 -10
- package/src/components/DatasetQualityInline.vue +5 -2
- package/src/components/DatasetQualityScore.vue +3 -3
- package/src/components/DatasetQualityTooltipContent.vue +2 -2
- package/src/components/DateRangeDetails.vue +7 -3
- package/src/components/ExtraAccordion.vue +3 -1
- package/src/components/OrganizationNameWithCertificate.vue +2 -2
- package/src/components/PaddedContainer.vue +28 -0
- package/src/components/Pagination.vue +2 -2
- package/src/components/Placeholder.vue +1 -1
- package/src/components/ReadMore.vue +17 -17
- package/src/components/ResourceAccordion/DataStructure.vue +8 -3
- package/src/components/ResourceAccordion/EditButton.vue +2 -2
- package/src/components/ResourceAccordion/JsonPreview.client.vue +7 -5
- package/src/components/ResourceAccordion/MapContainer.client.vue +2 -2
- package/src/components/ResourceAccordion/Metadata.vue +10 -10
- package/src/components/ResourceAccordion/PdfPreview.client.vue +7 -5
- package/src/components/ResourceAccordion/Pmtiles.client.vue +3 -3
- package/src/components/ResourceAccordion/Preview.vue +2 -2
- package/src/components/ResourceAccordion/ResourceAccordion.vue +23 -15
- package/src/components/ResourceAccordion/SchemaBadge.vue +4 -4
- package/src/components/ResourceAccordion/XmlPreview.client.vue +7 -5
- package/src/components/ReuseCard.vue +3 -3
- package/src/components/ReuseDetails.vue +2 -2
- package/src/components/SmallChart.vue +33 -30
- package/src/components/StatBox.vue +12 -7
- package/src/components/Toggletip.vue +0 -1
- package/src/components/TranslationT.vue +51 -0
- package/src/composables/useTranslation.ts +169 -0
- package/src/functions/activities.ts +36 -0
- package/src/functions/api.ts +4 -4
- package/src/functions/datasets.ts +28 -0
- package/src/functions/dates.ts +4 -4
- package/src/functions/helpers.ts +3 -3
- package/src/functions/organizations.ts +2 -2
- package/src/functions/pagination.ts +9 -0
- package/src/functions/resources.ts +2 -2
- package/src/functions/reuses.ts +3 -3
- package/src/main.ts +24 -20
- package/src/types/activity.ts +24 -0
- package/src/types/api.ts +8 -0
- package/src/types/badges.ts +4 -0
- package/src/types/dataservices.ts +6 -8
- package/src/types/datasets.ts +6 -1
- package/src/types/site.ts +1 -0
- package/src/types/topics.ts +17 -2
- package/dist/JsonPreview.client-BRhCOHlE.js +0 -93
- package/dist/Swagger.client-ch5H8aT2.js +0 -4
- package/dist/XmlPreview.client-BcbnRWAp.js +0 -85
|
@@ -46,7 +46,7 @@
|
|
|
46
46
|
/>
|
|
47
47
|
</ContentLoader>
|
|
48
48
|
<div
|
|
49
|
-
v-else-if="changesThisYear"
|
|
49
|
+
v-else-if="data && changesThisYear"
|
|
50
50
|
class="ml-2"
|
|
51
51
|
>
|
|
52
52
|
<SmallChart
|
|
@@ -60,8 +60,12 @@
|
|
|
60
60
|
</div>
|
|
61
61
|
</div>
|
|
62
62
|
<template v-if="lastValue && lastMonth">
|
|
63
|
-
<p
|
|
64
|
-
|
|
63
|
+
<p
|
|
64
|
+
v-if="since"
|
|
65
|
+
class="mt-1 mb-0 text-xs"
|
|
66
|
+
>
|
|
67
|
+
{{ t("depuis ") }}
|
|
68
|
+
{{ formatDate(since, { dateStyle: undefined, year: 'numeric', month: 'short', day: undefined }) }}
|
|
65
69
|
</p>
|
|
66
70
|
<p class="mt-1 mb-0 text-xs text-success-darkest">
|
|
67
71
|
<strong>
|
|
@@ -124,7 +128,7 @@
|
|
|
124
128
|
/>
|
|
125
129
|
</ContentLoader>
|
|
126
130
|
<div
|
|
127
|
-
v-else-if="changesThisYear"
|
|
131
|
+
v-else-if="data && changesThisYear"
|
|
128
132
|
class="ml-2"
|
|
129
133
|
>
|
|
130
134
|
<SmallChart
|
|
@@ -149,21 +153,22 @@
|
|
|
149
153
|
|
|
150
154
|
<script setup lang="ts">
|
|
151
155
|
import { computed } from 'vue'
|
|
152
|
-
import { useI18n } from 'vue-i18n'
|
|
153
156
|
import { ContentLoader } from 'vue-content-loader'
|
|
154
157
|
import { useFormatDate } from '../functions/dates'
|
|
155
158
|
import { summarize } from '../functions/helpers'
|
|
159
|
+
import { useTranslation } from '../composables/useTranslation'
|
|
156
160
|
import SmallChart from './SmallChart.vue'
|
|
157
161
|
|
|
158
162
|
const props = defineProps<{
|
|
159
163
|
title: string
|
|
160
|
-
data
|
|
164
|
+
data?: Record<string, number> | null
|
|
161
165
|
type: 'line' | 'bar'
|
|
162
166
|
size?: 'sm'
|
|
163
167
|
summary?: number | null
|
|
168
|
+
since?: string | null
|
|
164
169
|
}>()
|
|
165
170
|
|
|
166
|
-
const { t } =
|
|
171
|
+
const { t } = useTranslation()
|
|
167
172
|
const { formatDate } = useFormatDate()
|
|
168
173
|
|
|
169
174
|
const months = computed(() => props.data ? Object.keys(props.data) : [])
|
|
@@ -68,7 +68,6 @@ const calculatePanelPosition = () => {
|
|
|
68
68
|
console.error('Cannot find the popover of the Toggletip.)')
|
|
69
69
|
return
|
|
70
70
|
}
|
|
71
|
-
console.log(popover)
|
|
72
71
|
const popoverRect = popover.getBoundingClientRect()
|
|
73
72
|
panelStyle.value = {
|
|
74
73
|
left: `${popoverRect.left + window.scrollX}px`,
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
<template>
|
|
2
|
+
<component :is="tag">
|
|
3
|
+
<template
|
|
4
|
+
v-for="(part, index) in parts"
|
|
5
|
+
:key="index"
|
|
6
|
+
>
|
|
7
|
+
<component
|
|
8
|
+
:is="slots[part.placeholder]"
|
|
9
|
+
v-if="part.placeholder && slots[part.placeholder]"
|
|
10
|
+
/>
|
|
11
|
+
<template v-else-if="part.text">
|
|
12
|
+
{{ part.text }}
|
|
13
|
+
</template>
|
|
14
|
+
<template v-else-if="part.placeholder">
|
|
15
|
+
{{ '{' + part.placeholder + '}' }}
|
|
16
|
+
</template>
|
|
17
|
+
</template>
|
|
18
|
+
</component>
|
|
19
|
+
</template>
|
|
20
|
+
|
|
21
|
+
<script setup lang="ts">
|
|
22
|
+
import { computed, useSlots } from 'vue'
|
|
23
|
+
import { parseTextWithPlaceholders, useTranslation } from '../composables/useTranslation'
|
|
24
|
+
|
|
25
|
+
const props = withDefaults(defineProps<{
|
|
26
|
+
keypath: string
|
|
27
|
+
tag?: string
|
|
28
|
+
n?: number
|
|
29
|
+
count?: number
|
|
30
|
+
}>(), {
|
|
31
|
+
tag: 'span',
|
|
32
|
+
})
|
|
33
|
+
|
|
34
|
+
const slots = useSlots()
|
|
35
|
+
const { t } = useTranslation()
|
|
36
|
+
|
|
37
|
+
const parts = computed(() => {
|
|
38
|
+
const { keypath, n, count, ...interpolations } = props
|
|
39
|
+
|
|
40
|
+
const options: Record<string, string | number> = {
|
|
41
|
+
...interpolations,
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
if (n !== undefined) options.n = n
|
|
45
|
+
if (count !== undefined) options.count = count
|
|
46
|
+
|
|
47
|
+
const translated = t(keypath, options)
|
|
48
|
+
|
|
49
|
+
return parseTextWithPlaceholders(translated)
|
|
50
|
+
})
|
|
51
|
+
</script>
|
|
@@ -0,0 +1,169 @@
|
|
|
1
|
+
import { ref } from 'vue'
|
|
2
|
+
|
|
3
|
+
// Declare useRequestHeaders for TypeScript (Nuxt composable)
|
|
4
|
+
declare const useRequestHeader: ((header: string) => string | undefined) | undefined
|
|
5
|
+
|
|
6
|
+
export type TranslationOptions = Record<string, string | number>
|
|
7
|
+
|
|
8
|
+
const PLACEHOLDER_REGEX = /\{(\w+)\}/g
|
|
9
|
+
|
|
10
|
+
// Pre-register all available translation files at build time
|
|
11
|
+
const translationModules = import.meta.glob<Record<string, string>>('../../../locales/*.json', {
|
|
12
|
+
import: 'default',
|
|
13
|
+
})
|
|
14
|
+
|
|
15
|
+
function detectLanguage(): string {
|
|
16
|
+
// Server-side (Nuxt)
|
|
17
|
+
try {
|
|
18
|
+
const header = useRequestHeader?.('accept-language')
|
|
19
|
+
const acceptLanguage = header
|
|
20
|
+
if (acceptLanguage) {
|
|
21
|
+
const primaryLang = acceptLanguage.split(',')[0]!.split('-')[0]!.toLowerCase()
|
|
22
|
+
return primaryLang
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
catch {
|
|
26
|
+
// useRequestHeaders not available, continue to client-side detection
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
// Client-side
|
|
30
|
+
if (typeof window !== 'undefined' && navigator.language) {
|
|
31
|
+
return navigator.language.split('-')[0]!.toLowerCase()
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
return 'fr'
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
async function loadTranslationFile(lang: string): Promise<Record<string, string>> {
|
|
38
|
+
const modulePath = `../../../locales/${lang}.json`
|
|
39
|
+
const moduleLoader = translationModules[modulePath]
|
|
40
|
+
|
|
41
|
+
if (!moduleLoader) {
|
|
42
|
+
return {} // Translation file doesn't exist
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
try {
|
|
46
|
+
return await moduleLoader()
|
|
47
|
+
}
|
|
48
|
+
catch {
|
|
49
|
+
return {}
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
function interpolate(text: string, values: Record<string, string | number>): string {
|
|
54
|
+
return text.replace(PLACEHOLDER_REGEX, (match, key) => {
|
|
55
|
+
return key in values ? String(values[key]) : match
|
|
56
|
+
})
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
export function extractPlaceholders(text: string): string[] {
|
|
60
|
+
const placeholders: string[] = []
|
|
61
|
+
const regex = new RegExp(PLACEHOLDER_REGEX.source, PLACEHOLDER_REGEX.flags)
|
|
62
|
+
let match
|
|
63
|
+
|
|
64
|
+
while ((match = regex.exec(text)) !== null) {
|
|
65
|
+
placeholders.push(match[1]!)
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
return placeholders
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
export function parseTextWithPlaceholders(text: string): Array<{ text?: string, placeholder?: string }> {
|
|
72
|
+
const result: Array<{ text?: string, placeholder?: string }> = []
|
|
73
|
+
const regex = new RegExp(PLACEHOLDER_REGEX.source, PLACEHOLDER_REGEX.flags)
|
|
74
|
+
let lastIndex = 0
|
|
75
|
+
let match
|
|
76
|
+
|
|
77
|
+
while ((match = regex.exec(text)) !== null) {
|
|
78
|
+
if (match.index > lastIndex) {
|
|
79
|
+
result.push({ text: text.slice(lastIndex, match.index) })
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
result.push({ placeholder: match[1] })
|
|
83
|
+
|
|
84
|
+
lastIndex = regex.lastIndex
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
if (lastIndex < text.length) {
|
|
88
|
+
result.push({ text: text.slice(lastIndex) })
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
return result
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
function handlePluralization(key: string, count: number): string {
|
|
95
|
+
const parts = key.split('|').map(part => part.trim())
|
|
96
|
+
|
|
97
|
+
if (parts.length === 1) {
|
|
98
|
+
return parts[0]!
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
if (parts.length === 2) {
|
|
102
|
+
// French pluralization rule: 0 or 1 = singular, > 1 = plural
|
|
103
|
+
return count <= 1 ? parts[0]! : parts[1]!
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
if (parts.length >= 3) {
|
|
107
|
+
if (count === 0) {
|
|
108
|
+
return parts[0]!
|
|
109
|
+
}
|
|
110
|
+
else if (count === 1) {
|
|
111
|
+
return parts[1]!
|
|
112
|
+
}
|
|
113
|
+
else {
|
|
114
|
+
return parts[2]!
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
return parts[0]!
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
const translations = ref<Record<string, Record<string, string>>>({})
|
|
122
|
+
|
|
123
|
+
export const loadCurrentTranslations = async () => {
|
|
124
|
+
const currentLang = detectLanguage()
|
|
125
|
+
if (currentLang in translations.value) {
|
|
126
|
+
// Already loaded
|
|
127
|
+
return
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
translations.value[currentLang] = await loadTranslationFile(currentLang)
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
export const useTranslation = () => {
|
|
134
|
+
const locale = detectLanguage()
|
|
135
|
+
|
|
136
|
+
// Initialize translations if not already done.
|
|
137
|
+
loadCurrentTranslations()
|
|
138
|
+
|
|
139
|
+
const t = (key: string, options?: TranslationOptions | number): string => {
|
|
140
|
+
let result = key
|
|
141
|
+
|
|
142
|
+
if (typeof options == 'number') {
|
|
143
|
+
options = { count: options, n: options }
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
// Try to get translation from loaded translations first
|
|
147
|
+
if (translations.value && translations.value[locale] && translations.value[locale][key]) {
|
|
148
|
+
result = translations.value[locale][key]
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
const count = options?.n ?? options?.count
|
|
152
|
+
if (count !== undefined) {
|
|
153
|
+
result = handlePluralization(result, Number(count))
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
if (options && Object.keys(options).length > 0) {
|
|
157
|
+
result = interpolate(result, options)
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
return result
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
return { t, locale }
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
export const t = async (key: string, options?: TranslationOptions): Promise<string> => {
|
|
167
|
+
const { t: translate } = useTranslation()
|
|
168
|
+
return translate(key, options)
|
|
169
|
+
}
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
import { useTranslation } from '../composables/useTranslation'
|
|
2
|
+
import type { Activity } from '../types/activity'
|
|
3
|
+
|
|
4
|
+
export function getActivityTranslation(activity: Activity): string {
|
|
5
|
+
const { t } = useTranslation()
|
|
6
|
+
|
|
7
|
+
// Simple mapping of activity keys to human-readable text
|
|
8
|
+
const translations: Record<string, string> = {
|
|
9
|
+
'dataset:created': t('a créé le jeu de données'),
|
|
10
|
+
'dataset:updated': t('a mis à jour le jeu de données'),
|
|
11
|
+
'dataset:deleted': t('a supprimé le jeu de données'),
|
|
12
|
+
'dataset:discussed': t('a discuté du jeu de données'),
|
|
13
|
+
'dataset:followed': t('suit le jeu de données'),
|
|
14
|
+
'dataset:resource:added': t('a ajouté une ressource'),
|
|
15
|
+
'dataset:resource:updated': t('a mis à jour une ressource'),
|
|
16
|
+
'dataset:resource:deleted': t('a supprimé une ressource'),
|
|
17
|
+
'dataservice:created': t('a créé le service de données'),
|
|
18
|
+
'dataservice:updated': t('a mis à jour le service de données'),
|
|
19
|
+
'dataservice:deleted': t('a supprimé le service de données'),
|
|
20
|
+
'dataservice:discussed': t('a discuté du service de données'),
|
|
21
|
+
'dataservice:followed': t('suit le service de données'),
|
|
22
|
+
'organization:created': t('a créé l\'organisation'),
|
|
23
|
+
'organization:updated': t('a mis à jour l\'organisation'),
|
|
24
|
+
'organization:followed': t('suit l\'organisation'),
|
|
25
|
+
'reuse:created': t('a créé la réutilisation'),
|
|
26
|
+
'reuse:updated': t('a mis à jour la réutilisation'),
|
|
27
|
+
'reuse:deleted': t('a supprimé la réutilisation'),
|
|
28
|
+
'reuse:discussed': t('a discuté de la réutilisation'),
|
|
29
|
+
'reuse:followed': t('suit la réutilisation'),
|
|
30
|
+
'user:followed': t('suit l\'utilisateur'),
|
|
31
|
+
'topic:created': t('a créé le sujet'),
|
|
32
|
+
'topic:updated': t('a mis à jour le sujet'),
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
return translations[activity.key] || activity.label || activity.key
|
|
36
|
+
}
|
package/src/functions/api.ts
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { ref, toValue, watchEffect, type ComputedRef, type Ref } from 'vue'
|
|
2
2
|
import { ofetch } from 'ofetch'
|
|
3
|
-
import { useI18n } from 'vue-i18n'
|
|
4
3
|
import { useComponentsConfig } from '../config'
|
|
4
|
+
import { useTranslation } from '../composables/useTranslation'
|
|
5
5
|
import type { AsyncData, AsyncDataExecuteOptions, AsyncDataRequestStatus, UseFetchOptions } from './api.types'
|
|
6
6
|
|
|
7
7
|
export async function useFetch<DataT, ErrorT = never>(
|
|
@@ -10,7 +10,7 @@ export async function useFetch<DataT, ErrorT = never>(
|
|
|
10
10
|
): Promise<AsyncData<DataT, ErrorT>> {
|
|
11
11
|
const config = useComponentsConfig()
|
|
12
12
|
|
|
13
|
-
const { locale } =
|
|
13
|
+
const { locale } = useTranslation()
|
|
14
14
|
|
|
15
15
|
if (config.customUseFetch) {
|
|
16
16
|
return await config.customUseFetch(url, options)
|
|
@@ -35,11 +35,11 @@ export async function useFetch<DataT, ErrorT = never>(
|
|
|
35
35
|
options.headers.set('X-API-KEY', config.devApiKey)
|
|
36
36
|
}
|
|
37
37
|
|
|
38
|
-
if (locale
|
|
38
|
+
if (locale) {
|
|
39
39
|
if (!options.params) {
|
|
40
40
|
options.params = {}
|
|
41
41
|
}
|
|
42
|
-
options.params['lang'] = locale
|
|
42
|
+
options.params['lang'] = locale
|
|
43
43
|
}
|
|
44
44
|
},
|
|
45
45
|
async onResponseError() {
|
|
@@ -1,6 +1,11 @@
|
|
|
1
1
|
import { useComponentsConfig } from '../config'
|
|
2
2
|
import type { Dataset, DatasetV2 } from '../types/datasets'
|
|
3
3
|
import type { CommunityResource, Resource } from '../types/resources'
|
|
4
|
+
import { removeMarkdown } from './markdown'
|
|
5
|
+
|
|
6
|
+
// Dataset description constants
|
|
7
|
+
export const DESCRIPTION_SHORT_MAX_LENGTH = 200
|
|
8
|
+
export const DESCRIPTION_MIN_LENGTH = 200
|
|
4
9
|
|
|
5
10
|
function constructUrl(baseUrl: string, path: string): string {
|
|
6
11
|
const url = new URL(baseUrl)
|
|
@@ -22,3 +27,26 @@ export function isCommunityResource(resource: Resource | CommunityResource): boo
|
|
|
22
27
|
export function getResourceExternalUrl(dataset: Dataset | DatasetV2 | Omit<Dataset, 'resources' | 'community_resources'>, resource: Resource | CommunityResource): string {
|
|
23
28
|
return `${dataset.page}#/${isCommunityResource(resource) ? 'community-resources' : 'resources'}/${resource.id}`
|
|
24
29
|
}
|
|
30
|
+
|
|
31
|
+
/**
|
|
32
|
+
* Returns the short description to display.
|
|
33
|
+
* If description_short is provided, it is used.
|
|
34
|
+
* Otherwise, the first DESCRIPTION_SHORT_MAX_LENGTH characters of description are used.
|
|
35
|
+
*/
|
|
36
|
+
export async function getShortDescription(
|
|
37
|
+
description: string | null | undefined,
|
|
38
|
+
descriptionShort: string | null | undefined
|
|
39
|
+
): Promise<string> {
|
|
40
|
+
if (descriptionShort?.trim()) {
|
|
41
|
+
return descriptionShort
|
|
42
|
+
}
|
|
43
|
+
if (description?.trim()) {
|
|
44
|
+
// description field is a markdown field that may contain HTML tags, so we should trim it
|
|
45
|
+
const plainText = (await removeMarkdown(description)).trim()
|
|
46
|
+
if (plainText.length > DESCRIPTION_SHORT_MAX_LENGTH) {
|
|
47
|
+
return `${plainText.substring(0, DESCRIPTION_SHORT_MAX_LENGTH - 1)}…`
|
|
48
|
+
}
|
|
49
|
+
return plainText
|
|
50
|
+
}
|
|
51
|
+
return ''
|
|
52
|
+
}
|
package/src/functions/dates.ts
CHANGED
|
@@ -1,9 +1,9 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { useTranslation } from '../composables/useTranslation'
|
|
2
2
|
|
|
3
3
|
const SECONDS_IN_A_DAY = 3600 * 24
|
|
4
4
|
|
|
5
5
|
export function useFormatDate() {
|
|
6
|
-
const { t, locale } =
|
|
6
|
+
const { t, locale } = useTranslation()
|
|
7
7
|
|
|
8
8
|
const formatDate = (date: Date | string | null | undefined, options: Intl.DateTimeFormatOptions = {}) => {
|
|
9
9
|
if (!date) {
|
|
@@ -13,7 +13,7 @@ export function useFormatDate() {
|
|
|
13
13
|
if (!('dateStyle' in options)) {
|
|
14
14
|
options.dateStyle = 'long'
|
|
15
15
|
}
|
|
16
|
-
return new Intl.DateTimeFormat(locale
|
|
16
|
+
return new Intl.DateTimeFormat(locale, options).format(date)
|
|
17
17
|
}
|
|
18
18
|
|
|
19
19
|
/**
|
|
@@ -58,7 +58,7 @@ export function useFormatDate() {
|
|
|
58
58
|
const diffInUnit = Math.abs(diff / unit.seconds)
|
|
59
59
|
return diffInUnit < unit.changeAfter
|
|
60
60
|
})!
|
|
61
|
-
return new Intl.RelativeTimeFormat(locale
|
|
61
|
+
return new Intl.RelativeTimeFormat(locale, { numeric: 'auto' }).format(Math.round(diff / correctUnit?.seconds), correctUnit?.unit)
|
|
62
62
|
}
|
|
63
63
|
|
|
64
64
|
/**
|
package/src/functions/helpers.ts
CHANGED
|
@@ -1,9 +1,9 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { useTranslation } from '../composables/useTranslation'
|
|
2
2
|
|
|
3
3
|
export const filesize = (val: number) => {
|
|
4
|
-
const { t, locale } =
|
|
4
|
+
const { t, locale } = useTranslation()
|
|
5
5
|
const suffix = t('o')
|
|
6
|
-
const formatter = new Intl.NumberFormat(locale
|
|
6
|
+
const formatter = new Intl.NumberFormat(locale, { minimumFractionDigits: 1, maximumFractionDigits: 1 })
|
|
7
7
|
const units = ['', 'K', 'M', 'G', 'T', 'P', 'E', 'Z']
|
|
8
8
|
for (const unit of units) {
|
|
9
9
|
if (Math.abs(val) < 1024.0) {
|
|
@@ -1,8 +1,8 @@
|
|
|
1
|
-
import { useI18n } from 'vue-i18n'
|
|
2
1
|
import type { Component } from 'vue'
|
|
3
2
|
import { RiBankLine, RiBuilding2Line, RiCommunityLine, RiGovernmentLine, RiUserLine } from '@remixicon/vue'
|
|
4
3
|
import { useComponentsConfig } from '../config'
|
|
5
4
|
import type { Organization } from '../types/organizations'
|
|
5
|
+
import { useTranslation } from '../composables/useTranslation'
|
|
6
6
|
|
|
7
7
|
export const CERTIFIED = 'certified'
|
|
8
8
|
export const PUBLIC_SERVICE = 'public-service'
|
|
@@ -31,7 +31,7 @@ export function hasBadge(organization: Organization, kind: string) {
|
|
|
31
31
|
}
|
|
32
32
|
|
|
33
33
|
export function getOrganizationTypes(): Array<{ type: OrganizationTypes | UserType, label: string, icon: Component | null }> {
|
|
34
|
-
const { t } =
|
|
34
|
+
const { t } = useTranslation()
|
|
35
35
|
return [{
|
|
36
36
|
type: PUBLIC_SERVICE,
|
|
37
37
|
label: t('Service public'),
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import { useRoute } from 'vue-router'
|
|
2
|
+
|
|
3
|
+
export function getLink(page: number): string {
|
|
4
|
+
const route = useRoute()
|
|
5
|
+
const routePath = route.path
|
|
6
|
+
const search = new URLSearchParams(route.query as Record<string, string>)
|
|
7
|
+
search.set('page', page.toFixed(0))
|
|
8
|
+
return `${routePath}?${search.toString()}`
|
|
9
|
+
}
|
|
@@ -1,6 +1,5 @@
|
|
|
1
1
|
import { readonly, type Component } from 'vue'
|
|
2
2
|
|
|
3
|
-
import { useI18n } from 'vue-i18n'
|
|
4
3
|
import { RiEarthLine, RiMap2Line } from '@remixicon/vue'
|
|
5
4
|
import Archive from '../components/Icons/Archive.vue'
|
|
6
5
|
import Code from '../components/Icons/Code.vue'
|
|
@@ -9,6 +8,7 @@ import Image from '../components/Icons/Image.vue'
|
|
|
9
8
|
import Link from '../components/Icons/Link.vue'
|
|
10
9
|
import Table from '../components/Icons/Table.vue'
|
|
11
10
|
import type { Resource } from '../types/resources'
|
|
11
|
+
import { useTranslation } from '../composables/useTranslation'
|
|
12
12
|
|
|
13
13
|
export function getResourceFormatIcon(format: string): Component | null {
|
|
14
14
|
switch (format?.trim()?.toLowerCase()) {
|
|
@@ -90,7 +90,7 @@ export const RESOURCE_TYPE = readonly(['main', 'documentation', 'update', 'api',
|
|
|
90
90
|
export type ResourceType = typeof RESOURCE_TYPE[number]
|
|
91
91
|
|
|
92
92
|
export const getResourceLabel = (type: ResourceType) => {
|
|
93
|
-
const { t } =
|
|
93
|
+
const { t } = useTranslation()
|
|
94
94
|
switch (type) {
|
|
95
95
|
case 'main':
|
|
96
96
|
return t('Fichier principal') // TODO: manage the plural case
|
package/src/functions/reuses.ts
CHANGED
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
import { ofetch } from 'ofetch'
|
|
2
|
-
import { useI18n } from 'vue-i18n'
|
|
3
2
|
import { useComponentsConfig } from '../config'
|
|
4
3
|
import type { ReuseTopic, ReuseType } from '../types/reuses'
|
|
4
|
+
import { useTranslation } from '../composables/useTranslation'
|
|
5
5
|
|
|
6
6
|
let reuseTypesRequest: Promise<Array<ReuseType>> | null = null
|
|
7
7
|
export function useFetchReuseTypes() {
|
|
8
8
|
const config = useComponentsConfig()
|
|
9
|
-
const { locale } =
|
|
9
|
+
const { locale } = useTranslation()
|
|
10
10
|
|
|
11
11
|
return async (): Promise<Array<ReuseType>> => {
|
|
12
12
|
if (reuseTypesRequest) {
|
|
@@ -15,7 +15,7 @@ export function useFetchReuseTypes() {
|
|
|
15
15
|
|
|
16
16
|
return await (reuseTypesRequest = ofetch<Array<ReuseType>>('api/1/reuses/types/', {
|
|
17
17
|
baseURL: config.apiBase,
|
|
18
|
-
query: { lang: locale
|
|
18
|
+
query: { lang: locale },
|
|
19
19
|
}))
|
|
20
20
|
}
|
|
21
21
|
}
|
package/src/main.ts
CHANGED
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
import type { App, Plugin } from 'vue'
|
|
2
|
-
import {
|
|
2
|
+
import type { Activity, ActivityKey } from './types/activity.js'
|
|
3
|
+
import type { PaginatedArray } from './types/api.js'
|
|
3
4
|
import type { ContactPoint, ContactPointRole } from './types/contact_point.js'
|
|
4
|
-
import type { Badge, Badges } from './types/badges'
|
|
5
|
+
import type { Badge, Badges, TranslatedBadge } from './types/badges'
|
|
5
6
|
import type { Dataset, DatasetV2, DatasetV2WithFullObject, NewDataset, Quality, Rel } from './types/datasets'
|
|
6
7
|
import type { NewDataservice, Dataservice, DataserviceAccessAudience, DataserviceAccessAudienceCondition, DataserviceAccessAudienceType } from './types/dataservices'
|
|
7
8
|
import type { Frequency, Frequencies } from './types/frequency'
|
|
@@ -11,12 +12,14 @@ import type { License } from './types/licenses'
|
|
|
11
12
|
import type { Member, MemberRole, NewOrganization, Organization } from './types/organizations'
|
|
12
13
|
import type { Owned, OwnedWithId } from './types/owned'
|
|
13
14
|
import type { NewReuse, Reuse, ReuseTopic, ReuseType } from './types/reuses'
|
|
14
|
-
import type { TopicV2 } from './types/topics'
|
|
15
|
+
import type { TopicV2, TopicElement, TopicElementClass, TopicElementRel } from './types/topics'
|
|
15
16
|
import type { CommunityResource, FileResourceFileType, RemoteResourceFileType, ResourceFileType, ResourceType, Resource } from './types/resources'
|
|
16
17
|
import type { Site } from './types/site'
|
|
17
18
|
import type { Weight, WellType } from './types/ui'
|
|
18
19
|
import type { User } from './types/users'
|
|
19
20
|
|
|
21
|
+
import ActivityList from './components/ActivityList/ActivityList.vue'
|
|
22
|
+
import UserActivityList from './components/ActivityList/UserActivityList.vue'
|
|
20
23
|
import AnimatedLoader from './components/AnimatedLoader.vue'
|
|
21
24
|
import AppLink from './components/AppLink.vue'
|
|
22
25
|
import Avatar from './components/Avatar.vue'
|
|
@@ -34,10 +37,12 @@ import DatasetQualityItem from './components/DatasetQualityItem.vue'
|
|
|
34
37
|
import DatasetQualityScore from './components/DatasetQualityScore.vue'
|
|
35
38
|
import DatasetQualityTooltipContent from './components/DatasetQualityTooltipContent.vue'
|
|
36
39
|
import ExtraAccordion from './components/ExtraAccordion.vue'
|
|
40
|
+
import LabelTag from './components/DatasetLabelTag.vue'
|
|
37
41
|
import OrganizationCard from './components/OrganizationCard.vue'
|
|
38
42
|
import OrganizationNameWithCertificate from './components/OrganizationNameWithCertificate.vue'
|
|
39
43
|
import OwnerType from './components/OwnerType.vue'
|
|
40
44
|
import OwnerTypeIcon from './components/OwnerTypeIcon.vue'
|
|
45
|
+
import PaddedContainer from './components/PaddedContainer.vue'
|
|
41
46
|
import Pagination from './components/Pagination.vue'
|
|
42
47
|
import Placeholder from './components/Placeholder.vue'
|
|
43
48
|
import ReadMore from './components/ReadMore.vue'
|
|
@@ -55,12 +60,15 @@ import TabPanel from './components/Tabs/TabPanel.vue'
|
|
|
55
60
|
import TabPanels from './components/Tabs/TabPanels.vue'
|
|
56
61
|
import Tooltip from './components/Tooltip.vue'
|
|
57
62
|
import Toggletip from './components/Toggletip.vue'
|
|
63
|
+
import TranslationT from './components/TranslationT.vue'
|
|
58
64
|
import type { UseFetchFunction } from './functions/api.types'
|
|
59
65
|
import { configKey, useComponentsConfig, type PluginConfig } from './config.js'
|
|
60
66
|
|
|
61
67
|
export * from './composables/useActiveDescendant'
|
|
62
68
|
export * from './composables/useReuseType'
|
|
69
|
+
export * from './composables/useTranslation'
|
|
63
70
|
|
|
71
|
+
export * from './functions/activities'
|
|
64
72
|
export * from './functions/datasets'
|
|
65
73
|
export * from './functions/dates'
|
|
66
74
|
export * from './functions/helpers'
|
|
@@ -69,6 +77,7 @@ export * from './functions/matomo'
|
|
|
69
77
|
export * from './functions/never'
|
|
70
78
|
export * from './functions/organizations'
|
|
71
79
|
export * from './functions/owned'
|
|
80
|
+
export * from './functions/pagination'
|
|
72
81
|
export * from './functions/resources'
|
|
73
82
|
export * from './functions/reuses'
|
|
74
83
|
export * from './functions/schemas'
|
|
@@ -76,6 +85,8 @@ export * from './functions/users'
|
|
|
76
85
|
|
|
77
86
|
export type {
|
|
78
87
|
UseFetchFunction,
|
|
88
|
+
Activity,
|
|
89
|
+
ActivityKey,
|
|
79
90
|
Badge,
|
|
80
91
|
Badges,
|
|
81
92
|
CommunityResource,
|
|
@@ -104,6 +115,7 @@ export type {
|
|
|
104
115
|
Organization,
|
|
105
116
|
Owned,
|
|
106
117
|
OwnedWithId,
|
|
118
|
+
PaginatedArray,
|
|
107
119
|
Quality,
|
|
108
120
|
Rel,
|
|
109
121
|
RemoteResourceFileType,
|
|
@@ -115,7 +127,11 @@ export type {
|
|
|
115
127
|
ReuseType,
|
|
116
128
|
Site,
|
|
117
129
|
SpatialZone,
|
|
130
|
+
TranslatedBadge,
|
|
118
131
|
TopicV2,
|
|
132
|
+
TopicElement,
|
|
133
|
+
TopicElementClass,
|
|
134
|
+
TopicElementRel,
|
|
119
135
|
User,
|
|
120
136
|
Weight,
|
|
121
137
|
WellType,
|
|
@@ -129,29 +145,13 @@ const datagouv: Plugin<PluginConfig> = {
|
|
|
129
145
|
const textClamp = await import('vue3-text-clamp')
|
|
130
146
|
options.textClamp = textClamp.default
|
|
131
147
|
}
|
|
132
|
-
try {
|
|
133
|
-
// There is no condition to check if vue-i18n is instancied, only an error...
|
|
134
|
-
useI18n()
|
|
135
|
-
}
|
|
136
|
-
catch {
|
|
137
|
-
const i18n = createI18n({
|
|
138
|
-
legacy: false,
|
|
139
|
-
globalInjection: true,
|
|
140
|
-
locale: 'fr',
|
|
141
|
-
messages: {},
|
|
142
|
-
formatFallbackMessages: true,
|
|
143
|
-
missingWarn: false,
|
|
144
|
-
fallbackFormat: true,
|
|
145
|
-
fallbackWarn: false,
|
|
146
|
-
})
|
|
147
|
-
app.use(i18n)
|
|
148
|
-
}
|
|
149
148
|
},
|
|
150
149
|
}
|
|
151
150
|
|
|
152
151
|
export {
|
|
153
152
|
datagouv,
|
|
154
153
|
useComponentsConfig,
|
|
154
|
+
ActivityList,
|
|
155
155
|
AnimatedLoader,
|
|
156
156
|
AppLink,
|
|
157
157
|
Avatar,
|
|
@@ -169,10 +169,12 @@ export {
|
|
|
169
169
|
DatasetQualityTooltipContent,
|
|
170
170
|
DateRangeDetails,
|
|
171
171
|
ExtraAccordion,
|
|
172
|
+
LabelTag,
|
|
172
173
|
OrganizationCard,
|
|
173
174
|
OrganizationNameWithCertificate,
|
|
174
175
|
OwnerType,
|
|
175
176
|
OwnerTypeIcon,
|
|
177
|
+
PaddedContainer,
|
|
176
178
|
Pagination,
|
|
177
179
|
Placeholder,
|
|
178
180
|
ReadMore,
|
|
@@ -190,4 +192,6 @@ export {
|
|
|
190
192
|
TabPanels,
|
|
191
193
|
Tooltip,
|
|
192
194
|
Toggletip,
|
|
195
|
+
TranslationT,
|
|
196
|
+
UserActivityList,
|
|
193
197
|
}
|