@dominikcz/greg 0.9.27
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 +397 -0
- package/bin/greg.js +241 -0
- package/bin/init.js +351 -0
- package/bin/templates/docs/getting-started.md +47 -0
- package/bin/templates/docs/index.md +11 -0
- package/bin/templates/greg.config.js +39 -0
- package/bin/templates/greg.config.ts +38 -0
- package/bin/templates/index.html +16 -0
- package/bin/templates/src/App.svelte +5 -0
- package/bin/templates/src/app.css +20 -0
- package/bin/templates/src/main.js +9 -0
- package/bin/templates/svelte.config.js +1 -0
- package/bin/templates/tsconfig.json +21 -0
- package/bin/templates/vite.config.js +23 -0
- package/docs/__partials/markdown/examples/basic.md +4 -0
- package/docs/__partials/markdown/examples/diff.md +10 -0
- package/docs/__partials/markdown/examples/focus.md +5 -0
- package/docs/__partials/markdown/examples/language-title.md +3 -0
- package/docs/__partials/markdown/examples/line-highlighting.md +5 -0
- package/docs/__partials/markdown/examples/line-numbers.md +5 -0
- package/docs/__partials/note.md +4 -0
- package/docs/guide/__shared-warning.md +4 -0
- package/docs/guide/asset-handling.md +88 -0
- package/docs/guide/deploying.md +162 -0
- package/docs/guide/getting-started.md +334 -0
- package/docs/guide/index.md +23 -0
- package/docs/guide/localization.md +290 -0
- package/docs/guide/markdown/code.md +95 -0
- package/docs/guide/markdown/components-and-mermaid.md +43 -0
- package/docs/guide/markdown/containers.md +110 -0
- package/docs/guide/markdown/header-anchors.md +34 -0
- package/docs/guide/markdown/includes.md +84 -0
- package/docs/guide/markdown/index.md +20 -0
- package/docs/guide/markdown/inline-attributes.md +21 -0
- package/docs/guide/markdown/links-and-toc.md +64 -0
- package/docs/guide/markdown/math.md +54 -0
- package/docs/guide/markdown/syntax-highlighting.md +75 -0
- package/docs/guide/routing.md +150 -0
- package/docs/guide/using-svelte.md +88 -0
- package/docs/guide/versioning.md +281 -0
- package/docs/incompatibilities.md +48 -0
- package/docs/index.md +43 -0
- package/docs/reference/badge.md +100 -0
- package/docs/reference/carbon-ads.md +46 -0
- package/docs/reference/code-group.md +126 -0
- package/docs/reference/home-page.md +232 -0
- package/docs/reference/index.md +18 -0
- package/docs/reference/markdowndocs.md +275 -0
- package/docs/reference/outline.md +79 -0
- package/docs/reference/search.md +263 -0
- package/docs/reference/steps.md +200 -0
- package/docs/reference/team-page.md +189 -0
- package/docs/reference/theme.md +150 -0
- package/fakeDocsGenerator/generate_docs.js +310 -0
- package/package.json +92 -0
- package/scripts/build-versions.js +609 -0
- package/scripts/generate-static.js +79 -0
- package/scripts/render-markdown.js +420 -0
- package/src/lib/MarkdownDocs/AiChat.svelte +936 -0
- package/src/lib/MarkdownDocs/BackToTop.svelte +68 -0
- package/src/lib/MarkdownDocs/Breadcrumb.svelte +68 -0
- package/src/lib/MarkdownDocs/DocsNavigation.svelte +149 -0
- package/src/lib/MarkdownDocs/DocsSiteHeader.svelte +758 -0
- package/src/lib/MarkdownDocs/DocsVersionSwitcher.svelte +103 -0
- package/src/lib/MarkdownDocs/MarkdownDocs.svelte +2115 -0
- package/src/lib/MarkdownDocs/MarkdownRenderer.svelte +487 -0
- package/src/lib/MarkdownDocs/Outline.svelte +238 -0
- package/src/lib/MarkdownDocs/PrevNext.svelte +115 -0
- package/src/lib/MarkdownDocs/SearchModal.svelte +1241 -0
- package/src/lib/MarkdownDocs/TreeView.svelte +32 -0
- package/src/lib/MarkdownDocs/TreeViewItem.svelte +219 -0
- package/src/lib/MarkdownDocs/VersionOutdatedNotice.svelte +72 -0
- package/src/lib/MarkdownDocs/__tests__/codeDirectives.test.js +54 -0
- package/src/lib/MarkdownDocs/__tests__/common.test.js +41 -0
- package/src/lib/MarkdownDocs/__tests__/docsExamplesLint.test.js +77 -0
- package/src/lib/MarkdownDocs/__tests__/fixtures/docs/markdown/__partial-basic.md +3 -0
- package/src/lib/MarkdownDocs/__tests__/fixtures/docs/markdown/snippet.js +9 -0
- package/src/lib/MarkdownDocs/__tests__/fixtures/includes/part.md +11 -0
- package/src/lib/MarkdownDocs/__tests__/fixtures/includes/wrapper.md +5 -0
- package/src/lib/MarkdownDocs/__tests__/fixtures/snippets/sample.js +8 -0
- package/src/lib/MarkdownDocs/__tests__/fixtures/snippets/sample.md +5 -0
- package/src/lib/MarkdownDocs/__tests__/helpers.js +67 -0
- package/src/lib/MarkdownDocs/__tests__/localeUtils.test.js +204 -0
- package/src/lib/MarkdownDocs/__tests__/markdown.test.js +704 -0
- package/src/lib/MarkdownDocs/__tests__/markdownRendererRuntime.test.js +65 -0
- package/src/lib/MarkdownDocs/__tests__/searchIndexBuilder.test.js +117 -0
- package/src/lib/MarkdownDocs/__tests__/sqliteStore.test.js +202 -0
- package/src/lib/MarkdownDocs/__tests__/useRouter.test.js +16 -0
- package/src/lib/MarkdownDocs/ai/adapters/customAdapter.js +14 -0
- package/src/lib/MarkdownDocs/ai/adapters/customAdapter.ts +43 -0
- package/src/lib/MarkdownDocs/ai/adapters/ollamaAdapter.js +81 -0
- package/src/lib/MarkdownDocs/ai/adapters/ollamaAdapter.ts +116 -0
- package/src/lib/MarkdownDocs/ai/adapters/openaiAdapter.js +92 -0
- package/src/lib/MarkdownDocs/ai/adapters/openaiAdapter.ts +137 -0
- package/src/lib/MarkdownDocs/ai/aiProvider.ts +31 -0
- package/src/lib/MarkdownDocs/ai/characters.js +52 -0
- package/src/lib/MarkdownDocs/ai/characters.ts +69 -0
- package/src/lib/MarkdownDocs/ai/chunkStore.ts +25 -0
- package/src/lib/MarkdownDocs/ai/chunker.js +85 -0
- package/src/lib/MarkdownDocs/ai/chunker.ts +135 -0
- package/src/lib/MarkdownDocs/ai/docLinker.js +26 -0
- package/src/lib/MarkdownDocs/ai/docLinker.ts +36 -0
- package/src/lib/MarkdownDocs/ai/promptBuilder.js +33 -0
- package/src/lib/MarkdownDocs/ai/promptBuilder.ts +53 -0
- package/src/lib/MarkdownDocs/ai/ragPipeline.js +54 -0
- package/src/lib/MarkdownDocs/ai/ragPipeline.ts +106 -0
- package/src/lib/MarkdownDocs/ai/stores/memoryStore.js +88 -0
- package/src/lib/MarkdownDocs/ai/stores/memoryStore.ts +112 -0
- package/src/lib/MarkdownDocs/ai/stores/sqliteStore.ts +372 -0
- package/src/lib/MarkdownDocs/ai/types.ts +71 -0
- package/src/lib/MarkdownDocs/aiServer.js +288 -0
- package/src/lib/MarkdownDocs/codeDirectives.js +191 -0
- package/src/lib/MarkdownDocs/codeFenceInfo.js +45 -0
- package/src/lib/MarkdownDocs/codeGroup.ts +46 -0
- package/src/lib/MarkdownDocs/common.ts +47 -0
- package/src/lib/MarkdownDocs/docsUtils.js +281 -0
- package/src/lib/MarkdownDocs/index.plugins.js +22 -0
- package/src/lib/MarkdownDocs/layouts/LayoutDoc.svelte +8 -0
- package/src/lib/MarkdownDocs/layouts/LayoutHome.svelte +58 -0
- package/src/lib/MarkdownDocs/layouts/LayoutPage.svelte +9 -0
- package/src/lib/MarkdownDocs/loadGregConfig.js +82 -0
- package/src/lib/MarkdownDocs/localeUtils.ts +682 -0
- package/src/lib/MarkdownDocs/markdownRendererRuntime.ts +314 -0
- package/src/lib/MarkdownDocs/mermaidThemes.js +319 -0
- package/src/lib/MarkdownDocs/navigationUtils.js +22 -0
- package/src/lib/MarkdownDocs/rehypeCodeGroup.js +326 -0
- package/src/lib/MarkdownDocs/rehypeCodeTitle.js +96 -0
- package/src/lib/MarkdownDocs/rehypeToc.js +170 -0
- package/src/lib/MarkdownDocs/remarkCodeMeta.js +22 -0
- package/src/lib/MarkdownDocs/remarkContainers.js +329 -0
- package/src/lib/MarkdownDocs/remarkCustomAnchors.js +42 -0
- package/src/lib/MarkdownDocs/remarkEscapeSvelte.js +33 -0
- package/src/lib/MarkdownDocs/remarkGlobalComponents.js +65 -0
- package/src/lib/MarkdownDocs/remarkImports.js +461 -0
- package/src/lib/MarkdownDocs/remarkImportsBrowser.js +349 -0
- package/src/lib/MarkdownDocs/remarkInlineAttrs.js +95 -0
- package/src/lib/MarkdownDocs/remarkMathToHtml.js +138 -0
- package/src/lib/MarkdownDocs/searchIndexBuilder.js +497 -0
- package/src/lib/MarkdownDocs/searchServer.js +263 -0
- package/src/lib/MarkdownDocs/treeViewTypes.ts +11 -0
- package/src/lib/MarkdownDocs/useRouter.svelte.ts +114 -0
- package/src/lib/MarkdownDocs/useSplitter.svelte.ts +33 -0
- package/src/lib/MarkdownDocs/versioningDefaults.js +20 -0
- package/src/lib/MarkdownDocs/vitePluginAiServer.js +204 -0
- package/src/lib/MarkdownDocs/vitePluginCopyDocs.js +153 -0
- package/src/lib/MarkdownDocs/vitePluginFrontmatter.js +109 -0
- package/src/lib/MarkdownDocs/vitePluginGregConfig.js +108 -0
- package/src/lib/MarkdownDocs/vitePluginSearchIndex.js +57 -0
- package/src/lib/MarkdownDocs/vitePluginSearchServer.js +190 -0
- package/src/lib/components/Badge.svelte +59 -0
- package/src/lib/components/Button.svelte +138 -0
- package/src/lib/components/CarbonAds.svelte +99 -0
- package/src/lib/components/CodeGroup.svelte +102 -0
- package/src/lib/components/Feature.svelte +209 -0
- package/src/lib/components/Features.svelte +123 -0
- package/src/lib/components/Hero.svelte +399 -0
- package/src/lib/components/Image.svelte +128 -0
- package/src/lib/components/Link.svelte +105 -0
- package/src/lib/components/SocialLink.svelte +84 -0
- package/src/lib/components/SocialLinks.svelte +33 -0
- package/src/lib/components/Steps.svelte +143 -0
- package/src/lib/components/TeamMember.svelte +273 -0
- package/src/lib/components/TeamMembers.svelte +81 -0
- package/src/lib/components/TeamPage.svelte +65 -0
- package/src/lib/components/TeamPageSection.svelte +108 -0
- package/src/lib/components/TeamPageTitle.svelte +89 -0
- package/src/lib/components/index.js +24 -0
- package/src/lib/portal/context.js +12 -0
- package/src/lib/portal/index.js +3 -0
- package/src/lib/portal/portal.svelte +14 -0
- package/src/lib/portal/slot.svelte +8 -0
- package/src/lib/scss/__code.scss +128 -0
- package/src/lib/scss/__containers.scss +99 -0
- package/src/lib/scss/__markdown.scss +447 -0
- package/src/lib/scss/__scrollbar.scss +60 -0
- package/src/lib/scss/__steps.scss +100 -0
- package/src/lib/scss/__theme.scss +238 -0
- package/src/lib/scss/__toc.scss +55 -0
- package/src/lib/scss/__utilities.scss +7 -0
- package/src/lib/scss/greg.scss +9 -0
- package/src/lib/spinner/spinner.svelte +42 -0
- package/svelte.config.js +146 -0
- package/types/index.d.ts +456 -0
|
@@ -0,0 +1,2115 @@
|
|
|
1
|
+
<script lang="ts">
|
|
2
|
+
import { tick, type Snippet } from "svelte";
|
|
3
|
+
import DocsNavigation from "./DocsNavigation.svelte";
|
|
4
|
+
import DocsSiteHeader from "./DocsSiteHeader.svelte";
|
|
5
|
+
import SearchModal from "./SearchModal.svelte";
|
|
6
|
+
import Spinner from "./../spinner/spinner.svelte";
|
|
7
|
+
import {
|
|
8
|
+
prepareMenu,
|
|
9
|
+
flattenMenu,
|
|
10
|
+
getPrevNext,
|
|
11
|
+
getBreadcrumbItems,
|
|
12
|
+
parseSidebarConfig,
|
|
13
|
+
} from "./docsUtils";
|
|
14
|
+
import "./../scss/greg.scss";
|
|
15
|
+
import { setPortalsContext } from "./../portal";
|
|
16
|
+
import CarbonAds from "../components/CarbonAds.svelte";
|
|
17
|
+
import Outline from "./Outline.svelte";
|
|
18
|
+
import { useRouter } from "./useRouter.svelte";
|
|
19
|
+
import { useSplitter } from "./useSplitter.svelte";
|
|
20
|
+
import { handleCodeGroupClick, handleCodeGroupKeydown } from "./codeGroup";
|
|
21
|
+
import allFrontmatters from "virtual:greg-frontmatter";
|
|
22
|
+
import MarkdownRenderer from "./MarkdownRenderer.svelte";
|
|
23
|
+
import LayoutHome from "./layouts/LayoutHome.svelte";
|
|
24
|
+
import BackToTop from "./BackToTop.svelte";
|
|
25
|
+
import Breadcrumb from "./Breadcrumb.svelte";
|
|
26
|
+
import PrevNext from "./PrevNext.svelte";
|
|
27
|
+
import VersionOutdatedNotice from "./VersionOutdatedNotice.svelte";
|
|
28
|
+
import { EllipsisVertical } from "@lucide/svelte";
|
|
29
|
+
import gregConfig from "virtual:greg-config";
|
|
30
|
+
import {
|
|
31
|
+
type LocaleConfig,
|
|
32
|
+
getLocaleEntries,
|
|
33
|
+
getLocaleSwitchItems,
|
|
34
|
+
normalizeLocaleKey,
|
|
35
|
+
resolveLocaleForPath,
|
|
36
|
+
} from "./localeUtils";
|
|
37
|
+
import { withBase, withoutBase, normalizePath as normalizeSrcDir } from "./common";
|
|
38
|
+
import {
|
|
39
|
+
buildDefaultVersionPathPrefix,
|
|
40
|
+
DEFAULT_PATH_PREFIX,
|
|
41
|
+
DEFAULT_VERSIONS_DIR_NAME,
|
|
42
|
+
} from "./versioningDefaults.js";
|
|
43
|
+
|
|
44
|
+
type CarbonAdsOptions = {
|
|
45
|
+
code: string;
|
|
46
|
+
placement: string;
|
|
47
|
+
};
|
|
48
|
+
|
|
49
|
+
type OutlineLevel = false | number | [number, number] | "deep";
|
|
50
|
+
type OutlineOption =
|
|
51
|
+
| OutlineLevel
|
|
52
|
+
| { level?: OutlineLevel; label?: string };
|
|
53
|
+
|
|
54
|
+
type BadgeSpec = string | { text: string; type?: string };
|
|
55
|
+
type ThemeableImage =
|
|
56
|
+
| string
|
|
57
|
+
| { src: string; alt?: string }
|
|
58
|
+
| { light: string; dark: string; alt?: string };
|
|
59
|
+
type SocialLinkItem = {
|
|
60
|
+
icon: string | { svg: string };
|
|
61
|
+
link: string;
|
|
62
|
+
ariaLabel?: string;
|
|
63
|
+
};
|
|
64
|
+
type SidebarItem = {
|
|
65
|
+
text: string;
|
|
66
|
+
link?: string;
|
|
67
|
+
items?: SidebarItem[];
|
|
68
|
+
auto?: string;
|
|
69
|
+
badge?: BadgeSpec;
|
|
70
|
+
};
|
|
71
|
+
|
|
72
|
+
type TopNavItem = {
|
|
73
|
+
text: string;
|
|
74
|
+
link?: string;
|
|
75
|
+
target?: string;
|
|
76
|
+
items?: {
|
|
77
|
+
text: string;
|
|
78
|
+
link?: string;
|
|
79
|
+
target?: string;
|
|
80
|
+
items?: { text: string; link?: string; target?: string }[];
|
|
81
|
+
}[];
|
|
82
|
+
};
|
|
83
|
+
|
|
84
|
+
type Props = {
|
|
85
|
+
srcDir?: string;
|
|
86
|
+
docsBase?: string;
|
|
87
|
+
children?: Snippet;
|
|
88
|
+
version?: string;
|
|
89
|
+
mainTitle?: string;
|
|
90
|
+
carbonAds?: CarbonAdsOptions;
|
|
91
|
+
/** VitePress-compatible outline option. false = disabled, [2,3] = default. */
|
|
92
|
+
outline?: OutlineOption | boolean;
|
|
93
|
+
/**
|
|
94
|
+
* Key of the active Mermaid diagram theme.
|
|
95
|
+
* Built-in values: `'material'` (default).
|
|
96
|
+
*/
|
|
97
|
+
mermaidTheme?: string;
|
|
98
|
+
/**
|
|
99
|
+
* Extra (or override) Mermaid theme configs keyed by theme name.
|
|
100
|
+
* Merged on top of the built-in themes inside MarkdownRenderer.
|
|
101
|
+
*/
|
|
102
|
+
mermaidThemes?: Record<string, Record<string, unknown>>;
|
|
103
|
+
/** Show breadcrumb navigation above content (doc layout only). */
|
|
104
|
+
breadcrumb?: boolean;
|
|
105
|
+
/** Show Back To Top button. */
|
|
106
|
+
backToTop?: boolean;
|
|
107
|
+
/** Show the file's last-modified date below content (doc layout only).
|
|
108
|
+
* `true` ďż˝ uses default format `{ dateStyle: 'medium' }` and browser locale.
|
|
109
|
+
* Object ďż˝ `{ text?, locale?, formatOptions? }` for full control.
|
|
110
|
+
*/
|
|
111
|
+
lastModified?:
|
|
112
|
+
| boolean
|
|
113
|
+
| {
|
|
114
|
+
text?: string;
|
|
115
|
+
locale?: string;
|
|
116
|
+
formatOptions?: Intl.DateTimeFormatOptions;
|
|
117
|
+
};
|
|
118
|
+
/**
|
|
119
|
+
* Sidebar tree configuration.
|
|
120
|
+
* `'auto'` (default) ďż˝ generated from the docs folder structure.
|
|
121
|
+
* Pass an array of `SidebarItem` objects to define the sidebar manually;
|
|
122
|
+
* items with an `auto` path have their children auto-generated.
|
|
123
|
+
*/
|
|
124
|
+
sidebar?: "auto" | SidebarItem[];
|
|
125
|
+
/** Top navigation bar items. */
|
|
126
|
+
nav?: TopNavItem[];
|
|
127
|
+
/** VitePress-compatible i18n routing behavior for locale switcher. */
|
|
128
|
+
i18nRouting?: boolean;
|
|
129
|
+
/** VitePress-compatible language switcher aria-label. */
|
|
130
|
+
langMenuLabel?: string;
|
|
131
|
+
/** VitePress-compatible mobile sidebar menu label. */
|
|
132
|
+
sidebarMenuLabel?: string;
|
|
133
|
+
/** VitePress-compatible skip-to-content label. */
|
|
134
|
+
skipToContentLabel?: string;
|
|
135
|
+
/** VitePress-compatible back-to-top aria-label. */
|
|
136
|
+
returnToTopLabel?: string;
|
|
137
|
+
/** VitePress-compatible appearance switcher label. */
|
|
138
|
+
darkModeSwitchLabel?: string;
|
|
139
|
+
/** VitePress-compatible title for light mode button. */
|
|
140
|
+
lightModeSwitchTitle?: string;
|
|
141
|
+
/** VitePress-compatible title for dark mode button. */
|
|
142
|
+
darkModeSwitchTitle?: string;
|
|
143
|
+
/** VitePress-compatible previous/next labels. */
|
|
144
|
+
docFooter?: {
|
|
145
|
+
prev?: string | false;
|
|
146
|
+
next?: string | false;
|
|
147
|
+
};
|
|
148
|
+
externalLinkIcon?: boolean;
|
|
149
|
+
siteTitle?: string | false;
|
|
150
|
+
logo?: ThemeableImage;
|
|
151
|
+
socialLinks?: SocialLinkItem[];
|
|
152
|
+
editLink?: { pattern: string; text?: string };
|
|
153
|
+
footer?: { message?: string; copyright?: string };
|
|
154
|
+
aside?: boolean | "left";
|
|
155
|
+
lastUpdated?: {
|
|
156
|
+
text?: string;
|
|
157
|
+
formatOptions?: Intl.DateTimeFormatOptions & { forceLocale?: boolean };
|
|
158
|
+
};
|
|
159
|
+
/**
|
|
160
|
+
* Custom search provider.
|
|
161
|
+
* `(query: string, limit?: number) => Promise<SearchResult[]>`
|
|
162
|
+
* Overrides greg.config.js ďż˝ search.provider when set.
|
|
163
|
+
* The function must return objects matching the SearchResult shape:
|
|
164
|
+
* { id, title, titleHtml, sectionTitle, sectionTitleHtml?, sectionAnchor, excerptHtml, score }
|
|
165
|
+
*/
|
|
166
|
+
searchProvider?: (query: string, limit?: number) => Promise<any[]>;
|
|
167
|
+
};
|
|
168
|
+
|
|
169
|
+
type VersionManifestEntry = {
|
|
170
|
+
version: string;
|
|
171
|
+
title?: string;
|
|
172
|
+
path?: string;
|
|
173
|
+
};
|
|
174
|
+
|
|
175
|
+
type VersionManifest = {
|
|
176
|
+
default?: string | null;
|
|
177
|
+
versions?: VersionManifestEntry[];
|
|
178
|
+
aliases?: Record<string, string>;
|
|
179
|
+
};
|
|
180
|
+
|
|
181
|
+
type VersioningUiConfig = {
|
|
182
|
+
versionMenuLabel?: string;
|
|
183
|
+
manifestUnavailableText?: string;
|
|
184
|
+
showManifestUnavailableStatus?: boolean;
|
|
185
|
+
outdatedVersionMessage?: string;
|
|
186
|
+
outdatedVersionActionLabel?: string;
|
|
187
|
+
};
|
|
188
|
+
|
|
189
|
+
type VersioningLocaleUiConfig = Omit<
|
|
190
|
+
VersioningUiConfig,
|
|
191
|
+
"showManifestUnavailableStatus"
|
|
192
|
+
>;
|
|
193
|
+
|
|
194
|
+
type VersioningLocaleConfig = {
|
|
195
|
+
ui?: VersioningLocaleUiConfig;
|
|
196
|
+
};
|
|
197
|
+
|
|
198
|
+
let {
|
|
199
|
+
children,
|
|
200
|
+
srcDir: configuredSrcDir = normalizeSrcDir(
|
|
201
|
+
Object.prototype.hasOwnProperty.call((gregConfig as any), "docsBase")
|
|
202
|
+
? (gregConfig as any).docsBase
|
|
203
|
+
: "",
|
|
204
|
+
),
|
|
205
|
+
version: globalVersion = (gregConfig as any).version ?? "",
|
|
206
|
+
mainTitle: globalMainTitle = (gregConfig as any).mainTitle ?? "Greg",
|
|
207
|
+
carbonAds = (gregConfig as any).carbonAds,
|
|
208
|
+
outline: globalOutline =
|
|
209
|
+
(gregConfig as any).outline ?? ([2, 3] as [number, number]),
|
|
210
|
+
mermaidTheme = (gregConfig as any).mermaidTheme,
|
|
211
|
+
mermaidThemes,
|
|
212
|
+
breadcrumb = (gregConfig as any).breadcrumb ?? false,
|
|
213
|
+
backToTop = (gregConfig as any).backToTop ?? false,
|
|
214
|
+
lastModified: globalLastModified = (gregConfig as any).lastModified ?? false,
|
|
215
|
+
sidebar: globalSidebar = (gregConfig as any).sidebar ?? "auto",
|
|
216
|
+
nav: globalNav = (gregConfig as any).nav ?? [],
|
|
217
|
+
i18nRouting = (gregConfig as any).i18nRouting ?? true,
|
|
218
|
+
langMenuLabel: globalLangMenuLabel =
|
|
219
|
+
(gregConfig as any).langMenuLabel ?? "Change language",
|
|
220
|
+
sidebarMenuLabel: globalSidebarMenuLabel =
|
|
221
|
+
(gregConfig as any).sidebarMenuLabel ?? "Menu",
|
|
222
|
+
skipToContentLabel: globalSkipToContentLabel =
|
|
223
|
+
(gregConfig as any).skipToContentLabel ?? "Skip to content",
|
|
224
|
+
returnToTopLabel: globalReturnToTopLabel =
|
|
225
|
+
(gregConfig as any).returnToTopLabel ?? "Back to top",
|
|
226
|
+
darkModeSwitchLabel: globalDarkModeSwitchLabel =
|
|
227
|
+
(gregConfig as any).darkModeSwitchLabel ?? "Appearance",
|
|
228
|
+
lightModeSwitchTitle: globalLightModeSwitchTitle =
|
|
229
|
+
(gregConfig as any).lightModeSwitchTitle ?? "Switch to light theme",
|
|
230
|
+
darkModeSwitchTitle: globalDarkModeSwitchTitle =
|
|
231
|
+
(gregConfig as any).darkModeSwitchTitle ?? "Switch to dark theme",
|
|
232
|
+
docFooter: globalDocFooter =
|
|
233
|
+
(gregConfig as any).docFooter ??
|
|
234
|
+
({ prev: "Previous", next: "Next" } as {
|
|
235
|
+
prev?: string | false;
|
|
236
|
+
next?: string | false;
|
|
237
|
+
}),
|
|
238
|
+
externalLinkIcon: globalExternalLinkIcon =
|
|
239
|
+
(gregConfig as any).externalLinkIcon ?? false,
|
|
240
|
+
siteTitle: globalSiteTitle = (gregConfig as any).siteTitle,
|
|
241
|
+
logo: globalLogo = (gregConfig as any).logo,
|
|
242
|
+
socialLinks: globalSocialLinks =
|
|
243
|
+
((gregConfig as any).socialLinks ?? []) as SocialLinkItem[],
|
|
244
|
+
editLink: globalEditLink = (gregConfig as any).editLink,
|
|
245
|
+
footer: globalFooter = (gregConfig as any).footer,
|
|
246
|
+
aside: globalAside = (gregConfig as any).aside ?? true,
|
|
247
|
+
lastUpdated: globalLastUpdated = (gregConfig as any).lastUpdated,
|
|
248
|
+
searchProvider,
|
|
249
|
+
}: Props = $props();
|
|
250
|
+
|
|
251
|
+
const configLocales = ((gregConfig as any).locales ?? {}) as Record<
|
|
252
|
+
string,
|
|
253
|
+
LocaleConfig
|
|
254
|
+
>;
|
|
255
|
+
// Locale entries are precomputed here (before the router) so they can be
|
|
256
|
+
// used in normalizePathForLookup to translate segment-based URLs to srcDir paths.
|
|
257
|
+
const localeEntries = $derived(getLocaleEntries(configuredSrcDir, configLocales));
|
|
258
|
+
const versioningConfig = ((gregConfig as any).versioning ?? null) as
|
|
259
|
+
| {
|
|
260
|
+
ui?: VersioningUiConfig;
|
|
261
|
+
locales?: Record<string, VersioningLocaleConfig>;
|
|
262
|
+
pathPrefix?: string;
|
|
263
|
+
branches?: Array<unknown>;
|
|
264
|
+
folders?: Array<unknown>;
|
|
265
|
+
foldersDir?: string;
|
|
266
|
+
}
|
|
267
|
+
| null;
|
|
268
|
+
const globalVersioningUi = (versioningConfig?.ui ?? {}) as VersioningUiConfig;
|
|
269
|
+
|
|
270
|
+
|
|
271
|
+
function getLocalizedVersioningUi(
|
|
272
|
+
localesMap: Record<string, VersioningLocaleConfig> | undefined,
|
|
273
|
+
localeKey: string,
|
|
274
|
+
): VersioningUiConfig {
|
|
275
|
+
if (!localesMap) return {};
|
|
276
|
+
const normalized = normalizeLocaleKey(localeKey);
|
|
277
|
+
const noTrailing = normalized.replace(/\/$/, "");
|
|
278
|
+
const trimmed = normalized.replace(/^\/+|\/+$/g, "");
|
|
279
|
+
const candidates = [normalized, noTrailing, trimmed].filter(Boolean);
|
|
280
|
+
for (const key of candidates) {
|
|
281
|
+
const found = localesMap[key];
|
|
282
|
+
if (found?.ui) return found.ui;
|
|
283
|
+
}
|
|
284
|
+
return {};
|
|
285
|
+
}
|
|
286
|
+
|
|
287
|
+
function normalizeVersionPrefix(value: string | undefined): string {
|
|
288
|
+
const fallbackPrefix = buildDefaultVersionPathPrefix((gregConfig as any).base);
|
|
289
|
+
const cleaned = String(value || fallbackPrefix || DEFAULT_PATH_PREFIX)
|
|
290
|
+
.trim()
|
|
291
|
+
.replace(/^\/+|\/+$/g, "");
|
|
292
|
+
return "/" + (cleaned || DEFAULT_VERSIONS_DIR_NAME);
|
|
293
|
+
}
|
|
294
|
+
|
|
295
|
+
function findActiveVersion(pathname: string, prefix: string): string | null {
|
|
296
|
+
if (pathname === prefix || !pathname.startsWith(prefix + "/")) return null;
|
|
297
|
+
const rest = pathname.slice(prefix.length + 1);
|
|
298
|
+
const segment = rest.split("/")[0];
|
|
299
|
+
return segment || null;
|
|
300
|
+
}
|
|
301
|
+
|
|
302
|
+
function stripVersionPrefixFromPath(pathname: string, prefix: string): string {
|
|
303
|
+
const normalizedPath = normalizeSrcDir(pathname);
|
|
304
|
+
const activeVersion = findActiveVersion(normalizedPath, prefix);
|
|
305
|
+
if (!activeVersion) return normalizedPath;
|
|
306
|
+
|
|
307
|
+
const versionRoot = `${prefix}/${activeVersion}`;
|
|
308
|
+
const suffix = normalizedPath.slice(versionRoot.length);
|
|
309
|
+
return normalizeSrcDir(suffix || "/");
|
|
310
|
+
}
|
|
311
|
+
|
|
312
|
+
let versionManifest = $state<VersionManifest | null>(null);
|
|
313
|
+
let manifestVersionOptions = $state<{ version: string; title: string; path: string }[]>([]);
|
|
314
|
+
let versionManifestLoadError = $state(false);
|
|
315
|
+
const versionPathPrefix = normalizeVersionPrefix(versioningConfig?.pathPrefix);
|
|
316
|
+
const hasVersionDefinitions = $derived.by(() => {
|
|
317
|
+
if (!versioningConfig) return false;
|
|
318
|
+
const hasBranches =
|
|
319
|
+
Array.isArray(versioningConfig.branches) &&
|
|
320
|
+
versioningConfig.branches.length > 0;
|
|
321
|
+
const hasFolders =
|
|
322
|
+
Array.isArray(versioningConfig.folders) &&
|
|
323
|
+
versioningConfig.folders.length > 0;
|
|
324
|
+
const hasFoldersDir =
|
|
325
|
+
typeof versioningConfig.foldersDir === "string" &&
|
|
326
|
+
versioningConfig.foldersDir.trim().length > 0;
|
|
327
|
+
return hasBranches || hasFolders || hasFoldersDir;
|
|
328
|
+
});
|
|
329
|
+
function formatOutdatedMessage(
|
|
330
|
+
currentTitle: string,
|
|
331
|
+
defaultTitle: string,
|
|
332
|
+
template: string,
|
|
333
|
+
): string {
|
|
334
|
+
if (!template) {
|
|
335
|
+
return `You are viewing an older documentation version (${currentTitle}). ${defaultTitle} is currently recommended.`;
|
|
336
|
+
}
|
|
337
|
+
return template
|
|
338
|
+
.replaceAll("{current}", currentTitle)
|
|
339
|
+
.replaceAll("{default}", defaultTitle);
|
|
340
|
+
}
|
|
341
|
+
|
|
342
|
+
$effect(() => {
|
|
343
|
+
let cancelled = false;
|
|
344
|
+
|
|
345
|
+
if (!hasVersionDefinitions) {
|
|
346
|
+
versionManifest = null;
|
|
347
|
+
manifestVersionOptions = [];
|
|
348
|
+
versionManifestLoadError = false;
|
|
349
|
+
return () => {
|
|
350
|
+
cancelled = true;
|
|
351
|
+
};
|
|
352
|
+
}
|
|
353
|
+
|
|
354
|
+
const manifestUrl = withBase(`${versionPathPrefix}/versions.json`);
|
|
355
|
+
|
|
356
|
+
fetch(manifestUrl)
|
|
357
|
+
.then((res) => {
|
|
358
|
+
if (!res.ok) {
|
|
359
|
+
throw new Error(`Unable to load ${manifestUrl}: ${res.status}`);
|
|
360
|
+
}
|
|
361
|
+
return res.json() as Promise<VersionManifest>;
|
|
362
|
+
})
|
|
363
|
+
.then((data) => {
|
|
364
|
+
if (cancelled) return;
|
|
365
|
+
versionManifest = data;
|
|
366
|
+
versionManifestLoadError = false;
|
|
367
|
+
const versions = Array.isArray(data.versions) ? data.versions : [];
|
|
368
|
+
manifestVersionOptions = versions
|
|
369
|
+
.map((entry) => {
|
|
370
|
+
const version = String(entry.version || "").trim();
|
|
371
|
+
if (!version) return null;
|
|
372
|
+
const title = String(entry.title || version);
|
|
373
|
+
const rawPath = String(entry.path || `${versionPathPrefix}/${version}/`);
|
|
374
|
+
const path = normalizeSrcDir(rawPath);
|
|
375
|
+
return { version, title, path };
|
|
376
|
+
})
|
|
377
|
+
.filter((entry): entry is { version: string; title: string; path: string } => Boolean(entry));
|
|
378
|
+
})
|
|
379
|
+
.catch(() => {
|
|
380
|
+
if (cancelled) return;
|
|
381
|
+
versionManifest = null;
|
|
382
|
+
manifestVersionOptions = [];
|
|
383
|
+
versionManifestLoadError = true;
|
|
384
|
+
console.warn(`[greg] Could not load ${manifestUrl}.`);
|
|
385
|
+
});
|
|
386
|
+
|
|
387
|
+
return () => {
|
|
388
|
+
cancelled = true;
|
|
389
|
+
};
|
|
390
|
+
});
|
|
391
|
+
|
|
392
|
+
function navigateToVersion(version: string) {
|
|
393
|
+
const next = manifestVersionOptions.find((entry) => entry.version === version);
|
|
394
|
+
if (!next) return;
|
|
395
|
+
|
|
396
|
+
const currentPath = withoutBase(window.location.pathname);
|
|
397
|
+
const currentVersion = findActiveVersion(currentPath, versionPathPrefix);
|
|
398
|
+
const isDefaultTarget =
|
|
399
|
+
Boolean(resolvedDefaultVersion) && version === resolvedDefaultVersion;
|
|
400
|
+
const search = window.location.search || "";
|
|
401
|
+
const hash = window.location.hash || "";
|
|
402
|
+
|
|
403
|
+
let targetPath = next.path;
|
|
404
|
+
|
|
405
|
+
if (currentVersion) {
|
|
406
|
+
const currentPrefix = `${versionPathPrefix}/${currentVersion}`;
|
|
407
|
+
const suffix =
|
|
408
|
+
currentPath === currentPrefix
|
|
409
|
+
? ""
|
|
410
|
+
: currentPath.startsWith(currentPrefix + "/")
|
|
411
|
+
? currentPath.slice(currentPrefix.length)
|
|
412
|
+
: "";
|
|
413
|
+
if (isDefaultTarget) {
|
|
414
|
+
targetPath = suffix ? normalizeSrcDir(suffix) : "/";
|
|
415
|
+
} else {
|
|
416
|
+
targetPath = normalizeSrcDir(next.path + suffix);
|
|
417
|
+
}
|
|
418
|
+
} else if (
|
|
419
|
+
currentPath === currentSrcDir ||
|
|
420
|
+
currentPath.startsWith(currentSrcDir + "/")
|
|
421
|
+
) {
|
|
422
|
+
if (isDefaultTarget) {
|
|
423
|
+
targetPath = currentPath;
|
|
424
|
+
} else {
|
|
425
|
+
const suffix = currentPath.slice(currentSrcDir.length);
|
|
426
|
+
targetPath = normalizeSrcDir(next.path + currentSrcDir + suffix);
|
|
427
|
+
}
|
|
428
|
+
}
|
|
429
|
+
|
|
430
|
+
window.location.assign(`${withBase(targetPath)}${search}${hash}`);
|
|
431
|
+
}
|
|
432
|
+
|
|
433
|
+
// -- Outline -----------------------------------------------------------------
|
|
434
|
+
function normalizeOutline(
|
|
435
|
+
o: OutlineOption | boolean | undefined | null,
|
|
436
|
+
): { level: OutlineLevel; label: string } | null {
|
|
437
|
+
if (o === false || o === null || o === undefined) return null;
|
|
438
|
+
if (o === true)
|
|
439
|
+
return { level: [2, 3] as [number, number], label: "On this page" };
|
|
440
|
+
if (
|
|
441
|
+
typeof o === "object" &&
|
|
442
|
+
!Array.isArray(o) &&
|
|
443
|
+
("level" in o || "label" in o)
|
|
444
|
+
) {
|
|
445
|
+
const oo = o as { level?: OutlineLevel; label?: string };
|
|
446
|
+
return {
|
|
447
|
+
level: oo.level ?? ([2, 3] as [number, number]),
|
|
448
|
+
label: oo.label ?? "On this page",
|
|
449
|
+
};
|
|
450
|
+
}
|
|
451
|
+
return { level: o as OutlineLevel, label: "On this page" };
|
|
452
|
+
}
|
|
453
|
+
|
|
454
|
+
let mainEl = $state<HTMLElement | undefined>(undefined);
|
|
455
|
+
|
|
456
|
+
// -- Modules -----------------------------------------------------------------
|
|
457
|
+
// Frontmatter parsed from raw YAML by vitePluginFrontmatter (virtual module).
|
|
458
|
+
// Keyed by Vite-style absolute paths, e.g. '/docs/guide/index.md'.
|
|
459
|
+
// Used as the known-paths set for routing (no glob/import needed).
|
|
460
|
+
type FrontmatterEntry = {
|
|
461
|
+
title?: string;
|
|
462
|
+
order?: number;
|
|
463
|
+
layout?: "doc" | "home" | "page";
|
|
464
|
+
renderer?: "runtime" | "mdsvex";
|
|
465
|
+
hero?: Record<string, unknown>;
|
|
466
|
+
features?: unknown[];
|
|
467
|
+
outline?: OutlineOption | boolean;
|
|
468
|
+
badge?: BadgeSpec;
|
|
469
|
+
prev?: false | { text: string; link: string };
|
|
470
|
+
next?: false | { text: string; link: string };
|
|
471
|
+
_mtime?: string;
|
|
472
|
+
};
|
|
473
|
+
const frontmatters = allFrontmatters as Record<string, FrontmatterEntry>;
|
|
474
|
+
|
|
475
|
+
// -- Theme -------------------------------------------------------------------
|
|
476
|
+
function getSystemTheme(): "light" | "dark" {
|
|
477
|
+
return window.matchMedia("(prefers-color-scheme: dark)").matches
|
|
478
|
+
? "dark"
|
|
479
|
+
: "light";
|
|
480
|
+
}
|
|
481
|
+
|
|
482
|
+
const THEME_KEY = "greg-theme";
|
|
483
|
+
const THEME_SOURCE_KEY = "greg-theme-source";
|
|
484
|
+
|
|
485
|
+
const storedTheme = localStorage.getItem(THEME_KEY);
|
|
486
|
+
const storedSource = localStorage.getItem(THEME_SOURCE_KEY);
|
|
487
|
+
const initialThemeSource: "system" | "manual" =
|
|
488
|
+
storedSource === "manual" ? "manual" : "system";
|
|
489
|
+
|
|
490
|
+
let themeSource = $state<"system" | "manual">(initialThemeSource);
|
|
491
|
+
|
|
492
|
+
let theme = $state<"light" | "dark">(
|
|
493
|
+
initialThemeSource === "manual" &&
|
|
494
|
+
(storedTheme === "light" || storedTheme === "dark")
|
|
495
|
+
? storedTheme
|
|
496
|
+
: getSystemTheme(),
|
|
497
|
+
);
|
|
498
|
+
let lastSystemPrefersDark = $state(getSystemTheme() === "dark");
|
|
499
|
+
|
|
500
|
+
function setThemeManually(next: "light" | "dark") {
|
|
501
|
+
themeSource = "manual";
|
|
502
|
+
theme = next;
|
|
503
|
+
}
|
|
504
|
+
|
|
505
|
+
$effect(() => {
|
|
506
|
+
if (themeSource === "manual") {
|
|
507
|
+
localStorage.setItem(THEME_SOURCE_KEY, "manual");
|
|
508
|
+
localStorage.setItem(THEME_KEY, theme);
|
|
509
|
+
return;
|
|
510
|
+
}
|
|
511
|
+
|
|
512
|
+
localStorage.removeItem(THEME_SOURCE_KEY);
|
|
513
|
+
localStorage.removeItem(THEME_KEY);
|
|
514
|
+
});
|
|
515
|
+
|
|
516
|
+
$effect(() => {
|
|
517
|
+
const mq = window.matchMedia("(prefers-color-scheme: dark)");
|
|
518
|
+
|
|
519
|
+
const syncFromSystemPreference = (force: boolean) => {
|
|
520
|
+
const currentPrefersDark = mq.matches;
|
|
521
|
+
const changed = currentPrefersDark !== lastSystemPrefersDark;
|
|
522
|
+
|
|
523
|
+
if (changed) {
|
|
524
|
+
lastSystemPrefersDark = currentPrefersDark;
|
|
525
|
+
const next = currentPrefersDark ? "dark" : "light";
|
|
526
|
+
themeSource = "system";
|
|
527
|
+
theme = next;
|
|
528
|
+
return;
|
|
529
|
+
}
|
|
530
|
+
|
|
531
|
+
// Keep following system only when currently in system mode.
|
|
532
|
+
if (force && themeSource === "system") {
|
|
533
|
+
theme = currentPrefersDark ? "dark" : "light";
|
|
534
|
+
}
|
|
535
|
+
};
|
|
536
|
+
|
|
537
|
+
const handleSystemPreferenceEvent = () => {
|
|
538
|
+
syncFromSystemPreference(false);
|
|
539
|
+
};
|
|
540
|
+
|
|
541
|
+
syncFromSystemPreference(true);
|
|
542
|
+
|
|
543
|
+
mq.addEventListener("change", handleSystemPreferenceEvent);
|
|
544
|
+
window.addEventListener("focus", handleSystemPreferenceEvent);
|
|
545
|
+
document.addEventListener(
|
|
546
|
+
"visibilitychange",
|
|
547
|
+
handleSystemPreferenceEvent,
|
|
548
|
+
);
|
|
549
|
+
|
|
550
|
+
return () => {
|
|
551
|
+
mq.removeEventListener("change", handleSystemPreferenceEvent);
|
|
552
|
+
window.removeEventListener("focus", handleSystemPreferenceEvent);
|
|
553
|
+
document.removeEventListener(
|
|
554
|
+
"visibilitychange",
|
|
555
|
+
handleSystemPreferenceEvent,
|
|
556
|
+
);
|
|
557
|
+
};
|
|
558
|
+
});
|
|
559
|
+
|
|
560
|
+
$effect(() => {
|
|
561
|
+
const href = withBase(theme === "dark" ? "/favicon-dark.svg?v=5" : "/favicon-light.svg?v=5");
|
|
562
|
+
|
|
563
|
+
let el = document.querySelector(
|
|
564
|
+
'link[data-greg-favicon="true"]',
|
|
565
|
+
) as HTMLLinkElement | null;
|
|
566
|
+
|
|
567
|
+
if (!el) {
|
|
568
|
+
el = document.createElement("link");
|
|
569
|
+
el.rel = "icon";
|
|
570
|
+
el.type = "image/svg+xml";
|
|
571
|
+
el.setAttribute("data-greg-favicon", "true");
|
|
572
|
+
document.head.appendChild(el);
|
|
573
|
+
}
|
|
574
|
+
|
|
575
|
+
if (el.href !== new URL(href, window.location.origin).href) {
|
|
576
|
+
el.href = href;
|
|
577
|
+
}
|
|
578
|
+
});
|
|
579
|
+
|
|
580
|
+
// -- Search ------------------------------------------------------------------
|
|
581
|
+
const searchEnabled = $derived(
|
|
582
|
+
Boolean(searchProvider) ||
|
|
583
|
+
(gregConfig as any)?.search?.provider !== "none",
|
|
584
|
+
);
|
|
585
|
+
let searchOpen = $state(false);
|
|
586
|
+
|
|
587
|
+
$effect(() => {
|
|
588
|
+
if (!searchEnabled) return;
|
|
589
|
+
function handleGlobalKeydown(e: KeyboardEvent) {
|
|
590
|
+
if ((e.ctrlKey || e.metaKey) && e.key === "k") {
|
|
591
|
+
e.preventDefault();
|
|
592
|
+
searchOpen = true;
|
|
593
|
+
}
|
|
594
|
+
}
|
|
595
|
+
window.addEventListener("keydown", handleGlobalKeydown);
|
|
596
|
+
return () => window.removeEventListener("keydown", handleGlobalKeydown);
|
|
597
|
+
});
|
|
598
|
+
|
|
599
|
+
// -- Router ------------------------------------------------------------------
|
|
600
|
+
const router = useRouter(frontmatters, (path) =>
|
|
601
|
+
resolveLocaleForPath(
|
|
602
|
+
stripVersionPrefixFromPath(
|
|
603
|
+
withoutBase(path),
|
|
604
|
+
versionPathPrefix,
|
|
605
|
+
),
|
|
606
|
+
configuredSrcDir,
|
|
607
|
+
configLocales,
|
|
608
|
+
{
|
|
609
|
+
mainTitle: globalMainTitle,
|
|
610
|
+
nav: globalNav,
|
|
611
|
+
sidebar: globalSidebar,
|
|
612
|
+
outline: globalOutline,
|
|
613
|
+
langMenuLabel: globalLangMenuLabel,
|
|
614
|
+
sidebarMenuLabel: globalSidebarMenuLabel,
|
|
615
|
+
skipToContentLabel: globalSkipToContentLabel,
|
|
616
|
+
returnToTopLabel: globalReturnToTopLabel,
|
|
617
|
+
darkModeSwitchLabel: globalDarkModeSwitchLabel,
|
|
618
|
+
lightModeSwitchTitle: globalLightModeSwitchTitle,
|
|
619
|
+
darkModeSwitchTitle: globalDarkModeSwitchTitle,
|
|
620
|
+
searchButtonLabel: "Search...",
|
|
621
|
+
searchModalLabel: "Search",
|
|
622
|
+
searchPlaceholder: "Search docs...",
|
|
623
|
+
searchLoadingText: "Loading index...",
|
|
624
|
+
searchErrorText: "Failed to load search index.",
|
|
625
|
+
searchSearchingText: "Searching...",
|
|
626
|
+
searchNoResultsText: "No results for",
|
|
627
|
+
searchStartText:
|
|
628
|
+
"Start typing to search across all documentation.",
|
|
629
|
+
searchResultsAriaLabel: "Search results",
|
|
630
|
+
searchNavigateText: "navigate",
|
|
631
|
+
searchSelectText: "open",
|
|
632
|
+
searchCloseText: "close",
|
|
633
|
+
aiTabLabel: "Ask AI",
|
|
634
|
+
aiPlaceholder: "Ask a question about the docs\u2026",
|
|
635
|
+
aiLoadingText: "Thinking\u2026",
|
|
636
|
+
aiErrorText: "Something went wrong. Please try again.",
|
|
637
|
+
aiStartText: "Ask me anything about this documentation. My answers are based exclusively on the docs.",
|
|
638
|
+
aiSourcesLabel: "Sources",
|
|
639
|
+
aiClearChatLabel: "Clear chat",
|
|
640
|
+
aiSendLabel: "Send",
|
|
641
|
+
docFooter: globalDocFooter,
|
|
642
|
+
externalLinkIcon: globalExternalLinkIcon,
|
|
643
|
+
siteTitle: globalSiteTitle,
|
|
644
|
+
logo: globalLogo,
|
|
645
|
+
socialLinks: globalSocialLinks,
|
|
646
|
+
editLink: globalEditLink,
|
|
647
|
+
footer: globalFooter,
|
|
648
|
+
aside: globalAside,
|
|
649
|
+
lastUpdated: globalLastUpdated,
|
|
650
|
+
}).srcDir,
|
|
651
|
+
(path) => {
|
|
652
|
+
const spaPath = stripVersionPrefixFromPath(
|
|
653
|
+
withoutBase(path),
|
|
654
|
+
versionPathPrefix,
|
|
655
|
+
);
|
|
656
|
+
// Translate segment-based locale URLs to their srcDir-prefixed equivalent
|
|
657
|
+
// for content lookup. e.g. '/pl/guide' → '/documentation/pl/guide'.
|
|
658
|
+
const entry = localeEntries.find(
|
|
659
|
+
(e) => e.segment && (spaPath === e.segment || spaPath.startsWith(e.segment + "/"))
|
|
660
|
+
);
|
|
661
|
+
if (entry) return normalizeSrcDir(entry.srcDir + spaPath.slice(entry.segment.length));
|
|
662
|
+
return spaPath;
|
|
663
|
+
},
|
|
664
|
+
);
|
|
665
|
+
|
|
666
|
+
const routePath = $derived(
|
|
667
|
+
stripVersionPrefixFromPath(
|
|
668
|
+
withoutBase(router.active),
|
|
669
|
+
versionPathPrefix,
|
|
670
|
+
),
|
|
671
|
+
);
|
|
672
|
+
const localeContext = $derived(
|
|
673
|
+
resolveLocaleForPath(routePath, configuredSrcDir, configLocales, {
|
|
674
|
+
mainTitle: globalMainTitle,
|
|
675
|
+
nav: globalNav,
|
|
676
|
+
sidebar: globalSidebar,
|
|
677
|
+
outline: globalOutline,
|
|
678
|
+
langMenuLabel: globalLangMenuLabel,
|
|
679
|
+
sidebarMenuLabel: globalSidebarMenuLabel,
|
|
680
|
+
skipToContentLabel: globalSkipToContentLabel,
|
|
681
|
+
returnToTopLabel: globalReturnToTopLabel,
|
|
682
|
+
darkModeSwitchLabel: globalDarkModeSwitchLabel,
|
|
683
|
+
lightModeSwitchTitle: globalLightModeSwitchTitle,
|
|
684
|
+
darkModeSwitchTitle: globalDarkModeSwitchTitle,
|
|
685
|
+
searchButtonLabel: "Search...",
|
|
686
|
+
searchModalLabel: "Search",
|
|
687
|
+
searchPlaceholder: "Search docs...",
|
|
688
|
+
searchLoadingText: "Loading index...",
|
|
689
|
+
searchErrorText: "Failed to load search index.",
|
|
690
|
+
searchSearchingText: "Searching...",
|
|
691
|
+
searchNoResultsText: "No results for",
|
|
692
|
+
searchStartText:
|
|
693
|
+
"Start typing to search across all documentation.",
|
|
694
|
+
searchResultsAriaLabel: "Search results",
|
|
695
|
+
searchNavigateText: "navigate",
|
|
696
|
+
searchSelectText: "open",
|
|
697
|
+
searchCloseText: "close",
|
|
698
|
+
aiTabLabel: "Ask AI",
|
|
699
|
+
aiPlaceholder: "Ask a question about the docs\u2026",
|
|
700
|
+
aiLoadingText: "Thinking\u2026",
|
|
701
|
+
aiErrorText: "Something went wrong. Please try again.",
|
|
702
|
+
aiStartText: "Ask me anything about this documentation. My answers are based exclusively on the docs.",
|
|
703
|
+
aiSourcesLabel: "Sources",
|
|
704
|
+
aiClearChatLabel: "Clear chat",
|
|
705
|
+
aiSendLabel: "Send",
|
|
706
|
+
docFooter: globalDocFooter,
|
|
707
|
+
externalLinkIcon: globalExternalLinkIcon,
|
|
708
|
+
siteTitle: globalSiteTitle,
|
|
709
|
+
logo: globalLogo,
|
|
710
|
+
socialLinks: globalSocialLinks,
|
|
711
|
+
editLink: globalEditLink,
|
|
712
|
+
footer: globalFooter,
|
|
713
|
+
aside: globalAside,
|
|
714
|
+
lastUpdated: globalLastUpdated,
|
|
715
|
+
}),
|
|
716
|
+
);
|
|
717
|
+
const currentSrcDir = $derived(localeContext.srcDir);
|
|
718
|
+
function applyCurrentVersionPrefix(pathname: string): string {
|
|
719
|
+
const normalizedPath = normalizeSrcDir(pathname);
|
|
720
|
+
const currentPath = withoutBase(window.location.pathname);
|
|
721
|
+
const currentVersion = findActiveVersion(currentPath, versionPathPrefix);
|
|
722
|
+
if (!currentVersion) return normalizedPath;
|
|
723
|
+
|
|
724
|
+
if (
|
|
725
|
+
normalizedPath === currentSrcDir ||
|
|
726
|
+
normalizedPath.startsWith(currentSrcDir + "/")
|
|
727
|
+
) {
|
|
728
|
+
return normalizeSrcDir(
|
|
729
|
+
`${versionPathPrefix}/${currentVersion}${normalizedPath}`,
|
|
730
|
+
);
|
|
731
|
+
}
|
|
732
|
+
|
|
733
|
+
return normalizedPath;
|
|
734
|
+
}
|
|
735
|
+
|
|
736
|
+
function navigateInternal(path: string) {
|
|
737
|
+
router.navigate(withBase(applyCurrentVersionPrefix(path)));
|
|
738
|
+
}
|
|
739
|
+
|
|
740
|
+
function navigateInternalWithAnchor(path: string, anchor?: string) {
|
|
741
|
+
router.navigateWithAnchor(
|
|
742
|
+
withBase(applyCurrentVersionPrefix(path)),
|
|
743
|
+
anchor,
|
|
744
|
+
);
|
|
745
|
+
}
|
|
746
|
+
|
|
747
|
+
const SEARCH_HIGHLIGHT_STORAGE_KEY = "greg-search-highlight";
|
|
748
|
+
|
|
749
|
+
function clearSearchHighlights(container: HTMLElement) {
|
|
750
|
+
const marks = container.querySelectorAll("mark[data-greg-search-highlight='true']");
|
|
751
|
+
for (const mark of marks) {
|
|
752
|
+
const parent = mark.parentNode;
|
|
753
|
+
if (!parent) continue;
|
|
754
|
+
parent.replaceChild(document.createTextNode(mark.textContent || ""), mark);
|
|
755
|
+
parent.normalize();
|
|
756
|
+
}
|
|
757
|
+
}
|
|
758
|
+
|
|
759
|
+
function escapeRegex(value: string): string {
|
|
760
|
+
return value.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
761
|
+
}
|
|
762
|
+
|
|
763
|
+
function extractSearchTerms(phrase: string): string[] {
|
|
764
|
+
return Array.from(
|
|
765
|
+
new Set(
|
|
766
|
+
phrase
|
|
767
|
+
.split(/\s+/)
|
|
768
|
+
.map((token) => token.trim())
|
|
769
|
+
.filter((token) => token.length >= 2),
|
|
770
|
+
),
|
|
771
|
+
);
|
|
772
|
+
}
|
|
773
|
+
|
|
774
|
+
function highlightSearchTerms(container: HTMLElement, phrase: string): number {
|
|
775
|
+
const terms = extractSearchTerms(phrase);
|
|
776
|
+
if (terms.length === 0) return 0;
|
|
777
|
+
|
|
778
|
+
const pattern = new RegExp(`(${terms.map(escapeRegex).join("|")})`, "gi");
|
|
779
|
+
const loweredTerms = terms.map((term) => term.toLowerCase());
|
|
780
|
+
const walker = document.createTreeWalker(container, NodeFilter.SHOW_TEXT, {
|
|
781
|
+
acceptNode: (node) => {
|
|
782
|
+
const parent = node.parentElement;
|
|
783
|
+
if (!parent) return NodeFilter.FILTER_REJECT;
|
|
784
|
+
if (
|
|
785
|
+
parent.closest(
|
|
786
|
+
"script,style,code,pre,kbd,samp,var,mark[data-greg-search-highlight='true']",
|
|
787
|
+
)
|
|
788
|
+
) {
|
|
789
|
+
return NodeFilter.FILTER_REJECT;
|
|
790
|
+
}
|
|
791
|
+
const text = String(node.textContent || "").toLowerCase();
|
|
792
|
+
if (!text) return NodeFilter.FILTER_REJECT;
|
|
793
|
+
return loweredTerms.some((term) => text.includes(term))
|
|
794
|
+
? NodeFilter.FILTER_ACCEPT
|
|
795
|
+
: NodeFilter.FILTER_REJECT;
|
|
796
|
+
},
|
|
797
|
+
});
|
|
798
|
+
|
|
799
|
+
const textNodes: Text[] = [];
|
|
800
|
+
while (walker.nextNode()) {
|
|
801
|
+
textNodes.push(walker.currentNode as Text);
|
|
802
|
+
}
|
|
803
|
+
|
|
804
|
+
let count = 0;
|
|
805
|
+
for (const textNode of textNodes) {
|
|
806
|
+
const text = textNode.textContent || "";
|
|
807
|
+
pattern.lastIndex = 0;
|
|
808
|
+
if (!pattern.test(text)) continue;
|
|
809
|
+
pattern.lastIndex = 0;
|
|
810
|
+
|
|
811
|
+
const fragment = document.createDocumentFragment();
|
|
812
|
+
let lastIndex = 0;
|
|
813
|
+
let match: RegExpExecArray | null;
|
|
814
|
+
|
|
815
|
+
while ((match = pattern.exec(text)) !== null) {
|
|
816
|
+
const index = match.index;
|
|
817
|
+
if (index > lastIndex) {
|
|
818
|
+
fragment.appendChild(
|
|
819
|
+
document.createTextNode(text.slice(lastIndex, index)),
|
|
820
|
+
);
|
|
821
|
+
}
|
|
822
|
+
|
|
823
|
+
const mark = document.createElement("mark");
|
|
824
|
+
mark.setAttribute("data-greg-search-highlight", "true");
|
|
825
|
+
mark.textContent = match[0];
|
|
826
|
+
fragment.appendChild(mark);
|
|
827
|
+
count++;
|
|
828
|
+
lastIndex = index + match[0].length;
|
|
829
|
+
}
|
|
830
|
+
|
|
831
|
+
if (lastIndex < text.length) {
|
|
832
|
+
fragment.appendChild(document.createTextNode(text.slice(lastIndex)));
|
|
833
|
+
}
|
|
834
|
+
|
|
835
|
+
textNode.parentNode?.replaceChild(fragment, textNode);
|
|
836
|
+
}
|
|
837
|
+
|
|
838
|
+
return count;
|
|
839
|
+
}
|
|
840
|
+
|
|
841
|
+
function applyPendingSearchHighlights() {
|
|
842
|
+
if (!mainEl) return;
|
|
843
|
+
|
|
844
|
+
let raw = "";
|
|
845
|
+
try {
|
|
846
|
+
raw = sessionStorage.getItem(SEARCH_HIGHLIGHT_STORAGE_KEY) || "";
|
|
847
|
+
} catch {
|
|
848
|
+
return;
|
|
849
|
+
}
|
|
850
|
+
if (!raw) return;
|
|
851
|
+
|
|
852
|
+
let parsed: { query?: string; path?: string; timestamp?: number } | null = null;
|
|
853
|
+
try {
|
|
854
|
+
parsed = JSON.parse(raw);
|
|
855
|
+
} catch {
|
|
856
|
+
parsed = null;
|
|
857
|
+
}
|
|
858
|
+
if (!parsed?.query) return;
|
|
859
|
+
|
|
860
|
+
clearSearchHighlights(mainEl);
|
|
861
|
+
const matches = highlightSearchTerms(mainEl, parsed.query);
|
|
862
|
+
|
|
863
|
+
if (matches > 0) {
|
|
864
|
+
const first = mainEl.querySelector(
|
|
865
|
+
"mark[data-greg-search-highlight='true']",
|
|
866
|
+
) as HTMLElement | null;
|
|
867
|
+
first?.scrollIntoView({ block: "center", behavior: "smooth" });
|
|
868
|
+
try {
|
|
869
|
+
sessionStorage.removeItem(SEARCH_HIGHLIGHT_STORAGE_KEY);
|
|
870
|
+
} catch {
|
|
871
|
+
// Ignore storage errors.
|
|
872
|
+
}
|
|
873
|
+
}
|
|
874
|
+
}
|
|
875
|
+
|
|
876
|
+
function hasPendingSearchHighlightForRoute(): boolean {
|
|
877
|
+
let raw = "";
|
|
878
|
+
try {
|
|
879
|
+
raw = sessionStorage.getItem(SEARCH_HIGHLIGHT_STORAGE_KEY) || "";
|
|
880
|
+
} catch {
|
|
881
|
+
return false;
|
|
882
|
+
}
|
|
883
|
+
if (!raw) return false;
|
|
884
|
+
|
|
885
|
+
try {
|
|
886
|
+
const parsed = JSON.parse(raw) as { query?: string };
|
|
887
|
+
return Boolean(parsed?.query);
|
|
888
|
+
} catch {
|
|
889
|
+
return false;
|
|
890
|
+
}
|
|
891
|
+
}
|
|
892
|
+
|
|
893
|
+
$effect(() => {
|
|
894
|
+
const activePath = routePath;
|
|
895
|
+
if (!activePath) return;
|
|
896
|
+
if (!hasPendingSearchHighlightForRoute()) return;
|
|
897
|
+
|
|
898
|
+
let cancelled = false;
|
|
899
|
+
const timers: number[] = [];
|
|
900
|
+
const start = Date.now();
|
|
901
|
+
const schedule = async () => {
|
|
902
|
+
await tick();
|
|
903
|
+
const run = () => {
|
|
904
|
+
if (cancelled) return;
|
|
905
|
+
applyPendingSearchHighlights();
|
|
906
|
+
if (!hasPendingSearchHighlightForRoute()) return;
|
|
907
|
+
if (Date.now() - start > 6000) return;
|
|
908
|
+
const id = window.setTimeout(run, 250);
|
|
909
|
+
timers.push(id);
|
|
910
|
+
};
|
|
911
|
+
run();
|
|
912
|
+
};
|
|
913
|
+
|
|
914
|
+
schedule();
|
|
915
|
+
return () => {
|
|
916
|
+
cancelled = true;
|
|
917
|
+
for (const id of timers) window.clearTimeout(id);
|
|
918
|
+
};
|
|
919
|
+
});
|
|
920
|
+
|
|
921
|
+
$effect(() => {
|
|
922
|
+
const handler = () => {
|
|
923
|
+
let cancelled = false;
|
|
924
|
+
const timers: number[] = [];
|
|
925
|
+
const start = Date.now();
|
|
926
|
+
const run = async () => {
|
|
927
|
+
await tick();
|
|
928
|
+
if (cancelled) return;
|
|
929
|
+
applyPendingSearchHighlights();
|
|
930
|
+
if (!hasPendingSearchHighlightForRoute()) return;
|
|
931
|
+
if (Date.now() - start > 6000) return;
|
|
932
|
+
const id = window.setTimeout(run, 250);
|
|
933
|
+
timers.push(id);
|
|
934
|
+
};
|
|
935
|
+
run();
|
|
936
|
+
|
|
937
|
+
return () => {
|
|
938
|
+
cancelled = true;
|
|
939
|
+
for (const id of timers) window.clearTimeout(id);
|
|
940
|
+
};
|
|
941
|
+
};
|
|
942
|
+
|
|
943
|
+
const listener = () => {
|
|
944
|
+
const cleanup = handler();
|
|
945
|
+
if (cleanup) window.setTimeout(cleanup, 6500);
|
|
946
|
+
};
|
|
947
|
+
|
|
948
|
+
window.addEventListener("greg-search-highlight-request", listener);
|
|
949
|
+
return () =>
|
|
950
|
+
window.removeEventListener(
|
|
951
|
+
"greg-search-highlight-request",
|
|
952
|
+
listener,
|
|
953
|
+
);
|
|
954
|
+
});
|
|
955
|
+
|
|
956
|
+
function navigateHome(path: string) {
|
|
957
|
+
const currentPath = withoutBase(window.location.pathname);
|
|
958
|
+
const currentVersion = findActiveVersion(currentPath, versionPathPrefix);
|
|
959
|
+
if (currentVersion) {
|
|
960
|
+
window.location.assign(withBase(path));
|
|
961
|
+
return;
|
|
962
|
+
}
|
|
963
|
+
navigateInternal(path);
|
|
964
|
+
}
|
|
965
|
+
const localizedVersioningUi = $derived(
|
|
966
|
+
getLocalizedVersioningUi(versioningConfig?.locales, localeContext.key),
|
|
967
|
+
);
|
|
968
|
+
const resolvedVersioningUi = $derived({
|
|
969
|
+
...globalVersioningUi,
|
|
970
|
+
...localizedVersioningUi,
|
|
971
|
+
});
|
|
972
|
+
const versionMenuLabel = $derived(
|
|
973
|
+
String(resolvedVersioningUi.versionMenuLabel || "Version"),
|
|
974
|
+
);
|
|
975
|
+
const manifestUnavailableText = $derived(
|
|
976
|
+
String(
|
|
977
|
+
resolvedVersioningUi.manifestUnavailableText ||
|
|
978
|
+
"Version selector unavailable",
|
|
979
|
+
).trim(),
|
|
980
|
+
);
|
|
981
|
+
const showManifestUnavailableStatus = $derived(
|
|
982
|
+
globalVersioningUi.showManifestUnavailableStatus !== false,
|
|
983
|
+
);
|
|
984
|
+
const outdatedVersionActionLabel = $derived(
|
|
985
|
+
String(resolvedVersioningUi.outdatedVersionActionLabel || "Go to latest"),
|
|
986
|
+
);
|
|
987
|
+
const outdatedVersionMessageTemplate = $derived(
|
|
988
|
+
String(resolvedVersioningUi.outdatedVersionMessage || "").trim(),
|
|
989
|
+
);
|
|
990
|
+
const activeDocsVersion = $derived(
|
|
991
|
+
findActiveVersion(
|
|
992
|
+
withoutBase(window.location.pathname),
|
|
993
|
+
versionPathPrefix,
|
|
994
|
+
),
|
|
995
|
+
);
|
|
996
|
+
const resolvedDefaultVersion = $derived.by(() => {
|
|
997
|
+
if (!versionManifest) return null;
|
|
998
|
+
const raw = String(versionManifest.default || "").trim();
|
|
999
|
+
if (!raw) return null;
|
|
1000
|
+
const aliasTarget = versionManifest.aliases?.[raw];
|
|
1001
|
+
return String(aliasTarget || raw).trim() || null;
|
|
1002
|
+
});
|
|
1003
|
+
const defaultVersionEntry = $derived(
|
|
1004
|
+
manifestVersionOptions.find((entry) => entry.version === resolvedDefaultVersion) ?? null,
|
|
1005
|
+
);
|
|
1006
|
+
const activeVersionEntry = $derived(
|
|
1007
|
+
manifestVersionOptions.find((entry) => entry.version === activeDocsVersion) ?? null,
|
|
1008
|
+
);
|
|
1009
|
+
const activeMarkdownFetchPath = $derived.by(() => {
|
|
1010
|
+
const mdPath = router.activeMarkdownPath;
|
|
1011
|
+
if (!mdPath) return null;
|
|
1012
|
+
if (!activeDocsVersion) return withBase(mdPath);
|
|
1013
|
+
return withBase(`${versionPathPrefix}/${activeDocsVersion}${mdPath}`);
|
|
1014
|
+
});
|
|
1015
|
+
const showOutdatedVersionNotice = $derived(
|
|
1016
|
+
Boolean(
|
|
1017
|
+
activeDocsVersion &&
|
|
1018
|
+
resolvedDefaultVersion &&
|
|
1019
|
+
activeDocsVersion !== resolvedDefaultVersion,
|
|
1020
|
+
),
|
|
1021
|
+
);
|
|
1022
|
+
const versionStatusText = $derived(
|
|
1023
|
+
versionManifestLoadError &&
|
|
1024
|
+
hasVersionDefinitions &&
|
|
1025
|
+
showManifestUnavailableStatus
|
|
1026
|
+
? manifestUnavailableText
|
|
1027
|
+
: "",
|
|
1028
|
+
);
|
|
1029
|
+
const mainTitle = $derived(localeContext.mainTitle);
|
|
1030
|
+
const nav = $derived(localeContext.nav);
|
|
1031
|
+
const sidebar = $derived(localeContext.sidebar);
|
|
1032
|
+
const outline = $derived(localeContext.outline);
|
|
1033
|
+
const version = $derived(globalVersion);
|
|
1034
|
+
const localeFrontmatters = $derived.by(() => {
|
|
1035
|
+
const currentIndexKey = currentSrcDir === "/"
|
|
1036
|
+
? "/index.md"
|
|
1037
|
+
: currentSrcDir + "/index.md";
|
|
1038
|
+
const currentPrefix = currentSrcDir === "/"
|
|
1039
|
+
? "/"
|
|
1040
|
+
: currentSrcDir + "/";
|
|
1041
|
+
const inLocale = Object.fromEntries(
|
|
1042
|
+
Object.entries(frontmatters).filter(([key]) => {
|
|
1043
|
+
if (
|
|
1044
|
+
key !== currentIndexKey &&
|
|
1045
|
+
!key.startsWith(currentPrefix)
|
|
1046
|
+
) {
|
|
1047
|
+
return false;
|
|
1048
|
+
}
|
|
1049
|
+
// Root locale should not include localized subtrees (e.g. /docs/pl/*).
|
|
1050
|
+
if (currentSrcDir === normalizeSrcDir(configuredSrcDir)) {
|
|
1051
|
+
const otherLocaleRoots = localeContext.allSrcDirs.filter(
|
|
1052
|
+
(rp) => rp !== currentSrcDir,
|
|
1053
|
+
);
|
|
1054
|
+
if (
|
|
1055
|
+
otherLocaleRoots.some(
|
|
1056
|
+
(rp) =>
|
|
1057
|
+
key === (rp === "/" ? "/index.md" : rp + "/index.md") ||
|
|
1058
|
+
key.startsWith(rp === "/" ? "/" : rp + "/"),
|
|
1059
|
+
)
|
|
1060
|
+
) {
|
|
1061
|
+
return false;
|
|
1062
|
+
}
|
|
1063
|
+
}
|
|
1064
|
+
return true;
|
|
1065
|
+
}),
|
|
1066
|
+
);
|
|
1067
|
+
return inLocale;
|
|
1068
|
+
});
|
|
1069
|
+
const menu = $derived.by(() => {
|
|
1070
|
+
return Array.isArray(sidebar)
|
|
1071
|
+
? (parseSidebarConfig(
|
|
1072
|
+
sidebar,
|
|
1073
|
+
localeFrontmatters,
|
|
1074
|
+
currentSrcDir,
|
|
1075
|
+
) ??
|
|
1076
|
+
prepareMenu(
|
|
1077
|
+
localeFrontmatters,
|
|
1078
|
+
currentSrcDir,
|
|
1079
|
+
localeFrontmatters,
|
|
1080
|
+
))
|
|
1081
|
+
: prepareMenu(
|
|
1082
|
+
localeFrontmatters,
|
|
1083
|
+
currentSrcDir,
|
|
1084
|
+
localeFrontmatters,
|
|
1085
|
+
);
|
|
1086
|
+
});
|
|
1087
|
+
const flat = $derived(flattenMenu(menu));
|
|
1088
|
+
const localeSwitchItems = $derived(
|
|
1089
|
+
getLocaleSwitchItems({
|
|
1090
|
+
entries: localeContext.entries,
|
|
1091
|
+
activePath: routePath,
|
|
1092
|
+
activeSrcDir: currentSrcDir,
|
|
1093
|
+
activeLocaleKey: localeContext.key,
|
|
1094
|
+
frontmatters,
|
|
1095
|
+
preservePath: i18nRouting,
|
|
1096
|
+
}),
|
|
1097
|
+
);
|
|
1098
|
+
const hasRootLocale = $derived(
|
|
1099
|
+
localeContext.entries.some((entry) => entry.key === "/"),
|
|
1100
|
+
);
|
|
1101
|
+
const baseDocsRoot = $derived(normalizeSrcDir(configuredSrcDir));
|
|
1102
|
+
const defaultLocaleRoot = $derived(
|
|
1103
|
+
localeContext.entries[0]?.srcDir ?? baseDocsRoot,
|
|
1104
|
+
);
|
|
1105
|
+
|
|
1106
|
+
// If every locale is namespaced (e.g. /en/, /pl/), redirect `/` and the
|
|
1107
|
+
// base docs root (e.g. /docs) to the first configured locale root.
|
|
1108
|
+
$effect(() => {
|
|
1109
|
+
if (hasRootLocale) return;
|
|
1110
|
+
if (routePath !== "/" && routePath !== baseDocsRoot) return;
|
|
1111
|
+
if (routePath === defaultLocaleRoot) return;
|
|
1112
|
+
navigateInternal(defaultLocaleRoot);
|
|
1113
|
+
});
|
|
1114
|
+
|
|
1115
|
+
const langMenuLabel = $derived(
|
|
1116
|
+
localeContext.langMenuLabel ?? "Change language",
|
|
1117
|
+
);
|
|
1118
|
+
const sidebarMenuLabel = $derived(
|
|
1119
|
+
localeContext.sidebarMenuLabel ?? "Menu",
|
|
1120
|
+
);
|
|
1121
|
+
const skipToContentLabel = $derived(
|
|
1122
|
+
localeContext.skipToContentLabel ?? "Skip to content",
|
|
1123
|
+
);
|
|
1124
|
+
const returnToTopLabel = $derived(
|
|
1125
|
+
localeContext.returnToTopLabel ?? "Back to top",
|
|
1126
|
+
);
|
|
1127
|
+
const darkModeSwitchLabel = $derived(
|
|
1128
|
+
localeContext.darkModeSwitchLabel ?? "Appearance",
|
|
1129
|
+
);
|
|
1130
|
+
const lightModeSwitchTitle = $derived(
|
|
1131
|
+
localeContext.lightModeSwitchTitle ?? "Switch to light theme",
|
|
1132
|
+
);
|
|
1133
|
+
const darkModeSwitchTitle = $derived(
|
|
1134
|
+
localeContext.darkModeSwitchTitle ?? "Switch to dark theme",
|
|
1135
|
+
);
|
|
1136
|
+
const searchButtonLabel = $derived(
|
|
1137
|
+
localeContext.searchButtonLabel ?? "Search...",
|
|
1138
|
+
);
|
|
1139
|
+
const searchModalLabel = $derived(
|
|
1140
|
+
localeContext.searchModalLabel ?? "Search",
|
|
1141
|
+
);
|
|
1142
|
+
const searchPlaceholder = $derived(
|
|
1143
|
+
localeContext.searchPlaceholder ?? "Search docs...",
|
|
1144
|
+
);
|
|
1145
|
+
const searchLoadingText = $derived(
|
|
1146
|
+
localeContext.searchLoadingText ?? "Loading index...",
|
|
1147
|
+
);
|
|
1148
|
+
const searchErrorText = $derived(
|
|
1149
|
+
localeContext.searchErrorText ?? "Failed to load search index.",
|
|
1150
|
+
);
|
|
1151
|
+
const searchSearchingText = $derived(
|
|
1152
|
+
localeContext.searchSearchingText ?? "Searching...",
|
|
1153
|
+
);
|
|
1154
|
+
const searchNoResultsText = $derived(
|
|
1155
|
+
localeContext.searchNoResultsText ?? "No results for",
|
|
1156
|
+
);
|
|
1157
|
+
const searchStartText = $derived(
|
|
1158
|
+
localeContext.searchStartText ??
|
|
1159
|
+
"Start typing to search across all documentation.",
|
|
1160
|
+
);
|
|
1161
|
+
const searchResultsAriaLabel = $derived(
|
|
1162
|
+
localeContext.searchResultsAriaLabel ?? "Search results",
|
|
1163
|
+
);
|
|
1164
|
+
const searchNavigateText = $derived(
|
|
1165
|
+
localeContext.searchNavigateText ?? "navigate",
|
|
1166
|
+
);
|
|
1167
|
+
const searchSelectText = $derived(
|
|
1168
|
+
localeContext.searchSelectText ?? "open",
|
|
1169
|
+
);
|
|
1170
|
+
const searchCloseText = $derived(
|
|
1171
|
+
localeContext.searchCloseText ?? "close",
|
|
1172
|
+
);
|
|
1173
|
+
const aiTabLabel = $derived(
|
|
1174
|
+
localeContext.aiTabLabel ?? "Ask AI",
|
|
1175
|
+
);
|
|
1176
|
+
const aiPlaceholder = $derived(
|
|
1177
|
+
localeContext.aiPlaceholder ?? "Ask a question about the docs\u2026",
|
|
1178
|
+
);
|
|
1179
|
+
const aiLoadingText = $derived(
|
|
1180
|
+
localeContext.aiLoadingText ?? "Thinking\u2026",
|
|
1181
|
+
);
|
|
1182
|
+
const aiErrorText = $derived(
|
|
1183
|
+
localeContext.aiErrorText ?? "Something went wrong. Please try again.",
|
|
1184
|
+
);
|
|
1185
|
+
const aiStartText = $derived(
|
|
1186
|
+
localeContext.aiStartText ?? "Ask me anything about this documentation. My answers are based exclusively on the docs.",
|
|
1187
|
+
);
|
|
1188
|
+
const aiSourcesLabel = $derived(
|
|
1189
|
+
localeContext.aiSourcesLabel ?? "Sources",
|
|
1190
|
+
);
|
|
1191
|
+
const aiClearChatLabel = $derived(
|
|
1192
|
+
localeContext.aiClearChatLabel ?? "Clear chat",
|
|
1193
|
+
);
|
|
1194
|
+
const aiSendLabel = $derived(
|
|
1195
|
+
localeContext.aiSendLabel ?? "Send",
|
|
1196
|
+
);
|
|
1197
|
+
const docFooterPrevLabel = $derived(
|
|
1198
|
+
localeContext.docFooter?.prev ?? "Previous",
|
|
1199
|
+
);
|
|
1200
|
+
const docFooterNextLabel = $derived(
|
|
1201
|
+
localeContext.docFooter?.next ?? "Next",
|
|
1202
|
+
);
|
|
1203
|
+
const siteTitle = $derived(
|
|
1204
|
+
localeContext.siteTitle === undefined
|
|
1205
|
+
? mainTitle
|
|
1206
|
+
: localeContext.siteTitle,
|
|
1207
|
+
);
|
|
1208
|
+
const logo = $derived(localeContext.logo);
|
|
1209
|
+
const socialLinks = $derived(localeContext.socialLinks ?? []);
|
|
1210
|
+
const editLink = $derived(localeContext.editLink);
|
|
1211
|
+
const footer = $derived(localeContext.footer);
|
|
1212
|
+
const asideMode = $derived(localeContext.aside ?? true);
|
|
1213
|
+
const externalLinkIcon = $derived(localeContext.externalLinkIcon ?? false);
|
|
1214
|
+
|
|
1215
|
+
const lastModified = $derived.by(() => {
|
|
1216
|
+
const hasVisibilityToggle = Boolean(globalLastModified);
|
|
1217
|
+
const hasLastUpdatedConfig =
|
|
1218
|
+
Boolean(globalLastUpdated) ||
|
|
1219
|
+
Boolean(localeContext.lastUpdated) ||
|
|
1220
|
+
Boolean(localeContext.lastUpdatedText);
|
|
1221
|
+
|
|
1222
|
+
if (!hasVisibilityToggle && !hasLastUpdatedConfig) return false;
|
|
1223
|
+
|
|
1224
|
+
const base =
|
|
1225
|
+
(typeof globalLastModified === "object"
|
|
1226
|
+
? globalLastModified
|
|
1227
|
+
: globalLastUpdated) ?? {};
|
|
1228
|
+
|
|
1229
|
+
if (typeof globalLastModified === "object" || globalLastUpdated) {
|
|
1230
|
+
return {
|
|
1231
|
+
...base,
|
|
1232
|
+
formatOptions:
|
|
1233
|
+
localeContext.lastUpdated?.formatOptions ??
|
|
1234
|
+
base.formatOptions,
|
|
1235
|
+
text:
|
|
1236
|
+
localeContext.lastUpdated?.text ??
|
|
1237
|
+
localeContext.lastUpdatedText ??
|
|
1238
|
+
base.text,
|
|
1239
|
+
};
|
|
1240
|
+
}
|
|
1241
|
+
if (localeContext.lastUpdated || localeContext.lastUpdatedText) {
|
|
1242
|
+
return {
|
|
1243
|
+
text:
|
|
1244
|
+
localeContext.lastUpdated?.text ??
|
|
1245
|
+
localeContext.lastUpdatedText,
|
|
1246
|
+
formatOptions: localeContext.lastUpdated?.formatOptions,
|
|
1247
|
+
};
|
|
1248
|
+
}
|
|
1249
|
+
return true;
|
|
1250
|
+
});
|
|
1251
|
+
|
|
1252
|
+
function resolveLastModifiedFormat(
|
|
1253
|
+
value: unknown,
|
|
1254
|
+
): Intl.DateTimeFormatOptions {
|
|
1255
|
+
const source =
|
|
1256
|
+
value && typeof value === "object" && "formatOptions" in value
|
|
1257
|
+
? (value as any).formatOptions
|
|
1258
|
+
: null;
|
|
1259
|
+
if (!source || typeof source !== "object") {
|
|
1260
|
+
return { dateStyle: "medium" };
|
|
1261
|
+
}
|
|
1262
|
+
const { forceLocale: _forceLocale, ...rest } = source as any;
|
|
1263
|
+
return Object.keys(rest).length ? rest : { dateStyle: "medium" };
|
|
1264
|
+
}
|
|
1265
|
+
|
|
1266
|
+
function resolveLastModifiedLocale(value: unknown): string {
|
|
1267
|
+
const source =
|
|
1268
|
+
value && typeof value === "object" && "formatOptions" in value
|
|
1269
|
+
? (value as any).formatOptions
|
|
1270
|
+
: null;
|
|
1271
|
+
const forceLocale =
|
|
1272
|
+
Boolean(source && typeof source === "object" && (source as any).forceLocale);
|
|
1273
|
+
const explicitLocale =
|
|
1274
|
+
value && typeof value === "object" && "locale" in value
|
|
1275
|
+
? (value as any).locale
|
|
1276
|
+
: null;
|
|
1277
|
+
|
|
1278
|
+
if (explicitLocale) return explicitLocale;
|
|
1279
|
+
if (forceLocale && localeContext.lang) return localeContext.lang;
|
|
1280
|
+
return navigator.language;
|
|
1281
|
+
}
|
|
1282
|
+
|
|
1283
|
+
$effect(() => {
|
|
1284
|
+
if (!localeContext.lang) return;
|
|
1285
|
+
document.documentElement.lang = localeContext.lang;
|
|
1286
|
+
});
|
|
1287
|
+
|
|
1288
|
+
$effect(() => {
|
|
1289
|
+
if (!localeContext.dir) return;
|
|
1290
|
+
document.documentElement.dir = localeContext.dir;
|
|
1291
|
+
});
|
|
1292
|
+
|
|
1293
|
+
type CompiledMarkdownModule = {
|
|
1294
|
+
default: any;
|
|
1295
|
+
};
|
|
1296
|
+
|
|
1297
|
+
// Keep this list explicit: only selected pages are compiled as Svelte modules.
|
|
1298
|
+
const compiledMarkdownModules = import.meta.glob(
|
|
1299
|
+
[
|
|
1300
|
+
"/docs/reference/home-page.md",
|
|
1301
|
+
"/docs/pl/reference/home-page.md",
|
|
1302
|
+
"/docs/reference/team-page.md",
|
|
1303
|
+
"/docs/pl/reference/team-page.md",
|
|
1304
|
+
"/docs/test.md",
|
|
1305
|
+
],
|
|
1306
|
+
) as Record<
|
|
1307
|
+
string,
|
|
1308
|
+
() => Promise<CompiledMarkdownModule>
|
|
1309
|
+
>;
|
|
1310
|
+
|
|
1311
|
+
const hasCompiledMarkdownModule = $derived.by(() => {
|
|
1312
|
+
const mdPath = router.activeMarkdownPath;
|
|
1313
|
+
if (!mdPath) return false;
|
|
1314
|
+
if (compiledMarkdownModules[mdPath]) return true;
|
|
1315
|
+
return Object.keys(compiledMarkdownModules).some(
|
|
1316
|
+
(key) => key.endsWith(mdPath) || mdPath.endsWith(key),
|
|
1317
|
+
);
|
|
1318
|
+
});
|
|
1319
|
+
|
|
1320
|
+
function resolveCompiledLoader(mdPath: string) {
|
|
1321
|
+
const exact = compiledMarkdownModules[mdPath];
|
|
1322
|
+
if (exact) return exact;
|
|
1323
|
+
|
|
1324
|
+
const fallbackKey = Object.keys(compiledMarkdownModules).find(
|
|
1325
|
+
(key) => key.endsWith(mdPath) || mdPath.endsWith(key),
|
|
1326
|
+
);
|
|
1327
|
+
return fallbackKey ? compiledMarkdownModules[fallbackKey] : undefined;
|
|
1328
|
+
}
|
|
1329
|
+
|
|
1330
|
+
async function loadCompiledMarkdown(mdPath: string) {
|
|
1331
|
+
const loader = resolveCompiledLoader(mdPath);
|
|
1332
|
+
if (!loader) return null;
|
|
1333
|
+
try {
|
|
1334
|
+
return await loader();
|
|
1335
|
+
} catch (err) {
|
|
1336
|
+
console.error("[greg] Failed to load compiled markdown module", {
|
|
1337
|
+
mdPath,
|
|
1338
|
+
err,
|
|
1339
|
+
});
|
|
1340
|
+
return null;
|
|
1341
|
+
}
|
|
1342
|
+
}
|
|
1343
|
+
|
|
1344
|
+
const title = $derived(router.title(flat));
|
|
1345
|
+
|
|
1346
|
+
// -- Content fetch -----------------------------------------------------------
|
|
1347
|
+
async function fetchMarkdown(mdPath: string): Promise<string> {
|
|
1348
|
+
const res = await fetch(mdPath);
|
|
1349
|
+
if (!res.ok)
|
|
1350
|
+
throw new Error(`${res.status} ${res.statusText} ďż˝ ${mdPath}`);
|
|
1351
|
+
return res.text();
|
|
1352
|
+
}
|
|
1353
|
+
|
|
1354
|
+
// -- Layout ------------------------------------------------------------------
|
|
1355
|
+
// Resolve the key of the active page in the frontmatter map so we can read
|
|
1356
|
+
// `layout` (and any other fields) without loading the full module.
|
|
1357
|
+
const activeKey = $derived.by(() => {
|
|
1358
|
+
const rel = routePath
|
|
1359
|
+
.replace(currentSrcDir, "")
|
|
1360
|
+
.replace(/^\//, "");
|
|
1361
|
+
const candidates: string[] = rel
|
|
1362
|
+
? (
|
|
1363
|
+
currentSrcDir === "/"
|
|
1364
|
+
? [
|
|
1365
|
+
`/${rel}.md`,
|
|
1366
|
+
`/${rel}/index.md`,
|
|
1367
|
+
]
|
|
1368
|
+
: [
|
|
1369
|
+
`${currentSrcDir}/${rel}.md`,
|
|
1370
|
+
`${currentSrcDir}/${rel}/index.md`,
|
|
1371
|
+
]
|
|
1372
|
+
)
|
|
1373
|
+
: (currentSrcDir === "/"
|
|
1374
|
+
? [`/index.md`]
|
|
1375
|
+
: [`${currentSrcDir}/index.md`, `${currentSrcDir}index.md`]);
|
|
1376
|
+
return candidates.find((c) => c in frontmatters) ?? null;
|
|
1377
|
+
});
|
|
1378
|
+
|
|
1379
|
+
const activeFrontmatter = $derived(
|
|
1380
|
+
activeKey ? frontmatters[activeKey] : undefined,
|
|
1381
|
+
);
|
|
1382
|
+
|
|
1383
|
+
/** Apply withBase to a hero image src string or themed-image object. */
|
|
1384
|
+
function applyBaseToImage(image: unknown): unknown {
|
|
1385
|
+
if (!image) return image;
|
|
1386
|
+
if (typeof image === "string") return withBase(image);
|
|
1387
|
+
const img = image as Record<string, unknown>;
|
|
1388
|
+
if ("light" in img || "dark" in img) {
|
|
1389
|
+
return {
|
|
1390
|
+
...img,
|
|
1391
|
+
light: typeof img.light === "string" ? withBase(img.light) : img.light,
|
|
1392
|
+
dark: typeof img.dark === "string" ? withBase(img.dark) : img.dark,
|
|
1393
|
+
};
|
|
1394
|
+
}
|
|
1395
|
+
if ("src" in img) return { ...img, src: typeof img.src === "string" ? withBase(img.src as string) : img.src };
|
|
1396
|
+
return image;
|
|
1397
|
+
}
|
|
1398
|
+
|
|
1399
|
+
/** Resolve a frontmatter link so bare absolute paths get the currentSrcDir prefix. */
|
|
1400
|
+
const resolvedHero = $derived.by(() => {
|
|
1401
|
+
const hero = (activeFrontmatter as any)?.hero;
|
|
1402
|
+
if (!hero) return undefined;
|
|
1403
|
+
return {
|
|
1404
|
+
...hero,
|
|
1405
|
+
image: applyBaseToImage(hero.image),
|
|
1406
|
+
actions: hero.actions?.map((a: { link: string; [k: string]: unknown }) => ({
|
|
1407
|
+
...a,
|
|
1408
|
+
link: (() => {
|
|
1409
|
+
const link: string = a.link ?? "";
|
|
1410
|
+
if (!link || !link.startsWith("/")) return link;
|
|
1411
|
+
if (link === currentSrcDir || link.startsWith(currentSrcDir + "/")) return link;
|
|
1412
|
+
return normalizeSrcDir(currentSrcDir + link);
|
|
1413
|
+
})(),
|
|
1414
|
+
})),
|
|
1415
|
+
};
|
|
1416
|
+
});
|
|
1417
|
+
|
|
1418
|
+
const resolvedFeatures = $derived(
|
|
1419
|
+
(activeFrontmatter as any)?.features?.map((f: { icon?: unknown; link?: string; [k: string]: unknown }) => ({
|
|
1420
|
+
...f,
|
|
1421
|
+
icon: f.icon ? applyBaseToImage(f.icon) : f.icon,
|
|
1422
|
+
link: (() => {
|
|
1423
|
+
const link = f.link ?? "";
|
|
1424
|
+
if (!link || !link.startsWith("/")) return f.link;
|
|
1425
|
+
if (link === currentSrcDir || link.startsWith(currentSrcDir + "/")) return link;
|
|
1426
|
+
return normalizeSrcDir(currentSrcDir + link);
|
|
1427
|
+
})(),
|
|
1428
|
+
}))
|
|
1429
|
+
);
|
|
1430
|
+
|
|
1431
|
+
/** True when the URL is inside the current locale root path but no matching file exists. */
|
|
1432
|
+
const notFound = $derived(activeKey === null && routePath.startsWith(currentSrcDir));
|
|
1433
|
+
const notFoundUi = $derived.by(() => {
|
|
1434
|
+
const isPl =
|
|
1435
|
+
localeContext.key === "/pl/" ||
|
|
1436
|
+
String(localeContext.lang || "").toLowerCase().startsWith("pl");
|
|
1437
|
+
|
|
1438
|
+
if (isPl) {
|
|
1439
|
+
return {
|
|
1440
|
+
title: "Strona nie znaleziona",
|
|
1441
|
+
description: "Nie znaleziono strony dla podanego adresu.",
|
|
1442
|
+
backLabel: "Wroc do dokumentacji",
|
|
1443
|
+
};
|
|
1444
|
+
}
|
|
1445
|
+
|
|
1446
|
+
return {
|
|
1447
|
+
title: "Page not found",
|
|
1448
|
+
description: "We could not find a page for this address.",
|
|
1449
|
+
backLabel: "Back to documentation",
|
|
1450
|
+
};
|
|
1451
|
+
});
|
|
1452
|
+
const activeLayout = $derived<"doc" | "home" | "page">(
|
|
1453
|
+
activeFrontmatter?.layout ?? "doc",
|
|
1454
|
+
);
|
|
1455
|
+
const useCompiledRenderer = $derived(
|
|
1456
|
+
activeFrontmatter?.renderer === "mdsvex" || hasCompiledMarkdownModule,
|
|
1457
|
+
);
|
|
1458
|
+
|
|
1459
|
+
/** Page-level outline: reads `outline` from active page frontmatter, falls back to global setting. */
|
|
1460
|
+
const outlineNorm = $derived(
|
|
1461
|
+
activeFrontmatter?.outline !== undefined
|
|
1462
|
+
? normalizeOutline(
|
|
1463
|
+
activeFrontmatter.outline as OutlineOption | boolean,
|
|
1464
|
+
)
|
|
1465
|
+
: normalizeOutline(outline),
|
|
1466
|
+
);
|
|
1467
|
+
|
|
1468
|
+
/** Auto prev/next from sidebar order; overridable per-page via frontmatter. */
|
|
1469
|
+
const prevNextAuto = $derived(getPrevNext(routePath, flat));
|
|
1470
|
+
const prevNext = $derived({
|
|
1471
|
+
prev:
|
|
1472
|
+
activeFrontmatter?.prev === false
|
|
1473
|
+
? null
|
|
1474
|
+
: activeFrontmatter?.prev &&
|
|
1475
|
+
typeof activeFrontmatter.prev === "object"
|
|
1476
|
+
? {
|
|
1477
|
+
label: activeFrontmatter.prev.text,
|
|
1478
|
+
link: activeFrontmatter.prev.link,
|
|
1479
|
+
}
|
|
1480
|
+
: prevNextAuto.prev,
|
|
1481
|
+
next:
|
|
1482
|
+
activeFrontmatter?.next === false
|
|
1483
|
+
? null
|
|
1484
|
+
: activeFrontmatter?.next &&
|
|
1485
|
+
typeof activeFrontmatter.next === "object"
|
|
1486
|
+
? {
|
|
1487
|
+
label: activeFrontmatter.next.text,
|
|
1488
|
+
link: activeFrontmatter.next.link,
|
|
1489
|
+
}
|
|
1490
|
+
: prevNextAuto.next,
|
|
1491
|
+
});
|
|
1492
|
+
|
|
1493
|
+
/** Breadcrumb path from root to active page. */
|
|
1494
|
+
const breadcrumbItems = $derived(getBreadcrumbItems(routePath, menu));
|
|
1495
|
+
|
|
1496
|
+
/** Whether the left nav sidebar should be visible. */
|
|
1497
|
+
const showSidebar = $derived(activeLayout === "doc");
|
|
1498
|
+
/** Whether the right outline / ads aside should be visible. */
|
|
1499
|
+
const showOutline = $derived(activeLayout === "doc" && asideMode !== false);
|
|
1500
|
+
|
|
1501
|
+
// -- Splitter ----------------------------------------------------------------
|
|
1502
|
+
const sp = useSplitter();
|
|
1503
|
+
|
|
1504
|
+
setPortalsContext();
|
|
1505
|
+
|
|
1506
|
+
// -- Internal links ----------------------------------------------------------
|
|
1507
|
+
const EXTERNAL_RE = /^(?:[a-z][a-z\d+\-.]*:|\/{2})/i;
|
|
1508
|
+
|
|
1509
|
+
function handleInternalLinks(event: MouseEvent) {
|
|
1510
|
+
const anchor = (event.target as HTMLElement | null)?.closest("a");
|
|
1511
|
+
if (!anchor) return;
|
|
1512
|
+
|
|
1513
|
+
const href = anchor.getAttribute("href");
|
|
1514
|
+
if (!href) return;
|
|
1515
|
+
|
|
1516
|
+
const explicitTarget = anchor.getAttribute("target");
|
|
1517
|
+
if (explicitTarget && explicitTarget !== "_self") {
|
|
1518
|
+
event.preventDefault();
|
|
1519
|
+
const destination = anchor.href || href;
|
|
1520
|
+
if (explicitTarget === "_blank") {
|
|
1521
|
+
window.open(destination, "_blank", "noopener,noreferrer");
|
|
1522
|
+
} else {
|
|
1523
|
+
window.open(destination, explicitTarget);
|
|
1524
|
+
}
|
|
1525
|
+
return;
|
|
1526
|
+
}
|
|
1527
|
+
|
|
1528
|
+
if (EXTERNAL_RE.test(href)) return;
|
|
1529
|
+
|
|
1530
|
+
if (href.startsWith("#")) {
|
|
1531
|
+
event.preventDefault();
|
|
1532
|
+
navigateInternalWithAnchor(routePath, href.slice(1));
|
|
1533
|
+
return;
|
|
1534
|
+
}
|
|
1535
|
+
|
|
1536
|
+
const hashIdx = href.indexOf("#");
|
|
1537
|
+
const pathPart = hashIdx >= 0 ? href.slice(0, hashIdx) : href;
|
|
1538
|
+
const hashPart = hashIdx >= 0 ? href.slice(hashIdx + 1) : "";
|
|
1539
|
+
const pathWithoutBase = withoutBase(pathPart);
|
|
1540
|
+
|
|
1541
|
+
const cleanSrcDir = currentSrcDir.replace(/\/+$/, "");
|
|
1542
|
+
|
|
1543
|
+
let resolvedPath: string;
|
|
1544
|
+
if (pathWithoutBase.startsWith("/")) {
|
|
1545
|
+
// In markdown docs, leading "/" is docs-root relative (srcDir),
|
|
1546
|
+
// matching include semantics.
|
|
1547
|
+
if (
|
|
1548
|
+
pathWithoutBase === cleanSrcDir ||
|
|
1549
|
+
pathWithoutBase.startsWith(cleanSrcDir + "/")
|
|
1550
|
+
) {
|
|
1551
|
+
resolvedPath = pathWithoutBase;
|
|
1552
|
+
} else {
|
|
1553
|
+
resolvedPath = cleanSrcDir + pathWithoutBase;
|
|
1554
|
+
}
|
|
1555
|
+
} else {
|
|
1556
|
+
try {
|
|
1557
|
+
// Resolve relative links against the current markdown file directory,
|
|
1558
|
+
// not the route path (which may not end with "/").
|
|
1559
|
+
const mdPath =
|
|
1560
|
+
router.activeMarkdownPath ?? `${routePath}.md`;
|
|
1561
|
+
const mdDir = mdPath.slice(0, mdPath.lastIndexOf("/") + 1);
|
|
1562
|
+
resolvedPath = new URL(pathPart, window.location.origin + mdDir)
|
|
1563
|
+
.pathname;
|
|
1564
|
+
} catch {
|
|
1565
|
+
return;
|
|
1566
|
+
}
|
|
1567
|
+
}
|
|
1568
|
+
|
|
1569
|
+
resolvedPath = resolvedPath.replace(/\.(md|html)$/i, "");
|
|
1570
|
+
resolvedPath =
|
|
1571
|
+
resolvedPath.replace(/\/index$/, "") || currentSrcDir;
|
|
1572
|
+
|
|
1573
|
+
event.preventDefault();
|
|
1574
|
+
navigateInternalWithAnchor(resolvedPath, hashPart || undefined);
|
|
1575
|
+
}
|
|
1576
|
+
</script>
|
|
1577
|
+
|
|
1578
|
+
<!-- svelte-ignore a11y_no_noninteractive_element_interactions, a11y_no_static_element_interactions -->
|
|
1579
|
+
<div
|
|
1580
|
+
class="greg"
|
|
1581
|
+
data-theme={theme}
|
|
1582
|
+
data-external-link-icon={externalLinkIcon ? "true" : "false"}
|
|
1583
|
+
onmousemove={sp.onMouseMove}
|
|
1584
|
+
onmouseup={sp.onMouseUp}
|
|
1585
|
+
onclick={handleCodeGroupClick}
|
|
1586
|
+
onkeydown={handleCodeGroupKeydown}
|
|
1587
|
+
>
|
|
1588
|
+
<a class="skip-link" href="#greg-main-content">{skipToContentLabel}</a>
|
|
1589
|
+
<DocsSiteHeader
|
|
1590
|
+
srcDir={currentSrcDir}
|
|
1591
|
+
{siteTitle}
|
|
1592
|
+
{logo}
|
|
1593
|
+
{socialLinks}
|
|
1594
|
+
{mainTitle}
|
|
1595
|
+
{version}
|
|
1596
|
+
{nav}
|
|
1597
|
+
locales={localeSwitchItems}
|
|
1598
|
+
{langMenuLabel}
|
|
1599
|
+
{theme}
|
|
1600
|
+
{darkModeSwitchLabel}
|
|
1601
|
+
{lightModeSwitchTitle}
|
|
1602
|
+
{darkModeSwitchTitle}
|
|
1603
|
+
{searchButtonLabel}
|
|
1604
|
+
showSearch={searchEnabled}
|
|
1605
|
+
versionOptions={manifestVersionOptions}
|
|
1606
|
+
activeVersion={activeDocsVersion}
|
|
1607
|
+
{versionMenuLabel}
|
|
1608
|
+
{versionStatusText}
|
|
1609
|
+
onVersionChange={navigateToVersion}
|
|
1610
|
+
onThemeChange={(t) => setThemeManually(t)}
|
|
1611
|
+
navigate={navigateInternal}
|
|
1612
|
+
{navigateHome}
|
|
1613
|
+
onOpenSearch={() => (searchOpen = true)}
|
|
1614
|
+
/>
|
|
1615
|
+
|
|
1616
|
+
{#if showOutdatedVersionNotice && defaultVersionEntry && activeVersionEntry}
|
|
1617
|
+
<VersionOutdatedNotice
|
|
1618
|
+
currentTitle={activeVersionEntry.title}
|
|
1619
|
+
defaultTitle={defaultVersionEntry.title}
|
|
1620
|
+
message={formatOutdatedMessage(
|
|
1621
|
+
activeVersionEntry.title,
|
|
1622
|
+
defaultVersionEntry.title,
|
|
1623
|
+
outdatedVersionMessageTemplate,
|
|
1624
|
+
)}
|
|
1625
|
+
actionLabel={outdatedVersionActionLabel}
|
|
1626
|
+
onGoToDefault={() => navigateToVersion(defaultVersionEntry.version)}
|
|
1627
|
+
/>
|
|
1628
|
+
{/if}
|
|
1629
|
+
|
|
1630
|
+
<div class="greg-body" class:aside-left={asideMode === "left"}>
|
|
1631
|
+
{#if showSidebar}
|
|
1632
|
+
<aside bind:this={sp.aside}>
|
|
1633
|
+
<DocsNavigation
|
|
1634
|
+
{menu}
|
|
1635
|
+
srcDir={currentSrcDir}
|
|
1636
|
+
ariaLabel={sidebarMenuLabel}
|
|
1637
|
+
active={routePath}
|
|
1638
|
+
navigate={navigateInternal}
|
|
1639
|
+
/>
|
|
1640
|
+
</aside>
|
|
1641
|
+
<!-- svelte-ignore a11y_no_noninteractive_element_interactions, a11y_no_static_element_interactions -->
|
|
1642
|
+
<div
|
|
1643
|
+
class="splitter"
|
|
1644
|
+
bind:this={sp.splitter}
|
|
1645
|
+
onmousedown={sp.onMouseDown}
|
|
1646
|
+
>
|
|
1647
|
+
<span class="splitter-handle" aria-hidden="true">
|
|
1648
|
+
<EllipsisVertical size={18} />
|
|
1649
|
+
</span>
|
|
1650
|
+
</div>
|
|
1651
|
+
{/if}
|
|
1652
|
+
<!-- svelte-ignore a11y_click_events_have_key_events, a11y_no_noninteractive_element_interactions -->
|
|
1653
|
+
<main
|
|
1654
|
+
id="greg-main-content"
|
|
1655
|
+
bind:this={mainEl}
|
|
1656
|
+
class:layout-home={activeLayout === "home"}
|
|
1657
|
+
class:layout-page={activeLayout === "page"}
|
|
1658
|
+
onclick={handleInternalLinks}
|
|
1659
|
+
tabindex="-1"
|
|
1660
|
+
>
|
|
1661
|
+
{#if activeLayout === "home"}
|
|
1662
|
+
<LayoutHome
|
|
1663
|
+
hero={resolvedHero}
|
|
1664
|
+
features={resolvedFeatures}
|
|
1665
|
+
/>
|
|
1666
|
+
{:else if router.activeMarkdownPath}
|
|
1667
|
+
{#if useCompiledRenderer}
|
|
1668
|
+
{#await loadCompiledMarkdown(router.activeMarkdownPath)}
|
|
1669
|
+
<div class="spinner-wrap">
|
|
1670
|
+
<Spinner />
|
|
1671
|
+
</div>
|
|
1672
|
+
{:then compiledModule}
|
|
1673
|
+
{#if compiledModule}
|
|
1674
|
+
{#if breadcrumb}
|
|
1675
|
+
<Breadcrumb
|
|
1676
|
+
items={breadcrumbItems}
|
|
1677
|
+
navigate={navigateInternal}
|
|
1678
|
+
srcDir={currentSrcDir}
|
|
1679
|
+
/>
|
|
1680
|
+
{/if}
|
|
1681
|
+
{@const CompiledPage = compiledModule.default}
|
|
1682
|
+
<div class="markdown-renderer markdown-body">
|
|
1683
|
+
<CompiledPage />
|
|
1684
|
+
</div>
|
|
1685
|
+
{#if lastModified && activeFrontmatter?._mtime}
|
|
1686
|
+
{@const lmLabel =
|
|
1687
|
+
(typeof lastModified === "object"
|
|
1688
|
+
? lastModified.text
|
|
1689
|
+
: null) ?? "Last updated:"}
|
|
1690
|
+
{@const lmFmt =
|
|
1691
|
+
resolveLastModifiedFormat(lastModified)}
|
|
1692
|
+
{@const lmLocale =
|
|
1693
|
+
resolveLastModifiedLocale(lastModified)}
|
|
1694
|
+
<p class="doc-last-modified">
|
|
1695
|
+
{lmLabel}
|
|
1696
|
+
{new Intl.DateTimeFormat(
|
|
1697
|
+
lmLocale,
|
|
1698
|
+
lmFmt,
|
|
1699
|
+
).format(new Date(activeFrontmatter._mtime))}
|
|
1700
|
+
</p>
|
|
1701
|
+
{/if}
|
|
1702
|
+
{#if activeLayout === "doc"}
|
|
1703
|
+
<PrevNext
|
|
1704
|
+
prev={prevNext.prev}
|
|
1705
|
+
next={prevNext.next}
|
|
1706
|
+
prevLabel={docFooterPrevLabel || undefined}
|
|
1707
|
+
nextLabel={docFooterNextLabel || undefined}
|
|
1708
|
+
navigate={navigateInternal}
|
|
1709
|
+
/>
|
|
1710
|
+
{/if}
|
|
1711
|
+
{:else}
|
|
1712
|
+
<p class="fetch-error">Could not load page.</p>
|
|
1713
|
+
{/if}
|
|
1714
|
+
{:catch}
|
|
1715
|
+
<p class="fetch-error">Could not load page.</p>
|
|
1716
|
+
{/await}
|
|
1717
|
+
{:else}
|
|
1718
|
+
{#await fetchMarkdown(activeMarkdownFetchPath || router.activeMarkdownPath)}
|
|
1719
|
+
<div class="spinner-wrap">
|
|
1720
|
+
<Spinner />
|
|
1721
|
+
</div>
|
|
1722
|
+
{:then markdown}
|
|
1723
|
+
{#if breadcrumb}
|
|
1724
|
+
<Breadcrumb
|
|
1725
|
+
items={breadcrumbItems}
|
|
1726
|
+
navigate={navigateInternal}
|
|
1727
|
+
srcDir={currentSrcDir}
|
|
1728
|
+
/>
|
|
1729
|
+
{/if}
|
|
1730
|
+
<MarkdownRenderer
|
|
1731
|
+
{markdown}
|
|
1732
|
+
baseUrl={activeMarkdownFetchPath || router.activeMarkdownPath}
|
|
1733
|
+
docsPrefix={withBase(currentSrcDir)}
|
|
1734
|
+
{mermaidTheme}
|
|
1735
|
+
{mermaidThemes}
|
|
1736
|
+
colorTheme={theme}
|
|
1737
|
+
/>
|
|
1738
|
+
{#if lastModified && activeFrontmatter?._mtime}
|
|
1739
|
+
{@const lmLabel =
|
|
1740
|
+
(typeof lastModified === "object"
|
|
1741
|
+
? lastModified.text
|
|
1742
|
+
: null) ?? "Last updated:"}
|
|
1743
|
+
{@const lmFmt =
|
|
1744
|
+
resolveLastModifiedFormat(lastModified)}
|
|
1745
|
+
{@const lmLocale =
|
|
1746
|
+
resolveLastModifiedLocale(lastModified)}
|
|
1747
|
+
<p class="doc-last-modified">
|
|
1748
|
+
{lmLabel}
|
|
1749
|
+
{new Intl.DateTimeFormat(lmLocale, lmFmt).format(
|
|
1750
|
+
new Date(activeFrontmatter._mtime),
|
|
1751
|
+
)}
|
|
1752
|
+
</p>
|
|
1753
|
+
{/if}
|
|
1754
|
+
{#if
|
|
1755
|
+
editLink?.pattern &&
|
|
1756
|
+
router.activeMarkdownPath &&
|
|
1757
|
+
activeLayout === "doc"
|
|
1758
|
+
}
|
|
1759
|
+
{@const editPath = router.activeMarkdownPath
|
|
1760
|
+
.replace(normalizeSrcDir(configuredSrcDir), "")
|
|
1761
|
+
.replace(/^\//, "")}
|
|
1762
|
+
<p class="doc-edit-link">
|
|
1763
|
+
<a
|
|
1764
|
+
href={editLink.pattern.replace(
|
|
1765
|
+
/:path/g,
|
|
1766
|
+
editPath,
|
|
1767
|
+
)}
|
|
1768
|
+
target="_blank"
|
|
1769
|
+
rel="noopener noreferrer"
|
|
1770
|
+
>{editLink.text ?? "Edit this page"}</a>
|
|
1771
|
+
</p>
|
|
1772
|
+
{/if}
|
|
1773
|
+
{#if
|
|
1774
|
+
editLink?.pattern &&
|
|
1775
|
+
router.activeMarkdownPath &&
|
|
1776
|
+
activeLayout === "doc"
|
|
1777
|
+
}
|
|
1778
|
+
{@const editPath = router.activeMarkdownPath
|
|
1779
|
+
.replace(normalizeSrcDir(configuredSrcDir), "")
|
|
1780
|
+
.replace(/^\//, "")}
|
|
1781
|
+
<p class="doc-edit-link">
|
|
1782
|
+
<a
|
|
1783
|
+
href={editLink.pattern.replace(
|
|
1784
|
+
/:path/g,
|
|
1785
|
+
editPath,
|
|
1786
|
+
)}
|
|
1787
|
+
target="_blank"
|
|
1788
|
+
rel="noopener noreferrer"
|
|
1789
|
+
>{editLink.text ?? "Edit this page"}</a>
|
|
1790
|
+
</p>
|
|
1791
|
+
{/if}
|
|
1792
|
+
{#if activeLayout === "doc"}
|
|
1793
|
+
<PrevNext
|
|
1794
|
+
prev={prevNext.prev}
|
|
1795
|
+
next={prevNext.next}
|
|
1796
|
+
prevLabel={docFooterPrevLabel || undefined}
|
|
1797
|
+
nextLabel={docFooterNextLabel || undefined}
|
|
1798
|
+
navigate={navigateInternal}
|
|
1799
|
+
/>
|
|
1800
|
+
{/if}
|
|
1801
|
+
{:catch}
|
|
1802
|
+
<p class="fetch-error">Could not load page.</p>
|
|
1803
|
+
{/await}
|
|
1804
|
+
{/if}
|
|
1805
|
+
{:else if notFound}
|
|
1806
|
+
<div class="not-found">
|
|
1807
|
+
<p class="not-found-code">404</p>
|
|
1808
|
+
<h1>{notFoundUi.title}</h1>
|
|
1809
|
+
<p>{notFoundUi.description}</p>
|
|
1810
|
+
<p class="not-found-path"><code>{router.active}</code></p>
|
|
1811
|
+
<!-- svelte-ignore a11y_invalid_attribute -->
|
|
1812
|
+
<a
|
|
1813
|
+
href={withBase(currentSrcDir)}
|
|
1814
|
+
onclick={(e) => {
|
|
1815
|
+
e.preventDefault();
|
|
1816
|
+
navigateInternal(currentSrcDir);
|
|
1817
|
+
}}>{notFoundUi.backLabel}</a
|
|
1818
|
+
>
|
|
1819
|
+
</div>
|
|
1820
|
+
{:else if title}
|
|
1821
|
+
<h1>{title}</h1>
|
|
1822
|
+
{@render children?.()}
|
|
1823
|
+
{/if}
|
|
1824
|
+
</main>
|
|
1825
|
+
<aside
|
|
1826
|
+
class="greg-aside-outline"
|
|
1827
|
+
class:hidden={(!outlineNorm && !carbonAds) || !showOutline || asideMode === false}
|
|
1828
|
+
>
|
|
1829
|
+
{#if outlineNorm && showOutline}
|
|
1830
|
+
<Outline
|
|
1831
|
+
container={mainEl}
|
|
1832
|
+
level={outlineNorm.level}
|
|
1833
|
+
label={outlineNorm.label}
|
|
1834
|
+
active={routePath}
|
|
1835
|
+
/>
|
|
1836
|
+
{/if}
|
|
1837
|
+
{#if carbonAds && showOutline}
|
|
1838
|
+
<CarbonAds
|
|
1839
|
+
code={carbonAds.code}
|
|
1840
|
+
placement={carbonAds.placement}
|
|
1841
|
+
active={routePath}
|
|
1842
|
+
/>
|
|
1843
|
+
{/if}
|
|
1844
|
+
</aside>
|
|
1845
|
+
</div>
|
|
1846
|
+
{#if
|
|
1847
|
+
!showSidebar &&
|
|
1848
|
+
(footer?.message || footer?.copyright)
|
|
1849
|
+
}
|
|
1850
|
+
<footer class="doc-footer">
|
|
1851
|
+
{#if footer?.message}<p>{footer.message}</p>{/if}
|
|
1852
|
+
{#if footer?.copyright}<p>{footer.copyright}</p>{/if}
|
|
1853
|
+
</footer>
|
|
1854
|
+
{/if}
|
|
1855
|
+
{#if searchEnabled}
|
|
1856
|
+
<SearchModal
|
|
1857
|
+
bind:open={searchOpen}
|
|
1858
|
+
onClose={() => (searchOpen = false)}
|
|
1859
|
+
onNavigate={navigateInternalWithAnchor}
|
|
1860
|
+
localeSrcDir={currentSrcDir}
|
|
1861
|
+
allLocaleSrcDirs={localeContext.allSrcDirs}
|
|
1862
|
+
baseSrcDir={normalizeSrcDir(configuredSrcDir)}
|
|
1863
|
+
{searchModalLabel}
|
|
1864
|
+
{searchPlaceholder}
|
|
1865
|
+
{searchLoadingText}
|
|
1866
|
+
{searchErrorText}
|
|
1867
|
+
{searchSearchingText}
|
|
1868
|
+
{searchNoResultsText}
|
|
1869
|
+
{searchStartText}
|
|
1870
|
+
{searchResultsAriaLabel}
|
|
1871
|
+
{searchNavigateText}
|
|
1872
|
+
{searchSelectText}
|
|
1873
|
+
{searchCloseText}
|
|
1874
|
+
{aiTabLabel}
|
|
1875
|
+
{aiPlaceholder}
|
|
1876
|
+
{aiLoadingText}
|
|
1877
|
+
{aiErrorText}
|
|
1878
|
+
{aiStartText}
|
|
1879
|
+
{aiSourcesLabel}
|
|
1880
|
+
{aiClearChatLabel}
|
|
1881
|
+
{aiSendLabel}
|
|
1882
|
+
{searchProvider}
|
|
1883
|
+
/>
|
|
1884
|
+
{/if}
|
|
1885
|
+
{#if backToTop}
|
|
1886
|
+
<BackToTop target={mainEl} label={returnToTopLabel} />
|
|
1887
|
+
{/if}
|
|
1888
|
+
</div>
|
|
1889
|
+
|
|
1890
|
+
<style lang="scss">
|
|
1891
|
+
.skip-link {
|
|
1892
|
+
position: absolute;
|
|
1893
|
+
left: 0.75rem;
|
|
1894
|
+
top: 0.5rem;
|
|
1895
|
+
z-index: 1000;
|
|
1896
|
+
padding: 0.4rem 0.6rem;
|
|
1897
|
+
border-radius: 0.4rem;
|
|
1898
|
+
border: 1px solid var(--greg-border-color);
|
|
1899
|
+
background: var(--greg-header-background);
|
|
1900
|
+
color: var(--greg-color);
|
|
1901
|
+
text-decoration: none;
|
|
1902
|
+
transform: translateY(-150%);
|
|
1903
|
+
transition: transform 0.15s ease;
|
|
1904
|
+
}
|
|
1905
|
+
|
|
1906
|
+
.skip-link:focus-visible {
|
|
1907
|
+
transform: translateY(0);
|
|
1908
|
+
outline: 2px solid var(--greg-accent);
|
|
1909
|
+
outline-offset: 2px;
|
|
1910
|
+
}
|
|
1911
|
+
|
|
1912
|
+
.greg {
|
|
1913
|
+
display: flex;
|
|
1914
|
+
flex-flow: column nowrap;
|
|
1915
|
+
background-color: var(--greg-background);
|
|
1916
|
+
color: var(--greg-color);
|
|
1917
|
+
height: 100vh;
|
|
1918
|
+
overflow: hidden;
|
|
1919
|
+
}
|
|
1920
|
+
|
|
1921
|
+
/* -- Body layout ---------------------------------------------------------- */
|
|
1922
|
+
.greg-body {
|
|
1923
|
+
display: flex;
|
|
1924
|
+
flex-flow: row nowrap;
|
|
1925
|
+
flex: 1;
|
|
1926
|
+
overflow: hidden;
|
|
1927
|
+
width: 100%;
|
|
1928
|
+
|
|
1929
|
+
&.aside-left {
|
|
1930
|
+
.greg-aside-outline {
|
|
1931
|
+
order: 1;
|
|
1932
|
+
border-left: none;
|
|
1933
|
+
border-right: 1px solid var(--greg-border-color);
|
|
1934
|
+
}
|
|
1935
|
+
|
|
1936
|
+
main {
|
|
1937
|
+
order: 2;
|
|
1938
|
+
}
|
|
1939
|
+
}
|
|
1940
|
+
}
|
|
1941
|
+
|
|
1942
|
+
.splitter {
|
|
1943
|
+
background-color: transparent;
|
|
1944
|
+
border-left: 1px solid var(--greg-border-color);
|
|
1945
|
+
width: 7px;
|
|
1946
|
+
height: 100%;
|
|
1947
|
+
cursor: col-resize;
|
|
1948
|
+
flex-shrink: 0;
|
|
1949
|
+
display: flex;
|
|
1950
|
+
align-items: center;
|
|
1951
|
+
justify-content: center;
|
|
1952
|
+
user-select: none;
|
|
1953
|
+
transition: background-color 0.15s;
|
|
1954
|
+
|
|
1955
|
+
.splitter-handle {
|
|
1956
|
+
width: 7px;
|
|
1957
|
+
height: 32px;
|
|
1958
|
+
color: var(--greg-splitter-handler-color);
|
|
1959
|
+
background-color: var(--greg-splitter-handler-background);
|
|
1960
|
+
border-radius: 0 7px 7px 0;
|
|
1961
|
+
transition: color 0.15s;
|
|
1962
|
+
pointer-events: none;
|
|
1963
|
+
display: flex;
|
|
1964
|
+
align-items: center;
|
|
1965
|
+
justify-content: center;
|
|
1966
|
+
overflow: hidden;
|
|
1967
|
+
|
|
1968
|
+
:global(svg) {
|
|
1969
|
+
display: block;
|
|
1970
|
+
margin: 0 auto;
|
|
1971
|
+
flex-shrink: 0;
|
|
1972
|
+
padding: 0;
|
|
1973
|
+
}
|
|
1974
|
+
|
|
1975
|
+
}
|
|
1976
|
+
|
|
1977
|
+
&:hover {
|
|
1978
|
+
border-color: var(--greg-splitter-active-border);
|
|
1979
|
+
|
|
1980
|
+
.splitter-handle {
|
|
1981
|
+
background-color: var(--greg-splitter-active-handler-background);
|
|
1982
|
+
color: var(--greg-splitter-active-handler-color);
|
|
1983
|
+
}
|
|
1984
|
+
}
|
|
1985
|
+
}
|
|
1986
|
+
|
|
1987
|
+
aside {
|
|
1988
|
+
width: 16rem;
|
|
1989
|
+
background-color: var(--greg-menu-background);
|
|
1990
|
+
padding: 1rem 0.75rem;
|
|
1991
|
+
display: flex;
|
|
1992
|
+
flex-flow: column nowrap;
|
|
1993
|
+
gap: 0.4rem;
|
|
1994
|
+
flex-shrink: 0;
|
|
1995
|
+
overflow-y: auto;
|
|
1996
|
+
|
|
1997
|
+
&.greg-aside-outline {
|
|
1998
|
+
width: 280px;
|
|
1999
|
+
border-right: none;
|
|
2000
|
+
border-left: 1px solid var(--greg-border-color);
|
|
2001
|
+
padding: 1rem;
|
|
2002
|
+
overflow-y: auto;
|
|
2003
|
+
}
|
|
2004
|
+
|
|
2005
|
+
&.hidden {
|
|
2006
|
+
display: none;
|
|
2007
|
+
}
|
|
2008
|
+
}
|
|
2009
|
+
|
|
2010
|
+
main {
|
|
2011
|
+
background-color: var(--greg-main-background);
|
|
2012
|
+
padding: 2rem 3rem;
|
|
2013
|
+
width: 100%;
|
|
2014
|
+
display: flex;
|
|
2015
|
+
flex-flow: column nowrap;
|
|
2016
|
+
gap: 0.5em;
|
|
2017
|
+
justify-content: flex-start;
|
|
2018
|
+
overflow-y: auto;
|
|
2019
|
+
|
|
2020
|
+
h1 {
|
|
2021
|
+
font-size: 1.8rem;
|
|
2022
|
+
font-weight: 700;
|
|
2023
|
+
border-bottom: 1px solid var(--greg-border-color);
|
|
2024
|
+
padding-bottom: 0.5rem;
|
|
2025
|
+
margin-bottom: 0.5rem;
|
|
2026
|
+
color: var(--greg-color);
|
|
2027
|
+
}
|
|
2028
|
+
}
|
|
2029
|
+
|
|
2030
|
+
.spinner-wrap {
|
|
2031
|
+
flex: 1;
|
|
2032
|
+
display: flex;
|
|
2033
|
+
align-items: center;
|
|
2034
|
+
justify-content: center;
|
|
2035
|
+
|
|
2036
|
+
:global(.loader::before),
|
|
2037
|
+
:global(.loader::after) {
|
|
2038
|
+
box-shadow: 0 0 0 3px var(--greg-accent) inset;
|
|
2039
|
+
filter: drop-shadow(40px 40px 0 var(--greg-accent));
|
|
2040
|
+
}
|
|
2041
|
+
:global(.loader::after) {
|
|
2042
|
+
filter: drop-shadow(-40px 40px 0 var(--greg-accent));
|
|
2043
|
+
}
|
|
2044
|
+
}
|
|
2045
|
+
|
|
2046
|
+
.doc-last-modified {
|
|
2047
|
+
font-size: 0.8rem;
|
|
2048
|
+
color: var(--greg-menu-section-color);
|
|
2049
|
+
margin-top: 1.5rem;
|
|
2050
|
+
}
|
|
2051
|
+
|
|
2052
|
+
.doc-edit-link {
|
|
2053
|
+
font-size: 0.85rem;
|
|
2054
|
+
margin: 0.25rem 0 0;
|
|
2055
|
+
|
|
2056
|
+
a {
|
|
2057
|
+
color: var(--greg-accent);
|
|
2058
|
+
text-decoration: none;
|
|
2059
|
+
|
|
2060
|
+
&:hover {
|
|
2061
|
+
text-decoration: underline;
|
|
2062
|
+
}
|
|
2063
|
+
}
|
|
2064
|
+
}
|
|
2065
|
+
|
|
2066
|
+
.doc-footer {
|
|
2067
|
+
border-top: 1px solid var(--greg-border-color);
|
|
2068
|
+
padding: 1rem 2rem;
|
|
2069
|
+
color: var(--greg-menu-section-color);
|
|
2070
|
+
font-size: 0.85rem;
|
|
2071
|
+
|
|
2072
|
+
p {
|
|
2073
|
+
margin: 0.2rem 0;
|
|
2074
|
+
}
|
|
2075
|
+
}
|
|
2076
|
+
|
|
2077
|
+
.not-found {
|
|
2078
|
+
flex: 1;
|
|
2079
|
+
display: flex;
|
|
2080
|
+
flex-direction: column;
|
|
2081
|
+
align-items: center;
|
|
2082
|
+
justify-content: center;
|
|
2083
|
+
gap: 0.5rem;
|
|
2084
|
+
text-align: center;
|
|
2085
|
+
padding: 4rem 2rem;
|
|
2086
|
+
|
|
2087
|
+
.not-found-code {
|
|
2088
|
+
font-size: 6rem;
|
|
2089
|
+
font-weight: 700;
|
|
2090
|
+
line-height: 1;
|
|
2091
|
+
color: var(--greg-accent);
|
|
2092
|
+
margin: 0;
|
|
2093
|
+
}
|
|
2094
|
+
|
|
2095
|
+
h1 {
|
|
2096
|
+
font-size: 1.5rem;
|
|
2097
|
+
margin: 0 0 0.5rem;
|
|
2098
|
+
}
|
|
2099
|
+
|
|
2100
|
+
.not-found-path {
|
|
2101
|
+
color: var(--greg-menu-section-color);
|
|
2102
|
+
font-size: 0.9rem;
|
|
2103
|
+
margin: 0 0 1.5rem;
|
|
2104
|
+
}
|
|
2105
|
+
|
|
2106
|
+
a {
|
|
2107
|
+
color: var(--greg-accent);
|
|
2108
|
+
font-weight: 500;
|
|
2109
|
+
text-decoration: none;
|
|
2110
|
+
&:hover {
|
|
2111
|
+
text-decoration: underline;
|
|
2112
|
+
}
|
|
2113
|
+
}
|
|
2114
|
+
}
|
|
2115
|
+
</style>
|