@datagouv/components-next 1.0.2-dev.13 → 1.0.2-dev.130
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/{Control-DuZJdKV_.js → Control-b-43UT2B.js} +10 -10
- package/dist/{Datafair.client-8haHXl47.js → Datafair.client-CFl7MKdB.js} +4 -4
- package/dist/{Event--kp8kMdJ.js → Event-py_AAwJY.js} +35 -35
- package/dist/{Image-34hvypZI.js → Image-BHQYZWzH.js} +23 -23
- package/dist/JsonPreview.client-CowJU7XP.js +40 -0
- package/dist/{Map-BjUnLyj8.js → Map-CtP7leHl.js} +1742 -1602
- package/dist/{MapContainer.client-l6HuXTHR.js → MapContainer.client-CgdBWq2S.js} +37 -38
- package/dist/{OSM-s40W6sQ2.js → OSM-gdDSIsDA.js} +15 -23
- package/dist/{PdfPreview.client-4OueK-2Z.js → PdfPreview.client-Dm4l4fyb.js} +823 -855
- package/dist/Pmtiles.client-DsjzH41p.js +26123 -0
- package/dist/PreviewWrapper.vue_vue_type_script_setup_true_lang-BgFVrfFi.js +61 -0
- package/dist/{ScaleLine-KW-nXqp3.js → ScaleLine-hG-FhNqy.js} +2 -2
- package/dist/{Tile-DbNFNPfU.js → Tile-D6rFonpT.js} +164 -167
- package/dist/{TileImage-BsXBxMtq.js → TileImage-cfrEgz5y.js} +131 -119
- package/dist/{View-BR92hTWP.js → View-vp7vDb9q.js} +482 -421
- package/dist/XmlPreview.client-DBceFnA1.js +34 -0
- package/dist/{common-PJfpC179.js → common-C8efyyuc.js} +57 -57
- package/dist/components-next.css +1 -52
- package/dist/components-next.js +172 -143
- package/dist/components.css +2 -2
- package/dist/{index-CVTIoZQ0.js → index-DWmJJEjk.js} +21855 -15827
- package/dist/{leaflet-src-7m1mB8LI.js → leaflet-src-CGmV0V-K.js} +162 -164
- package/dist/main-CQREzlRN.js +72634 -0
- package/dist/{proj-DsetBcW7.js → proj-CsjVeojN.js} +663 -627
- package/dist/{text-clamp.esm-B5kW_XMt.js → text-clamp.esm-DVseBqZZ.js} +4 -4
- package/dist/{tilecoord-Db24Px13.js → tilecoord-CPcQJnrF.js} +258 -249
- package/dist/{vue3-json-viewer-BXwup7nO.js → vue3-json-viewer-CJLQFzXZ.js} +10 -16
- package/dist/{vue3-xml-viewer.common-CWer_T5-.js → vue3-xml-viewer.common-d08lX8ER.js} +1 -1
- package/package.json +50 -36
- package/src/chart.ts +5 -0
- package/src/components/ActivityList/ActivityList.vue +3 -2
- package/src/components/Chart/ChartViewer.vue +227 -0
- package/src/components/Chart/ChartViewerWrapper.vue +174 -0
- package/src/components/DataserviceCard.vue +3 -0
- package/src/components/DatasetCard.vue +9 -4
- package/src/components/Form/Listbox.vue +101 -0
- package/src/components/Form/SearchableSelect.vue +2 -1
- package/src/components/InfiniteLoader.vue +53 -0
- package/src/components/ObjectCardHeader.vue +11 -4
- 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/RadioInput.vue +7 -2
- package/src/components/ReadMore.vue +1 -1
- package/src/components/ResourceAccordion/DataStructure.vue +11 -33
- package/src/components/ResourceAccordion/Downloads.vue +160 -0
- package/src/components/ResourceAccordion/JsonPreview.client.vue +23 -104
- package/src/components/ResourceAccordion/MapContainer.client.vue +1 -3
- package/src/components/ResourceAccordion/Metadata.vue +1 -2
- package/src/components/ResourceAccordion/PdfPreview.client.vue +24 -87
- package/src/components/ResourceAccordion/Pmtiles.client.vue +3 -1
- package/src/components/ResourceAccordion/Preview.vue +11 -11
- package/src/components/ResourceAccordion/PreviewWrapper.vue +82 -0
- package/src/components/ResourceAccordion/ResourceAccordion.vue +12 -111
- package/src/components/ResourceAccordion/XmlPreview.client.vue +16 -98
- package/src/components/ResourceExplorer/ResourceExplorer.vue +22 -19
- package/src/components/ResourceExplorer/ResourceExplorerSidebar.vue +2 -2
- package/src/components/ResourceExplorer/ResourceExplorerViewer.vue +50 -148
- package/src/components/ResourceExplorer/ResourceSelector.vue +113 -0
- package/src/components/ReuseCard.vue +12 -4
- package/src/components/Search/GlobalSearch.vue +217 -115
- package/src/components/Search/SearchInput.vue +5 -4
- package/src/components/TabularExplorer/TabularCell.vue +51 -0
- package/src/components/TabularExplorer/TabularCellPopover.vue +170 -0
- package/src/components/TabularExplorer/TabularExplorer.vue +1106 -0
- package/src/components/TabularExplorer/TabularFilterContent.vue +353 -0
- package/src/components/TabularExplorer/TabularFilterPopover.vue +106 -0
- package/src/components/TabularExplorer/types.ts +86 -0
- package/src/composables/useHasTabularData.ts +13 -0
- package/src/composables/useMetrics.ts +1 -1
- package/src/composables/useResourceCapabilities.ts +1 -1
- package/src/composables/useSearchFilter.ts +120 -0
- package/src/composables/useStableQueryParams.ts +38 -6
- package/src/composables/useTabularProfile.ts +70 -0
- package/src/config.ts +20 -3
- package/src/functions/activities.ts +3 -3
- package/src/functions/api.ts +20 -42
- package/src/functions/api.types.ts +1 -0
- package/src/functions/charts.ts +79 -0
- package/src/functions/datasets.ts +0 -17
- package/src/functions/metrics.ts +6 -4
- package/src/functions/resources.ts +61 -1
- package/src/functions/tabular.ts +171 -0
- package/src/functions/tabularApi.ts +96 -11
- package/src/main.ts +95 -9
- 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 +5 -1
- package/src/types/resources.ts +0 -1
- package/src/types/search.ts +63 -1
- package/src/types/site.ts +5 -3
- package/src/types/ui.ts +2 -0
- package/src/types/users.ts +2 -1
- package/src/types/visualizations.ts +93 -0
- package/assets/swagger-themes/newspaper.css +0 -1670
- package/dist/JsonPreview.client-D53pj9Cw.js +0 -72
- package/dist/Pmtiles.client-4j3VTYkz.js +0 -25185
- package/dist/Swagger.client-DPBmsH9q.js +0 -4
- package/dist/XmlPreview.client-XElkoA4F.js +0 -64
- package/dist/main-BbT-LUXy.js +0 -105854
- package/src/components/ResourceAccordion/Swagger.client.vue +0 -48
- package/src/functions/pagination.ts +0 -9
|
@@ -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 {
|
|
@@ -89,6 +91,11 @@ export function getResourceTitleId(resource: Resource) {
|
|
|
89
91
|
export const RESOURCE_TYPE = readonly(['main', 'documentation', 'update', 'api', 'code', 'other'] as const)
|
|
90
92
|
export type ResourceType = typeof RESOURCE_TYPE[number]
|
|
91
93
|
|
|
94
|
+
// Page size used by ResourceExplorer for its per-type resource fetches.
|
|
95
|
+
// Exported so consumers (e.g. the dataset page's `main` prefetch) can build a
|
|
96
|
+
// byte-for-byte identical cache key and let Nuxt dedupe the request.
|
|
97
|
+
export const RESOURCE_EXPLORER_PAGE_SIZE = 10
|
|
98
|
+
|
|
92
99
|
export const getResourceLabel = (type: ResourceType, count?: number) => {
|
|
93
100
|
const { t } = useTranslation()
|
|
94
101
|
switch (type) {
|
|
@@ -129,3 +136,56 @@ export const detectOgcService = (resource: Resource) => {
|
|
|
129
136
|
}
|
|
130
137
|
return false
|
|
131
138
|
}
|
|
139
|
+
|
|
140
|
+
export function isCommunityResource(resource: Resource | CommunityResource): boolean {
|
|
141
|
+
return 'organization' in resource || 'owner' in resource
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
export function getResourceExternalUrl(dataset: Dataset | DatasetV2 | Omit<Dataset, 'resources' | 'community_resources'>, resource: Resource | CommunityResource): string {
|
|
145
|
+
return `${dataset.page}${isCommunityResource(resource) ? '/community-resources' : ''}?resource_id=${resource.id}`
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
export function getResourceFilesize(resource: Resource): null | number {
|
|
149
|
+
if (resource.filesize) return resource.filesize
|
|
150
|
+
if ('analysis:content-length' in resource.extras) return resource.extras['analysis:content-length'] as number
|
|
151
|
+
|
|
152
|
+
return null
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
type CorsStatus = 'allowed' | 'blocked' | 'unknown'
|
|
156
|
+
|
|
157
|
+
export const getResourceCorsStatus = (resource: Resource): CorsStatus => {
|
|
158
|
+
const extras = resource.extras
|
|
159
|
+
if (!extras || !('check:cors:allow-origin' in extras)) {
|
|
160
|
+
return 'unknown'
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
const allowOrigin = extras['check:cors:allow-origin'] as string | undefined
|
|
164
|
+
const rawMethods = extras['check:cors:allow-methods'] as string | undefined
|
|
165
|
+
|
|
166
|
+
// Check if allow-origin is '*' or contains one of our trusted domains
|
|
167
|
+
const config = useComponentsConfig()
|
|
168
|
+
const trustedDomains = config.trustedDomains ?? []
|
|
169
|
+
const hasPublicCors = allowOrigin === '*'
|
|
170
|
+
const hasSpecificCors = allowOrigin
|
|
171
|
+
? trustedDomains.some((domain) => {
|
|
172
|
+
try {
|
|
173
|
+
const hostname = new URL(allowOrigin).hostname
|
|
174
|
+
return hostname === domain || hostname.endsWith(`.${domain}`)
|
|
175
|
+
}
|
|
176
|
+
catch {
|
|
177
|
+
return false
|
|
178
|
+
}
|
|
179
|
+
})
|
|
180
|
+
: false
|
|
181
|
+
|
|
182
|
+
const isOriginAllowed = hasPublicCors || hasSpecificCors
|
|
183
|
+
|
|
184
|
+
// Ensure GET method is allowed
|
|
185
|
+
const allowedMethods = rawMethods
|
|
186
|
+
? rawMethods.split(',').map(m => m.trim().toUpperCase())
|
|
187
|
+
: []
|
|
188
|
+
const supportsGet = allowedMethods.length === 0 || allowedMethods.includes('GET')
|
|
189
|
+
|
|
190
|
+
return isOriginAllowed && supportsGet ? 'allowed' : 'blocked'
|
|
191
|
+
}
|
|
@@ -0,0 +1,171 @@
|
|
|
1
|
+
import type { Component } from 'vue'
|
|
2
|
+
import {
|
|
3
|
+
RiHashtag,
|
|
4
|
+
RiPriceTag3Line,
|
|
5
|
+
RiText,
|
|
6
|
+
RiCalendarLine,
|
|
7
|
+
RiCalendarEventLine,
|
|
8
|
+
RiTimeLine,
|
|
9
|
+
RiCheckboxLine,
|
|
10
|
+
RiMapPin2Line,
|
|
11
|
+
RiGlobalLine,
|
|
12
|
+
RiFocus3Line,
|
|
13
|
+
RiFingerprintLine,
|
|
14
|
+
RiMailLine,
|
|
15
|
+
RiLinksLine,
|
|
16
|
+
RiPhoneLine,
|
|
17
|
+
RiUserLine,
|
|
18
|
+
RiMoneyEuroCircleLine,
|
|
19
|
+
RiPercentLine,
|
|
20
|
+
RiBracesLine,
|
|
21
|
+
RiCodeLine,
|
|
22
|
+
} from '@remixicon/vue'
|
|
23
|
+
import { useTranslation } from '../composables/useTranslation'
|
|
24
|
+
import type { TranslationFunction } from '../composables/useTranslation'
|
|
25
|
+
import type { ColumnFilters, ColumnType } from '../components/TabularExplorer/types'
|
|
26
|
+
|
|
27
|
+
export function hasFilterForColumn(filters: Record<string, ColumnFilters>, column: string): boolean {
|
|
28
|
+
const f = filters[column]
|
|
29
|
+
if (!f) return false
|
|
30
|
+
return !!(f.in?.length || f.exact != null || f.contains || f.null || f.min != null || f.max != null)
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
export type TypeDisplay = {
|
|
34
|
+
icon: Component
|
|
35
|
+
label: string
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
export function buildTypeConfig(t: TranslationFunction): Record<ColumnType, TypeDisplay> {
|
|
39
|
+
return {
|
|
40
|
+
number: { icon: RiHashtag, label: t('Nombre') },
|
|
41
|
+
categorical: { icon: RiPriceTag3Line, label: t('Catégoriel') },
|
|
42
|
+
text: { icon: RiText, label: t('Texte') },
|
|
43
|
+
date: { icon: RiCalendarLine, label: t('Date') },
|
|
44
|
+
boolean: { icon: RiCheckboxLine, label: t('Booléen') },
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
export function resolveColumnType(colInfo: { python_type: string, format?: string }, isCategorical: boolean): ColumnType {
|
|
49
|
+
if (['int', 'float'].includes(colInfo.python_type)) return 'number'
|
|
50
|
+
if (colInfo.format === 'year') return 'date'
|
|
51
|
+
if (['date', 'datetime'].includes(colInfo.python_type)) return 'date'
|
|
52
|
+
if (colInfo.python_type === 'bool') return 'boolean'
|
|
53
|
+
if (isCategorical) return 'categorical'
|
|
54
|
+
return 'text'
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
// csv-detective formats that carry no semantic meaning beyond the generic
|
|
58
|
+
// display type already resolved by `ColumnType` — these fall back to
|
|
59
|
+
// `buildTypeConfig` (Texte / Nombre / Date / Booléen) in the header.
|
|
60
|
+
export const GENERIC_FORMATS = new Set(['string', 'int', 'float', 'date', 'bool', 'booleen'])
|
|
61
|
+
|
|
62
|
+
// Semantic types detected by csv-detective (see ../../../csv-detective/csv_detective/formats).
|
|
63
|
+
// The list is intentionally exhaustive, but unknown formats are tolerated:
|
|
64
|
+
// `getColumnDisplay` humanizes any format missing from this map.
|
|
65
|
+
export function buildFormatConfig(t: TranslationFunction): Record<string, TypeDisplay> {
|
|
66
|
+
return {
|
|
67
|
+
// Geo — territories & addresses
|
|
68
|
+
adresse: { icon: RiMapPin2Line, label: t('Adresse') },
|
|
69
|
+
commune: { icon: RiMapPin2Line, label: t('Commune') },
|
|
70
|
+
code_commune: { icon: RiMapPin2Line, label: t('Code INSEE commune') },
|
|
71
|
+
code_commune_insee: { icon: RiMapPin2Line, label: t('Code INSEE commune') },
|
|
72
|
+
departement: { icon: RiMapPin2Line, label: t('Département') },
|
|
73
|
+
code_departement: { icon: RiMapPin2Line, label: t('Code département') },
|
|
74
|
+
region: { icon: RiMapPin2Line, label: t('Région') },
|
|
75
|
+
code_region: { icon: RiMapPin2Line, label: t('Code région') },
|
|
76
|
+
code_epci: { icon: RiMapPin2Line, label: t('Code EPCI') },
|
|
77
|
+
code_postal: { icon: RiMapPin2Line, label: t('Code postal') },
|
|
78
|
+
insee_canton: { icon: RiMapPin2Line, label: t('Canton') },
|
|
79
|
+
code_fantoir: { icon: RiMapPin2Line, label: t('Code FANTOIR') },
|
|
80
|
+
id_rnb: { icon: RiMapPin2Line, label: t('Identifiant RNB') },
|
|
81
|
+
// Geo — countries
|
|
82
|
+
pays: { icon: RiGlobalLine, label: t('Pays') },
|
|
83
|
+
iso_country_code_alpha2: { icon: RiGlobalLine, label: t('Code pays (ISO α-2)') },
|
|
84
|
+
iso_country_code_alpha3: { icon: RiGlobalLine, label: t('Code pays (ISO α-3)') },
|
|
85
|
+
iso_country_code_numeric: { icon: RiGlobalLine, label: t('Code pays (ISO num.)') },
|
|
86
|
+
// Geo — coordinates
|
|
87
|
+
latitude_wgs: { icon: RiFocus3Line, label: t('Latitude') },
|
|
88
|
+
latitude_wgs_fr_metropole: { icon: RiFocus3Line, label: t('Latitude') },
|
|
89
|
+
longitude_wgs: { icon: RiFocus3Line, label: t('Longitude') },
|
|
90
|
+
longitude_wgs_fr_metropole: { icon: RiFocus3Line, label: t('Longitude') },
|
|
91
|
+
latitude_l93: { icon: RiFocus3Line, label: t('Latitude (Lambert 93)') },
|
|
92
|
+
longitude_l93: { icon: RiFocus3Line, label: t('Longitude (Lambert 93)') },
|
|
93
|
+
latlon_wgs: { icon: RiFocus3Line, label: t('Latitude / longitude') },
|
|
94
|
+
lonlat_wgs: { icon: RiFocus3Line, label: t('Longitude / latitude') },
|
|
95
|
+
// Identifiers
|
|
96
|
+
siren: { icon: RiFingerprintLine, label: t('SIREN') },
|
|
97
|
+
siret: { icon: RiFingerprintLine, label: t('SIRET') },
|
|
98
|
+
code_rna: { icon: RiFingerprintLine, label: t('Identifiant RNA') },
|
|
99
|
+
code_waldec: { icon: RiFingerprintLine, label: t('Identifiant WALDEC') },
|
|
100
|
+
code_import: { icon: RiFingerprintLine, label: t('Code Import') },
|
|
101
|
+
uai: { icon: RiFingerprintLine, label: t('UAI') },
|
|
102
|
+
insee_ape700: { icon: RiFingerprintLine, label: t('Code APE') },
|
|
103
|
+
code_csp_insee: { icon: RiFingerprintLine, label: t('Code CSP') },
|
|
104
|
+
uuid: { icon: RiFingerprintLine, label: t('UUID') },
|
|
105
|
+
mongo_object_id: { icon: RiFingerprintLine, label: t('Identifiant Mongo') },
|
|
106
|
+
// Contact
|
|
107
|
+
email: { icon: RiMailLine, label: t('Email') },
|
|
108
|
+
url: { icon: RiLinksLine, label: t('URL') },
|
|
109
|
+
tel_fr: { icon: RiPhoneLine, label: t('Téléphone') },
|
|
110
|
+
username: { icon: RiUserLine, label: t('Nom d\'utilisateur') },
|
|
111
|
+
// People
|
|
112
|
+
sexe: { icon: RiUserLine, label: t('Sexe') },
|
|
113
|
+
csp_insee: { icon: RiUserLine, label: t('Catégorie socio-pro.') },
|
|
114
|
+
// Quantities
|
|
115
|
+
money: { icon: RiMoneyEuroCircleLine, label: t('Montant') },
|
|
116
|
+
percent: { icon: RiPercentLine, label: t('Pourcentage') },
|
|
117
|
+
// Temporal
|
|
118
|
+
year: { icon: RiCalendarLine, label: t('Année') },
|
|
119
|
+
date_fr: { icon: RiCalendarLine, label: t('Date') },
|
|
120
|
+
jour_de_la_semaine: { icon: RiCalendarEventLine, label: t('Jour de la semaine') },
|
|
121
|
+
mois_de_lannee: { icon: RiCalendarEventLine, label: t('Mois de l\'année') },
|
|
122
|
+
datetime_naive: { icon: RiTimeLine, label: t('Date et heure') },
|
|
123
|
+
datetime_aware: { icon: RiTimeLine, label: t('Date et heure') },
|
|
124
|
+
datetime_rfc822: { icon: RiTimeLine, label: t('Date et heure') },
|
|
125
|
+
// Structured
|
|
126
|
+
json: { icon: RiBracesLine, label: t('JSON') },
|
|
127
|
+
geojson: { icon: RiBracesLine, label: t('GeoJSON') },
|
|
128
|
+
binary: { icon: RiCodeLine, label: t('Binaire') },
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
// Tolerant fallback for a semantic format we don't explicitly know about:
|
|
133
|
+
// "code_truc_machin" -> "Code truc machin".
|
|
134
|
+
export function humanizeFormat(format: string): string {
|
|
135
|
+
const spaced = format.replace(/_/g, ' ').trim()
|
|
136
|
+
return spaced.charAt(0).toUpperCase() + spaced.slice(1)
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
export function useFormatTabular() {
|
|
140
|
+
const { locale } = useTranslation()
|
|
141
|
+
|
|
142
|
+
function formatNumber(value: unknown): string {
|
|
143
|
+
const num = Number(value)
|
|
144
|
+
if (Number.isNaN(num)) return String(value)
|
|
145
|
+
return num.toLocaleString(locale)
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
function formatCellDate(value: unknown): string {
|
|
149
|
+
if (value == null || value === '') return '–'
|
|
150
|
+
const d = new Date(String(value))
|
|
151
|
+
if (Number.isNaN(d.getTime())) return String(value)
|
|
152
|
+
return new Intl.DateTimeFormat(locale, { day: '2-digit', month: '2-digit', year: 'numeric' }).format(d)
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
return { formatNumber, formatCellDate }
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
const TRUTHY_VALUES = ['true', '1', 'oui', 'yes']
|
|
159
|
+
const FALSY_VALUES = ['false', '0', 'non', 'no']
|
|
160
|
+
|
|
161
|
+
export function isTruthy(value: unknown): boolean {
|
|
162
|
+
if (typeof value === 'boolean') return value
|
|
163
|
+
if (typeof value === 'string') return TRUTHY_VALUES.includes(value.toLowerCase())
|
|
164
|
+
return Boolean(value)
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
export function isFalsy(value: unknown): boolean {
|
|
168
|
+
if (typeof value === 'boolean') return !value
|
|
169
|
+
if (typeof value === 'string') return FALSY_VALUES.includes(value.toLowerCase())
|
|
170
|
+
return !value
|
|
171
|
+
}
|
|
@@ -1,20 +1,105 @@
|
|
|
1
1
|
import { ofetch } from 'ofetch'
|
|
2
2
|
import { useComponentsConfig, type PluginConfig } from '../config'
|
|
3
|
+
import type { GenericFilter } from '../types/visualizations'
|
|
4
|
+
import type { SortConfig, TabularProfileResponse } from '../components/TabularExplorer/types'
|
|
3
5
|
|
|
4
|
-
export type SortConfig
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
6
|
+
export type { SortConfig }
|
|
7
|
+
|
|
8
|
+
export type TabularDataResponse = {
|
|
9
|
+
data: Array<Record<string, unknown>>
|
|
10
|
+
links: {
|
|
11
|
+
profile: string
|
|
12
|
+
swagger: string
|
|
13
|
+
next: string
|
|
14
|
+
}
|
|
15
|
+
meta: { total: number }
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
export type TabularAggregateType = 'avg' | 'sum' | 'count' | 'min' | 'max'
|
|
19
|
+
|
|
20
|
+
export type FetchTabularDataOptions = {
|
|
21
|
+
resourceId: string
|
|
22
|
+
page?: number
|
|
23
|
+
pageSize?: number
|
|
24
|
+
columns?: Array<string> | undefined
|
|
25
|
+
sort?: SortConfig
|
|
26
|
+
groupBy?: string | undefined
|
|
27
|
+
aggregation?: {
|
|
28
|
+
column: string
|
|
29
|
+
type: TabularAggregateType
|
|
30
|
+
} | undefined
|
|
31
|
+
filters?: GenericFilter | undefined
|
|
32
|
+
}
|
|
8
33
|
|
|
9
34
|
/**
|
|
10
|
-
* Call Tabular-api to get table content
|
|
35
|
+
* Call Tabular-api to get table content with options object
|
|
11
36
|
*/
|
|
12
|
-
export async function
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
37
|
+
export async function fetchTabularData(config: PluginConfig, options: FetchTabularDataOptions): Promise<TabularDataResponse> {
|
|
38
|
+
const page = options.page ?? 1
|
|
39
|
+
const pageSize = options.pageSize ?? config.tabularApiPageSize ?? 15
|
|
40
|
+
let url = `${config.tabularApiUrl}/api/resources/${options.resourceId}/data/?page=${page}&page_size=${pageSize}`
|
|
41
|
+
if (options.columns) {
|
|
42
|
+
url += `&columns=${options.columns.map(col => encodeURIComponent(col)).join(',')}`
|
|
43
|
+
}
|
|
44
|
+
if (options.sort) {
|
|
45
|
+
url += `&${encodeURIComponent(options.sort.column)}__sort=${encodeURIComponent(options.sort.direction)}`
|
|
46
|
+
}
|
|
47
|
+
if (options.groupBy && options.aggregation?.type) {
|
|
48
|
+
url += `&${encodeURIComponent(options.groupBy)}__groupby&${encodeURIComponent(options.aggregation.column)}__${encodeURIComponent(options.aggregation.type)}`
|
|
49
|
+
}
|
|
50
|
+
if (options.filters) {
|
|
51
|
+
const filterQuery = buildFilterQuery(options.filters)
|
|
52
|
+
if (filterQuery) {
|
|
53
|
+
url += `&${filterQuery}`
|
|
54
|
+
}
|
|
16
55
|
}
|
|
17
|
-
return await ofetch(url)
|
|
56
|
+
return await ofetch<TabularDataResponse>(url)
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
function buildFilterQuery(filters: GenericFilter): string {
|
|
60
|
+
const params: Array<string> = []
|
|
61
|
+
if ('filters' in filters) {
|
|
62
|
+
for (const filter of filters.filters) {
|
|
63
|
+
if ('filters' in filter) {
|
|
64
|
+
params.push(buildFilterQuery(filter))
|
|
65
|
+
}
|
|
66
|
+
else {
|
|
67
|
+
if (filter.condition === 'is_null') {
|
|
68
|
+
params.push(`${encodeURIComponent(filter.column)}__isnull`)
|
|
69
|
+
}
|
|
70
|
+
else if (filter.condition === 'is_not_null') {
|
|
71
|
+
params.push(`${encodeURIComponent(filter.column)}__isnotnull`)
|
|
72
|
+
}
|
|
73
|
+
else if (filter.value !== null && filter.value !== undefined && filter.value !== '') {
|
|
74
|
+
params.push(`${encodeURIComponent(filter.column)}__${encodeURIComponent(filter.condition)}=${encodeURIComponent(filter.value)}`)
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
else {
|
|
80
|
+
const filter = filters
|
|
81
|
+
if (filter.condition === 'is_null') {
|
|
82
|
+
params.push(`${encodeURIComponent(filter.column)}__isnull`)
|
|
83
|
+
}
|
|
84
|
+
else if (filter.condition === 'is_not_null') {
|
|
85
|
+
params.push(`${encodeURIComponent(filter.column)}__isnotnull`)
|
|
86
|
+
}
|
|
87
|
+
else if (filter.value !== null && filter.value !== undefined && filter.value !== '') {
|
|
88
|
+
params.push(`${encodeURIComponent(filter.column)}__${encodeURIComponent(filter.condition)}=${encodeURIComponent(filter.value)}`)
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
return params.join('&')
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
/**
|
|
95
|
+
* Call Tabular-api to get table content
|
|
96
|
+
*/
|
|
97
|
+
export async function getData(config: PluginConfig, id: string, page: number, sortConfig?: SortConfig | null): Promise<TabularDataResponse> {
|
|
98
|
+
return fetchTabularData(config, {
|
|
99
|
+
resourceId: id,
|
|
100
|
+
page,
|
|
101
|
+
sort: sortConfig ?? undefined,
|
|
102
|
+
})
|
|
18
103
|
}
|
|
19
104
|
|
|
20
105
|
/**
|
|
@@ -22,5 +107,5 @@ export async function getData(config: PluginConfig, id: string, page: number, so
|
|
|
22
107
|
*/
|
|
23
108
|
export function useGetProfile() {
|
|
24
109
|
const config = useComponentsConfig()
|
|
25
|
-
return (id: string) => ofetch(`${config.tabularApiUrl}/api/resources/${id}/profile/`)
|
|
110
|
+
return (id: string) => ofetch<TabularProfileResponse>(`${config.tabularApiUrl}/api/resources/${id}/profile/`)
|
|
26
111
|
}
|
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'
|
|
@@ -23,8 +23,12 @@ import type { Site } from './types/site'
|
|
|
23
23
|
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
|
-
import type {
|
|
27
|
-
import {
|
|
26
|
+
import type { Chart, ChartForm, ChartForApi, FilterCondition, Filter, AndFilters, GenericFilter, XAxisType, XAxisSortBy, SortDirection, XAxis, XAxisForm, UnitPosition, YAxis, DataSeriesType, DataSeries, DataSeriesForm, CombinedSort, ColumnDefinition, ColumnsDefinition } from './types/visualizations'
|
|
27
|
+
import type { ColumnType } from './components/TabularExplorer/types'
|
|
28
|
+
import type { GlobalSearchConfig, SearchType, SearchTypeConfig, SortOption, HiddenFilter, BuiltInFilterKey, DatasetSearchConfig, DatasetSearchFilters, DataserviceSearchConfig, DataserviceSearchFilters, ReuseSearchConfig, ReuseSearchFilters, OrganizationSearchConfig, OrganizationSearchFilters, TopicSearchConfig, TopicSearchFilters } from './types/search'
|
|
29
|
+
import { getDefaultDatasetConfig, getDefaultDataserviceConfig, getDefaultReuseConfig, getDefaultOrganizationConfig, getDefaultTopicConfig, getDefaultGlobalSearchConfig, defaultDatasetSortOptions, defaultDataserviceSortOptions, defaultReuseSortOptions, defaultOrganizationSortOptions } from './types/search'
|
|
30
|
+
import { useSearchFilter } from './composables/useSearchFilter'
|
|
31
|
+
import type { UseSearchFilterOptions } from './composables/useSearchFilter'
|
|
28
32
|
|
|
29
33
|
import ActivityList from './components/ActivityList/ActivityList.vue'
|
|
30
34
|
import UserActivityList from './components/ActivityList/UserActivityList.vue'
|
|
@@ -37,6 +41,9 @@ import BrandedButton from './components/BrandedButton.vue'
|
|
|
37
41
|
import CopyButton from './components/CopyButton.vue'
|
|
38
42
|
import DataserviceCard from './components/DataserviceCard.vue'
|
|
39
43
|
import DatasetCard from './components/DatasetCard.vue'
|
|
44
|
+
import DataStructure from './components/ResourceAccordion/DataStructure.vue'
|
|
45
|
+
import Downloads from './components/ResourceAccordion/Downloads.vue'
|
|
46
|
+
import Metadata from './components/ResourceAccordion/Metadata.vue'
|
|
40
47
|
import DescriptionListTerm from './components/DescriptionListTerm.vue'
|
|
41
48
|
import DescriptionListDetails from './components/DescriptionListDetails.vue'
|
|
42
49
|
import DiscussionMessageCard from './components/DiscussionMessageCard.vue'
|
|
@@ -57,6 +64,7 @@ import LoadingBlock from './components/LoadingBlock.vue'
|
|
|
57
64
|
import MarkdownViewer from './components/MarkdownViewer.vue'
|
|
58
65
|
import OrganizationCard from './components/OrganizationCard.vue'
|
|
59
66
|
import OrganizationHorizontalCard from './components/OrganizationHorizontalCard.vue'
|
|
67
|
+
import ObjectCardOwner from './components/ObjectCardOwner.vue'
|
|
60
68
|
import OrganizationLogo from './components/OrganizationLogo.vue'
|
|
61
69
|
import OrganizationNameWithCertificate from './components/OrganizationNameWithCertificate.vue'
|
|
62
70
|
import OwnerType from './components/OwnerType.vue'
|
|
@@ -70,16 +78,16 @@ import PostCard from './components/PostCard.vue'
|
|
|
70
78
|
import ReadMore from './components/ReadMore.vue'
|
|
71
79
|
import ResourceAccordion from './components/ResourceAccordion/ResourceAccordion.vue'
|
|
72
80
|
import ResourceIcon from './components/ResourceAccordion/ResourceIcon.vue'
|
|
81
|
+
import ResourceSelector from './components/ResourceExplorer/ResourceSelector.vue'
|
|
73
82
|
import ResourceExplorer from './components/ResourceExplorer/ResourceExplorer.vue'
|
|
74
83
|
import ResourceExplorerSidebar from './components/ResourceExplorer/ResourceExplorerSidebar.vue'
|
|
75
84
|
import ResourceExplorerViewer from './components/ResourceExplorer/ResourceExplorerViewer.vue'
|
|
76
|
-
import
|
|
85
|
+
import OpenApiViewer from './components/OpenApiViewer/OpenApiViewer.vue'
|
|
77
86
|
import ReuseCard from './components/ReuseCard.vue'
|
|
78
87
|
import ReuseHorizontalCard from './components/ReuseHorizontalCard.vue'
|
|
79
88
|
import ReuseDetails from './components/ReuseDetails.vue'
|
|
80
89
|
import SchemaCard from './components/SchemaCard.vue'
|
|
81
90
|
import SimpleBanner from './components/SimpleBanner.vue'
|
|
82
|
-
import SmallChart from './components/SmallChart.vue'
|
|
83
91
|
import StatBox from './components/StatBox.vue'
|
|
84
92
|
import Tab from './components/Tabs/Tab.vue'
|
|
85
93
|
import TabGroup from './components/Tabs/TabGroup.vue'
|
|
@@ -94,16 +102,24 @@ import GlobalSearch from './components/Search/GlobalSearch.vue'
|
|
|
94
102
|
import SearchInput from './components/Search/SearchInput.vue'
|
|
95
103
|
import SearchableSelect from './components/Form/SearchableSelect.vue'
|
|
96
104
|
import SelectGroup from './components/Form/SelectGroup.vue'
|
|
105
|
+
import Listbox from './components/Form/Listbox.vue'
|
|
106
|
+
import InfiniteLoader from './components/InfiniteLoader.vue'
|
|
107
|
+
import TabularExplorer from './components/TabularExplorer/TabularExplorer.vue'
|
|
97
108
|
import type { UseFetchFunction } from './functions/api.types'
|
|
98
109
|
import { configKey, useComponentsConfig, type PluginConfig } from './config.js'
|
|
110
|
+
import { ofetch } from 'ofetch'
|
|
111
|
+
import { useTranslation } from './composables/useTranslation'
|
|
99
112
|
|
|
100
113
|
export { Toaster, toast } from 'vue-sonner'
|
|
101
114
|
|
|
102
115
|
export * from './composables/useActiveDescendant'
|
|
116
|
+
export * from './composables/useDebouncedRef'
|
|
103
117
|
export * from './composables/useMetrics'
|
|
104
118
|
export * from './composables/useReuseType'
|
|
105
119
|
export * from './composables/useTranslation'
|
|
106
120
|
export * from './composables/useHasTabularData'
|
|
121
|
+
export * from './composables/useResourceCapabilities'
|
|
122
|
+
export * from './composables/useTabularProfile'
|
|
107
123
|
|
|
108
124
|
export * from './functions/activities'
|
|
109
125
|
export * from './functions/datasets'
|
|
@@ -116,17 +132,33 @@ export * from './functions/metrics'
|
|
|
116
132
|
export * from './functions/never'
|
|
117
133
|
export * from './functions/organizations'
|
|
118
134
|
export * from './functions/owned'
|
|
119
|
-
export * from './functions/pagination'
|
|
120
135
|
export * from './functions/resources'
|
|
121
136
|
export * from './functions/reuses'
|
|
122
137
|
export * from './functions/schemas'
|
|
138
|
+
export * from './functions/tabular'
|
|
123
139
|
export * from './functions/users'
|
|
140
|
+
export * from './functions/tabularApi'
|
|
141
|
+
export * from './functions/charts'
|
|
124
142
|
export * from './types/access_types'
|
|
125
143
|
|
|
126
144
|
export type {
|
|
127
145
|
GlobalSearchConfig,
|
|
128
146
|
SearchType,
|
|
147
|
+
SearchTypeConfig,
|
|
129
148
|
SortOption,
|
|
149
|
+
HiddenFilter,
|
|
150
|
+
BuiltInFilterKey,
|
|
151
|
+
DatasetSearchConfig,
|
|
152
|
+
DatasetSearchFilters,
|
|
153
|
+
DataserviceSearchConfig,
|
|
154
|
+
DataserviceSearchFilters,
|
|
155
|
+
ReuseSearchConfig,
|
|
156
|
+
ReuseSearchFilters,
|
|
157
|
+
OrganizationSearchConfig,
|
|
158
|
+
OrganizationSearchFilters,
|
|
159
|
+
TopicSearchConfig,
|
|
160
|
+
TopicSearchFilters,
|
|
161
|
+
UseSearchFilterOptions,
|
|
130
162
|
UseFetchFunction,
|
|
131
163
|
AccessType,
|
|
132
164
|
AccessAudience,
|
|
@@ -168,7 +200,6 @@ export type {
|
|
|
168
200
|
Owned,
|
|
169
201
|
OwnedWithFullObject,
|
|
170
202
|
OwnedWithId,
|
|
171
|
-
Page,
|
|
172
203
|
PageBloc,
|
|
173
204
|
ContentBloc,
|
|
174
205
|
BlocWithTitle,
|
|
@@ -218,6 +249,27 @@ export type {
|
|
|
218
249
|
ValidataError,
|
|
219
250
|
Weight,
|
|
220
251
|
WellType,
|
|
252
|
+
Chart,
|
|
253
|
+
ChartForm,
|
|
254
|
+
ChartForApi,
|
|
255
|
+
CombinedSort,
|
|
256
|
+
ColumnDefinition,
|
|
257
|
+
ColumnType,
|
|
258
|
+
ColumnsDefinition,
|
|
259
|
+
FilterCondition,
|
|
260
|
+
Filter,
|
|
261
|
+
AndFilters,
|
|
262
|
+
GenericFilter,
|
|
263
|
+
XAxisType,
|
|
264
|
+
XAxisSortBy,
|
|
265
|
+
SortDirection,
|
|
266
|
+
XAxis,
|
|
267
|
+
XAxisForm,
|
|
268
|
+
UnitPosition,
|
|
269
|
+
YAxis,
|
|
270
|
+
DataSeriesType,
|
|
271
|
+
DataSeries,
|
|
272
|
+
DataSeriesForm,
|
|
221
273
|
}
|
|
222
274
|
|
|
223
275
|
export {
|
|
@@ -225,16 +277,43 @@ export {
|
|
|
225
277
|
getDefaultDataserviceConfig,
|
|
226
278
|
getDefaultReuseConfig,
|
|
227
279
|
getDefaultOrganizationConfig,
|
|
280
|
+
getDefaultTopicConfig,
|
|
228
281
|
getDefaultGlobalSearchConfig,
|
|
229
282
|
defaultDatasetSortOptions,
|
|
230
283
|
defaultDataserviceSortOptions,
|
|
231
284
|
defaultReuseSortOptions,
|
|
232
285
|
defaultOrganizationSortOptions,
|
|
286
|
+
useSearchFilter,
|
|
233
287
|
}
|
|
234
288
|
|
|
235
289
|
// Vue Plugin
|
|
236
290
|
const datagouv: Plugin<PluginConfig> = {
|
|
237
291
|
async install(app: App, options) {
|
|
292
|
+
// Default `$fetch` to an ofetch instance carrying the datagouv API specifics + the consumer's
|
|
293
|
+
// auth hooks, so everything downstream (the default `useFetch`, imperative helpers) can rely on
|
|
294
|
+
// a single configured fetch. A consumer that provides its own `$fetch` keeps full control.
|
|
295
|
+
if (!options.$fetch) {
|
|
296
|
+
options.$fetch = ofetch.create({
|
|
297
|
+
baseURL: options.apiBase,
|
|
298
|
+
onRequest(context) {
|
|
299
|
+
if (options.onRequest) {
|
|
300
|
+
if (Array.isArray(options.onRequest)) options.onRequest.forEach(hook => hook(context))
|
|
301
|
+
else options.onRequest(context)
|
|
302
|
+
}
|
|
303
|
+
context.options.headers.set('Content-Type', 'application/json')
|
|
304
|
+
context.options.headers.set('Accept', 'application/json')
|
|
305
|
+
if (options.devApiKey) context.options.headers.set('X-API-KEY', options.devApiKey)
|
|
306
|
+
const { locale } = useTranslation()
|
|
307
|
+
if (locale) {
|
|
308
|
+
context.options.params ??= {}
|
|
309
|
+
context.options.params['lang'] = locale
|
|
310
|
+
}
|
|
311
|
+
},
|
|
312
|
+
onRequestError: options.onRequestError,
|
|
313
|
+
onResponse: options.onResponse,
|
|
314
|
+
onResponseError: options.onResponseError,
|
|
315
|
+
})
|
|
316
|
+
}
|
|
238
317
|
app.provide(configKey, options)
|
|
239
318
|
if (!options.textClamp) {
|
|
240
319
|
const textClamp = await import('vue3-text-clamp')
|
|
@@ -256,6 +335,9 @@ export {
|
|
|
256
335
|
CopyButton,
|
|
257
336
|
DataserviceCard,
|
|
258
337
|
DatasetCard,
|
|
338
|
+
DataStructure,
|
|
339
|
+
Downloads,
|
|
340
|
+
Metadata,
|
|
259
341
|
DatasetInformationSection,
|
|
260
342
|
DatasetTemporalitySection,
|
|
261
343
|
DatasetSpatialSection,
|
|
@@ -295,15 +377,16 @@ export {
|
|
|
295
377
|
ResourceExplorer,
|
|
296
378
|
ResourceExplorerSidebar,
|
|
297
379
|
ResourceExplorerViewer,
|
|
380
|
+
ObjectCardOwner,
|
|
298
381
|
ResourceIcon,
|
|
382
|
+
ResourceSelector,
|
|
299
383
|
ReuseCard,
|
|
300
384
|
ReuseDetails,
|
|
301
385
|
ReuseHorizontalCard,
|
|
302
386
|
SchemaCard,
|
|
303
387
|
SimpleBanner,
|
|
304
|
-
SmallChart,
|
|
305
388
|
StatBox,
|
|
306
|
-
|
|
389
|
+
OpenApiViewer,
|
|
307
390
|
Tab,
|
|
308
391
|
TabGroup,
|
|
309
392
|
TabList,
|
|
@@ -318,4 +401,7 @@ export {
|
|
|
318
401
|
SearchInput,
|
|
319
402
|
SearchableSelect,
|
|
320
403
|
SelectGroup,
|
|
404
|
+
Listbox,
|
|
405
|
+
InfiniteLoader,
|
|
406
|
+
TabularExplorer,
|
|
321
407
|
}
|
|
@@ -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
|