@lupinum/ginko-content 1.0.0-beta.5 → 1.0.0-beta.7

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/README.md CHANGED
@@ -15,7 +15,7 @@
15
15
  </p>
16
16
 
17
17
  > [!WARNING]
18
- > Version `1.0.0-beta.5` is a prerelease. Install it from npm's `next`
18
+ > Version `1.0.0-beta.7` is a prerelease. Install it from npm's `next`
19
19
  > channel. The stable `0.3` line remains on `latest`.
20
20
 
21
21
  ## Why use this package?
@@ -32,7 +32,7 @@ output.
32
32
 
33
33
  - Node.js 22.18–22.x, 24.11–24.x, or 26+
34
34
  - Nuxt 4.5.1 through Nuxt 4.x
35
- - Vue 3.5.35 through Vue 3.x
35
+ - Vue 3.5.40 through Vue 3.x
36
36
  - ESM; CommonJS `require()` is not supported
37
37
 
38
38
  ## Installation
@@ -40,13 +40,13 @@ output.
40
40
  Use the Nuxt CLI:
41
41
 
42
42
  ```bash
43
- npx nuxi module add @lupinum/ginko-content@1.0.0-beta.5
43
+ npx nuxi module add @lupinum/ginko-content@1.0.0-beta.7
44
44
  ```
45
45
 
46
46
  Or install and register the module by hand:
47
47
 
48
48
  ```bash
49
- pnpm add @lupinum/ginko-content@1.0.0-beta.5
49
+ pnpm add @lupinum/ginko-content@1.0.0-beta.7
50
50
  ```
51
51
 
52
52
  ```ts
package/dist/module.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@lupinum/ginko-content",
3
- "version": "1.0.0-beta.5",
3
+ "version": "1.0.0-beta.7",
4
4
  "configKey": "content",
5
5
  "compatibility": {
6
6
  "nuxt": ">=4.5.1 <5"
package/dist/module.mjs CHANGED
@@ -34,7 +34,7 @@ import { assertCanonicalHighlightOptionNames } from '../dist/parsers/markdown-pl
34
34
  import { BUILTIN_MARKDOWN_RENDER_CONTRACTS } from '../dist/core/markdown/builtin-render-contracts.js';
35
35
 
36
36
  const name = "@lupinum/ginko-content";
37
- const version = "1.0.0-beta.5";
37
+ const version = "1.0.0-beta.7";
38
38
  const peerDependencies = {
39
39
  nuxt: ">=4.5.1 <5"};
40
40
 
@@ -252,6 +252,7 @@ const registerContentDevRuntime = (nuxt, options, contentContext) => {
252
252
  };
253
253
 
254
254
  const runtimeAppImportSpecs = [
255
+ { name: "useContentLocalePath", as: "useContentLocalePath", from: "./app/composables/use-content-locale-path.js" },
255
256
  { name: "useContentPage", as: "useContentPage", from: "./app/composables/use-content-page.js" },
256
257
  { name: "useContentSearch", as: "useGinkoContentSearch", from: "./app/composables/search.js" }
257
258
  ];
@@ -1109,6 +1110,7 @@ const collectRawMarkdownRoutesFromGeneratedFrontmatter = (markdown) => {
1109
1110
  };
1110
1111
  const publicOutputPath = (publicDir, route) => join(publicDir, normalizeStaticRoutePath(route).replace(/^\//, ""));
1111
1112
 
1113
+ const runtimeModuleImportURL = (modulePath, toFileURL = pathToFileURL) => toFileURL(modulePath).href;
1112
1114
  const assertPrerenderedContentSnapshot = (buildDir) => {
1113
1115
  const snapshotPath = resolve$1(buildDir, "content-cache/snapshot.json");
1114
1116
  let raw;
@@ -1166,7 +1168,7 @@ ${details}` : "")
1166
1168
  writeFileSync(indexPath, json, "utf8");
