@octanejs/docusaurus 0.0.3 → 0.0.5
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 +143 -5
- package/package.json +26 -4
- package/src/bin.js +1 -1
- package/src/client-components.tsrx +134 -0
- package/src/client.js +18 -0
- package/src/document.js +153 -0
- package/src/hydrate.js +52 -0
- package/src/manifest.js +89 -8
- package/src/route-modules.js +56 -0
- package/src/routes.js +133 -0
- package/src/server.js +64 -0
- package/src/theme/DocCategoryGeneratedIndexPage.js +1 -0
- package/src/theme/DocItem.js +1 -0
- package/src/theme/DocRoot.js +1 -0
- package/src/theme/DocTagDocListPage.js +1 -0
- package/src/theme/DocTagsListPage.js +1 -0
- package/src/theme/DocVersionRoot.js +1 -0
- package/src/theme/DocsRoot.js +1 -0
- package/src/theme/Root.js +1 -0
- package/src/theme/styles.css +183 -0
- package/src/theme-components.tsrx +462 -0
- package/src/theme.js +25 -0
- package/src/vite.js +50 -2
- package/types/client.d.ts +58 -0
- package/types/hydrate.d.ts +21 -0
- package/types/index.d.ts +63 -4
- package/types/server.d.ts +80 -0
- package/types/theme.d.ts +8 -0
- package/types/virtual.d.ts +10 -0
- package/types/vite.d.ts +12 -0
|
@@ -0,0 +1,462 @@
|
|
|
1
|
+
import { createContext, useContext, type ComponentBody, type OctaneNode } from 'octane';
|
|
2
|
+
import { Link as RouterLink, type Location } from '@octanejs/remix-router';
|
|
3
|
+
import { Head, Seo } from '@octanejs/seo';
|
|
4
|
+
import { useDocusaurusManifest } from './client-components.tsrx';
|
|
5
|
+
|
|
6
|
+
type NavigationItem = {
|
|
7
|
+
label: string;
|
|
8
|
+
href?: string;
|
|
9
|
+
to?: string;
|
|
10
|
+
position?: 'left' | 'right';
|
|
11
|
+
};
|
|
12
|
+
|
|
13
|
+
type NavbarConfig = {
|
|
14
|
+
title?: string;
|
|
15
|
+
logo?: {
|
|
16
|
+
alt?: string;
|
|
17
|
+
href?: string;
|
|
18
|
+
src: string;
|
|
19
|
+
};
|
|
20
|
+
items?: NavigationItem[];
|
|
21
|
+
};
|
|
22
|
+
|
|
23
|
+
type FooterItem = {
|
|
24
|
+
label: string;
|
|
25
|
+
href?: string;
|
|
26
|
+
to?: string;
|
|
27
|
+
};
|
|
28
|
+
|
|
29
|
+
type FooterConfig = {
|
|
30
|
+
copyright?: string;
|
|
31
|
+
links?: Array<{
|
|
32
|
+
title: string;
|
|
33
|
+
items: FooterItem[];
|
|
34
|
+
}>;
|
|
35
|
+
};
|
|
36
|
+
|
|
37
|
+
type ClassicThemeConfig = {
|
|
38
|
+
navbar?: NavbarConfig;
|
|
39
|
+
footer?: FooterConfig;
|
|
40
|
+
};
|
|
41
|
+
|
|
42
|
+
type SidebarItem = {
|
|
43
|
+
type: string;
|
|
44
|
+
label: string;
|
|
45
|
+
href?: string;
|
|
46
|
+
key?: string;
|
|
47
|
+
items?: SidebarItem[];
|
|
48
|
+
};
|
|
49
|
+
|
|
50
|
+
type DocLink = {
|
|
51
|
+
title: string;
|
|
52
|
+
permalink: string;
|
|
53
|
+
description?: string;
|
|
54
|
+
};
|
|
55
|
+
|
|
56
|
+
type DocsVersion = {
|
|
57
|
+
label: string;
|
|
58
|
+
noIndex?: boolean;
|
|
59
|
+
docsSidebars: Record<string, SidebarItem[]>;
|
|
60
|
+
};
|
|
61
|
+
|
|
62
|
+
type DocMetadata = {
|
|
63
|
+
title?: string;
|
|
64
|
+
description?: string;
|
|
65
|
+
permalink?: string;
|
|
66
|
+
previous?: DocLink;
|
|
67
|
+
next?: DocLink;
|
|
68
|
+
};
|
|
69
|
+
|
|
70
|
+
type DocContent =
|
|
71
|
+
ComponentBody<Record<string, unknown>> & {
|
|
72
|
+
contentTitle?: string;
|
|
73
|
+
frontMatter?: {
|
|
74
|
+
title?: string;
|
|
75
|
+
description?: string;
|
|
76
|
+
noIndex?: boolean;
|
|
77
|
+
};
|
|
78
|
+
metadata?: DocMetadata;
|
|
79
|
+
};
|
|
80
|
+
|
|
81
|
+
type RouteProps = {
|
|
82
|
+
attributes?: Record<string, unknown>;
|
|
83
|
+
routes?: RouteProps[];
|
|
84
|
+
path?: string;
|
|
85
|
+
};
|
|
86
|
+
|
|
87
|
+
const VersionContext = createContext<DocsVersion | null>(null);
|
|
88
|
+
|
|
89
|
+
function isExternal(href: string): boolean {
|
|
90
|
+
return /^(?:[a-z][a-z\d+.-]*:|\/\/)/i.test(href);
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
function resolvedHref(item: { href?: string; to?: string }): string | null {
|
|
94
|
+
return item.to ?? item.href ?? null;
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
function withBaseUrl(baseUrl: string, href: string): string {
|
|
98
|
+
if (href === '' || isExternal(href) || href.startsWith('#')) return href;
|
|
99
|
+
if (href === baseUrl.replace(/\/$/, '')) return baseUrl;
|
|
100
|
+
if (href.startsWith(baseUrl)) return href;
|
|
101
|
+
return `${baseUrl}${href.replace(/^(?:\.\/|\/)/, '')}`;
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
function absoluteUrl(siteUrl: string, href: string): string {
|
|
105
|
+
try {
|
|
106
|
+
return new URL(href, siteUrl).href;
|
|
107
|
+
} catch {
|
|
108
|
+
return href;
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
function sidebarKey(item: SidebarItem): string {
|
|
113
|
+
return item.key ?? item.href ?? `${item.type}:${item.label}`;
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
function navigationKey(item: NavigationItem | FooterItem): string {
|
|
117
|
+
return resolvedHref(item) ?? item.label;
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
function findSidebarCategory(items: SidebarItem[], href: string): SidebarItem | null {
|
|
121
|
+
for (const item of items) {
|
|
122
|
+
if (item.type !== 'category') continue;
|
|
123
|
+
if (item.href === href) return item;
|
|
124
|
+
const nested = findSidebarCategory(item.items ?? [], href);
|
|
125
|
+
if (nested !== null) return nested;
|
|
126
|
+
}
|
|
127
|
+
return null;
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
function categoryItems(version: DocsVersion, href: string): DocLink[] {
|
|
131
|
+
for (const sidebar of Object.values(version.docsSidebars)) {
|
|
132
|
+
const category = findSidebarCategory(sidebar, href);
|
|
133
|
+
if (category === null) continue;
|
|
134
|
+
return (category.items ?? []).flatMap(
|
|
135
|
+
(item) => item.href === undefined
|
|
136
|
+
? []
|
|
137
|
+
: [
|
|
138
|
+
{
|
|
139
|
+
title: item.label,
|
|
140
|
+
permalink: item.href,
|
|
141
|
+
},
|
|
142
|
+
],
|
|
143
|
+
);
|
|
144
|
+
}
|
|
145
|
+
return [];
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
function useDocsVersion(): DocsVersion {
|
|
149
|
+
const version = useContext(VersionContext);
|
|
150
|
+
if (version === null) {
|
|
151
|
+
throw new Error('[@octanejs/docusaurus] The classic DocRoot requires DocVersionRoot.');
|
|
152
|
+
}
|
|
153
|
+
return version;
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
function ThemeLink(props: {
|
|
157
|
+
href: string;
|
|
158
|
+
class?: string;
|
|
159
|
+
'aria-current'?: 'page';
|
|
160
|
+
children?: OctaneNode;
|
|
161
|
+
}) @{
|
|
162
|
+
@if (isExternal(props.href)) {
|
|
163
|
+
<a
|
|
164
|
+
href={props.href}
|
|
165
|
+
class={props.class}
|
|
166
|
+
aria-current={props['aria-current']}
|
|
167
|
+
>{props.children}</a>
|
|
168
|
+
} @else {
|
|
169
|
+
<RouterLink
|
|
170
|
+
to={props.href}
|
|
171
|
+
class={props.class}
|
|
172
|
+
aria-current={props['aria-current']}
|
|
173
|
+
>{props.children}</RouterLink>
|
|
174
|
+
}
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
function Navbar(props: { config?: NavbarConfig }) @{
|
|
178
|
+
const manifest = useDocusaurusManifest();
|
|
179
|
+
const title = props.config?.title ?? manifest.site.title;
|
|
180
|
+
const brandHref = withBaseUrl(manifest.baseUrl, props.config?.logo?.href ?? manifest.baseUrl);
|
|
181
|
+
const items = props.config?.items ?? [];
|
|
182
|
+
|
|
183
|
+
<header class="octane-docs-navbar">
|
|
184
|
+
<ThemeLink href={brandHref} class="octane-docs-navbar__brand">
|
|
185
|
+
@if (props.config?.logo !== undefined) {
|
|
186
|
+
<img
|
|
187
|
+
src={withBaseUrl(manifest.baseUrl, props.config.logo.src)}
|
|
188
|
+
alt={props.config.logo.alt ?? title}
|
|
189
|
+
/>
|
|
190
|
+
}
|
|
191
|
+
<span>{title}</span>
|
|
192
|
+
</ThemeLink>
|
|
193
|
+
@if (items.length > 0) {
|
|
194
|
+
<nav aria-label="Primary">
|
|
195
|
+
<ul class="octane-docs-navbar__items">
|
|
196
|
+
@for (const item of items; key navigationKey(item)) {
|
|
197
|
+
@if (resolvedHref(item) !== null) {
|
|
198
|
+
<li>
|
|
199
|
+
<ThemeLink
|
|
200
|
+
href={withBaseUrl(manifest.baseUrl, resolvedHref(item)!)}
|
|
201
|
+
>{item.label}</ThemeLink>
|
|
202
|
+
</li>
|
|
203
|
+
}
|
|
204
|
+
}
|
|
205
|
+
</ul>
|
|
206
|
+
</nav>
|
|
207
|
+
}
|
|
208
|
+
</header>
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
function Footer(props: { baseUrl: string; config?: FooterConfig }) @{
|
|
212
|
+
const groups = props.config?.links ?? [];
|
|
213
|
+
// Docusaurus treats the developer-provided copyright as trusted site HTML.
|
|
214
|
+
const copyright = props.config?.copyright;
|
|
215
|
+
|
|
216
|
+
@if (groups.length > 0 || copyright !== undefined) {
|
|
217
|
+
<footer class="octane-docs-footer">
|
|
218
|
+
@if (groups.length > 0) {
|
|
219
|
+
<ul class="octane-docs-footer__links">
|
|
220
|
+
@for (const group of groups; key group.title) {
|
|
221
|
+
<li>
|
|
222
|
+
<strong>{group.title}</strong>
|
|
223
|
+
<ul class="octane-docs-footer__items">
|
|
224
|
+
@for (const item of group.items; key navigationKey(item)) {
|
|
225
|
+
@if (resolvedHref(item) !== null) {
|
|
226
|
+
<li>
|
|
227
|
+
<ThemeLink
|
|
228
|
+
href={withBaseUrl(props.baseUrl, resolvedHref(item)!)}
|
|
229
|
+
>{item.label}</ThemeLink>
|
|
230
|
+
</li>
|
|
231
|
+
}
|
|
232
|
+
}
|
|
233
|
+
</ul>
|
|
234
|
+
</li>
|
|
235
|
+
}
|
|
236
|
+
</ul>
|
|
237
|
+
}
|
|
238
|
+
@if (copyright !== undefined) {
|
|
239
|
+
<div
|
|
240
|
+
class="octane-docs-footer__copyright"
|
|
241
|
+
dangerouslySetInnerHTML={{ __html: copyright }}
|
|
242
|
+
/>
|
|
243
|
+
}
|
|
244
|
+
</footer>
|
|
245
|
+
}
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
function SidebarItems(props: { items: SidebarItem[]; pathname: string }) @{
|
|
249
|
+
<ul>
|
|
250
|
+
@for (const item of props.items; key sidebarKey(item)) {
|
|
251
|
+
<li>
|
|
252
|
+
@if (item.type === 'category' && item.items !== undefined) {
|
|
253
|
+
<>
|
|
254
|
+
@if (item.href !== undefined) {
|
|
255
|
+
<ThemeLink
|
|
256
|
+
href={item.href}
|
|
257
|
+
class="octane-docs-sidebar__category"
|
|
258
|
+
aria-current={item.href === props.pathname ? 'page' : undefined}
|
|
259
|
+
>{item.label}</ThemeLink>
|
|
260
|
+
} @else {
|
|
261
|
+
<span class="octane-docs-sidebar__category">{item.label}</span>
|
|
262
|
+
}
|
|
263
|
+
<SidebarItems items={item.items} pathname={props.pathname} />
|
|
264
|
+
</>
|
|
265
|
+
} @else if (item.href !== undefined) {
|
|
266
|
+
<ThemeLink
|
|
267
|
+
href={item.href}
|
|
268
|
+
aria-current={item.href === props.pathname ? 'page' : undefined}
|
|
269
|
+
>{item.label}</ThemeLink>
|
|
270
|
+
} @else {
|
|
271
|
+
<span>{item.label}</span>
|
|
272
|
+
}
|
|
273
|
+
</li>
|
|
274
|
+
}
|
|
275
|
+
</ul>
|
|
276
|
+
}
|
|
277
|
+
|
|
278
|
+
function PageMetadata(props: {
|
|
279
|
+
title?: string;
|
|
280
|
+
description?: string;
|
|
281
|
+
permalink?: string;
|
|
282
|
+
noIndex?: boolean;
|
|
283
|
+
}) @{
|
|
284
|
+
const manifest = useDocusaurusManifest();
|
|
285
|
+
const title =
|
|
286
|
+
props.title === undefined || props.title === manifest.site.title
|
|
287
|
+
? manifest.site.title
|
|
288
|
+
: `${props.title} | ${manifest.site.title}`;
|
|
289
|
+
|
|
290
|
+
<Head>
|
|
291
|
+
<Seo
|
|
292
|
+
title={title}
|
|
293
|
+
description={props.description}
|
|
294
|
+
canonical={props.permalink === undefined
|
|
295
|
+
? undefined
|
|
296
|
+
: absoluteUrl(manifest.site.url, props.permalink)}
|
|
297
|
+
openGraph={{ title, description: props.description }}
|
|
298
|
+
robots={props.noIndex === true ? { index: false, follow: false } : undefined}
|
|
299
|
+
/>
|
|
300
|
+
</Head>
|
|
301
|
+
}
|
|
302
|
+
|
|
303
|
+
function Paginator(props: { previous?: DocLink; next?: DocLink }) @{
|
|
304
|
+
@if (props.previous !== undefined || props.next !== undefined) {
|
|
305
|
+
<nav class="octane-docs-paginator" aria-label="Documentation pages">
|
|
306
|
+
<div>
|
|
307
|
+
@if (props.previous !== undefined) {
|
|
308
|
+
<ThemeLink href={props.previous.permalink}>← {props.previous.title}</ThemeLink>
|
|
309
|
+
}
|
|
310
|
+
</div>
|
|
311
|
+
<div class="octane-docs-paginator__next">
|
|
312
|
+
@if (props.next !== undefined) {
|
|
313
|
+
<ThemeLink href={props.next.permalink}>{props.next.title} →</ThemeLink>
|
|
314
|
+
}
|
|
315
|
+
</div>
|
|
316
|
+
</nav>
|
|
317
|
+
}
|
|
318
|
+
}
|
|
319
|
+
|
|
320
|
+
function CardList(props: { items: DocLink[] }) @{
|
|
321
|
+
<ul class="octane-docs-card-list">
|
|
322
|
+
@for (const item of props.items; key item.permalink) {
|
|
323
|
+
<li class="octane-docs-card">
|
|
324
|
+
<ThemeLink href={item.permalink}>{item.title}</ThemeLink>
|
|
325
|
+
@if (item.description !== undefined) {
|
|
326
|
+
<p>{item.description}</p>
|
|
327
|
+
}
|
|
328
|
+
</li>
|
|
329
|
+
}
|
|
330
|
+
</ul>
|
|
331
|
+
}
|
|
332
|
+
|
|
333
|
+
export function Root(props: { children?: OctaneNode }) @{
|
|
334
|
+
<>
|
|
335
|
+
{props.children}
|
|
336
|
+
</>
|
|
337
|
+
}
|
|
338
|
+
|
|
339
|
+
export function DocsRoot(props: { children?: OctaneNode }) @{
|
|
340
|
+
const manifest = useDocusaurusManifest();
|
|
341
|
+
const themeConfig = manifest.site.themeConfig as ClassicThemeConfig;
|
|
342
|
+
|
|
343
|
+
<div class="octane-docusaurus">
|
|
344
|
+
<Navbar config={themeConfig.navbar} />
|
|
345
|
+
{props.children}
|
|
346
|
+
<Footer baseUrl={manifest.baseUrl} config={themeConfig.footer} />
|
|
347
|
+
</div>
|
|
348
|
+
}
|
|
349
|
+
|
|
350
|
+
export function DocVersionRoot(props: { children?: OctaneNode; version: DocsVersion }) @{
|
|
351
|
+
<VersionContext.Provider value={props.version}>{props.children}</VersionContext.Provider>
|
|
352
|
+
}
|
|
353
|
+
|
|
354
|
+
export function DocRoot(props: {
|
|
355
|
+
children?: OctaneNode;
|
|
356
|
+
location: Location;
|
|
357
|
+
route: RouteProps;
|
|
358
|
+
}) @{
|
|
359
|
+
const version = useDocsVersion();
|
|
360
|
+
const activeRoute = props.route.routes?.find((route) => route.path === props.location.pathname);
|
|
361
|
+
const sidebarId = activeRoute?.attributes?.sidebar;
|
|
362
|
+
const sidebar =
|
|
363
|
+
typeof sidebarId === 'string' ? version.docsSidebars[sidebarId] ?? [] : [];
|
|
364
|
+
|
|
365
|
+
<div class="octane-docs-layout">
|
|
366
|
+
@if (sidebar.length > 0) {
|
|
367
|
+
<aside class="octane-docs-sidebar" aria-label="Documentation sidebar">
|
|
368
|
+
<SidebarItems items={sidebar} pathname={props.location.pathname} />
|
|
369
|
+
</aside>
|
|
370
|
+
}
|
|
371
|
+
<main class="octane-docs-main">{props.children}</main>
|
|
372
|
+
</div>
|
|
373
|
+
}
|
|
374
|
+
|
|
375
|
+
export function DocItem(props: { content: DocContent }) @{
|
|
376
|
+
const manifest = useDocusaurusManifest();
|
|
377
|
+
const version = useDocsVersion();
|
|
378
|
+
const Content = props.content;
|
|
379
|
+
const metadata = Content.metadata ?? {};
|
|
380
|
+
const frontMatter = Content.frontMatter ?? {};
|
|
381
|
+
const title = frontMatter.title ?? metadata.title ?? Content.contentTitle;
|
|
382
|
+
const description = frontMatter.description ?? metadata.description;
|
|
383
|
+
|
|
384
|
+
<>
|
|
385
|
+
<PageMetadata
|
|
386
|
+
title={title}
|
|
387
|
+
description={description}
|
|
388
|
+
permalink={metadata.permalink}
|
|
389
|
+
noIndex={frontMatter.noIndex === true || version.noIndex === true || manifest.site.noIndex}
|
|
390
|
+
/>
|
|
391
|
+
<article class="octane-docs-article">
|
|
392
|
+
<Content />
|
|
393
|
+
<Paginator previous={metadata.previous} next={metadata.next} />
|
|
394
|
+
</article>
|
|
395
|
+
</>
|
|
396
|
+
}
|
|
397
|
+
|
|
398
|
+
export function DocCategoryGeneratedIndexPage(props: {
|
|
399
|
+
categoryGeneratedIndex: {
|
|
400
|
+
title: string;
|
|
401
|
+
description?: string;
|
|
402
|
+
permalink: string;
|
|
403
|
+
navigation?: {
|
|
404
|
+
previous?: DocLink;
|
|
405
|
+
next?: DocLink;
|
|
406
|
+
};
|
|
407
|
+
};
|
|
408
|
+
}) @{
|
|
409
|
+
const version = useDocsVersion();
|
|
410
|
+
const page = props.categoryGeneratedIndex;
|
|
411
|
+
const items = categoryItems(version, page.permalink);
|
|
412
|
+
<div class="octane-docs-list-page">
|
|
413
|
+
<PageMetadata
|
|
414
|
+
title={page.title}
|
|
415
|
+
description={page.description}
|
|
416
|
+
permalink={page.permalink}
|
|
417
|
+
noIndex={version.noIndex}
|
|
418
|
+
/>
|
|
419
|
+
<h1>{page.title}</h1>
|
|
420
|
+
@if (page.description !== undefined) {
|
|
421
|
+
<p>{page.description}</p>
|
|
422
|
+
}
|
|
423
|
+
<CardList items={items} />
|
|
424
|
+
<Paginator previous={page.navigation?.previous} next={page.navigation?.next} />
|
|
425
|
+
</div>
|
|
426
|
+
}
|
|
427
|
+
|
|
428
|
+
export function DocTagsListPage(props: {
|
|
429
|
+
tags: Array<{ label: string; permalink: string; description?: string }>;
|
|
430
|
+
location: Location;
|
|
431
|
+
}) @{
|
|
432
|
+
const version = useDocsVersion();
|
|
433
|
+
<div class="octane-docs-list-page">
|
|
434
|
+
<PageMetadata title="Tags" permalink={props.location.pathname} noIndex={version.noIndex} />
|
|
435
|
+
<h1>Tags</h1>
|
|
436
|
+
<CardList items={props.tags.map((tag) => ({ ...tag, title: tag.label }))} />
|
|
437
|
+
</div>
|
|
438
|
+
}
|
|
439
|
+
|
|
440
|
+
export function DocTagDocListPage(props: {
|
|
441
|
+
tag: {
|
|
442
|
+
label: string;
|
|
443
|
+
description?: string;
|
|
444
|
+
permalink: string;
|
|
445
|
+
items: DocLink[];
|
|
446
|
+
};
|
|
447
|
+
}) @{
|
|
448
|
+
const version = useDocsVersion();
|
|
449
|
+
<div class="octane-docs-list-page">
|
|
450
|
+
<PageMetadata
|
|
451
|
+
title={props.tag.label}
|
|
452
|
+
description={props.tag.description}
|
|
453
|
+
permalink={props.tag.permalink}
|
|
454
|
+
noIndex={version.noIndex}
|
|
455
|
+
/>
|
|
456
|
+
<h1>{props.tag.label}</h1>
|
|
457
|
+
@if (props.tag.description !== undefined) {
|
|
458
|
+
<p>{props.tag.description}</p>
|
|
459
|
+
}
|
|
460
|
+
<CardList items={props.tag.items} />
|
|
461
|
+
</div>
|
|
462
|
+
}
|
package/src/theme.js
ADDED
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import { fileURLToPath } from 'node:url';
|
|
2
|
+
|
|
3
|
+
const THEME_PATH = fileURLToPath(new URL('./theme', import.meta.url));
|
|
4
|
+
const STYLES_PATH = fileURLToPath(new URL('./theme/styles.css', import.meta.url));
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* Docusaurus theme plugin exposing Octane-authored docs route components.
|
|
8
|
+
*
|
|
9
|
+
* Add this function to `themes` in `docusaurus.config.*`. Docusaurus remains
|
|
10
|
+
* responsible for content/plugin loading; the Octane Vite bridge resolves the
|
|
11
|
+
* returned theme modules and imports the client stylesheet.
|
|
12
|
+
*/
|
|
13
|
+
export default function octaneClassicTheme() {
|
|
14
|
+
return {
|
|
15
|
+
name: '@octanejs/docusaurus-theme-classic',
|
|
16
|
+
getThemePath() {
|
|
17
|
+
return THEME_PATH;
|
|
18
|
+
},
|
|
19
|
+
getClientModules() {
|
|
20
|
+
return [STYLES_PATH];
|
|
21
|
+
},
|
|
22
|
+
};
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
export { octaneClassicTheme };
|
package/src/vite.js
CHANGED
|
@@ -3,9 +3,12 @@ import { createOctaneCompiler } from 'octane/compiler/bundler';
|
|
|
3
3
|
import { compileDocusaurusMdx } from './mdx.js';
|
|
4
4
|
import { loadDocusaurusSite } from './load-site.js';
|
|
5
5
|
import { createDocusaurusManifest, resolveDocusaurusId } from './manifest.js';
|
|
6
|
+
import { collectDocusaurusRouteModuleReferences } from './route-modules.js';
|
|
6
7
|
|
|
7
8
|
export const DOCUSAURUS_MANIFEST_ID = 'virtual:octane-docusaurus-manifest';
|
|
9
|
+
export const DOCUSAURUS_ROUTES_ID = 'virtual:octane-docusaurus-routes';
|
|
8
10
|
const RESOLVED_DOCUSAURUS_MANIFEST_ID = `\0${DOCUSAURUS_MANIFEST_ID}`;
|
|
11
|
+
const RESOLVED_DOCUSAURUS_ROUTES_ID = `\0${DOCUSAURUS_ROUTES_ID}`;
|
|
9
12
|
|
|
10
13
|
function cleanId(id) {
|
|
11
14
|
return id.split(/[?#]/, 1)[0];
|
|
@@ -27,6 +30,27 @@ function serializableManifest(manifest) {
|
|
|
27
30
|
.replace(/</g, '\\u003c');
|
|
28
31
|
}
|
|
29
32
|
|
|
33
|
+
function clientRoutesModule(manifest) {
|
|
34
|
+
const clientModules = manifest.assets.clientModules
|
|
35
|
+
.map((specifier) => `import ${JSON.stringify(specifier)};\n`)
|
|
36
|
+
.join('');
|
|
37
|
+
const importers = [...collectDocusaurusRouteModuleReferences(manifest.routes)]
|
|
38
|
+
.sort(([left], [right]) => left.localeCompare(right))
|
|
39
|
+
.map(
|
|
40
|
+
([key, specifier]) => `\t${JSON.stringify(key)}: () => import(${JSON.stringify(specifier)}),`,
|
|
41
|
+
)
|
|
42
|
+
.join('\n');
|
|
43
|
+
return `${clientModules}import { createDocusaurusRoutes } from "@octanejs/docusaurus/client";
|
|
44
|
+
import manifest from ${JSON.stringify(DOCUSAURUS_MANIFEST_ID)};
|
|
45
|
+
|
|
46
|
+
export { manifest };
|
|
47
|
+
export const routeModules = {
|
|
48
|
+
${importers}
|
|
49
|
+
};
|
|
50
|
+
export const routes = createDocusaurusRoutes(manifest, routeModules);
|
|
51
|
+
`;
|
|
52
|
+
}
|
|
53
|
+
|
|
30
54
|
function createSharedState(options) {
|
|
31
55
|
let loaded;
|
|
32
56
|
let manifest;
|
|
@@ -76,6 +100,17 @@ function createSharedState(options) {
|
|
|
76
100
|
}
|
|
77
101
|
|
|
78
102
|
export function docusaurusBridge(options = {}, shared = createSharedState(options)) {
|
|
103
|
+
let server;
|
|
104
|
+
|
|
105
|
+
function invalidateVirtualModules() {
|
|
106
|
+
if (server === undefined) return;
|
|
107
|
+
for (const id of [RESOLVED_DOCUSAURUS_MANIFEST_ID, RESOLVED_DOCUSAURUS_ROUTES_ID]) {
|
|
108
|
+
const module = server.moduleGraph.getModuleById(id);
|
|
109
|
+
if (module !== undefined) server.moduleGraph.invalidateModule(module);
|
|
110
|
+
}
|
|
111
|
+
server.ws.send({ type: 'full-reload' });
|
|
112
|
+
}
|
|
113
|
+
|
|
79
114
|
return {
|
|
80
115
|
name: 'octane-docusaurus-bridge',
|
|
81
116
|
enforce: 'pre',
|
|
@@ -83,6 +118,9 @@ export function docusaurusBridge(options = {}, shared = createSharedState(option
|
|
|
83
118
|
getManifest: () => shared.getManifest(),
|
|
84
119
|
reload: () => shared.refresh(),
|
|
85
120
|
},
|
|
121
|
+
configureServer(value) {
|
|
122
|
+
server = value;
|
|
123
|
+
},
|
|
86
124
|
async configResolved(config) {
|
|
87
125
|
shared.setRoot(config.root ?? process.cwd());
|
|
88
126
|
await shared.refresh();
|
|
@@ -102,18 +140,28 @@ export function docusaurusBridge(options = {}, shared = createSharedState(option
|
|
|
102
140
|
const resolved = path.isAbsolute(source) ? source : resolveDocusaurusId(source, manifest);
|
|
103
141
|
if (resolved !== null) this.addWatchFile?.(cleanId(resolved));
|
|
104
142
|
}
|
|
143
|
+
for (const clientModule of manifest.assets.clientModules) {
|
|
144
|
+
this.addWatchFile?.(cleanId(clientModule));
|
|
145
|
+
}
|
|
105
146
|
},
|
|
106
147
|
async watchChange() {
|
|
107
148
|
await shared.refresh();
|
|
149
|
+
invalidateVirtualModules();
|
|
108
150
|
},
|
|
109
151
|
async resolveId(id) {
|
|
110
152
|
if (id === DOCUSAURUS_MANIFEST_ID) return RESOLVED_DOCUSAURUS_MANIFEST_ID;
|
|
153
|
+
if (id === DOCUSAURUS_ROUTES_ID) return RESOLVED_DOCUSAURUS_ROUTES_ID;
|
|
111
154
|
const manifest = await shared.getManifest();
|
|
112
155
|
return resolveDocusaurusId(id, manifest);
|
|
113
156
|
},
|
|
114
157
|
async load(id) {
|
|
115
|
-
if (id
|
|
116
|
-
|
|
158
|
+
if (id === RESOLVED_DOCUSAURUS_MANIFEST_ID) {
|
|
159
|
+
return `export default ${serializableManifest(await shared.getManifest())};\n`;
|
|
160
|
+
}
|
|
161
|
+
if (id === RESOLVED_DOCUSAURUS_ROUTES_ID) {
|
|
162
|
+
return clientRoutesModule(await shared.getManifest());
|
|
163
|
+
}
|
|
164
|
+
return null;
|
|
117
165
|
},
|
|
118
166
|
};
|
|
119
167
|
}
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
/// <reference path="./virtual.d.ts" />
|
|
2
|
+
|
|
3
|
+
import type { ComponentBody, OctaneNode } from 'octane';
|
|
4
|
+
import type {
|
|
5
|
+
DOMRouterOpts,
|
|
6
|
+
DataRouter,
|
|
7
|
+
Location,
|
|
8
|
+
MemoryRouterOpts,
|
|
9
|
+
NavigateFunction,
|
|
10
|
+
RouteObject,
|
|
11
|
+
} from '@octanejs/remix-router';
|
|
12
|
+
import type {
|
|
13
|
+
DocusaurusManifest,
|
|
14
|
+
DocusaurusManifestRoute,
|
|
15
|
+
DocusaurusPluginIdentifier,
|
|
16
|
+
} from './index.js';
|
|
17
|
+
|
|
18
|
+
export type DocusaurusRouteModuleNamespace = Record<string, unknown>;
|
|
19
|
+
export type DocusaurusRouteModuleImporter = () => Promise<DocusaurusRouteModuleNamespace>;
|
|
20
|
+
export type DocusaurusRouteModuleRegistry = Record<string, DocusaurusRouteModuleImporter>;
|
|
21
|
+
|
|
22
|
+
export interface DocusaurusRouteContext {
|
|
23
|
+
plugin: DocusaurusPluginIdentifier;
|
|
24
|
+
data: Record<string, unknown>;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
export interface DocusaurusRouteComponentProps extends Record<string, unknown> {
|
|
28
|
+
route: DocusaurusManifestRoute & { routes: DocusaurusManifestRoute[] };
|
|
29
|
+
location: Location;
|
|
30
|
+
params: Readonly<Record<string, string | undefined>>;
|
|
31
|
+
navigate: NavigateFunction;
|
|
32
|
+
children?: OctaneNode;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
export declare function createDocusaurusRoutes(
|
|
36
|
+
manifest: DocusaurusManifest,
|
|
37
|
+
registry: DocusaurusRouteModuleRegistry,
|
|
38
|
+
): RouteObject[];
|
|
39
|
+
|
|
40
|
+
export declare function createDocusaurusBrowserRouter(
|
|
41
|
+
manifest: DocusaurusManifest,
|
|
42
|
+
registry: DocusaurusRouteModuleRegistry,
|
|
43
|
+
options?: DOMRouterOpts,
|
|
44
|
+
): DataRouter;
|
|
45
|
+
|
|
46
|
+
export declare function createDocusaurusMemoryRouter(
|
|
47
|
+
manifest: DocusaurusManifest,
|
|
48
|
+
registry: DocusaurusRouteModuleRegistry,
|
|
49
|
+
options?: MemoryRouterOpts,
|
|
50
|
+
): DataRouter;
|
|
51
|
+
|
|
52
|
+
export declare const DocusaurusRouterProvider: ComponentBody<{
|
|
53
|
+
manifest: DocusaurusManifest;
|
|
54
|
+
router: DataRouter;
|
|
55
|
+
}>;
|
|
56
|
+
|
|
57
|
+
export declare function useDocusaurusManifest(): DocusaurusManifest;
|
|
58
|
+
export declare function useDocusaurusRouteContext(): DocusaurusRouteContext;
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import type { Root } from 'octane';
|
|
2
|
+
import type { DataRouter, DOMRouterOpts } from '@octanejs/remix-router';
|
|
3
|
+
import type { DocusaurusManifest } from './index.js';
|
|
4
|
+
import type { DocusaurusRouteModuleRegistry } from './client.js';
|
|
5
|
+
|
|
6
|
+
export interface DocusaurusHydrateOptions extends DOMRouterOpts {
|
|
7
|
+
identifierPrefix?: string;
|
|
8
|
+
signal?: AbortSignal;
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
export interface DocusaurusHydrationRoot {
|
|
12
|
+
root: Root;
|
|
13
|
+
router: DataRouter;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
export declare function hydrateDocusaurusRoot(
|
|
17
|
+
container: Element,
|
|
18
|
+
manifest: DocusaurusManifest,
|
|
19
|
+
registry: DocusaurusRouteModuleRegistry,
|
|
20
|
+
options?: DocusaurusHydrateOptions,
|
|
21
|
+
): Promise<DocusaurusHydrationRoot>;
|