@raystack/chronicle 0.3.0 → 0.5.0
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 +425 -9937
- package/package.json +19 -10
- package/src/cli/commands/build.ts +33 -31
- package/src/cli/commands/dev.ts +23 -31
- package/src/cli/commands/init.ts +38 -132
- package/src/cli/commands/serve.ts +41 -55
- package/src/cli/commands/start.ts +20 -31
- package/src/cli/index.ts +14 -14
- package/src/cli/utils/config.ts +58 -30
- package/src/cli/utils/index.ts +3 -3
- package/src/cli/utils/resolve.ts +7 -3
- package/src/cli/utils/scaffold.ts +11 -130
- package/src/components/mdx/code.tsx +10 -1
- package/src/components/mdx/details.module.css +1 -26
- package/src/components/mdx/details.tsx +2 -3
- package/src/components/mdx/image.tsx +5 -34
- package/src/components/mdx/index.tsx +15 -1
- package/src/components/mdx/link.tsx +18 -15
- package/src/components/ui/breadcrumbs.tsx +8 -42
- package/src/components/ui/search.tsx +63 -51
- package/src/lib/api-routes.ts +6 -8
- package/src/lib/config.ts +12 -36
- package/src/lib/head.tsx +49 -0
- package/src/lib/openapi.ts +8 -8
- package/src/lib/page-context.tsx +111 -0
- package/src/lib/remark-strip-md-extensions.ts +14 -0
- package/src/lib/source.ts +139 -63
- package/src/pages/ApiLayout.tsx +33 -0
- package/src/pages/ApiPage.tsx +73 -0
- package/src/pages/DocsLayout.tsx +18 -0
- package/src/pages/DocsPage.tsx +43 -0
- package/src/pages/NotFound.tsx +17 -0
- package/src/server/App.tsx +72 -0
- 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 +118 -0
- package/src/server/api/specs.ts +9 -0
- package/src/server/build-search-index.ts +117 -0
- package/src/server/entry-client.tsx +88 -0
- package/src/server/entry-server.tsx +102 -0
- package/src/server/routes/llms.txt.ts +21 -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 +40 -0
- package/src/server/utils/safe-path.ts +17 -0
- package/src/server/vite-config.ts +133 -0
- package/src/themes/default/Layout.tsx +78 -48
- package/src/themes/default/Page.module.css +44 -0
- package/src/themes/default/Page.tsx +9 -11
- package/src/themes/default/Toc.tsx +25 -39
- package/src/themes/default/index.ts +7 -9
- package/src/themes/paper/ChapterNav.tsx +64 -45
- package/src/themes/paper/Layout.module.css +1 -1
- package/src/themes/paper/Layout.tsx +24 -12
- package/src/themes/paper/Page.module.css +16 -4
- package/src/themes/paper/Page.tsx +56 -63
- package/src/themes/paper/ReadingProgress.tsx +160 -139
- package/src/themes/paper/index.ts +5 -5
- package/src/themes/registry.ts +14 -7
- package/src/types/config.ts +86 -67
- package/src/types/content.ts +5 -21
- package/src/types/globals.d.ts +4 -0
- package/src/types/theme.ts +4 -3
- package/tsconfig.json +2 -3
- package/next.config.mjs +0 -10
- package/source.config.ts +0 -51
- package/src/app/[[...slug]]/layout.tsx +0 -15
- package/src/app/[[...slug]]/page.tsx +0 -106
- package/src/app/api/apis-proxy/route.ts +0 -59
- package/src/app/api/health/route.ts +0 -3
- package/src/app/api/search/route.ts +0 -90
- package/src/app/apis/[[...slug]]/layout.tsx +0 -26
- package/src/app/apis/[[...slug]]/page.tsx +0 -117
- package/src/app/layout.tsx +0 -57
- package/src/app/llms-full.txt/route.ts +0 -18
- package/src/app/llms.txt/route.ts +0 -15
- package/src/app/og/route.tsx +0 -62
- package/src/app/providers.tsx +0 -8
- package/src/app/robots.ts +0 -10
- package/src/app/sitemap.ts +0 -29
- package/src/cli/utils/process.ts +0 -7
- package/src/themes/default/font.ts +0 -6
- /package/src/{app/apis/[[...slug]]/layout.module.css → pages/ApiLayout.module.css} +0 -0
package/src/cli/utils/config.ts
CHANGED
|
@@ -1,43 +1,71 @@
|
|
|
1
|
-
import fs from 'fs'
|
|
2
|
-
import path from 'path'
|
|
3
|
-
import
|
|
4
|
-
import
|
|
5
|
-
import type
|
|
1
|
+
import fs from 'node:fs/promises';
|
|
2
|
+
import path from 'node:path';
|
|
3
|
+
import chalk from 'chalk';
|
|
4
|
+
import { parse } from 'yaml';
|
|
5
|
+
import { chronicleConfigSchema, type ChronicleConfig } from '@/types';
|
|
6
6
|
|
|
7
7
|
export interface CLIConfig {
|
|
8
|
-
config: ChronicleConfig
|
|
9
|
-
configPath: string
|
|
10
|
-
contentDir: string
|
|
8
|
+
config: ChronicleConfig;
|
|
9
|
+
configPath: string;
|
|
10
|
+
contentDir: string;
|
|
11
|
+
preset?: string;
|
|
11
12
|
}
|
|
12
13
|
|
|
13
|
-
export function
|
|
14
|
-
if (
|
|
15
|
-
|
|
16
|
-
return path.resolve('content')
|
|
14
|
+
export function resolveConfigPath(configPath?: string): string | undefined {
|
|
15
|
+
if (configPath) return path.resolve(configPath);
|
|
16
|
+
return undefined;
|
|
17
17
|
}
|
|
18
18
|
|
|
19
|
-
function
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
19
|
+
async function readConfig(configPath: string): Promise<string> {
|
|
20
|
+
return fs.readFile(configPath, 'utf-8').catch((error: NodeJS.ErrnoException) => {
|
|
21
|
+
if (error.code === 'ENOENT') {
|
|
22
|
+
console.log(chalk.red(`Error: chronicle.yaml not found at '${configPath}'`));
|
|
23
|
+
console.log(chalk.gray("Run 'chronicle init' to create one"));
|
|
24
|
+
} else {
|
|
25
|
+
console.log(chalk.red(`Error: Failed to read '${configPath}'`));
|
|
26
|
+
console.log(chalk.gray(error.message));
|
|
27
|
+
}
|
|
28
|
+
process.exit(1);
|
|
29
|
+
});
|
|
25
30
|
}
|
|
26
31
|
|
|
27
|
-
|
|
28
|
-
const
|
|
32
|
+
function validateConfig(raw: string, configPath: string): ChronicleConfig {
|
|
33
|
+
const parsed = parse(raw);
|
|
34
|
+
const result = chronicleConfigSchema.safeParse(parsed);
|
|
29
35
|
|
|
30
|
-
if (!
|
|
31
|
-
console.log(chalk.red(`Error: chronicle.yaml
|
|
32
|
-
|
|
33
|
-
|
|
36
|
+
if (!result.success) {
|
|
37
|
+
console.log(chalk.red(`Error: Invalid chronicle.yaml at '${configPath}'`));
|
|
38
|
+
for (const issue of result.error.issues) {
|
|
39
|
+
const path = issue.path.join('.');
|
|
40
|
+
console.log(chalk.gray(` ${path ? `${path}: ` : ''}${issue.message}`));
|
|
41
|
+
}
|
|
42
|
+
process.exit(1);
|
|
34
43
|
}
|
|
35
44
|
|
|
36
|
-
|
|
45
|
+
return result.data;
|
|
46
|
+
}
|
|
37
47
|
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
48
|
+
export function resolveContentDir(config: ChronicleConfig, configPath: string, contentFlag?: string): string {
|
|
49
|
+
if (contentFlag) return path.resolve(contentFlag);
|
|
50
|
+
if (config.content) return path.resolve(path.dirname(configPath), config.content);
|
|
51
|
+
return path.resolve('content');
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
export function resolvePreset(config: ChronicleConfig, presetFlag?: string): string | undefined {
|
|
55
|
+
return presetFlag ?? config.preset;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
export async function loadCLIConfig(
|
|
59
|
+
configPath?: string,
|
|
60
|
+
options?: { content?: string; preset?: string }
|
|
61
|
+
): Promise<CLIConfig> {
|
|
62
|
+
const resolvedConfigPath = resolveConfigPath(configPath)
|
|
63
|
+
?? path.join(process.cwd(), 'chronicle.yaml');
|
|
64
|
+
|
|
65
|
+
const raw = await readConfig(resolvedConfigPath);
|
|
66
|
+
const config = validateConfig(raw, resolvedConfigPath);
|
|
67
|
+
const contentDir = resolveContentDir(config, resolvedConfigPath, options?.content);
|
|
68
|
+
const preset = resolvePreset(config, options?.preset);
|
|
69
|
+
|
|
70
|
+
return { config, configPath: resolvedConfigPath, contentDir, preset };
|
|
43
71
|
}
|
package/src/cli/utils/index.ts
CHANGED
|
@@ -1,3 +1,3 @@
|
|
|
1
|
-
export * from './config'
|
|
2
|
-
export * from './
|
|
3
|
-
export * from './scaffold'
|
|
1
|
+
export * from './config';
|
|
2
|
+
export * from './resolve';
|
|
3
|
+
export * from './scaffold';
|
package/src/cli/utils/resolve.ts
CHANGED
|
@@ -1,6 +1,10 @@
|
|
|
1
|
-
import path from 'path'
|
|
2
|
-
import { fileURLToPath } from 'url'
|
|
1
|
+
import path from 'path';
|
|
2
|
+
import { fileURLToPath } from 'url';
|
|
3
3
|
|
|
4
4
|
// After bundling: dist/cli/index.js → ../.. = package root
|
|
5
5
|
// After install: node_modules/@raystack/chronicle/dist/cli/index.js → ../.. = package root
|
|
6
|
-
export const PACKAGE_ROOT = path.resolve(
|
|
6
|
+
export const PACKAGE_ROOT = path.resolve(
|
|
7
|
+
path.dirname(fileURLToPath(import.meta.url)),
|
|
8
|
+
'..',
|
|
9
|
+
'..'
|
|
10
|
+
);
|
|
@@ -1,137 +1,18 @@
|
|
|
1
|
-
import
|
|
2
|
-
import
|
|
3
|
-
import
|
|
4
|
-
import path from 'path'
|
|
5
|
-
import chalk from 'chalk'
|
|
6
|
-
import { PACKAGE_ROOT } from './resolve'
|
|
1
|
+
import fs from 'node:fs/promises';
|
|
2
|
+
import path from 'node:path';
|
|
3
|
+
import { PACKAGE_ROOT } from './resolve';
|
|
7
4
|
|
|
8
|
-
|
|
5
|
+
export async function linkContent(contentDir: string): Promise<void> {
|
|
6
|
+
const linkPath = path.join(PACKAGE_ROOT, '.content');
|
|
7
|
+
const target = path.resolve(contentDir);
|
|
9
8
|
|
|
10
|
-
function copyRecursive(src: string, dest: string) {
|
|
11
|
-
const stat = fs.statSync(src)
|
|
12
|
-
if (stat.isDirectory()) {
|
|
13
|
-
fs.mkdirSync(dest, { recursive: true })
|
|
14
|
-
for (const entry of fs.readdirSync(src)) {
|
|
15
|
-
copyRecursive(path.join(src, entry), path.join(dest, entry))
|
|
16
|
-
}
|
|
17
|
-
} else {
|
|
18
|
-
fs.copyFileSync(src, dest)
|
|
19
|
-
}
|
|
20
|
-
}
|
|
21
|
-
|
|
22
|
-
function ensureRemoved(targetPath: string) {
|
|
23
9
|
try {
|
|
24
|
-
fs.
|
|
25
|
-
|
|
10
|
+
const existing = await fs.readlink(linkPath);
|
|
11
|
+
if (existing === target) return;
|
|
12
|
+
await fs.unlink(linkPath);
|
|
26
13
|
} catch {
|
|
27
|
-
//
|
|
28
|
-
}
|
|
29
|
-
}
|
|
30
|
-
|
|
31
|
-
export function detectPackageManager(): string {
|
|
32
|
-
if (process.env.npm_config_user_agent) {
|
|
33
|
-
return process.env.npm_config_user_agent.split('/')[0]
|
|
34
|
-
}
|
|
35
|
-
const cwd = process.cwd()
|
|
36
|
-
if (fs.existsSync(path.join(cwd, 'bun.lock')) || fs.existsSync(path.join(cwd, 'bun.lockb'))) return 'bun'
|
|
37
|
-
if (fs.existsSync(path.join(cwd, 'pnpm-lock.yaml'))) return 'pnpm'
|
|
38
|
-
if (fs.existsSync(path.join(cwd, 'yarn.lock'))) return 'yarn'
|
|
39
|
-
return 'npm'
|
|
40
|
-
}
|
|
41
|
-
|
|
42
|
-
function generateNextConfig(scaffoldPath: string) {
|
|
43
|
-
const config = `import { createMDX } from 'fumadocs-mdx/next'
|
|
44
|
-
|
|
45
|
-
const withMDX = createMDX()
|
|
46
|
-
|
|
47
|
-
/** @type {import('next').NextConfig} */
|
|
48
|
-
const nextConfig = {
|
|
49
|
-
reactStrictMode: true,
|
|
50
|
-
}
|
|
51
|
-
|
|
52
|
-
export default withMDX(nextConfig)
|
|
53
|
-
`
|
|
54
|
-
fs.writeFileSync(path.join(scaffoldPath, 'next.config.mjs'), config)
|
|
55
|
-
}
|
|
56
|
-
|
|
57
|
-
function createPackageJson(): Record<string, unknown> {
|
|
58
|
-
return {
|
|
59
|
-
name: 'chronicle-docs',
|
|
60
|
-
private: true,
|
|
61
|
-
dependencies: {
|
|
62
|
-
'@raystack/chronicle': `^${getChronicleVersion()}`,
|
|
63
|
-
},
|
|
64
|
-
devDependencies: {
|
|
65
|
-
'@raystack/tools-config': '0.56.0',
|
|
66
|
-
'openapi-types': '^12.1.3',
|
|
67
|
-
typescript: '5.9.3',
|
|
68
|
-
'@types/react': '^19.2.10',
|
|
69
|
-
'@types/node': '^25.1.0',
|
|
70
|
-
},
|
|
71
|
-
}
|
|
72
|
-
}
|
|
73
|
-
|
|
74
|
-
function ensureDeps() {
|
|
75
|
-
const cwd = process.cwd()
|
|
76
|
-
const cwdPkgJson = path.join(cwd, 'package.json')
|
|
77
|
-
const cwdNodeModules = path.join(cwd, 'node_modules')
|
|
78
|
-
|
|
79
|
-
if (fs.existsSync(cwdPkgJson) && fs.existsSync(cwdNodeModules)) {
|
|
80
|
-
// Case 1: existing project with deps installed
|
|
81
|
-
return
|
|
14
|
+
// link doesn't exist
|
|
82
15
|
}
|
|
83
16
|
|
|
84
|
-
|
|
85
|
-
if (!fs.existsSync(cwdPkgJson)) {
|
|
86
|
-
fs.writeFileSync(cwdPkgJson, JSON.stringify(createPackageJson(), null, 2) + '\n')
|
|
87
|
-
}
|
|
88
|
-
|
|
89
|
-
if (!fs.existsSync(cwdNodeModules)) {
|
|
90
|
-
const pm = detectPackageManager()
|
|
91
|
-
console.log(chalk.cyan(`Installing dependencies with ${pm}...`))
|
|
92
|
-
execSync(`${pm} install`, { cwd, stdio: 'inherit' })
|
|
93
|
-
}
|
|
94
|
-
}
|
|
95
|
-
|
|
96
|
-
export function getChronicleVersion(): string {
|
|
97
|
-
const pkgPath = path.join(PACKAGE_ROOT, 'package.json')
|
|
98
|
-
const pkg = JSON.parse(fs.readFileSync(pkgPath, 'utf-8'))
|
|
99
|
-
return pkg.version
|
|
100
|
-
}
|
|
101
|
-
|
|
102
|
-
export function resolveNextCli(): string {
|
|
103
|
-
const chronicleRequire = createRequire(path.join(PACKAGE_ROOT, 'package.json'))
|
|
104
|
-
return chronicleRequire.resolve('next/dist/bin/next')
|
|
105
|
-
}
|
|
106
|
-
|
|
107
|
-
export function scaffoldDir(contentDir: string): string {
|
|
108
|
-
const scaffoldPath = path.join(process.cwd(), '.chronicle')
|
|
109
|
-
|
|
110
|
-
// Create .chronicle/ if not exists
|
|
111
|
-
if (!fs.existsSync(scaffoldPath)) {
|
|
112
|
-
fs.mkdirSync(scaffoldPath, { recursive: true })
|
|
113
|
-
}
|
|
114
|
-
|
|
115
|
-
// Copy package files
|
|
116
|
-
for (const name of COPY_FILES) {
|
|
117
|
-
const src = path.join(PACKAGE_ROOT, name)
|
|
118
|
-
const dest = path.join(scaffoldPath, name)
|
|
119
|
-
ensureRemoved(dest)
|
|
120
|
-
copyRecursive(src, dest)
|
|
121
|
-
}
|
|
122
|
-
|
|
123
|
-
// Generate next.config.mjs
|
|
124
|
-
generateNextConfig(scaffoldPath)
|
|
125
|
-
|
|
126
|
-
// Symlink content dir
|
|
127
|
-
const contentLink = path.join(scaffoldPath, 'content')
|
|
128
|
-
ensureRemoved(contentLink)
|
|
129
|
-
fs.symlinkSync(path.resolve(contentDir), contentLink)
|
|
130
|
-
|
|
131
|
-
// Ensure dependencies are available
|
|
132
|
-
ensureDeps()
|
|
133
|
-
|
|
134
|
-
console.log(chalk.gray(`Scaffold: ${scaffoldPath}`))
|
|
135
|
-
|
|
136
|
-
return scaffoldPath
|
|
17
|
+
await fs.symlink(target, linkPath);
|
|
137
18
|
}
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
'use client'
|
|
2
2
|
|
|
3
|
-
import type
|
|
3
|
+
import { type ComponentProps, isValidElement, Children } from 'react'
|
|
4
|
+
import { Mermaid } from './mermaid'
|
|
4
5
|
import styles from './code.module.css'
|
|
5
6
|
|
|
6
7
|
type PreProps = ComponentProps<'pre'> & {
|
|
@@ -16,6 +17,14 @@ export function MdxCode({ children, className, ...props }: ComponentProps<'code'
|
|
|
16
17
|
}
|
|
17
18
|
|
|
18
19
|
export function MdxPre({ children, title, className, ...props }: PreProps) {
|
|
20
|
+
// Detect mermaid code blocks
|
|
21
|
+
if (isValidElement(children)) {
|
|
22
|
+
const childProps = children.props as { className?: string; children?: string }
|
|
23
|
+
if (childProps.className?.includes('language-mermaid') && typeof childProps.children === 'string') {
|
|
24
|
+
return <Mermaid chart={childProps.children} />
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
|
|
19
28
|
return (
|
|
20
29
|
<div className={styles.codeBlock}>
|
|
21
30
|
{title && <div className={styles.codeHeader}>{title}</div>}
|
|
@@ -1,35 +1,10 @@
|
|
|
1
1
|
.details {
|
|
2
|
-
border: 1px solid var(--rs-color-border-base-primary);
|
|
3
|
-
border-radius: var(--rs-radius-2);
|
|
4
2
|
margin: var(--rs-space-5) 0;
|
|
5
3
|
}
|
|
6
4
|
|
|
7
|
-
.
|
|
8
|
-
padding: var(--rs-space-4) var(--rs-space-5);
|
|
9
|
-
cursor: pointer;
|
|
5
|
+
.trigger {
|
|
10
6
|
font-weight: 500;
|
|
11
7
|
font-size: var(--rs-font-size-small);
|
|
12
|
-
color: var(--rs-color-text-base-primary);
|
|
13
|
-
background: var(--rs-color-background-base-secondary);
|
|
14
|
-
list-style: none;
|
|
15
|
-
display: flex;
|
|
16
|
-
align-items: center;
|
|
17
|
-
gap: var(--rs-space-3);
|
|
18
|
-
}
|
|
19
|
-
|
|
20
|
-
.summary::-webkit-details-marker {
|
|
21
|
-
display: none;
|
|
22
|
-
}
|
|
23
|
-
|
|
24
|
-
.summary::before {
|
|
25
|
-
content: '▶';
|
|
26
|
-
font-size: 10px;
|
|
27
|
-
transition: transform 0.2s ease;
|
|
28
|
-
color: var(--rs-color-text-base-secondary);
|
|
29
|
-
}
|
|
30
|
-
|
|
31
|
-
.details[open] > .summary::before {
|
|
32
|
-
transform: rotate(90deg);
|
|
33
8
|
}
|
|
34
9
|
|
|
35
10
|
.content {
|
|
@@ -1,9 +1,8 @@
|
|
|
1
1
|
import type { ComponentProps } from 'react'
|
|
2
|
-
import styles from './details.module.css'
|
|
3
2
|
|
|
4
3
|
export function MdxDetails({ children, className, ...props }: ComponentProps<'details'>) {
|
|
5
4
|
return (
|
|
6
|
-
<details className={
|
|
5
|
+
<details className={className} {...props}>
|
|
7
6
|
{children}
|
|
8
7
|
</details>
|
|
9
8
|
)
|
|
@@ -11,7 +10,7 @@ export function MdxDetails({ children, className, ...props }: ComponentProps<'de
|
|
|
11
10
|
|
|
12
11
|
export function MdxSummary({ children, className, ...props }: ComponentProps<'summary'>) {
|
|
13
12
|
return (
|
|
14
|
-
<summary className={
|
|
13
|
+
<summary className={className} {...props}>
|
|
15
14
|
{children}
|
|
16
15
|
</summary>
|
|
17
16
|
)
|
|
@@ -1,38 +1,9 @@
|
|
|
1
|
-
|
|
1
|
+
import type { ComponentProps } from 'react';
|
|
2
2
|
|
|
3
|
-
|
|
4
|
-
import type { ComponentProps } from 'react'
|
|
3
|
+
type ImageProps = ComponentProps<'img'>;
|
|
5
4
|
|
|
6
|
-
|
|
7
|
-
src
|
|
8
|
-
width?: number | string
|
|
9
|
-
height?: number | string
|
|
10
|
-
}
|
|
11
|
-
|
|
12
|
-
export function Image({ src, alt, width, height, ...props }: ImageProps) {
|
|
13
|
-
if (!src || typeof src !== 'string') return null
|
|
14
|
-
|
|
15
|
-
const isExternal = src.startsWith('http://') || src.startsWith('https://')
|
|
16
|
-
|
|
17
|
-
if (isExternal) {
|
|
18
|
-
return (
|
|
19
|
-
// eslint-disable-next-line @next/next/no-img-element
|
|
20
|
-
<img
|
|
21
|
-
src={src}
|
|
22
|
-
alt={alt ?? ''}
|
|
23
|
-
width={width}
|
|
24
|
-
height={height}
|
|
25
|
-
{...props}
|
|
26
|
-
/>
|
|
27
|
-
)
|
|
28
|
-
}
|
|
5
|
+
export function Image({ src, alt, ...props }: ImageProps) {
|
|
6
|
+
if (!src) return null;
|
|
29
7
|
|
|
30
|
-
return
|
|
31
|
-
<NextImage
|
|
32
|
-
src={src}
|
|
33
|
-
alt={alt ?? ''}
|
|
34
|
-
width={typeof width === 'string' ? parseInt(width, 10) : (width ?? 800)}
|
|
35
|
-
height={typeof height === 'string' ? parseInt(height, 10) : (height ?? 400)}
|
|
36
|
-
/>
|
|
37
|
-
)
|
|
8
|
+
return <img src={src} alt={alt ?? ''} loading='lazy' {...props} />;
|
|
38
9
|
}
|
|
@@ -8,6 +8,20 @@ import { Mermaid } from './mermaid'
|
|
|
8
8
|
import { MdxParagraph } from './paragraph'
|
|
9
9
|
import { CalloutContainer, CalloutTitle, CalloutDescription, MdxBlockquote } from '@/components/common/callout'
|
|
10
10
|
import { Tabs } from '@raystack/apsara'
|
|
11
|
+
import { type ComponentProps, useEffect, useState } from 'react'
|
|
12
|
+
|
|
13
|
+
function ClientOnly({ children }: { children: React.ReactNode }) {
|
|
14
|
+
const [mounted, setMounted] = useState(false)
|
|
15
|
+
useEffect(() => setMounted(true), [])
|
|
16
|
+
return mounted ? <>{children}</> : null
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
function MdxTabs(props: ComponentProps<typeof Tabs>) {
|
|
20
|
+
return <ClientOnly><Tabs {...props} /></ClientOnly>
|
|
21
|
+
}
|
|
22
|
+
MdxTabs.List = Tabs.List
|
|
23
|
+
MdxTabs.Trigger = Tabs.Trigger
|
|
24
|
+
MdxTabs.Content = Tabs.Content
|
|
11
25
|
|
|
12
26
|
export const mdxComponents: MDXComponents = {
|
|
13
27
|
p: MdxParagraph,
|
|
@@ -27,7 +41,7 @@ export const mdxComponents: MDXComponents = {
|
|
|
27
41
|
Callout: CalloutContainer,
|
|
28
42
|
CalloutTitle,
|
|
29
43
|
CalloutDescription,
|
|
30
|
-
Tabs,
|
|
44
|
+
Tabs: MdxTabs,
|
|
31
45
|
Mermaid,
|
|
32
46
|
}
|
|
33
47
|
|
|
@@ -1,38 +1,41 @@
|
|
|
1
|
-
|
|
1
|
+
import { Link as ApsaraLink } from '@raystack/apsara';
|
|
2
|
+
import type { ComponentProps } from 'react';
|
|
3
|
+
import { Link as RouterLink } from 'react-router';
|
|
2
4
|
|
|
3
|
-
|
|
4
|
-
import { Link as ApsaraLink } from '@raystack/apsara'
|
|
5
|
-
import type { ComponentProps } from 'react'
|
|
6
|
-
|
|
7
|
-
type LinkProps = ComponentProps<'a'>
|
|
5
|
+
type LinkProps = ComponentProps<'a'>;
|
|
8
6
|
|
|
9
7
|
export function Link({ href, children, ...props }: LinkProps) {
|
|
10
8
|
if (!href) {
|
|
11
|
-
return <span {...props}>{children}</span
|
|
9
|
+
return <span {...props}>{children}</span>;
|
|
12
10
|
}
|
|
13
11
|
|
|
14
|
-
const isExternal = href.startsWith('http://') || href.startsWith('https://')
|
|
15
|
-
const isAnchor = href.startsWith('#')
|
|
12
|
+
const isExternal = href.startsWith('http://') || href.startsWith('https://');
|
|
13
|
+
const isAnchor = href.startsWith('#');
|
|
16
14
|
|
|
17
15
|
if (isAnchor) {
|
|
18
16
|
return (
|
|
19
17
|
<ApsaraLink href={href} {...props}>
|
|
20
18
|
{children}
|
|
21
19
|
</ApsaraLink>
|
|
22
|
-
)
|
|
20
|
+
);
|
|
23
21
|
}
|
|
24
22
|
|
|
25
23
|
if (isExternal) {
|
|
26
24
|
return (
|
|
27
|
-
<ApsaraLink
|
|
25
|
+
<ApsaraLink
|
|
26
|
+
href={href}
|
|
27
|
+
target='_blank'
|
|
28
|
+
rel='noopener noreferrer'
|
|
29
|
+
{...props}
|
|
30
|
+
>
|
|
28
31
|
{children}
|
|
29
32
|
</ApsaraLink>
|
|
30
|
-
)
|
|
33
|
+
);
|
|
31
34
|
}
|
|
32
35
|
|
|
33
36
|
return (
|
|
34
|
-
<
|
|
37
|
+
<RouterLink to={href} className={props.className}>
|
|
35
38
|
{children}
|
|
36
|
-
</
|
|
37
|
-
)
|
|
39
|
+
</RouterLink>
|
|
40
|
+
);
|
|
38
41
|
}
|
|
@@ -1,53 +1,19 @@
|
|
|
1
1
|
'use client'
|
|
2
2
|
|
|
3
3
|
import { Breadcrumb } from '@raystack/apsara'
|
|
4
|
-
import
|
|
4
|
+
import { getBreadcrumbItems } from 'fumadocs-core/breadcrumb'
|
|
5
|
+
import type { Root } from 'fumadocs-core/page-tree'
|
|
5
6
|
|
|
6
7
|
interface BreadcrumbsProps {
|
|
7
8
|
slug: string[]
|
|
8
|
-
tree:
|
|
9
|
-
}
|
|
10
|
-
|
|
11
|
-
function findInTree(items: PageTreeItem[], targetPath: string): PageTreeItem | undefined {
|
|
12
|
-
for (const item of items) {
|
|
13
|
-
const itemUrl = item.url || `/${item.name.toLowerCase().replace(/\s+/g, '-')}`
|
|
14
|
-
if (itemUrl === targetPath || itemUrl === `/${targetPath}`) {
|
|
15
|
-
return item
|
|
16
|
-
}
|
|
17
|
-
if (item.children) {
|
|
18
|
-
const found = findInTree(item.children, targetPath)
|
|
19
|
-
if (found) return found
|
|
20
|
-
}
|
|
21
|
-
}
|
|
22
|
-
return undefined
|
|
23
|
-
}
|
|
24
|
-
|
|
25
|
-
function getFirstPageUrl(item: PageTreeItem): string | undefined {
|
|
26
|
-
if (item.type === 'page' && item.url) {
|
|
27
|
-
return item.url
|
|
28
|
-
}
|
|
29
|
-
if (item.children) {
|
|
30
|
-
for (const child of item.children) {
|
|
31
|
-
const url = getFirstPageUrl(child)
|
|
32
|
-
if (url) return url
|
|
33
|
-
}
|
|
34
|
-
}
|
|
35
|
-
return undefined
|
|
9
|
+
tree: Root
|
|
36
10
|
}
|
|
37
11
|
|
|
38
12
|
export function Breadcrumbs({ slug, tree }: BreadcrumbsProps) {
|
|
39
|
-
const
|
|
13
|
+
const url = slug.length === 0 ? '/' : `/${slug.join('/')}`
|
|
14
|
+
const items = getBreadcrumbItems(url, tree, { includePage: true })
|
|
40
15
|
|
|
41
|
-
|
|
42
|
-
const currentPath = `/${slug.slice(0, i + 1).join('/')}`
|
|
43
|
-
const node = findInTree(tree.children, currentPath)
|
|
44
|
-
const href = node?.url || (node && getFirstPageUrl(node)) || currentPath
|
|
45
|
-
const label = node?.name ?? slug[i]
|
|
46
|
-
items.push({
|
|
47
|
-
label: label.charAt(0).toUpperCase() + label.slice(1),
|
|
48
|
-
href,
|
|
49
|
-
})
|
|
50
|
-
}
|
|
16
|
+
if (items.length === 0) return null
|
|
51
17
|
|
|
52
18
|
return (
|
|
53
19
|
<Breadcrumb size="small">
|
|
@@ -55,10 +21,10 @@ export function Breadcrumbs({ slug, tree }: BreadcrumbsProps) {
|
|
|
55
21
|
const breadcrumbItem = (
|
|
56
22
|
<Breadcrumb.Item
|
|
57
23
|
key={`item-${index}`}
|
|
58
|
-
href={item.
|
|
24
|
+
href={item.url}
|
|
59
25
|
current={index === items.length - 1}
|
|
60
26
|
>
|
|
61
|
-
{item.
|
|
27
|
+
{item.name}
|
|
62
28
|
</Breadcrumb.Item>
|
|
63
29
|
)
|
|
64
30
|
if (index === 0) return [breadcrumbItem]
|