From 6a9c651fece4e0a04eb2c10699710941991e46da Mon Sep 17 00:00:00 2001 From: BatLeDev Date: Wed, 21 Oct 2026 10:17:31 +0200 Subject: [PATCH 1/2] fix(ui): load site theme and public info from hashed simple-directory urls createSession({ siteInfo: true }) fetched _public at startup and logged 'refreshSiteInfo is deprecated'. index.html now loads the hashed _theme.css and _public.js filled by createSpaMiddleware (privateDirectoryUrl), as in catalogs and processings. The tags are injected through SITE_RESOURCES, empty in standalone mode (useSimpleDirectory: false). --- api/src/app.ts | 7 +++++-- api/src/config.ts | 7 +++++++ ui/index.html | 2 +- ui/src/main.ts | 2 +- ui/vite.config.ts | 4 ++-- 5 files changed, 16 insertions(+), 6 deletions(-) diff --git a/api/src/app.ts b/api/src/app.ts index 967776a..e6dfcf0 100644 --- a/api/src/app.ts +++ b/api/src/app.ts @@ -1,7 +1,7 @@ import { resolve } from 'node:path' import express from 'express' import { errorHandler, createSiteMiddleware, createSpaMiddleware } from '@data-fair/lib-express' -import config, { uiConfig } from '#config' +import config, { uiConfig, siteResourcesHtml } from '#config' import adminRouter from './admin/router.ts' export const app = express() @@ -16,7 +16,10 @@ app.use(express.urlencoded({ extended: true })) app.use('/api/admin', adminRouter) if (config.serveUi) { - app.use(await createSpaMiddleware(resolve(import.meta.dirname, '../../ui/dist'), uiConfig)) + app.use(await createSpaMiddleware(resolve(import.meta.dirname, '../../ui/dist'), uiConfig, { + extraHtmlTemplateParams: { SITE_RESOURCES: siteResourcesHtml }, + privateDirectoryUrl: config.useSimpleDirectory ? config.privateDirectoryUrl : undefined + })) } app.use(errorHandler) diff --git a/api/src/config.ts b/api/src/config.ts index cc95e4f..4edf3df 100644 --- a/api/src/config.ts +++ b/api/src/config.ts @@ -14,3 +14,10 @@ export const uiConfig = { useSimpleDirectory: apiConfig.useSimpleDirectory } export type UiConfig = typeof uiConfig + +// site theme and public info served by simple-directory, absent in standalone mode +// the hash placeholders are filled by createSpaMiddleware (privateDirectoryUrl option) +export const siteResourcesHtml = apiConfig.useSimpleDirectory + ? ` + ` + : '' diff --git a/ui/index.html b/ui/index.html index 38faf6f..bd29ac2 100644 --- a/ui/index.html +++ b/ui/index.html @@ -3,7 +3,7 @@ - + {SITE_RESOURCES}
diff --git a/ui/src/main.ts b/ui/src/main.ts index 0708b5f..4c72c0b 100644 --- a/ui/src/main.ts +++ b/ui/src/main.ts @@ -26,7 +26,7 @@ import DOMPurify from 'dompurify' let session, localeDayjs, i18n if ($uiConfig.useSimpleDirectory) { // Don't integrate session in standalone mode - session = await createSession({ directoryUrl: $sitePath + '/simple-directory', siteInfo: true }) + session = await createSession({ directoryUrl: $sitePath + '/simple-directory' }) localeDayjs = createLocaleDayjs(session.state.lang) i18n = createI18n({ locale: session.state.lang }) Object.assign(vuetifyOptions, vuetifySessionOptions(session)) diff --git a/ui/vite.config.ts b/ui/vite.config.ts index 52c3963..3c1d341 100644 --- a/ui/vite.config.ts +++ b/ui/vite.config.ts @@ -70,8 +70,8 @@ export default defineConfig(({ command }) => ({ async transformIndexHtml (html) { // in production this injection will be performed by an express middleware if (process.env.NODE_ENV !== 'development') return html - const { uiConfig } = await import('../api/src/config.ts') - return microTemplate(html, { SITE_PATH: '', UI_CONFIG: JSON.stringify(uiConfig) }) + const { uiConfig, siteResourcesHtml } = await import('../api/src/config.ts') + return microTemplate(html, { SITE_RESOURCES: siteResourcesHtml, SITE_PATH: '', UI_CONFIG: JSON.stringify(uiConfig), THEME_CSS_HASH: '', PUBLIC_SITE_INFO_HASH: '' }) } } ], From e5a91cd8247aa07f5f7e7333a07365fdff79263f Mon Sep 17 00:00:00 2001 From: BatLeDev Date: Wed, 21 Oct 2026 10:17:37 +0200 Subject: [PATCH 2/2] fix(ui): reload the doc when its url query params change Since Vue Router 5 route.query is not reactive, so watch(route.query) was an invalid source and the doc was never refetched after an in-app query change (e.g. a d-frame parent switching dataset). Watch the resolved doc url instead, so operation/params changes still do not refetch it. Also make home externalDocs and servers props optional, both are optional in OpenAPI. --- ui/src/components/home.vue | 6 +++--- ui/src/pages/index.vue | 28 +++++++++++++++++----------- 2 files changed, 20 insertions(+), 14 deletions(-) diff --git a/ui/src/components/home.vue b/ui/src/components/home.vue index e05f34a..ab0d627 100644 --- a/ui/src/components/home.vue +++ b/ui/src/components/home.vue @@ -208,10 +208,10 @@ marked.setOptions({ gfm: true }) -const { info, externalDocs, servers, schemas } = defineProps<{ +const { info, externalDocs = undefined, servers = undefined, schemas } = defineProps<{ info: OpenAPISpecs['info'], - externalDocs: OpenAPISpecs['externalDocs'], - servers: OpenAPISpecs['servers'], + externalDocs?: OpenAPISpecs['externalDocs'], + servers?: OpenAPISpecs['servers'], schemas: Components['schemas'] | undefined }>() diff --git a/ui/src/pages/index.vue b/ui/src/pages/index.vue index 9be5d80..65a3354 100644 --- a/ui/src/pages/index.vue +++ b/ui/src/pages/index.vue @@ -141,15 +141,15 @@ const fullOperation = computed<{ return null }) -watch(route.query, () => { - derefDoc.value = undefined - errorMessage.value = null - +/* + * Resolve the doc URL from the query + * -> watched by value, so that operation/params changes in the query do not refetch the doc + */ +const target = computed<{ url?: string, error?: string }>(() => { if (route.query.urlType) { // Check url type if (!Object.keys($uiConfig.allowedUrls).includes(route.query.urlType as string)) { - errorMessage.value = t('errors.invalidUrlType') - return + return { error: t('errors.invalidUrlType') } } const template = $uiConfig.allowedUrls[route.query.urlType as string] @@ -159,18 +159,24 @@ watch(route.query, () => { const requiredParams = (template.match(/{[^}]+}/g) || []).map((param) => param.slice(1, -1)) const missingParams = requiredParams.filter((param) => !queryParams[param]) if (missingParams.length > 0) { - errorMessage.value = t('errors.missingParams', { params: missingParams.join(', ') }) - return + return { error: t('errors.missingParams', { params: missingParams.join(', ') }) } } // Replace the template params with the query params - url.value = urlTemplate.parseTemplate(template).expand(queryParams) + return { url: urlTemplate.parseTemplate(template).expand(queryParams) } } else if ($uiConfig.defaultUrl) { - url.value = $uiConfig.defaultUrl + return { url: $uiConfig.defaultUrl } } else { - errorMessage.value = t('errors.missingUrl') + return { error: t('errors.missingUrl') } } +}) + +watch([() => target.value.url, () => target.value.error], ([newUrl, error]) => { + derefDoc.value = undefined + errorMessage.value = error ?? null + if (!newUrl) return + url.value = newUrl urlFetch.refresh() }, { immediate: true })