Skip to content

Commit 5da4bf3

Browse files
rsbhclaude
andauthored
fix: paper theme search and relative link resolution (#45)
* fix: enable search in paper theme via Cmd+K shortcut Search component was missing from paper theme. Add it with hidden trigger button so only keyboard shortcut (Cmd/Ctrl+K) opens the dialog. Refactor Search props from className to classNames object for flexibility. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * fix: resolve relative links in MDX content to absolute routes Replace remark-strip-md-extensions with remark-resolve-links that resolves relative links (./page.mdx, ../dir/page.md) to absolute routes based on the source file's location. Also strips index/readme suffixes to match route conventions. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * fix: include reading time in page API for client navigation Load MDX module in /api/page endpoint to include _readingTime in frontmatter response, matching SSR behavior in entry-server.tsx. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * fix: address CodeRabbit review comments - Use lastIndexOf for /content/ to avoid matching outer path directories - Guard all URI schemes (mailto:, tel:, ftp:, etc.) not just http(s) - Remove duplicate Search mount from ApiLayout — theme Layouts own search Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
1 parent 9c0a7f9 commit 5da4bf3

12 files changed

Lines changed: 70 additions & 25 deletions

File tree

examples/versioned/content/dev/api.mdx

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,3 +6,8 @@ order: 2
66
# Dev API notes — latest
77

88
Latest `/dev/api`.
9+
10+
- [Dev Home](./index.mdx)
11+
- [Docs Guide](../docs/guide.mdx)
12+
- [Docs Home](../docs/index.mdx)
13+
- [Guide with hash](../docs/guide.mdx#some-section)

examples/versioned/content/dev/index.mdx

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,3 +6,7 @@ order: 1
66
# Dev — latest
77

88
Latest `/dev`.
9+
10+
- [API Notes](./api.mdx)
11+
- [Docs Home](../docs/index.mdx)
12+
- [Docs Guide](../docs/guide.mdx)

examples/versioned/content/docs/guide.mdx

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,3 +6,7 @@ order: 2
66
# Guide — latest docs
77

88
Latest `/docs/guide`.
9+
10+
- [Docs Home](./index.mdx)
11+
- [Dev API](../dev/api.mdx)
12+
- [Dev Home](../dev/index.mdx)

examples/versioned/content/docs/index.mdx

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,10 @@ order: 1
88

99
This is `/docs` on latest (3.0).
1010

11+
## Links
12+
13+
- [Guide](./guide.mdx)
14+
1115
## Getting Started
1216

1317
Lorem ipsum dolor sit amet, consectetur adipiscing elit. Sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat.

packages/chronicle/src/components/ui/search.tsx

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -13,10 +13,10 @@ import { usePageContext } from '@/lib/page-context';
1313
import styles from './search.module.css';
1414

1515
interface SearchProps {
16-
className?: string;
16+
classNames?: { trigger?: string };
1717
}
1818

19-
export function Search({ className }: SearchProps) {
19+
export function Search({ classNames }: SearchProps) {
2020
const [open, setOpen] = useState(false);
2121
const navigate = useNavigate();
2222
const { version } = usePageContext();
@@ -60,7 +60,7 @@ export function Search({ className }: SearchProps) {
6060
aria-label='Search'
6161
title='Search (Ctrl/⌘K)'
6262
onClick={() => setOpen(true)}
63-
className={className}
63+
className={classNames?.trigger}
6464
>
6565
<MagnifyingGlassIcon width={16} height={16} />
6666
</IconButton>
Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
import path from 'node:path'
2+
import { visit } from 'unist-util-visit'
3+
import type { Plugin } from 'unified'
4+
import type { Link } from 'mdast'
5+
6+
const remarkResolveLinks: Plugin = () => {
7+
return (tree, file) => {
8+
const filePath = file.path
9+
if (!filePath) return
10+
11+
const contentIdx = filePath.lastIndexOf('/content/')
12+
if (contentIdx === -1) return
13+
14+
const relative = filePath.slice(contentIdx + '/content/'.length)
15+
const dir = path.posix.dirname(relative)
16+
17+
visit(tree, 'link', (node: Link) => {
18+
if (!node.url) return
19+
if (/^[a-z][a-z0-9+\-.]*:/i.test(node.url)) return
20+
if (node.url.startsWith('#')) return
21+
if (node.url.startsWith('/')) return
22+
23+
const [rawPath, hash] = node.url.split('#')
24+
const stripped = rawPath.replace(/\.mdx?$/, '')
25+
let resolved = path.posix.normalize(path.posix.join(dir, stripped))
26+
resolved = resolved.replace(/\/(index|readme)$/i, '') || '.'
27+
node.url = `/${resolved}${hash ? `#${hash}` : ''}`
28+
})
29+
}
30+
}
31+
32+
export default remarkResolveLinks

packages/chronicle/src/lib/remark-strip-md-extensions.ts

Lines changed: 0 additions & 14 deletions
This file was deleted.

packages/chronicle/src/pages/ApiLayout.tsx

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,5 @@
11
import { cx } from 'class-variance-authority';
22
import type { ReactNode } from 'react';
3-
import { Search } from '@/components/ui/search';
43
import { buildApiPageTree } from '@/lib/api-routes';
54
import { usePageContext } from '@/lib/page-context';
65
import { getTheme } from '@/themes/registry';
@@ -26,7 +25,6 @@ export function ApiLayout({ children }: ApiLayoutProps) {
2625
content: styles.content
2726
}}
2827
>
29-
<Search className={styles.hiddenSearch} />
3028
{children}
3129
</Layout>
3230
);

packages/chronicle/src/server/api/page.ts

Lines changed: 10 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import { defineHandler, HTTPError } from 'nitro';
2-
import { getPage, getPageNav, extractFrontmatter, getRelativePath, getOriginalPath } from '@/lib/source';
2+
import { getPage, getPageNav, extractFrontmatter, getRelativePath, getOriginalPath, loadPageModule } from '@/lib/source';
33

44
export default defineHandler(async event => {
55
const slugParam = event.url.searchParams.get('slug') ?? '';
@@ -11,11 +11,17 @@ export default defineHandler(async event => {
1111
}
1212

1313
const nav = await getPageNav(slug);
14+
const originalPath = getOriginalPath(page);
15+
const relativePath = getRelativePath(page);
16+
const mdxModule = (originalPath || relativePath) ? await loadPageModule(originalPath || relativePath) : null;
1417

1518
return {
16-
frontmatter: extractFrontmatter(page, slug[slug.length - 1]),
17-
relativePath: getRelativePath(page),
18-
originalPath: getOriginalPath(page),
19+
frontmatter: {
20+
...extractFrontmatter(page, slug[slug.length - 1]),
21+
_readingTime: mdxModule?._readingTime,
22+
},
23+
relativePath,
24+
originalPath,
1925
prev: nav.prev,
2026
next: nav.next,
2127
};

packages/chronicle/src/server/vite-config.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ import fs from 'node:fs/promises';
77
import path from 'node:path';
88
import remarkDirective from 'remark-directive';
99
import { type InlineConfig } from 'vite';
10-
import remarkStripMdExtensions from '../lib/remark-strip-md-extensions';
10+
import remarkResolveLinks from '../lib/remark-resolve-links';
1111
import remarkReadingTime from 'remark-reading-time';
1212
import remarkUnusedDirectives from '../lib/remark-unused-directives';
1313

@@ -77,7 +77,7 @@ export async function createViteConfig(
7777
},
7878
}],
7979
remarkUnusedDirectives,
80-
remarkStripMdExtensions,
80+
remarkResolveLinks,
8181
remarkMdxMermaid,
8282
remarkReadingTime,
8383
],

0 commit comments

Comments
 (0)