@datagouv/components-next 0.0.31 → 0.1.0
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 +36 -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-NQ9byxF5.js +92 -0
- package/dist/{MapContainer.client-DXVDyZYz.js → MapContainer.client-D7Y0OXMU.js} +17587 -6826
- package/dist/{PdfPreview.client-XwjUHnmx.js → PdfPreview.client-DU6FbUh0.js} +47 -48
- package/dist/{Pmtiles.client-DfnKDlpg.js → Pmtiles.client-DCOxft6M.js} +7609 -7503
- package/dist/Swagger.client-DTHhEAFT.js +4 -0
- package/dist/XmlPreview.client-BEOCeCP8.js +84 -0
- package/dist/components-next.css +6 -6
- package/dist/components-next.js +96 -68
- 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-Qu3kUOIH.js → main-DFEQrdg5.js} +54440 -56951
- 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-NQY1dx9T.js → vue3-xml-viewer.common-RC76oYFu.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 +17 -17
- package/src/components/DatasetLabelTag.vue +40 -0
- package/src/components/DatasetQuality.vue +13 -11
- package/src/components/DatasetQualityInline.vue +7 -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 +4 -5
- 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 +9 -3
- 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 +2 -2
- package/src/components/ResourceAccordion/Preview.vue +2 -2
- package/src/components/ResourceAccordion/ResourceAccordion.vue +23 -15
- package/src/components/ResourceAccordion/SchemaBadge.vue +6 -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 +6 -6
- package/src/components/Toggletip.vue +15 -20
- package/src/components/TranslationT.vue +51 -0
- package/src/composables/useActiveDescendant.ts +1 -1
- 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 +29 -1
- package/src/functions/dates.ts +4 -4
- package/src/functions/helpers.ts +3 -3
- package/src/functions/organizations.ts +3 -3
- 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 +46 -26
- 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 +3 -1
- package/src/types/site.ts +1 -0
- package/src/types/topics.ts +17 -2
- package/dist/JsonPreview.client-B5cv59th.js +0 -93
- package/dist/Swagger.client-BisHyZkP.js +0 -4
- package/dist/XmlPreview.client-BSjMew4d.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
|
|
@@ -61,7 +61,7 @@
|
|
|
61
61
|
</div>
|
|
62
62
|
<template v-if="lastValue && lastMonth">
|
|
63
63
|
<p class="mt-1 mb-0 text-xs">
|
|
64
|
-
{{
|
|
64
|
+
{{ t('depuis juillet 2022') }}
|
|
65
65
|
</p>
|
|
66
66
|
<p class="mt-1 mb-0 text-xs text-success-darkest">
|
|
67
67
|
<strong>
|
|
@@ -124,7 +124,7 @@
|
|
|
124
124
|
/>
|
|
125
125
|
</ContentLoader>
|
|
126
126
|
<div
|
|
127
|
-
v-else-if="changesThisYear"
|
|
127
|
+
v-else-if="data && changesThisYear"
|
|
128
128
|
class="ml-2"
|
|
129
129
|
>
|
|
130
130
|
<SmallChart
|
|
@@ -149,21 +149,21 @@
|
|
|
149
149
|
|
|
150
150
|
<script setup lang="ts">
|
|
151
151
|
import { computed } from 'vue'
|
|
152
|
-
import { useI18n } from 'vue-i18n'
|
|
153
152
|
import { ContentLoader } from 'vue-content-loader'
|
|
154
153
|
import { useFormatDate } from '../functions/dates'
|
|
155
154
|
import { summarize } from '../functions/helpers'
|
|
155
|
+
import { useTranslation } from '../composables/useTranslation'
|
|
156
156
|
import SmallChart from './SmallChart.vue'
|
|
157
157
|
|
|
158
158
|
const props = defineProps<{
|
|
159
159
|
title: string
|
|
160
|
-
data
|
|
160
|
+
data?: Record<string, number> | null
|
|
161
161
|
type: 'line' | 'bar'
|
|
162
162
|
size?: 'sm'
|
|
163
163
|
summary?: number | null
|
|
164
164
|
}>()
|
|
165
165
|
|
|
166
|
-
const { t } =
|
|
166
|
+
const { t } = useTranslation()
|
|
167
167
|
const { formatDate } = useFormatDate()
|
|
168
168
|
|
|
169
169
|
const months = computed(() => props.data ? Object.keys(props.data) : [])
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
<template>
|
|
2
2
|
<Popover
|
|
3
3
|
v-slot="{ open, close }"
|
|
4
|
+
ref="popover"
|
|
4
5
|
class="relative"
|
|
5
6
|
>
|
|
6
7
|
<!--
|
|
@@ -11,17 +12,13 @@
|
|
|
11
12
|
:value="open"
|
|
12
13
|
@changed="calculatePanelPosition"
|
|
13
14
|
/>
|
|
14
|
-
<PopoverButton
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
v-bind="buttonProps"
|
|
22
|
-
>
|
|
23
|
-
<slot />
|
|
24
|
-
</BrandedButton>
|
|
15
|
+
<PopoverButton
|
|
16
|
+
v-bind="buttonProps"
|
|
17
|
+
class="w-8 h-8 rounded-full -outline-offset-2 inline-flex items-center justify-center bg-transparent border-transparent hover:!bg-gray-some"
|
|
18
|
+
>
|
|
19
|
+
<slot>
|
|
20
|
+
<RiInformationLine class="size-5" />
|
|
21
|
+
</slot>
|
|
25
22
|
</PopoverButton>
|
|
26
23
|
|
|
27
24
|
<ClientOnly>
|
|
@@ -49,7 +46,6 @@
|
|
|
49
46
|
import { Popover, PopoverButton, PopoverPanel } from '@headlessui/vue'
|
|
50
47
|
import { nextTick, onBeforeUnmount, onMounted, onUpdated, ref, useTemplateRef } from 'vue'
|
|
51
48
|
import { RiInformationLine } from '@remixicon/vue'
|
|
52
|
-
import BrandedButton from './BrandedButton.vue'
|
|
53
49
|
import ClientOnly from './ClientOnly.vue'
|
|
54
50
|
import ValueWatcher from './ValueWatcher.vue'
|
|
55
51
|
|
|
@@ -58,7 +54,7 @@ defineProps<{
|
|
|
58
54
|
noMargin?: boolean
|
|
59
55
|
}>()
|
|
60
56
|
|
|
61
|
-
const
|
|
57
|
+
const popoverRef = useTemplateRef('popover')
|
|
62
58
|
const panelStyle = ref({})
|
|
63
59
|
|
|
64
60
|
// Since the parent of the component can have an overflow-hidden
|
|
@@ -66,17 +62,16 @@ const panelStyle = ref({})
|
|
|
66
62
|
// We need to compute the correct position of the tooltip.
|
|
67
63
|
const calculatePanelPosition = () => {
|
|
68
64
|
nextTick(() => {
|
|
69
|
-
const
|
|
65
|
+
const popover = popoverRef.value?.$el
|
|
70
66
|
|
|
71
|
-
if (!
|
|
72
|
-
console.error('Cannot find the
|
|
67
|
+
if (!popover) {
|
|
68
|
+
console.error('Cannot find the popover of the Toggletip.)')
|
|
73
69
|
return
|
|
74
70
|
}
|
|
75
|
-
|
|
76
|
-
const buttonRect = button.getBoundingClientRect()
|
|
71
|
+
const popoverRect = popover.getBoundingClientRect()
|
|
77
72
|
panelStyle.value = {
|
|
78
|
-
left: `${
|
|
79
|
-
top: `${
|
|
73
|
+
left: `${popoverRect.left + window.scrollX}px`,
|
|
74
|
+
top: `${popoverRect.bottom + window.scrollY}px`,
|
|
80
75
|
}
|
|
81
76
|
})
|
|
82
77
|
}
|
|
@@ -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>
|
|
@@ -5,7 +5,7 @@ export type Option = {
|
|
|
5
5
|
id: string
|
|
6
6
|
}
|
|
7
7
|
|
|
8
|
-
export
|
|
8
|
+
export function useActiveDescendant<T extends Option>(options: MaybeRefOrGetter<Array<T>>, direction: 'horizontal' | 'vertical') {
|
|
9
9
|
const active = ref<string | undefined>()
|
|
10
10
|
|
|
11
11
|
const activeOption = computed<T | undefined>(() => toValue(options).find(option => option.id === active.value))
|
|
@@ -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]![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)
|
|
@@ -8,7 +13,7 @@ function constructUrl(baseUrl: string, path: string): string {
|
|
|
8
13
|
return url.toString()
|
|
9
14
|
}
|
|
10
15
|
|
|
11
|
-
export
|
|
16
|
+
export function getDatasetOEmbedHtml(type: string, id: string): string {
|
|
12
17
|
const config = useComponentsConfig()
|
|
13
18
|
|
|
14
19
|
const staticUrl = constructUrl(config.baseUrl, 'oembed.js')
|
|
@@ -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'),
|
|
@@ -91,7 +91,7 @@ export function isOrganizationCertified(organization: Organization | null): bool
|
|
|
91
91
|
return hasBadge(organization, CERTIFIED) && (isType(organization, PUBLIC_SERVICE) || isType(organization, LOCAL_AUTHORITY))
|
|
92
92
|
}
|
|
93
93
|
|
|
94
|
-
export
|
|
94
|
+
export function getOrganizationOEmbedHtml(type: string, id: string): string {
|
|
95
95
|
const config = useComponentsConfig()
|
|
96
96
|
|
|
97
97
|
const staticUrl = constructUrl(config.baseUrl, 'oembed.js')
|
|
@@ -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
|
}
|