skrypt-ai 0.4.2 → 0.6.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/auth/index.d.ts +13 -3
- package/dist/auth/index.js +101 -9
- package/dist/auth/keychain.d.ts +5 -0
- package/dist/auth/keychain.js +82 -0
- package/dist/auth/notices.d.ts +3 -0
- package/dist/auth/notices.js +42 -0
- package/dist/autofix/index.d.ts +0 -4
- package/dist/autofix/index.js +10 -24
- package/dist/capture/browser.d.ts +11 -0
- package/dist/capture/browser.js +173 -0
- package/dist/capture/diff.d.ts +23 -0
- package/dist/capture/diff.js +52 -0
- package/dist/capture/index.d.ts +23 -0
- package/dist/capture/index.js +210 -0
- package/dist/capture/naming.d.ts +17 -0
- package/dist/capture/naming.js +45 -0
- package/dist/capture/parser.d.ts +15 -0
- package/dist/capture/parser.js +80 -0
- package/dist/capture/types.d.ts +57 -0
- package/dist/capture/types.js +1 -0
- package/dist/cli.js +20 -3
- package/dist/commands/autofix.js +136 -120
- package/dist/commands/cron.js +58 -47
- package/dist/commands/deploy.js +123 -102
- package/dist/commands/generate.js +125 -7
- package/dist/commands/heal.d.ts +10 -0
- package/dist/commands/heal.js +201 -0
- package/dist/commands/i18n.js +146 -111
- package/dist/commands/import.d.ts +2 -0
- package/dist/commands/import.js +157 -0
- package/dist/commands/init.js +19 -7
- package/dist/commands/lint.js +50 -44
- package/dist/commands/llms-txt.js +59 -49
- package/dist/commands/login.js +63 -34
- package/dist/commands/mcp.js +6 -0
- package/dist/commands/monitor.js +13 -8
- package/dist/commands/qa.d.ts +2 -0
- package/dist/commands/qa.js +43 -0
- package/dist/commands/review-pr.js +108 -92
- package/dist/commands/sdk.js +128 -122
- package/dist/commands/security.d.ts +2 -0
- package/dist/commands/security.js +109 -0
- package/dist/commands/test.js +91 -92
- package/dist/commands/version.js +104 -75
- package/dist/commands/watch.js +130 -114
- package/dist/config/types.js +2 -2
- package/dist/context-hub/index.d.ts +23 -0
- package/dist/context-hub/index.js +179 -0
- package/dist/context-hub/mappings.d.ts +8 -0
- package/dist/context-hub/mappings.js +55 -0
- package/dist/context-hub/types.d.ts +33 -0
- package/dist/context-hub/types.js +1 -0
- package/dist/generator/generator.js +39 -6
- package/dist/generator/types.d.ts +7 -0
- package/dist/generator/writer.d.ts +3 -1
- package/dist/generator/writer.js +36 -7
- package/dist/importers/confluence.d.ts +5 -0
- package/dist/importers/confluence.js +137 -0
- package/dist/importers/detect.d.ts +20 -0
- package/dist/importers/detect.js +121 -0
- package/dist/importers/docusaurus.d.ts +5 -0
- package/dist/importers/docusaurus.js +279 -0
- package/dist/importers/gitbook.d.ts +5 -0
- package/dist/importers/gitbook.js +189 -0
- package/dist/importers/github.d.ts +8 -0
- package/dist/importers/github.js +99 -0
- package/dist/importers/index.d.ts +15 -0
- package/dist/importers/index.js +30 -0
- package/dist/importers/markdown.d.ts +6 -0
- package/dist/importers/markdown.js +105 -0
- package/dist/importers/mintlify.d.ts +5 -0
- package/dist/importers/mintlify.js +172 -0
- package/dist/importers/notion.d.ts +5 -0
- package/dist/importers/notion.js +174 -0
- package/dist/importers/readme.d.ts +5 -0
- package/dist/importers/readme.js +184 -0
- package/dist/importers/transform.d.ts +90 -0
- package/dist/importers/transform.js +457 -0
- package/dist/importers/types.d.ts +37 -0
- package/dist/importers/types.js +1 -0
- package/dist/llm/anthropic-client.d.ts +1 -0
- package/dist/llm/anthropic-client.js +3 -1
- package/dist/llm/index.d.ts +6 -4
- package/dist/llm/index.js +76 -261
- package/dist/llm/openai-client.d.ts +1 -0
- package/dist/llm/openai-client.js +7 -2
- package/dist/plugins/index.js +7 -0
- package/dist/qa/checks.d.ts +10 -0
- package/dist/qa/checks.js +492 -0
- package/dist/qa/fixes.d.ts +30 -0
- package/dist/qa/fixes.js +277 -0
- package/dist/qa/index.d.ts +29 -0
- package/dist/qa/index.js +187 -0
- package/dist/qa/types.d.ts +24 -0
- package/dist/qa/types.js +1 -0
- package/dist/scanner/csharp.d.ts +23 -0
- package/dist/scanner/csharp.js +421 -0
- package/dist/scanner/index.js +53 -26
- package/dist/scanner/java.d.ts +39 -0
- package/dist/scanner/java.js +318 -0
- package/dist/scanner/kotlin.d.ts +23 -0
- package/dist/scanner/kotlin.js +389 -0
- package/dist/scanner/php.d.ts +57 -0
- package/dist/scanner/php.js +351 -0
- package/dist/scanner/python.js +17 -0
- package/dist/scanner/ruby.d.ts +36 -0
- package/dist/scanner/ruby.js +431 -0
- package/dist/scanner/swift.d.ts +25 -0
- package/dist/scanner/swift.js +392 -0
- package/dist/scanner/types.d.ts +1 -1
- package/dist/template/content/docs/_navigation.json +46 -0
- package/dist/template/content/docs/_sidebars.json +684 -0
- package/dist/template/content/docs/core.md +4544 -0
- package/dist/template/content/docs/index.mdx +89 -0
- package/dist/template/content/docs/integrations.md +1158 -0
- package/dist/template/content/docs/llms-full.md +403 -0
- package/dist/template/content/docs/llms.txt +4588 -0
- package/dist/template/content/docs/other.md +10379 -0
- package/dist/template/content/docs/tools.md +746 -0
- package/dist/template/content/docs/types.md +531 -0
- package/dist/template/docs.json +13 -11
- package/dist/template/mdx-components.tsx +27 -2
- package/dist/template/package.json +6 -0
- package/dist/template/public/search-index.json +1 -1
- package/dist/template/scripts/build-search-index.mjs +149 -13
- package/dist/template/src/app/api/chat/route.ts +83 -128
- package/dist/template/src/app/docs/[...slug]/page.tsx +75 -20
- package/dist/template/src/app/docs/llms-full.md +151 -4
- package/dist/template/src/app/docs/llms.txt +2464 -847
- package/dist/template/src/app/docs/page.mdx +48 -38
- package/dist/template/src/app/layout.tsx +3 -1
- package/dist/template/src/app/page.tsx +22 -8
- package/dist/template/src/components/ai-chat.tsx +73 -64
- package/dist/template/src/components/breadcrumbs.tsx +21 -23
- package/dist/template/src/components/copy-button.tsx +13 -9
- package/dist/template/src/components/copy-page-button.tsx +54 -0
- package/dist/template/src/components/docs-layout.tsx +37 -25
- package/dist/template/src/components/header.tsx +51 -10
- package/dist/template/src/components/mdx/card.tsx +17 -3
- package/dist/template/src/components/mdx/code-block.tsx +13 -9
- package/dist/template/src/components/mdx/code-group.tsx +13 -8
- package/dist/template/src/components/mdx/heading.tsx +15 -2
- package/dist/template/src/components/mdx/highlighted-code.tsx +13 -8
- package/dist/template/src/components/mdx/index.tsx +2 -0
- package/dist/template/src/components/mdx/mermaid.tsx +110 -0
- package/dist/template/src/components/mdx/screenshot.tsx +150 -0
- package/dist/template/src/components/scroll-to-hash.tsx +48 -0
- package/dist/template/src/components/sidebar.tsx +12 -18
- package/dist/template/src/components/table-of-contents.tsx +9 -0
- package/dist/template/src/lib/highlight.ts +3 -88
- package/dist/template/src/lib/navigation.ts +159 -0
- package/dist/template/src/lib/search-types.ts +4 -1
- package/dist/template/src/lib/search.ts +30 -7
- package/dist/template/src/styles/globals.css +17 -6
- package/dist/utils/files.d.ts +9 -1
- package/dist/utils/files.js +59 -10
- package/dist/utils/validation.d.ts +0 -3
- package/dist/utils/validation.js +0 -26
- package/package.json +5 -1
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
'use client'
|
|
2
|
+
|
|
3
|
+
import { useEffect } from 'react'
|
|
4
|
+
import { usePathname } from 'next/navigation'
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* Handles scrolling to hash targets after page navigation.
|
|
8
|
+
* Next.js App Router can intercept hash links before content is rendered.
|
|
9
|
+
* This component uses a MutationObserver to wait for the target element.
|
|
10
|
+
*/
|
|
11
|
+
export function ScrollToHash() {
|
|
12
|
+
const pathname = usePathname()
|
|
13
|
+
|
|
14
|
+
useEffect(() => {
|
|
15
|
+
const hash = window.location.hash
|
|
16
|
+
if (!hash) return
|
|
17
|
+
|
|
18
|
+
const id = hash.slice(1)
|
|
19
|
+
|
|
20
|
+
// Try immediate scroll first
|
|
21
|
+
const target = document.getElementById(id)
|
|
22
|
+
if (target) {
|
|
23
|
+
target.scrollIntoView({ behavior: 'smooth', block: 'start' })
|
|
24
|
+
return
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
// If element not found yet (SSR/hydration timing), observe DOM
|
|
28
|
+
const observer = new MutationObserver(() => {
|
|
29
|
+
const el = document.getElementById(id)
|
|
30
|
+
if (el) {
|
|
31
|
+
observer.disconnect()
|
|
32
|
+
el.scrollIntoView({ behavior: 'smooth', block: 'start' })
|
|
33
|
+
}
|
|
34
|
+
})
|
|
35
|
+
|
|
36
|
+
observer.observe(document.body, { childList: true, subtree: true })
|
|
37
|
+
|
|
38
|
+
// Cleanup after 5 seconds
|
|
39
|
+
const timeout = setTimeout(() => observer.disconnect(), 5000)
|
|
40
|
+
|
|
41
|
+
return () => {
|
|
42
|
+
observer.disconnect()
|
|
43
|
+
clearTimeout(timeout)
|
|
44
|
+
}
|
|
45
|
+
}, [pathname])
|
|
46
|
+
|
|
47
|
+
return null
|
|
48
|
+
}
|
|
@@ -5,15 +5,13 @@ import { usePathname } from 'next/navigation'
|
|
|
5
5
|
import { ChevronRight, BookOpen, Code, FileText, Settings, Key, Zap, Shield, Globe, Terminal, Database, Cloud, Lock, Rocket, Search, Star, Heart, Package, Puzzle, GitBranch, Cpu } from 'lucide-react'
|
|
6
6
|
import { useState, useMemo } from 'react'
|
|
7
7
|
import { cn } from '@/lib/utils'
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
interface NavGroup { group: string; icon?: string; pages: (NavPage | NavGroup)[] }
|
|
11
|
-
interface DocsConfig { navigation: NavGroup[] }
|
|
8
|
+
import type { NavPage, NavGroup } from '@/lib/navigation'
|
|
9
|
+
import { isNavGroup } from '@/lib/navigation'
|
|
12
10
|
|
|
13
11
|
interface SidebarProps {
|
|
14
12
|
open?: boolean
|
|
15
13
|
onClose?: () => void
|
|
16
|
-
|
|
14
|
+
groups: NavGroup[]
|
|
17
15
|
}
|
|
18
16
|
|
|
19
17
|
const iconMap: Record<string, React.ComponentType<{ size?: number; className?: string }>> = {
|
|
@@ -22,10 +20,6 @@ const iconMap: Record<string, React.ComponentType<{ size?: number; className?: s
|
|
|
22
20
|
Package, Puzzle, GitBranch, Cpu,
|
|
23
21
|
}
|
|
24
22
|
|
|
25
|
-
function isNavGroup(item: NavPage | NavGroup): item is NavGroup {
|
|
26
|
-
return 'group' in item && !('path' in item)
|
|
27
|
-
}
|
|
28
|
-
|
|
29
23
|
/** Check if any descendant page matches the current pathname */
|
|
30
24
|
function hasActivePage(pages: (NavPage | NavGroup)[], pathname: string): boolean {
|
|
31
25
|
for (const item of pages) {
|
|
@@ -39,7 +33,7 @@ function hasActivePage(pages: (NavPage | NavGroup)[], pathname: string): boolean
|
|
|
39
33
|
return false
|
|
40
34
|
}
|
|
41
35
|
|
|
42
|
-
export function Sidebar({ open, onClose,
|
|
36
|
+
export function Sidebar({ open, onClose, groups }: SidebarProps) {
|
|
43
37
|
const pathname = usePathname()
|
|
44
38
|
|
|
45
39
|
return (
|
|
@@ -61,8 +55,8 @@ export function Sidebar({ open, onClose, docsConfig }: SidebarProps) {
|
|
|
61
55
|
open ? 'translate-x-0' : '-translate-x-full lg:translate-x-0'
|
|
62
56
|
)}
|
|
63
57
|
>
|
|
64
|
-
<nav className="py-4 pr-2 space-y-
|
|
65
|
-
{
|
|
58
|
+
<nav className="py-4 pr-2 space-y-5">
|
|
59
|
+
{groups.map((group) => (
|
|
66
60
|
<SidebarGroup key={group.group} group={group} pathname={pathname} onNavigate={onClose} level={0} />
|
|
67
61
|
))}
|
|
68
62
|
</nav>
|
|
@@ -80,7 +74,7 @@ function SidebarGroup({ group, pathname, onNavigate, level }: { group: NavGroup;
|
|
|
80
74
|
// Limit nesting to 3 levels
|
|
81
75
|
if (level > 2) return null
|
|
82
76
|
|
|
83
|
-
const paddingLeft = level === 0 ? 'pl-4' : level === 1 ? 'pl-
|
|
77
|
+
const paddingLeft = level === 0 ? 'pl-4' : level === 1 ? 'pl-8' : 'pl-12'
|
|
84
78
|
|
|
85
79
|
return (
|
|
86
80
|
<div>
|
|
@@ -88,15 +82,15 @@ function SidebarGroup({ group, pathname, onNavigate, level }: { group: NavGroup;
|
|
|
88
82
|
onClick={() => setExpanded(!expanded)}
|
|
89
83
|
aria-expanded={expanded}
|
|
90
84
|
className={cn(
|
|
91
|
-
'flex items-center gap-2
|
|
85
|
+
'flex items-center gap-2 w-full mb-1 py-1 text-[0.6875rem] font-semibold uppercase tracking-wider text-[var(--color-text-tertiary)] hover:text-[var(--color-text-secondary)] transition-colors',
|
|
92
86
|
paddingLeft
|
|
93
87
|
)}
|
|
94
88
|
>
|
|
95
89
|
<ChevronRight
|
|
96
|
-
size={
|
|
97
|
-
className={cn('
|
|
90
|
+
size={10}
|
|
91
|
+
className={cn('shrink-0 transition-transform duration-100', expanded && 'rotate-90')}
|
|
98
92
|
/>
|
|
99
|
-
{Icon && <Icon size={
|
|
93
|
+
{Icon && <Icon size={12} className="shrink-0" />}
|
|
100
94
|
{group.group}
|
|
101
95
|
</button>
|
|
102
96
|
{expanded && (
|
|
@@ -141,7 +135,7 @@ function SidebarPageItem({ page, pathname, onNavigate, level }: { page: NavPage;
|
|
|
141
135
|
}, [page, pathname])
|
|
142
136
|
const [expanded, setExpanded] = useState(isActive)
|
|
143
137
|
|
|
144
|
-
const paddingClass = level === 0 ? 'pl-
|
|
138
|
+
const paddingClass = level === 0 ? 'pl-7' : level === 1 ? 'pl-11' : 'pl-14'
|
|
145
139
|
|
|
146
140
|
return (
|
|
147
141
|
<div>
|
|
@@ -21,6 +21,7 @@ export function TableOfContents() {
|
|
|
21
21
|
|
|
22
22
|
const elements = article.querySelectorAll('h2, h3')
|
|
23
23
|
const items: Heading[] = []
|
|
24
|
+
const seenIds = new Set<string>()
|
|
24
25
|
|
|
25
26
|
// Generic headings that repeat in API reference pages — skip them in TOC
|
|
26
27
|
const genericHeadings = new Set([
|
|
@@ -28,6 +29,7 @@ export function TableOfContents() {
|
|
|
28
29
|
'returned validator function', 'requirements',
|
|
29
30
|
'when results are returned', 'when each value is returned',
|
|
30
31
|
'validationresult shape',
|
|
32
|
+
'example', 'related', 'notes', 'properties',
|
|
31
33
|
])
|
|
32
34
|
|
|
33
35
|
elements.forEach((el) => {
|
|
@@ -37,9 +39,16 @@ export function TableOfContents() {
|
|
|
37
39
|
// Skip generic/repeated headings
|
|
38
40
|
if (genericHeadings.has(normalized)) return
|
|
39
41
|
|
|
42
|
+
// Skip headings inside Card components (Cards use h3 for titles, not doc headings)
|
|
43
|
+
if (el.closest('.card-link') || el.closest('[data-card]')) return
|
|
44
|
+
|
|
40
45
|
const id = el.id || normalized.replace(/\s+/g, '-')
|
|
41
46
|
if (!el.id) el.id = id
|
|
42
47
|
|
|
48
|
+
// Deduplicate — skip if we've already seen this ID
|
|
49
|
+
if (seenIds.has(id)) return
|
|
50
|
+
seenIds.add(id)
|
|
51
|
+
|
|
43
52
|
items.push({
|
|
44
53
|
id,
|
|
45
54
|
text,
|
|
@@ -1,7 +1,4 @@
|
|
|
1
|
-
import {
|
|
2
|
-
|
|
3
|
-
// Cache highlighter instances per loaded theme set
|
|
4
|
-
const highlighterCache = new Map<string, Highlighter>()
|
|
1
|
+
import { codeToHtml, type BundledTheme } from 'shiki'
|
|
5
2
|
|
|
6
3
|
export type ThemeName =
|
|
7
4
|
| 'catppuccin-mocha'
|
|
@@ -28,98 +25,16 @@ export const AVAILABLE_THEMES: { name: ThemeName; label: string; isDark: boolean
|
|
|
28
25
|
|
|
29
26
|
export const DEFAULT_THEME: ThemeName = 'catppuccin-mocha'
|
|
30
27
|
|
|
31
|
-
const SUPPORTED_LANGS = [
|
|
32
|
-
'typescript',
|
|
33
|
-
'javascript',
|
|
34
|
-
'python',
|
|
35
|
-
'go',
|
|
36
|
-
'rust',
|
|
37
|
-
'json',
|
|
38
|
-
'yaml',
|
|
39
|
-
'bash',
|
|
40
|
-
'shell',
|
|
41
|
-
'markdown',
|
|
42
|
-
'html',
|
|
43
|
-
'css',
|
|
44
|
-
'jsx',
|
|
45
|
-
'tsx',
|
|
46
|
-
'sql',
|
|
47
|
-
'graphql',
|
|
48
|
-
'diff',
|
|
49
|
-
]
|
|
50
|
-
|
|
51
|
-
// Start with just the default theme; load others on demand
|
|
52
|
-
let defaultHighlighter: Highlighter | null = null
|
|
53
|
-
let defaultHighlighterPromise: Promise<Highlighter> | null = null
|
|
54
|
-
|
|
55
|
-
async function getDefaultHighlighter(): Promise<Highlighter> {
|
|
56
|
-
if (defaultHighlighter) return defaultHighlighter
|
|
57
|
-
if (defaultHighlighterPromise) return defaultHighlighterPromise
|
|
58
|
-
|
|
59
|
-
defaultHighlighterPromise = createHighlighter({
|
|
60
|
-
themes: [DEFAULT_THEME] as BundledTheme[],
|
|
61
|
-
langs: SUPPORTED_LANGS,
|
|
62
|
-
}).then(hl => {
|
|
63
|
-
defaultHighlighter = hl
|
|
64
|
-
highlighterCache.set(DEFAULT_THEME, hl)
|
|
65
|
-
return hl
|
|
66
|
-
})
|
|
67
|
-
|
|
68
|
-
return defaultHighlighterPromise
|
|
69
|
-
}
|
|
70
|
-
|
|
71
|
-
async function getHighlighterForTheme(theme: ThemeName): Promise<Highlighter> {
|
|
72
|
-
if (theme === DEFAULT_THEME || highlighterCache.has(DEFAULT_THEME)) {
|
|
73
|
-
// Try to load the theme into the existing default highlighter
|
|
74
|
-
const hl = await getDefaultHighlighter()
|
|
75
|
-
try {
|
|
76
|
-
// Check if the theme is already loaded by attempting to use it
|
|
77
|
-
const loadedThemes = hl.getLoadedThemes()
|
|
78
|
-
if (!loadedThemes.includes(theme as BundledTheme)) {
|
|
79
|
-
await hl.loadTheme(theme as BundledTheme)
|
|
80
|
-
}
|
|
81
|
-
return hl
|
|
82
|
-
} catch {
|
|
83
|
-
// If loading into existing highlighter fails, create a new one
|
|
84
|
-
}
|
|
85
|
-
}
|
|
86
|
-
|
|
87
|
-
// Check cache
|
|
88
|
-
const cached = highlighterCache.get(theme)
|
|
89
|
-
if (cached) return cached
|
|
90
|
-
|
|
91
|
-
// Create a new highlighter with this theme
|
|
92
|
-
const hl = await createHighlighter({
|
|
93
|
-
themes: [theme] as BundledTheme[],
|
|
94
|
-
langs: SUPPORTED_LANGS,
|
|
95
|
-
})
|
|
96
|
-
highlighterCache.set(theme, hl)
|
|
97
|
-
return hl
|
|
98
|
-
}
|
|
99
|
-
|
|
100
|
-
export async function getHighlighter(): Promise<Highlighter> {
|
|
101
|
-
return getDefaultHighlighter()
|
|
102
|
-
}
|
|
103
|
-
|
|
104
28
|
export async function highlight(
|
|
105
29
|
code: string,
|
|
106
30
|
language: string,
|
|
107
31
|
theme: ThemeName = DEFAULT_THEME
|
|
108
32
|
): Promise<string> {
|
|
109
|
-
const hl = await getHighlighterForTheme(theme)
|
|
110
|
-
|
|
111
33
|
const lang = normalizeLanguage(language)
|
|
112
|
-
|
|
113
34
|
try {
|
|
114
|
-
return
|
|
115
|
-
lang,
|
|
116
|
-
theme: theme as BundledTheme,
|
|
117
|
-
})
|
|
35
|
+
return await codeToHtml(code, { lang, theme: theme as BundledTheme })
|
|
118
36
|
} catch {
|
|
119
|
-
return
|
|
120
|
-
lang: 'text',
|
|
121
|
-
theme: theme as BundledTheme,
|
|
122
|
-
})
|
|
37
|
+
return await codeToHtml(code, { lang: 'text', theme: theme as BundledTheme })
|
|
123
38
|
}
|
|
124
39
|
}
|
|
125
40
|
|
|
@@ -0,0 +1,159 @@
|
|
|
1
|
+
export interface NavPage {
|
|
2
|
+
title: string
|
|
3
|
+
path: string
|
|
4
|
+
description?: string
|
|
5
|
+
pages?: NavPage[]
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
export interface NavGroup {
|
|
9
|
+
group: string
|
|
10
|
+
icon?: string
|
|
11
|
+
pages: (NavPage | NavGroup)[]
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
export interface NavTab {
|
|
15
|
+
tab: string
|
|
16
|
+
icon?: string
|
|
17
|
+
groups: NavGroup[]
|
|
18
|
+
href?: string
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
export type Navigation = NavGroup[] | NavTab[]
|
|
22
|
+
|
|
23
|
+
export function isNavGroup(item: NavPage | NavGroup): item is NavGroup {
|
|
24
|
+
return 'group' in item && !('path' in item)
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
/** Check if navigation uses tabs format */
|
|
28
|
+
export function hasTabs(navigation: Navigation): navigation is NavTab[] {
|
|
29
|
+
return navigation.length > 0 && 'tab' in navigation[0]
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
/** Recursively check if a page path exists within a list of groups */
|
|
33
|
+
function groupsContainPath(groups: NavGroup[], pathname: string): boolean {
|
|
34
|
+
for (const group of groups) {
|
|
35
|
+
for (const item of group.pages) {
|
|
36
|
+
if (isNavGroup(item)) {
|
|
37
|
+
if (groupsContainPath([item], pathname)) return true
|
|
38
|
+
} else {
|
|
39
|
+
if (item.path === pathname) return true
|
|
40
|
+
if (item.pages && pagesContainPath(item.pages, pathname)) return true
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
return false
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
function pagesContainPath(pages: NavPage[], pathname: string): boolean {
|
|
48
|
+
for (const page of pages) {
|
|
49
|
+
if (page.path === pathname) return true
|
|
50
|
+
if (page.pages && pagesContainPath(page.pages, pathname)) return true
|
|
51
|
+
}
|
|
52
|
+
return false
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
/** Find the active tab based on current pathname */
|
|
56
|
+
export function getActiveTab(pathname: string, tabs: NavTab[]): NavTab | null {
|
|
57
|
+
for (const tab of tabs) {
|
|
58
|
+
if (tab.href && tab.href === pathname) return tab
|
|
59
|
+
if (tab.groups && groupsContainPath(tab.groups, pathname)) return tab
|
|
60
|
+
}
|
|
61
|
+
// Default to first tab if no match
|
|
62
|
+
return tabs.length > 0 ? tabs[0] : null
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
/** Get sidebar groups for current pathname (works for both flat and tabbed nav) */
|
|
66
|
+
export function getTabGroups(navigation: Navigation, pathname: string): NavGroup[] {
|
|
67
|
+
if (!hasTabs(navigation)) return navigation
|
|
68
|
+
const active = getActiveTab(pathname, navigation)
|
|
69
|
+
return active?.groups ?? []
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
/** Get first page path for a tab (for linking) */
|
|
73
|
+
export function getTabHref(tab: NavTab): string {
|
|
74
|
+
if (tab.href) return tab.href
|
|
75
|
+
for (const group of tab.groups) {
|
|
76
|
+
for (const item of group.pages) {
|
|
77
|
+
if (isNavGroup(item)) {
|
|
78
|
+
for (const subItem of item.pages) {
|
|
79
|
+
if (!isNavGroup(subItem) && subItem.path) return subItem.path
|
|
80
|
+
}
|
|
81
|
+
} else {
|
|
82
|
+
return item.path
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
return '/'
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
/** Flatten all pages across all tabs/groups for prev/next navigation */
|
|
90
|
+
export function getAllPagesFlat(navigation: Navigation): Array<{ title: string; path: string }> {
|
|
91
|
+
const pages: Array<{ title: string; path: string }> = []
|
|
92
|
+
|
|
93
|
+
function collectFromPages(items: (NavPage | NavGroup)[]) {
|
|
94
|
+
for (const item of items) {
|
|
95
|
+
if (isNavGroup(item)) {
|
|
96
|
+
collectFromPages(item.pages)
|
|
97
|
+
} else {
|
|
98
|
+
pages.push({ title: item.title, path: item.path })
|
|
99
|
+
if (item.pages) collectFromPages(item.pages)
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
if (hasTabs(navigation)) {
|
|
105
|
+
for (const tab of navigation) {
|
|
106
|
+
if (tab.groups) {
|
|
107
|
+
for (const group of tab.groups) {
|
|
108
|
+
collectFromPages(group.pages)
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
} else {
|
|
113
|
+
for (const group of navigation) {
|
|
114
|
+
collectFromPages(group.pages)
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
return pages
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
/** Look up page info from navigation (works with both formats) */
|
|
122
|
+
export function findPageInNavigation(
|
|
123
|
+
navigation: Navigation,
|
|
124
|
+
pathname: string
|
|
125
|
+
): { title?: string; description?: string } {
|
|
126
|
+
function searchPages(items: (NavPage | NavGroup)[]): { title?: string; description?: string } | null {
|
|
127
|
+
for (const item of items) {
|
|
128
|
+
if (isNavGroup(item)) {
|
|
129
|
+
const found = searchPages(item.pages)
|
|
130
|
+
if (found) return found
|
|
131
|
+
} else {
|
|
132
|
+
if (item.path === pathname) return { title: item.title, description: item.description }
|
|
133
|
+
if (item.pages) {
|
|
134
|
+
const found = searchPages(item.pages)
|
|
135
|
+
if (found) return found
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
return null
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
if (hasTabs(navigation)) {
|
|
143
|
+
for (const tab of navigation) {
|
|
144
|
+
if (tab.groups) {
|
|
145
|
+
for (const group of tab.groups) {
|
|
146
|
+
const found = searchPages(group.pages)
|
|
147
|
+
if (found) return found
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
} else {
|
|
152
|
+
for (const group of navigation) {
|
|
153
|
+
const found = searchPages(group.pages)
|
|
154
|
+
if (found) return found
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
return {}
|
|
159
|
+
}
|
|
@@ -1,6 +1,5 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Type definitions for Orama search database
|
|
3
|
-
* Fixes P0: Removes `any` types from search functionality
|
|
4
3
|
*/
|
|
5
4
|
|
|
6
5
|
import type { Orama, Results, SearchParams } from '@orama/orama'
|
|
@@ -9,6 +8,8 @@ import type { Orama, Results, SearchParams } from '@orama/orama'
|
|
|
9
8
|
export interface SearchDocument {
|
|
10
9
|
id: string
|
|
11
10
|
title: string
|
|
11
|
+
headings: string
|
|
12
|
+
keywords: string
|
|
12
13
|
content: string
|
|
13
14
|
href: string
|
|
14
15
|
section: string
|
|
@@ -18,6 +19,8 @@ export interface SearchDocument {
|
|
|
18
19
|
export type SearchDatabase = Orama<{
|
|
19
20
|
id: 'string'
|
|
20
21
|
title: 'string'
|
|
22
|
+
headings: 'string'
|
|
23
|
+
keywords: 'string'
|
|
21
24
|
content: 'string'
|
|
22
25
|
href: 'string'
|
|
23
26
|
section: 'string'
|
|
@@ -7,6 +7,8 @@ let loadPromise: Promise<void> | null = null
|
|
|
7
7
|
const schema = {
|
|
8
8
|
id: 'string' as const,
|
|
9
9
|
title: 'string' as const,
|
|
10
|
+
headings: 'string' as const,
|
|
11
|
+
keywords: 'string' as const,
|
|
10
12
|
content: 'string' as const,
|
|
11
13
|
href: 'string' as const,
|
|
12
14
|
section: 'string' as const,
|
|
@@ -41,7 +43,15 @@ async function loadSearchIndex(): Promise<void> {
|
|
|
41
43
|
return
|
|
42
44
|
}
|
|
43
45
|
|
|
44
|
-
const newDb = await create({
|
|
46
|
+
const newDb = await create({
|
|
47
|
+
schema,
|
|
48
|
+
components: {
|
|
49
|
+
tokenizer: {
|
|
50
|
+
stemming: true,
|
|
51
|
+
language: 'english',
|
|
52
|
+
},
|
|
53
|
+
},
|
|
54
|
+
}) as SearchDatabase
|
|
45
55
|
await load(newDb, data as RawData)
|
|
46
56
|
db = newDb
|
|
47
57
|
} catch (err) {
|
|
@@ -55,10 +65,21 @@ async function loadSearchIndex(): Promise<void> {
|
|
|
55
65
|
}
|
|
56
66
|
|
|
57
67
|
/**
|
|
58
|
-
* Generate a snippet with highlighted search terms
|
|
68
|
+
* Generate a snippet with highlighted search terms, preferring heading matches
|
|
59
69
|
*/
|
|
60
|
-
function generateSnippet(content: string, query: string, maxLength: number = 150): string {
|
|
70
|
+
function generateSnippet(content: string, headings: string, query: string, maxLength: number = 150): string {
|
|
61
71
|
const terms = query.toLowerCase().split(/\s+/).filter(Boolean)
|
|
72
|
+
|
|
73
|
+
// Check if any headings match — show that context first
|
|
74
|
+
if (headings) {
|
|
75
|
+
const headingList = headings.split(' | ')
|
|
76
|
+
for (const heading of headingList) {
|
|
77
|
+
if (terms.some(term => heading.toLowerCase().includes(term))) {
|
|
78
|
+
return heading
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
|
|
62
83
|
const contentLower = content.toLowerCase()
|
|
63
84
|
|
|
64
85
|
// Find the best position to start the snippet (where most terms match)
|
|
@@ -110,11 +131,13 @@ export async function search(query: string): Promise<SearchResultWithHighlight[]
|
|
|
110
131
|
try {
|
|
111
132
|
const results = await oramaSearch(db, {
|
|
112
133
|
term: query,
|
|
113
|
-
properties: ['title', 'content'],
|
|
134
|
+
properties: ['title', 'headings', 'keywords', 'content'],
|
|
114
135
|
limit: 10,
|
|
115
|
-
tolerance:
|
|
136
|
+
tolerance: 2,
|
|
116
137
|
boost: {
|
|
117
|
-
title:
|
|
138
|
+
title: 5,
|
|
139
|
+
headings: 3,
|
|
140
|
+
keywords: 2,
|
|
118
141
|
content: 1,
|
|
119
142
|
},
|
|
120
143
|
})
|
|
@@ -124,7 +147,7 @@ export async function search(query: string): Promise<SearchResultWithHighlight[]
|
|
|
124
147
|
href: hit.document.href,
|
|
125
148
|
content: hit.document.content,
|
|
126
149
|
section: hit.document.section || undefined,
|
|
127
|
-
snippet: generateSnippet(hit.document.content, query),
|
|
150
|
+
snippet: generateSnippet(hit.document.content, hit.document.headings, query),
|
|
128
151
|
score: hit.score,
|
|
129
152
|
}))
|
|
130
153
|
} catch (err) {
|
|
@@ -80,14 +80,14 @@
|
|
|
80
80
|
--color-bg: #0f1117;
|
|
81
81
|
--color-bg-secondary: #1a1d27;
|
|
82
82
|
--color-bg-tertiary: #252833;
|
|
83
|
-
--color-text: #
|
|
84
|
-
--color-text-secondary: #
|
|
83
|
+
--color-text: #e0e2e6;
|
|
84
|
+
--color-text-secondary: #b0b4bc;
|
|
85
85
|
--color-text-tertiary: #6b7280;
|
|
86
86
|
--color-border: rgba(255, 255, 255, 0.07);
|
|
87
87
|
--color-border-strong: rgba(255, 255, 255, 0.12);
|
|
88
88
|
--color-code-bg: #0b0c0e;
|
|
89
89
|
--color-code-border: rgba(255, 255, 255, 0.1);
|
|
90
|
-
--color-code-text: #
|
|
90
|
+
--color-code-text: #e0e2e6;
|
|
91
91
|
}
|
|
92
92
|
}
|
|
93
93
|
|
|
@@ -95,14 +95,14 @@
|
|
|
95
95
|
--color-bg: #0f1117;
|
|
96
96
|
--color-bg-secondary: #1a1d27;
|
|
97
97
|
--color-bg-tertiary: #252833;
|
|
98
|
-
--color-text: #
|
|
99
|
-
--color-text-secondary: #
|
|
98
|
+
--color-text: #e0e2e6;
|
|
99
|
+
--color-text-secondary: #b0b4bc;
|
|
100
100
|
--color-text-tertiary: #6b7280;
|
|
101
101
|
--color-border: rgba(255, 255, 255, 0.07);
|
|
102
102
|
--color-border-strong: rgba(255, 255, 255, 0.12);
|
|
103
103
|
--color-code-bg: #0b0c0e;
|
|
104
104
|
--color-code-border: rgba(255, 255, 255, 0.1);
|
|
105
|
-
--color-code-text: #
|
|
105
|
+
--color-code-text: #e0e2e6;
|
|
106
106
|
}
|
|
107
107
|
|
|
108
108
|
/* Error page icon — dark mode overrides (avoids Tailwind dark: variant mismatch) */
|
|
@@ -257,6 +257,17 @@ code {
|
|
|
257
257
|
border-bottom-width: 2px;
|
|
258
258
|
}
|
|
259
259
|
|
|
260
|
+
/* Cards and card groups should not inherit prose link styling */
|
|
261
|
+
.prose .card-link,
|
|
262
|
+
.prose a:has(> .group) {
|
|
263
|
+
border-bottom: none !important;
|
|
264
|
+
font-weight: inherit;
|
|
265
|
+
}
|
|
266
|
+
.prose .card-link:hover,
|
|
267
|
+
.prose a:has(> .group):hover {
|
|
268
|
+
border-bottom: none !important;
|
|
269
|
+
}
|
|
270
|
+
|
|
260
271
|
.prose p {
|
|
261
272
|
margin-top: 1.25em;
|
|
262
273
|
margin-bottom: 1.25em;
|
package/dist/utils/files.d.ts
CHANGED
|
@@ -1,9 +1,17 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Recursively find all .md and .mdx files in a directory.
|
|
3
|
-
* Skips hidden directories and
|
|
3
|
+
* Skips hidden directories, node_modules, and symlinks.
|
|
4
4
|
*/
|
|
5
5
|
export declare function findMdxFiles(dir: string): string[];
|
|
6
6
|
/**
|
|
7
7
|
* Convert string to URL-safe slug
|
|
8
8
|
*/
|
|
9
9
|
export declare function slugify(str: string): string;
|
|
10
|
+
/**
|
|
11
|
+
* Simple YAML frontmatter parser — splits on --- markers.
|
|
12
|
+
* Returns parsed key-value data and remaining content body.
|
|
13
|
+
*/
|
|
14
|
+
export declare function parseFrontmatter(content: string): {
|
|
15
|
+
data: Record<string, unknown>;
|
|
16
|
+
content: string;
|
|
17
|
+
};
|