@lupinum/ginko-content 0.1.1 → 0.1.2
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 +22 -6
- package/compatibility.json +2 -2
- package/dist/cli.mjs +108 -36
- package/dist/core/references/schema.d.ts +1 -0
- package/dist/core/references/schema.js +33 -10
- package/dist/module.d.mts +78 -19
- package/dist/module.json +1 -1
- package/dist/module.mjs +196 -33
- package/dist/public/client.d.ts +4 -2
- package/dist/public/client.js +2 -0
- package/dist/runtime/app/components/ContentRenderer.vue +9 -5
- package/dist/runtime/app/composables/head.d.ts +27 -3
- package/dist/runtime/app/composables/head.js +82 -78
- package/dist/runtime/app/composables/query-api.d.ts +9 -10
- package/dist/runtime/app/composables/search.d.ts +4 -4
- package/dist/runtime/app/composables/search.js +11 -5
- package/dist/runtime/app/composables/use-content.d.ts +35 -15
- package/dist/runtime/app/composables/use-content.js +83 -13
- package/dist/runtime/query/unified.d.ts +1 -0
- package/dist/runtime/query/unified.js +93 -6
- package/dist/runtime/server/api/search.d.ts +2 -1
- package/dist/runtime/server/api/search.js +6 -2
- package/dist/runtime/server/pagefind.js +1 -0
- package/dist/runtime/server/search.d.ts +11 -0
- package/dist/runtime/server/search.js +24 -7
- package/dist/runtime/shared/search.js +3 -2
- package/dist/testing/provider-fixture.js +1 -0
- package/dist/types/config.d.ts +27 -7
- package/dist/types/config.js +26 -2
- package/dist/types/module.d.ts +35 -3
- package/dist/types/query.d.ts +19 -11
- package/dist/types/search.d.ts +3 -1
- package/dist/types.d.mts +2 -2
- package/dist/web-types.json +1 -1
- package/package.json +4 -4
|
@@ -4,95 +4,99 @@ function isHeadImageObject(image) {
|
|
|
4
4
|
return typeof image === "object" && image !== null;
|
|
5
5
|
}
|
|
6
6
|
export const useContentHead = (_content, to = useRoute()) => {
|
|
7
|
-
const content = unref(_content);
|
|
8
7
|
const config = useRuntimeConfig();
|
|
9
|
-
const appConfig = config.app;
|
|
10
8
|
const applyHead = (head) => useHead(head);
|
|
11
|
-
const refreshHead = (data =
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
const title = head.title || data?.title;
|
|
19
|
-
if (title) {
|
|
20
|
-
head.title = title;
|
|
21
|
-
if (import.meta.server && !head.meta.some((m) => m.property === "og:title")) {
|
|
22
|
-
head.meta.push({
|
|
23
|
-
property: "og:title",
|
|
24
|
-
content: title
|
|
25
|
-
});
|
|
26
|
-
}
|
|
9
|
+
const refreshHead = (data = unref(_content)) => {
|
|
10
|
+
const head = resolveContentHead(data, to, config);
|
|
11
|
+
if (!head) return;
|
|
12
|
+
if (import.meta.client) {
|
|
13
|
+
nextTick(() => applyHead(head));
|
|
14
|
+
} else {
|
|
15
|
+
applyHead(head);
|
|
27
16
|
}
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
17
|
+
};
|
|
18
|
+
watch(() => unref(_content), refreshHead, { immediate: true });
|
|
19
|
+
};
|
|
20
|
+
export const resolveContentHead = (data, to, config) => {
|
|
21
|
+
if (!to.path || !data) {
|
|
22
|
+
return null;
|
|
23
|
+
}
|
|
24
|
+
const head = Object.assign({}, data?.head || data?.seo || {});
|
|
25
|
+
const appConfig = config.app;
|
|
26
|
+
head.meta = [...head.meta || []];
|
|
27
|
+
head.link = [...head.link || []];
|
|
28
|
+
const title = head.title || data?.title;
|
|
29
|
+
if (title) {
|
|
30
|
+
head.title = title;
|
|
31
|
+
if (import.meta.server && !head.meta.some((m) => m.property === "og:title")) {
|
|
32
|
+
head.meta.push({
|
|
33
|
+
property: "og:title",
|
|
34
|
+
content: title
|
|
35
|
+
});
|
|
43
36
|
}
|
|
44
|
-
|
|
45
|
-
|
|
37
|
+
}
|
|
38
|
+
const siteUrl = typeof config.public.siteUrl === "string" ? withoutTrailingSlash(config.public.siteUrl) : void 0;
|
|
39
|
+
if (import.meta.server && siteUrl) {
|
|
40
|
+
const url = withoutTrailingSlash(joinURL(siteUrl, appConfig.baseURL, to.fullPath));
|
|
41
|
+
if (!head.meta.some((m) => m.property === "og:url")) {
|
|
46
42
|
head.meta.push({
|
|
47
|
-
|
|
48
|
-
content:
|
|
43
|
+
property: "og:url",
|
|
44
|
+
content: url
|
|
45
|
+
});
|
|
46
|
+
}
|
|
47
|
+
if (!head.link.some((m) => m.rel === "canonical")) {
|
|
48
|
+
head.link.push({
|
|
49
|
+
rel: "canonical",
|
|
50
|
+
href: url
|
|
49
51
|
});
|
|
50
52
|
}
|
|
51
|
-
|
|
53
|
+
}
|
|
54
|
+
const description = head?.description || data?.description;
|
|
55
|
+
if (description && head.meta.filter((m) => m.name === "description").length === 0) {
|
|
56
|
+
head.meta.push({
|
|
57
|
+
name: "description",
|
|
58
|
+
content: description
|
|
59
|
+
});
|
|
60
|
+
}
|
|
61
|
+
if (import.meta.server && description && !head.meta.some((m) => m.property === "og:description")) {
|
|
62
|
+
head.meta.push({
|
|
63
|
+
property: "og:description",
|
|
64
|
+
content: description
|
|
65
|
+
});
|
|
66
|
+
}
|
|
67
|
+
const image = head?.image || data?.image;
|
|
68
|
+
if (import.meta.server && image && head.meta.filter((m) => m.property === "og:image").length === 0) {
|
|
69
|
+
if (typeof image === "string") {
|
|
52
70
|
head.meta.push({
|
|
53
|
-
property: "og:
|
|
54
|
-
content:
|
|
71
|
+
property: "og:image",
|
|
72
|
+
content: siteUrl && !hasProtocol(image) ? new URL(joinURL(appConfig.baseURL, image), siteUrl).href : image
|
|
55
73
|
});
|
|
56
74
|
}
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
"
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
property: "og:image",
|
|
80
|
-
content: siteUrl && !isAbsoluteURL ? new URL(imageURL, siteUrl).href : imageURL
|
|
81
|
-
});
|
|
82
|
-
} else if (image[key]) {
|
|
83
|
-
head.meta.push({
|
|
84
|
-
property: `og:image:${key}`,
|
|
85
|
-
content: image[key]
|
|
86
|
-
});
|
|
87
|
-
}
|
|
75
|
+
if (isHeadImageObject(image)) {
|
|
76
|
+
const imageKeys = [
|
|
77
|
+
"src",
|
|
78
|
+
"secure_url",
|
|
79
|
+
"type",
|
|
80
|
+
"width",
|
|
81
|
+
"height",
|
|
82
|
+
"alt"
|
|
83
|
+
];
|
|
84
|
+
for (const key of imageKeys) {
|
|
85
|
+
if (key === "src" && image.src) {
|
|
86
|
+
const isAbsoluteURL = hasProtocol(image.src);
|
|
87
|
+
const imageURL = isAbsoluteURL ? image.src : joinURL(appConfig.baseURL, image.src ?? "/");
|
|
88
|
+
head.meta.push({
|
|
89
|
+
property: "og:image",
|
|
90
|
+
content: siteUrl && !isAbsoluteURL ? new URL(imageURL, siteUrl).href : imageURL
|
|
91
|
+
});
|
|
92
|
+
} else if (image[key]) {
|
|
93
|
+
head.meta.push({
|
|
94
|
+
property: `og:image:${key}`,
|
|
95
|
+
content: image[key]
|
|
96
|
+
});
|
|
88
97
|
}
|
|
89
98
|
}
|
|
90
99
|
}
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
} else {
|
|
94
|
-
applyHead(head);
|
|
95
|
-
}
|
|
96
|
-
};
|
|
97
|
-
watch(() => unref(_content), refreshHead, { immediate: true });
|
|
100
|
+
}
|
|
101
|
+
return head;
|
|
98
102
|
};
|
|
@@ -1,26 +1,25 @@
|
|
|
1
1
|
import type { ParsedContent } from '../../../types/content';
|
|
2
|
-
import type {
|
|
3
|
-
import type { ContentVariant, ContentTreeItem, BacklinksOptions, BacklinksResult, BacklinkSource, DocumentFromHandle, ManyOptions, LocalizedDoc, NeighborsOptions, NeighborsResult, OneOptions, PopulateSpec, PopulateFromOptions, PopulatedDocument, PaginationOptions, PaginationResult, ResolveOneOptions, ResolveOneResult, TreeOptions, VariantsOptions } from '../../../types/query';
|
|
2
|
+
import type { ContentCollectionTarget, ContentVariant, ContentTreeItem, BacklinksOptions, BacklinksResult, BacklinkSource, DocumentFromHandle, ManyOptions, LocalizedDoc, NeighborsOptions, NeighborsResult, OneOptions, PopulateSpec, PopulateFromOptions, PopulatedDocument, PaginationOptions, PaginationResult, ResolveOneOptions, ResolveOneResult, TreeOptions, VariantsOptions } from '../../../types/query';
|
|
4
3
|
import { type ContentQueryContext } from '../../query/unified';
|
|
5
4
|
export declare const createClientContentQueryContext: () => ContentQueryContext;
|
|
6
|
-
export declare function resolveOne<const H extends
|
|
7
|
-
export declare function one<const H extends
|
|
8
|
-
export declare function many<const H extends
|
|
9
|
-
export declare function paginate<const H extends
|
|
10
|
-
export declare function backlinks<const Target extends
|
|
11
|
-
export declare function variants<H extends
|
|
5
|
+
export declare function resolveOne<const H extends ContentCollectionTarget, O extends ResolveOneOptions<H, PopulateSpec | undefined>>(handle: H, options: O): Promise<ResolveOneResult<PopulatedDocument<DocumentFromHandle<H>, PopulateFromOptions<O>>>>;
|
|
6
|
+
export declare function one<const H extends ContentCollectionTarget, O extends OneOptions<H, PopulateSpec | undefined>>(handle: H, options: O): Promise<LocalizedDoc<PopulatedDocument<DocumentFromHandle<H>, PopulateFromOptions<O>>> | null>;
|
|
7
|
+
export declare function many<const H extends ContentCollectionTarget, O extends ManyOptions<H, PopulateSpec | undefined>>(handle: H, options?: O & ManyOptions<H, PopulateSpec | undefined>): Promise<Array<LocalizedDoc<PopulatedDocument<DocumentFromHandle<H>, PopulateFromOptions<O>>>>>;
|
|
8
|
+
export declare function paginate<const H extends ContentCollectionTarget, O extends PaginationOptions<H, PopulateSpec | undefined>>(handle: H, options: O): Promise<PaginationResult<PopulatedDocument<DocumentFromHandle<H>, PopulateFromOptions<O>>>>;
|
|
9
|
+
export declare function backlinks<const Target extends ContentCollectionTarget, const Source extends BacklinkSource | ReadonlyArray<BacklinkSource>, P extends PopulateSpec | undefined = undefined>(handle: Target, options: BacklinksOptions<Target, Source, P>): Promise<BacklinksResult<Source, P>>;
|
|
10
|
+
export declare function variants<H extends ContentCollectionTarget>(handle: H, options: VariantsOptions<H>): Promise<Array<ContentVariant<H extends {
|
|
12
11
|
__schema: {
|
|
13
12
|
_output: infer O;
|
|
14
13
|
};
|
|
15
14
|
} ? O & ParsedContent : ParsedContent>>>;
|
|
16
|
-
export declare function tree<H extends
|
|
15
|
+
export declare function tree<H extends ContentCollectionTarget, Fields extends ReadonlyArray<string> | undefined = undefined>(handle: H, options?: Omit<TreeOptions<H>, 'fields'> & {
|
|
17
16
|
fields?: Fields;
|
|
18
17
|
}): Promise<ContentTreeItem<H extends {
|
|
19
18
|
__schema: {
|
|
20
19
|
_output: infer O;
|
|
21
20
|
};
|
|
22
21
|
} ? O & ParsedContent : ParsedContent, Fields>[]>;
|
|
23
|
-
export declare function neighbors<H extends
|
|
22
|
+
export declare function neighbors<H extends ContentCollectionTarget>(handle: H, options: NeighborsOptions<H>): Promise<NeighborsResult<H extends {
|
|
24
23
|
__schema: {
|
|
25
24
|
_output: infer O;
|
|
26
25
|
};
|
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
import type { ComputedRef, Ref } from 'vue';
|
|
2
2
|
import type { MaybeRefOrGetter } from '#imports';
|
|
3
3
|
import { ref } from '#imports';
|
|
4
|
-
import type {
|
|
4
|
+
import type { ContentCollectionHandle } from '../../../types/config';
|
|
5
5
|
import type { ContentNavigationItem } from '../../../types/content';
|
|
6
|
-
import type { ContentSearchSection } from '../../../types/query';
|
|
6
|
+
import type { ContentCollectionMap, ContentCollectionStringName, ContentSearchSection } from '../../../types/query';
|
|
7
7
|
import type { ContentSearchResult } from '../../../types/search';
|
|
8
8
|
export interface UseContentSearchResultsOptions {
|
|
9
9
|
locale?: MaybeRefOrGetter<string | undefined>;
|
|
@@ -52,7 +52,7 @@ export interface UseContentSearchDataResult {
|
|
|
52
52
|
navigation: ComputedRef<ContentNavigationItem[]>;
|
|
53
53
|
searchTerm: ReturnType<typeof ref<string>>;
|
|
54
54
|
}
|
|
55
|
-
export declare function useContentSearchData<K extends keyof ContentCollectionMap & string>(collection: K, options?: UseContentSearchDataOptions): Promise<UseContentSearchDataResult>;
|
|
56
|
-
export declare function useContentSearchData(collection:
|
|
55
|
+
export declare function useContentSearchData<K extends keyof ContentCollectionMap & string>(collection: ContentCollectionHandle<K> | K, options?: UseContentSearchDataOptions): Promise<UseContentSearchDataResult>;
|
|
56
|
+
export declare function useContentSearchData(collection: ContentCollectionStringName | ContentCollectionHandle, options?: UseContentSearchDataOptions): Promise<UseContentSearchDataResult>;
|
|
57
57
|
export declare const useContentSearchResults: (search: MaybeRefOrGetter<string>, options?: UseContentSearchResultsOptions) => Promise<UseContentSearchResultsResult>;
|
|
58
58
|
export declare const useContentSearch: (options?: UseContentSearchOptions) => Promise<UseContentSearchResult>;
|
|
@@ -13,7 +13,7 @@ const defaultSearchConfig = {
|
|
|
13
13
|
engine: "minisearch",
|
|
14
14
|
minisearch: {
|
|
15
15
|
fields: ["title", "content", "headings"],
|
|
16
|
-
storeFields: ["path", "title", "excerpt", "anchor", "locale"],
|
|
16
|
+
storeFields: ["path", "title", "excerpt", "anchor", "locale", "collection"],
|
|
17
17
|
boost: {
|
|
18
18
|
title: 4,
|
|
19
19
|
headings: 2,
|
|
@@ -95,14 +95,16 @@ const loadSearchSections = async (collection, options) => {
|
|
|
95
95
|
}
|
|
96
96
|
});
|
|
97
97
|
};
|
|
98
|
+
const collectionName = (collection) => typeof collection === "string" ? collection : collection.name;
|
|
98
99
|
export async function useContentSearchData(collection, options = {}) {
|
|
100
|
+
const name = collectionName(collection);
|
|
99
101
|
const searchTerm = ref("");
|
|
100
102
|
const locale = computed(() => toValue(options.locale));
|
|
101
|
-
const payload = await useAsyncData(computed(() => `content-search-data:${
|
|
103
|
+
const payload = await useAsyncData(computed(() => `content-search-data:${name}:${locale.value || "default"}`), async () => {
|
|
102
104
|
const locale2 = toValue(options.locale);
|
|
103
105
|
const [navigation, files2] = await Promise.all([
|
|
104
|
-
tree(
|
|
105
|
-
loadSearchSections(
|
|
106
|
+
tree(name, locale2 ? { locale: locale2 } : {}),
|
|
107
|
+
loadSearchSections(name, options)
|
|
106
108
|
]);
|
|
107
109
|
return {
|
|
108
110
|
files: files2,
|
|
@@ -239,7 +241,10 @@ const useCmsSearch = async (search, apiBaseURL, options) => {
|
|
|
239
241
|
});
|
|
240
242
|
const { data, pending, error } = await useFetch(requestUrl);
|
|
241
243
|
return {
|
|
242
|
-
results: computed(() => data.value || [])
|
|
244
|
+
results: computed(() => (data.value || []).map((result) => ({
|
|
245
|
+
...result,
|
|
246
|
+
collection: typeof result.collection === "string" ? result.collection : ""
|
|
247
|
+
}))),
|
|
243
248
|
pending: computed(() => pending.value),
|
|
244
249
|
error: computed(() => error.value)
|
|
245
250
|
};
|
|
@@ -292,6 +297,7 @@ const usePagefindSearch = (search, pagefindUrl, options) => {
|
|
|
292
297
|
const [path = "", anchor] = String(data?.url || "").split("#");
|
|
293
298
|
return {
|
|
294
299
|
path,
|
|
300
|
+
collection: typeof data?.meta?.collection === "string" ? data.meta.collection : "",
|
|
295
301
|
title: data?.meta?.title || path,
|
|
296
302
|
excerpt: data?.excerpt || "",
|
|
297
303
|
score: result.score,
|
|
@@ -1,7 +1,6 @@
|
|
|
1
1
|
import type { ComputedRef, MaybeRefOrGetter } from 'vue';
|
|
2
2
|
import { createError } from '#imports';
|
|
3
|
-
import type {
|
|
4
|
-
import type { BacklinksOptions, BacklinksResult, BacklinkSource, ContentVariant, ContentTreeItem, ContentResolvedMeta, DocumentFromHandle, LocalePathEntry, LocaleFallback, ManyOptions, LocalizedDoc, NeighborsOptions, NeighborsResult, OneOptions, PopulateSpec, PopulateFromOptions, PopulatedDocument, PaginationOptions, PaginationResult, ResolveOneOptions, ResolveOneResult, SortSpec, TreeOptions, VariantsOptions } from '../../../types/query';
|
|
3
|
+
import type { BacklinksOptions, BacklinksResult, BacklinkSource, ContentCollectionTarget, ContentVariant, ContentTreeItem, ContentResolvedMeta, DocumentFromHandle, LocalePathEntry, LocaleFallback, ManyOptions, LocalizedDoc, NeighborsOptions, NeighborsResult, OneOptions, PopulateSpec, PopulateFromOptions, PopulatedDocument, PaginationOptions, PaginationResult, ResolveOneOptions, ResolveOneResult, SortSpec, TreeOptions, VariantsOptions } from '../../../types/query';
|
|
5
4
|
type ReactiveLeaf<T> = T | MaybeRefOrGetter<T>;
|
|
6
5
|
type ReactiveValue<V> = V extends ((...args: never[]) => unknown) | readonly unknown[] ? ReactiveLeaf<V> : V extends object | undefined ? ReactiveLeaf<V> | {
|
|
7
6
|
[P in keyof NonNullable<V>]?: ReactiveLeaf<NonNullable<V>[P]>;
|
|
@@ -22,7 +21,7 @@ interface UseContentOneReturn<T> {
|
|
|
22
21
|
* `options` changes. Caching follows Nuxt's standard `useAsyncData` semantics
|
|
23
22
|
* keyed by `${collection}:${stableHash(options)}`.
|
|
24
23
|
*/
|
|
25
|
-
export declare function useContentOne<const H extends
|
|
24
|
+
export declare function useContentOne<const H extends ContentCollectionTarget, O extends OneOptions<H, PopulateSpec | undefined>>(handle: H, options: Reactive<O>): Promise<UseContentOneReturn<PopulatedDocument<DocFromHandle<H>, PopulateFromOptions<O>>>>;
|
|
26
25
|
type ContentPageSurroundOptions<H> = boolean | Omit<NeighborsOptions<H>, 'by' | 'locale' | 'fallback'>;
|
|
27
26
|
type ContentPageOneOptions<H, P extends PopulateSpec | undefined> = Omit<OneOptions<H, P>, 'by' | 'locale' | 'fallback'> & {
|
|
28
27
|
/**
|
|
@@ -37,7 +36,7 @@ export type UseContentPageOptions<H = unknown, P extends PopulateSpec | undefine
|
|
|
37
36
|
/**
|
|
38
37
|
* Load previous/next items alongside the page.
|
|
39
38
|
*
|
|
40
|
-
* Pass `true` for the default
|
|
39
|
+
* Pass `true` for the default surround query, or an options object to choose
|
|
41
40
|
* fields. The page query remains one request unless this is enabled.
|
|
42
41
|
*/
|
|
43
42
|
surround?: ContentPageSurroundOptions<H>;
|
|
@@ -48,8 +47,11 @@ export type UseContentPageOptions<H = unknown, P extends PopulateSpec | undefine
|
|
|
48
47
|
notFound?: false | (() => Parameters<typeof createError>[0]);
|
|
49
48
|
};
|
|
50
49
|
interface UseContentPageReturn<T> {
|
|
50
|
+
data: ComputedRef<LocalizedDoc<T> | undefined>;
|
|
51
51
|
page: ComputedRef<LocalizedDoc<T> | undefined>;
|
|
52
|
-
|
|
52
|
+
previous: ComputedRef<ContentTreeItem<T> | null>;
|
|
53
|
+
next: ComputedRef<ContentTreeItem<T> | null>;
|
|
54
|
+
surround: ComputedRef<Array<ContentTreeItem<T>>>;
|
|
53
55
|
pending: ComputedRef<boolean>;
|
|
54
56
|
status: ComputedRef<string>;
|
|
55
57
|
error: ComputedRef<unknown>;
|
|
@@ -60,7 +62,7 @@ interface UseContentPageReturn<T> {
|
|
|
60
62
|
* components; the explicit `useContentOne` selector remains the lower-level
|
|
61
63
|
* primitive for custom reads.
|
|
62
64
|
*/
|
|
63
|
-
export declare function useContentPage<const H extends
|
|
65
|
+
export declare function useContentPage<const H extends ContentCollectionTarget, P extends PopulateSpec | undefined = undefined>(handle: H, options?: UseContentPageOptions<H, P>): Promise<UseContentPageReturn<PopulatedDocument<DocFromHandle<H>, P>>>;
|
|
64
66
|
interface UseContentResolveOneReturn<T> {
|
|
65
67
|
doc: ComputedRef<LocalizedDoc<T> | null>;
|
|
66
68
|
explain: ComputedRef<ResolveOneResult<T>['explain'] | null>;
|
|
@@ -71,7 +73,7 @@ interface UseContentResolveOneReturn<T> {
|
|
|
71
73
|
/**
|
|
72
74
|
* Reactively resolve a single document with a first-class diagnostic envelope.
|
|
73
75
|
*/
|
|
74
|
-
export declare function useContentResolveOne<const H extends
|
|
76
|
+
export declare function useContentResolveOne<const H extends ContentCollectionTarget, O extends ResolveOneOptions<H, PopulateSpec | undefined>>(handle: H, options: Reactive<O>): Promise<UseContentResolveOneReturn<PopulatedDocument<DocFromHandle<H>, PopulateFromOptions<O>>>>;
|
|
75
77
|
interface UseContentManyReturn<T> {
|
|
76
78
|
data: ComputedRef<Array<LocalizedDoc<T>>>;
|
|
77
79
|
status: ComputedRef<string>;
|
|
@@ -95,11 +97,11 @@ interface UseContentPaginationReturn<T> {
|
|
|
95
97
|
/**
|
|
96
98
|
* Reactively resolve a list of documents.
|
|
97
99
|
*/
|
|
98
|
-
export declare function useContentMany<const H extends
|
|
100
|
+
export declare function useContentMany<const H extends ContentCollectionTarget, O extends ManyOptions<H, PopulateSpec | undefined>>(handle: H, options?: Reactive<O>): Promise<UseContentManyReturn<PopulatedDocument<DocFromHandle<H>, PopulateFromOptions<O>>>>;
|
|
99
101
|
/**
|
|
100
102
|
* Reactively resolve one page of documents plus total/page navigation metadata.
|
|
101
103
|
*/
|
|
102
|
-
export declare function useContentPagination<const H extends
|
|
104
|
+
export declare function useContentPagination<const H extends ContentCollectionTarget, O extends PaginationOptions<H, PopulateSpec | undefined>>(handle: H, options: Reactive<O>): Promise<UseContentPaginationReturn<PopulatedDocument<DocFromHandle<H>, PopulateFromOptions<O>>>>;
|
|
103
105
|
interface UseContentBacklinksReturn<T> {
|
|
104
106
|
data: ComputedRef<T[]>;
|
|
105
107
|
status: ComputedRef<string>;
|
|
@@ -109,7 +111,7 @@ interface UseContentBacklinksReturn<T> {
|
|
|
109
111
|
/**
|
|
110
112
|
* Reactively resolve source documents that reference a target document.
|
|
111
113
|
*/
|
|
112
|
-
export declare function useContentBacklinks<const Target extends
|
|
114
|
+
export declare function useContentBacklinks<const Target extends ContentCollectionTarget, const Source extends BacklinkSource | ReadonlyArray<BacklinkSource>, P extends PopulateSpec | undefined = undefined>(handle: Target, options: Reactive<BacklinksOptions<Target, Source, P>> & {
|
|
113
115
|
from: ReactiveValue<Source>;
|
|
114
116
|
}): Promise<UseContentBacklinksReturn<BacklinksResult<Source, P>[number]>>;
|
|
115
117
|
interface UseContentVariantsReturn<T> {
|
|
@@ -123,17 +125,35 @@ interface UseContentVariantsReturn<T> {
|
|
|
123
125
|
* locale switchers. Returns one entry per configured locale, with a
|
|
124
126
|
* `translated` flag.
|
|
125
127
|
*/
|
|
126
|
-
export declare function useContentVariants<H extends
|
|
128
|
+
export declare function useContentVariants<H extends ContentCollectionTarget>(handle: H, options: Reactive<VariantsOptions<H>>): Promise<UseContentVariantsReturn<DocFromHandle<H>>>;
|
|
127
129
|
interface UseContentTreeReturn<T> {
|
|
128
130
|
data: ComputedRef<T[]>;
|
|
129
131
|
status: ComputedRef<string>;
|
|
130
132
|
error: ComputedRef<unknown>;
|
|
131
133
|
refresh: () => Promise<void>;
|
|
132
134
|
}
|
|
135
|
+
export type ContentNavigationNode<T> = Omit<ContentTreeItem<T>, 'children'> & {
|
|
136
|
+
id: string;
|
|
137
|
+
path: string;
|
|
138
|
+
title: string;
|
|
139
|
+
children: Array<ContentNavigationNode<T>>;
|
|
140
|
+
};
|
|
141
|
+
interface UseContentNavigationReturn<T> {
|
|
142
|
+
data: ComputedRef<Array<ContentNavigationNode<T>>>;
|
|
143
|
+
firstPage: ComputedRef<ContentNavigationNode<T> | null>;
|
|
144
|
+
paths: ComputedRef<Set<string>>;
|
|
145
|
+
status: ComputedRef<string>;
|
|
146
|
+
error: ComputedRef<unknown>;
|
|
147
|
+
refresh: () => Promise<void>;
|
|
148
|
+
}
|
|
133
149
|
/**
|
|
134
150
|
* Reactively resolve the navigation tree for a collection.
|
|
135
151
|
*/
|
|
136
|
-
export declare function useContentTree<H extends
|
|
152
|
+
export declare function useContentTree<H extends ContentCollectionTarget>(handle: H, options?: Reactive<TreeOptions<H>>): Promise<UseContentTreeReturn<ContentTreeItem<DocFromHandle<H>>>>;
|
|
153
|
+
/**
|
|
154
|
+
* Reactively resolve normalized navigation for sidebars and active-state UI.
|
|
155
|
+
*/
|
|
156
|
+
export declare function useContentNavigation<H extends ContentCollectionTarget>(handle: H, options?: Reactive<TreeOptions<H>>): Promise<UseContentNavigationReturn<DocFromHandle<H>>>;
|
|
137
157
|
interface UseContentNeighborsReturn<T> {
|
|
138
158
|
data: ComputedRef<NeighborsResult<T>>;
|
|
139
159
|
pending: ComputedRef<boolean>;
|
|
@@ -144,7 +164,7 @@ interface UseContentNeighborsReturn<T> {
|
|
|
144
164
|
/**
|
|
145
165
|
* Reactively resolve previous/next surroundings for a document.
|
|
146
166
|
*/
|
|
147
|
-
export declare function useContentNeighbors<H extends
|
|
167
|
+
export declare function useContentNeighbors<H extends ContentCollectionTarget>(handle: H, options: Reactive<NeighborsOptions<H>>): Promise<UseContentNeighborsReturn<DocFromHandle<H>>>;
|
|
148
168
|
interface UseContentLocaleSwitchReturn<T> {
|
|
149
169
|
/**
|
|
150
170
|
* Per-locale path map of the resolved document, keyed by locale code.
|
|
@@ -184,5 +204,5 @@ interface UseContentLocaleSwitchReturn<T> {
|
|
|
184
204
|
* </script>
|
|
185
205
|
* ```
|
|
186
206
|
*/
|
|
187
|
-
export declare function useContentLocaleSwitch<const H extends
|
|
188
|
-
export type { ManyOptions, OneOptions, BacklinksOptions, BacklinksResult, LocalizedDoc, ContentResolvedMeta, DocumentFromHandle, NeighborsOptions, NeighborsResult, PaginationOptions, PaginationResult, PopulateSpec, PopulateFromOptions, PopulatedDocument, ResolveOneOptions, ResolveOneResult, SortSpec, TreeOptions, VariantsOptions, ContentVariant };
|
|
207
|
+
export declare function useContentLocaleSwitch<const H extends ContentCollectionTarget, O extends OneOptions<H, PopulateSpec | undefined>>(handle: H, options: Reactive<O>): Promise<UseContentLocaleSwitchReturn<PopulatedDocument<DocFromHandle<H>, PopulateFromOptions<O>>>>;
|
|
208
|
+
export type { ManyOptions, OneOptions, BacklinksOptions, BacklinksResult, LocalizedDoc, ContentResolvedMeta, ContentCollectionTarget, DocumentFromHandle, NeighborsOptions, NeighborsResult, PaginationOptions, PaginationResult, PopulateSpec, PopulateFromOptions, PopulatedDocument, ResolveOneOptions, ResolveOneResult, SortSpec, TreeOptions, VariantsOptions, ContentVariant };
|
|
@@ -2,6 +2,7 @@ import { computed, shallowRef, toValue, watch } from "vue";
|
|
|
2
2
|
import { hash } from "ohash";
|
|
3
3
|
import { createError, useAsyncData, useRoute } from "#imports";
|
|
4
4
|
import { createClientContentQueryContext } from "./query-api.js";
|
|
5
|
+
import { resolveCollectionI18n } from "../../../features/localization/path";
|
|
5
6
|
import {
|
|
6
7
|
backlinks as backlinksWithContext,
|
|
7
8
|
many as manyWithContext,
|
|
@@ -12,7 +13,9 @@ import {
|
|
|
12
13
|
tree as treeWithContext,
|
|
13
14
|
variants as variantsWithContext
|
|
14
15
|
} from "../../query/unified.js";
|
|
16
|
+
import { resolveActiveLocale } from "./locale.js";
|
|
15
17
|
import { useContentRoute } from "./route.js";
|
|
18
|
+
import { getContentRuntime } from "./runtime.js";
|
|
16
19
|
const unwrapDeep = (value, seen = /* @__PURE__ */ new WeakSet()) => {
|
|
17
20
|
if (value === null || value === void 0) return value;
|
|
18
21
|
if (typeof value === "function") return unwrapDeep(toValue(value), seen);
|
|
@@ -35,6 +38,7 @@ const resolveOptions = (reactive) => {
|
|
|
35
38
|
return unwrapDeep(reactive);
|
|
36
39
|
};
|
|
37
40
|
const stableKey = (prefix, name, options) => `${prefix}:${name}:${hash(options)}`;
|
|
41
|
+
const contentCollectionName = (handle) => typeof handle === "string" ? handle : handle.name;
|
|
38
42
|
export async function useContentOne(handle, options) {
|
|
39
43
|
const resolved = computed(() => resolveOptions(options));
|
|
40
44
|
const key = computed(() => stableKey("content-one", typeof handle === "string" ? handle : handle.name, resolved.value));
|
|
@@ -61,7 +65,7 @@ const normalizePageNotFound = (notFound) => {
|
|
|
61
65
|
if (notFound === false) return void 0;
|
|
62
66
|
return createError(notFound ? notFound() : defaultPageNotFound());
|
|
63
67
|
};
|
|
64
|
-
const
|
|
68
|
+
const normalizePageSurround = (surround) => {
|
|
65
69
|
if (!surround) return void 0;
|
|
66
70
|
return surround === true ? {} : surround;
|
|
67
71
|
};
|
|
@@ -88,6 +92,18 @@ export async function useContentPage(handle, options = {}) {
|
|
|
88
92
|
const isBrowser = import.meta.client && typeof window !== "undefined";
|
|
89
93
|
const { notFound, surround, ...oneOptions } = options;
|
|
90
94
|
const routeSelector = { route: () => normalizeRoutePath(route.path) };
|
|
95
|
+
const runtime = getContentRuntime();
|
|
96
|
+
const activeLocale = computed(() => {
|
|
97
|
+
const explicitLocale = toValue(oneOptions.locale);
|
|
98
|
+
if (explicitLocale) {
|
|
99
|
+
return explicitLocale;
|
|
100
|
+
}
|
|
101
|
+
const { locales, defaultLocale } = resolveCollectionI18n(contentCollectionName(handle), runtime);
|
|
102
|
+
if (!locales.length && !defaultLocale) {
|
|
103
|
+
return void 0;
|
|
104
|
+
}
|
|
105
|
+
return resolveActiveLocale(locales, defaultLocale);
|
|
106
|
+
});
|
|
91
107
|
const rawPage = shallowRef(null);
|
|
92
108
|
const resolvingRoute = shallowRef(false);
|
|
93
109
|
const sawRouteQueryPending = shallowRef(false);
|
|
@@ -98,12 +114,13 @@ export async function useContentPage(handle, options = {}) {
|
|
|
98
114
|
useContentRoute(page);
|
|
99
115
|
const pageResultPromise = useContentOne(handle, {
|
|
100
116
|
...oneOptions,
|
|
117
|
+
locale: () => activeLocale.value,
|
|
101
118
|
by: routeSelector
|
|
102
119
|
});
|
|
103
|
-
const surroundOptions =
|
|
104
|
-
const
|
|
120
|
+
const surroundOptions = normalizePageSurround(surround);
|
|
121
|
+
const surroundResultPromise = surroundOptions ? useContentNeighbors(handle, {
|
|
105
122
|
...surroundOptions,
|
|
106
|
-
|
|
123
|
+
locale: () => activeLocale.value,
|
|
107
124
|
..."fallback" in oneOptions ? { fallback: oneOptions.fallback } : {},
|
|
108
125
|
by: routeSelector
|
|
109
126
|
}) : void 0;
|
|
@@ -135,30 +152,40 @@ export async function useContentPage(handle, options = {}) {
|
|
|
135
152
|
}
|
|
136
153
|
}
|
|
137
154
|
});
|
|
138
|
-
const
|
|
139
|
-
const pending = computed(() => pageResult.pending.value || Boolean(
|
|
155
|
+
const surroundResult = surroundResultPromise ? await surroundResultPromise : void 0;
|
|
156
|
+
const pending = computed(() => pageResult.pending.value || Boolean(surroundResult?.pending.value));
|
|
140
157
|
const pageError = computed(() => {
|
|
141
158
|
if (pageResult.error.value) return pageResult.error.value;
|
|
142
|
-
if (
|
|
159
|
+
if (surroundResult?.error.value) return surroundResult.error.value;
|
|
143
160
|
if (pending.value || resolvingRoute.value || page.value || notFound === false) return void 0;
|
|
144
161
|
return normalizePageNotFound(notFound);
|
|
145
162
|
});
|
|
146
163
|
if (!isBrowser && pageError.value) {
|
|
147
164
|
throw pageError.value;
|
|
148
165
|
}
|
|
166
|
+
const previous = computed(() => {
|
|
167
|
+
if (!page.value || !surroundResult) return null;
|
|
168
|
+
return surroundResult.data.value.prev;
|
|
169
|
+
});
|
|
170
|
+
const next = computed(() => {
|
|
171
|
+
if (!page.value || !surroundResult) return null;
|
|
172
|
+
return surroundResult.data.value.next;
|
|
173
|
+
});
|
|
174
|
+
const pageSurround = computed(() => {
|
|
175
|
+
return [previous.value, next.value].filter((item) => Boolean(item));
|
|
176
|
+
});
|
|
149
177
|
return {
|
|
178
|
+
data: page,
|
|
150
179
|
page,
|
|
151
|
-
surround:
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
return [neighbors.prev, neighbors.next];
|
|
155
|
-
}),
|
|
180
|
+
surround: pageSurround,
|
|
181
|
+
previous,
|
|
182
|
+
next,
|
|
156
183
|
pending,
|
|
157
184
|
status: computed(() => pageResult.status.value),
|
|
158
185
|
error: pageError,
|
|
159
186
|
refresh: async () => {
|
|
160
187
|
await pageResult.refresh();
|
|
161
|
-
if (
|
|
188
|
+
if (surroundResult) await surroundResult.refresh();
|
|
162
189
|
}
|
|
163
190
|
};
|
|
164
191
|
}
|
|
@@ -263,6 +290,37 @@ export async function useContentVariants(handle, options) {
|
|
|
263
290
|
refresh: () => asyncData.refresh()
|
|
264
291
|
};
|
|
265
292
|
}
|
|
293
|
+
const navigationNodeId = (item) => {
|
|
294
|
+
const id = item.id ?? item._id ?? item._canonicalKey ?? item.path ?? item._path ?? item.title;
|
|
295
|
+
return typeof id === "string" && id.length ? id : item.title;
|
|
296
|
+
};
|
|
297
|
+
const normalizeContentNavigation = (items = []) => {
|
|
298
|
+
return items.map((item) => {
|
|
299
|
+
const source = item;
|
|
300
|
+
return {
|
|
301
|
+
...item,
|
|
302
|
+
id: navigationNodeId(source),
|
|
303
|
+
path: item.path,
|
|
304
|
+
title: item.title,
|
|
305
|
+
children: normalizeContentNavigation(item.children || [])
|
|
306
|
+
};
|
|
307
|
+
});
|
|
308
|
+
};
|
|
309
|
+
const findFirstContentNavigationPage = (items) => {
|
|
310
|
+
for (const item of items) {
|
|
311
|
+
if (item.path) return item;
|
|
312
|
+
const child = findFirstContentNavigationPage(item.children);
|
|
313
|
+
if (child) return child;
|
|
314
|
+
}
|
|
315
|
+
return null;
|
|
316
|
+
};
|
|
317
|
+
const collectContentNavigationPaths = (items, paths = /* @__PURE__ */ new Set()) => {
|
|
318
|
+
for (const item of items) {
|
|
319
|
+
if (item.path) paths.add(item.path);
|
|
320
|
+
collectContentNavigationPaths(item.children, paths);
|
|
321
|
+
}
|
|
322
|
+
return paths;
|
|
323
|
+
};
|
|
266
324
|
export async function useContentTree(handle, options = {}) {
|
|
267
325
|
const resolved = computed(() => resolveOptions(options));
|
|
268
326
|
const key = computed(() => stableKey("content-tree", typeof handle === "string" ? handle : handle.name, resolved.value));
|
|
@@ -279,6 +337,18 @@ export async function useContentTree(handle, options = {}) {
|
|
|
279
337
|
refresh: () => asyncData.refresh()
|
|
280
338
|
};
|
|
281
339
|
}
|
|
340
|
+
export async function useContentNavigation(handle, options = {}) {
|
|
341
|
+
const tree = await useContentTree(handle, options);
|
|
342
|
+
const data = computed(() => normalizeContentNavigation(tree.data.value));
|
|
343
|
+
return {
|
|
344
|
+
data,
|
|
345
|
+
firstPage: computed(() => findFirstContentNavigationPage(data.value)),
|
|
346
|
+
paths: computed(() => collectContentNavigationPaths(data.value)),
|
|
347
|
+
status: tree.status,
|
|
348
|
+
error: tree.error,
|
|
349
|
+
refresh: tree.refresh
|
|
350
|
+
};
|
|
351
|
+
}
|
|
282
352
|
export async function useContentNeighbors(handle, options) {
|
|
283
353
|
const resolved = computed(() => resolveOptions(options));
|
|
284
354
|
const key = computed(() => stableKey("content-neighbors", typeof handle === "string" ? handle : handle.name, resolved.value));
|