@raystack/chronicle 0.1.0-canary.e11f924

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.
Files changed (104) hide show
  1. package/bin/chronicle.js +2 -0
  2. package/dist/cli/index.js +9788 -0
  3. package/next.config.mjs +10 -0
  4. package/package.json +62 -0
  5. package/source.config.ts +50 -0
  6. package/src/app/[[...slug]]/layout.tsx +15 -0
  7. package/src/app/[[...slug]]/page.tsx +57 -0
  8. package/src/app/api/apis-proxy/route.ts +59 -0
  9. package/src/app/api/health/route.ts +3 -0
  10. package/src/app/api/search/route.ts +90 -0
  11. package/src/app/apis/[[...slug]]/layout.module.css +22 -0
  12. package/src/app/apis/[[...slug]]/layout.tsx +26 -0
  13. package/src/app/apis/[[...slug]]/page.tsx +57 -0
  14. package/src/app/layout.tsx +26 -0
  15. package/src/app/llms-full.txt/route.ts +18 -0
  16. package/src/app/llms.txt/route.ts +15 -0
  17. package/src/app/providers.tsx +8 -0
  18. package/src/cli/commands/build.ts +33 -0
  19. package/src/cli/commands/dev.ts +34 -0
  20. package/src/cli/commands/init.ts +58 -0
  21. package/src/cli/commands/serve.ts +54 -0
  22. package/src/cli/commands/start.ts +34 -0
  23. package/src/cli/index.ts +21 -0
  24. package/src/cli/utils/config.ts +43 -0
  25. package/src/cli/utils/index.ts +2 -0
  26. package/src/cli/utils/process.ts +7 -0
  27. package/src/components/api/code-snippets.module.css +7 -0
  28. package/src/components/api/code-snippets.tsx +76 -0
  29. package/src/components/api/endpoint-page.module.css +58 -0
  30. package/src/components/api/endpoint-page.tsx +283 -0
  31. package/src/components/api/field-row.module.css +126 -0
  32. package/src/components/api/field-row.tsx +204 -0
  33. package/src/components/api/field-section.module.css +24 -0
  34. package/src/components/api/field-section.tsx +100 -0
  35. package/src/components/api/index.ts +8 -0
  36. package/src/components/api/json-editor.module.css +9 -0
  37. package/src/components/api/json-editor.tsx +61 -0
  38. package/src/components/api/key-value-editor.module.css +13 -0
  39. package/src/components/api/key-value-editor.tsx +62 -0
  40. package/src/components/api/method-badge.module.css +4 -0
  41. package/src/components/api/method-badge.tsx +29 -0
  42. package/src/components/api/response-panel.module.css +8 -0
  43. package/src/components/api/response-panel.tsx +44 -0
  44. package/src/components/common/breadcrumb.tsx +3 -0
  45. package/src/components/common/button.tsx +3 -0
  46. package/src/components/common/callout.module.css +7 -0
  47. package/src/components/common/callout.tsx +27 -0
  48. package/src/components/common/code-block.tsx +3 -0
  49. package/src/components/common/dialog.tsx +3 -0
  50. package/src/components/common/index.ts +10 -0
  51. package/src/components/common/input-field.tsx +3 -0
  52. package/src/components/common/sidebar.tsx +3 -0
  53. package/src/components/common/switch.tsx +3 -0
  54. package/src/components/common/table.tsx +3 -0
  55. package/src/components/common/tabs.tsx +3 -0
  56. package/src/components/mdx/code.module.css +42 -0
  57. package/src/components/mdx/code.tsx +27 -0
  58. package/src/components/mdx/details.module.css +37 -0
  59. package/src/components/mdx/details.tsx +18 -0
  60. package/src/components/mdx/image.tsx +38 -0
  61. package/src/components/mdx/index.tsx +35 -0
  62. package/src/components/mdx/link.tsx +38 -0
  63. package/src/components/mdx/mermaid.module.css +9 -0
  64. package/src/components/mdx/mermaid.tsx +37 -0
  65. package/src/components/mdx/paragraph.module.css +8 -0
  66. package/src/components/mdx/paragraph.tsx +19 -0
  67. package/src/components/mdx/table.tsx +40 -0
  68. package/src/components/ui/breadcrumbs.tsx +72 -0
  69. package/src/components/ui/client-theme-switcher.tsx +18 -0
  70. package/src/components/ui/footer.module.css +27 -0
  71. package/src/components/ui/footer.tsx +30 -0
  72. package/src/components/ui/search.module.css +104 -0
  73. package/src/components/ui/search.tsx +202 -0
  74. package/src/lib/api-routes.ts +120 -0
  75. package/src/lib/config.ts +47 -0
  76. package/src/lib/get-llm-text.ts +10 -0
  77. package/src/lib/index.ts +2 -0
  78. package/src/lib/openapi.ts +188 -0
  79. package/src/lib/remark-unused-directives.ts +30 -0
  80. package/src/lib/schema.ts +99 -0
  81. package/src/lib/snippet-generators.ts +87 -0
  82. package/src/lib/source.ts +67 -0
  83. package/src/themes/default/Layout.module.css +81 -0
  84. package/src/themes/default/Layout.tsx +133 -0
  85. package/src/themes/default/Page.module.css +46 -0
  86. package/src/themes/default/Page.tsx +21 -0
  87. package/src/themes/default/Toc.module.css +48 -0
  88. package/src/themes/default/Toc.tsx +66 -0
  89. package/src/themes/default/font.ts +6 -0
  90. package/src/themes/default/index.ts +13 -0
  91. package/src/themes/paper/ChapterNav.module.css +71 -0
  92. package/src/themes/paper/ChapterNav.tsx +96 -0
  93. package/src/themes/paper/Layout.module.css +33 -0
  94. package/src/themes/paper/Layout.tsx +25 -0
  95. package/src/themes/paper/Page.module.css +174 -0
  96. package/src/themes/paper/Page.tsx +107 -0
  97. package/src/themes/paper/ReadingProgress.module.css +132 -0
  98. package/src/themes/paper/ReadingProgress.tsx +294 -0
  99. package/src/themes/paper/index.ts +8 -0
  100. package/src/themes/registry.ts +14 -0
  101. package/src/types/config.ts +69 -0
  102. package/src/types/content.ts +35 -0
  103. package/src/types/index.ts +3 -0
  104. package/src/types/theme.ts +22 -0
