@lupinum/ginko 0.4.0 → 0.6.0
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/CHANGELOG.md +161 -0
- package/README.md +2 -2
- package/dist/basics.d.mts +492 -0
- package/dist/basics.d.ts +492 -0
- package/dist/basics.mjs +416 -0
- package/dist/cli/index.mjs +59 -56
- package/dist/index.d.mts +5 -4
- package/dist/index.d.ts +5 -4
- package/dist/index.mjs +3 -3
- package/dist/internal/search.mjs +6 -3
- package/dist/internal.d.mts +387 -41
- package/dist/internal.d.ts +387 -41
- package/dist/internal.mjs +267 -101
- package/dist/nuxt/module.d.mts +2 -2
- package/dist/nuxt/module.d.ts +2 -2
- package/dist/nuxt/module.mjs +108 -15
- package/dist/nuxt/runtime/GinkoImage.d.mts +1 -1
- package/dist/nuxt/runtime/GinkoMarkdown.mjs +2 -2
- package/dist/nuxt/runtime/composables.d.mts +4 -3
- package/dist/nuxt/runtime/composables.mjs +19 -15
- package/dist/nuxt/runtime/fetch.d.mts +3 -1
- package/dist/nuxt/runtime/fetch.mjs +12 -6
- package/dist/nuxt/runtime/markdown.d.mts +3 -0
- package/dist/nuxt/runtime/markdown.mjs +15 -3
- package/dist/nuxt/runtime/plugin.mjs +6 -35
- package/dist/nuxt/runtime/preview-listener.d.mts +13 -0
- package/dist/nuxt/runtime/preview-listener.mjs +37 -0
- package/dist/nuxt/runtime/preview.vue +3 -1
- package/dist/nuxt/runtime/schema.d.ts +6 -0
- package/dist/nuxt/runtime/server/bundles.d.ts +4 -0
- package/dist/nuxt/runtime/server/files-md.d.ts +2 -0
- package/dist/nuxt/runtime/server/files.d.ts +2 -0
- package/dist/nuxt/runtime/server/headers.d.ts +2 -0
- package/dist/nuxt/runtime/server/items.d.mts +12 -0
- package/dist/nuxt/runtime/server/items.d.ts +12 -0
- package/dist/nuxt/runtime/server/items.mjs +17 -0
- package/dist/nuxt/runtime/server/llms-full.d.ts +2 -0
- package/dist/nuxt/runtime/server/llms.d.ts +2 -0
- package/dist/nuxt/runtime/server/markdown-trees.d.ts +2 -0
- package/dist/nuxt/runtime/server/markdown.d.ts +2 -0
- package/dist/nuxt/runtime/server/markdown.mjs +1 -1
- package/dist/nuxt/runtime/server/platform-md.d.mts +3 -0
- package/dist/nuxt/runtime/server/platform-md.d.ts +3 -0
- package/dist/nuxt/runtime/server/platform-md.mjs +71 -0
- package/dist/nuxt/runtime/server/redirects.d.ts +2 -0
- package/dist/nuxt/runtime/server/robots.d.ts +2 -0
- package/dist/nuxt/runtime/server/search.d.ts +2 -0
- package/dist/nuxt/runtime/server/site-config.d.ts +14 -0
- package/dist/nuxt/runtime/server/sitemap-source.d.ts +2 -0
- package/dist/nuxt/runtime/server/sitemap-xml.d.ts +24 -0
- package/dist/nuxt/runtime/server/sitemap.d.ts +2 -0
- package/dist/nuxt/runtime/shared.d.mts +10 -3
- package/dist/nuxt/runtime/shared.mjs +2 -2
- package/dist/shared/{ginko.BBkZW2nW.d.ts → ginko.6cI8KXZP.d.mts} +2 -2
- package/dist/shared/{ginko.CrNRBkS5.mjs → ginko.B1-DoOea.mjs} +253 -309
- package/dist/shared/ginko.BNVC-g9n.mjs +10389 -0
- package/dist/shared/{ginko.Bwan3BB-.d.mts → ginko.B_pACpVU.d.ts} +2 -2
- package/dist/shared/ginko.BaFweQcB.d.mts +91 -0
- package/dist/shared/ginko.BiDXgjLy.d.ts +91 -0
- package/dist/shared/ginko.C-oz8wwI.mjs +95 -0
- package/dist/shared/{ginko.BDF3bW0C.d.ts → ginko.CLzVaDi4.d.ts} +56 -5
- package/dist/shared/ginko.C_nq6vk7.d.mts +1622 -0
- package/dist/shared/ginko.CjWB8N7f.d.ts +128 -0
- package/dist/shared/ginko.D1PeQ3ds.mjs +36 -0
- package/dist/shared/ginko.D2oht0Kx.d.ts +1622 -0
- package/dist/shared/ginko.DCHGkQ7P.mjs +116 -0
- package/dist/shared/ginko.DIl7l-75.mjs +26 -0
- package/dist/shared/{ginko.akHewCGt.d.mts → ginko.DkIc0ZZk.d.mts} +56 -5
- package/dist/shared/{ginko.D-fNojn9.d.mts → ginko.DrkUeYFP.d.mts} +824 -10
- package/dist/shared/{ginko.D-fNojn9.d.ts → ginko.DrkUeYFP.d.ts} +824 -10
- package/dist/shared/ginko.WAfoMhv1.mjs +601 -0
- package/dist/shared/ginko.o1yIKlgj.d.mts +128 -0
- package/dist/shared/{ginko.Dht0xxE5.mjs → ginko.raCzOPYC.mjs} +1 -2
- package/dist/shared/{ginko.C90fPpMP.mjs → ginko.t-gbwzxh.mjs} +128 -101
- package/dist/shared/ginko.yKMIjJ68.mjs +84 -0
- package/dist/vue.d.mts +5 -4
- package/dist/vue.d.ts +5 -4
- package/dist/vue.mjs +1 -1
- package/i18n/ar.json +212 -0
- package/i18n/de.json +9 -1
- package/i18n/en.json +9 -1
- package/i18n/es.json +12 -4
- package/i18n/fr.json +212 -0
- package/i18n/it.json +212 -0
- package/package.json +7 -3
- package/dist/shared/ginko.BTUfZCfa.d.mts +0 -172
- package/dist/shared/ginko.C-QjDn3O.mjs +0 -4944
- package/dist/shared/ginko.CQvxhyez.mjs +0 -40
- package/dist/shared/ginko.Dc0oNcDv.d.ts +0 -172
- package/dist/shared/ginko.Mn30dmlm.mjs +0 -20
package/dist/nuxt/module.mjs
CHANGED
|
@@ -1,11 +1,14 @@
|
|
|
1
|
-
import { hasNuxtModule, defineNuxtModule, createResolver, tryResolveModule, installModule, addServerTemplate, addServerHandler, addComponentsDir, addTemplate, addTypeTemplate, addNitroPlugin, addPlugin, extendPages, addImports, addComponent } from '@nuxt/kit';
|
|
2
|
-
import { existsSync, mkdirSync, copyFileSync, readdirSync, rmSync } from 'node:fs';
|
|
1
|
+
import { hasNuxtModule, defineNuxtModule, createResolver, tryResolveModule, installModule, addServerTemplate, addServerHandler, addComponentsDir, addTemplate, addTypeTemplate, addNitroPlugin, addPlugin, extendPages, addImports, addComponent, addServerImports } from '@nuxt/kit';
|
|
2
|
+
import { watch, existsSync, readFileSync, mkdirSync, copyFileSync, readdirSync, rmSync } from 'node:fs';
|
|
3
3
|
import { join, relative, resolve, basename } from 'node:path';
|
|
4
4
|
import { pathToFileURL } from 'node:url';
|
|
5
|
-
import { f as fileLinkIssues } from '../shared/ginko.
|
|
6
|
-
import {
|
|
7
|
-
import {
|
|
8
|
-
import '../shared/ginko.
|
|
5
|
+
import { f as fileLinkIssues } from '../shared/ginko.raCzOPYC.mjs';
|
|
6
|
+
import { createHash, randomUUID } from 'node:crypto';
|
|
7
|
+
import { c as checkSite, s as serializeSchema } from '../shared/ginko.B1-DoOea.mjs';
|
|
8
|
+
import { g as siteForContent } from '../shared/ginko.C-oz8wwI.mjs';
|
|
9
|
+
import { L as redirectProblems, N as entryRedirects, F as coreMessage } from '../shared/ginko.BNVC-g9n.mjs';
|
|
10
|
+
import { w as withMarkdownRefs, f as fileMarkdownRef } from '../shared/ginko.DIl7l-75.mjs';
|
|
11
|
+
import '../shared/ginko.D1PeQ3ds.mjs';
|
|
9
12
|
import 'comark';
|
|
10
13
|
import 'comark/plugins/alert';
|
|
11
14
|
import 'comark/plugins/attributes';
|
|
@@ -19,6 +22,32 @@ import 'comark/plugins/task-list';
|
|
|
19
22
|
import 'comark/plugins/toc';
|
|
20
23
|
import '@lupinum/ginko-i18n/core';
|
|
21
24
|
|
|
25
|
+
function appSchema(schema) {
|
|
26
|
+
const { components: _components, ...schemaRest } = schema;
|
|
27
|
+
return {
|
|
28
|
+
...schemaRest,
|
|
29
|
+
bricks: Object.fromEntries(Object.entries(schema.bricks).map(([key, brick]) => [key, appBrick(brick)]))
|
|
30
|
+
};
|
|
31
|
+
}
|
|
32
|
+
function appBrick(brick) {
|
|
33
|
+
const { describe: _describe, tasks: _tasks, ...rest } = brick;
|
|
34
|
+
return { ...rest, label: "", fields: appFields(brick.fields) };
|
|
35
|
+
}
|
|
36
|
+
function componentsForMarkdown(schema) {
|
|
37
|
+
return Object.fromEntries(Object.entries(schema.components ?? {}).map(([tag, def]) => [
|
|
38
|
+
tag,
|
|
39
|
+
{ kind: def.kind, props: Object.fromEntries(Object.entries(def.props).map(([name, field]) => [name, { type: field.type }])) }
|
|
40
|
+
]));
|
|
41
|
+
}
|
|
42
|
+
function appFields(fields) {
|
|
43
|
+
return Object.fromEntries(Object.entries(fields).map(([name, field]) => [name, appField(field)]));
|
|
44
|
+
}
|
|
45
|
+
function appField(field) {
|
|
46
|
+
const { hint: _hint, group: _group, ...rest } = field;
|
|
47
|
+
if (rest.type === "list") return { ...rest, label: "", itemLabel: "", of: appFields(rest.of) };
|
|
48
|
+
return { ...rest, label: "" };
|
|
49
|
+
}
|
|
50
|
+
|
|
22
51
|
const OWNED = { locales: "locales", defaultLocale: "the first entry of locales", baseUrl: "url", timeZone: "timeZone" };
|
|
23
52
|
function checkI18nOptions(userI18n) {
|
|
24
53
|
const set = new Set(userI18n && typeof userI18n === "object" ? Object.entries(userI18n).filter(([, value]) => value !== void 0).map(([key]) => key) : []);
|
|
@@ -91,12 +120,13 @@ function unservedPaths(schema, pages) {
|
|
|
91
120
|
add(pages, "");
|
|
92
121
|
const served = (path) => patterns.some((pattern) => pattern.test(path));
|
|
93
122
|
const warnings = [];
|
|
123
|
+
const ours = (locale) => schema.locales.includes(locale);
|
|
94
124
|
for (const [key, paths] of Object.entries(schema.pages)) {
|
|
95
|
-
for (const [locale, path] of Object.entries(paths)) if (path && !served(path)) warnings.push(`pages.${key}.${locale}: no page at \u201C${path}\u201D. Fix: create the page or change the path in ginko.config.ts.`);
|
|
125
|
+
for (const [locale, path] of Object.entries(paths)) if (path && ours(locale) && !served(path)) warnings.push(`pages.${key}.${locale}: no page at \u201C${path}\u201D. Fix: create the page or change the path in ginko.config.ts.`);
|
|
96
126
|
}
|
|
97
127
|
for (const [key, brick] of Object.entries(schema.bricks)) {
|
|
98
128
|
if (brick.kind !== "collection" || !brick.route) continue;
|
|
99
|
-
for (const [locale, route] of Object.entries(brick.route)) if (route && !served(route.replace("[slug]", "beispiel"))) warnings.push(`${key}.route.${locale}: no page at \u201C${route}\u201D. Fix: create the page (for example pages/\u2026/[slug].vue) or change the route.`);
|
|
129
|
+
for (const [locale, route] of Object.entries(brick.route)) if (route && ours(locale) && !served(route.replace("[slug]", "beispiel"))) warnings.push(`${key}.route.${locale}: no page at \u201C${route}\u201D. Fix: create the page (for example pages/\u2026/[slug].vue) or change the route.`);
|
|
100
130
|
}
|
|
101
131
|
return warnings;
|
|
102
132
|
}
|
|
@@ -148,8 +178,61 @@ function ginkoRouteRules(site) {
|
|
|
148
178
|
return rules;
|
|
149
179
|
}
|
|
150
180
|
|
|
151
|
-
|
|
152
|
-
|
|
181
|
+
const CONFIG_NAMES = new Set(["ts", "mts", "js", "mjs"].map((ext) => `ginko.config.${ext}`));
|
|
182
|
+
function restartOnConfigChange(nuxt, rootDir) {
|
|
183
|
+
let ready = false;
|
|
184
|
+
let pending;
|
|
185
|
+
let restarting = false;
|
|
186
|
+
const restart = (name) => {
|
|
187
|
+
restarting = true;
|
|
188
|
+
console.info(`[ginko] ${name} changed: restarting the dev server.`);
|
|
189
|
+
void Promise.resolve(nuxt.callHook("restart", { hard: true })).catch((error) => console.error("[ginko] The dev server could not restart:", error));
|
|
190
|
+
};
|
|
191
|
+
const hashOf = (file) => existsSync(file) ? createHash("sha1").update(readFileSync(file)).digest("hex") : "";
|
|
192
|
+
const hashes = new Map([...CONFIG_NAMES].map((name) => [name, hashOf(join(rootDir, name))]));
|
|
193
|
+
const check = async (name) => {
|
|
194
|
+
const file = join(rootDir, name);
|
|
195
|
+
const hash = hashOf(file);
|
|
196
|
+
if (restarting || !hash || hashes.get(name) === hash) return;
|
|
197
|
+
hashes.set(name, hash);
|
|
198
|
+
const problem = await configProblem(file);
|
|
199
|
+
if (problem) console.error(`[ginko] ${name} has an error, the dev server keeps the last good version: ${problem}`);
|
|
200
|
+
else if (ready) restart(name);
|
|
201
|
+
else pending = name;
|
|
202
|
+
};
|
|
203
|
+
let queue = Promise.resolve();
|
|
204
|
+
let debounce;
|
|
205
|
+
const watcher = watch(rootDir, (_event, name) => {
|
|
206
|
+
if (!name || !CONFIG_NAMES.has(name)) return;
|
|
207
|
+
clearTimeout(debounce);
|
|
208
|
+
debounce = setTimeout(() => {
|
|
209
|
+
queue = queue.then(() => check(name)).catch((error) => console.error("[ginko]", error));
|
|
210
|
+
}, 100);
|
|
211
|
+
});
|
|
212
|
+
watcher.unref();
|
|
213
|
+
let settle;
|
|
214
|
+
nuxt.hook("ready", () => nuxt.hook("build:done", () => {
|
|
215
|
+
settle = setTimeout(() => {
|
|
216
|
+
ready = true;
|
|
217
|
+
if (pending && !restarting) restart(pending);
|
|
218
|
+
}, 1e3);
|
|
219
|
+
}));
|
|
220
|
+
nuxt.hook("close", () => {
|
|
221
|
+
clearTimeout(debounce);
|
|
222
|
+
clearTimeout(settle);
|
|
223
|
+
watcher.close();
|
|
224
|
+
});
|
|
225
|
+
}
|
|
226
|
+
async function configProblem(file) {
|
|
227
|
+
try {
|
|
228
|
+
const module = await import(`${pathToFileURL(file).href}?ginko=${randomUUID()}`);
|
|
229
|
+
const site = module.default;
|
|
230
|
+
if (!site?.site || !site.bricks) return "the default export must be the result of defineSite().";
|
|
231
|
+
const problems = checkSite(site);
|
|
232
|
+
return problems.length ? problems.join(" ") : void 0;
|
|
233
|
+
} catch (error) {
|
|
234
|
+
return error instanceof Error ? error.message.split("\n")[0] : String(error);
|
|
235
|
+
}
|
|
153
236
|
}
|
|
154
237
|
|
|
155
238
|
const runtimeExt = import.meta.url.endsWith(".mjs") ? "mjs" : "ts";
|
|
@@ -157,18 +240,19 @@ const module$1 = defineNuxtModule({
|
|
|
157
240
|
meta: { name: "@lupinum/ginko", configKey: "ginko", compatibility: { nuxt: "^4.6.0 || ^5.0.0-0" } },
|
|
158
241
|
// The site's data for the Nuxt SEO modules it installs (nuxt-seo.ts). Read from nuxt.config, before setup runs.
|
|
159
242
|
moduleDependencies: (nuxt) => nuxtSeoDependencies(nuxt.options.ginko, nuxt),
|
|
160
|
-
async setup(
|
|
161
|
-
if (!
|
|
243
|
+
async setup(options, nuxt) {
|
|
244
|
+
if (!options?.site || !options.bricks) throw new Error("[ginko] Option `ginko` is missing: the result of defineSite() from ginko.config.ts.");
|
|
245
|
+
const site = siteForContent(options, process.env.GINKO_CONTENT);
|
|
162
246
|
const schema = serializeSchema(site);
|
|
163
247
|
const problems = checkSite(site, schema);
|
|
164
248
|
if (problems.length) throw new Error(`[ginko] ginko.config.ts is invalid:
|
|
165
249
|
- ${problems.join("\n- ")}`);
|
|
250
|
+
if (nuxt.options.dev) restartOnConfigChange(nuxt, nuxt.options.rootDir);
|
|
166
251
|
const sources = {
|
|
167
252
|
platform: Object.values(schema.bricks).some((b) => b.source !== "files"),
|
|
168
253
|
files: Object.values(schema.bricks).some((b) => b.source === "files")
|
|
169
254
|
};
|
|
170
255
|
const { resolve } = createResolver(import.meta.url);
|
|
171
|
-
if (nuxt.options.dev) nuxt.options.watch.push(...configFilesToWatch(nuxt.options.rootDir));
|
|
172
256
|
Object.assign(nuxt.options.runtimeConfig.public, { ginko: {
|
|
173
257
|
site: schema.site,
|
|
174
258
|
name: site.name,
|
|
@@ -178,6 +262,10 @@ const module$1 = defineNuxtModule({
|
|
|
178
262
|
locales: schema.locales,
|
|
179
263
|
mainLocale: schema.locales[0],
|
|
180
264
|
timeZone: schema.timeZone,
|
|
265
|
+
// ISO country code or ''; the hours rules of the basics take it as `country`, JSON-LD as `addressCountry`.
|
|
266
|
+
country: site.country ?? "",
|
|
267
|
+
// The resolved holiday calendar (`schema.holidays`) or '': `custom` means the site passes its own `calendar`.
|
|
268
|
+
holidays: schema.holidays ?? "",
|
|
181
269
|
seo: site.seo ?? {},
|
|
182
270
|
agents: site.agents ?? {},
|
|
183
271
|
sources
|
|
@@ -222,8 +310,10 @@ export const ogTemplate = 'GinkoCard'` : "export const defineOgImage = undefined
|
|
|
222
310
|
nuxt.options.alias["#ginko/og"] = resolve(nuxt.options.buildDir, "ginko/og.mjs");
|
|
223
311
|
addTemplate({ filename: "ginko/schema-org.mjs", getContents: () => runsNuxtSeo("nuxt-schema-org", nuxt) ? `export { useSchemaOrg } from '#imports'` : "export const useSchemaOrg = undefined" });
|
|
224
312
|
nuxt.options.alias["#ginko/schema-org"] = resolve(nuxt.options.buildDir, "ginko/schema-org.mjs");
|
|
225
|
-
addTemplate({ filename: "ginko/schema.mjs", getContents: () => `export default ${JSON.stringify(schema)}` });
|
|
313
|
+
addTemplate({ filename: "ginko/schema.mjs", getContents: () => `export default ${JSON.stringify(appSchema(schema))}` });
|
|
226
314
|
nuxt.options.alias["#ginko/schema"] = resolve(nuxt.options.buildDir, "ginko/schema.mjs");
|
|
315
|
+
addTemplate({ filename: "ginko/components.mjs", getContents: () => `export default ${JSON.stringify(componentsForMarkdown(schema))}` });
|
|
316
|
+
nuxt.options.alias["#ginko/components"] = resolve(nuxt.options.buildDir, "ginko/components.mjs");
|
|
227
317
|
addServerTemplate({ filename: "#ginko/schema", getContents: () => `export default ${JSON.stringify(schema)}` });
|
|
228
318
|
const composables = resolve(`./runtime/composables.${runtimeExt}`);
|
|
229
319
|
addTypeTemplate({ filename: "types/ginko-virtual.d.ts", getContents: () => `/// <reference path=${JSON.stringify(resolve("./runtime/schema.d.ts"))} />
|
|
@@ -252,6 +342,7 @@ export {}` }, { nuxt: true, nitro: true });
|
|
|
252
342
|
else {
|
|
253
343
|
addServerTemplate({ filename: "#ginko/files", getContents: () => "export default {}" });
|
|
254
344
|
addServerTemplate({ filename: "#ginko/files-md", getContents: () => "export default {}" });
|
|
345
|
+
addServerTemplate({ filename: "#ginko/search", getContents: () => "export default {}" });
|
|
255
346
|
}
|
|
256
347
|
nuxt.hook("pages:resolved", (pages) => {
|
|
257
348
|
for (const warning of unservedPaths(schema, pages)) console.warn(`[ginko] ${warning}`);
|
|
@@ -262,12 +353,14 @@ export {}` }, { nuxt: true, nitro: true });
|
|
|
262
353
|
extendPages((pages) => {
|
|
263
354
|
pages.push({ name: "ginko-preview", path: "/__ginko/preview", file: resolve("./runtime/preview.vue") });
|
|
264
355
|
});
|
|
356
|
+
addServerHandler({ route: "/__ginko/platform/:locale/:rev/md/**", handler: resolve(`./runtime/server/platform-md.${runtimeExt}`) });
|
|
265
357
|
}
|
|
266
358
|
for (const name of ["useGinko", "useGinkoEntry", "useGinkoQuery", "useGinkoNeighbors", "useGinkoNav", "useGinkoBreadcrumbs", "useGinkoToday", "useGinkoWeek", "useGinkoLocale", "useGinkoSeo", "useGinkoPath", "useGinkoLang", "useGinkoSite"]) addImports({ name, from: composables });
|
|
267
359
|
for (const name of ["GinkoItem", "GinkoPageKey", "GinkoLocale", "GinkoSeoInput"]) addImports({ name, from: composables, type: true });
|
|
268
360
|
addComponent({ name: "GinkoMarkdown", export: "GinkoMarkdown", filePath: resolve(`./runtime/GinkoMarkdown.${runtimeExt}`) });
|
|
269
361
|
addComponent({ name: "GinkoImage", export: "GinkoImage", filePath: resolve(`./runtime/GinkoImage.${runtimeExt}`) });
|
|
270
362
|
for (const name of ["useGinkoMarkdown", "fetchGinkoMarkdown"]) addImports({ name, from: resolve(`./runtime/markdown.${runtimeExt}`) });
|
|
363
|
+
addServerImports([{ name: "getGinkoItems", from: resolve(`./runtime/server/items.${runtimeExt}`) }, { name: "GinkoServerItem", from: resolve(`./runtime/server/items.${runtimeExt}`), type: true }]);
|
|
271
364
|
}
|
|
272
365
|
});
|
|
273
366
|
async function useFiles(schema, site, nuxt) {
|
|
@@ -312,7 +405,7 @@ ${linkReport}`);
|
|
|
312
405
|
writeImages(result.imageFiles);
|
|
313
406
|
trees = /* @__PURE__ */ new Map();
|
|
314
407
|
const now = /* @__PURE__ */ new Date();
|
|
315
|
-
bundles = Object.fromEntries(Object.entries(result.bundles).map(([locale, bundle]) => [locale,
|
|
408
|
+
bundles = Object.fromEntries(Object.entries(result.bundles).map(([locale, bundle]) => [locale, withMarkdownRefs(content.publicBundle(bundle, schema, now, fileOptions), schema, trees, (brick, id, field) => fileMarkdownRef(locale, brick, id, field))]));
|
|
316
409
|
searches = Object.fromEntries(await Promise.all(Object.entries(bundles).map(async ([locale, bundle]) => [locale, await content.searchIndex(bundle, trees, schema)])));
|
|
317
410
|
};
|
|
318
411
|
await build();
|
|
@@ -33,7 +33,7 @@ export declare const GinkoImage: import("vue").DefineComponent<import("vue").Ext
|
|
|
33
33
|
default: boolean;
|
|
34
34
|
};
|
|
35
35
|
}>> & Readonly<{}>, {
|
|
36
|
-
sizes: string;
|
|
37
36
|
image: ImageValue | undefined;
|
|
37
|
+
sizes: string;
|
|
38
38
|
eager: boolean;
|
|
39
39
|
}, {}, {}, {}, string, import("vue").ComponentProvideOptions, true, {}, any>;
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { computed, defineComponent, h, inject, provide } from "vue";
|
|
2
|
-
import
|
|
2
|
+
import components from "#ginko/components";
|
|
3
3
|
import { GINKO_COMPONENTS_KEY, GINKO_TOC_KEY } from "../../internal.mjs";
|
|
4
4
|
import { GinkoMarkdown as GinkoMarkdownTree } from "../../vue.mjs";
|
|
5
5
|
import { useGinkoMarkdown } from "./markdown.mjs";
|
|
@@ -19,6 +19,6 @@ export const GinkoMarkdown = defineComponent({
|
|
|
19
19
|
let tree;
|
|
20
20
|
provide(GINKO_TOC_KEY, computed(() => tree?.value?.toc ?? null));
|
|
21
21
|
tree = await useGinkoMarkdown(() => props.value);
|
|
22
|
-
return () => h(GinkoMarkdownTree, { value: tree.value, components: { ...provided, ...props.components }, registry:
|
|
22
|
+
return () => h(GinkoMarkdownTree, { value: tree.value, components: { ...provided, ...props.components }, registry: components });
|
|
23
23
|
}
|
|
24
24
|
});
|
|
@@ -90,11 +90,12 @@ export declare function useGinkoLang(): (item: {
|
|
|
90
90
|
fallback?: readonly string[];
|
|
91
91
|
}, path: string) => string | undefined;
|
|
92
92
|
/**
|
|
93
|
-
* The visible entry whose slug in the current locale is `slug`; otherwise a real 404 (SPEC 8).
|
|
93
|
+
* The visible entry whose slug in the current locale is `slug`; otherwise a real 404 (SPEC 8). An entry without a
|
|
94
|
+
* page in this locale (untranslated) is a 404 too, also when a list links it to its main-language page. `slug` may be a
|
|
94
95
|
* getter such as `() => route.params.slug`, so a reused page follows client navigation; an array (catch-all
|
|
95
|
-
* route) is joined with `/`. The 404 is thrown
|
|
96
|
+
* route) is joined with `/`. The 404 is thrown on every read of the value while no entry matches, not at the call.
|
|
96
97
|
*/
|
|
97
|
-
export declare function useGinkoEntry<K extends CollectionKey>(brick: K, slug: MaybeRefOrGetter<string | string[] | undefined>):
|
|
98
|
+
export declare function useGinkoEntry<K extends CollectionKey>(brick: K, slug: MaybeRefOrGetter<string | string[] | undefined>): Readonly<Ref<GinkoItem<K>>>;
|
|
98
99
|
export interface GinkoSeoInput {
|
|
99
100
|
title?: string;
|
|
100
101
|
description?: string;
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { computed, createError, toValue, useI18n, useHead, useRoute, useRuntimeConfig, useState } from "#imports";
|
|
1
|
+
import { computed, createError, toRef, toValue, useI18n, useHead, useRoute, useRuntimeConfig, useState } from "#imports";
|
|
2
2
|
import schema from "#ginko/schema";
|
|
3
3
|
import { defineOgImage, ogTemplate } from "#ginko/og";
|
|
4
4
|
import { useSchemaOrg } from "#ginko/schema-org";
|
|
@@ -9,10 +9,12 @@ import {
|
|
|
9
9
|
navTree,
|
|
10
10
|
neighbors,
|
|
11
11
|
organizationLd,
|
|
12
|
+
ORGANIZATION_TYPES,
|
|
12
13
|
query,
|
|
13
14
|
seoHead,
|
|
14
15
|
todayIn,
|
|
15
|
-
websiteLd
|
|
16
|
+
websiteLd,
|
|
17
|
+
withCountry
|
|
16
18
|
} from "../../internal.mjs";
|
|
17
19
|
import { withUrl } from "./shared.mjs";
|
|
18
20
|
export const BUNDLE_STATE = "ginko:bundle";
|
|
@@ -53,12 +55,11 @@ export function useGinkoWeek() {
|
|
|
53
55
|
}
|
|
54
56
|
export function useGinko(brick) {
|
|
55
57
|
const bundle = useVisibleBundle([brick]);
|
|
56
|
-
const locale = useGinkoLocale();
|
|
57
58
|
return computed(() => {
|
|
58
59
|
const value = bundle.value.bricks[brick];
|
|
59
60
|
const def = schema.bricks[brick];
|
|
60
61
|
if (value?.kind === "collection" && def) {
|
|
61
|
-
return { items: def.kind === "collection" && def.route ? withUrl(value.items, locale.value) : value.items };
|
|
62
|
+
return { items: def.kind === "collection" && def.route ? withUrl(value.items, bundle.value.locale, bundle.value.mainLocale) : value.items };
|
|
62
63
|
}
|
|
63
64
|
return value;
|
|
64
65
|
});
|
|
@@ -77,24 +78,22 @@ function useVisibleBundle(bricks) {
|
|
|
77
78
|
return { ...bundle.value, bricks: out };
|
|
78
79
|
});
|
|
79
80
|
}
|
|
80
|
-
function withRouteUrl(brick, items,
|
|
81
|
+
function withRouteUrl(brick, items, bundle) {
|
|
81
82
|
const def = schema.bricks[brick];
|
|
82
|
-
return def?.kind === "collection" && def.route ? withUrl(items, locale) : items;
|
|
83
|
+
return def?.kind === "collection" && def.route ? withUrl(items, bundle.locale, bundle.mainLocale) : items;
|
|
83
84
|
}
|
|
84
85
|
export function useGinkoQuery(brick, q = {}) {
|
|
85
86
|
const bundle = useVisibleBundle([brick]);
|
|
86
|
-
const locale = useGinkoLocale();
|
|
87
87
|
return computed(() => {
|
|
88
88
|
const result = query(bundle.value, brick, toValue(q), schema);
|
|
89
|
-
return { ...result, items: withRouteUrl(brick, result.items,
|
|
89
|
+
return { ...result, items: withRouteUrl(brick, result.items, bundle.value) };
|
|
90
90
|
});
|
|
91
91
|
}
|
|
92
92
|
export function useGinkoNeighbors(brick, id, options = {}) {
|
|
93
93
|
const bundle = useVisibleBundle([brick]);
|
|
94
|
-
const locale = useGinkoLocale();
|
|
95
94
|
return computed(() => {
|
|
96
95
|
const { prev, next } = neighbors(bundle.value, brick, toValue(id), options.order);
|
|
97
|
-
const one = (item) => item && withRouteUrl(brick, [item],
|
|
96
|
+
const one = (item) => item && withRouteUrl(brick, [item], bundle.value)[0];
|
|
98
97
|
return { prev: one(prev), next: one(next) };
|
|
99
98
|
});
|
|
100
99
|
}
|
|
@@ -116,12 +115,15 @@ export function useGinkoLang() {
|
|
|
116
115
|
export function useGinkoEntry(brick, slug) {
|
|
117
116
|
const items = useGinko(brick);
|
|
118
117
|
const locale = useGinkoLocale();
|
|
119
|
-
|
|
118
|
+
const entry = computed(() => {
|
|
120
119
|
const def = schema.bricks[brick];
|
|
121
120
|
const route = def.kind === "collection" ? def.route?.[locale.value] : void 0;
|
|
122
121
|
const value = toValue(slug);
|
|
123
122
|
const path = value === void 0 ? void 0 : route?.replace("[slug]", Array.isArray(value) ? value.join("/") : value);
|
|
124
|
-
|
|
123
|
+
return items.value.items.find((i) => path && i.urls?.[locale.value] === path);
|
|
124
|
+
});
|
|
125
|
+
return toRef(() => {
|
|
126
|
+
const item = entry.value;
|
|
125
127
|
if (!item) throw createError({ statusCode: 404, statusMessage: "Not found", fatal: true });
|
|
126
128
|
return item;
|
|
127
129
|
});
|
|
@@ -153,7 +155,8 @@ export function useGinkoSeo(input) {
|
|
|
153
155
|
}
|
|
154
156
|
});
|
|
155
157
|
}
|
|
156
|
-
|
|
158
|
+
const pageLd = () => (toValue(input).jsonLd ?? []).map((node) => withCountry(node, config.country));
|
|
159
|
+
if (useSchemaOrg) useSchemaOrg(computed(() => pageLd().map(({ "@context": _context, ...node }) => node)));
|
|
157
160
|
const trim = (p) => p.length > 1 ? p.replace(/\/$/, "") : p;
|
|
158
161
|
useHead(() => {
|
|
159
162
|
const page = toValue(input);
|
|
@@ -161,12 +164,13 @@ export function useGinkoSeo(input) {
|
|
|
161
164
|
const raw = generated ? void 0 : page.image ?? seo.image;
|
|
162
165
|
const image = raw === void 0 ? void 0 : typeof raw === "string" ? { url: absolute(raw) } : { ...raw, url: absolute(raw.url) };
|
|
163
166
|
const head = seoHead({ siteName: config.name, titleTemplate: seo.titleTemplate, title: page.title, description: page.description ?? seo.description, image, type: page.type, noindex: page.noindex });
|
|
164
|
-
const ld = useSchemaOrg ? [] :
|
|
167
|
+
const ld = useSchemaOrg ? [] : pageLd();
|
|
165
168
|
const home = Object.keys(schema.pages).find((key) => schema.pages[key]?.[config.mainLocale] === "/");
|
|
166
169
|
if (!useSchemaOrg && home !== void 0 && trim(route.path) === trim(path(home, locale.value))) {
|
|
167
170
|
const lang = i18n.locales.find((l) => l.code === locale.value)?.language ?? locale.value;
|
|
168
171
|
ld.unshift(websiteLd({ name: config.name, url: config.url, lang }));
|
|
169
|
-
|
|
172
|
+
const own = ld.some((node) => ORGANIZATION_TYPES.includes(node["@type"]));
|
|
173
|
+
if (seo.organization && !own) ld.splice(1, 0, organizationLd({ name: config.name, url: config.url, ...seo.organization, logo: seo.organization.logo && absolute(seo.organization.logo) }));
|
|
170
174
|
}
|
|
171
175
|
return {
|
|
172
176
|
title: head.title,
|
|
@@ -8,8 +8,10 @@ import type { GinkoPublicConfig } from './shared.mjs';
|
|
|
8
8
|
export declare function loadBundle(config: GinkoPublicConfig, locale: Locale, minRev: string | undefined, loadFiles: (locale: Locale) => Promise<Bundle | undefined>, siteToken?: string): Promise<Bundle>;
|
|
9
9
|
/** A platform failure: a plain error that carries the status, for the caller to turn into its framework's error. */
|
|
10
10
|
export declare class PlatformError extends Error {
|
|
11
|
+
readonly platformStatus?: number | undefined;
|
|
11
12
|
readonly statusCode = 503;
|
|
12
|
-
|
|
13
|
+
/** `platformStatus`: the platform's own HTTP status when it answered with one (409: it has not reached `minRev`). */
|
|
14
|
+
constructor(message: string, cause?: unknown, platformStatus?: number | undefined);
|
|
13
15
|
}
|
|
14
16
|
/**
|
|
15
17
|
* The 503 text. In dev the platform is a local address that is often not running (a new site, a pasted README
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import schema from "#ginko/schema";
|
|
2
|
-
import { BUNDLE_FORMAT, bundleFits, isBundle, knownBricks, withFileBricks } from "../../internal.mjs";
|
|
2
|
+
import { BUNDLE_FORMAT, bundleFits, isBundle, knownBricks, SITE_BUDGET, withFileBricks } from "../../internal.mjs";
|
|
3
3
|
const TIMEOUT_MS = 1e4;
|
|
4
|
-
const MAX_CHARS =
|
|
4
|
+
const MAX_CHARS = (SITE_BUDGET.siteBytes + SITE_BUDGET.treeBytes) * 1.5;
|
|
5
5
|
export async function loadBundle(config, locale, minRev, loadFiles, siteToken) {
|
|
6
6
|
const [platform, files] = await Promise.all([
|
|
7
7
|
config.sources.platform ? fetchBundle(config, locale, minRev, siteToken) : void 0,
|
|
@@ -10,10 +10,12 @@ export async function loadBundle(config, locale, minRev, loadFiles, siteToken) {
|
|
|
10
10
|
return withFileBricks(platform, files, schema);
|
|
11
11
|
}
|
|
12
12
|
export class PlatformError extends Error {
|
|
13
|
-
|
|
14
|
-
constructor(message, cause) {
|
|
13
|
+
/** `platformStatus`: the platform's own HTTP status when it answered with one (409: it has not reached `minRev`). */
|
|
14
|
+
constructor(message, cause, platformStatus) {
|
|
15
15
|
super(message, { cause });
|
|
16
|
+
this.platformStatus = platformStatus;
|
|
16
17
|
}
|
|
18
|
+
statusCode = 503;
|
|
17
19
|
}
|
|
18
20
|
export function unreachableMessage(config, url) {
|
|
19
21
|
const local = /^https?:\/\/(localhost|127\.0\.0\.1|\[::1\])(:|\/|$)/.test(config.platform);
|
|
@@ -34,16 +36,20 @@ export async function fetchBundle(config, locale, minRev, siteToken) {
|
|
|
34
36
|
if (minRev && /^\d+\.\d+$/.test(minRev)) query.set("minRev", minRev);
|
|
35
37
|
const url = `${config.platform.replace(/\/$/, "")}/bundle/${encodeURIComponent(config.site)}?${query}`;
|
|
36
38
|
let body;
|
|
39
|
+
let status;
|
|
37
40
|
try {
|
|
38
41
|
const headers = { accept: "application/json" };
|
|
39
42
|
if (siteToken) headers[SITE_TOKEN_HEADER] = siteToken;
|
|
40
43
|
const response = await fetch(url, { headers, signal: AbortSignal.timeout(TIMEOUT_MS) });
|
|
41
|
-
if (!response.ok)
|
|
44
|
+
if (!response.ok) {
|
|
45
|
+
status = response.status;
|
|
46
|
+
throw new Error(`HTTP ${status}`);
|
|
47
|
+
}
|
|
42
48
|
const text = await response.text();
|
|
43
49
|
if (text.length > MAX_CHARS) throw new Error("bundle too large");
|
|
44
50
|
body = JSON.parse(text);
|
|
45
51
|
} catch (error) {
|
|
46
|
-
throw new PlatformError(unreachableMessage(config, url), error);
|
|
52
|
+
throw new PlatformError(unreachableMessage(config, url), error, status);
|
|
47
53
|
}
|
|
48
54
|
if (!isBundle(body) || body.site !== config.site || !bundleFits(body, schema)) throw new PlatformError("Content in the wrong format");
|
|
49
55
|
return knownBricks(body, schema);
|
|
@@ -1,5 +1,8 @@
|
|
|
1
1
|
import { type ComputedRef, type MaybeRefOrGetter } from 'vue';
|
|
2
|
+
import type { NuxtApp } from '#app';
|
|
2
3
|
import type { MarkdownTree, MarkdownValue } from '../../internal.mjs';
|
|
4
|
+
/** The trees of this server request (empty outside a server render). @internal Filled by the Ginko plugin. */
|
|
5
|
+
export declare function serverTrees(nuxtApp: NuxtApp): Map<string, MarkdownTree>;
|
|
3
6
|
/**
|
|
4
7
|
* The tree of a markdown value, for server code such as a list page's `useAsyncData` that reads
|
|
5
8
|
* several texts and keeps only what the page needs. `undefined` (an optional field) gives `null`.
|
|
@@ -1,15 +1,27 @@
|
|
|
1
1
|
import { computed, toValue } from "vue";
|
|
2
|
-
import { useAsyncData } from "#imports";
|
|
2
|
+
import { tryUseNuxtApp, useAsyncData } from "#imports";
|
|
3
|
+
const requestTrees = /* @__PURE__ */ new WeakMap();
|
|
4
|
+
export function serverTrees(nuxtApp) {
|
|
5
|
+
const context = nuxtApp.ssrContext;
|
|
6
|
+
if (!context) return /* @__PURE__ */ new Map();
|
|
7
|
+
let trees = requestTrees.get(context);
|
|
8
|
+
if (!trees) requestTrees.set(context, trees = /* @__PURE__ */ new Map());
|
|
9
|
+
return trees;
|
|
10
|
+
}
|
|
11
|
+
function loadTree(ref, nuxtApp = tryUseNuxtApp() ?? void 0) {
|
|
12
|
+
const tree = import.meta.server && nuxtApp?.ssrContext ? requestTrees.get(nuxtApp.ssrContext)?.get(ref) : void 0;
|
|
13
|
+
return tree ? Promise.resolve(tree) : $fetch(ref);
|
|
14
|
+
}
|
|
3
15
|
export async function fetchGinkoMarkdown(value) {
|
|
4
16
|
if (!value) return null;
|
|
5
|
-
return "nodes" in value ? value :
|
|
17
|
+
return "nodes" in value ? value : loadTree(value.ref);
|
|
6
18
|
}
|
|
7
19
|
export async function useGinkoMarkdown(value) {
|
|
8
20
|
const ref = computed(() => {
|
|
9
21
|
const v = toValue(value);
|
|
10
22
|
return v && "ref" in v ? v.ref : "";
|
|
11
23
|
});
|
|
12
|
-
const { data } = await useAsyncData(() => `ginko-md:${ref.value}`, () => ref.value ?
|
|
24
|
+
const { data } = await useAsyncData(() => `ginko-md:${ref.value}`, (nuxtApp) => ref.value ? loadTree(ref.value, nuxtApp) : Promise.resolve(null));
|
|
13
25
|
return computed(() => {
|
|
14
26
|
const v = toValue(value);
|
|
15
27
|
return v && "nodes" in v ? v : data.value ?? null;
|
|
@@ -1,8 +1,9 @@
|
|
|
1
|
-
import { addAlternatesResolver, addRouteMiddleware,
|
|
1
|
+
import { addAlternatesResolver, addRouteMiddleware, createError, defineNuxtPlugin, useRequestHeader, useRequestURL, useResponseHeader, useRuntimeConfig, useState } from "#imports";
|
|
2
2
|
import schema from "#ginko/schema";
|
|
3
|
-
import {
|
|
3
|
+
import { platformMarkdownRef, withMarkdownRefs } from "../../internal.mjs";
|
|
4
4
|
import { BUNDLE_STATE, PREVIEW_STATE } from "./composables.mjs";
|
|
5
5
|
import { loadBundle as loadBundleCore, PlatformError } from "./fetch.mjs";
|
|
6
|
+
import { serverTrees } from "./markdown.mjs";
|
|
6
7
|
import { localeFromPath } from "@lupinum/ginko-i18n/core";
|
|
7
8
|
import { config as i18nConfig } from "#ginko-i18n/config.mjs";
|
|
8
9
|
async function loadBundle(config, locale, minRev, siteToken) {
|
|
@@ -24,10 +25,11 @@ export default defineNuxtPlugin(async (nuxtApp) => {
|
|
|
24
25
|
const preview = useState(PREVIEW_STATE, () => null);
|
|
25
26
|
const load = async (locale) => {
|
|
26
27
|
if (bundles.value[locale]) return;
|
|
27
|
-
|
|
28
|
+
const full = await loadBundle(config, locale, minRev, siteToken);
|
|
29
|
+
const rev = `${full.schemaRev}.${full.contentRev}`;
|
|
30
|
+
bundles.value = { ...bundles.value, [locale]: import.meta.server && config.sources.platform ? withMarkdownRefs(full, schema, serverTrees(nuxtApp), (brick, id, field) => platformMarkdownRef(locale, rev, brick, id, field)) : full };
|
|
28
31
|
};
|
|
29
32
|
const path = useRequestURL().pathname;
|
|
30
|
-
if (import.meta.client && PREVIEW.test(path)) listenToPlatform(nuxtApp, config, bundles, preview);
|
|
31
33
|
const errorQuery = ERROR_PAGE.test(path) ? useRequestURL().searchParams : void 0;
|
|
32
34
|
const pageError = () => import.meta.server ? nuxtApp.ssrContext?.error : nuxtApp.payload.error;
|
|
33
35
|
const errorPage = () => {
|
|
@@ -56,34 +58,3 @@ function entryUrls(bundle, path) {
|
|
|
56
58
|
}
|
|
57
59
|
return void 0;
|
|
58
60
|
}
|
|
59
|
-
function listenToPlatform(nuxtApp, config, bundles, preview) {
|
|
60
|
-
const origin = config.platform ? new URL(config.platform).origin : "";
|
|
61
|
-
window.addEventListener("message", (event) => {
|
|
62
|
-
const state = preview.value;
|
|
63
|
-
if (!state || !origin || event.source !== window.parent || event.origin !== origin) return;
|
|
64
|
-
const data = event.data;
|
|
65
|
-
if (typeof data !== "object" || data === null) return;
|
|
66
|
-
const message = data;
|
|
67
|
-
if (message.type !== "ginko:preview" || message.session !== state.session || message.site !== config.site || message.locale !== state.locale || !isBundle(message.bundle) || !bundleFits(message.bundle, schema)) return;
|
|
68
|
-
const platform = knownBricks(message.bundle, schema);
|
|
69
|
-
const locale = state.locale;
|
|
70
|
-
void filesOf(locale).then((files) => {
|
|
71
|
-
if (preview.value?.session !== state.session) return;
|
|
72
|
-
bundles.value = { ...bundles.value, [locale]: withFileBricks(platform, files, schema) };
|
|
73
|
-
void nuxtApp.runWithContext(() => useError().value ? clearError() : void 0);
|
|
74
|
-
});
|
|
75
|
-
});
|
|
76
|
-
const fileBundles = {};
|
|
77
|
-
const filesOf = (locale) => fileBundles[locale] ??= config.sources.files ? $fetch(`/__ginko/files/${locale}.json`).catch(() => void 0) : Promise.resolve(void 0);
|
|
78
|
-
const interceptLink = (event) => {
|
|
79
|
-
const state = preview.value;
|
|
80
|
-
if (!state || event.type === "auxclick" && event.button !== 1 || !(event.target instanceof Element)) return;
|
|
81
|
-
const link = event.target.closest("a[href]");
|
|
82
|
-
if (!link) return;
|
|
83
|
-
event.preventDefault();
|
|
84
|
-
const url = new URL(link.getAttribute("href") ?? "", location.href);
|
|
85
|
-
if (origin && url.origin === location.origin) window.parent.postMessage({ type: "ginko:navigate", session: state.session, path: url.pathname + url.search + url.hash }, origin);
|
|
86
|
-
};
|
|
87
|
-
document.addEventListener("click", interceptLink, true);
|
|
88
|
-
document.addEventListener("auxclick", interceptLink, true);
|
|
89
|
-
}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import type { NuxtApp } from '#app';
|
|
2
|
+
import type { Ref } from 'vue';
|
|
3
|
+
import { type Bundle, type Locale } from '../../internal.mjs';
|
|
4
|
+
import type { GinkoPublicConfig } from './shared.mjs';
|
|
5
|
+
/**
|
|
6
|
+
* Preview messages (SPEC 8 "Vorschau"). Only the platform's frame (source and origin) may send a bundle
|
|
7
|
+
* for the session and locale the preview page announced. Links are cancelled and reported to the platform.
|
|
8
|
+
* Once per page load: a second call does nothing.
|
|
9
|
+
*/
|
|
10
|
+
export declare function listenToPlatform(nuxtApp: NuxtApp, config: GinkoPublicConfig, bundles: Ref<Partial<Record<Locale, Bundle>>>, preview: Ref<{
|
|
11
|
+
session: string;
|
|
12
|
+
locale: Locale;
|
|
13
|
+
} | null>): void;
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
import { clearError, useError } from "#imports";
|
|
2
|
+
import schema from "#ginko/schema";
|
|
3
|
+
import { bundleFits, isBundle, knownBricks, withFileBricks } from "../../internal.mjs";
|
|
4
|
+
let listening = false;
|
|
5
|
+
export function listenToPlatform(nuxtApp, config, bundles, preview) {
|
|
6
|
+
if (listening) return;
|
|
7
|
+
listening = true;
|
|
8
|
+
const origin = config.platform ? new URL(config.platform).origin : "";
|
|
9
|
+
window.addEventListener("message", (event) => {
|
|
10
|
+
const state = preview.value;
|
|
11
|
+
if (!state || !origin || event.source !== window.parent || event.origin !== origin) return;
|
|
12
|
+
const data = event.data;
|
|
13
|
+
if (typeof data !== "object" || data === null) return;
|
|
14
|
+
const message = data;
|
|
15
|
+
if (message.type !== "ginko:preview" || message.session !== state.session || message.site !== config.site || message.locale !== state.locale || !isBundle(message.bundle) || !bundleFits(message.bundle, schema)) return;
|
|
16
|
+
const platform = knownBricks(message.bundle, schema);
|
|
17
|
+
const locale = state.locale;
|
|
18
|
+
void filesOf(locale).then((files) => {
|
|
19
|
+
if (preview.value?.session !== state.session) return;
|
|
20
|
+
bundles.value = { ...bundles.value, [locale]: withFileBricks(platform, files, schema) };
|
|
21
|
+
void nuxtApp.runWithContext(() => useError().value ? clearError() : void 0);
|
|
22
|
+
});
|
|
23
|
+
});
|
|
24
|
+
const fileBundles = {};
|
|
25
|
+
const filesOf = (locale) => fileBundles[locale] ??= config.sources.files ? $fetch(`/__ginko/files/${locale}.json`).catch(() => void 0) : Promise.resolve(void 0);
|
|
26
|
+
const interceptLink = (event) => {
|
|
27
|
+
const state = preview.value;
|
|
28
|
+
if (!state || event.type === "auxclick" && event.button !== 1 || !(event.target instanceof Element)) return;
|
|
29
|
+
const link = event.target.closest("a[href]");
|
|
30
|
+
if (!link) return;
|
|
31
|
+
event.preventDefault();
|
|
32
|
+
const url = new URL(link.getAttribute("href") ?? "", location.href);
|
|
33
|
+
if (origin && url.origin === location.origin) window.parent.postMessage({ type: "ginko:navigate", session: state.session, path: url.pathname + url.search + url.hash }, origin);
|
|
34
|
+
};
|
|
35
|
+
document.addEventListener("click", interceptLink, true);
|
|
36
|
+
document.addEventListener("auxclick", interceptLink, true);
|
|
37
|
+
}
|
|
@@ -1,13 +1,15 @@
|
|
|
1
1
|
<script setup>
|
|
2
|
-
import { definePageMeta, navigateTo, onBeforeUnmount, onMounted, ref, useRequestHeaders, useRoute, useRouter, useRuntimeConfig, useState, watch } from "#imports";
|
|
2
|
+
import { definePageMeta, navigateTo, onBeforeUnmount, onMounted, ref, useNuxtApp, useRequestHeaders, useRoute, useRouter, useRuntimeConfig, useState, watch } from "#imports";
|
|
3
3
|
import { coreMessage } from "../../internal.mjs";
|
|
4
4
|
import { BUNDLE_STATE, PREVIEW_STATE } from "./composables.mjs";
|
|
5
|
+
import { listenToPlatform } from "./preview-listener.mjs";
|
|
5
6
|
definePageMeta({ layout: false });
|
|
6
7
|
const route = useRoute();
|
|
7
8
|
const router = useRouter();
|
|
8
9
|
const config = useRuntimeConfig().public.ginko;
|
|
9
10
|
const bundles = useState(BUNDLE_STATE, () => ({}));
|
|
10
11
|
const preview = useState(PREVIEW_STATE, () => null);
|
|
12
|
+
if (import.meta.client) listenToPlatform(useNuxtApp(), config, bundles, preview);
|
|
11
13
|
const reader = useState("ginko:preview-reader", () => (import.meta.server ? useRequestHeaders(["accept-language"])["accept-language"]?.split(/[,;]/)[0] : navigator.language) || "en");
|
|
12
14
|
function isSafePath(raw) {
|
|
13
15
|
let decoded;
|
|
@@ -7,6 +7,12 @@ declare module '#ginko/schema' {
|
|
|
7
7
|
export default schema
|
|
8
8
|
}
|
|
9
9
|
|
|
10
|
+
// The Markdown component registry for the renderer (module.ts, componentsForMarkdown): declared props and their types.
|
|
11
|
+
declare module '#ginko/components' {
|
|
12
|
+
const components: import('@lupinum/ginko/internal').AttributeRegistry
|
|
13
|
+
export default components
|
|
14
|
+
}
|
|
15
|
+
|
|
10
16
|
// The site's file bricks as bundles per locale (CONTENT.md), a Nitro virtual module built from content/.
|
|
11
17
|
declare module '#ginko/files' {
|
|
12
18
|
const bundles: Partial<Record<import('@lupinum/ginko').Locale, import('@lupinum/ginko/internal').Bundle>>
|
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
import { getRequestHeader } from 'nuxt/server';
|
|
2
|
+
import type { Bundle, Locale } from '../../../internal.mjs';
|
|
3
|
+
import type { GinkoPublicConfig } from '../shared.mjs';
|
|
4
|
+
export declare function loadServerBundles(event: Parameters<typeof getRequestHeader>[0], config: GinkoPublicConfig, locales: readonly Locale[]): Promise<Partial<Record<Locale, Bundle>>>;
|