@raystack/chronicle 0.6.1 → 0.7.1
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/dist/cli/index.js +277 -27
- package/package.json +5 -1
- package/src/components/ui/search.tsx +3 -3
- package/src/lib/config.ts +5 -0
- package/src/lib/remark-resolve-images.ts +59 -0
- package/src/lib/remark-resolve-links.ts +32 -0
- package/src/lib/source.ts +20 -4
- package/src/pages/ApiLayout.tsx +0 -2
- package/src/pages/LandingPage.module.css +137 -24
- package/src/pages/LandingPage.tsx +23 -7
- package/src/server/api/apis-proxy.ts +2 -2
- package/src/server/api/health.ts +1 -1
- package/src/server/api/page.ts +2 -2
- package/src/server/api/search.ts +4 -4
- package/src/server/api/specs.ts +2 -2
- package/src/server/entry-server.tsx +4 -1
- package/src/server/routes/[...slug].md.ts +1 -2
- package/src/server/routes/[version]/llms.txt.ts +1 -2
- package/src/server/routes/_content/[...path].ts +40 -0
- package/src/server/routes/llms.txt.ts +2 -3
- package/src/server/routes/og.tsx +1 -3
- package/src/server/routes/robots.txt.ts +2 -3
- package/src/server/routes/sitemap.xml.ts +3 -5
- package/src/server/vite-config.ts +8 -2
- package/src/themes/paper/ChapterNav.module.css +23 -12
- package/src/themes/paper/ChapterNav.tsx +1 -17
- package/src/themes/paper/Layout.module.css +61 -16
- package/src/themes/paper/Layout.tsx +73 -17
- package/src/themes/paper/Page.module.css +89 -37
- package/src/themes/paper/Page.tsx +89 -53
- package/src/themes/paper/ReaderModeContext.tsx +28 -0
- package/src/themes/paper/ReadingProgress.tsx +1 -0
- package/src/themes/paper/fonts/DepartureMono-Regular.woff2 +0 -0
- package/src/themes/registry.ts +1 -1
- package/src/types/config.ts +1 -0
- package/src/types/content.ts +1 -0
- package/src/lib/remark-strip-md-extensions.ts +0 -14
package/src/pages/ApiLayout.tsx
CHANGED
|
@@ -1,6 +1,5 @@
|
|
|
1
1
|
import { cx } from 'class-variance-authority';
|
|
2
2
|
import type { ReactNode } from 'react';
|
|
3
|
-
import { Search } from '@/components/ui/search';
|
|
4
3
|
import { buildApiPageTree } from '@/lib/api-routes';
|
|
5
4
|
import { usePageContext } from '@/lib/page-context';
|
|
6
5
|
import { getTheme } from '@/themes/registry';
|
|
@@ -26,7 +25,6 @@ export function ApiLayout({ children }: ApiLayoutProps) {
|
|
|
26
25
|
content: styles.content
|
|
27
26
|
}}
|
|
28
27
|
>
|
|
29
|
-
<Search className={styles.hiddenSearch} />
|
|
30
28
|
{children}
|
|
31
29
|
</Layout>
|
|
32
30
|
);
|
|
@@ -1,56 +1,169 @@
|
|
|
1
1
|
.root {
|
|
2
2
|
display: flex;
|
|
3
3
|
flex-direction: column;
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
4
|
+
padding: var(--rs-space-12) var(--rs-space-9);
|
|
5
|
+
width: 100%;
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
.header {
|
|
9
|
+
display: flex;
|
|
10
|
+
align-items: center;
|
|
11
|
+
padding-bottom: var(--rs-space-10);
|
|
12
|
+
border-bottom: 0.5px solid var(--rs-color-border-base-primary);
|
|
8
13
|
}
|
|
9
14
|
|
|
10
15
|
.title {
|
|
11
|
-
|
|
12
|
-
font-
|
|
13
|
-
|
|
16
|
+
flex: 1;
|
|
17
|
+
font-family: var(--paper-font-mono, inherit);
|
|
18
|
+
font-size: 64px;
|
|
19
|
+
line-height: 1.1;
|
|
20
|
+
color: var(--rs-color-foreground-accent-primary);
|
|
21
|
+
text-transform: uppercase;
|
|
14
22
|
margin: 0;
|
|
23
|
+
font-weight: var(--rs-font-weight-regular);
|
|
15
24
|
}
|
|
16
25
|
|
|
17
26
|
.description {
|
|
27
|
+
width: 385px;
|
|
28
|
+
flex-shrink: 0;
|
|
29
|
+
font-family: var(--paper-font-body, inherit);
|
|
18
30
|
font-size: var(--rs-font-size-regular);
|
|
19
|
-
|
|
31
|
+
line-height: 1.4;
|
|
32
|
+
color: var(--rs-color-foreground-base-primary);
|
|
20
33
|
margin: 0;
|
|
21
34
|
}
|
|
22
35
|
|
|
23
36
|
.grid {
|
|
24
|
-
display:
|
|
25
|
-
|
|
26
|
-
gap: var(--rs-space-
|
|
37
|
+
display: flex;
|
|
38
|
+
flex-wrap: wrap;
|
|
39
|
+
gap: var(--rs-space-7);
|
|
40
|
+
margin-top: var(--rs-space-9);
|
|
27
41
|
}
|
|
28
42
|
|
|
29
43
|
.card {
|
|
30
44
|
display: flex;
|
|
31
45
|
flex-direction: column;
|
|
32
|
-
gap: var(--rs-space-
|
|
33
|
-
padding: var(--rs-space-
|
|
34
|
-
border: 1px solid var(--rs-color-border-base-primary);
|
|
35
|
-
border-radius: var(--rs-radius-3);
|
|
46
|
+
gap: var(--rs-space-7);
|
|
47
|
+
padding: var(--rs-space-2);
|
|
36
48
|
text-decoration: none;
|
|
37
49
|
color: inherit;
|
|
38
|
-
|
|
39
|
-
|
|
50
|
+
width: 360px;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
.card:hover .cardImage {
|
|
54
|
+
border-color: var(--rs-color-foreground-accent-primary);
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
.cardImage {
|
|
58
|
+
position: relative;
|
|
59
|
+
display: flex;
|
|
60
|
+
align-items: center;
|
|
61
|
+
justify-content: center;
|
|
62
|
+
width: 100%;
|
|
63
|
+
aspect-ratio: 1.05;
|
|
64
|
+
min-height: 280px;
|
|
65
|
+
max-width: 360px;
|
|
66
|
+
background: var(--rs-color-background-accent-secondary);
|
|
67
|
+
border: 1px solid var(--rs-color-foreground-accent-primary);
|
|
68
|
+
overflow: hidden;
|
|
69
|
+
transition: border-color 0.15s ease;
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
.cardImage::before {
|
|
73
|
+
content: "";
|
|
74
|
+
position: absolute;
|
|
75
|
+
inset: 0;
|
|
76
|
+
background-image:
|
|
77
|
+
radial-gradient(circle, var(--rs-color-foreground-accent-primary) 1px, transparent 1px);
|
|
78
|
+
background-size: 10px 10px;
|
|
79
|
+
opacity: 0.3;
|
|
80
|
+
pointer-events: none;
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
.cardImageLabel {
|
|
84
|
+
position: absolute;
|
|
85
|
+
font-family: var(--paper-font-mono, monospace);
|
|
86
|
+
font-size: var(--rs-font-size-micro);
|
|
87
|
+
text-transform: uppercase;
|
|
88
|
+
color: var(--rs-color-foreground-base-secondary);
|
|
89
|
+
background: var(--rs-color-background-accent-secondary);
|
|
90
|
+
padding: var(--rs-space-1);
|
|
91
|
+
white-space: nowrap;
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
.cardImageLabelTop {
|
|
95
|
+
top: 19px;
|
|
96
|
+
left: -1px;
|
|
97
|
+
writing-mode: vertical-rl;
|
|
98
|
+
transform: rotate(180deg);
|
|
40
99
|
}
|
|
41
100
|
|
|
42
|
-
.
|
|
43
|
-
|
|
44
|
-
|
|
101
|
+
.cardImageLabelRight {
|
|
102
|
+
bottom: 19px;
|
|
103
|
+
right: -1px;
|
|
104
|
+
writing-mode: vertical-rl;
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
.cardIcon {
|
|
108
|
+
width: 90%;
|
|
109
|
+
height: 90%;
|
|
110
|
+
min-width: 64px;
|
|
111
|
+
min-height: 64px;
|
|
112
|
+
color: var(--rs-color-foreground-accent-primary);
|
|
113
|
+
opacity: 0.3;
|
|
114
|
+
transition: opacity 0.2s ease, transform 0.2s ease;
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
.card:hover .cardIcon {
|
|
118
|
+
opacity: 0.5;
|
|
119
|
+
transform: scale(1.05);
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
.card:hover .cardImage {
|
|
123
|
+
background: var(--rs-color-background-accent-primary);
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
.cardBody {
|
|
127
|
+
display: flex;
|
|
128
|
+
flex-direction: column;
|
|
129
|
+
gap: var(--rs-space-3);
|
|
130
|
+
padding-bottom: var(--rs-space-4);
|
|
45
131
|
}
|
|
46
132
|
|
|
47
133
|
.cardLabel {
|
|
134
|
+
font-family: var(--paper-font-mono, inherit);
|
|
48
135
|
font-size: var(--rs-font-size-large);
|
|
49
|
-
|
|
136
|
+
line-height: 1.4;
|
|
137
|
+
text-transform: uppercase;
|
|
138
|
+
color: var(--rs-color-foreground-base-primary);
|
|
50
139
|
}
|
|
51
140
|
|
|
52
|
-
.
|
|
141
|
+
.cardDescription {
|
|
142
|
+
font-family: var(--paper-font-body, inherit);
|
|
53
143
|
font-size: var(--rs-font-size-small);
|
|
54
|
-
|
|
55
|
-
|
|
144
|
+
line-height: 1.4;
|
|
145
|
+
color: var(--rs-color-foreground-base-primary);
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
@media (max-width: 768px) {
|
|
149
|
+
.header {
|
|
150
|
+
flex-direction: column;
|
|
151
|
+
gap: var(--rs-space-5);
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
.title {
|
|
155
|
+
font-size: var(--rs-font-size-t4);
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
.description {
|
|
159
|
+
width: 100%;
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
.card {
|
|
163
|
+
width: 100%;
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
.cardImage {
|
|
167
|
+
max-width: 100%;
|
|
168
|
+
}
|
|
56
169
|
}
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { FolderIcon } from '@heroicons/react/24/outline';
|
|
1
2
|
import { Link as RouterLink } from 'react-router';
|
|
2
3
|
import { getLandingEntries } from '@/lib/config';
|
|
3
4
|
import { usePageContext } from '@/lib/page-context';
|
|
@@ -13,15 +14,30 @@ export function LandingPage() {
|
|
|
13
14
|
|
|
14
15
|
return (
|
|
15
16
|
<div className={styles.root}>
|
|
16
|
-
<
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
17
|
+
<div className={styles.header}>
|
|
18
|
+
<h1 className={styles.title}>{heading}</h1>
|
|
19
|
+
{config.site.description ? (
|
|
20
|
+
<p className={styles.description}>{config.site.description}</p>
|
|
21
|
+
) : null}
|
|
22
|
+
</div>
|
|
20
23
|
<div className={styles.grid}>
|
|
21
|
-
{entries.map((entry) => (
|
|
24
|
+
{entries.map((entry, i) => (
|
|
22
25
|
<RouterLink key={entry.href} to={entry.href} className={styles.card}>
|
|
23
|
-
<
|
|
24
|
-
|
|
26
|
+
<div className={styles.cardImage} aria-hidden='true'>
|
|
27
|
+
<span className={`${styles.cardImageLabel} ${styles.cardImageLabelTop}`}>
|
|
28
|
+
Fig_{String(i + 1).padStart(3, '0')}
|
|
29
|
+
</span>
|
|
30
|
+
<span className={`${styles.cardImageLabel} ${styles.cardImageLabelRight}`}>
|
|
31
|
+
[ {entry.label} ]
|
|
32
|
+
</span>
|
|
33
|
+
<FolderIcon className={styles.cardIcon} />
|
|
34
|
+
</div>
|
|
35
|
+
<div className={styles.cardBody}>
|
|
36
|
+
<span className={styles.cardLabel}>{entry.label}</span>
|
|
37
|
+
{entry.description ? (
|
|
38
|
+
<span className={styles.cardDescription}>{entry.description}</span>
|
|
39
|
+
) : null}
|
|
40
|
+
</div>
|
|
25
41
|
</RouterLink>
|
|
26
42
|
))}
|
|
27
43
|
</div>
|
|
@@ -51,11 +51,11 @@ export default defineHandler(async event => {
|
|
|
51
51
|
? await response.json()
|
|
52
52
|
: await response.text();
|
|
53
53
|
|
|
54
|
-
return {
|
|
54
|
+
return Response.json({
|
|
55
55
|
status: response.status,
|
|
56
56
|
statusText: response.statusText,
|
|
57
57
|
body: responseBody
|
|
58
|
-
};
|
|
58
|
+
});
|
|
59
59
|
} catch (error) {
|
|
60
60
|
const message =
|
|
61
61
|
error instanceof Error
|
package/src/server/api/health.ts
CHANGED
package/src/server/api/page.ts
CHANGED
|
@@ -12,11 +12,11 @@ export default defineHandler(async event => {
|
|
|
12
12
|
|
|
13
13
|
const nav = await getPageNav(slug);
|
|
14
14
|
|
|
15
|
-
return {
|
|
15
|
+
return Response.json({
|
|
16
16
|
frontmatter: extractFrontmatter(page, slug[slug.length - 1]),
|
|
17
17
|
relativePath: getRelativePath(page),
|
|
18
18
|
originalPath: getOriginalPath(page),
|
|
19
19
|
prev: nav.prev,
|
|
20
20
|
next: nav.next,
|
|
21
|
-
};
|
|
21
|
+
});
|
|
22
22
|
});
|
package/src/server/api/search.ts
CHANGED
|
@@ -125,7 +125,7 @@ export default defineHandler(async event => {
|
|
|
125
125
|
|
|
126
126
|
if (!query) {
|
|
127
127
|
const docs = await getDocs(ctx);
|
|
128
|
-
return docs
|
|
128
|
+
return Response.json(docs
|
|
129
129
|
.filter(d => d.type === 'page')
|
|
130
130
|
.slice(0, 8)
|
|
131
131
|
.map(d => ({
|
|
@@ -133,13 +133,13 @@ export default defineHandler(async event => {
|
|
|
133
133
|
url: d.url,
|
|
134
134
|
type: d.type,
|
|
135
135
|
content: d.title
|
|
136
|
-
}));
|
|
136
|
+
})));
|
|
137
137
|
}
|
|
138
138
|
|
|
139
|
-
return index.search(query).map(r => ({
|
|
139
|
+
return Response.json(index.search(query).map(r => ({
|
|
140
140
|
id: r.id,
|
|
141
141
|
url: r.url,
|
|
142
142
|
type: r.type,
|
|
143
143
|
content: r.title
|
|
144
|
-
}));
|
|
144
|
+
})));
|
|
145
145
|
});
|
package/src/server/api/specs.ts
CHANGED
|
@@ -15,7 +15,7 @@ export default defineHandler(async event => {
|
|
|
15
15
|
}
|
|
16
16
|
|
|
17
17
|
const apiConfigs = getApiConfigsForVersion(config, versionDir);
|
|
18
|
-
if (!apiConfigs.length) return [];
|
|
18
|
+
if (!apiConfigs.length) return Response.json([]);
|
|
19
19
|
|
|
20
|
-
return loadApiSpecs(apiConfigs);
|
|
20
|
+
return Response.json(await loadApiSpecs(apiConfigs));
|
|
21
21
|
});
|
|
@@ -54,7 +54,10 @@ export default {
|
|
|
54
54
|
const pageData = page
|
|
55
55
|
? {
|
|
56
56
|
slug: pageSlug,
|
|
57
|
-
frontmatter:
|
|
57
|
+
frontmatter: {
|
|
58
|
+
...extractFrontmatter(page, pageSlug[pageSlug.length - 1]),
|
|
59
|
+
_readingTime: mdxModule?._readingTime,
|
|
60
|
+
},
|
|
58
61
|
content: mdxModule?.default
|
|
59
62
|
? React.createElement(mdxModule.default, { components: mdxComponents })
|
|
60
63
|
: null,
|
|
@@ -34,6 +34,5 @@ export default defineHandler(async event => {
|
|
|
34
34
|
throw new HTTPError({ status: 404, message: 'Not Found' });
|
|
35
35
|
}
|
|
36
36
|
|
|
37
|
-
|
|
38
|
-
return matter(raw).content;
|
|
37
|
+
return new Response(matter(raw).content, { headers: { 'Content-Type': 'text/markdown; charset=utf-8' } });
|
|
39
38
|
});
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
import fs from 'node:fs/promises';
|
|
2
|
+
import path from 'node:path';
|
|
3
|
+
import { defineHandler, HTTPError } from 'nitro';
|
|
4
|
+
import { safePath } from '@/server/utils/safe-path';
|
|
5
|
+
|
|
6
|
+
const MIME: Record<string, string> = {
|
|
7
|
+
'.png': 'image/png',
|
|
8
|
+
'.jpg': 'image/jpeg',
|
|
9
|
+
'.jpeg': 'image/jpeg',
|
|
10
|
+
'.gif': 'image/gif',
|
|
11
|
+
'.svg': 'image/svg+xml',
|
|
12
|
+
'.webp': 'image/webp',
|
|
13
|
+
'.ico': 'image/x-icon',
|
|
14
|
+
'.pdf': 'application/pdf',
|
|
15
|
+
};
|
|
16
|
+
|
|
17
|
+
export default defineHandler(async event => {
|
|
18
|
+
const pathname = event.path?.replace(/^\/_content/, '') || '';
|
|
19
|
+
if (!pathname || pathname.endsWith('.md') || pathname.endsWith('.mdx')) {
|
|
20
|
+
throw new HTTPError({ status: 404, message: 'Not Found' });
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
const contentDir = __CHRONICLE_CONTENT_DIR__;
|
|
24
|
+
let filePath: string | null = null;
|
|
25
|
+
try { filePath = safePath(contentDir, pathname); } catch { /* malformed URL encoding */ }
|
|
26
|
+
if (!filePath) throw new HTTPError({ status: 404, message: 'Not Found' });
|
|
27
|
+
|
|
28
|
+
const data = await fs.readFile(filePath).catch(() => null);
|
|
29
|
+
if (!data) throw new HTTPError({ status: 404, message: 'Not Found' });
|
|
30
|
+
|
|
31
|
+
const ext = path.extname(filePath).toLowerCase();
|
|
32
|
+
const contentType = MIME[ext] ?? 'application/octet-stream';
|
|
33
|
+
|
|
34
|
+
return new Response(data, {
|
|
35
|
+
headers: {
|
|
36
|
+
'Content-Type': contentType,
|
|
37
|
+
'Cache-Control': 'public, max-age=86400',
|
|
38
|
+
},
|
|
39
|
+
});
|
|
40
|
+
});
|
|
@@ -4,7 +4,7 @@ import { buildLlmsTxt } from '@/lib/llms';
|
|
|
4
4
|
import { extractFrontmatter, getPagesForVersion } from '@/lib/source';
|
|
5
5
|
import { LATEST_CONTEXT } from '@/lib/version-source';
|
|
6
6
|
|
|
7
|
-
export default defineHandler(async
|
|
7
|
+
export default defineHandler(async () => {
|
|
8
8
|
const config = loadConfig();
|
|
9
9
|
|
|
10
10
|
const pages = await getPagesForVersion(LATEST_CONTEXT);
|
|
@@ -14,6 +14,5 @@ export default defineHandler(async event => {
|
|
|
14
14
|
LATEST_CONTEXT,
|
|
15
15
|
);
|
|
16
16
|
|
|
17
|
-
|
|
18
|
-
return body;
|
|
17
|
+
return new Response(body, { headers: { 'Content-Type': 'text/plain' } });
|
|
19
18
|
});
|
package/src/server/routes/og.tsx
CHANGED
|
@@ -69,7 +69,5 @@ export default defineHandler(async event => {
|
|
|
69
69
|
},
|
|
70
70
|
);
|
|
71
71
|
|
|
72
|
-
|
|
73
|
-
event.res.headers.set('Cache-Control', 'public, max-age=86400');
|
|
74
|
-
return svg;
|
|
72
|
+
return new Response(svg, { headers: { 'Content-Type': 'image/svg+xml', 'Cache-Control': 'public, max-age=86400' } });
|
|
75
73
|
});
|
|
@@ -1,11 +1,10 @@
|
|
|
1
1
|
import { defineHandler } from 'nitro';
|
|
2
2
|
import { loadConfig } from '@/lib/config';
|
|
3
3
|
|
|
4
|
-
export default defineHandler(
|
|
4
|
+
export default defineHandler(() => {
|
|
5
5
|
const config = loadConfig();
|
|
6
6
|
const sitemap = config.url ? `\nSitemap: ${config.url}/sitemap.xml` : '';
|
|
7
7
|
const body = `User-agent: *\nAllow: /${sitemap}`;
|
|
8
8
|
|
|
9
|
-
|
|
10
|
-
return body;
|
|
9
|
+
return new Response(body, { headers: { 'Content-Type': 'text/plain' } });
|
|
11
10
|
});
|
|
@@ -4,12 +4,11 @@ import { getAllVersions, getApiConfigsForVersion, loadConfig } from '@/lib/confi
|
|
|
4
4
|
import { loadApiSpecs } from '@/lib/openapi';
|
|
5
5
|
import { getPages } from '@/lib/source';
|
|
6
6
|
|
|
7
|
-
export default defineHandler(async
|
|
7
|
+
export default defineHandler(async () => {
|
|
8
8
|
const config = loadConfig();
|
|
9
9
|
|
|
10
10
|
if (!config.url) {
|
|
11
|
-
|
|
12
|
-
return '<urlset xmlns="http://www.sitemaps.org/schemas/sitemap/0.9"/>';
|
|
11
|
+
return new Response('<urlset xmlns="http://www.sitemaps.org/schemas/sitemap/0.9"/>', { headers: { 'Content-Type': 'application/xml' } });
|
|
13
12
|
}
|
|
14
13
|
|
|
15
14
|
const baseUrl = config.url.replace(/\/$/, '');
|
|
@@ -43,6 +42,5 @@ export default defineHandler(async event => {
|
|
|
43
42
|
${[...docPages, ...apiPages].join('\n')}
|
|
44
43
|
</urlset>`;
|
|
45
44
|
|
|
46
|
-
|
|
47
|
-
return xml;
|
|
45
|
+
return new Response(xml, { headers: { 'Content-Type': 'application/xml' } });
|
|
48
46
|
});
|
|
@@ -7,7 +7,9 @@ import fs from 'node:fs/promises';
|
|
|
7
7
|
import path from 'node:path';
|
|
8
8
|
import remarkDirective from 'remark-directive';
|
|
9
9
|
import { type InlineConfig } from 'vite';
|
|
10
|
-
import
|
|
10
|
+
import remarkResolveImages from '../lib/remark-resolve-images';
|
|
11
|
+
import remarkResolveLinks from '../lib/remark-resolve-links';
|
|
12
|
+
import remarkReadingTime from 'remark-reading-time';
|
|
11
13
|
import remarkUnusedDirectives from '../lib/remark-unused-directives';
|
|
12
14
|
|
|
13
15
|
function resolveOutputDir(projectRoot: string, preset?: string): string {
|
|
@@ -55,6 +57,8 @@ export async function createViteConfig(
|
|
|
55
57
|
mdx({
|
|
56
58
|
default: defineFumadocsConfig({
|
|
57
59
|
mdxOptions: {
|
|
60
|
+
remarkImageOptions: false,
|
|
61
|
+
valueToExport: ['readingTime'],
|
|
58
62
|
remarkPlugins: [
|
|
59
63
|
remarkDirective,
|
|
60
64
|
[remarkDirectiveAdmonition, {
|
|
@@ -75,8 +79,10 @@ export async function createViteConfig(
|
|
|
75
79
|
},
|
|
76
80
|
}],
|
|
77
81
|
remarkUnusedDirectives,
|
|
78
|
-
|
|
82
|
+
remarkResolveLinks,
|
|
83
|
+
remarkResolveImages,
|
|
79
84
|
remarkMdxMermaid,
|
|
85
|
+
remarkReadingTime,
|
|
80
86
|
],
|
|
81
87
|
},
|
|
82
88
|
}),
|
|
@@ -1,21 +1,25 @@
|
|
|
1
1
|
.nav {
|
|
2
2
|
display: flex;
|
|
3
3
|
flex-direction: column;
|
|
4
|
-
gap: var(--rs-space-
|
|
4
|
+
gap: var(--rs-space-8);
|
|
5
5
|
}
|
|
6
6
|
|
|
7
7
|
.chapter {
|
|
8
8
|
display: flex;
|
|
9
9
|
flex-direction: column;
|
|
10
10
|
gap: var(--rs-space-2);
|
|
11
|
+
margin-top: var(--rs-space-8);
|
|
11
12
|
}
|
|
12
13
|
|
|
13
14
|
.chapterLabel {
|
|
15
|
+
font-family: var(--paper-font-mono);
|
|
14
16
|
font-size: var(--rs-font-size-small);
|
|
15
|
-
font-weight:
|
|
17
|
+
font-weight: var(--rs-font-weight-medium);
|
|
18
|
+
line-height: var(--rs-line-height-small);
|
|
19
|
+
letter-spacing: var(--rs-letter-spacing-small);
|
|
16
20
|
text-transform: uppercase;
|
|
17
|
-
|
|
18
|
-
|
|
21
|
+
color: var(--rs-color-foreground-base-secondary);
|
|
22
|
+
padding: 0 var(--rs-space-3);
|
|
19
23
|
white-space: nowrap;
|
|
20
24
|
overflow: hidden;
|
|
21
25
|
text-overflow: ellipsis;
|
|
@@ -27,30 +31,32 @@
|
|
|
27
31
|
margin: 0;
|
|
28
32
|
display: flex;
|
|
29
33
|
flex-direction: column;
|
|
30
|
-
gap: var(--rs-space-1);
|
|
31
|
-
padding-left: var(--rs-space-4);
|
|
32
34
|
}
|
|
33
35
|
|
|
34
36
|
.link {
|
|
35
37
|
display: flex;
|
|
36
38
|
align-items: center;
|
|
37
|
-
gap: var(--rs-space-
|
|
39
|
+
gap: var(--rs-space-3);
|
|
40
|
+
font-family: var(--paper-font-body);
|
|
38
41
|
font-size: var(--rs-font-size-small);
|
|
39
|
-
|
|
42
|
+
font-weight: var(--rs-font-weight-regular);
|
|
43
|
+
line-height: var(--rs-line-height-small);
|
|
44
|
+
letter-spacing: var(--rs-letter-spacing-small);
|
|
45
|
+
color: var(--rs-color-foreground-base-primary);
|
|
40
46
|
text-decoration: none;
|
|
41
|
-
padding: var(--rs-space-
|
|
47
|
+
padding: var(--rs-space-3);
|
|
48
|
+
border-radius: var(--rs-radius-2);
|
|
42
49
|
white-space: nowrap;
|
|
43
50
|
overflow: hidden;
|
|
44
51
|
text-overflow: ellipsis;
|
|
45
52
|
}
|
|
46
53
|
|
|
47
54
|
.link:hover {
|
|
48
|
-
color: var(--rs-color-foreground-
|
|
55
|
+
color: var(--rs-color-foreground-accent-primary);
|
|
49
56
|
}
|
|
50
57
|
|
|
51
58
|
.active {
|
|
52
59
|
color: var(--rs-color-foreground-accent-primary);
|
|
53
|
-
font-weight: 500;
|
|
54
60
|
}
|
|
55
61
|
|
|
56
62
|
.icon {
|
|
@@ -60,9 +66,14 @@
|
|
|
60
66
|
}
|
|
61
67
|
|
|
62
68
|
.subLabel {
|
|
69
|
+
font-family: var(--paper-font-mono);
|
|
63
70
|
font-size: var(--rs-font-size-small);
|
|
64
|
-
font-weight:
|
|
71
|
+
font-weight: var(--rs-font-weight-medium);
|
|
72
|
+
line-height: var(--rs-line-height-small);
|
|
73
|
+
letter-spacing: var(--rs-letter-spacing-small);
|
|
74
|
+
text-transform: uppercase;
|
|
65
75
|
color: var(--rs-color-foreground-base-secondary);
|
|
76
|
+
padding: 0 var(--rs-space-3);
|
|
66
77
|
margin-top: var(--rs-space-3);
|
|
67
78
|
display: block;
|
|
68
79
|
white-space: nowrap;
|
|
@@ -15,23 +15,8 @@ interface ChapterNavProps {
|
|
|
15
15
|
tree: Root;
|
|
16
16
|
}
|
|
17
17
|
|
|
18
|
-
function buildChapterIndices(
|
|
19
|
-
children: Node[]
|
|
20
|
-
): Map<Node, number> {
|
|
21
|
-
const indices = new Map<Node, number>();
|
|
22
|
-
let index = 0;
|
|
23
|
-
for (const item of children) {
|
|
24
|
-
if (item.type === 'folder') {
|
|
25
|
-
index++;
|
|
26
|
-
indices.set(item, index);
|
|
27
|
-
}
|
|
28
|
-
}
|
|
29
|
-
return indices;
|
|
30
|
-
}
|
|
31
|
-
|
|
32
18
|
export function ChapterNav({ tree }: ChapterNavProps) {
|
|
33
19
|
const { pathname } = useLocation();
|
|
34
|
-
const chapterIndices = buildChapterIndices(tree.children);
|
|
35
20
|
|
|
36
21
|
return (
|
|
37
22
|
<nav className={styles.nav}>
|
|
@@ -40,11 +25,10 @@ export function ChapterNav({ tree }: ChapterNavProps) {
|
|
|
40
25
|
if (item.type === 'separator') return null;
|
|
41
26
|
|
|
42
27
|
if (item.type === 'folder') {
|
|
43
|
-
const chapterIndex = chapterIndices.get(item) ?? 0;
|
|
44
28
|
return (
|
|
45
29
|
<li key={item.name?.toString()} className={styles.chapter}>
|
|
46
30
|
<span className={styles.chapterLabel}>
|
|
47
|
-
{
|
|
31
|
+
{item.name}
|
|
48
32
|
</span>
|
|
49
33
|
<ul className={styles.chapterItems}>
|
|
50
34
|
{item.children.map(child => (
|