@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.
Files changed (77) hide show
  1. package/assets/main.css +4 -0
  2. package/dist/Datafair.client-4zDFMXaE.js +30 -0
  3. package/dist/JsonPreview.client-DSKs3Wg7.js +40 -0
  4. package/dist/{MapContainer.client-DRkAmdOc.js → MapContainer.client-Cjwpar3k.js} +35 -38
  5. package/dist/{PdfPreview.client-C-w6-w44.js → PdfPreview.client-6H3KMLOL.js} +822 -865
  6. package/dist/{Pmtiles.client-BR7_ldHY.js → Pmtiles.client-BDAAb3_H.js} +574 -579
  7. package/dist/PreviewWrapper.vue_vue_type_script_setup_true_lang-BFSXn0mv.js +61 -0
  8. package/dist/XmlPreview.client-DnL8nMyL.js +34 -0
  9. package/dist/components-next.css +3 -3
  10. package/dist/components-next.js +140 -131
  11. package/dist/components.css +1 -1
  12. package/dist/{index-SrYZwgCT.js → index-7yWP5a5K.js} +1 -1
  13. package/dist/main-C-0Gkcks.js +73004 -0
  14. package/dist/{vue3-xml-viewer.common-BRxsqI9j.js → vue3-xml-viewer.common-xigir_CG.js} +1 -1
  15. package/package.json +14 -9
  16. package/src/components/ActivityList/ActivityList.vue +0 -2
  17. package/src/components/Form/SearchableSelect.vue +2 -1
  18. package/src/components/InfiniteLoader.vue +53 -0
  19. package/src/components/OpenApiViewer/ContentTypeSelect.vue +48 -0
  20. package/src/components/OpenApiViewer/EndpointRequest.vue +164 -0
  21. package/src/components/OpenApiViewer/EndpointResponses.vue +149 -0
  22. package/src/components/OpenApiViewer/OpenApiViewer.vue +308 -0
  23. package/src/components/OpenApiViewer/SchemaPanel.vue +53 -0
  24. package/src/components/OpenApiViewer/SchemaTree.vue +77 -0
  25. package/src/components/OpenApiViewer/openapi.ts +150 -0
  26. package/src/components/OrganizationNameWithCertificate.vue +3 -2
  27. package/src/components/Pagination.vue +8 -5
  28. package/src/components/ReadMore.vue +1 -1
  29. package/src/components/ResourceAccordion/Datafair.client.vue +4 -10
  30. package/src/components/ResourceAccordion/JsonPreview.client.vue +23 -121
  31. package/src/components/ResourceAccordion/MapContainer.client.vue +7 -11
  32. package/src/components/ResourceAccordion/Metadata.vue +1 -2
  33. package/src/components/ResourceAccordion/PdfPreview.client.vue +24 -103
  34. package/src/components/ResourceAccordion/Pmtiles.client.vue +5 -10
  35. package/src/components/ResourceAccordion/Preview.vue +15 -20
  36. package/src/components/ResourceAccordion/PreviewLoader.vue +1 -2
  37. package/src/components/ResourceAccordion/PreviewUnavailable.vue +22 -0
  38. package/src/components/ResourceAccordion/PreviewWrapper.vue +82 -0
  39. package/src/components/ResourceAccordion/ResourceAccordion.vue +5 -7
  40. package/src/components/ResourceAccordion/XmlPreview.client.vue +16 -115
  41. package/src/components/ResourceExplorer/ResourceExplorer.vue +81 -13
  42. package/src/components/ResourceExplorer/ResourceExplorerSidebar.vue +2 -2
  43. package/src/components/ResourceExplorer/ResourceExplorerViewer.vue +30 -11
  44. package/src/components/Search/GlobalSearch.vue +164 -107
  45. package/src/components/TabularExplorer/TabularCell.vue +51 -0
  46. package/src/components/TabularExplorer/TabularCellPopover.vue +170 -0
  47. package/src/components/TabularExplorer/TabularExplorer.vue +870 -0
  48. package/src/components/TabularExplorer/TabularFilterContent.vue +351 -0
  49. package/src/components/TabularExplorer/TabularFilterPopover.vue +111 -0
  50. package/src/components/TabularExplorer/types.ts +83 -0
  51. package/src/composables/useResourceCapabilities.ts +1 -1
  52. package/src/composables/useSearchFilter.ts +90 -0
  53. package/src/composables/useStableQueryParams.ts +28 -3
  54. package/src/config.ts +2 -0
  55. package/src/functions/api.ts +34 -33
  56. package/src/functions/api.types.ts +1 -0
  57. package/src/functions/datasets.ts +0 -17
  58. package/src/functions/resources.ts +56 -1
  59. package/src/functions/tabular.ts +60 -0
  60. package/src/functions/tabularApi.ts +4 -6
  61. package/src/main.ts +14 -6
  62. package/src/types/dataservices.ts +2 -0
  63. package/src/types/pages.ts +0 -5
  64. package/src/types/posts.ts +2 -2
  65. package/src/types/reports.ts +3 -0
  66. package/src/types/search.ts +45 -1
  67. package/src/types/site.ts +5 -3
  68. package/src/types/users.ts +0 -1
  69. package/assets/swagger-themes/newspaper.css +0 -1670
  70. package/dist/Datafair.client-E5D6ePRC.js +0 -35
  71. package/dist/JsonPreview.client-C-6eBbPw.js +0 -87
  72. package/dist/Swagger.client-D4-F6yEf.js +0 -4
  73. package/dist/XmlPreview.client-Dl2VCgXF.js +0 -79
  74. package/dist/main-B2kXxWRG.js +0 -105833
  75. package/src/components/ResourceAccordion/Swagger.client.vue +0 -48
  76. package/src/functions/pagination.ts +0 -9
  77. /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