@@ -0,0 +1,294 @@
1
+ 'use client'
2
+
3
+ import { cx } from 'class-variance-authority'
4
+ import { useCallback, useEffect, useMemo, useRef, useState } from 'react'
5
+ import type { TocItem } from '@/types'
6
+ import styles from './ReadingProgress.module.css'
7
+
8
+ interface Heading {
9
+ title: string
10
+ level: number
11
+ id: string
12
+ url: string
13
+ yPosition: number
14
+ }
15
+
16
+ const ARTICLE_SELECTOR = '[data-article-content]'
17
+ const TICK_HEIGHT = 20
18
+ const NAV_HEIGHT = 60
19
+
20
+ function calculateTickBounds(containerHeight: number) {
21
+ const numTicks = Math.floor(containerHeight / TICK_HEIGHT) + 1
22
+ const maxPosition = (numTicks - 1) * TICK_HEIGHT
23
+ return { numTicks, maxPosition }
24
+ }
25
+
26
+ function snapToTick(value: number, maxPosition: number): number {
27
+ const snapped = Math.round(value / TICK_HEIGHT) * TICK_HEIGHT
28
+ return Math.max(0, Math.min(snapped, maxPosition))
29
+ }
30
+
31
+ function resolveOverlaps(headings: Heading[], maxPosition: number): Heading[] {
32
+ if (headings.length <= 1) return headings
33
+
34
+ const resolved: Heading[] = []
35
+ let lastUsedPos = -TICK_HEIGHT
36
+
37
+ for (const heading of headings) {
38
+ let newPos = heading.yPosition
39
+ if (newPos <= lastUsedPos) {
40
+ newPos = lastUsedPos + TICK_HEIGHT
41
+ }
42
+ resolved.push({ ...heading, yPosition: newPos })
43
+ lastUsedPos = newPos
44
+ }
45
+
46
+ // Backward pass: clamp-and-shift to prevent overlapping positions
47
+ for (let i = resolved.length - 1; i >= 0; i--) {
48
+ const maxAllowed =
49
+ i === resolved.length - 1
50
+ ? maxPosition
51
+ : resolved[i + 1].yPosition - TICK_HEIGHT
52
+
53
+ const clampedPos = Math.max(0, maxAllowed)
54
+ if (resolved[i].yPosition > clampedPos) {
55
+ resolved[i] = { ...resolved[i], yPosition: clampedPos }
56
+ for (let j = i - 1; j >= 0; j--) {
57
+ const upperBound = resolved[j + 1].yPosition - TICK_HEIGHT
58
+ if (resolved[j].yPosition > upperBound) {
59
+ resolved[j] = { ...resolved[j], yPosition: Math.max(0, upperBound) }
60
+ } else {
61
+ break
62
+ }
63
+ }
64
+ }
65
+ }
66
+
67
+ return resolved
68
+ }
69
+
70
+ interface ReadingProgressProps {
71
+ items: TocItem[]
72
+ }
73
+
74
+ export function ReadingProgress({ items }: ReadingProgressProps) {
75
+ const [headings, setHeadings] = useState<Heading[]>([])
76
+ const [containerHeight, setContainerHeight] = useState<number>(0)
77
+ const [ready, setReady] = useState<boolean>(false)
78
+ const [isScrollable, setIsScrollable] = useState<boolean>(true)
79
+ const containerRef = useRef<HTMLDivElement>(null)
80
+ const scrollMarkerRef = useRef<HTMLDivElement>(null)
81
+ const scrollPosRef = useRef<number>(0)
82
+
83
+ const { numTicks, maxPosition } = useMemo(
84
+ () => calculateTickBounds(containerHeight),
85
+ [containerHeight]
86
+ )
87
+
88
+ const recalcHeadings = useCallback(() => {
89
+ const article = document.querySelector(ARTICLE_SELECTOR)
90
+ const container = containerRef.current
91
+ if (!article || !container || !items.length) return
92
+
93
+ const articleBox = article.getBoundingClientRect()
94
+ const containerBox = container.getBoundingClientRect()
95
+ const articleTop = articleBox.top + window.scrollY
96
+
97
+ const hasScroll = articleBox.height > window.innerHeight
98
+ setIsScrollable(hasScroll)
99
+ setContainerHeight(containerBox.height)
100
+
101
+ const { maxPosition: maxPos } = calculateTickBounds(containerBox.height)
102
+
103
+ const mapped = items
104
+ .map((tocItem) => {
105
+ const id = tocItem.url.startsWith('#') ? tocItem.url.slice(1) : tocItem.url
106
+ const node = document.getElementById(id)
107
+ if (!node) return null
108
+
109
+ const { top } = node.getBoundingClientRect()
110
+ const headingPosInArticle = top + window.scrollY - articleTop
111
+ const progress = headingPosInArticle / articleBox.height
112
+ const rawY = progress * maxPos
113
+ const yPos = snapToTick(rawY, maxPos)
114
+
115
+ return {
116
+ title: tocItem.title,
117
+ level: tocItem.depth,
118
+ id,
119
+ url: tocItem.url,
120
+ yPosition: yPos,
121
+ }
122
+ })
123
+ .filter((item): item is Heading => item !== null)
124
+
125
+ const resolvedItems = resolveOverlaps(mapped, maxPos)
126
+ setHeadings(resolvedItems)
127
+ }, [items])
128
+
129
+ // Imperative DOM updates to avoid React re-render on every scroll event
130
+ const handleScroll = useCallback(() => {
131
+ const article = document.querySelector(ARTICLE_SELECTOR)
132
+ const container = containerRef.current
133
+ const scrollMarker = scrollMarkerRef.current
134
+ if (!article || !container || !scrollMarker) return
135
+
136
+ const { top, height } = article.getBoundingClientRect()
137
+ const { height: cHeight } = container.getBoundingClientRect()
138
+ const viewportHeight = window.innerHeight
139
+ const { maxPosition: maxPos } = calculateTickBounds(cHeight)
140
+
141
+ let newScrollPos: number
142
+ if (top > 0) {
143
+ newScrollPos = 0
144
+ } else {
145
+ const scrolled = Math.abs(top)
146
+ const scrollRange = height - viewportHeight
147
+ const progress = scrollRange > 0 ? Math.min(1, scrolled / scrollRange) : 0
148
+ const rawPos = progress * maxPos
149
+ newScrollPos = snapToTick(rawPos, maxPos)
150
+ }
151
+
152
+ const prevScrollPos = scrollPosRef.current
153
+ if (newScrollPos !== prevScrollPos) {
154
+ scrollPosRef.current = newScrollPos
155
+ scrollMarker.style.top = `${newScrollPos}px`
156
+
157
+ const textEl = scrollMarker.querySelector('[data-scroll-text]')
158
+ if (textEl) {
159
+ textEl.textContent = (maxPos > 0 ? newScrollPos / maxPos : 0).toFixed(2)
160
+ }
161
+
162
+ const tickLines = container.querySelectorAll('[data-tick-line]')
163
+ tickLines.forEach((tick) => {
164
+ const tickPos = Number(tick.getAttribute('data-tick-pos'))
165
+ if (tickPos < newScrollPos) {
166
+ tick.classList.remove(styles.tickLineAfter)
167
+ tick.classList.add(styles.tickLineBefore)
168
+ } else {
169
+ tick.classList.remove(styles.tickLineBefore)
170
+ tick.classList.add(styles.tickLineAfter)
171
+ }
172
+ })
173
+ }
174
+ }, [])
175
+
176
+ useEffect(() => {
177
+ recalcHeadings()
178
+ handleScroll()
179
+ setReady(true)
180
+
181
+ const article = document.querySelector(ARTICLE_SELECTOR)
182
+ let ro: ResizeObserver | undefined
183
+ if (article) {
184
+ ro = new ResizeObserver(() => {
185
+ recalcHeadings()
186
+ handleScroll()
187
+ })
188
+ ro.observe(article)
189
+ }
190
+
191
+ window.addEventListener('resize', recalcHeadings)
192
+ window.addEventListener('scroll', handleScroll, { passive: true })
193
+
194
+ return () => {
195
+ ro?.disconnect()
196
+ window.removeEventListener('resize', recalcHeadings)
197
+ window.removeEventListener('scroll', handleScroll)
198
+ }
199
+ }, [recalcHeadings, handleScroll])
200
+
201
+ const scrollToTick = (y: number): void => {
202
+ const article = document.querySelector(ARTICLE_SELECTOR)
203
+ if (!article || maxPosition === 0) return
204
+
205
+ const articleBox = article.getBoundingClientRect()
206
+ const articleTop = articleBox.top + window.scrollY
207
+ const progress = y / maxPosition
208
+ const articlePos = progress * articleBox.height
209
+ const targetScroll = articleTop + articlePos - NAV_HEIGHT
210
+
211
+ window.scrollTo({ top: Math.max(0, targetScroll), behavior: 'smooth' })
212
+ }
213
+
214
+ const scrollToHeading = (id: string): void => {
215
+ const element = document.getElementById(id)
216
+ if (!element) return
217
+
218
+ const elementTop = element.getBoundingClientRect().top + window.scrollY
219
+ window.scrollTo({ top: Math.max(0, elementTop - NAV_HEIGHT), behavior: 'smooth' })
220
+ }
221
+
222
+ const ticks = useMemo(
223
+ () => Array.from({ length: numTicks }, (_, i) => i * TICK_HEIGHT),
224
+ [numTicks]
225
+ )
226
+
227
+ if (!isScrollable || ticks.length < 2) {
228
+ return <div ref={containerRef} className={styles.container} />
229
+ }
230
+
231
+ return (
232
+ <div ref={containerRef} className={styles.container}>
233
+ <div className={styles.inner}>
234
+ {ticks.map((y, i) => (
235
+ <div key={`tick-${i}`} style={{ top: `${y}px` }} className={styles.tickContainer}>
236
+ <div data-tick-line data-tick-pos={y} className={cx(styles.tickLine, styles.tickLineAfter)} />
237
+ <div className={styles.tickClickable} onClick={() => scrollToTick(y)} />
238
+ </div>
239
+ ))}
240
+
241
+ {headings.map((h, i) => (
242
+ <div key={h.id || i} className={styles.headingContainer}>
243
+ <div
244
+ className={styles.headingLabel}
245
+ style={{
246
+ top: `${h.yPosition - 6}px`,
247
+ right: '24px',
248
+ zIndex: h.level < 4 ? 10 : 0,
249
+ transitionDelay: `${50 * i}ms`,
250
+ }}
251
+ >
252
+ <a
253
+ href={h.url}
254
+ className={styles.headingLink}
255
+ onClick={(e) => {
256
+ e.preventDefault()
257
+ e.stopPropagation()
258
+ scrollToHeading(h.id)
259
+ }}
260
+ >
261
+ {h.title}
262
+ </a>
263
+ </div>
264
+ </div>
265
+ ))}
266
+
267
+ {headings.map((h, i) => (
268
+ <div
269
+ key={`line-${i}`}
270
+ className={styles.connectingLine}
271
+ style={{
272
+ top: `${h.yPosition}px`,
273
+ width: `${Math.max(4, (3 - h.level) * 4 + 12)}px`,
274
+ }}
275
+ />
276
+ ))}
277
+
278
+ <div
279
+ ref={scrollMarkerRef}
280
+ className={cx(
281
+ styles.scrollMarkerContainer,
282
+ ready ? styles.scrollMarkerContainerReady : styles.scrollMarkerContainerNotReady
283
+ )}
284
+ style={{ top: '0px' }}
285
+ >
286
+ <div className={styles.scrollMarkerLine} />
287
+ <span data-scroll-text className={styles.scrollMarkerText}>
288
+ 0.00
289
+ </span>
290
+ </div>
291
+ </div>
292
+ </div>
293
+ )
294
+ }
@@ -0,0 +1,8 @@
1
+ import { Layout } from './Layout'
2
+ import { Page } from './Page'
3
+ import type { Theme } from '@/types'
4
+
5
+ export const paperTheme: Theme = {
6
+ Layout,
7
+ Page,
8
+ }
@@ -0,0 +1,14 @@
1
+ import type { Theme } from '@/types'
2
+ import { defaultTheme } from './default'
3
+ import { paperTheme } from './paper'
4
+
5
+ const themes: Record<string, Theme> = {
6
+ default: defaultTheme,
7
+ paper: paperTheme,
8
+ }
9
+
10
+ export function getTheme(name?: string): Theme {
11
+ if (!name || !themes[name]) return defaultTheme
12
+
13
+ return themes[name]
14
+ }
@@ -0,0 +1,69 @@
1
+ export interface ChronicleConfig {
2
+ title: string
3
+ description?: string
4
+ logo?: LogoConfig
5
+ theme?: ThemeConfig
6
+ navigation?: NavigationConfig
7
+ search?: SearchConfig
8
+ footer?: FooterConfig
9
+ api?: ApiConfig[]
10
+ llms?: LlmsConfig
11
+ }
12
+
13
+ export interface LlmsConfig {
14
+ enabled?: boolean
15
+ }
16
+
17
+ export interface ApiConfig {
18
+ name: string
19
+ spec: string
20
+ basePath: string
21
+ server: ApiServerConfig
22
+ auth?: ApiAuthConfig
23
+ }
24
+
25
+ export interface ApiServerConfig {
26
+ url: string
27
+ description?: string
28
+ }
29
+
30
+ export interface ApiAuthConfig {
31
+ type: string
32
+ header: string
33
+ placeholder?: string
34
+ }
35
+
36
+ export interface LogoConfig {
37
+ light?: string
38
+ dark?: string
39
+ }
40
+
41
+ export interface ThemeConfig {
42
+ name: 'default' | 'paper'
43
+ colors?: Record<string, string>
44
+ }
45
+
46
+ export interface NavigationConfig {
47
+ links?: NavLink[]
48
+ social?: SocialLink[]
49
+ }
50
+
51
+ export interface NavLink {
52
+ label: string
53
+ href: string
54
+ }
55
+
56
+ export interface SocialLink {
57
+ type: 'github' | 'twitter' | 'discord' | string
58
+ href: string
59
+ }
60
+
61
+ export interface SearchConfig {
62
+ enabled?: boolean
63
+ placeholder?: string
64
+ }
65
+
66
+ export interface FooterConfig {
67
+ copyright?: string
68
+ links?: NavLink[]
69
+ }
@@ -0,0 +1,35 @@
1
+ import type { ReactNode } from 'react'
2
+
3
+ export interface Frontmatter {
4
+ title: string
5
+ description?: string
6
+ order?: number
7
+ icon?: string
8
+ }
9
+
10
+ export interface Page {
11
+ slug: string[]
12
+ frontmatter: Frontmatter
13
+ content: ReactNode
14
+ toc: TocItem[]
15
+ }
16
+
17
+ export interface TocItem {
18
+ title: string
19
+ url: string
20
+ depth: number
21
+ }
22
+
23
+ export interface PageTreeItem {
24
+ type: 'page' | 'folder' | 'separator'
25
+ name: string
26
+ url?: string
27
+ order?: number
28
+ icon?: string
29
+ children?: PageTreeItem[]
30
+ }
31
+
32
+ export interface PageTree {
33
+ name: string
34
+ children: PageTreeItem[]
35
+ }
@@ -0,0 +1,3 @@
1
+ export * from './config'
2
+ export * from './content'
3
+ export * from './theme'
@@ -0,0 +1,22 @@
1
+ import type { ReactNode } from 'react'
2
+ import type { ChronicleConfig } from './config'
3
+ import type { Page, PageTree } from './content'
4
+
5
+ export interface ThemeLayoutProps {
6
+ children: ReactNode
7
+ config: ChronicleConfig
8
+ tree: PageTree
9
+ classNames?: { layout?: string; body?: string; sidebar?: string; content?: string }
10
+ }
11
+
12
+ export interface ThemePageProps {
13
+ page: Page
14
+ config: ChronicleConfig
15
+ tree: PageTree
16
+ }
17
+
18
+ export interface Theme {
19
+ Layout: React.ComponentType<ThemeLayoutProps>
20
+ Page: React.ComponentType<ThemePageProps>
21
+ className?: string
22
+ }