@monkeyplus/flow 6.0.56 → 6.0.58

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@monkeyplus/flow",
3
- "version": "6.0.56",
3
+ "version": "6.0.58",
4
4
  "description": "@monkeyplus/flow package-first runtime with Vite, Nitro, Vue and a workspace playground.",
5
5
  "license": "MIT",
6
6
  "publishConfig": {
@@ -1,4 +1,4 @@
1
- import type { DynamicRouteEntry, FlowLocale, GetUrlOptions, HeadDefinition, PageDefinition, PageLocaleDefinition, PageUrlInfo } from '../../src/runtime/pages';
1
+ import type { DynamicRouteEntry, FlowLocale, GetUrlOptions, HeadDefinition, PageDefinition, PageLocaleDefinition, PageUrlInfo, BreadcrumbItem } from '../../src/runtime/pages';
2
2
  export interface ResolvedPage {
3
3
  definition: PageDefinition;
4
4
  locale: FlowLocale;
@@ -13,8 +13,10 @@ export interface ResolvedPage {
13
13
  */
14
14
  seo: HeadDefinition;
15
15
  context: Record<string, unknown>;
16
+ breadcrumbs: BreadcrumbItem[];
16
17
  }
17
18
  export declare function getUrl(namePage: string, localeCode?: string, options?: GetUrlOptions): Promise<string | undefined>;
18
19
  export declare function getUrls(withLocale?: boolean, omitNoPublish?: boolean): Promise<(string | PageUrlInfo)[]>;
19
20
  export declare function resolvePage(pathname: string, request?: Request): Promise<ResolvedPage | undefined>;
20
21
  export declare function resolvePageByName(name: string, pathname: string, request?: Request): Promise<ResolvedPage | undefined>;
22
+ export declare function resolveBreadcrumbs(pageName: string, pathname: string, localeCode: string, params: Record<string, string>, dynamic: DynamicRouteEntry | undefined, request: Request | undefined, visited?: Set<string>): Promise<BreadcrumbItem[]>;
@@ -98,10 +98,14 @@ function createLocale(code) {
98
98
  function createContext(definition, locale, localePage, pathname, params, dynamic, request) {
99
99
  const imageUtils = getFlowImageRuntimeUtils();
100
100
  const preview = request?.headers.get("cookie")?.includes("flow_preview=true") || false;
101
+ const config = getFlowRuntimeConfig();
102
+ const siteUrl = config.flow?.siteUrl || "";
103
+ const fullPath = siteUrl ? `${siteUrl.replace(/\/$/, "")}${pathname}` : pathname;
101
104
  return {
102
105
  dynamic,
103
106
  locale,
104
107
  path: pathname,
108
+ fullPath,
105
109
  name: definition.name,
106
110
  page: localePage,
107
111
  params,
@@ -317,6 +321,7 @@ export async function resolvePage(pathname, request) {
317
321
  const assign = match.localePage.dynamic?.assign;
318
322
  const resolvedContext = mergeResolvedContext(context, layoutContext.context, dynamic, assign);
319
323
  const head = mergeHeadDefinitions(layoutContext.head.global, layoutContext.head.layout, pageHead);
324
+ const breadcrumbs = await resolveBreadcrumbs(match.definition.name, pathname, match.localeCode, match.params, dynamic, request);
320
325
  return {
321
326
  definition: match.definition,
322
327
  locale,
@@ -327,7 +332,8 @@ export async function resolvePage(pathname, request) {
327
332
  dynamic,
328
333
  head,
329
334
  seo: head,
330
- context: resolvedContext
335
+ context: resolvedContext,
336
+ breadcrumbs
331
337
  };
332
338
  }
333
339
  return void 0;
@@ -350,6 +356,7 @@ export async function resolvePageByName(name, pathname, request) {
350
356
  const layoutContext = await resolveLayoutContext(definition, ctx);
351
357
  const context = localePage.context ? await localePage.context(ctx) : {};
352
358
  const head = mergeHeadDefinitions(layoutContext.head.global, layoutContext.head.layout, pageHead);
359
+ const breadcrumbs = await resolveBreadcrumbs(definition.name, pathname, localeCode, params, void 0, request);
353
360
  return {
354
361
  definition,
355
362
  locale,
@@ -359,8 +366,56 @@ export async function resolvePageByName(name, pathname, request) {
359
366
  params,
360
367
  head,
361
368
  seo: head,
362
- context: mergeResolvedContext(context, layoutContext.context)
369
+ context: mergeResolvedContext(context, layoutContext.context),
370
+ breadcrumbs
363
371
  };
364
372
  }
365
373
  return void 0;
366
374
  }
375
+ export async function resolveBreadcrumbs(pageName, pathname, localeCode, params, dynamic, request, visited = /* @__PURE__ */ new Set()) {
376
+ if (visited.has(pageName)) {
377
+ return [];
378
+ }
379
+ visited.add(pageName);
380
+ const definition = pageDefinitions.find((candidate) => candidate.name === pageName);
381
+ if (!definition) return [];
382
+ const localePage = definition.locales[localeCode];
383
+ if (!localePage || !localePage.breadcrumb) return [];
384
+ const locale = createLocale(localeCode);
385
+ const ctx = createContext(definition, locale, localePage, pathname, params, dynamic, request);
386
+ const pageContext = await executeContextDefinition(localePage.context, pageName, ctx);
387
+ const pageHead = await executeContextDefinition(localePage.head, pageName, ctx);
388
+ const breadcrumbCtx = {
389
+ ...ctx,
390
+ context: pageContext,
391
+ head: pageHead,
392
+ seo: pageHead.seo || pageHead
393
+ };
394
+ const result = await localePage.breadcrumb(breadcrumbCtx);
395
+ if (!result) return [];
396
+ let currentItems = [];
397
+ if (result.items) {
398
+ currentItems = result.items;
399
+ } else if (result.item) {
400
+ currentItems = [result.item];
401
+ }
402
+ for (const item of currentItems) {
403
+ if (!item.url || item.url === "#") {
404
+ item.url = await ctx.utils.getUrl(pageName, localeCode, { params });
405
+ }
406
+ }
407
+ let parentItems = [];
408
+ if (result.parent) {
409
+ let parentName = "";
410
+ let parentParams = params;
411
+ if (typeof result.parent === "string") {
412
+ parentName = result.parent;
413
+ } else {
414
+ parentName = result.parent.name;
415
+ parentParams = result.parent.params;
416
+ }
417
+ const parentPathname = await ctx.utils.getUrl(parentName, localeCode, { params: parentParams }) || "/";
418
+ parentItems = await resolveBreadcrumbs(parentName, parentPathname, localeCode, parentParams, void 0, request, visited);
419
+ }
420
+ return [...parentItems, ...currentItems];
421
+ }
@@ -142,6 +142,7 @@ ${cleanTrace}`);
142
142
  };
143
143
  app.provide("context", context);
144
144
  app.provide("utils", utils);
145
+ app.provide("breadcrumbs", page.breadcrumbs);
145
146
  const head = createHead();
146
147
  head.use(UnheadSchemaOrg());
147
148
  head.push({
@@ -149,6 +150,9 @@ ${cleanTrace}`);
149
150
  lang: page.locale.lang
150
151
  }
151
152
  });
153
+ if (page.head) {
154
+ head.push(page.head);
155
+ }
152
156
  head.push(normalizeSeoToHead(page.head, fallbackTitle, fallbackDescription));
153
157
  app.use(head);
154
158
  installFlowVuePlugins(app);
@@ -168,7 +172,8 @@ export async function renderDocument(page, clientAssets) {
168
172
  title: fallbackTitle,
169
173
  locale: page.locale,
170
174
  mode,
171
- images: getFlowImageBootPayload()
175
+ images: getFlowImageBootPayload(),
176
+ breadcrumbs: page.breadcrumbs
172
177
  };
173
178
  const rendered = await renderBody(page);
174
179
  const body = stripVueFragmentMarkers(rendered.body);
@@ -198,5 +203,28 @@ export async function renderDocument(page, clientAssets) {
198
203
  ].join("\n "),
199
204
  body: pageMarkup
200
205
  });
