@lupinum/ginko-content 0.1.4 → 0.1.6

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.
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "Ginko Content release compatibility",
3
3
  "releaseStack": {
4
- "@lupinum/ginko-content": "0.1.4",
4
+ "@lupinum/ginko-content": "0.1.6",
5
5
  "@lupinum/ginko-cms": "0.1.2",
6
6
  "@lupinum/ginko-cms-convex": "0.1.1",
7
7
  "@lupinum/ginko-cms-contract": "0.1.1"
@@ -16,7 +16,7 @@
16
16
  * - **`byRoute`**: route path (user-visible URL) → content id. Used by the
17
17
  * page resolver when the request arrives as a localized URL.
18
18
  * - **`byRef`**: normalized ref string → canonical key. This is how
19
- * `[link](ref:authors/evan)` finds its target without a scan.
19
+ * markdown links such as `[Ada]($authors.ada)` find their target without a scan.
20
20
  * - **`referenceTargets`**: the `buildReferenceTargets` map — every shape
21
21
  * the user might plausibly write to point at a document (canonical
22
22
  * name, locale-prefixed path, short slug) pre-resolved to a canonical
@@ -28,4 +28,23 @@ export declare const parseRefLink: (value: string) => {
28
28
  } | null;
29
29
  export declare const collectMarkdownRefLinks: (node: unknown) => string[];
30
30
  export declare const rewriteMarkdownRefLinks: <T>(node: T, resolvedRefs?: Record<string, string>) => T;
31
+ export interface MarkdownQuickLinkTarget {
32
+ route: string;
33
+ params?: Record<string, string | number>;
34
+ query?: Record<string, string | number | boolean | undefined>;
35
+ }
36
+ export type MarkdownQuickLinks = Record<string, Record<string, MarkdownQuickLinkTarget>>;
37
+ export type ResolveQuickLinkRoute = (route: {
38
+ name: string;
39
+ hash?: string;
40
+ params?: Record<string, string | number>;
41
+ query?: Record<string, string | number | boolean | undefined>;
42
+ }) => string;
43
+ export declare const resolveConfiguredQuickLink: (href: string, links: MarkdownQuickLinks | undefined, resolveRoute: ResolveQuickLinkRoute) => string | undefined;
44
+ export declare const resolveConfiguredQuickLinks: (hrefs: string[], links: MarkdownQuickLinks | undefined, resolveRoute: ResolveQuickLinkRoute) => {
45
+ [k: string]: string;
46
+ };
47
+ export declare const resolveMarkdownRenderRefs: (node: unknown, resolvedRefs: Record<string, string> | undefined, links: MarkdownQuickLinks | undefined, resolveRoute: ResolveQuickLinkRoute) => {
48
+ [x: string]: string;
49
+ };
31
50
  export declare const buildReferenceTargets: (contents: ParsedContent[], locales?: string[]) => Map<string, string>;
@@ -1,5 +1,6 @@
1
1
  import { isMarkdownRoot, mapMarkdownNode } from "../markdown/tree.js";
2
2
  export const CONTENT_REF_LINK_PREFIX = "$";
3
+ const MARKDOWN_LINK_PROP_KEYS = ["href", "to"];
3
4
  export const normalizeReferenceValue = (value) => String(value).replace(/^\/+|\/+$/g, "");
4
5
  export const parseRefLink = (value) => {
5
6
  if (typeof value !== "string" || !value.startsWith(CONTENT_REF_LINK_PREFIX)) {
@@ -17,6 +18,17 @@ export const parseRefLink = (value) => {
17
18
  };
18
19
  export const collectMarkdownRefLinks = (node) => {
19
20
  const refs = /* @__PURE__ */ new Set();
21
+ const collectProps = (props) => {
22
+ if (!props || typeof props !== "object") {
23
+ return;
24
+ }
25
+ for (const key of MARKDOWN_LINK_PROP_KEYS) {
26
+ const value = props[key];
27
+ if (typeof value === "string" && parseRefLink(value)) {
28
+ refs.add(value);
29
+ }
30
+ }
31
+ };
20
32
  const visit = (current) => {
21
33
  if (!current || typeof current !== "object") {
22
34
  return;
@@ -27,10 +39,7 @@ export const collectMarkdownRefLinks = (node) => {
27
39
  }
28
40
  if (current.type === "element") {
29
41
  const markdownNode = current;
30
- const href2 = markdownNode.tag === "a" && typeof markdownNode.props?.href === "string" ? markdownNode.props.href : void 0;
31
- if (href2 && parseRefLink(href2)) {
32
- refs.add(href2);
33
- }
42
+ collectProps(markdownNode.props);
34
43
  markdownNode.children?.forEach(visit);
35
44
  return;
36
45
  }
@@ -38,16 +47,27 @@ export const collectMarkdownRefLinks = (node) => {
38
47
  current.forEach(visit);
39
48
  return;
40
49
  }
41
- const href = typeof current.props?.href === "string" && current.tag === "a" ? current.props.href : void 0;
42
- if (href && parseRefLink(href)) {
43
- refs.add(href);
44
- }
50
+ collectProps(current.props);
45
51
  visit(current.children);
46
52
  };
47
53
  visit(node);
48
54
  return Array.from(refs);
49
55
  };
50
56
  export const rewriteMarkdownRefLinks = (node, resolvedRefs = {}) => {
57
+ const rewriteProps = (props) => {
58
+ if (!props || typeof props !== "object") {
59
+ return void 0;
60
+ }
61
+ let next;
62
+ for (const key of MARKDOWN_LINK_PROP_KEYS) {
63
+ const value = props[key];
64
+ if (typeof value === "string" && resolvedRefs[value]) {
65
+ next ||= { ...props };
66
+ next[key] = resolvedRefs[value];
67
+ }
68
+ }
69
+ return next || props;
70
+ };
51
71
  const visit = (current) => {
52
72
  if (!current || typeof current !== "object") {
53
73
  return current;
@@ -60,16 +80,13 @@ export const rewriteMarkdownRefLinks = (node, resolvedRefs = {}) => {
60
80
  }
61
81
  if (current.type === "element") {
62
82
  return mapMarkdownNode(current, (markdownNode) => {
63
- const href2 = typeof markdownNode.props?.href === "string" && markdownNode.tag === "a" ? markdownNode.props.href : void 0;
64
- if (!href2 || !resolvedRefs[href2]) {
83
+ const props2 = rewriteProps(markdownNode.props);
84
+ if (props2 === markdownNode.props) {
65
85
  return markdownNode;
66
86
  }
67
87
  return {
68
88
  ...markdownNode,
69
- props: {
70
- ...markdownNode.props,
71
- href: resolvedRefs[href2]
72
- }
89
+ props: props2
73
90
  };
74
91
  });
75
92
  }
@@ -77,12 +94,9 @@ export const rewriteMarkdownRefLinks = (node, resolvedRefs = {}) => {
77
94
  return current.map((item) => visit(item));
78
95
  }
79
96
  const next = { ...current };
80
- const href = typeof next.props?.href === "string" && next.tag === "a" ? next.props.href : void 0;
81
- if (href && resolvedRefs[href]) {
82
- next.props = {
83
- ...next.props,
84
- href: resolvedRefs[href]
85
- };
97
+ const props = rewriteProps(next.props);
98
+ if (props !== next.props) {
99
+ next.props = props;
86
100
  }
87
101
  if (Array.isArray(next.children)) {
88
102
  next.children = next.children.map((child) => visit(child));
@@ -91,6 +105,44 @@ export const rewriteMarkdownRefLinks = (node, resolvedRefs = {}) => {
91
105
  };
92
106
  return visit(node);
93
107
  };
108
+ export const resolveConfiguredQuickLink = (href, links, resolveRoute) => {
109
+ const parsed = parseRefLink(href);
110
+ if (!parsed || !links) {
111
+ return void 0;
112
+ }
113
+ const separator = parsed.ref.indexOf(".");
114
+ if (separator <= 0 || separator === parsed.ref.length - 1) {
115
+ return void 0;
116
+ }
117
+ const namespace = parsed.ref.slice(0, separator);
118
+ const key = parsed.ref.slice(separator + 1);
119
+ const target = links[namespace]?.[key];
120
+ if (!target?.route) {
121
+ return void 0;
122
+ }
123
+ return resolveRoute({
124
+ name: target.route,
125
+ ...target.params ? { params: target.params } : {},
126
+ ...target.query ? { query: target.query } : {},
127
+ ...parsed.hash ? { hash: parsed.hash } : {}
128
+ });
129
+ };
130
+ export const resolveConfiguredQuickLinks = (hrefs, links, resolveRoute) => {
131
+ return Object.fromEntries(hrefs.flatMap((href) => {
132
+ const resolved = resolveConfiguredQuickLink(href, links, resolveRoute);
133
+ return resolved ? [[href, resolved]] : [];
134
+ }));
135
+ };
136
+ export const resolveMarkdownRenderRefs = (node, resolvedRefs, links, resolveRoute) => {
137
+ const quickRefs = resolveConfiguredQuickLinks(collectMarkdownRefLinks(node), links, resolveRoute);
138
+ const concreteContentRefs = Object.fromEntries(
139
+ Object.entries(resolvedRefs || {}).filter(([href, value]) => value && value !== href)
140
+ );
141
+ return {
142
+ ...quickRefs,
143
+ ...concreteContentRefs
144
+ };
145
+ };
94
146
  export const buildReferenceTargets = (contents, locales = []) => {
95
147
  const targets = /* @__PURE__ */ new Map();
96
148
  for (const document of contents) {
@@ -1,6 +1,5 @@
1
1
  import { prefixStorage } from "unstorage";
2
2
  import { useStorage } from "nitropack/runtime";
3
- import virtualContentConfig from "#content/virtual/config";
4
3
  import { makeIgnored } from "../../core/content/ignore";
5
4
  import { getContentRuntimeContext } from "./context.js";
6
5
  import { getPreview, isPreview } from "./preview.js";
@@ -34,34 +33,8 @@ export const cacheParsedStorage = (event) => {
34
33
  }
35
34
  return createScopedStorage("cache:content:parsed");
36
35
  };
37
- const mergeCollectionConfigs = (runtimeCollections = {}, sourceCollections = {}) => {
38
- const names = /* @__PURE__ */ new Set([
39
- ...Object.keys(sourceCollections),
40
- ...Object.keys(runtimeCollections)
41
- ]);
42
- return Object.fromEntries(Array.from(names).map((name) => {
43
- const source = sourceCollections[name] || {};
44
- const runtime = runtimeCollections[name] || {};
45
- const sourceProviders = source.providers;
46
- return [
47
- name,
48
- {
49
- ...source,
50
- ...runtime,
51
- ...source.schema ? { schema: source.schema } : {},
52
- ...sourceProviders ? { providers: sourceProviders } : {}
53
- }
54
- ];
55
- }));
56
- };
57
36
  export const contentConfig = () => {
58
- const runtimeContent = getContentRuntimeConfig().content;
59
- const sourceContent = virtualContentConfig || {};
60
- return {
61
- ...runtimeContent,
62
- ...sourceContent.agent ? { agent: sourceContent.agent } : {},
63
- collections: mergeCollectionConfigs(runtimeContent.collections, sourceContent.collections)
64
- };
37
+ return getContentRuntimeConfig().content;
65
38
  };
66
39
  export const contentIgnorePredicate = (key) => {
67
40
  const isIgnored = makeIgnored(contentConfig().ignores);
package/dist/module.d.mts CHANGED
@@ -132,6 +132,15 @@ interface ContentAgentConfig {
132
132
  sections?: ContentAgentSectionConfig[];
133
133
  pages?: ContentAgentAppPageConfig[];
134
134
  }
135
+ interface ContentAgentRuntimeAppPageConfig extends Omit<ContentAgentAppPageConfig, 'title' | 'description' | 'render'> {
136
+ title: ContentAgentLocalizedValue;
137
+ description: ContentAgentLocalizedValue;
138
+ markdown: ContentAgentLocalizedValue;
139
+ render?: ContentAgentAppPageConfig['render'];
140
+ }
141
+ interface ContentAgentRuntimeConfig extends Omit<ContentAgentConfig, 'pages'> {
142
+ pages?: ContentAgentRuntimeAppPageConfig[];
143
+ }
135
144
  /**
136
145
  * Object source shape accepted for Nuxt Content v3 migration.
137
146
  */
@@ -1405,6 +1414,21 @@ interface ContentRevalidateOptions {
1405
1414
  */
1406
1415
  allowUnsigned?: boolean;
1407
1416
  }
1417
+ interface ContentLinkRouteTarget {
1418
+ /**
1419
+ * Nuxt route name passed to Nuxt I18n `localePath()`.
1420
+ */
1421
+ route: string;
1422
+ /**
1423
+ * Static route params forwarded to Nuxt I18n `localePath()`.
1424
+ */
1425
+ params?: Record<string, string | number>;
1426
+ /**
1427
+ * Static query params forwarded to Nuxt I18n `localePath()`.
1428
+ */
1429
+ query?: Record<string, string | number | boolean | undefined>;
1430
+ }
1431
+ type ContentLinksOptions = Record<string, Record<string, ContentLinkRouteTarget>>;
1408
1432
  interface ContentAgentRouteOptions {
1409
1433
  routes?: boolean;
1410
1434
  linkHeaders?: boolean;
@@ -1454,6 +1478,15 @@ interface ModuleOptions {
1454
1478
  * `x-ginko-revalidate-token` or `authorization: Bearer <token>`.
1455
1479
  */
1456
1480
  revalidate?: false | ContentRevalidateOptions;
1481
+ /**
1482
+ * Writer-facing markdown quick links. Values point at Nuxt route names;
1483
+ * localized paths stay owned by Nuxt I18n.
1484
+ *
1485
+ * @example
1486
+ * content.links.main.pricing = { route: 'pricing' }
1487
+ * markdown: [Pricing]($main.pricing)
1488
+ */
1489
+ links?: ContentLinksOptions;
1457
1490
  /**
1458
1491
  * First-class agent markdown and LLM route features.
1459
1492
  */
@@ -1673,6 +1706,7 @@ interface ModulePublicRuntimeConfig {
1673
1706
  locales: ContentContext$1['locales'];
1674
1707
  provider: ContentContext$1['provider'];
1675
1708
  providers: ContentContext$1['providers'];
1709
+ links: ContentContext$1['links'];
1676
1710
  collections: Record<string, {
1677
1711
  source: ContentCollectionConfig$1['source'];
1678
1712
  exclude?: ContentCollectionConfig$1['exclude'];
@@ -1726,4 +1760,4 @@ interface ModuleHooks {
1726
1760
  }
1727
1761
 
1728
1762
  export { _default as default, normalizeContentConfigCollectionNames };
1729
- export type { AgentMetadataField, AgentMetadataFieldList, BacklinkFields, BacklinkSource, BacklinksOptions, BacklinksResult, CollectionQueryBuilder, CollectionQueryField, CollectionQueryFieldValue, CollectionQueryKey, CollectionQueryOperator, CollectionQueryValue, CollectionSchema, ContentAgentAppPageConfig, ContentAgentAppPageContext, ContentAgentCollectionConfig, ContentAgentConfig, ContentAgentLocalizedValue, ContentAgentMarkdownMetadataConfig, ContentAgentMarkdownOptions, ContentAgentMarkdownPolicyConfig, ContentAgentRouteOptions, ContentAgentSectionConfig, ContentAgentSiteConfig, ContentCacheArtifact, ContentCmsCollectionConfig, ContentCmsFieldConfig, ContentCmsFieldType, ContentCmsRelationConfig, ContentCollectionConfig, ContentCollectionHandle, ContentCollectionI18nConfig, ContentCollectionI18nMap, ContentCollectionItem, ContentCollectionItemSurroundingsOptions, ContentCollectionKind, ContentCollectionMap, ContentCollectionName, ContentCollectionNavigationOptions, ContentCollectionPageOptions, ContentCollectionRouteConfig, ContentCollectionRouteMetaOptions, ContentCollectionSearchSectionsOptions, ContentCollectionSource, ContentCollectionSourceObject, ContentCollectionStringName, ContentCollectionTarget, ContentConfig, ContentContext, ContentI18nOptions, ContentLocaleEntry, ContentLocaleRoute, ContentManifest, ContentMiniSearchOptions, ContentNavigationItem, ContentPageResult, ContentPreviewOptions, ContentProviderName, ContentProviderSearchRequest, ContentQueryBuilder, ContentQueryBuilderParams, ContentQueryBuilderWhere, ContentQueryFetcher, ContentQueryRequest, ContentQuerySortFields, ContentQuerySortOptions, ContentQuerySortParams, ContentReferenceSchema, ContentResolvedMeta, ContentRevalidateOptions, ContentRouteMeta, ContentSearchEngine, ContentSearchIndexRecord, ContentSearchOptions, ContentSearchPublicRuntimeConfig, ContentSearchResult, ContentSearchSection, ContentSelector, ContentSeoImage, ContentSeoMeta, ContentSitemapAlternative, ContentSitemapAssertOptions, ContentSitemapAssertSitemapOptions, ContentSitemapEntry, ContentSitemapImage, ContentSitemapOptions, ContentTransformer, ContentTreeItem, ContentVariant, DefineCollectionObject, DefineCollectionOptions, DocumentFromHandle, LocaleFallback, LocalePathEntry, LocalizedContentDocument, LocalizedDoc, ManifestVariant, ManyOptions, MarkdownNode, MarkdownOptions, MarkdownParsedContent, MarkdownPluginDescriptor, MarkdownPluginOptions, MarkdownRoot, ModuleHooks, ModuleOptions, MountOptions, NavItem, NeighborsOptions, NeighborsResult, OneOptions, PaginationOptions, PaginationResult, ParseContentOptions, ParsedContent, ParsedContentInternalMeta, ParsedContentMeta, PopulateFromOptions, PopulateSpec, PopulatedDocument, QueryGroupBuilder, QueryGroupFunction, QueryMatchOperator, QueryOperators, QueryOrderDirection, QueryOrderOptions, QueryWhere, ResolutionEnvelope, ResolveContentReferenceOptions, ResolveOneOptions, ResolveOneResult, ResolvedContentContext, ResolvedContentI18nOptions, ResolvedMarkdownPlugin, ResolvedVariant, SortDirection, SortSpec, StrictParsedContent, StrictParsedContentMeta, Toc, TocLink, TransformContentOptions, TransformContentSource, TreeOptions, VariantsOptions };
1763
+ export type { AgentMetadataField, AgentMetadataFieldList, BacklinkFields, BacklinkSource, BacklinksOptions, BacklinksResult, CollectionQueryBuilder, CollectionQueryField, CollectionQueryFieldValue, CollectionQueryKey, CollectionQueryOperator, CollectionQueryValue, CollectionSchema, ContentAgentAppPageConfig, ContentAgentAppPageContext, ContentAgentCollectionConfig, ContentAgentConfig, ContentAgentLocalizedValue, ContentAgentMarkdownMetadataConfig, ContentAgentMarkdownOptions, ContentAgentMarkdownPolicyConfig, ContentAgentRouteOptions, ContentAgentRuntimeAppPageConfig, ContentAgentRuntimeConfig, ContentAgentSectionConfig, ContentAgentSiteConfig, ContentCacheArtifact, ContentCmsCollectionConfig, ContentCmsFieldConfig, ContentCmsFieldType, ContentCmsRelationConfig, ContentCollectionConfig, ContentCollectionHandle, ContentCollectionI18nConfig, ContentCollectionI18nMap, ContentCollectionItem, ContentCollectionItemSurroundingsOptions, ContentCollectionKind, ContentCollectionMap, ContentCollectionName, ContentCollectionNavigationOptions, ContentCollectionPageOptions, ContentCollectionRouteConfig, ContentCollectionRouteMetaOptions, ContentCollectionSearchSectionsOptions, ContentCollectionSource, ContentCollectionSourceObject, ContentCollectionStringName, ContentCollectionTarget, ContentConfig, ContentContext, ContentI18nOptions, ContentLinkRouteTarget, ContentLinksOptions, ContentLocaleEntry, ContentLocaleRoute, ContentManifest, ContentMiniSearchOptions, ContentNavigationItem, ContentPageResult, ContentPreviewOptions, ContentProviderName, ContentProviderSearchRequest, ContentQueryBuilder, ContentQueryBuilderParams, ContentQueryBuilderWhere, ContentQueryFetcher, ContentQueryRequest, ContentQuerySortFields, ContentQuerySortOptions, ContentQuerySortParams, ContentReferenceSchema, ContentResolvedMeta, ContentRevalidateOptions, ContentRouteMeta, ContentSearchEngine, ContentSearchIndexRecord, ContentSearchOptions, ContentSearchPublicRuntimeConfig, ContentSearchResult, ContentSearchSection, ContentSelector, ContentSeoImage, ContentSeoMeta, ContentSitemapAlternative, ContentSitemapAssertOptions, ContentSitemapAssertSitemapOptions, ContentSitemapEntry, ContentSitemapImage, ContentSitemapOptions, ContentTransformer, ContentTreeItem, ContentVariant, DefineCollectionObject, DefineCollectionOptions, DocumentFromHandle, LocaleFallback, LocalePathEntry, LocalizedContentDocument, LocalizedDoc, ManifestVariant, ManyOptions, MarkdownNode, MarkdownOptions, MarkdownParsedContent, MarkdownPluginDescriptor, MarkdownPluginOptions, MarkdownRoot, ModuleHooks, ModuleOptions, MountOptions, NavItem, NeighborsOptions, NeighborsResult, OneOptions, PaginationOptions, PaginationResult, ParseContentOptions, ParsedContent, ParsedContentInternalMeta, ParsedContentMeta, PopulateFromOptions, PopulateSpec, PopulatedDocument, QueryGroupBuilder, QueryGroupFunction, QueryMatchOperator, QueryOperators, QueryOrderDirection, QueryOrderOptions, QueryWhere, ResolutionEnvelope, ResolveContentReferenceOptions, ResolveOneOptions, ResolveOneResult, ResolvedContentContext, ResolvedContentI18nOptions, ResolvedMarkdownPlugin, ResolvedVariant, SortDirection, SortSpec, StrictParsedContent, StrictParsedContentMeta, Toc, TocLink, TransformContentOptions, TransformContentSource, TreeOptions, VariantsOptions };
package/dist/module.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@lupinum/ginko-content",
3
- "version": "0.1.4",
3
+ "version": "0.1.6",
4
4
  "configKey": "content",
5
5
  "compatibility": {
6
6
  "nuxt": ">=4.0.0"
package/dist/module.mjs CHANGED
@@ -30,7 +30,7 @@ export { agentMetadataFields, defineAgentAppPage, defineAgentMarkdownPolicy, def
30
30
  import { collectTopLevelReferenceFieldsByTarget } from '../dist/core/references/schema';
31
31
 
32
32
  const name = "@lupinum/ginko-content";
33
- const version = "0.1.4";
33
+ const version = "0.1.6";
34
34
 
35
35
  const CONFIG_FILES = [
36
36
  "content.config.ts",
@@ -52,7 +52,7 @@ async function loadContentConfig(nuxt) {
52
52
  return loaded?.default || loaded || {};
53
53
  }
54
54
 
55
- const createVirtualContentTemplates = (contentContext, nuxt, contentConfigPath, addTemplateImpl) => {
55
+ const createVirtualContentTemplates = (contentContext, nuxt, _contentConfigPath, addTemplateImpl) => {
56
56
  const transformersTemplate = addTemplateImpl({
57
57
  filename: "content/virtual-transformers.mjs",
58
58
  write: true,
@@ -73,10 +73,7 @@ const createVirtualContentTemplates = (contentContext, nuxt, contentConfigPath,
73
73
  const virtualConfigTemplate = addTemplateImpl({
74
74
  filename: "content/virtual-config.mjs",
75
75
  write: true,
76
- getContents: () => contentConfigPath ? [
77
- `import config from ${JSON.stringify(contentConfigPath)}`,
78
- "export default config || {}"
79
- ].join("\n") : "export default {}"
76
+ getContents: () => "export default {}"
80
77
  }).dst;
81
78
  const virtualProvidersTemplate = addTemplateImpl({
82
79
  filename: "content/virtual-providers.mjs",
@@ -302,8 +299,9 @@ const registerContentI18nTemplate = (addTemplateImpl, hasNuxtI18nModule) => {
302
299
  filename: "content-i18n.mjs",
303
300
  write: true,
304
301
  getContents: () => hasNuxtI18nModule ? [
305
- "export { useRouteBaseName, useSetI18nParams, useSwitchLocalePath } from '#i18n'"
302
+ "export { useLocalePath, useRouteBaseName, useSetI18nParams, useSwitchLocalePath } from '#i18n'"
306
303
  ].join("\n") : [
304
+ "import { useRouter } from '#imports'",
307
305
  "const routeNameLocaleRE = /___([^_]+)$/",
308
306
  "const resolveName = (value) => {",
309
307
  " if (typeof value === 'string') {",
@@ -314,6 +312,24 @@ const registerContentI18nTemplate = (addTemplateImpl, hasNuxtI18nModule) => {
314
312
  " }",
315
313
  " return undefined",
316
314
  "}",
315
+ "const normalizeRoutePath = (value) => value.startsWith('/') ? value : `/${value}`",
316
+ "const definedRecord = value => value && typeof value === 'object'",
317
+ " ? Object.fromEntries(Object.entries(value).filter(([, item]) => item !== undefined))",
318
+ " : undefined",
319
+ "export const useLocalePath = () => {",
320
+ " const router = useRouter()",
321
+ " return (value) => {",
322
+ " if (typeof value === 'string') return normalizeRoutePath(value)",
323
+ " const name = resolveName(value)",
324
+ " if (!name) return ''",
325
+ " return router.resolve({",
326
+ " name,",
327
+ " ...(definedRecord(value.params) ? { params: definedRecord(value.params) } : {}),",
328
+ " ...(definedRecord(value.query) ? { query: definedRecord(value.query) } : {}),",
329
+ " ...(typeof value.hash === 'string' ? { hash: value.hash } : {})",
330
+ " }).href",
331
+ " }",
332
+ "}",
317
333
  "export const useRouteBaseName = () => (value) => {",
318
334
  " const name = resolveName(value)",
319
335
  " return typeof name === 'string' ? name.replace(routeNameLocaleRE, '') : undefined",
@@ -326,6 +342,7 @@ const registerContentI18nTemplate = (addTemplateImpl, hasNuxtI18nModule) => {
326
342
  filename: "content-i18n.d.mts",
327
343
  write: true,
328
344
  getContents: () => [
345
+ "export function useLocalePath(): (route: string | { name?: string, hash?: string, params?: Record<string, unknown>, query?: Record<string, unknown> }, locale?: string) => string",
329
346
  "export function useRouteBaseName(): (route: { name?: unknown } | unknown) => string | undefined",
330
347
  "export function useSetI18nParams(): (params: Record<string, unknown>) => void",
331
348
  "export function useSwitchLocalePath(): (locale: string) => string"
@@ -335,6 +352,7 @@ const registerContentI18nTemplate = (addTemplateImpl, hasNuxtI18nModule) => {
335
352
  filename: "types/content-i18n.d.ts",
336
353
  getContents: () => [
337
354
  "declare module '#build/content-i18n.mjs' {",
355
+ " export function useLocalePath(): (route: string | { name?: string, hash?: string, params?: Record<string, unknown>, query?: Record<string, unknown> }, locale?: string) => string",
338
356
  " export function useRouteBaseName(): (route: { name?: unknown } | unknown) => string | undefined",
339
357
  " export function useSetI18nParams(): (params: Record<string, unknown>) => void",
340
358
  " export function useSwitchLocalePath(): (locale: string) => string",
@@ -1478,15 +1496,6 @@ const registerContentNitroConfig = ({
1478
1496
  nitroConfig.prerender.routes.push(`/${locale}/llms.txt`, `/${locale}/llms-full.txt`);
1479
1497
  }
1480
1498
  }
1481
- for (const page of appContentConfig.agent.pages || []) {
1482
- const routes = typeof page.route === "string" ? [page.route] : Object.values(page.route);
1483
- for (const route of routes) {
1484
- const normalized = route === "/" ? "/" : `/${route.replace(/^\/+|\/+$/g, "")}`;
1485
- nitroConfig.prerender.routes.push(
1486
- normalized === "/" ? "/raw/index.md" : `/raw${normalized}.md`
1487
- );
1488
- }
1489
- }
1490
1499
  }
1491
1500
  });
1492
1501
  };
@@ -1557,6 +1566,7 @@ const contentModuleDefaults = {
1557
1566
  watch: true,
1558
1567
  sources: {},
1559
1568
  ignores: [],
1569
+ links: {},
1560
1570
  collections: {},
1561
1571
  markdown: {
1562
1572
  plugins: [],
@@ -1642,7 +1652,55 @@ const sanitizePrivateMarkdownPlugins = (plugins) => plugins.map((plugin) => ({
1642
1652
  name: plugin.name,
1643
1653
  options: sanitizePrivateMarkdownPluginValue(plugin.options || {})
1644
1654
  }));
1645
- const applyContentRuntimeConfig = (nuxt, options, contentContext, runtimeCollections, buildIntegrity, cacheIntegrity) => {
1655
+ const localizedValue = (value, locale, fallback = "") => {
1656
+ if (typeof value === "string") return value;
1657
+ if (!value) return fallback;
1658
+ return value[locale] || Object.values(value)[0] || fallback;
1659
+ };
1660
+ const resolveAgentAppPageValue = async (value, ctx) => {
1661
+ if (typeof value === "function") {
1662
+ return await value(ctx);
1663
+ }
1664
+ return localizedValue(value, ctx.locale);
1665
+ };
1666
+ const sanitizeAgentConfig = async (agent, contentContext, siteUrl) => {
1667
+ if (!agent) {
1668
+ return void 0;
1669
+ }
1670
+ const defaultLocale = agent.site?.defaultLocale || contentContext.defaultLocale || contentContext.locales?.[0] || "en";
1671
+ const locales = agent.site?.locales?.length ? agent.site.locales : contentContext.locales?.length ? contentContext.locales : [defaultLocale];
1672
+ const agentSiteUrl = siteUrl || agent.site?.url || "http://localhost:3000";
1673
+ const pages = await Promise.all((agent.pages || []).map(async (page) => {
1674
+ const title = {};
1675
+ const description = {};
1676
+ const markdown = {};
1677
+ for (const locale of locales) {
1678
+ const ctx = { locale, defaultLocale, siteUrl: agentSiteUrl };
1679
+ title[locale] = await resolveAgentAppPageValue(page.title, ctx);
1680
+ description[locale] = await resolveAgentAppPageValue(page.description, ctx);
1681
+ markdown[locale] = await page.render(ctx);
1682
+ }
1683
+ return {
1684
+ id: page.id,
1685
+ route: page.route,
1686
+ section: page.section,
1687
+ title,
1688
+ description,
1689
+ updated: page.updated,
1690
+ includeInIndex: page.includeInIndex,
1691
+ includeInFull: page.includeInFull,
1692
+ metadata: page.metadata,
1693
+ markdown
1694
+ };
1695
+ }));
1696
+ return {
1697
+ ...agent.site ? { site: agent.site } : {},
1698
+ ...agent.markdown ? { markdown: agent.markdown } : {},
1699
+ ...agent.sections ? { sections: agent.sections } : {},
1700
+ ...pages.length ? { pages } : {}
1701
+ };
1702
+ };
1703
+ const applyContentRuntimeConfig = async (nuxt, options, contentContext, appContentConfig, runtimeCollections, privateRuntimeCollections, buildIntegrity, cacheIntegrity) => {
1646
1704
  const revalidate = options.revalidate === false ? void 0 : options.revalidate;
1647
1705
  const searchRuntime = contentContext.search === false ? false : {
1648
1706
  apiBaseURL: contentContext.search.apiBaseURL || `${options.api.baseURL.replace(/\/$/, "")}/search`,
@@ -1661,6 +1719,7 @@ const applyContentRuntimeConfig = (nuxt, options, contentContext, runtimeCollect
1661
1719
  translatedSlugs: contentContext.translatedSlugs ?? false,
1662
1720
  strictTranslatedSlugs: contentContext.strictTranslatedSlugs ?? false,
1663
1721
  collections: runtimeCollections,
1722
+ links: contentContext.links || {},
1664
1723
  integrity: buildIntegrity,
1665
1724
  experimental: {
1666
1725
  stripQueryParameters: options.experimental.stripQueryParameters
@@ -1685,8 +1744,10 @@ const applyContentRuntimeConfig = (nuxt, options, contentContext, runtimeCollect
1685
1744
  navigation: contentContext.navigation,
1686
1745
  contentHead: options.contentHead ?? true
1687
1746
  });
1747
+ const runtimeAgent = await sanitizeAgentConfig(appContentConfig.agent, contentContext, siteUrl);
1688
1748
  const privateContentRuntime = {
1689
1749
  ...contentContext,
1750
+ ...runtimeAgent ? { agent: runtimeAgent } : {},
1690
1751
  markdown: {
1691
1752
  ...contentContext.markdown,
1692
1753
  plugins: sanitizePrivateMarkdownPlugins(contentContext.markdown.plugins)
@@ -1701,13 +1762,14 @@ const applyContentRuntimeConfig = (nuxt, options, contentContext, runtimeCollect
1701
1762
  allowUnsigned: revalidate.allowUnsigned === true
1702
1763
  } : false,
1703
1764
  ...privateContentRuntime,
1704
- collections: runtimeCollections
1765
+ collections: privateRuntimeCollections
1705
1766
  });
1706
1767
  };
1707
1768
 
1708
1769
  const registerContentContextFinalization = ({
1709
1770
  nuxt,
1710
1771
  options,
1772
+ appContentConfig,
1711
1773
  contentContext,
1712
1774
  buildIntegrity,
1713
1775
  resolvePath,
@@ -1728,22 +1790,31 @@ const registerContentContextFinalization = ({
1728
1790
  };
1729
1791
  onResolved(resolvedContentContext);
1730
1792
  await validateBuiltinMarkdownPlugins(resolvedContentContext.markdown.plugins, resolvePath);
1731
- const runtimeCollections = Object.fromEntries(Object.entries(options.collections || {}).map(([name, collection]) => {
1793
+ const collectionEntries = Object.entries(options.collections || {}).map(([name, collection]) => {
1732
1794
  const references = collectTopLevelReferenceFieldsByTarget(collection.schema);
1733
- return [
1734
- name,
1735
- {
1736
- ...collection.source ? { source: collection.source } : {},
1737
- ...collection.exclude ? { exclude: collection.exclude } : {},
1738
- ...collection.type ? { type: collection.type } : {},
1739
- strict: collection.strict ?? true,
1740
- ...collection.route ? { route: collection.route } : {},
1741
- ...typeof collection.sitemap === "boolean" ? { sitemap: collection.sitemap } : {},
1742
- ...collection.i18n && collection.i18n !== true ? { i18n: collection.i18n } : {},
1743
- ...Object.keys(references).length ? { references } : {}
1744
- }
1745
- ];
1746
- }));
1795
+ const runtimeCollection = {
1796
+ ...collection.source ? { source: collection.source } : {},
1797
+ ...collection.exclude ? { exclude: collection.exclude } : {},
1798
+ ...collection.type ? { type: collection.type } : {},
1799
+ strict: collection.strict ?? true,
1800
+ ...collection.route ? { route: collection.route } : {},
1801
+ ...typeof collection.translatedSlugs === "boolean" ? { translatedSlugs: collection.translatedSlugs } : {},
1802
+ ...typeof collection.sitemap === "boolean" ? { sitemap: collection.sitemap } : {},
1803
+ ...collection.i18n && collection.i18n !== true ? { i18n: collection.i18n } : {},
1804
+ ...collection.cms ? { cms: collection.cms } : {},
1805
+ ...collection.agent ? { agent: collection.agent } : {},
1806
+ ...Object.keys(references).length ? { references } : {}
1807
+ };
1808
+ return [name, runtimeCollection, collection];
1809
+ });
1810
+ const runtimeCollections = Object.fromEntries(collectionEntries.map(([name, runtimeCollection]) => [name, runtimeCollection]));
1811
+ const privateRuntimeCollections = Object.fromEntries(collectionEntries.map(([name, runtimeCollection, collection]) => [
1812
+ name,
1813
+ {
1814
+ ...runtimeCollection,
1815
+ ...nuxt.options.dev && collection.schema ? { schema: collection.schema } : {}
1816
+ }
1817
+ ]));
1747
1818
  const cacheIntegrity = hash({
1748
1819
  locales: resolvedContentContext.locales,
1749
1820
  defaultLocale: resolvedContentContext.defaultLocale,
@@ -1756,7 +1827,7 @@ const registerContentContextFinalization = ({
1756
1827
  yaml: resolvedContentContext.yaml,
1757
1828
  csv: resolvedContentContext.csv
1758
1829
  });
1759
- applyContentRuntimeConfig(nuxt, options, resolvedContentContext, runtimeCollections, buildIntegrity, cacheIntegrity);
1830
+ await applyContentRuntimeConfig(nuxt, options, resolvedContentContext, appContentConfig, runtimeCollections, privateRuntimeCollections, buildIntegrity, cacheIntegrity);
1760
1831
  });
1761
1832
  };
1762
1833
 
@@ -1916,6 +1987,7 @@ const module$1 = defineNuxtModule({
1916
1987
  registerContentContextFinalization({
1917
1988
  nuxt,
1918
1989
  options,
1990
+ appContentConfig,
1919
1991
  contentContext,
1920
1992
  buildIntegrity,
1921
1993
  resolvePath,
@@ -4,7 +4,8 @@ import { useRuntimeConfig } from "#imports";
4
4
  import { useContentPreview } from "../../composables/preview";
5
5
  import { useUnwrap } from "../../composables/useUnwrap";
6
6
  import MarkdownRenderer from "./MarkdownRenderer.js";
7
- import { rewriteMarkdownRefLinks } from "../../../../integrations/vue/markdown-refs";
7
+ import { useLocalePath } from "../../composables/content-i18n";
8
+ import { resolveMarkdownRenderRefs, rewriteMarkdownRefLinks } from "../../../../core/references/resolve";
8
9
  import { loadContentComponentEntries } from "../../../../integrations/vue/content-components";
9
10
  import { resolveMarkdownRendererComponents } from "../../../markdown/plugins";
10
11
  import { isMarkdownRoot } from "../../../../core/markdown/tree";
@@ -39,14 +40,19 @@ const props = defineProps({
39
40
  }
40
41
  });
41
42
  const debug = import.meta.dev || useContentPreview().isEnabled();
42
- const runtimeContent = useRuntimeConfig().public.content;
43
+ const runtimeContent = useRuntimeConfig().public?.content || {};
43
44
  const attrs = useAttrs();
44
45
  const { unwrap: unwrapRoot } = useUnwrap();
46
+ const localePath = useLocalePath();
45
47
  const locale = computed(() => props.value.locale || props.value._resolvedLocale || props.value._locale);
48
+ const linkLocale = computed(() => props.value._requestedLocale || locale.value);
46
49
  const defaultLocale = computed(() => props.value.defaultLocale || runtimeContent.defaultLocale);
47
50
  const locales = computed(() => {
48
51
  const variantLocales = Array.isArray(props.value.variants) ? props.value.variants.map((variant) => variant.locale).filter((locale2) => Boolean(locale2)) : [];
49
- return variantLocales.length ? variantLocales : runtimeContent.locales || [];
52
+ return Array.from(/* @__PURE__ */ new Set([
53
+ ...runtimeContent.locales || [],
54
+ ...variantLocales
55
+ ]));
50
56
  });
51
57
  const body = computed(() => {
52
58
  let body2 = props.value.body || props.value;
@@ -56,7 +62,13 @@ const body = computed(() => {
56
62
  if (!isMarkdownRoot(body2)) {
57
63
  return null;
58
64
  }
59
- return props.value._resolvedRefs ? rewriteMarkdownRefLinks(body2, props.value._resolvedRefs) : body2;
65
+ const resolvedRefs = resolveMarkdownRenderRefs(
66
+ body2,
67
+ props.value._resolvedRefs,
68
+ runtimeContent.links,
69
+ (route) => localePath(route, linkLocale.value)
70
+ );
71
+ return Object.keys(resolvedRefs).length ? rewriteMarkdownRefLinks(body2, resolvedRefs) : body2;
60
72
  });
61
73
  const renderedBody = computed(() => {
62
74
  if (!body.value) {
@@ -70,7 +82,7 @@ const resolvedComponents = computed(() => {
70
82
  }
71
83
  return {
72
84
  ...Object.fromEntries(loadContentComponentEntries(renderedBody.value, runtimeContent.markdown?.tags || {})),
73
- ...resolveMarkdownRendererComponents(runtimeContent.markdown.plugins),
85
+ ...resolveMarkdownRendererComponents(runtimeContent.markdown?.plugins || []),
74
86
  ...props.components
75
87
  };
76
88
  });
@@ -1 +1 @@
1
- export { useRouteBaseName, useSetI18nParams, useSwitchLocalePath } from "#build/content-i18n.mjs";
1
+ export { useLocalePath, useRouteBaseName, useSetI18nParams, useSwitchLocalePath } from "#build/content-i18n.mjs";
@@ -49,6 +49,12 @@ const resolveLocalizedMaybeFunction = async (value, ctx) => {
49
49
  if (typeof value === "function") return await value(ctx);
50
50
  return localizedValue(value, ctx.locale);
51
51
  };
52
+ const resolveAppPageMarkdown = async (page, ctx) => {
53
+ if (typeof page.render === "function") {
54
+ return await page.render(ctx);
55
+ }
56
+ return localizedValue(page.markdown, ctx.locale);
57
+ };
52
58
  const resolveAppPageRoute = (page, locale) => normalizeAgentRoutePath(localizedValue(page.route, locale, "/"));
53
59
  const createAppPageContext = (locale, siteUrl) => ({
54
60
  locale,
@@ -96,6 +102,7 @@ const createAppOwnedAgentPages = async (locale, siteUrl) => {
96
102
  const path = resolveAppPageRoute(page, locale);
97
103
  const rawPath = agentRawPathForRoute(path);
98
104
  const section = resolveSection(page.section, locale);
105
+ const markdown = await resolveAppPageMarkdown(page, ctx);
99
106
  result.push({
100
107
  title: await resolveLocalizedMaybeFunction(page.title, ctx),
101
108
  description: await resolveLocalizedMaybeFunction(page.description, ctx),
@@ -113,7 +120,7 @@ const createAppOwnedAgentPages = async (locale, siteUrl) => {
113
120
  metadataFields: page.metadata,
114
121
  includeInIndex: page.includeInIndex !== false,
115
122
  includeInFull: page.includeInFull !== false,
116
- markdown: await page.render(ctx)
123
+ markdown
117
124
  });
118
125
  }
119
126
  return result;
@@ -1,4 +1,5 @@
1
1
  declare module '#build/content-i18n.mjs' {
2
+ export function useLocalePath(): (route: string | { name?: string, hash?: string, params?: Record<string, unknown>, query?: Record<string, unknown> }, locale?: string) => string
2
3
  export function useRouteBaseName(): (route: { name?: unknown } | unknown) => string | undefined
3
4
  export function useSetI18nParams(): (params: Record<string, unknown>) => void
4
5
  export function useSwitchLocalePath(): (locale: string) => string
@@ -1,12 +1,20 @@
1
1
  import { collectMarkdownRefLinks, parseRefLink } from "../core/references/resolve";
2
+ import { projectContentPathToLocale } from "../features/localization/path";
2
3
  import { contentConfig } from "./driver.js";
3
- import { getContentManifest, resolveVariant } from "./manifest.js";
4
- const prefixLocaleRoute = (path, locale) => {
5
- const defaultLocale = contentConfig().defaultLocale;
6
- if (!locale || locale === defaultLocale) {
7
- return path;
4
+ import { resolveCanonicalKey, resolveVariant } from "./manifest.js";
5
+ const isConfiguredQuickLink = (href) => {
6
+ const parsed = parseRefLink(href);
7
+ if (!parsed) {
8
+ return false;
8
9
  }
9
- return path === "/" ? `/${locale}` : `/${locale}${path}`;
10
+ const separator = parsed.ref.indexOf(".");
11
+ if (separator <= 0 || separator === parsed.ref.length - 1) {
12
+ return false;
13
+ }
14
+ const namespace = parsed.ref.slice(0, separator);
15
+ const key = parsed.ref.slice(separator + 1);
16
+ const links = contentConfig().links;
17
+ return Boolean(links?.[namespace]?.[key]);
10
18
  };
11
19
  const resolveDocumentRefLinks = async (event, content, requestedLocale) => {
12
20
  if (!content || content._type !== "markdown" || !content.body) {
@@ -16,14 +24,16 @@ const resolveDocumentRefLinks = async (event, content, requestedLocale) => {
16
24
  if (!hrefs.length) {
17
25
  return void 0;
18
26
  }
19
- const manifest = await getContentManifest(event);
20
27
  const entries = await Promise.all(hrefs.map(async (href) => {
21
28
  const parsed = parseRefLink(href);
22
29
  if (!parsed) {
23
30
  return null;
24
31
  }
25
- const canonicalKey = manifest.byRef[parsed.ref];
32
+ const canonicalKey = await resolveCanonicalKey(event, parsed.ref);
26
33
  if (!canonicalKey) {
34
+ if (isConfiguredQuickLink(href)) {
35
+ return [href, href];
36
+ }
27
37
  if (import.meta.dev) {
28
38
  console.warn(`[content] Could not resolve markdown ref "${href}" in "${content._file || content._id}"`);
29
39
  }
@@ -39,7 +49,8 @@ const resolveDocumentRefLinks = async (event, content, requestedLocale) => {
39
49
  if (import.meta.dev && requestedLocale && variant.resolvedLocale && variant.resolvedLocale !== requestedLocale) {
40
50
  console.warn(`[content] Markdown ref "${href}" in "${content._file || content._id}" fell back from locale "${requestedLocale}" to "${variant.resolvedLocale}"`);
41
51
  }
42
- return [href, `${prefixLocaleRoute(variant.path, variant.resolvedLocale)}${parsed.hash}`];
52
+ const routeLocale = variant.fallback && requestedLocale ? requestedLocale : variant.resolvedLocale;
53
+ return [href, `${projectContentPathToLocale(variant.path, routeLocale, contentConfig().defaultLocale)}${parsed.hash}`];
43
54
  }));
44
55
  const resolvedRefs = Object.fromEntries(entries.filter((entry) => Boolean(entry)));
45
56
  return Object.keys(resolvedRefs).length ? resolvedRefs : void 0;
@@ -47,6 +47,29 @@ const collectReferenceIssues = (schema, value, resolveReference, issues, path =
47
47
  }
48
48
  }
49
49
  };
50
+ const collectDerivedReferenceIssues = (references, value, resolveReference, issues) => {
51
+ if (!references) {
52
+ return;
53
+ }
54
+ for (const [collection, fields] of Object.entries(references)) {
55
+ for (const field of fields) {
56
+ const fieldValue = value[field];
57
+ const values = Array.isArray(fieldValue) ? fieldValue : [fieldValue];
58
+ for (const item of values) {
59
+ if (typeof item === "undefined" || item === null) {
60
+ continue;
61
+ }
62
+ if (typeof item !== "string") {
63
+ issues.push(`${field}: expected a string reference`);
64
+ continue;
65
+ }
66
+ if (!resolveReference(item, collection === "*" ? void 0 : collection)) {
67
+ issues.push(`${field}: unresolved reference "${item}"${collection !== "*" ? ` in collection "${collection}"` : ""}`);
68
+ }
69
+ }
70
+ }
71
+ }
72
+ };
50
73
  const internalDocumentFields = /* @__PURE__ */ new Set([
51
74
  "_id",
52
75
  "_file",
@@ -310,18 +333,24 @@ export const validateContentGraph = (contents, config) => {
310
333
  continue;
311
334
  }
312
335
  const collection = getCollectionConfig(document._collection, config.collections);
336
+ const references = collection?.references;
313
337
  const schema = collection?.schema;
314
- if (!schema) {
338
+ if (!schema && !references) {
315
339
  continue;
316
340
  }
317
341
  const issues = [];
318
- collectReferenceIssues(schema, document, (value, collection2) => {
342
+ const resolveReference = (value, collection2) => {
319
343
  const canonicalId = referenceTargets.get(normalizeReferenceValue(value));
320
344
  if (!canonicalId) {
321
345
  return false;
322
346
  }
323
347
  return collection2 ? targetCollections.get(canonicalId)?.has(collection2) === true : true;
324
- }, issues);
348
+ };
349
+ if (schema) {
350
+ collectReferenceIssues(schema, document, resolveReference, issues);
351
+ } else {
352
+ collectDerivedReferenceIssues(references, document, resolveReference, issues);
353
+ }
325
354
  if (!issues.length) {
326
355
  continue;
327
356
  }
@@ -125,6 +125,15 @@ export interface ContentAgentConfig {
125
125
  sections?: ContentAgentSectionConfig[];
126
126
  pages?: ContentAgentAppPageConfig[];
127
127
  }
128
+ export interface ContentAgentRuntimeAppPageConfig extends Omit<ContentAgentAppPageConfig, 'title' | 'description' | 'render'> {
129
+ title: ContentAgentLocalizedValue;
130
+ description: ContentAgentLocalizedValue;
131
+ markdown: ContentAgentLocalizedValue;
132
+ render?: ContentAgentAppPageConfig['render'];
133
+ }
134
+ export interface ContentAgentRuntimeConfig extends Omit<ContentAgentConfig, 'pages'> {
135
+ pages?: ContentAgentRuntimeAppPageConfig[];
136
+ }
128
137
  /**
129
138
  * Object source shape accepted for Nuxt Content v3 migration.
130
139
  */
@@ -264,6 +264,21 @@ export interface ContentRevalidateOptions {
264
264
  */
265
265
  allowUnsigned?: boolean;
266
266
  }
267
+ export interface ContentLinkRouteTarget {
268
+ /**
269
+ * Nuxt route name passed to Nuxt I18n `localePath()`.
270
+ */
271
+ route: string;
272
+ /**
273
+ * Static route params forwarded to Nuxt I18n `localePath()`.
274
+ */
275
+ params?: Record<string, string | number>;
276
+ /**
277
+ * Static query params forwarded to Nuxt I18n `localePath()`.
278
+ */
279
+ query?: Record<string, string | number | boolean | undefined>;
280
+ }
281
+ export type ContentLinksOptions = Record<string, Record<string, ContentLinkRouteTarget>>;
267
282
  export interface ContentAgentRouteOptions {
268
283
  routes?: boolean;
269
284
  linkHeaders?: boolean;
@@ -313,6 +328,15 @@ export interface ModuleOptions {
313
328
  * `x-ginko-revalidate-token` or `authorization: Bearer <token>`.
314
329
  */
315
330
  revalidate?: false | ContentRevalidateOptions;
331
+ /**
332
+ * Writer-facing markdown quick links. Values point at Nuxt route names;
333
+ * localized paths stay owned by Nuxt I18n.
334
+ *
335
+ * @example
336
+ * content.links.main.pricing = { route: 'pricing' }
337
+ * markdown: [Pricing]($main.pricing)
338
+ */
339
+ links?: ContentLinksOptions;
316
340
  /**
317
341
  * First-class agent markdown and LLM route features.
318
342
  */
package/dist/types.d.mts CHANGED
@@ -8,4 +8,4 @@ export { type agentMetadataFields, type defineAgentAppPage, type defineAgentMark
8
8
 
9
9
  export { default, type normalizeContentConfigCollectionNames } from './module.mjs'
10
10
 
11
- export { type AgentMetadataField, type AgentMetadataFieldList, type BacklinkFields, type BacklinkSource, type BacklinksOptions, type BacklinksResult, type CollectionQueryBuilder, type CollectionQueryField, type CollectionQueryFieldValue, type CollectionQueryKey, type CollectionQueryOperator, type CollectionQueryValue, type CollectionSchema, type ContentAgentAppPageConfig, type ContentAgentAppPageContext, type ContentAgentCollectionConfig, type ContentAgentConfig, type ContentAgentLocalizedValue, type ContentAgentMarkdownMetadataConfig, type ContentAgentMarkdownOptions, type ContentAgentMarkdownPolicyConfig, type ContentAgentRouteOptions, type ContentAgentSectionConfig, type ContentAgentSiteConfig, type ContentCacheArtifact, type ContentCmsCollectionConfig, type ContentCmsFieldConfig, type ContentCmsFieldType, type ContentCmsRelationConfig, type ContentCollectionConfig, type ContentCollectionHandle, type ContentCollectionI18nConfig, type ContentCollectionI18nMap, type ContentCollectionItem, type ContentCollectionItemSurroundingsOptions, type ContentCollectionKind, type ContentCollectionMap, type ContentCollectionName, type ContentCollectionNavigationOptions, type ContentCollectionPageOptions, type ContentCollectionRouteConfig, type ContentCollectionRouteMetaOptions, type ContentCollectionSearchSectionsOptions, type ContentCollectionSource, type ContentCollectionSourceObject, type ContentCollectionStringName, type ContentCollectionTarget, type ContentConfig, type ContentContext, type ContentI18nOptions, type ContentLocaleEntry, type ContentLocaleRoute, type ContentManifest, type ContentMiniSearchOptions, type ContentNavigationItem, type ContentPageResult, type ContentPreviewOptions, type ContentProviderName, type ContentProviderSearchRequest, type ContentQueryBuilder, type ContentQueryBuilderParams, type ContentQueryBuilderWhere, type ContentQueryFetcher, type ContentQueryRequest, type ContentQuerySortFields, type ContentQuerySortOptions, type ContentQuerySortParams, type ContentReferenceSchema, type ContentResolvedMeta, type ContentRevalidateOptions, type ContentRouteMeta, type ContentSearchEngine, type ContentSearchIndexRecord, type ContentSearchOptions, type ContentSearchPublicRuntimeConfig, type ContentSearchResult, type ContentSearchSection, type ContentSelector, type ContentSeoImage, type ContentSeoMeta, type ContentSitemapAlternative, type ContentSitemapAssertOptions, type ContentSitemapAssertSitemapOptions, type ContentSitemapEntry, type ContentSitemapImage, type ContentSitemapOptions, type ContentTransformer, type ContentTreeItem, type ContentVariant, type DefineCollectionObject, type DefineCollectionOptions, type DocumentFromHandle, type LocaleFallback, type LocalePathEntry, type LocalizedContentDocument, type LocalizedDoc, type ManifestVariant, type ManyOptions, type MarkdownNode, type MarkdownOptions, type MarkdownParsedContent, type MarkdownPluginDescriptor, type MarkdownPluginOptions, type MarkdownRoot, type ModuleHooks, type ModuleOptions, type MountOptions, type NavItem, type NeighborsOptions, type NeighborsResult, type OneOptions, type PaginationOptions, type PaginationResult, type ParseContentOptions, type ParsedContent, type ParsedContentInternalMeta, type ParsedContentMeta, type PopulateFromOptions, type PopulateSpec, type PopulatedDocument, type QueryGroupBuilder, type QueryGroupFunction, type QueryMatchOperator, type QueryOperators, type QueryOrderDirection, type QueryOrderOptions, type QueryWhere, type ResolutionEnvelope, type ResolveContentReferenceOptions, type ResolveOneOptions, type ResolveOneResult, type ResolvedContentContext, type ResolvedContentI18nOptions, type ResolvedMarkdownPlugin, type ResolvedVariant, type SortDirection, type SortSpec, type StrictParsedContent, type StrictParsedContentMeta, type Toc, type TocLink, type TransformContentOptions, type TransformContentSource, type TreeOptions, type VariantsOptions } from './module.mjs'
11
+ export { type AgentMetadataField, type AgentMetadataFieldList, type BacklinkFields, type BacklinkSource, type BacklinksOptions, type BacklinksResult, type CollectionQueryBuilder, type CollectionQueryField, type CollectionQueryFieldValue, type CollectionQueryKey, type CollectionQueryOperator, type CollectionQueryValue, type CollectionSchema, type ContentAgentAppPageConfig, type ContentAgentAppPageContext, type ContentAgentCollectionConfig, type ContentAgentConfig, type ContentAgentLocalizedValue, type ContentAgentMarkdownMetadataConfig, type ContentAgentMarkdownOptions, type ContentAgentMarkdownPolicyConfig, type ContentAgentRouteOptions, type ContentAgentRuntimeAppPageConfig, type ContentAgentRuntimeConfig, type ContentAgentSectionConfig, type ContentAgentSiteConfig, type ContentCacheArtifact, type ContentCmsCollectionConfig, type ContentCmsFieldConfig, type ContentCmsFieldType, type ContentCmsRelationConfig, type ContentCollectionConfig, type ContentCollectionHandle, type ContentCollectionI18nConfig, type ContentCollectionI18nMap, type ContentCollectionItem, type ContentCollectionItemSurroundingsOptions, type ContentCollectionKind, type ContentCollectionMap, type ContentCollectionName, type ContentCollectionNavigationOptions, type ContentCollectionPageOptions, type ContentCollectionRouteConfig, type ContentCollectionRouteMetaOptions, type ContentCollectionSearchSectionsOptions, type ContentCollectionSource, type ContentCollectionSourceObject, type ContentCollectionStringName, type ContentCollectionTarget, type ContentConfig, type ContentContext, type ContentI18nOptions, type ContentLinkRouteTarget, type ContentLinksOptions, type ContentLocaleEntry, type ContentLocaleRoute, type ContentManifest, type ContentMiniSearchOptions, type ContentNavigationItem, type ContentPageResult, type ContentPreviewOptions, type ContentProviderName, type ContentProviderSearchRequest, type ContentQueryBuilder, type ContentQueryBuilderParams, type ContentQueryBuilderWhere, type ContentQueryFetcher, type ContentQueryRequest, type ContentQuerySortFields, type ContentQuerySortOptions, type ContentQuerySortParams, type ContentReferenceSchema, type ContentResolvedMeta, type ContentRevalidateOptions, type ContentRouteMeta, type ContentSearchEngine, type ContentSearchIndexRecord, type ContentSearchOptions, type ContentSearchPublicRuntimeConfig, type ContentSearchResult, type ContentSearchSection, type ContentSelector, type ContentSeoImage, type ContentSeoMeta, type ContentSitemapAlternative, type ContentSitemapAssertOptions, type ContentSitemapAssertSitemapOptions, type ContentSitemapEntry, type ContentSitemapImage, type ContentSitemapOptions, type ContentTransformer, type ContentTreeItem, type ContentVariant, type DefineCollectionObject, type DefineCollectionOptions, type DocumentFromHandle, type LocaleFallback, type LocalePathEntry, type LocalizedContentDocument, type LocalizedDoc, type ManifestVariant, type ManyOptions, type MarkdownNode, type MarkdownOptions, type MarkdownParsedContent, type MarkdownPluginDescriptor, type MarkdownPluginOptions, type MarkdownRoot, type ModuleHooks, type ModuleOptions, type MountOptions, type NavItem, type NeighborsOptions, type NeighborsResult, type OneOptions, type PaginationOptions, type PaginationResult, type ParseContentOptions, type ParsedContent, type ParsedContentInternalMeta, type ParsedContentMeta, type PopulateFromOptions, type PopulateSpec, type PopulatedDocument, type QueryGroupBuilder, type QueryGroupFunction, type QueryMatchOperator, type QueryOperators, type QueryOrderDirection, type QueryOrderOptions, type QueryWhere, type ResolutionEnvelope, type ResolveContentReferenceOptions, type ResolveOneOptions, type ResolveOneResult, type ResolvedContentContext, type ResolvedContentI18nOptions, type ResolvedMarkdownPlugin, type ResolvedVariant, type SortDirection, type SortSpec, type StrictParsedContent, type StrictParsedContentMeta, type Toc, type TocLink, type TransformContentOptions, type TransformContentSource, type TreeOptions, type VariantsOptions } from './module.mjs'
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "framework": "vue",
3
3
  "name": "@lupinum/ginko-content",
4
- "version": "0.1.4",
4
+ "version": "0.1.6",
5
5
  "contributions": {
6
6
  "html": {
7
7
  "description-markup": "markdown",
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@lupinum/ginko-content",
3
- "version": "0.1.4",
3
+ "version": "0.1.6",
4
4
  "description": "Collection-first content engine for Nuxt",
5
5
  "homepage": "https://ginko-content.nuxt.dev",
6
6
  "bugs": {