@umami/shiso 0.61.0 → 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +27 -0
- package/bin/shiso.mjs +0 -0
- package/docs.schema.json +81 -20
- package/package.json +3 -2
- package/scripts/check-package.mjs +36 -0
- package/scripts/generate-search-index.mjs +79 -33
- package/scripts/lib/mdast.mjs +23 -0
- package/scripts/lib/slug.mjs +21 -0
- package/scripts/prerender.mjs +24 -17
- package/src/App.tsx +4 -5
- package/src/components/DocContent.tsx +7 -2
- package/src/components/Docs.tsx +13 -5
- package/src/components/Footer.tsx +6 -9
- package/src/components/Header.tsx +13 -3
- package/src/components/LanguageSwitcher.tsx +59 -0
- package/src/components/Layout.tsx +8 -0
- package/src/components/Search.tsx +12 -3
- package/src/components/VersionSwitcher.tsx +60 -0
- package/src/components/docs/PropertiesTable.tsx +7 -7
- package/src/entry-server.tsx +20 -8
- package/src/lib/docs-config.ts +397 -93
- package/src/lib/head.ts +15 -4
- package/src/lib/locale.ts +39 -0
- package/src/lib/search/provider.ts +7 -2
- package/src/lib/search/providers/local.ts +3 -3
- package/src/lib/search.ts +28 -0
- package/src/lib/site-config.ts +35 -22
- package/src/lib/types.ts +39 -2
- package/types/search.d.ts +11 -1
|
@@ -1,7 +1,12 @@
|
|
|
1
|
-
import type { SearchResult } from '@/lib/search';
|
|
1
|
+
import type { SearchContext, SearchResult } from '@/lib/search';
|
|
2
2
|
|
|
3
3
|
export interface SearchProvider {
|
|
4
|
-
|
|
4
|
+
/**
|
|
5
|
+
* `context` describes the active version/language scope. Providers may
|
|
6
|
+
* ignore it; the built-in local provider uses it to keep results inside
|
|
7
|
+
* the scope being browsed.
|
|
8
|
+
*/
|
|
9
|
+
search(query: string, limit?: number, context?: SearchContext): Promise<SearchResult[]>;
|
|
5
10
|
}
|
|
6
11
|
|
|
7
12
|
export type SearchProviderFactory = (
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import type { SearchRecord } from '@/lib/search';
|
|
2
|
-
import { searchIndex } from '@/lib/search';
|
|
2
|
+
import { filterRecordsByScope, searchIndex } from '@/lib/search';
|
|
3
3
|
import type { SearchProvider } from '@/lib/search/provider';
|
|
4
4
|
|
|
5
5
|
/** Built-in provider backed by the section index generated during the build. */
|
|
@@ -7,9 +7,9 @@ export function createLocalSearchProvider(_options: Record<string, unknown> = {}
|
|
|
7
7
|
let records: Promise<SearchRecord[]> | null = null;
|
|
8
8
|
|
|
9
9
|
return {
|
|
10
|
-
async search(query, limit) {
|
|
10
|
+
async search(query, limit, context) {
|
|
11
11
|
records ||= import('@/lib/search-index.generated').then(module => module.SEARCH_INDEX);
|
|
12
|
-
return searchIndex(await records, query, limit);
|
|
12
|
+
return searchIndex(filterRecordsByScope(await records, context), query, limit);
|
|
13
13
|
},
|
|
14
14
|
};
|
|
15
15
|
}
|
package/src/lib/search.ts
CHANGED
|
@@ -18,6 +18,34 @@ export interface SearchRecord {
|
|
|
18
18
|
id?: string;
|
|
19
19
|
/** Plain text of the section. */
|
|
20
20
|
text: string;
|
|
21
|
+
/** Navigation scope id, absent for single-scope sites. */
|
|
22
|
+
scopeId?: string;
|
|
23
|
+
/** Language label of the owning scope, when the site defines languages. */
|
|
24
|
+
language?: string;
|
|
25
|
+
/** Version label of the owning scope, when the site defines versions. */
|
|
26
|
+
version?: string;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
/** Where a search originates, so providers can stay inside the active scope. */
|
|
30
|
+
export interface SearchContext {
|
|
31
|
+
scopeId?: string;
|
|
32
|
+
language?: string;
|
|
33
|
+
version?: string;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
/**
|
|
37
|
+
* Restricts records to the active scope. Records without a scope id (from
|
|
38
|
+
* single-scope indexes or older caches) always pass.
|
|
39
|
+
*/
|
|
40
|
+
export function filterRecordsByScope(
|
|
41
|
+
records: SearchRecord[],
|
|
42
|
+
context?: SearchContext,
|
|
43
|
+
): SearchRecord[] {
|
|
44
|
+
if (!context?.scopeId) {
|
|
45
|
+
return records;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
return records.filter(record => !record.scopeId || record.scopeId === context.scopeId);
|
|
21
49
|
}
|
|
22
50
|
|
|
23
51
|
export interface SearchResult {
|
package/src/lib/site-config.ts
CHANGED
|
@@ -1,12 +1,21 @@
|
|
|
1
1
|
import rawConfig from 'virtual:shiso-docs-config';
|
|
2
2
|
import { resolveDocFile } from '@/lib/content';
|
|
3
|
-
import {
|
|
4
|
-
|
|
3
|
+
import {
|
|
4
|
+
assertDocsConfig,
|
|
5
|
+
getDefaultScope,
|
|
6
|
+
getScopeForPage,
|
|
7
|
+
getPageByPathname as getSitePageByPathname,
|
|
8
|
+
normalizeDocsSite,
|
|
9
|
+
} from '@/lib/docs-config';
|
|
10
|
+
import { getTextDirection, resolveLocale } from '@/lib/locale';
|
|
11
|
+
import { stripBase } from '@/lib/paths';
|
|
5
12
|
import { resolveSiteModel } from '@/lib/site-model';
|
|
6
13
|
import type {
|
|
7
14
|
DocsConfig,
|
|
15
|
+
DocsScope,
|
|
8
16
|
NormalizedDocsConfig,
|
|
9
17
|
NormalizedDocsPage,
|
|
18
|
+
NormalizedDocsSite,
|
|
10
19
|
RedirectRule,
|
|
11
20
|
SeoConfig,
|
|
12
21
|
} from '@/lib/types';
|
|
@@ -15,10 +24,29 @@ assertDocsConfig(rawConfig, 'docs.json');
|
|
|
15
24
|
|
|
16
25
|
export const siteConfig: DocsConfig = rawConfig;
|
|
17
26
|
|
|
18
|
-
|
|
27
|
+
/** The complete normalized site: every version/language scope. */
|
|
28
|
+
export const docsSite: NormalizedDocsSite = normalizeDocsSite(siteConfig, resolveDocFile);
|
|
29
|
+
|
|
30
|
+
/** The default scope's navigation, used where a single navigation is expected. */
|
|
31
|
+
export const docsConfig: NormalizedDocsConfig = getDefaultScope(docsSite).docs;
|
|
32
|
+
|
|
33
|
+
/** Landing page of the default scope: the site-wide "docs home" URL. */
|
|
34
|
+
export const docsHomeUrl = getDefaultScope(docsSite).firstPageUrl;
|
|
19
35
|
|
|
20
36
|
export const siteModel = resolveSiteModel(siteConfig, docsConfig);
|
|
21
37
|
|
|
38
|
+
/** Scope that owns the current pathname; the default scope for unknown paths. */
|
|
39
|
+
export function getScopeByPathname(pathname: string): DocsScope {
|
|
40
|
+
const page = getPageByPathname(pathname);
|
|
41
|
+
return page ? getScopeForPage(docsSite, page) : getDefaultScope(docsSite);
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
/** Document language and direction for a pathname, from its scope's language. */
|
|
45
|
+
export function getLocaleByPathname(pathname: string): { lang: string; dir: 'ltr' | 'rtl' } {
|
|
46
|
+
const lang = resolveLocale(getScopeByPathname(pathname).language, siteModel.locale);
|
|
47
|
+
return { lang, dir: getTextDirection(lang) };
|
|
48
|
+
}
|
|
49
|
+
|
|
22
50
|
export const siteName = siteModel.name;
|
|
23
51
|
|
|
24
52
|
/** Trailing-slash-insensitive route key for redirect matching. */
|
|
@@ -28,8 +56,8 @@ function toRouteKey(routePath: string): string {
|
|
|
28
56
|
}
|
|
29
57
|
|
|
30
58
|
/**
|
|
31
|
-
* Redirect rules with exact-match sources. Wildcard patterns are
|
|
32
|
-
*
|
|
59
|
+
* Redirect rules with exact-match sources. Wildcard patterns are rejected by
|
|
60
|
+
* the schema; this guard covers configs that bypassed validation.
|
|
33
61
|
*/
|
|
34
62
|
export function getRedirects(): RedirectRule[] {
|
|
35
63
|
return (siteConfig.redirects || []).filter(rule => {
|
|
@@ -40,7 +68,7 @@ export function getRedirects(): RedirectRule[] {
|
|
|
40
68
|
if (/[:*]/.test(rule.source)) {
|
|
41
69
|
console.warn(
|
|
42
70
|
`[shiso] Redirect source "${rule.source}" uses a wildcard pattern, which is not ` +
|
|
43
|
-
'
|
|
71
|
+
'supported — it will be skipped. Use an exact source path.',
|
|
44
72
|
);
|
|
45
73
|
return false;
|
|
46
74
|
}
|
|
@@ -76,23 +104,8 @@ export function showTimestamp(frontmatterValue: unknown): boolean {
|
|
|
76
104
|
return siteModel.showTimestamp;
|
|
77
105
|
}
|
|
78
106
|
|
|
79
|
-
export function normalizeParamSlug(slug: string): string {
|
|
80
|
-
const cleaned = slug.replace(/^\/+|\/+$/g, '');
|
|
81
|
-
|
|
82
|
-
if (!cleaned) {
|
|
83
|
-
return 'index';
|
|
84
|
-
}
|
|
85
|
-
|
|
86
|
-
if (cleaned === 'index') {
|
|
87
|
-
return cleaned;
|
|
88
|
-
}
|
|
89
|
-
|
|
90
|
-
return cleaned.replace(/\/index$/, '') || 'index';
|
|
91
|
-
}
|
|
92
|
-
|
|
93
107
|
export function getPageByPathname(pathname: string): NormalizedDocsPage | null {
|
|
94
|
-
|
|
95
|
-
return docsConfig.pageByLookupSlug[slug] || null;
|
|
108
|
+
return getSitePageByPathname(docsSite, stripBase(pathname));
|
|
96
109
|
}
|
|
97
110
|
|
|
98
111
|
export function getPageTitle(pageTitle?: string): string {
|
package/src/lib/types.ts
CHANGED
|
@@ -54,7 +54,7 @@ export interface TabItem {
|
|
|
54
54
|
|
|
55
55
|
export interface AnchorItem {
|
|
56
56
|
anchor: string;
|
|
57
|
-
href
|
|
57
|
+
href: string;
|
|
58
58
|
icon?: string;
|
|
59
59
|
hidden?: boolean;
|
|
60
60
|
target?: LinkTarget;
|
|
@@ -318,6 +318,12 @@ export interface NormalizedDocsPage {
|
|
|
318
318
|
tabId: string;
|
|
319
319
|
tabLabel: string;
|
|
320
320
|
order: number;
|
|
321
|
+
/** Id of the navigation scope (version/language) this page belongs to. */
|
|
322
|
+
scopeId: string;
|
|
323
|
+
/** Language label of the owning scope, when the site defines languages. */
|
|
324
|
+
language?: string;
|
|
325
|
+
/** Version label of the owning scope, when the site defines versions. */
|
|
326
|
+
version?: string;
|
|
321
327
|
/**
|
|
322
328
|
* Hidden pages are still routed and prerendered, so they remain reachable by
|
|
323
329
|
* URL, but are excluded from the sidebar, prev/next, the
|
|
@@ -377,10 +383,41 @@ export interface NormalizedDocsConfig {
|
|
|
377
383
|
}
|
|
378
384
|
|
|
379
385
|
export interface NormalizeOptions {
|
|
380
|
-
/** Route prefix for docs pages.
|
|
386
|
+
/** Route prefix for docs pages. Default "/docs"; "" serves docs at the root. */
|
|
381
387
|
docsPrefix?: string;
|
|
382
388
|
}
|
|
383
389
|
|
|
390
|
+
/**
|
|
391
|
+
* One navigation scope of a site: the ordinary navigation, a version, a
|
|
392
|
+
* language, or a version nested inside a language. Page references alone
|
|
393
|
+
* determine URLs; scopes never add their own URL prefixes.
|
|
394
|
+
*/
|
|
395
|
+
export interface DocsScope {
|
|
396
|
+
id: string;
|
|
397
|
+
/** Language label, when this scope belongs to a language. */
|
|
398
|
+
language?: string;
|
|
399
|
+
/** Version label, when this scope belongs to a version. */
|
|
400
|
+
version?: string;
|
|
401
|
+
/** Hidden scopes still build, but are omitted from switchers. */
|
|
402
|
+
hidden?: boolean;
|
|
403
|
+
isDefault: boolean;
|
|
404
|
+
/** Landing scope of its language: the language's default version. */
|
|
405
|
+
isLanguageDefault: boolean;
|
|
406
|
+
/** URL of the scope's first visible page — the landing page for switchers. */
|
|
407
|
+
firstPageUrl: string;
|
|
408
|
+
docs: NormalizedDocsConfig;
|
|
409
|
+
}
|
|
410
|
+
|
|
411
|
+
/** The complete normalized site: every scope, with global page lookups. */
|
|
412
|
+
export interface NormalizedDocsSite {
|
|
413
|
+
scopes: DocsScope[];
|
|
414
|
+
defaultScopeId: string;
|
|
415
|
+
/** Every routed page across all scopes, in scope order. */
|
|
416
|
+
pages: NormalizedDocsPage[];
|
|
417
|
+
/** Exact canonical URL to page, across all scopes. */
|
|
418
|
+
pageByUrl: Record<string, NormalizedDocsPage>;
|
|
419
|
+
}
|
|
420
|
+
|
|
384
421
|
export interface NormalizedLink extends ConfigLink {
|
|
385
422
|
target: LinkTarget;
|
|
386
423
|
}
|
package/types/search.d.ts
CHANGED
|
@@ -6,8 +6,18 @@ export interface SearchResult {
|
|
|
6
6
|
snippet?: string;
|
|
7
7
|
}
|
|
8
8
|
|
|
9
|
+
/**
|
|
10
|
+
* Where a search originates, so providers can keep results inside the
|
|
11
|
+
* version/language scope being browsed. Providers may ignore it.
|
|
12
|
+
*/
|
|
13
|
+
export interface SearchContext {
|
|
14
|
+
scopeId?: string;
|
|
15
|
+
language?: string;
|
|
16
|
+
version?: string;
|
|
17
|
+
}
|
|
18
|
+
|
|
9
19
|
export interface SearchProvider {
|
|
10
|
-
search(query: string, limit?: number): Promise<SearchResult[]>;
|
|
20
|
+
search(query: string, limit?: number, context?: SearchContext): Promise<SearchResult[]>;
|
|
11
21
|
}
|
|
12
22
|
|
|
13
23
|
export type SearchProviderFactory = (
|