201
- return transformHtmlTemplate(rendered.head, html);
206
+ let finalHtml = await transformHtmlTemplate(rendered.head, html);
207
+ const schemaOrgConfig = page.head.templateParams?.schemaOrg;
208
+ if (schemaOrgConfig?.inLanguage && schemaOrgConfig.inLanguage !== page.locale.lang) {
209
+ finalHtml = finalHtml.replace(/<script[^>]*data-hid="schema-org-graph"[^>]*>([\s\S]*?)<\/script>/, (match, content) => {
210
+ try {
211
+ const data = JSON.parse(content);
212
+ const replaceLang = (obj) => {
213
+ if (Array.isArray(obj)) {
214
+ obj.forEach(replaceLang);
215
+ } else if (typeof obj === "object" && obj !== null) {
216
+ if (obj.inLanguage === page.locale.lang) {
217
+ obj.inLanguage = schemaOrgConfig.inLanguage;
218
+ }
219
+ Object.values(obj).forEach(replaceLang);
220
+ }
221
+ };
222
+ replaceLang(data);
223
+ return match.replace(content, JSON.stringify(data));
224
+ } catch (e) {
225
+ return match;
226
+ }
227
+ });
228
+ }
229
+ return finalHtml;
202
230
  }
@@ -1,6 +1,6 @@
1
1
  export type { ContentDirectoryNode, ContentEntry, ContentFileNode, ContentTreeNode, } from '../../modules/content/query.ts';