1167
1169
  if (searchRuntime.engine === "pagefind") {
1168
1170
  const records = JSON.parse(json);
1169
- const { writePagefindIndex } = await import(resolveRuntimeModule("./server/pagefind.js"));
1171
+ const { writePagefindIndex } = await import(runtimeModuleImportURL(resolveRuntimeModule("./server/pagefind.js")));
1170
1172
  await writePagefindIndex(records, resolve$1(publicDir, "pagefind"), resolvedI18n.defaultLocale);
1171
1173
  }
1172
1174
  }
@@ -1364,6 +1366,29 @@ const registerContentNitroIntegrationHooks = (nitroConfig, options, contentConte
1364
1366
  });
1365
1367
  };
1366
1368
 
1369
+ const NUXT_NITRO_CACHE_DRIVER_SUFFIX = "/node_modules/@nuxt/nitro-server/dist/runtime/utils/cache-driver.mjs";
1370
+ const createNuxtWindowsCacheDriverResolver = (platform = process.platform, fromFileURL = fileURLToPath) => {
1371
+ if (platform !== "win32") {
1372
+ return void 0;
1373
+ }
1374
+ return {
1375
+ name: "ginko-content:nuxt-windows-cache-driver",
1376
+ resolveId(id) {
1377
+ if (!id.startsWith("file:")) {
1378
+ return null;
1379
+ }
1380
+ try {
1381
+ const url = new URL(id);
1382
+ if (url.protocol !== "file:" || url.hostname !== "" || url.search !== "" || url.hash !== "" || !url.pathname.toLowerCase().endsWith(NUXT_NITRO_CACHE_DRIVER_SUFFIX)) {
1383
+ return null;
1384
+ }
1385
+ return fromFileURL(url);
1386
+ } catch {
1387
+ return null;
1388
+ }
1389
+ }
1390
+ };
1391
+ };
1367
1392
  const hookNuxtBoundary$1 = (nuxt, name, callback) => {
1368
1393
  const hook = nuxt.hook;
1369
1394
  hook(name, callback);
@@ -1414,6 +1439,12 @@ const registerContentNitroConfig = ({
1414
1439
  }
1415
1440
  nitroConfig.bundledStorage = nitroConfig.bundledStorage || [];
1416
1441
  nitroConfig.bundledStorage.push("cache:content");
1442
+ const windowsCacheDriverResolver = createNuxtWindowsCacheDriverResolver();
1443
+ if (windowsCacheDriverResolver) {
1444
+ nitroConfig.rollupConfig ||= {};
1445
+ nitroConfig.rollupConfig.plugins ||= [];
1446
+ nitroConfig.rollupConfig.plugins.push(windowsCacheDriverResolver);
1447
+ }
1417
1448
  nitroConfig.externals = defu(typeof nitroConfig.externals === "object" ? nitroConfig.externals : {}, {
1418
1449
  inline: [
1419
1450
  resolveModuleFile("."),
@@ -2,14 +2,16 @@
2
2
  * Client-side public API surface (browser + SSR Vue).
3
3
  *
4
4
  * The unified query API (ADR-0016) is the supported core read path. The
5
- * public composable surface is exactly `useContentPage` and
6
- * `useContentSearch`; every other application workflow is
5
+ * public composable surface includes page loading, locale links, and
6
+ * search; every other application workflow is
7
7
  * ordinary Nuxt composition over the one-shot async query operations below.
8
8
  */
9
9
  export { one, many, count, paginate, backlinks, resolveOne, surround, navigation } from '../runtime/app/composables/query-api.js';
10
10
  export { getCollectionPath } from '../features/query/routes.js';
11
11
  export type { CollectionPathOptions } from '../features/query/routes.js';
12
12
  export { findFirstNavigationPage } from '../features/navigation/resolve.js';
13
+ export { useContentLocalePath } from '../runtime/app/composables/use-content-locale-path.js';
14
+ export type { ContentLocalePage, UseContentLocalePathOptions } from '../runtime/app/composables/use-content-locale-path.js';
13
15
  export { useContentPage } from '../runtime/app/composables/use-content-page.js';
14
16
  export type { ContentPageStatus, UseContentPageOptions, UseContentPageReturn } from '../runtime/app/composables/use-content-page.js';
15
17
  export type { QueryWhere, QueryOperators, ContentSelector, BacklinkFields, BacklinkSource, BacklinksOptions, BacklinksResult, CountOptions, LocaleFallback, DocumentFromHandle, OneOptions, ManyOptions, PaginationOptions, PaginationResult, PopulateSpec, PopulatedDocument, ResolveOneOptions, ResolveOneResult, LocalizedContentDocument, ResolutionEnvelope, SortSpec, ContentAlternate, ContentDocumentRoute, ContentDocumentResolution, ContentNavigationItem, ContentCollectionName, ContentCollectionTarget, ContentSearchSection, SurroundOptions, SurroundResult, NavigationOptions, ContentNavigationTreeItem, ResolvedContentNavigationItem, PaginationMode, OffsetPaginationResult, CursorPaginationResult } from '../types/query.js';
@@ -10,6 +10,7 @@ export {
10
10
  } from "../runtime/app/composables/query-api.js";
11
11
  export { getCollectionPath } from "../features/query/routes.js";
12
12
  export { findFirstNavigationPage } from "../features/navigation/resolve.js";
13
+ export { useContentLocalePath } from "../runtime/app/composables/use-content-locale-path.js";
13
14
  export { useContentPage } from "../runtime/app/composables/use-content-page.js";
14
15
  export { useContentSearch } from "../runtime/app/composables/search.js";
15
16
  export { querySiteData } from "../runtime/app/composables/site-data.js";
@@ -0,0 +1,10 @@
1
+ import { type MaybeRefOrGetter } from 'vue';
2
+ import type { UseContentPageReturn } from './use-content-page';
3
+ /** The existing page result supplies the facts; no shared state or second query. */
4
+ export type ContentLocalePage = Pick<UseContentPageReturn<unknown>, 'page' | 'status'>;
5
+ export interface UseContentLocalePathOptions<L extends string = string> {
6
+ /** Used only when the source is undefined (an application-only page). */
7
+ fallback?: (locale: L) => string | undefined;
8
+ }
9
+ /** Derive interactive locale links from a page result supplied by its owner. */
10
+ export declare function useContentLocalePath<L extends string = string>(source: MaybeRefOrGetter<ContentLocalePage | undefined>, options?: UseContentLocalePathOptions<L>): (locale: L) => string | undefined;
@@ -0,0 +1,20 @@
1
+ import { toValue } from "vue";
2
+ import { useRoute, useRouter } from "#imports";
3
+ import { pageMatchesRoute } from "./use-content-shared.js";
4
+ import { getContentRuntime } from "./runtime.js";
5
+ export function useContentLocalePath(source, options = {}) {
6
+ const route = useRoute();
7
+ const router = useRouter();
8
+ const runtime = getContentRuntime();
9
+ return (locale) => {
10
+ const result = toValue(source);
11
+ if (!result) return runtime.locales?.includes(locale) ? options.fallback?.(locale) : void 0;
12
+ if (result.status.value !== "success") return void 0;
13
+ const document = result.page.value;
14
+ if (!document || !pageMatchesRoute(document, route.path)) return void 0;
15
+ const alternates = document.route.alternates;
16
+ const alternate = alternates.find((item) => item.locale === locale && item.source === "variant") ?? alternates.find((item) => item.locale === locale);
17
+ if (!alternate) return void 0;
18
+ return router.resolve({ path: alternate.path, query: route.query, hash: route.hash }).fullPath;
19
+ };
20
+ }
@@ -3,21 +3,11 @@ import { useAsyncData, useRoute } from "#imports";
3
3
  import { resolveCollectionI18n } from "../../../features/localization/path.js";
4
4
  import { one, surround } from "./query-api.js";
5
5
  import { getContentRuntime } from "./runtime.js";
6
- import { contentCollectionName, resolveLocaleFromRoutePath, resolveOptions, stableKey } from "./use-content-shared.js";
7
- const normalizeRoutePath = (path) => {
8
- if (typeof path !== "string") return void 0;
9
- const normalized = path.replace(/\/+$/, "");
10
- return normalized || "/";
11
- };
6
+ import { contentCollectionName, normalizeRoutePath, pageMatchesRoute, resolveLocaleFromRoutePath, resolveOptions, stableKey } from "./use-content-shared.js";
12
7
  const normalizePageSurround = (surroundOption) => {
13
8
  if (!surroundOption) return void 0;
14
9
  return surroundOption === true ? {} : surroundOption;
15
10
  };
16
- const pageMatchesRoute = (doc, path) => {
17
- if (!doc?.route) return false;
18
- const normalizedPath = normalizeRoutePath(path);
19
- return normalizeRoutePath(doc.route.requestedPath) === normalizedPath || normalizeRoutePath(doc.route.resolvedPath) === normalizedPath;
20
- };
21
11
  export async function useContentPage(handle, options = {}) {
22
12
  const route = useRoute();
23
13
  const { surround: surroundOption, ...oneOptions } = options;
@@ -11,3 +11,18 @@ export declare const resolveOptions: <T extends Record<string, unknown>>(reactiv
11
11
  export declare const stableKey: (prefix: string, name: string, options: unknown) => string;
12
12
  export declare const contentCollectionName: (handle: ContentCollectionTarget) => string;
13
13
  export declare const resolveLocaleFromRoutePath: (path: string, locales: string[], defaultLocale?: string) => string | undefined;
14
+ export declare const normalizeRoutePath: (path: unknown) => string | undefined;
15
+ /**
16
+ * A resolved page "matches" the current route when either the exact
17
+ * selector the app queried with (`route.requestedPath`) or the document's
18
+ * own canonical public path (`route.resolvedPath`) equals the current route
19
+ * — the first covers a provider/alias match whose canonical path differs
20
+ * from the requested one, the
21
+ * second covers static/prerendered routes served under a normalized path.
22
+ */
23
+ export declare const pageMatchesRoute: (doc: {
24
+ route?: {
25
+ requestedPath?: string;
26
+ resolvedPath?: string;
27
+ };
28
+ } | null | undefined, path: string) => boolean;
@@ -27,3 +27,13 @@ export const resolveLocaleFromRoutePath = (path, locales, defaultLocale) => {
27
27
  const firstSegment = path.split("/").filter(Boolean)[0];
28
28
  return firstSegment && locales.includes(firstSegment) ? firstSegment : defaultLocale;
29
29
  };
30
+ export const normalizeRoutePath = (path) => {
31
+ if (typeof path !== "string") return void 0;
32
+ const normalized = path.replace(/\/+$/, "");
33
+ return normalized || "/";
34
+ };
35
+ export const pageMatchesRoute = (doc, path) => {
36
+ if (!doc?.route) return false;
37
+ const normalizedPath = normalizeRoutePath(path);
38
+ return normalizeRoutePath(doc.route.requestedPath) === normalizedPath || normalizeRoutePath(doc.route.resolvedPath) === normalizedPath;
39
+ };
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "framework": "vue",
3
3
  "name": "@lupinum/ginko-content",
4
- "version": "1.0.0-beta.5",
4
+ "version": "1.0.0-beta.7",
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": "1.0.0-beta.5",
3
+ "version": "1.0.0-beta.7",
4
4
  "description": "Collection-first content engine for Nuxt",
5
5
  "homepage": "https://ginko-content.lupinum.com",
6
6
  "bugs": {
@@ -172,7 +172,7 @@
172
172
  "nuxt": ">=4.5.1 <5",
173
173
  "pagefind": "^1.5.2",
174
174
  "vitest": ">=4.1.6 <5",
175
- "vue": "^3.5.35"
175
+ "vue": "^3.5.40"
176
176
  },
177
177
  "peerDependenciesMeta": {
178
178
  "beautiful-mermaid": {