@raystack/chronicle 0.1.0-canary.a320792 → 0.1.0-canary.ac60f9f
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 +150 -416
- package/package.json +13 -9
- package/src/cli/commands/build.ts +30 -48
- package/src/cli/commands/dev.ts +24 -13
- package/src/cli/commands/init.ts +38 -123
- package/src/cli/commands/serve.ts +35 -50
- package/src/cli/commands/start.ts +20 -16
- package/src/cli/index.ts +14 -14
- package/src/cli/utils/config.ts +25 -26
- package/src/cli/utils/index.ts +3 -2
- package/src/cli/utils/resolve.ts +7 -3
- package/src/cli/utils/scaffold.ts +14 -16
- package/src/components/mdx/code.tsx +1 -10
- package/src/components/mdx/details.module.css +24 -1
- package/src/components/mdx/details.tsx +3 -2
- package/src/components/mdx/image.tsx +5 -20
- package/src/components/mdx/index.tsx +3 -3
- package/src/components/mdx/link.tsx +24 -20
- package/src/components/ui/footer.tsx +2 -3
- package/src/components/ui/search.tsx +116 -71
- package/src/lib/config.ts +31 -29
- package/src/lib/get-llm-text.ts +10 -0
- package/src/lib/head.tsx +26 -22
- package/src/lib/openapi.ts +8 -8
- package/src/lib/page-context.tsx +76 -57
- package/src/lib/source.ts +144 -96
- package/src/pages/ApiLayout.tsx +22 -18
- package/src/pages/ApiPage.tsx +32 -27
- package/src/pages/DocsLayout.tsx +7 -7
- package/src/pages/DocsPage.tsx +11 -11
- package/src/pages/NotFound.tsx +11 -4
- package/src/server/App.tsx +35 -27
- package/src/server/api/apis-proxy.ts +69 -0
- package/src/server/api/health.ts +5 -0
- package/src/server/api/page/[...slug].ts +18 -0
- package/src/server/api/search.ts +170 -0
- package/src/server/api/specs.ts +9 -0
- package/src/server/build-search-index.ts +117 -0
- package/src/server/entry-client.tsx +52 -56
- package/src/server/entry-server.tsx +95 -35
- package/src/server/routes/llms.txt.ts +61 -0
- package/src/server/routes/og.tsx +75 -0
- package/src/server/routes/robots.txt.ts +11 -0
- package/src/server/routes/sitemap.xml.ts +39 -0
- package/src/server/utils/safe-path.ts +17 -0
- package/src/server/vite-config.ts +50 -49
- package/src/themes/default/Layout.tsx +69 -41
- package/src/themes/default/Page.module.css +0 -60
- package/src/themes/default/Page.tsx +9 -11
- package/src/themes/default/Toc.tsx +30 -28
- package/src/themes/default/index.ts +7 -9
- package/src/themes/paper/ChapterNav.tsx +59 -39
- package/src/themes/paper/Layout.module.css +1 -1
- package/src/themes/paper/Layout.tsx +24 -12
- package/src/themes/paper/Page.module.css +11 -4
- package/src/themes/paper/Page.tsx +67 -47
- package/src/themes/paper/ReadingProgress.tsx +160 -139
- package/src/themes/paper/index.ts +5 -5
- package/src/themes/registry.ts +7 -7
- package/src/types/globals.d.ts +4 -0
- package/src/cli/__tests__/config.test.ts +0 -25
- package/src/cli/__tests__/scaffold.test.ts +0 -10
- package/src/pages/__tests__/head.test.tsx +0 -57
- package/src/server/__tests__/entry-server.test.tsx +0 -35
- package/src/server/__tests__/handlers.test.ts +0 -77
- package/src/server/__tests__/og.test.ts +0 -23
- package/src/server/__tests__/router.test.ts +0 -72
- package/src/server/__tests__/vite-config.test.ts +0 -25
- package/src/server/dev.ts +0 -156
- package/src/server/entry-prod.ts +0 -127
- package/src/server/handlers/apis-proxy.ts +0 -52
- package/src/server/handlers/health.ts +0 -3
- package/src/server/handlers/llms.ts +0 -58
- package/src/server/handlers/og.ts +0 -87
- package/src/server/handlers/robots.ts +0 -11
- package/src/server/handlers/search.ts +0 -140
- package/src/server/handlers/sitemap.ts +0 -39
- package/src/server/handlers/specs.ts +0 -9
- package/src/server/index.html +0 -12
- package/src/server/prod.ts +0 -18
- package/src/server/router.ts +0 -42
- package/src/themes/default/font.ts +0 -4
|
@@ -1,64 +1,86 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
import {
|
|
11
|
-
import {
|
|
12
|
-
import
|
|
13
|
-
import
|
|
1
|
+
import { RectangleStackIcon } from '@heroicons/react/24/outline';
|
|
2
|
+
import {
|
|
3
|
+
Button,
|
|
4
|
+
Flex,
|
|
5
|
+
Headline,
|
|
6
|
+
Link,
|
|
7
|
+
Navbar,
|
|
8
|
+
Sidebar
|
|
9
|
+
} from '@raystack/apsara';
|
|
10
|
+
import { cx } from 'class-variance-authority';
|
|
11
|
+
import { useEffect, useMemo, useRef } from 'react';
|
|
12
|
+
import { Link as RouterLink, useLocation } from 'react-router';
|
|
13
|
+
import { MethodBadge } from '@/components/api/method-badge';
|
|
14
|
+
import { ClientThemeSwitcher } from '@/components/ui/client-theme-switcher';
|
|
15
|
+
import { Footer } from '@/components/ui/footer';
|
|
16
|
+
import { Search } from '@/components/ui/search';
|
|
17
|
+
import type { PageTreeItem, ThemeLayoutProps } from '@/types';
|
|
18
|
+
import styles from './Layout.module.css';
|
|
14
19
|
|
|
15
20
|
const iconMap: Record<string, React.ReactNode> = {
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
21
|
+
'rectangle-stack': <RectangleStackIcon width={16} height={16} />,
|
|
22
|
+
'method-get': <MethodBadge method='GET' size='micro' />,
|
|
23
|
+
'method-post': <MethodBadge method='POST' size='micro' />,
|
|
24
|
+
'method-put': <MethodBadge method='PUT' size='micro' />,
|
|
25
|
+
'method-delete': <MethodBadge method='DELETE' size='micro' />,
|
|
26
|
+
'method-patch': <MethodBadge method='PATCH' size='micro' />
|
|
22
27
|
};
|
|
23
28
|
|
|
24
29
|
let savedScrollTop = 0;
|
|
25
30
|
|
|
26
|
-
export function Layout({
|
|
31
|
+
export function Layout({
|
|
32
|
+
children,
|
|
33
|
+
config,
|
|
34
|
+
tree,
|
|
35
|
+
classNames
|
|
36
|
+
}: ThemeLayoutProps) {
|
|
27
37
|
const { pathname } = useLocation();
|
|
28
38
|
const scrollRef = useRef<HTMLDivElement>(null);
|
|
29
39
|
|
|
30
40
|
useEffect(() => {
|
|
31
41
|
const el = scrollRef.current;
|
|
32
42
|
if (!el) return;
|
|
33
|
-
const onScroll = () => {
|
|
43
|
+
const onScroll = () => {
|
|
44
|
+
savedScrollTop = el.scrollTop;
|
|
45
|
+
};
|
|
34
46
|
el.addEventListener('scroll', onScroll);
|
|
35
47
|
return () => el.removeEventListener('scroll', onScroll);
|
|
36
48
|
}, []);
|
|
37
49
|
|
|
38
50
|
useEffect(() => {
|
|
39
51
|
const el = scrollRef.current;
|
|
40
|
-
if (el)
|
|
52
|
+
if (el)
|
|
53
|
+
requestAnimationFrame(() => {
|
|
54
|
+
el.scrollTop = savedScrollTop;
|
|
55
|
+
});
|
|
41
56
|
}, [pathname]);
|
|
42
57
|
|
|
43
58
|
return (
|
|
44
|
-
<Flex direction=
|
|
59
|
+
<Flex direction='column' className={cx(styles.layout, classNames?.layout)}>
|
|
45
60
|
<Navbar className={styles.header}>
|
|
46
61
|
<Navbar.Start>
|
|
47
|
-
<
|
|
48
|
-
|
|
62
|
+
<RouterLink
|
|
63
|
+
to='/'
|
|
64
|
+
style={{ textDecoration: 'none', color: 'inherit' }}
|
|
65
|
+
>
|
|
66
|
+
<Headline size='small' weight='medium' as='h1'>
|
|
49
67
|
{config.title}
|
|
50
68
|
</Headline>
|
|
51
|
-
</
|
|
69
|
+
</RouterLink>
|
|
52
70
|
</Navbar.Start>
|
|
53
71
|
<Navbar.End>
|
|
54
|
-
<Flex gap=
|
|
55
|
-
{config.api?.map(
|
|
56
|
-
<
|
|
72
|
+
<Flex gap='medium' align='center' className={styles.navActions}>
|
|
73
|
+
{config.api?.map(api => (
|
|
74
|
+
<RouterLink
|
|
75
|
+
key={api.basePath}
|
|
76
|
+
to={api.basePath}
|
|
77
|
+
className={styles.navButton}
|
|
78
|
+
>
|
|
57
79
|
{api.name} API
|
|
58
|
-
</
|
|
80
|
+
</RouterLink>
|
|
59
81
|
))}
|
|
60
|
-
{config.navigation?.links?.map(
|
|
61
|
-
<Link key={link.href}
|
|
82
|
+
{config.navigation?.links?.map(link => (
|
|
83
|
+
<Link key={link.href} href={link.href}>
|
|
62
84
|
{link.label}
|
|
63
85
|
</Link>
|
|
64
86
|
))}
|
|
@@ -68,9 +90,13 @@ export function Layout({ children, config, tree, classNames }: ThemeLayoutProps)
|
|
|
68
90
|
</Navbar.End>
|
|
69
91
|
</Navbar>
|
|
70
92
|
<Flex className={cx(styles.body, classNames?.body)}>
|
|
71
|
-
<Sidebar
|
|
93
|
+
<Sidebar
|
|
94
|
+
defaultOpen
|
|
95
|
+
collapsible={false}
|
|
96
|
+
className={cx(styles.sidebar, classNames?.sidebar)}
|
|
97
|
+
>
|
|
72
98
|
<Sidebar.Main ref={scrollRef}>
|
|
73
|
-
{tree.children.map(
|
|
99
|
+
{tree.children.map(item => (
|
|
74
100
|
<SidebarNode
|
|
75
101
|
key={item.url ?? item.name}
|
|
76
102
|
item={item}
|
|
@@ -79,7 +105,9 @@ export function Layout({ children, config, tree, classNames }: ThemeLayoutProps)
|
|
|
79
105
|
))}
|
|
80
106
|
</Sidebar.Main>
|
|
81
107
|
</Sidebar>
|
|
82
|
-
<main className={cx(styles.content, classNames?.content)}>
|
|
108
|
+
<main className={cx(styles.content, classNames?.content)}>
|
|
109
|
+
{children}
|
|
110
|
+
</main>
|
|
83
111
|
</Flex>
|
|
84
112
|
<Footer config={config.footer} />
|
|
85
113
|
</Flex>
|
|
@@ -88,23 +116,23 @@ export function Layout({ children, config, tree, classNames }: ThemeLayoutProps)
|
|
|
88
116
|
|
|
89
117
|
function SidebarNode({
|
|
90
118
|
item,
|
|
91
|
-
pathname
|
|
119
|
+
pathname
|
|
92
120
|
}: {
|
|
93
121
|
item: PageTreeItem;
|
|
94
122
|
pathname: string;
|
|
95
123
|
}) {
|
|
96
|
-
if (item.type ===
|
|
124
|
+
if (item.type === 'separator') {
|
|
97
125
|
return null;
|
|
98
126
|
}
|
|
99
127
|
|
|
100
|
-
if (item.type ===
|
|
128
|
+
if (item.type === 'folder' && item.children) {
|
|
101
129
|
return (
|
|
102
130
|
<Sidebar.Group
|
|
103
131
|
label={item.name}
|
|
104
132
|
leadingIcon={item.icon ? iconMap[item.icon] : undefined}
|
|
105
133
|
classNames={{ items: styles.groupItems }}
|
|
106
134
|
>
|
|
107
|
-
{item.children.map(
|
|
135
|
+
{item.children.map(child => (
|
|
108
136
|
<SidebarNode
|
|
109
137
|
key={child.url ?? child.name}
|
|
110
138
|
item={child}
|
|
@@ -116,8 +144,8 @@ function SidebarNode({
|
|
|
116
144
|
}
|
|
117
145
|
|
|
118
146
|
const isActive = pathname === item.url;
|
|
119
|
-
const href = item.url ??
|
|
120
|
-
const link = useMemo(() => <
|
|
147
|
+
const href = item.url ?? '#';
|
|
148
|
+
const link = useMemo(() => <RouterLink to={href} />, [href]);
|
|
121
149
|
|
|
122
150
|
return (
|
|
123
151
|
<Sidebar.Item
|
|
@@ -38,69 +38,9 @@
|
|
|
38
38
|
margin-bottom: var(--rs-space-3);
|
|
39
39
|
}
|
|
40
40
|
|
|
41
|
-
.content :global(pre) {
|
|
42
|
-
background-color: var(--shiki-light-bg, #fff);
|
|
43
|
-
}
|
|
44
|
-
|
|
45
|
-
.content :global(pre code span) {
|
|
46
|
-
color: var(--shiki-light);
|
|
47
|
-
}
|
|
48
|
-
|
|
49
|
-
:global([data-theme="dark"]) .content :global(pre) {
|
|
50
|
-
background-color: var(--shiki-dark-bg, #24292e);
|
|
51
|
-
}
|
|
52
|
-
|
|
53
|
-
:global([data-theme="dark"]) .content :global(pre code span) {
|
|
54
|
-
color: var(--shiki-dark);
|
|
55
|
-
}
|
|
56
|
-
|
|
57
|
-
.content img {
|
|
58
|
-
max-width: 100%;
|
|
59
|
-
height: auto;
|
|
60
|
-
}
|
|
61
|
-
|
|
62
41
|
.content table {
|
|
63
42
|
display: block;
|
|
64
43
|
max-width: 100%;
|
|
65
44
|
overflow-x: auto;
|
|
66
45
|
margin-bottom: var(--rs-space-5);
|
|
67
46
|
}
|
|
68
|
-
|
|
69
|
-
.content details {
|
|
70
|
-
border: 1px solid var(--rs-color-border-base-primary);
|
|
71
|
-
border-radius: var(--rs-radius-2);
|
|
72
|
-
margin: var(--rs-space-5) 0;
|
|
73
|
-
overflow: hidden;
|
|
74
|
-
}
|
|
75
|
-
|
|
76
|
-
.content details summary {
|
|
77
|
-
padding: var(--rs-space-4) var(--rs-space-5);
|
|
78
|
-
cursor: pointer;
|
|
79
|
-
font-weight: 500;
|
|
80
|
-
font-size: var(--rs-font-size-small);
|
|
81
|
-
color: var(--rs-color-text-base-primary);
|
|
82
|
-
background: var(--rs-color-background-base-secondary);
|
|
83
|
-
list-style: none;
|
|
84
|
-
display: flex;
|
|
85
|
-
align-items: center;
|
|
86
|
-
gap: var(--rs-space-3);
|
|
87
|
-
}
|
|
88
|
-
|
|
89
|
-
.content details summary::-webkit-details-marker {
|
|
90
|
-
display: none;
|
|
91
|
-
}
|
|
92
|
-
|
|
93
|
-
.content details summary::before {
|
|
94
|
-
content: '▶';
|
|
95
|
-
font-size: 10px;
|
|
96
|
-
transition: transform 0.2s ease;
|
|
97
|
-
color: var(--rs-color-text-base-secondary);
|
|
98
|
-
}
|
|
99
|
-
|
|
100
|
-
.content details[open] > summary::before {
|
|
101
|
-
transform: rotate(90deg);
|
|
102
|
-
}
|
|
103
|
-
|
|
104
|
-
.content details > :not(summary) {
|
|
105
|
-
padding: var(--rs-space-4) var(--rs-space-5);
|
|
106
|
-
}
|
|
@@ -1,21 +1,19 @@
|
|
|
1
|
-
'use client'
|
|
1
|
+
'use client';
|
|
2
2
|
|
|
3
|
-
import { Flex } from '@raystack/apsara'
|
|
4
|
-
import
|
|
5
|
-
import {
|
|
6
|
-
import
|
|
7
|
-
import
|
|
3
|
+
import { Flex } from '@raystack/apsara';
|
|
4
|
+
import { Breadcrumbs } from '@/components/ui/breadcrumbs';
|
|
5
|
+
import type { ThemePageProps } from '@/types';
|
|
6
|
+
import styles from './Page.module.css';
|
|
7
|
+
import { Toc } from './Toc';
|
|
8
8
|
|
|
9
9
|
export function Page({ page, tree }: ThemePageProps) {
|
|
10
10
|
return (
|
|
11
11
|
<Flex className={styles.page}>
|
|
12
|
-
<article className={styles.article}>
|
|
12
|
+
<article className={styles.article} data-article-content>
|
|
13
13
|
<Breadcrumbs slug={page.slug} tree={tree} />
|
|
14
|
-
<div className={styles.content}>
|
|
15
|
-
{page.content}
|
|
16
|
-
</div>
|
|
14
|
+
<div className={styles.content}>{page.content}</div>
|
|
17
15
|
</article>
|
|
18
16
|
<Toc items={page.toc} />
|
|
19
17
|
</Flex>
|
|
20
|
-
)
|
|
18
|
+
);
|
|
21
19
|
}
|
|
@@ -1,55 +1,57 @@
|
|
|
1
|
-
'use client'
|
|
1
|
+
'use client';
|
|
2
2
|
|
|
3
|
-
import {
|
|
4
|
-
import {
|
|
5
|
-
import type { TocItem } from '@/types'
|
|
6
|
-
import styles from './Toc.module.css'
|
|
3
|
+
import { Text } from '@raystack/apsara';
|
|
4
|
+
import { useEffect, useState } from 'react';
|
|
5
|
+
import type { TocItem } from '@/types';
|
|
6
|
+
import styles from './Toc.module.css';
|
|
7
7
|
|
|
8
8
|
interface TocProps {
|
|
9
|
-
items: TocItem[]
|
|
9
|
+
items: TocItem[];
|
|
10
10
|
}
|
|
11
11
|
|
|
12
12
|
export function Toc({ items }: TocProps) {
|
|
13
|
-
const [activeId, setActiveId] = useState<string>('')
|
|
13
|
+
const [activeId, setActiveId] = useState<string>('');
|
|
14
14
|
|
|
15
15
|
// Filter to only show h2 and h3 headings
|
|
16
|
-
const filteredItems = items.filter(
|
|
16
|
+
const filteredItems = items.filter(
|
|
17
|
+
item => item.depth >= 2 && item.depth <= 3
|
|
18
|
+
);
|
|
17
19
|
|
|
18
20
|
useEffect(() => {
|
|
19
|
-
const headingIds = filteredItems.map(
|
|
21
|
+
const headingIds = filteredItems.map(item => item.url.replace('#', ''));
|
|
20
22
|
|
|
21
23
|
const observer = new IntersectionObserver(
|
|
22
|
-
|
|
23
|
-
entries.forEach(
|
|
24
|
+
entries => {
|
|
25
|
+
entries.forEach(entry => {
|
|
24
26
|
if (entry.isIntersecting) {
|
|
25
|
-
setActiveId(entry.target.id)
|
|
27
|
+
setActiveId(entry.target.id);
|
|
26
28
|
}
|
|
27
|
-
})
|
|
29
|
+
});
|
|
28
30
|
},
|
|
29
31
|
// -80px top: offset for fixed header, -80% bottom: trigger when heading is in top 20% of viewport
|
|
30
32
|
{ rootMargin: '-80px 0px -80% 0px' }
|
|
31
|
-
)
|
|
33
|
+
);
|
|
32
34
|
|
|
33
|
-
headingIds.forEach(
|
|
34
|
-
const element = document.getElementById(id)
|
|
35
|
-
if (element) observer.observe(element)
|
|
36
|
-
})
|
|
35
|
+
headingIds.forEach(id => {
|
|
36
|
+
const element = document.getElementById(id);
|
|
37
|
+
if (element) observer.observe(element);
|
|
38
|
+
});
|
|
37
39
|
|
|
38
|
-
return () => observer.disconnect()
|
|
39
|
-
}, [filteredItems])
|
|
40
|
+
return () => observer.disconnect();
|
|
41
|
+
}, [filteredItems]);
|
|
40
42
|
|
|
41
|
-
if (filteredItems.length === 0) return null
|
|
43
|
+
if (filteredItems.length === 0) return null;
|
|
42
44
|
|
|
43
45
|
return (
|
|
44
46
|
<aside className={styles.toc}>
|
|
45
|
-
<Text size={1} weight=
|
|
47
|
+
<Text size={1} weight='medium' className={styles.title}>
|
|
46
48
|
On this page
|
|
47
49
|
</Text>
|
|
48
50
|
<nav className={styles.nav}>
|
|
49
|
-
{filteredItems.map(
|
|
50
|
-
const id = item.url.replace('#', '')
|
|
51
|
-
const isActive = activeId === id
|
|
52
|
-
const isNested = item.depth > 2
|
|
51
|
+
{filteredItems.map(item => {
|
|
52
|
+
const id = item.url.replace('#', '');
|
|
53
|
+
const isActive = activeId === id;
|
|
54
|
+
const isNested = item.depth > 2;
|
|
53
55
|
return (
|
|
54
56
|
<a
|
|
55
57
|
key={item.url}
|
|
@@ -58,9 +60,9 @@ export function Toc({ items }: TocProps) {
|
|
|
58
60
|
>
|
|
59
61
|
{item.title}
|
|
60
62
|
</a>
|
|
61
|
-
)
|
|
63
|
+
);
|
|
62
64
|
})}
|
|
63
65
|
</nav>
|
|
64
66
|
</aside>
|
|
65
|
-
)
|
|
67
|
+
);
|
|
66
68
|
}
|
|
@@ -1,13 +1,11 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import {
|
|
3
|
-
import {
|
|
4
|
-
import {
|
|
5
|
-
import type { Theme } from '@/types'
|
|
1
|
+
import type { Theme } from '@/types';
|
|
2
|
+
import { Layout } from './Layout';
|
|
3
|
+
import { Page } from './Page';
|
|
4
|
+
import { Toc } from './Toc';
|
|
6
5
|
|
|
7
6
|
export const defaultTheme: Theme = {
|
|
8
7
|
Layout,
|
|
9
|
-
Page
|
|
10
|
-
|
|
11
|
-
}
|
|
8
|
+
Page
|
|
9
|
+
};
|
|
12
10
|
|
|
13
|
-
export { Layout, Page, Toc }
|
|
11
|
+
export { Layout, Page, Toc };
|
|
@@ -1,95 +1,115 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
import {
|
|
4
|
-
import
|
|
5
|
-
import type { PageTree, PageTreeItem } from '@/types'
|
|
6
|
-
import styles from './ChapterNav.module.css'
|
|
1
|
+
import { Link as RouterLink, useLocation } from 'react-router';
|
|
2
|
+
import { MethodBadge } from '@/components/api/method-badge';
|
|
3
|
+
import type { PageTree, PageTreeItem } from '@/types';
|
|
4
|
+
import styles from './ChapterNav.module.css';
|
|
7
5
|
|
|
8
6
|
const iconMap: Record<string, React.ReactNode> = {
|
|
9
|
-
'method-get': <MethodBadge method=
|
|
10
|
-
'method-post': <MethodBadge method=
|
|
11
|
-
'method-put': <MethodBadge method=
|
|
12
|
-
'method-delete': <MethodBadge method=
|
|
13
|
-
'method-patch': <MethodBadge method=
|
|
14
|
-
}
|
|
7
|
+
'method-get': <MethodBadge method='GET' size='micro' />,
|
|
8
|
+
'method-post': <MethodBadge method='POST' size='micro' />,
|
|
9
|
+
'method-put': <MethodBadge method='PUT' size='micro' />,
|
|
10
|
+
'method-delete': <MethodBadge method='DELETE' size='micro' />,
|
|
11
|
+
'method-patch': <MethodBadge method='PATCH' size='micro' />
|
|
12
|
+
};
|
|
15
13
|
|
|
16
14
|
interface ChapterNavProps {
|
|
17
|
-
tree: PageTree
|
|
15
|
+
tree: PageTree;
|
|
18
16
|
}
|
|
19
17
|
|
|
20
|
-
function buildChapterIndices(
|
|
21
|
-
|
|
22
|
-
|
|
18
|
+
function buildChapterIndices(
|
|
19
|
+
children: PageTreeItem[]
|
|
20
|
+
): Map<PageTreeItem, number> {
|
|
21
|
+
const indices = new Map<PageTreeItem, number>();
|
|
22
|
+
let index = 0;
|
|
23
23
|
for (const item of children) {
|
|
24
24
|
if (item.type === 'folder' && item.children) {
|
|
25
|
-
index
|
|
26
|
-
indices.set(item, index)
|
|
25
|
+
index++;
|
|
26
|
+
indices.set(item, index);
|
|
27
27
|
}
|
|
28
28
|
}
|
|
29
|
-
return indices
|
|
29
|
+
return indices;
|
|
30
30
|
}
|
|
31
31
|
|
|
32
32
|
export function ChapterNav({ tree }: ChapterNavProps) {
|
|
33
|
-
const { pathname } = useLocation()
|
|
34
|
-
const chapterIndices = buildChapterIndices(tree.children)
|
|
33
|
+
const { pathname } = useLocation();
|
|
34
|
+
const chapterIndices = buildChapterIndices(tree.children);
|
|
35
35
|
|
|
36
36
|
return (
|
|
37
37
|
<nav className={styles.nav}>
|
|
38
38
|
<ul className={styles.chapterItems}>
|
|
39
|
-
{tree.children.map(
|
|
40
|
-
if (item.type === 'separator') return null
|
|
39
|
+
{tree.children.map(item => {
|
|
40
|
+
if (item.type === 'separator') return null;
|
|
41
41
|
|
|
42
42
|
if (item.type === 'folder' && item.children) {
|
|
43
|
-
const chapterIndex = chapterIndices.get(item) ?? 0
|
|
43
|
+
const chapterIndex = chapterIndices.get(item) ?? 0;
|
|
44
44
|
return (
|
|
45
45
|
<li key={item.name} className={styles.chapter}>
|
|
46
46
|
<span className={styles.chapterLabel}>
|
|
47
47
|
{String(chapterIndex).padStart(2, '0')}. {item.name}
|
|
48
48
|
</span>
|
|
49
49
|
<ul className={styles.chapterItems}>
|
|
50
|
-
{item.children.map(
|
|
51
|
-
<ChapterItem
|
|
50
|
+
{item.children.map(child => (
|
|
51
|
+
<ChapterItem
|
|
52
|
+
key={child.url ?? child.name}
|
|
53
|
+
item={child}
|
|
54
|
+
pathname={pathname}
|
|
55
|
+
/>
|
|
52
56
|
))}
|
|
53
57
|
</ul>
|
|
54
58
|
</li>
|
|
55
|
-
)
|
|
59
|
+
);
|
|
56
60
|
}
|
|
57
61
|
|
|
58
|
-
return
|
|
62
|
+
return (
|
|
63
|
+
<ChapterItem
|
|
64
|
+
key={item.url ?? item.name}
|
|
65
|
+
item={item}
|
|
66
|
+
pathname={pathname}
|
|
67
|
+
/>
|
|
68
|
+
);
|
|
59
69
|
})}
|
|
60
70
|
</ul>
|
|
61
71
|
</nav>
|
|
62
|
-
)
|
|
72
|
+
);
|
|
63
73
|
}
|
|
64
74
|
|
|
65
|
-
function ChapterItem({
|
|
66
|
-
|
|
75
|
+
function ChapterItem({
|
|
76
|
+
item,
|
|
77
|
+
pathname
|
|
78
|
+
}: {
|
|
79
|
+
item: PageTreeItem;
|
|
80
|
+
pathname: string;
|
|
81
|
+
}) {
|
|
82
|
+
if (item.type === 'separator') return null;
|
|
67
83
|
|
|
68
84
|
if (item.type === 'folder' && item.children) {
|
|
69
85
|
return (
|
|
70
86
|
<li>
|
|
71
87
|
<span className={styles.subLabel}>{item.name}</span>
|
|
72
88
|
<ul className={styles.chapterItems}>
|
|
73
|
-
{item.children.map(
|
|
74
|
-
<ChapterItem
|
|
89
|
+
{item.children.map(child => (
|
|
90
|
+
<ChapterItem
|
|
91
|
+
key={child.url ?? child.name}
|
|
92
|
+
item={child}
|
|
93
|
+
pathname={pathname}
|
|
94
|
+
/>
|
|
75
95
|
))}
|
|
76
96
|
</ul>
|
|
77
97
|
</li>
|
|
78
|
-
)
|
|
98
|
+
);
|
|
79
99
|
}
|
|
80
100
|
|
|
81
|
-
const isActive = pathname === item.url
|
|
82
|
-
const icon = item.icon ? iconMap[item.icon] : null
|
|
101
|
+
const isActive = pathname === item.url;
|
|
102
|
+
const icon = item.icon ? iconMap[item.icon] : null;
|
|
83
103
|
|
|
84
104
|
return (
|
|
85
105
|
<li>
|
|
86
|
-
<
|
|
106
|
+
<RouterLink
|
|
87
107
|
to={item.url ?? '#'}
|
|
88
108
|
className={`${styles.link} ${isActive ? styles.active : ''}`}
|
|
89
109
|
>
|
|
90
110
|
{icon && <span className={styles.icon}>{icon}</span>}
|
|
91
111
|
<span>{item.name}</span>
|
|
92
|
-
</
|
|
112
|
+
</RouterLink>
|
|
93
113
|
</li>
|
|
94
|
-
)
|
|
114
|
+
);
|
|
95
115
|
}
|
|
@@ -13,7 +13,7 @@
|
|
|
13
13
|
padding: var(--rs-space-7) var(--rs-space-5);
|
|
14
14
|
background: var(--rs-color-background-neutral-primary);
|
|
15
15
|
overflow-y: auto;
|
|
16
|
-
font-family:
|
|
16
|
+
font-family: "SF Mono", "Fira Code", "Fira Mono", "Roboto Mono", monospace;
|
|
17
17
|
}
|
|
18
18
|
|
|
19
19
|
.title {
|
|
@@ -1,25 +1,37 @@
|
|
|
1
|
-
'use client'
|
|
1
|
+
'use client';
|
|
2
2
|
|
|
3
|
-
import { Flex, Headline } from '@raystack/apsara'
|
|
4
|
-
import { cx } from 'class-variance-authority'
|
|
5
|
-
import { Footer } from '@/components/ui/footer'
|
|
6
|
-
import {
|
|
7
|
-
import
|
|
8
|
-
import styles from './Layout.module.css'
|
|
3
|
+
import { Flex, Headline } from '@raystack/apsara';
|
|
4
|
+
import { cx } from 'class-variance-authority';
|
|
5
|
+
import { Footer } from '@/components/ui/footer';
|
|
6
|
+
import type { ThemeLayoutProps } from '@/types';
|
|
7
|
+
import { ChapterNav } from './ChapterNav';
|
|
8
|
+
import styles from './Layout.module.css';
|
|
9
9
|
|
|
10
|
-
export function Layout({
|
|
10
|
+
export function Layout({
|
|
11
|
+
children,
|
|
12
|
+
config,
|
|
13
|
+
tree,
|
|
14
|
+
classNames
|
|
15
|
+
}: ThemeLayoutProps) {
|
|
11
16
|
return (
|
|
12
|
-
<Flex direction=
|
|
17
|
+
<Flex direction='column' className={cx(styles.layout, classNames?.layout)}>
|
|
13
18
|
<Flex className={cx(styles.body, classNames?.body)}>
|
|
14
19
|
<aside className={cx(styles.sidebar, classNames?.sidebar)}>
|
|
15
|
-
<Headline
|
|
20
|
+
<Headline
|
|
21
|
+
size='small'
|
|
22
|
+
weight='medium'
|
|
23
|
+
as='h1'
|
|
24
|
+
className={styles.title}
|
|
25
|
+
>
|
|
16
26
|
{config.title}
|
|
17
27
|
</Headline>
|
|
18
28
|
<ChapterNav tree={tree} />
|
|
19
29
|
</aside>
|
|
20
|
-
<div className={cx(styles.content, classNames?.content)}>
|
|
30
|
+
<div className={cx(styles.content, classNames?.content)}>
|
|
31
|
+
{children}
|
|
32
|
+
</div>
|
|
21
33
|
</Flex>
|
|
22
34
|
<Footer config={config.footer} />
|
|
23
35
|
</Flex>
|
|
24
|
-
)
|
|
36
|
+
);
|
|
25
37
|
}
|
|
@@ -1,7 +1,12 @@
|
|
|
1
1
|
.main {
|
|
2
2
|
--paper-navbar-height: 40px;
|
|
3
3
|
--paper-navbar-padding: var(--rs-space-3);
|
|
4
|
-
--paper-navbar-total: calc(
|
|
4
|
+
--paper-navbar-total: calc(
|
|
5
|
+
var(--paper-navbar-height) +
|
|
6
|
+
var(--paper-navbar-padding) *
|
|
7
|
+
2 +
|
|
8
|
+
1px
|
|
9
|
+
);
|
|
5
10
|
|
|
6
11
|
flex: 1;
|
|
7
12
|
max-width: 1024px;
|
|
@@ -52,7 +57,7 @@
|
|
|
52
57
|
}
|
|
53
58
|
|
|
54
59
|
.breadcrumb {
|
|
55
|
-
font-family:
|
|
60
|
+
font-family: "SF Mono", "Fira Code", monospace;
|
|
56
61
|
font-size: var(--rs-font-size-small);
|
|
57
62
|
text-transform: uppercase;
|
|
58
63
|
letter-spacing: 0.05em;
|
|
@@ -94,13 +99,15 @@
|
|
|
94
99
|
}
|
|
95
100
|
|
|
96
101
|
.content {
|
|
97
|
-
font-family: Georgia,
|
|
102
|
+
font-family: Georgia, "Times New Roman", serif;
|
|
98
103
|
line-height: 1.8;
|
|
99
104
|
background: var(--rs-color-background-base-primary);
|
|
100
105
|
padding: var(--rs-space-9);
|
|
101
106
|
border-left: 1px solid var(--rs-color-border-base-primary);
|
|
102
107
|
border-right: 1px solid var(--rs-color-border-base-primary);
|
|
103
|
-
box-shadow:
|
|
108
|
+
box-shadow:
|
|
109
|
+
0 1px 3px rgba(0, 0, 0, 0.08),
|
|
110
|
+
0 4px 12px rgba(0, 0, 0, 0.04);
|
|
104
111
|
margin-bottom: var(--rs-space-9);
|
|
105
112
|
}
|
|
106
113
|
|