@@ -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 = await ofetch<DataT | null>(urlValue, {
39
- baseURL: config.apiBase,
40
- params: params ?? query,
41
- onRequest(param) {
42
- if (config.onRequest) {
43
- if (Array.isArray(config.onRequest)) {
44
- config.onRequest.forEach(r => r(param))
45
- }
46
- else {
47
- config.onRequest(param)
48
- }
49
- }
50
- const { options } = param
51
- options.headers.set('Content-Type', 'application/json')
52
- options.headers.set('Accept', 'application/json')
53
- options.credentials = 'include'
54
- if (config.devApiKey) {
55
- options.headers.set('X-API-KEY', config.devApiKey)
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
- if (locale) {
59
- if (!options.params) {
60
- options.params = {}
61
- }
62
- options.params['lang'] = locale
63
- }
64
- },
65
- onRequestError: config.onRequestError,
66
- onResponse: config.onResponse,
67
- onResponseError: config.onResponseError,
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: async () => {
94
- execute()
95
- },
96
+ refresh: () => execute(),
96
97
  execute,
97
98
  clear: () => {
98
99
  data.value = null
@@ -20,6 +20,7 @@ export type UseFetchOptions<DataT> = {
20
20
  transform?: (input: DataT) => DataT | Promise<DataT>
21
21
  pick?: string[]
22
22
  watch?: WatchSource[] | false
23
+ raw?: boolean
23
24
  }
24
25
 
25
26
  export type AsyncData<DataT, ErrorT> = {
@@ -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.type}`
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 { Page, PageBloc, ContentBloc, BlocWithTitle, DatasetsListBloc, DataservicesListBloc, ReusesListBloc, LinkInBloc, LinksListBloc, MarkdownBloc, AccordionItemBloc, AccordionListBloc, HeroBloc } from './types/pages'
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 Swagger from './components/ResourceAccordion/Swagger.client.vue'
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
- Swagger,
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
@@ -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
@@ -1,12 +1,12 @@
1
1
  import type { Dataset } from './datasets'
2
- import type { Page } from './pages'
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
@@ -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
  }
@@ -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 SearchTypeConfig = DatasetSearchConfig | DataserviceSearchConfig | ReuseSearchConfig | OrganizationSearchConfig
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
- datasets_page: string | null
5
- reuses_page: string | null
6
- dataservices_page: string | null
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
@@ -21,7 +21,6 @@ export type User = {
21
21
  page: UserReference['page']
22
22
  avatar: UserReference['avatar']
23
23
  avatar_thumbnail: UserReference['avatar_thumbnail']
24
- apikey?: string
25
24
  email?: string
26
25
  about: string
27
26
  website?: string