2
2
  export type { FlowBootPayload } from '../runtime/boot.ts';
3
- export { useContext, useGlobal, useHead, useLocale, usePage, useSeo, useSharedContext, useUtils, useView, } from '../runtime/composables.ts';
3
+ export { useContext, useGlobal, useHead, useLocale, usePage, useSeo, useSharedContext, useUrl, useUtils, useView, useBreadcrumb, } from '../runtime/composables.ts';
4
4
  export { defineFlowConfig, defineFlowModule, resolveFlowConfig } from '../runtime/config.ts';
5
5
  export type { FlowConfig, UserFlowConfig, } from '../runtime/config.ts';
6
6
  export { useSchemaOrg } from '../runtime/head.ts';
@@ -6,8 +6,10 @@ export {
6
6
  usePage,
7
7
  useSeo,
8
8
  useSharedContext,
9
+ useUrl,
9
10
  useUtils,
10
- useView
11
+ useView,
12
+ useBreadcrumb
11
13
  } from "../runtime/composables.mjs";
12
14
  export { defineFlowConfig, defineFlowModule, resolveFlowConfig } from "../runtime/config.mjs";
13
15
  export { useSchemaOrg } from "../runtime/head.mjs";
@@ -343,6 +343,9 @@ export function createFlowViteConfig(options = {}) {
343
343
  const { imports: userImports, ...userAutoImport } = options.userFlowConfig?.autoImport || {};
344
344
  const uiOptions = options.userFlowConfig?.ui || {};
345
345
  return defineConfig({
346
+ define: {
347
+ "import.meta.env.FLOW_SITE_URL": JSON.stringify(flowConfig.siteUrl || "")
348
+ },
346
349
  plugins: [
347
350
  createFlowVirtualServerModules(projectRoot, flowConfig),
348
351
  createFlowVirtualClientPages(projectRoot),
@@ -382,9 +385,11 @@ export function createFlowViteConfig(options = {}) {
382
385
  "useUtils",
383
386
  "useSharedContext",
384
387
  "useGlobal",
388
+ "useUrl",
385
389
  "useLocale",
386
390
  "usePage",
387
- "useView"
391
+ "useView",
392
+ "useBreadcrumb"
388
393
  ],
389
394
  ...mappedNitroImports
390
395
  },
@@ -1,17 +0,0 @@
1
- import type { FlowImageMeta, FlowImageOptions } from '../../modules/images/runtime/types.ts';
2
- import type { FlowHydrationMode, FlowLocale } from './pages';
3
- export interface FlowImageBootPayload {
4
- all: Record<string, FlowImageMeta>;
5
- options: FlowImageOptions;
6
- generateOutput: boolean;
7
- strapiURL?: string;
8
- }
9
- export interface FlowBootPayload {
10
- path: string;
11
- bundle: string;
12
- template: string;
13
- title: string;
14
- locale: FlowLocale;
15
- mode: FlowHydrationMode;
16
- images?: FlowImageBootPayload;
17
- }
@@ -1,4 +1,4 @@
1
- import type { FlowLocale, PageContextUtils, PageDefinition } from './pages.ts';
1
+ import type { BreadcrumbItem, FlowLocale, PageContextUtils, PageDefinition } from './pages.ts';
2
2
  export declare function useContext<T = any>(): T;
3
3
  export declare function useSeo(): any;
4
4
  export declare function useHead<T = any>(): T;
@@ -8,3 +8,11 @@ export declare function useGlobal<T = any>(): T;
8
8
  export declare function useLocale(): FlowLocale;
9
9
  export declare function usePage(): PageDefinition;
10
10
  export declare function useView(): PageDefinition['view'];
11
+ export interface UrlInfo {
12
+ pathname: string;
13
+ siteUrl?: string;
14
+ href: string;
15
+ joinUrl: (...params: string[]) => string;
16
+ }
17
+ export declare function useUrl(): UrlInfo;
18
+ export declare function useBreadcrumb(): BreadcrumbItem[];
@@ -1,3 +1,4 @@
1
+ import { joinURL } from "ufo";
1
2
  import { inject } from "vue";
2
3
  export function useContext() {
3
4
  const ctx = inject("context", {});
@@ -37,3 +38,24 @@ export function useView() {
37
38
  const ctx = inject("context", {});
38
39
  return { ...ctx.page?.view || {} };
39
40
  }
41
+ export function useUrl() {
42
+ const ctx = inject("context", {});
43
+ const pathname = ctx.path || "/";
44
+ const siteUrl = import.meta.env.FLOW_SITE_URL || void 0;
45
+ let href = pathname || "/";
46
+ if (siteUrl) {
47
+ const base = siteUrl.endsWith("/") ? siteUrl.slice(0, -1) : siteUrl;
48
+ const path = href.startsWith("/") ? href : `/${href}`;
49
+ href = base + path;
50
+ }
51
+ return {
52
+ pathname,
53
+ siteUrl,
54
+ href,
55
+ joinUrl: (...params) => joinURL(siteUrl || "", ...params)
56
+ };
57
+ }
58
+ export function useBreadcrumb() {
59
+ const breadcrumbs = inject("breadcrumbs", []);
60
+ return breadcrumbs;
61
+ }
@@ -61,6 +61,7 @@ async function hydrateIsland(element, boot) {
61
61
  const imageUtils = createBootImageUtils(boot);
62
62
  app.use(getClientHead());
63
63
  app.provide("utils", imageUtils);
64
+ app.provide("breadcrumbs", boot?.breadcrumbs || []);
64
65
  installFlowVuePlugins(app);
65
66
  app.mount(element);
66
67
  element.dataset.flowIslandHydrated = "true";
@@ -99,6 +99,7 @@ export interface PageContextUtils {
99
99
  export interface PageContextInput {
100
100
  locale: FlowLocale;
101
101
  path: string;
102
+ fullPath?: string;
102
103
  name: string;
103
104
  view: PageViewDefinition;
104
105
  page: PageLocaleDefinition;
@@ -107,6 +108,27 @@ export interface PageContextInput {
107
108
  utils: PageContextUtils;
108
109
  preview?: boolean;
109
110
  }
111
+ export interface BreadcrumbItem {
112
+ title: string;
113
+ caption?: string;
114
+ url?: string;
115
+ }
116
+ export interface BreadcrumbDefinition {
117
+ parent?: string | {
118
+ name: string;
119
+ params: Record<string, string>;
120
+ };
121
+ item?: BreadcrumbItem;
122
+ items?: BreadcrumbItem[];
123
+ }
124
+ export interface BreadcrumbContextInput extends PageContextInput {
125
+ context: Record<string, unknown>;
126
+ head: HeadDefinition;
127
+ /**
128
+ * @deprecated Use head.
129
+ */
130
+ seo: HeadDefinition;
131
+ }
110
132
  export interface PageLocaleDefinition {
111
133
  url: string;
112
134
  head?: (ctx: PageContextInput) => MaybePromise<HeadDefinition>;
@@ -115,6 +137,7 @@ export interface PageLocaleDefinition {
115
137
  */
116
138
  seo?: (ctx: PageContextInput) => MaybePromise<HeadDefinition>;
117
139
  context?: (ctx: PageContextInput) => MaybePromise<Record<string, unknown>>;
140
+ breadcrumb?: (ctx: BreadcrumbContextInput) => MaybePromise<BreadcrumbDefinition>;
118
141
  dynamic?: DynamicRouteOptions;
119
142
  }
120
143
  export interface PageDefinition {
@@ -123,4 +123,4 @@ declare module 'virtual:flow/bases' {
123
123
  const bases: Record<string, string>;
124
124
 
125
125
  export default bases;
126
- }
126
+ }