@datagouv/components-next 1.0.2-dev.6 → 1.0.2-dev.60
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-4zDFMXaE.js +30 -0
- package/dist/JsonPreview.client-DSKs3Wg7.js +40 -0
- package/dist/{MapContainer.client-DRkAmdOc.js → MapContainer.client-Cjwpar3k.js} +35 -38
- package/dist/{PdfPreview.client-C-w6-w44.js → PdfPreview.client-6H3KMLOL.js} +822 -865
- package/dist/{Pmtiles.client-BR7_ldHY.js → Pmtiles.client-BDAAb3_H.js} +574 -579
- package/dist/PreviewWrapper.vue_vue_type_script_setup_true_lang-BFSXn0mv.js +61 -0
- package/dist/XmlPreview.client-DnL8nMyL.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-7yWP5a5K.js} +1 -1
- package/dist/main-C-0Gkcks.js +73004 -0
- package/dist/{vue3-xml-viewer.common-BRxsqI9j.js → vue3-xml-viewer.common-xigir_CG.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 +164 -107
- 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 +45 -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,6 +296,7 @@ export type SortOption<Sort extends string> = {
|
|
|
291
296
|
|
|
292
297
|
export type DatasetSearchConfig = {
|
|
293
298
|
class: 'datasets'
|
|
299
|
+
key?: string
|
|
294
300
|
name?: string
|
|
295
301
|
hiddenFilters?: HiddenFilter<DatasetSearchFilters>[]
|
|
296
302
|
basicFilters?: (keyof DatasetSearchFilters)[]
|
|
@@ -300,6 +306,7 @@ export type DatasetSearchConfig = {
|
|
|
300
306
|
|
|
301
307
|
export type DataserviceSearchConfig = {
|
|
302
308
|
class: 'dataservices'
|
|
309
|
+
key?: string
|
|
303
310
|
name?: string
|
|
304
311
|
hiddenFilters?: HiddenFilter<DataserviceSearchFilters>[]
|
|
305
312
|
basicFilters?: (keyof DataserviceSearchFilters)[]
|
|
@@ -309,6 +316,7 @@ export type DataserviceSearchConfig = {
|
|
|
309
316
|
|
|
310
317
|
export type ReuseSearchConfig = {
|
|
311
318
|
class: 'reuses'
|
|
319
|
+
key?: string
|
|
312
320
|
name?: string
|
|
313
321
|
hiddenFilters?: HiddenFilter<ReuseSearchFilters>[]
|
|
314
322
|
basicFilters?: (keyof ReuseSearchFilters)[]
|
|
@@ -318,6 +326,7 @@ export type ReuseSearchConfig = {
|
|
|
318
326
|
|
|
319
327
|
export type OrganizationSearchConfig = {
|
|
320
328
|
class: 'organizations'
|
|
329
|
+
key?: string
|
|
321
330
|
name?: string
|
|
322
331
|
hiddenFilters?: HiddenFilter<OrganizationSearchFilters>[]
|
|
323
332
|
basicFilters?: (keyof OrganizationSearchFilters)[]
|
|
@@ -325,12 +334,31 @@ export type OrganizationSearchConfig = {
|
|
|
325
334
|
sortOptions?: SortOption<OrganizationSearchSort>[]
|
|
326
335
|
}
|
|
327
336
|
|
|
328
|
-
export type
|
|
337
|
+
export type TopicSearchConfig = {
|
|
338
|
+
class: 'topics'
|
|
339
|
+
key?: string
|
|
340
|
+
name?: string
|
|
341
|
+
hiddenFilters?: HiddenFilter<TopicSearchFilters>[]
|
|
342
|
+
basicFilters?: (keyof TopicSearchFilters)[]
|
|
343
|
+
advancedFilters?: (keyof TopicSearchFilters)[]
|
|
344
|
+
sortOptions?: SortOption<TopicSearchSort>[]
|
|
345
|
+
}
|
|
346
|
+
|
|
347
|
+
export type SearchTypeConfig = DatasetSearchConfig | DataserviceSearchConfig | ReuseSearchConfig | OrganizationSearchConfig | TopicSearchConfig
|
|
329
348
|
|
|
330
349
|
export type SearchType = SearchTypeConfig['class']
|
|
331
350
|
|
|
332
351
|
export type GlobalSearchConfig = SearchTypeConfig[]
|
|
333
352
|
|
|
353
|
+
// Maps each search class to its concrete response shape.
|
|
354
|
+
export type SearchResponseByClass = {
|
|
355
|
+
datasets: DatasetSearchResponse<Dataset>
|
|
356
|
+
dataservices: DataserviceSearchResponse<Dataservice>
|
|
357
|
+
reuses: ReuseSearchResponse<Reuse>
|
|
358
|
+
organizations: OrganizationSearchResponse<Organization>
|
|
359
|
+
topics: TopicSearchResponse<TopicV2>
|
|
360
|
+
}
|
|
361
|
+
|
|
334
362
|
// Helper functions for default configs
|
|
335
363
|
|
|
336
364
|
export const defaultDatasetSortOptions: SortOption<DatasetSearchSort>[] = [
|
|
@@ -397,11 +425,27 @@ export function getDefaultOrganizationConfig(overrides?: Partial<Omit<Organizati
|
|
|
397
425
|
}
|
|
398
426
|
}
|
|
399
427
|
|
|
428
|
+
export const defaultTopicSortOptions: SortOption<TopicSearchSort>[] = [
|
|
429
|
+
{ value: '-created', label: 'Date de création' },
|
|
430
|
+
{ value: '-last_modified', label: 'Dernière mise à jour' },
|
|
431
|
+
]
|
|
432
|
+
|
|
433
|
+
export function getDefaultTopicConfig(overrides?: Partial<Omit<TopicSearchConfig, 'class'>>): TopicSearchConfig {
|
|
434
|
+
return {
|
|
435
|
+
class: 'topics',
|
|
436
|
+
basicFilters: ['last_update_range', 'producer_type'],
|
|
437
|
+
advancedFilters: ['organization', 'tag'],
|
|
438
|
+
sortOptions: defaultTopicSortOptions,
|
|
439
|
+
...overrides,
|
|
440
|
+
}
|
|
441
|
+
}
|
|
442
|
+
|
|
400
443
|
export function getDefaultGlobalSearchConfig(): GlobalSearchConfig {
|
|
401
444
|
return [
|
|
402
445
|
getDefaultDatasetConfig(),
|
|
403
446
|
getDefaultDataserviceConfig(),
|
|
404
447
|
getDefaultReuseConfig(),
|
|
405
448
|
getDefaultOrganizationConfig(),
|
|
449
|
+
getDefaultTopicConfig(),
|
|
406
450
|
]
|
|
407
451
|
}
|
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
|