@datagouv/components-next 1.0.2-dev.6 → 1.0.2-dev.61
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/assets/main.css +4 -0
- package/dist/Datafair.client-CZBhcl2N.js +30 -0
- package/dist/JsonPreview.client-BFXc8Yds.js +40 -0
- package/dist/{MapContainer.client-DRkAmdOc.js → MapContainer.client-DXvpJcmy.js} +35 -38
- package/dist/{PdfPreview.client-C-w6-w44.js → PdfPreview.client-C4rWJaSZ.js} +822 -865
- package/dist/{Pmtiles.client-BR7_ldHY.js → Pmtiles.client-ByvF0n7m.js} +574 -579
- package/dist/PreviewWrapper.vue_vue_type_script_setup_true_lang-B6mL96Bm.js +61 -0
- package/dist/XmlPreview.client-aAATX5FP.js +34 -0
- package/dist/components-next.css +3 -3
- package/dist/components-next.js +140 -131
- package/dist/components.css +1 -1
- package/dist/{index-SrYZwgCT.js → index-9wBlcysa.js} +1 -1
- package/dist/main-DSV-Ai-o.js +73008 -0
- package/dist/{vue3-xml-viewer.common-BRxsqI9j.js → vue3-xml-viewer.common-EQMYhxMa.js} +1 -1
- package/package.json +14 -9
- package/src/components/ActivityList/ActivityList.vue +0 -2
- package/src/components/Form/SearchableSelect.vue +2 -1
- package/src/components/InfiniteLoader.vue +53 -0
- package/src/components/OpenApiViewer/ContentTypeSelect.vue +48 -0
- package/src/components/OpenApiViewer/EndpointRequest.vue +164 -0
- package/src/components/OpenApiViewer/EndpointResponses.vue +149 -0
- package/src/components/OpenApiViewer/OpenApiViewer.vue +308 -0
- package/src/components/OpenApiViewer/SchemaPanel.vue +53 -0
- package/src/components/OpenApiViewer/SchemaTree.vue +77 -0
- package/src/components/OpenApiViewer/openapi.ts +150 -0
- package/src/components/OrganizationNameWithCertificate.vue +3 -2
- package/src/components/Pagination.vue +8 -5
- package/src/components/ReadMore.vue +1 -1
- package/src/components/ResourceAccordion/Datafair.client.vue +4 -10
- package/src/components/ResourceAccordion/JsonPreview.client.vue +23 -121
- package/src/components/ResourceAccordion/MapContainer.client.vue +7 -11
- package/src/components/ResourceAccordion/Metadata.vue +1 -2
- package/src/components/ResourceAccordion/PdfPreview.client.vue +24 -103
- package/src/components/ResourceAccordion/Pmtiles.client.vue +5 -10
- package/src/components/ResourceAccordion/Preview.vue +15 -20
- package/src/components/ResourceAccordion/PreviewLoader.vue +1 -2
- package/src/components/ResourceAccordion/PreviewUnavailable.vue +22 -0
- package/src/components/ResourceAccordion/PreviewWrapper.vue +82 -0
- package/src/components/ResourceAccordion/ResourceAccordion.vue +5 -7
- package/src/components/ResourceAccordion/XmlPreview.client.vue +16 -115
- package/src/components/ResourceExplorer/ResourceExplorer.vue +81 -13
- package/src/components/ResourceExplorer/ResourceExplorerSidebar.vue +2 -2
- package/src/components/ResourceExplorer/ResourceExplorerViewer.vue +30 -11
- package/src/components/Search/GlobalSearch.vue +174 -108
- package/src/components/Search/SearchInput.vue +3 -3
- package/src/components/TabularExplorer/TabularCell.vue +51 -0
- package/src/components/TabularExplorer/TabularCellPopover.vue +170 -0
- package/src/components/TabularExplorer/TabularExplorer.vue +870 -0
- package/src/components/TabularExplorer/TabularFilterContent.vue +351 -0
- package/src/components/TabularExplorer/TabularFilterPopover.vue +111 -0
- package/src/components/TabularExplorer/types.ts +83 -0
- package/src/composables/useResourceCapabilities.ts +1 -1
- package/src/composables/useSearchFilter.ts +90 -0
- package/src/composables/useStableQueryParams.ts +28 -3
- package/src/config.ts +2 -0
- package/src/functions/api.ts +34 -33
- package/src/functions/api.types.ts +1 -0
- package/src/functions/datasets.ts +0 -17
- package/src/functions/resources.ts +56 -1
- package/src/functions/tabular.ts +60 -0
- package/src/functions/tabularApi.ts +4 -6
- package/src/main.ts +14 -6
- package/src/types/dataservices.ts +2 -0
- package/src/types/pages.ts +0 -5
- package/src/types/posts.ts +2 -2
- package/src/types/reports.ts +3 -0
- package/src/types/search.ts +50 -1
- package/src/types/site.ts +5 -3
- package/src/types/users.ts +0 -1
- package/assets/swagger-themes/newspaper.css +0 -1670
- package/dist/Datafair.client-E5D6ePRC.js +0 -35
- package/dist/JsonPreview.client-C-6eBbPw.js +0 -87
- package/dist/Swagger.client-D4-F6yEf.js +0 -4
- package/dist/XmlPreview.client-Dl2VCgXF.js +0 -79
- package/dist/main-B2kXxWRG.js +0 -105833
- package/src/components/ResourceAccordion/Swagger.client.vue +0 -48
- package/src/functions/pagination.ts +0 -9
- /package/assets/illustrations/{_microscope.svg → microscope.svg} +0 -0
|
@@ -1,11 +1,13 @@
|
|
|
1
|
-
import { ref, watch, type Ref } from 'vue'
|
|
1
|
+
import { computed, ref, watch, type Ref } from 'vue'
|
|
2
2
|
import type { SearchTypeConfig } from '../types/search'
|
|
3
|
+
import { forEachActiveCustomFilter, type CustomFilterEntry } from './useSearchFilter'
|
|
3
4
|
|
|
4
5
|
type FilterRefs = Record<string, Ref<unknown>>
|
|
5
6
|
|
|
6
7
|
interface StableQueryParamsOptions {
|
|
7
8
|
typeConfig: SearchTypeConfig | undefined
|
|
8
9
|
allFilters: FilterRefs
|
|
10
|
+
customFilterRegistry: Map<string, CustomFilterEntry>
|
|
9
11
|
q: Ref<string>
|
|
10
12
|
sort: Ref<string | undefined>
|
|
11
13
|
page: Ref<number>
|
|
@@ -17,7 +19,7 @@ interface StableQueryParamsOptions {
|
|
|
17
19
|
* Applies hiddenFilters first, then user filters (which can override hiddenFilters).
|
|
18
20
|
*/
|
|
19
21
|
export function useStableQueryParams(options: StableQueryParamsOptions) {
|
|
20
|
-
const { typeConfig, allFilters, q, sort, page, pageSize } = options
|
|
22
|
+
const { typeConfig, allFilters, customFilterRegistry, q, sort, page, pageSize } = options
|
|
21
23
|
const stableParams = ref<Record<string, unknown>>({})
|
|
22
24
|
|
|
23
25
|
const buildParams = () => {
|
|
@@ -50,6 +52,19 @@ export function useStableQueryParams(options: StableQueryParamsOptions) {
|
|
|
50
52
|
}
|
|
51
53
|
}
|
|
52
54
|
|
|
55
|
+
// 3.5. Apply custom filter values. Concatenate into an array on collision
|
|
56
|
+
// so a custom filter mapped onto a built-in apiParam (e.g. theme → tag)
|
|
57
|
+
// combines with an existing built-in value instead of overwriting it.
|
|
58
|
+
forEachActiveCustomFilter(customFilterRegistry, (apiParam, value) => {
|
|
59
|
+
const existing = params[apiParam]
|
|
60
|
+
if (existing === undefined) {
|
|
61
|
+
params[apiParam] = value
|
|
62
|
+
}
|
|
63
|
+
else {
|
|
64
|
+
params[apiParam] = Array.isArray(existing) ? [...existing, value] : [existing, value]
|
|
65
|
+
}
|
|
66
|
+
})
|
|
67
|
+
|
|
53
68
|
// 4. Always include q, sort (if valid for this type), page, page_size
|
|
54
69
|
if (q.value) {
|
|
55
70
|
params.q = q.value
|
|
@@ -66,9 +81,19 @@ export function useStableQueryParams(options: StableQueryParamsOptions) {
|
|
|
66
81
|
return params
|
|
67
82
|
}
|
|
68
83
|
|
|
84
|
+
// Computed that reads all custom filter values, establishing reactive dependencies
|
|
85
|
+
// on both the Map mutations (shallowReactive) and each entry's ref.value.
|
|
86
|
+
const customFilterValues = computed(() => {
|
|
87
|
+
const snapshot: Record<string, unknown> = {}
|
|
88
|
+
for (const [urlParam, entry] of customFilterRegistry) {
|
|
89
|
+
snapshot[urlParam] = entry.ref.value
|
|
90
|
+
}
|
|
91
|
+
return snapshot
|
|
92
|
+
})
|
|
93
|
+
|
|
69
94
|
// Watch all dependencies and update only if content changed
|
|
70
95
|
watch(
|
|
71
|
-
[q, sort, page, ...Object.values(allFilters)],
|
|
96
|
+
[q, sort, page, ...Object.values(allFilters), customFilterValues],
|
|
72
97
|
() => {
|
|
73
98
|
const newParams = buildParams()
|
|
74
99
|
// JSON.stringify comparison is safe here because buildParams() builds the object deterministically
|
package/src/config.ts
CHANGED
|
@@ -5,6 +5,8 @@ import type { FetchOptions } from 'ofetch'
|
|
|
5
5
|
export type PluginConfig = {
|
|
6
6
|
name: string // Name of the application (ex: data.gouv.fr)
|
|
7
7
|
baseUrl: string
|
|
8
|
+
/** Hostnames allowed in Access-Control-Allow-Origin for resource preview CORS checks (e.g. data.gouv.fr). */
|
|
9
|
+
trustedDomains?: string[]
|
|
8
10
|
apiBase: string
|
|
9
11
|
devApiKey?: string | null
|
|
10
12
|
datasetQualityGuideUrl?: string
|
package/src/functions/api.ts
CHANGED
|
@@ -24,6 +24,7 @@ export async function useFetch<DataT, ErrorT = never>(
|
|
|
24
24
|
return await config.customUseFetch(url, options)
|
|
25
25
|
}
|
|
26
26
|
|
|
27
|
+
const isRaw = options?.raw
|
|
27
28
|
const data: Ref<DataT | null> = ref(null)
|
|
28
29
|
const error: Ref<ErrorT | null> = ref(null)
|
|
29
30
|
const status = ref<AsyncDataRequestStatus>('idle')
|
|
@@ -35,37 +36,39 @@ export async function useFetch<DataT, ErrorT = never>(
|
|
|
35
36
|
const query = deepToValue(options?.query)
|
|
36
37
|
status.value = 'pending'
|
|
37
38
|
try {
|
|
38
|
-
data.value =
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
config.onRequest
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
39
|
+
data.value = isRaw
|
|
40
|
+
? await ofetch<DataT | null>(urlValue, { params: params ?? query })
|
|
41
|
+
: await ofetch<DataT | null>(urlValue, {
|
|
42
|
+
baseURL: config.apiBase,
|
|
43
|
+
params: params ?? query,
|
|
44
|
+
onRequest(param) {
|
|
45
|
+
if (config.onRequest) {
|
|
46
|
+
if (Array.isArray(config.onRequest)) {
|
|
47
|
+
config.onRequest.forEach(r => r(param))
|
|
48
|
+
}
|
|
49
|
+
else {
|
|
50
|
+
config.onRequest(param)
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
const { options } = param
|
|
54
|
+
options.headers.set('Content-Type', 'application/json')
|
|
55
|
+
options.headers.set('Accept', 'application/json')
|
|
56
|
+
options.credentials = 'include'
|
|
57
|
+
if (config.devApiKey) {
|
|
58
|
+
options.headers.set('X-API-KEY', config.devApiKey)
|
|
59
|
+
}
|
|
57
60
|
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
61
|
+
if (locale) {
|
|
62
|
+
if (!options.params) {
|
|
63
|
+
options.params = {}
|
|
64
|
+
}
|
|
65
|
+
options.params['lang'] = locale
|
|
66
|
+
}
|
|
67
|
+
},
|
|
68
|
+
onRequestError: config.onRequestError,
|
|
69
|
+
onResponse: config.onResponse,
|
|
70
|
+
onResponseError: config.onResponseError,
|
|
71
|
+
})
|
|
69
72
|
status.value = 'success'
|
|
70
73
|
}
|
|
71
74
|
catch (e) {
|
|
@@ -90,9 +93,7 @@ export async function useFetch<DataT, ErrorT = never>(
|
|
|
90
93
|
|
|
91
94
|
return {
|
|
92
95
|
data,
|
|
93
|
-
refresh:
|
|
94
|
-
execute()
|
|
95
|
-
},
|
|
96
|
+
refresh: () => execute(),
|
|
96
97
|
execute,
|
|
97
98
|
clear: () => {
|
|
98
99
|
data.value = null
|
|
@@ -1,6 +1,4 @@
|
|
|
1
1
|
import { useComponentsConfig } from '../config'
|
|
2
|
-
import type { Dataset, DatasetV2 } from '../types/datasets'
|
|
3
|
-
import type { CommunityResource, Resource } from '../types/resources'
|
|
4
2
|
|
|
5
3
|
function constructUrl(baseUrl: string, path: string): string {
|
|
6
4
|
const url = new URL(baseUrl)
|
|
@@ -14,18 +12,3 @@ export function getDatasetOEmbedHtml(type: string, id: string): string {
|
|
|
14
12
|
const staticUrl = constructUrl(config.baseUrl, 'oembed.js')
|
|
15
13
|
return `<div data-udata-${type}="${id}"></div><script data-udata="${config.baseUrl}" src="${staticUrl}" async defer></script>`
|
|
16
14
|
}
|
|
17
|
-
|
|
18
|
-
export function isCommunityResource(resource: Resource | CommunityResource): boolean {
|
|
19
|
-
return 'organization' in resource || 'owner' in resource
|
|
20
|
-
}
|
|
21
|
-
|
|
22
|
-
export function getResourceExternalUrl(dataset: Dataset | DatasetV2 | Omit<Dataset, 'resources' | 'community_resources'>, resource: Resource | CommunityResource): string {
|
|
23
|
-
return `${dataset.page}${isCommunityResource(resource) ? '/community-resources' : ''}?resource_id=${resource.id}`
|
|
24
|
-
}
|
|
25
|
-
|
|
26
|
-
export function getResourceFilesize(resource: Resource): null | number {
|
|
27
|
-
if (resource.filesize) return resource.filesize
|
|
28
|
-
if ('analysis:content-length' in resource.extras) return resource.extras['analysis:content-length'] as number
|
|
29
|
-
|
|
30
|
-
return null
|
|
31
|
-
}
|
|
@@ -1,13 +1,15 @@
|
|
|
1
1
|
import { readonly, type Component } from 'vue'
|
|
2
2
|
|
|
3
3
|
import { RiEarthLine, RiMap2Line } from '@remixicon/vue'
|
|
4
|
+
import { useComponentsConfig } from '../config'
|
|
4
5
|
import Archive from '../components/Icons/Archive.vue'
|
|
5
6
|
import Code from '../components/Icons/Code.vue'
|
|
7
|
+
import type { Dataset, DatasetV2 } from '../types/datasets'
|
|
6
8
|
import Documentation from '../components/Icons/Documentation.vue'
|
|
7
9
|
import Image from '../components/Icons/Image.vue'
|
|
8
10
|
import Link from '../components/Icons/Link.vue'
|
|
9
11
|
import Table from '../components/Icons/Table.vue'
|
|
10
|
-
import type { Resource } from '../types/resources'
|
|
12
|
+
import type { CommunityResource, Resource } from '../types/resources'
|
|
11
13
|
import { useTranslation } from '../composables/useTranslation'
|
|
12
14
|
|
|
13
15
|
export function getResourceFormatIcon(format: string): Component | null {
|
|
@@ -129,3 +131,56 @@ export const detectOgcService = (resource: Resource) => {
|
|
|
129
131
|
}
|
|
130
132
|
return false
|
|
131
133
|
}
|
|
134
|
+
|
|
135
|
+
export function isCommunityResource(resource: Resource | CommunityResource): boolean {
|
|
136
|
+
return 'organization' in resource || 'owner' in resource
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
export function getResourceExternalUrl(dataset: Dataset | DatasetV2 | Omit<Dataset, 'resources' | 'community_resources'>, resource: Resource | CommunityResource): string {
|
|
140
|
+
return `${dataset.page}${isCommunityResource(resource) ? '/community-resources' : ''}?resource_id=${resource.id}`
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
export function getResourceFilesize(resource: Resource): null | number {
|
|
144
|
+
if (resource.filesize) return resource.filesize
|
|
145
|
+
if ('analysis:content-length' in resource.extras) return resource.extras['analysis:content-length'] as number
|
|
146
|
+
|
|
147
|
+
return null
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
type CorsStatus = 'allowed' | 'blocked' | 'unknown'
|
|
151
|
+
|
|
152
|
+
export const getResourceCorsStatus = (resource: Resource): CorsStatus => {
|
|
153
|
+
const extras = resource.extras
|
|
154
|
+
if (!extras || !('check:cors:allow-origin' in extras)) {
|
|
155
|
+
return 'unknown'
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
const allowOrigin = extras['check:cors:allow-origin'] as string | undefined
|
|
159
|
+
const rawMethods = extras['check:cors:allow-methods'] as string | undefined
|
|
160
|
+
|
|
161
|
+
// Check if allow-origin is '*' or contains one of our trusted domains
|
|
162
|
+
const config = useComponentsConfig()
|
|
163
|
+
const trustedDomains = config.trustedDomains ?? []
|
|
164
|
+
const hasPublicCors = allowOrigin === '*'
|
|
165
|
+
const hasSpecificCors = allowOrigin
|
|
166
|
+
? trustedDomains.some((domain) => {
|
|
167
|
+
try {
|
|
168
|
+
const hostname = new URL(allowOrigin).hostname
|
|
169
|
+
return hostname === domain || hostname.endsWith(`.${domain}`)
|
|
170
|
+
}
|
|
171
|
+
catch {
|
|
172
|
+
return false
|
|
173
|
+
}
|
|
174
|
+
})
|
|
175
|
+
: false
|
|
176
|
+
|
|
177
|
+
const isOriginAllowed = hasPublicCors || hasSpecificCors
|
|
178
|
+
|
|
179
|
+
// Ensure GET method is allowed
|
|
180
|
+
const allowedMethods = rawMethods
|
|
181
|
+
? rawMethods.split(',').map(m => m.trim().toUpperCase())
|
|
182
|
+
: []
|
|
183
|
+
const supportsGet = allowedMethods.length === 0 || allowedMethods.includes('GET')
|
|
184
|
+
|
|
185
|
+
return isOriginAllowed && supportsGet ? 'allowed' : 'blocked'
|
|
186
|
+
}
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
import type { Component } from 'vue'
|
|
2
|
+
import {
|
|
3
|
+
RiHashtag,
|
|
4
|
+
RiPriceTag3Line,
|
|
5
|
+
RiText,
|
|
6
|
+
RiCalendarLine,
|
|
7
|
+
RiCheckboxLine,
|
|
8
|
+
} from '@remixicon/vue'
|
|
9
|
+
import { useTranslation } from '../composables/useTranslation'
|
|
10
|
+
import type { ColumnFilters, ColumnType } from '../components/TabularExplorer/types'
|
|
11
|
+
|
|
12
|
+
export function hasFilterForColumn(filters: Record<string, ColumnFilters>, column: string): boolean {
|
|
13
|
+
const f = filters[column]
|
|
14
|
+
if (!f) return false
|
|
15
|
+
return !!(f.in?.length || f.exact != null || f.contains || f.null || f.min != null || f.max != null)
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
export function buildTypeConfig(t: (s: string) => string): Record<ColumnType, { icon: Component, label: string }> {
|
|
19
|
+
return {
|
|
20
|
+
number: { icon: RiHashtag, label: t('Nombre') },
|
|
21
|
+
categorical: { icon: RiPriceTag3Line, label: t('Catégoriel') },
|
|
22
|
+
text: { icon: RiText, label: t('Texte') },
|
|
23
|
+
date: { icon: RiCalendarLine, label: t('Date') },
|
|
24
|
+
boolean: { icon: RiCheckboxLine, label: t('Booléen') },
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
export function useFormatTabular() {
|
|
29
|
+
const { locale } = useTranslation()
|
|
30
|
+
|
|
31
|
+
function formatNumber(value: unknown): string {
|
|
32
|
+
const num = Number(value)
|
|
33
|
+
if (Number.isNaN(num)) return String(value)
|
|
34
|
+
return num.toLocaleString(locale)
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
function formatCellDate(value: unknown): string {
|
|
38
|
+
if (value == null || value === '') return '–'
|
|
39
|
+
const d = new Date(String(value))
|
|
40
|
+
if (Number.isNaN(d.getTime())) return String(value)
|
|
41
|
+
return new Intl.DateTimeFormat(locale, { day: '2-digit', month: '2-digit', year: 'numeric' }).format(d)
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
return { formatNumber, formatCellDate }
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
const TRUTHY_VALUES = ['true', '1', 'oui', 'yes']
|
|
48
|
+
const FALSY_VALUES = ['false', '0', 'non', 'no']
|
|
49
|
+
|
|
50
|
+
export function isTruthy(value: unknown): boolean {
|
|
51
|
+
if (typeof value === 'boolean') return value
|
|
52
|
+
if (typeof value === 'string') return TRUTHY_VALUES.includes(value.toLowerCase())
|
|
53
|
+
return Boolean(value)
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
export function isFalsy(value: unknown): boolean {
|
|
57
|
+
if (typeof value === 'boolean') return !value
|
|
58
|
+
if (typeof value === 'string') return FALSY_VALUES.includes(value.toLowerCase())
|
|
59
|
+
return !value
|
|
60
|
+
}
|
|
@@ -1,18 +1,16 @@
|
|
|
1
1
|
import { ofetch } from 'ofetch'
|
|
2
2
|
import { useComponentsConfig, type PluginConfig } from '../config'
|
|
3
|
+
import type { SortConfig } from '../components/TabularExplorer/types'
|
|
3
4
|
|
|
4
|
-
export type SortConfig
|
|
5
|
-
column: string
|
|
6
|
-
type: string
|
|
7
|
-
} | null
|
|
5
|
+
export type { SortConfig }
|
|
8
6
|
|
|
9
7
|
/**
|
|
10
8
|
* Call Tabular-api to get table content
|
|
11
9
|
*/
|
|
12
|
-
export async function getData(config: PluginConfig, id: string, page: number, sortConfig?: SortConfig) {
|
|
10
|
+
export async function getData(config: PluginConfig, id: string, page: number, sortConfig?: SortConfig | null) {
|
|
13
11
|
let url = `${config.tabularApiUrl}/api/resources/${id}/data/?page=${page}&page_size=${config.tabularApiPageSize || 15}`
|
|
14
12
|
if (sortConfig) {
|
|
15
|
-
url = url + `&${sortConfig.column}__sort=${sortConfig.
|
|
13
|
+
url = url + `&${sortConfig.column}__sort=${sortConfig.direction}`
|
|
16
14
|
}
|
|
17
15
|
return await ofetch(url)
|
|
18
16
|
}
|
package/src/main.ts
CHANGED
|
@@ -13,7 +13,7 @@ import type { License } from './types/licenses'
|
|
|
13
13
|
import type { Member, MemberRole, NewOrganization, Organization, OrganizationOrSuggest, OrganizationReference, OrganizationSuggest } from './types/organizations'
|
|
14
14
|
import type { Owned, OwnedWithFullObject, OwnedWithId } from './types/owned'
|
|
15
15
|
import type { Comment, Thread } from './types/discussions'
|
|
16
|
-
import type {
|
|
16
|
+
import type { PageBloc, ContentBloc, BlocWithTitle, DatasetsListBloc, DataservicesListBloc, ReusesListBloc, LinkInBloc, LinksListBloc, MarkdownBloc, AccordionItemBloc, AccordionListBloc, HeroBloc } from './types/pages'
|
|
17
17
|
import type { Post } from './types/posts'
|
|
18
18
|
import type { ReuseReference, NewReuse, Reuse, ReuseTopic, ReuseType } from './types/reuses'
|
|
19
19
|
import type { RegisteredSchema, Schema, SchemaDetails, SchemaField, SchemaPath, SchemaPublicationMode, SchemaResponseData, SchemaVersion, ValidataError } from './types/schemas'
|
|
@@ -24,7 +24,9 @@ import type { Weight, WellType } from './types/ui'
|
|
|
24
24
|
import type { User, UserReference } from './types/users'
|
|
25
25
|
import type { Report, ReportSubject, ReportReason } from './types/reports'
|
|
26
26
|
import type { GlobalSearchConfig, SearchType, SortOption } from './types/search'
|
|
27
|
-
import { getDefaultDatasetConfig, getDefaultDataserviceConfig, getDefaultReuseConfig, getDefaultOrganizationConfig, getDefaultGlobalSearchConfig, defaultDatasetSortOptions, defaultDataserviceSortOptions, defaultReuseSortOptions, defaultOrganizationSortOptions } from './types/search'
|
|
27
|
+
import { getDefaultDatasetConfig, getDefaultDataserviceConfig, getDefaultReuseConfig, getDefaultOrganizationConfig, getDefaultTopicConfig, getDefaultGlobalSearchConfig, defaultDatasetSortOptions, defaultDataserviceSortOptions, defaultReuseSortOptions, defaultOrganizationSortOptions } from './types/search'
|
|
28
|
+
import { useSearchFilter } from './composables/useSearchFilter'
|
|
29
|
+
import type { UseSearchFilterOptions } from './composables/useSearchFilter'
|
|
28
30
|
|
|
29
31
|
import ActivityList from './components/ActivityList/ActivityList.vue'
|
|
30
32
|
import UserActivityList from './components/ActivityList/UserActivityList.vue'
|
|
@@ -73,7 +75,7 @@ import ResourceIcon from './components/ResourceAccordion/ResourceIcon.vue'
|
|
|
73
75
|
import ResourceExplorer from './components/ResourceExplorer/ResourceExplorer.vue'
|
|
74
76
|
import ResourceExplorerSidebar from './components/ResourceExplorer/ResourceExplorerSidebar.vue'
|
|
75
77
|
import ResourceExplorerViewer from './components/ResourceExplorer/ResourceExplorerViewer.vue'
|
|
76
|
-
import
|
|
78
|
+
import OpenApiViewer from './components/OpenApiViewer/OpenApiViewer.vue'
|
|
77
79
|
import ReuseCard from './components/ReuseCard.vue'
|
|
78
80
|
import ReuseHorizontalCard from './components/ReuseHorizontalCard.vue'
|
|
79
81
|
import ReuseDetails from './components/ReuseDetails.vue'
|
|
@@ -94,6 +96,8 @@ import GlobalSearch from './components/Search/GlobalSearch.vue'
|
|
|
94
96
|
import SearchInput from './components/Search/SearchInput.vue'
|
|
95
97
|
import SearchableSelect from './components/Form/SearchableSelect.vue'
|
|
96
98
|
import SelectGroup from './components/Form/SelectGroup.vue'
|
|
99
|
+
import InfiniteLoader from './components/InfiniteLoader.vue'
|
|
100
|
+
import TabularExplorer from './components/TabularExplorer/TabularExplorer.vue'
|
|
97
101
|
import type { UseFetchFunction } from './functions/api.types'
|
|
98
102
|
import { configKey, useComponentsConfig, type PluginConfig } from './config.js'
|
|
99
103
|
|
|
@@ -116,10 +120,10 @@ export * from './functions/metrics'
|
|
|
116
120
|
export * from './functions/never'
|
|
117
121
|
export * from './functions/organizations'
|
|
118
122
|
export * from './functions/owned'
|
|
119
|
-
export * from './functions/pagination'
|
|
120
123
|
export * from './functions/resources'
|
|
121
124
|
export * from './functions/reuses'
|
|
122
125
|
export * from './functions/schemas'
|
|
126
|
+
export * from './functions/tabular'
|
|
123
127
|
export * from './functions/users'
|
|
124
128
|
export * from './types/access_types'
|
|
125
129
|
|
|
@@ -127,6 +131,7 @@ export type {
|
|
|
127
131
|
GlobalSearchConfig,
|
|
128
132
|
SearchType,
|
|
129
133
|
SortOption,
|
|
134
|
+
UseSearchFilterOptions,
|
|
130
135
|
UseFetchFunction,
|
|
131
136
|
AccessType,
|
|
132
137
|
AccessAudience,
|
|
@@ -168,7 +173,6 @@ export type {
|
|
|
168
173
|
Owned,
|
|
169
174
|
OwnedWithFullObject,
|
|
170
175
|
OwnedWithId,
|
|
171
|
-
Page,
|
|
172
176
|
PageBloc,
|
|
173
177
|
ContentBloc,
|
|
174
178
|
BlocWithTitle,
|
|
@@ -225,11 +229,13 @@ export {
|
|
|
225
229
|
getDefaultDataserviceConfig,
|
|
226
230
|
getDefaultReuseConfig,
|
|
227
231
|
getDefaultOrganizationConfig,
|
|
232
|
+
getDefaultTopicConfig,
|
|
228
233
|
getDefaultGlobalSearchConfig,
|
|
229
234
|
defaultDatasetSortOptions,
|
|
230
235
|
defaultDataserviceSortOptions,
|
|
231
236
|
defaultReuseSortOptions,
|
|
232
237
|
defaultOrganizationSortOptions,
|
|
238
|
+
useSearchFilter,
|
|
233
239
|
}
|
|
234
240
|
|
|
235
241
|
// Vue Plugin
|
|
@@ -303,7 +309,7 @@ export {
|
|
|
303
309
|
SimpleBanner,
|
|
304
310
|
SmallChart,
|
|
305
311
|
StatBox,
|
|
306
|
-
|
|
312
|
+
OpenApiViewer,
|
|
307
313
|
Tab,
|
|
308
314
|
TabGroup,
|
|
309
315
|
TabList,
|
|
@@ -318,4 +324,6 @@ export {
|
|
|
318
324
|
SearchInput,
|
|
319
325
|
SearchableSelect,
|
|
320
326
|
SelectGroup,
|
|
327
|
+
InfiniteLoader,
|
|
328
|
+
TabularExplorer,
|
|
321
329
|
}
|
|
@@ -24,6 +24,7 @@ export type BaseDataservice = Owned & WithAccessType & {
|
|
|
24
24
|
license: string | null
|
|
25
25
|
private: boolean
|
|
26
26
|
rate_limiting: string
|
|
27
|
+
rate_limiting_url: string | null
|
|
27
28
|
title: DataserviceReference['title']
|
|
28
29
|
contact_points: Array<ContactPoint>
|
|
29
30
|
}
|
|
@@ -65,6 +66,7 @@ export type Dataservice = Owned & WithAccessType & {
|
|
|
65
66
|
permissions: { edit: boolean, delete: boolean }
|
|
66
67
|
private: boolean
|
|
67
68
|
rate_limiting: string
|
|
69
|
+
rate_limiting_url: string | null
|
|
68
70
|
self_api_url: DataserviceReference['self_api_url']
|
|
69
71
|
self_web_url: DataserviceReference['self_web_url']
|
|
70
72
|
slug: string
|
package/src/types/pages.ts
CHANGED
|
@@ -2,11 +2,6 @@ import type { DatasetV2 } from './datasets'
|
|
|
2
2
|
import type { Dataservice } from './dataservices'
|
|
3
3
|
import type { Reuse } from './reuses'
|
|
4
4
|
|
|
5
|
-
export type Page = {
|
|
6
|
-
id: string
|
|
7
|
-
blocs: Array<PageBloc>
|
|
8
|
-
}
|
|
9
|
-
|
|
10
5
|
export type BlocWithTitle = {
|
|
11
6
|
title: string
|
|
12
7
|
subtitle: string | null
|
package/src/types/posts.ts
CHANGED
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
import type { Dataset } from './datasets'
|
|
2
|
-
import type {
|
|
2
|
+
import type { PageBloc } from './pages'
|
|
3
3
|
import type { Reuse } from './reuses'
|
|
4
4
|
import type { User } from './users'
|
|
5
5
|
|
|
6
6
|
export type Post = {
|
|
7
7
|
body_type: 'markdown' | 'html' | 'blocs'
|
|
8
|
+
blocs: Array<PageBloc>
|
|
8
9
|
content: string
|
|
9
|
-
content_as_page: Page | null
|
|
10
10
|
created_at: string
|
|
11
11
|
credit_to: string
|
|
12
12
|
credit_url: string
|
package/src/types/reports.ts
CHANGED
|
@@ -16,11 +16,14 @@ export type Report = {
|
|
|
16
16
|
id: string
|
|
17
17
|
by: User | null
|
|
18
18
|
subject: ReportSubject | null
|
|
19
|
+
subject_embed_id: string | null
|
|
19
20
|
reason: ReportReasonValue
|
|
20
21
|
message: string
|
|
21
22
|
reported_at: string
|
|
22
23
|
self_api_url: string
|
|
23
24
|
subject_deleted_at: string | null
|
|
25
|
+
subject_deleted_by: User | null
|
|
26
|
+
subject_label: string | null
|
|
24
27
|
dismissed_at: string | null
|
|
25
28
|
dismissed_by: User | null
|
|
26
29
|
}
|
package/src/types/search.ts
CHANGED
|
@@ -1,5 +1,10 @@
|
|
|
1
1
|
import type { PaginatedArray } from './api'
|
|
2
2
|
import type { AccessType } from './access_types'
|
|
3
|
+
import type { Dataset } from './datasets'
|
|
4
|
+
import type { Dataservice } from './dataservices'
|
|
5
|
+
import type { Organization } from './organizations'
|
|
6
|
+
import type { Reuse } from './reuses'
|
|
7
|
+
import type { TopicV2 } from './topics'
|
|
3
8
|
import type {
|
|
4
9
|
CERTIFIED,
|
|
5
10
|
PUBLIC_SERVICE,
|
|
@@ -291,7 +296,9 @@ export type SortOption<Sort extends string> = {
|
|
|
291
296
|
|
|
292
297
|
export type DatasetSearchConfig = {
|
|
293
298
|
class: 'datasets'
|
|
299
|
+
key?: string
|
|
294
300
|
name?: string
|
|
301
|
+
placeholder?: string | null
|
|
295
302
|
hiddenFilters?: HiddenFilter<DatasetSearchFilters>[]
|
|
296
303
|
basicFilters?: (keyof DatasetSearchFilters)[]
|
|
297
304
|
advancedFilters?: (keyof DatasetSearchFilters)[]
|
|
@@ -300,7 +307,9 @@ export type DatasetSearchConfig = {
|
|
|
300
307
|
|
|
301
308
|
export type DataserviceSearchConfig = {
|
|
302
309
|
class: 'dataservices'
|
|
310
|
+
key?: string
|
|
303
311
|
name?: string
|
|
312
|
+
placeholder?: string | null
|
|
304
313
|
hiddenFilters?: HiddenFilter<DataserviceSearchFilters>[]
|
|
305
314
|
basicFilters?: (keyof DataserviceSearchFilters)[]
|
|
306
315
|
advancedFilters?: (keyof DataserviceSearchFilters)[]
|
|
@@ -309,7 +318,9 @@ export type DataserviceSearchConfig = {
|
|
|
309
318
|
|
|
310
319
|
export type ReuseSearchConfig = {
|
|
311
320
|
class: 'reuses'
|
|
321
|
+
key?: string
|
|
312
322
|
name?: string
|
|
323
|
+
placeholder?: string | null
|
|
313
324
|
hiddenFilters?: HiddenFilter<ReuseSearchFilters>[]
|
|
314
325
|
basicFilters?: (keyof ReuseSearchFilters)[]
|
|
315
326
|
advancedFilters?: (keyof ReuseSearchFilters)[]
|
|
@@ -318,19 +329,41 @@ export type ReuseSearchConfig = {
|
|
|
318
329
|
|
|
319
330
|
export type OrganizationSearchConfig = {
|
|
320
331
|
class: 'organizations'
|
|
332
|
+
key?: string
|
|
321
333
|
name?: string
|
|
334
|
+
placeholder?: string | null
|
|
322
335
|
hiddenFilters?: HiddenFilter<OrganizationSearchFilters>[]
|
|
323
336
|
basicFilters?: (keyof OrganizationSearchFilters)[]
|
|
324
337
|
advancedFilters?: (keyof OrganizationSearchFilters)[]
|
|
325
338
|
sortOptions?: SortOption<OrganizationSearchSort>[]
|
|
326
339
|
}
|
|
327
340
|
|
|
328
|
-
export type
|
|
341
|
+
export type TopicSearchConfig = {
|
|
342
|
+
class: 'topics'
|
|
343
|
+
key?: string
|
|
344
|
+
name?: string
|
|
345
|
+
placeholder?: string | null
|
|
346
|
+
hiddenFilters?: HiddenFilter<TopicSearchFilters>[]
|
|
347
|
+
basicFilters?: (keyof TopicSearchFilters)[]
|
|
348
|
+
advancedFilters?: (keyof TopicSearchFilters)[]
|
|
349
|
+
sortOptions?: SortOption<TopicSearchSort>[]
|
|
350
|
+
}
|
|
351
|
+
|
|
352
|
+
export type SearchTypeConfig = DatasetSearchConfig | DataserviceSearchConfig | ReuseSearchConfig | OrganizationSearchConfig | TopicSearchConfig
|
|
329
353
|
|
|
330
354
|
export type SearchType = SearchTypeConfig['class']
|
|
331
355
|
|
|
332
356
|
export type GlobalSearchConfig = SearchTypeConfig[]
|
|
333
357
|
|
|
358
|
+
// Maps each search class to its concrete response shape.
|
|
359
|
+
export type SearchResponseByClass = {
|
|
360
|
+
datasets: DatasetSearchResponse<Dataset>
|
|
361
|
+
dataservices: DataserviceSearchResponse<Dataservice>
|
|
362
|
+
reuses: ReuseSearchResponse<Reuse>
|
|
363
|
+
organizations: OrganizationSearchResponse<Organization>
|
|
364
|
+
topics: TopicSearchResponse<TopicV2>
|
|
365
|
+
}
|
|
366
|
+
|
|
334
367
|
// Helper functions for default configs
|
|
335
368
|
|
|
336
369
|
export const defaultDatasetSortOptions: SortOption<DatasetSearchSort>[] = [
|
|
@@ -397,11 +430,27 @@ export function getDefaultOrganizationConfig(overrides?: Partial<Omit<Organizati
|
|
|
397
430
|
}
|
|
398
431
|
}
|
|
399
432
|
|
|
433
|
+
export const defaultTopicSortOptions: SortOption<TopicSearchSort>[] = [
|
|
434
|
+
{ value: '-created', label: 'Date de création' },
|
|
435
|
+
{ value: '-last_modified', label: 'Dernière mise à jour' },
|
|
436
|
+
]
|
|
437
|
+
|
|
438
|
+
export function getDefaultTopicConfig(overrides?: Partial<Omit<TopicSearchConfig, 'class'>>): TopicSearchConfig {
|
|
439
|
+
return {
|
|
440
|
+
class: 'topics',
|
|
441
|
+
basicFilters: ['last_update_range', 'producer_type'],
|
|
442
|
+
advancedFilters: ['organization', 'tag'],
|
|
443
|
+
sortOptions: defaultTopicSortOptions,
|
|
444
|
+
...overrides,
|
|
445
|
+
}
|
|
446
|
+
}
|
|
447
|
+
|
|
400
448
|
export function getDefaultGlobalSearchConfig(): GlobalSearchConfig {
|
|
401
449
|
return [
|
|
402
450
|
getDefaultDatasetConfig(),
|
|
403
451
|
getDefaultDataserviceConfig(),
|
|
404
452
|
getDefaultReuseConfig(),
|
|
405
453
|
getDefaultOrganizationConfig(),
|
|
454
|
+
getDefaultTopicConfig(),
|
|
406
455
|
]
|
|
407
456
|
}
|
package/src/types/site.ts
CHANGED
|
@@ -1,9 +1,11 @@
|
|
|
1
|
+
import type { PageBloc } from './pages'
|
|
2
|
+
|
|
1
3
|
export type Site = {
|
|
2
4
|
id: string
|
|
3
5
|
title: string
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
6
|
+
datasets_blocs: Array<PageBloc>
|
|
7
|
+
reuses_blocs: Array<PageBloc>
|
|
8
|
+
dataservices_blocs: Array<PageBloc>
|
|
7
9
|
version: string
|
|
8
10
|
metrics: {
|
|
9
11
|
'dataservices': number
|