@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
|
@@ -1,5 +1,17 @@
|
|
|
1
|
+
@import url("https://fonts.googleapis.com/css2?family=Hanuman:wght@400;700&display=swap");
|
|
2
|
+
|
|
3
|
+
@font-face {
|
|
4
|
+
font-family: "Departure Mono";
|
|
5
|
+
src: url("./fonts/DepartureMono-Regular.woff2") format("woff2");
|
|
6
|
+
font-weight: 400;
|
|
7
|
+
font-style: normal;
|
|
8
|
+
font-display: swap;
|
|
9
|
+
}
|
|
10
|
+
|
|
1
11
|
.layout {
|
|
2
|
-
--paper-sidebar-width:
|
|
12
|
+
--paper-sidebar-width: 262px;
|
|
13
|
+
--paper-font-mono: "Departure Mono", "SF Mono", "Fira Code", monospace;
|
|
14
|
+
--paper-font-body: "Hanuman", sans-serif;
|
|
3
15
|
|
|
4
16
|
min-height: 100vh;
|
|
5
17
|
}
|
|
@@ -8,33 +20,66 @@
|
|
|
8
20
|
flex: 1;
|
|
9
21
|
}
|
|
10
22
|
|
|
23
|
+
:global(body) {
|
|
24
|
+
background: var(--rs-color-background-neutral-primary);
|
|
25
|
+
}
|
|
26
|
+
|
|
11
27
|
.sidebar {
|
|
12
28
|
width: var(--paper-sidebar-width);
|
|
13
|
-
|
|
29
|
+
display: flex;
|
|
30
|
+
flex-direction: column;
|
|
31
|
+
height: 100vh;
|
|
32
|
+
position: sticky;
|
|
33
|
+
top: 0;
|
|
14
34
|
background: var(--rs-color-background-neutral-primary);
|
|
15
|
-
overflow-y: auto;
|
|
16
|
-
font-family: "SF Mono", "Fira Code", "Fira Mono", "Roboto Mono", monospace;
|
|
17
35
|
}
|
|
18
36
|
|
|
19
|
-
.
|
|
37
|
+
.header {
|
|
38
|
+
display: flex;
|
|
39
|
+
align-items: center;
|
|
40
|
+
height: 48px;
|
|
41
|
+
padding: 0 var(--rs-space-5);
|
|
42
|
+
flex-shrink: 0;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
.contentDirTrigger {
|
|
46
|
+
border: none;
|
|
47
|
+
outline: none;
|
|
48
|
+
background: var(--rs-color-background-neutral-primary);
|
|
49
|
+
color: var(--rs-color-foreground-accent-primary);
|
|
50
|
+
font-family: var(--paper-font-mono);
|
|
51
|
+
font-size: var(--rs-font-size-regular);
|
|
52
|
+
font-weight: var(--rs-font-weight-medium);
|
|
53
|
+
line-height: var(--rs-line-height-mini);
|
|
54
|
+
letter-spacing: var(--rs-letter-spacing-mini);
|
|
20
55
|
text-transform: uppercase;
|
|
21
|
-
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
.title {
|
|
59
|
+
font-size: var(--rs-font-size-regular);
|
|
60
|
+
letter-spacing: var(--rs-letter-spacing-mini);
|
|
22
61
|
color: var(--rs-color-foreground-accent-primary);
|
|
23
|
-
font-family:
|
|
24
|
-
|
|
25
|
-
margin-bottom: var(--rs-space-7);
|
|
62
|
+
font-family: var(--paper-font-mono);
|
|
63
|
+
text-transform: uppercase;
|
|
26
64
|
}
|
|
27
65
|
|
|
28
|
-
.
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
66
|
+
.navScroll {
|
|
67
|
+
flex: 1;
|
|
68
|
+
overflow-y: auto;
|
|
69
|
+
padding: var(--rs-space-5) var(--rs-space-5);
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
.footer {
|
|
73
|
+
flex-shrink: 0;
|
|
74
|
+
padding: var(--rs-space-4) var(--rs-space-5);
|
|
75
|
+
border-top: 1px solid var(--rs-color-border-base-primary);
|
|
33
76
|
}
|
|
34
77
|
|
|
35
78
|
.content {
|
|
36
79
|
flex: 1;
|
|
37
|
-
overflow-y: auto;
|
|
38
80
|
background: var(--rs-color-background-neutral-primary);
|
|
39
|
-
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
.hiddenTrigger {
|
|
84
|
+
display: none;
|
|
40
85
|
}
|
|
@@ -1,44 +1,100 @@
|
|
|
1
1
|
'use client';
|
|
2
2
|
|
|
3
|
-
import { Flex,
|
|
3
|
+
import { Flex, Select, Text } from '@raystack/apsara';
|
|
4
4
|
import { cx } from 'class-variance-authority';
|
|
5
|
+
import { useLocation, useNavigate } from 'react-router';
|
|
6
|
+
import { getLandingEntries } from '@/lib/config';
|
|
7
|
+
import { getActiveContentDir } from '@/lib/navigation';
|
|
8
|
+
import { usePageContext } from '@/lib/page-context';
|
|
9
|
+
import { Search } from '@/components/ui/search';
|
|
5
10
|
import type { ThemeLayoutProps } from '@/types';
|
|
6
11
|
import { ChapterNav } from './ChapterNav';
|
|
7
|
-
import { ContentDirDropdown } from './ContentDirDropdown';
|
|
8
12
|
import styles from './Layout.module.css';
|
|
13
|
+
import { ReaderModeProvider, useReaderMode } from './ReaderModeContext';
|
|
9
14
|
import { VersionSwitcher } from './VersionSwitcher';
|
|
10
15
|
|
|
11
|
-
|
|
16
|
+
function SidebarHeader({ config }: { config: ThemeLayoutProps['config'] }) {
|
|
17
|
+
const { version } = usePageContext();
|
|
18
|
+
const { pathname } = useLocation();
|
|
19
|
+
const navigate = useNavigate();
|
|
20
|
+
|
|
21
|
+
const entries = getLandingEntries(config, version.dir);
|
|
22
|
+
|
|
23
|
+
if (entries.length <= 1) {
|
|
24
|
+
return (
|
|
25
|
+
<Text size={2} weight={500} className={styles.title}>
|
|
26
|
+
{config.site.title}
|
|
27
|
+
</Text>
|
|
28
|
+
);
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
const activeDir = getActiveContentDir(pathname, config);
|
|
32
|
+
const activeEntry =
|
|
33
|
+
entries.find(e => e.contentDir === activeDir) ?? entries[0];
|
|
34
|
+
|
|
35
|
+
return (
|
|
36
|
+
<Select
|
|
37
|
+
value={activeEntry.contentDir}
|
|
38
|
+
onValueChange={(val: string) => {
|
|
39
|
+
const entry = entries.find(e => e.contentDir === val);
|
|
40
|
+
if (entry) navigate(entry.href);
|
|
41
|
+
}}
|
|
42
|
+
>
|
|
43
|
+
<Select.Trigger size='small' className={styles.contentDirTrigger}>
|
|
44
|
+
<Select.Value placeholder={activeEntry.label} className={styles.title} />
|
|
45
|
+
</Select.Trigger>
|
|
46
|
+
<Select.Content>
|
|
47
|
+
{entries.map(entry => (
|
|
48
|
+
<Select.Item key={entry.href} value={entry.contentDir}>
|
|
49
|
+
{entry.label}
|
|
50
|
+
</Select.Item>
|
|
51
|
+
))}
|
|
52
|
+
</Select.Content>
|
|
53
|
+
</Select>
|
|
54
|
+
);
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
function LayoutInner({
|
|
12
58
|
children,
|
|
13
59
|
config,
|
|
14
60
|
tree,
|
|
15
61
|
hideSidebar,
|
|
16
62
|
classNames
|
|
17
63
|
}: ThemeLayoutProps) {
|
|
64
|
+
const { readerMode } = useReaderMode();
|
|
65
|
+
const showSidebar = !hideSidebar && !readerMode;
|
|
66
|
+
|
|
18
67
|
return (
|
|
19
68
|
<Flex direction='column' className={cx(styles.layout, classNames?.layout)}>
|
|
20
69
|
<Flex className={cx(styles.body, classNames?.body)}>
|
|
21
|
-
{
|
|
70
|
+
{showSidebar ? (
|
|
22
71
|
<aside className={cx(styles.sidebar, classNames?.sidebar)}>
|
|
23
|
-
<
|
|
24
|
-
|
|
25
|
-
weight='medium'
|
|
26
|
-
as='h1'
|
|
27
|
-
className={styles.title}
|
|
28
|
-
>
|
|
29
|
-
{config.site.title}
|
|
30
|
-
</Headline>
|
|
31
|
-
<div className={styles.nav}>
|
|
32
|
-
<VersionSwitcher />
|
|
33
|
-
<ContentDirDropdown />
|
|
72
|
+
<div className={styles.header}>
|
|
73
|
+
<SidebarHeader config={config} />
|
|
34
74
|
</div>
|
|
35
|
-
<
|
|
75
|
+
<div className={styles.navScroll}>
|
|
76
|
+
<ChapterNav tree={tree} />
|
|
77
|
+
</div>
|
|
78
|
+
{config.versions?.length ? (
|
|
79
|
+
<div className={styles.footer}>
|
|
80
|
+
<VersionSwitcher />
|
|
81
|
+
</div>
|
|
82
|
+
) : null}
|
|
36
83
|
</aside>
|
|
37
|
-
)}
|
|
84
|
+
) : null}
|
|
38
85
|
<div className={cx(styles.content, classNames?.content)}>
|
|
86
|
+
{config.search?.enabled && <Search classNames={{ trigger: styles.hiddenTrigger }} />}
|
|
39
87
|
{children}
|
|
40
88
|
</div>
|
|
41
89
|
</Flex>
|
|
42
90
|
</Flex>
|
|
43
91
|
);
|
|
44
92
|
}
|
|
93
|
+
|
|
94
|
+
export function Layout(props: ThemeLayoutProps) {
|
|
95
|
+
return (
|
|
96
|
+
<ReaderModeProvider>
|
|
97
|
+
<LayoutInner {...props} />
|
|
98
|
+
</ReaderModeProvider>
|
|
99
|
+
);
|
|
100
|
+
}
|
|
@@ -1,76 +1,89 @@
|
|
|
1
1
|
.main {
|
|
2
|
-
--paper-navbar-height: 40px;
|
|
3
|
-
--paper-navbar-padding: var(--rs-space-3);
|
|
4
|
-
--paper-navbar-total: calc(
|
|
5
|
-
var(--paper-navbar-height) +
|
|
6
|
-
var(--paper-navbar-padding) *
|
|
7
|
-
2 +
|
|
8
|
-
1px
|
|
9
|
-
);
|
|
10
|
-
|
|
11
2
|
flex: 1;
|
|
12
|
-
|
|
3
|
+
width: 90%;
|
|
4
|
+
max-width: calc(1024px + var(--rs-space-17));
|
|
5
|
+
margin: 0 auto;
|
|
6
|
+
padding-top: var(--rs-space-12);
|
|
7
|
+
padding-right: var(--rs-space-17);
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
.readerMode {
|
|
11
|
+
padding-right: 0;
|
|
13
12
|
margin: 0 auto;
|
|
14
13
|
}
|
|
15
14
|
|
|
16
15
|
.navbar {
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
border-bottom: 1px solid var(--rs-color-border-base-primary);
|
|
16
|
+
display: flex;
|
|
17
|
+
align-items: center;
|
|
20
18
|
justify-content: space-between;
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
top: 0;
|
|
19
|
+
height: 48px;
|
|
20
|
+
padding: var(--rs-space-2) var(--rs-space-7);
|
|
24
21
|
background: var(--rs-color-background-neutral-primary);
|
|
22
|
+
backdrop-filter: blur(8px);
|
|
23
|
+
border-bottom: 0.5px solid var(--rs-color-border-base-primary);
|
|
24
|
+
position: sticky;
|
|
25
|
+
top: 0;
|
|
25
26
|
z-index: 10;
|
|
26
|
-
max-width: 1024px;
|
|
27
27
|
}
|
|
28
28
|
|
|
29
29
|
.navLeft {
|
|
30
|
+
display: flex;
|
|
30
31
|
align-items: center;
|
|
32
|
+
gap: var(--rs-space-3);
|
|
31
33
|
}
|
|
32
34
|
|
|
33
35
|
.navRight {
|
|
36
|
+
display: flex;
|
|
34
37
|
align-items: center;
|
|
38
|
+
gap: var(--rs-space-3);
|
|
35
39
|
}
|
|
36
40
|
|
|
37
|
-
.
|
|
41
|
+
.arrows {
|
|
38
42
|
display: flex;
|
|
39
43
|
align-items: center;
|
|
40
|
-
|
|
44
|
+
gap: var(--rs-space-2);
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
.arrowLink {
|
|
48
|
+
display: flex;
|
|
49
|
+
align-items: center;
|
|
50
|
+
justify-content: center;
|
|
41
51
|
text-decoration: none;
|
|
52
|
+
color: var(--rs-color-foreground-base-primary);
|
|
53
|
+
padding: var(--rs-space-1);
|
|
54
|
+
border-radius: var(--rs-radius-2);
|
|
42
55
|
}
|
|
43
56
|
|
|
44
|
-
.
|
|
57
|
+
.arrowLink:hover {
|
|
45
58
|
color: var(--rs-color-foreground-accent-primary);
|
|
46
59
|
}
|
|
47
60
|
|
|
48
61
|
.arrowDisabled {
|
|
49
62
|
display: flex;
|
|
50
63
|
align-items: center;
|
|
64
|
+
justify-content: center;
|
|
51
65
|
color: var(--rs-color-foreground-base-tertiary);
|
|
52
66
|
opacity: 0.4;
|
|
53
|
-
|
|
54
|
-
border: none;
|
|
55
|
-
background: none;
|
|
56
|
-
padding: 0;
|
|
67
|
+
padding: var(--rs-space-1);
|
|
57
68
|
}
|
|
58
69
|
|
|
59
70
|
.breadcrumb {
|
|
60
|
-
|
|
71
|
+
display: flex;
|
|
72
|
+
align-items: center;
|
|
73
|
+
font-family: var(--paper-font-mono);
|
|
61
74
|
font-size: var(--rs-font-size-small);
|
|
62
|
-
|
|
63
|
-
letter-spacing:
|
|
64
|
-
margin-left: var(--rs-space-3);
|
|
75
|
+
line-height: var(--rs-line-height-small);
|
|
76
|
+
letter-spacing: var(--rs-letter-spacing-small);
|
|
65
77
|
}
|
|
66
78
|
|
|
67
79
|
.separator {
|
|
68
|
-
margin: 0 var(--rs-space-
|
|
80
|
+
margin: 0 var(--rs-space-1);
|
|
69
81
|
color: var(--rs-color-foreground-base-tertiary);
|
|
70
82
|
}
|
|
71
83
|
|
|
72
84
|
.crumbLink {
|
|
73
85
|
color: var(--rs-color-foreground-base-tertiary);
|
|
86
|
+
font-weight: var(--rs-font-weight-medium);
|
|
74
87
|
text-decoration: none;
|
|
75
88
|
}
|
|
76
89
|
|
|
@@ -80,29 +93,67 @@
|
|
|
80
93
|
|
|
81
94
|
.crumbActive {
|
|
82
95
|
color: var(--rs-color-foreground-base-primary);
|
|
83
|
-
font-weight:
|
|
96
|
+
font-weight: var(--rs-font-weight-medium);
|
|
84
97
|
}
|
|
85
98
|
|
|
86
99
|
.article {
|
|
87
100
|
flex: 1;
|
|
88
101
|
min-width: 0;
|
|
89
|
-
|
|
102
|
+
width: 100%;
|
|
90
103
|
padding: 0 var(--rs-space-7);
|
|
91
104
|
}
|
|
92
105
|
|
|
93
|
-
.
|
|
94
|
-
|
|
95
|
-
|
|
106
|
+
.articleHeader {
|
|
107
|
+
text-align: center;
|
|
108
|
+
max-width: 656px;
|
|
109
|
+
margin: 0 auto;
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
.readingTime {
|
|
113
|
+
display: block;
|
|
114
|
+
font-family: var(--paper-font-mono);
|
|
96
115
|
font-size: var(--rs-font-size-small);
|
|
116
|
+
font-weight: var(--rs-font-weight-regular);
|
|
117
|
+
line-height: 1.67;
|
|
118
|
+
letter-spacing: var(--rs-letter-spacing-t1);
|
|
119
|
+
color: var(--rs-color-foreground-base-tertiary);
|
|
120
|
+
margin-bottom: var(--rs-space-5);
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
.articleTitle {
|
|
124
|
+
font-family: var(--paper-font-body);
|
|
125
|
+
font-size: var(--rs-space-8);
|
|
126
|
+
font-weight: var(--rs-font-weight-medium);
|
|
127
|
+
line-height: var(--rs-space-10);
|
|
128
|
+
letter-spacing: var(--rs-letter-spacing-t1);
|
|
129
|
+
text-align: center;
|
|
130
|
+
color: var(--rs-color-foreground-base-primary);
|
|
131
|
+
margin: 0;
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
.articleSeparator {
|
|
135
|
+
width: 55px;
|
|
97
136
|
border: none;
|
|
98
|
-
|
|
137
|
+
border-top: 1px solid var(--rs-color-border-base-primary);
|
|
138
|
+
margin: var(--rs-space-10) auto;
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
.articleDescription {
|
|
142
|
+
font-family: var(--paper-font-body);
|
|
143
|
+
font-size: var(--rs-space-4);
|
|
144
|
+
font-weight: var(--rs-font-weight-medium);
|
|
145
|
+
line-height: var(--rs-space-7);
|
|
146
|
+
letter-spacing: var(--rs-letter-spacing-t1);
|
|
147
|
+
text-align: center;
|
|
148
|
+
color: var(--rs-color-foreground-base-secondary);
|
|
149
|
+
margin: var(--rs-space-4) 0 0;
|
|
99
150
|
}
|
|
100
151
|
|
|
101
152
|
.content {
|
|
102
|
-
font-family:
|
|
153
|
+
font-family: var(--paper-font-body);
|
|
103
154
|
line-height: 1.8;
|
|
104
155
|
background: var(--rs-color-background-base-primary);
|
|
105
|
-
padding: var(--rs-space-9);
|
|
156
|
+
padding: var(--rs-space-15) var(--rs-space-9) var(--rs-space-9);
|
|
106
157
|
border-left: 1px solid var(--rs-color-border-base-primary);
|
|
107
158
|
border-right: 1px solid var(--rs-color-border-base-primary);
|
|
108
159
|
box-shadow:
|
|
@@ -152,6 +203,7 @@
|
|
|
152
203
|
|
|
153
204
|
.content p {
|
|
154
205
|
margin: 0.75rem 0;
|
|
206
|
+
line-height: 2;
|
|
155
207
|
}
|
|
156
208
|
|
|
157
209
|
.content ul,
|
|
@@ -1,16 +1,31 @@
|
|
|
1
|
-
import {
|
|
2
|
-
|
|
3
|
-
|
|
1
|
+
import {
|
|
2
|
+
ArrowLeftIcon,
|
|
3
|
+
ArrowRightIcon,
|
|
4
|
+
ChevronRightIcon,
|
|
5
|
+
AdjustmentsHorizontalIcon,
|
|
6
|
+
EyeIcon,
|
|
7
|
+
SunIcon,
|
|
8
|
+
MoonIcon,
|
|
9
|
+
XMarkIcon,
|
|
10
|
+
} from '@heroicons/react/24/outline';
|
|
11
|
+
import { IconButton, useTheme } from '@raystack/apsara';
|
|
12
|
+
import { useEffect, useMemo, useState } from 'react';
|
|
4
13
|
import { Link as RouterLink, useLocation } from 'react-router';
|
|
5
14
|
import { getBreadcrumbItems } from 'fumadocs-core/breadcrumb';
|
|
6
15
|
import { flattenTree } from 'fumadocs-core/page-tree';
|
|
7
|
-
import { Search } from '@/components/ui/search';
|
|
8
16
|
import type { ThemePageProps } from '@/types';
|
|
9
17
|
import styles from './Page.module.css';
|
|
18
|
+
import { useReaderMode } from './ReaderModeContext';
|
|
10
19
|
import { ReadingProgress } from './ReadingProgress';
|
|
11
20
|
|
|
12
|
-
export function Page({ page,
|
|
21
|
+
export function Page({ page, tree }: ThemePageProps) {
|
|
13
22
|
const { pathname } = useLocation();
|
|
23
|
+
const [settingsOpen, setSettingsOpen] = useState(false);
|
|
24
|
+
const [isClient, setIsClient] = useState(false);
|
|
25
|
+
const { resolvedTheme, setTheme } = useTheme();
|
|
26
|
+
const { readerMode, toggleReaderMode } = useReaderMode();
|
|
27
|
+
|
|
28
|
+
useEffect(() => { setIsClient(true); }, []);
|
|
14
29
|
|
|
15
30
|
const { prev, next, crumbs } = useMemo(() => {
|
|
16
31
|
const pages = flattenTree(tree.children);
|
|
@@ -32,47 +47,33 @@ export function Page({ page, config, tree }: ThemePageProps) {
|
|
|
32
47
|
|
|
33
48
|
return (
|
|
34
49
|
<>
|
|
35
|
-
<main className={styles.main}>
|
|
36
|
-
<
|
|
37
|
-
<
|
|
38
|
-
{
|
|
39
|
-
|
|
40
|
-
to={prev.url}
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
<
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
className={styles.
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
className={styles.arrow}
|
|
59
|
-
aria-label='Next page'
|
|
60
|
-
>
|
|
61
|
-
<ChevronRightIcon width={14} height={14} />
|
|
62
|
-
</RouterLink>
|
|
63
|
-
) : (
|
|
64
|
-
<button
|
|
65
|
-
disabled
|
|
66
|
-
className={styles.arrowDisabled}
|
|
67
|
-
aria-label='Next page'
|
|
68
|
-
>
|
|
69
|
-
<ChevronRightIcon width={14} height={14} />
|
|
70
|
-
</button>
|
|
71
|
-
)}
|
|
50
|
+
<main className={`${styles.main} ${readerMode ? styles.readerMode : ''}`}>
|
|
51
|
+
<div className={styles.navbar}>
|
|
52
|
+
<div className={styles.navLeft}>
|
|
53
|
+
<div className={styles.arrows}>
|
|
54
|
+
{prev ? (
|
|
55
|
+
<RouterLink to={prev.url} className={styles.arrowLink} aria-label='Previous page'>
|
|
56
|
+
<ArrowLeftIcon width={14} height={14} />
|
|
57
|
+
</RouterLink>
|
|
58
|
+
) : (
|
|
59
|
+
<span className={styles.arrowDisabled} aria-hidden='true'>
|
|
60
|
+
<ArrowLeftIcon width={14} height={14} />
|
|
61
|
+
</span>
|
|
62
|
+
)}
|
|
63
|
+
{next ? (
|
|
64
|
+
<RouterLink to={next.url} className={styles.arrowLink} aria-label='Next page'>
|
|
65
|
+
<ArrowRightIcon width={14} height={14} />
|
|
66
|
+
</RouterLink>
|
|
67
|
+
) : (
|
|
68
|
+
<span className={styles.arrowDisabled} aria-hidden='true'>
|
|
69
|
+
<ArrowRightIcon width={14} height={14} />
|
|
70
|
+
</span>
|
|
71
|
+
)}
|
|
72
|
+
</div>
|
|
72
73
|
<nav className={styles.breadcrumb}>
|
|
73
74
|
{crumbs.map((crumb, i) => (
|
|
74
75
|
<span key={crumb.href}>
|
|
75
|
-
{i > 0 && <
|
|
76
|
+
{i > 0 && <ChevronRightIcon width={12} height={12} className={styles.separator} />}
|
|
76
77
|
{i === crumbs.length - 1 ? (
|
|
77
78
|
<span className={styles.crumbActive}>{crumb.label}</span>
|
|
78
79
|
) : (
|
|
@@ -83,18 +84,53 @@ export function Page({ page, config, tree }: ThemePageProps) {
|
|
|
83
84
|
</span>
|
|
84
85
|
))}
|
|
85
86
|
</nav>
|
|
86
|
-
</
|
|
87
|
-
<
|
|
88
|
-
{
|
|
89
|
-
|
|
87
|
+
</div>
|
|
88
|
+
<div className={styles.navRight}>
|
|
89
|
+
{settingsOpen ? (
|
|
90
|
+
<>
|
|
91
|
+
<IconButton size={2} onClick={toggleReaderMode} aria-label='Toggle reader mode'>
|
|
92
|
+
<EyeIcon width={14} height={14} />
|
|
93
|
+
</IconButton>
|
|
94
|
+
{isClient && (
|
|
95
|
+
<IconButton
|
|
96
|
+
size={2}
|
|
97
|
+
onClick={() => setTheme(resolvedTheme === 'dark' ? 'light' : 'dark')}
|
|
98
|
+
aria-label={resolvedTheme === 'dark' ? 'Switch to light theme' : 'Switch to dark theme'}
|
|
99
|
+
>
|
|
100
|
+
{resolvedTheme === 'dark'
|
|
101
|
+
? <SunIcon width={14} height={14} />
|
|
102
|
+
: <MoonIcon width={14} height={14} />
|
|
103
|
+
}
|
|
104
|
+
</IconButton>
|
|
105
|
+
)}
|
|
106
|
+
<IconButton size={2} onClick={() => setSettingsOpen(false)} aria-label='Close settings'>
|
|
107
|
+
<XMarkIcon width={14} height={14} />
|
|
108
|
+
</IconButton>
|
|
109
|
+
</>
|
|
110
|
+
) : (
|
|
111
|
+
<IconButton size={2} onClick={() => setSettingsOpen(true)} aria-label='Open settings'>
|
|
112
|
+
<AdjustmentsHorizontalIcon width={14} height={14} />
|
|
113
|
+
</IconButton>
|
|
114
|
+
)}
|
|
115
|
+
</div>
|
|
116
|
+
</div>
|
|
117
|
+
<div className={styles.content}>
|
|
118
|
+
<header className={styles.articleHeader}>
|
|
119
|
+
{page.frontmatter._readingTime && (
|
|
120
|
+
<span className={styles.readingTime}>{page.frontmatter._readingTime}min Read</span>
|
|
121
|
+
)}
|
|
122
|
+
<h1 className={styles.articleTitle}>{page.frontmatter.title}</h1>
|
|
123
|
+
{page.frontmatter.description && (
|
|
124
|
+
<p className={styles.articleDescription}>{page.frontmatter.description}</p>
|
|
90
125
|
)}
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
126
|
+
<hr className={styles.articleSeparator} />
|
|
127
|
+
</header>
|
|
128
|
+
<article className={styles.article} data-article-content>
|
|
129
|
+
{page.content}
|
|
130
|
+
</article>
|
|
131
|
+
</div>
|
|
96
132
|
</main>
|
|
97
|
-
<ReadingProgress items={page.toc} />
|
|
133
|
+
{!readerMode && <ReadingProgress items={page.toc} />}
|
|
98
134
|
</>
|
|
99
135
|
);
|
|
100
136
|
}
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
'use client';
|
|
2
|
+
|
|
3
|
+
import { createContext, useContext, useState, type ReactNode } from 'react';
|
|
4
|
+
|
|
5
|
+
interface ReaderModeContextValue {
|
|
6
|
+
readerMode: boolean;
|
|
7
|
+
toggleReaderMode: () => void;
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
const ReaderModeContext = createContext<ReaderModeContextValue>({
|
|
11
|
+
readerMode: false,
|
|
12
|
+
toggleReaderMode: () => {},
|
|
13
|
+
});
|
|
14
|
+
|
|
15
|
+
export function ReaderModeProvider({ children }: { children: ReactNode }) {
|
|
16
|
+
const [readerMode, setReaderMode] = useState(false);
|
|
17
|
+
return (
|
|
18
|
+
<ReaderModeContext.Provider
|
|
19
|
+
value={{ readerMode, toggleReaderMode: () => setReaderMode(v => !v) }}
|
|
20
|
+
>
|
|
21
|
+
{children}
|
|
22
|
+
</ReaderModeContext.Provider>
|
|
23
|
+
);
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
export function useReaderMode() {
|
|
27
|
+
return useContext(ReaderModeContext);
|
|
28
|
+
}
|
|
@@ -220,6 +220,7 @@ export function ReadingProgress({ items }: ReadingProgressProps) {
|
|
|
220
220
|
const element = document.getElementById(id);
|
|
221
221
|
if (!element) return;
|
|
222
222
|
|
|
223
|
+
history.pushState(null, '', `#${id}`);
|
|
223
224
|
const elementTop = element.getBoundingClientRect().top + window.scrollY;
|
|
224
225
|
window.scrollTo({
|
|
225
226
|
top: Math.max(0, elementTop - NAV_HEIGHT),
|
|
Binary file
|
package/src/themes/registry.ts
CHANGED
|
@@ -15,7 +15,7 @@ export function getTheme(name?: string): Theme {
|
|
|
15
15
|
|
|
16
16
|
export function getThemeConfig(name?: string) {
|
|
17
17
|
if (name === 'paper') {
|
|
18
|
-
return { enableSystem:
|
|
18
|
+
return { enableSystem: true };
|
|
19
19
|
}
|
|
20
20
|
return { enableSystem: true };
|
|
21
21
|
}
|
package/src/types/config.ts
CHANGED
package/src/types/content.ts
CHANGED
|
@@ -1,14 +0,0 @@
|
|
|
1
|
-
import { visit } from 'unist-util-visit'
|
|
2
|
-
import type { Plugin } from 'unified'
|
|
3
|
-
|
|
4
|
-
const remarkStripMdExtensions: Plugin = () => {
|
|
5
|
-
return (tree) => {
|
|
6
|
-
visit(tree, 'link', (node: any) => {
|
|
7
|
-
if (!node.url) return
|
|
8
|
-
if (node.url.startsWith('http://') || node.url.startsWith('https://')) return
|
|
9
|
-
node.url = node.url.replace(/\.mdx?(#|$)/, '$1')
|
|
10
|
-
})
|
|
11
|
-
}
|
|
12
|
-
}
|
|
13
|
-
|
|
14
|
-
export default remarkStripMdExtensions
|