@raystack/chronicle 0.11.2 → 0.11.3

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@raystack/chronicle",
3
- "version": "0.11.2",
3
+ "version": "0.11.3",
4
4
  "description": "Config-driven documentation framework",
5
5
  "license": "Apache-2.0",
6
6
  "type": "module",
@@ -1,10 +1,12 @@
1
1
  .dialogContent {
2
- border-radius: var(--rs-radius-4);
2
+ border-radius: var(--rs-radius-5);
3
3
  padding: 0px;
4
4
  width: 80%;
5
5
  max-width: 600px;
6
6
  position: fixed;
7
7
  top: 20%;
8
+ overflow: clip;
9
+ box-shadow: var(--rs-shadow-floating);
8
10
  }
9
11
 
10
12
  .input {
@@ -13,7 +15,8 @@
13
15
 
14
16
  .list {
15
17
  max-height: 400px;
16
- gap: var(--rs-space-3);
18
+ padding: 0 var(--rs-space-2);
19
+ gap: var(--rs-space-2);
17
20
  }
18
21
 
19
22
  .list :global([cmdk-group-heading]) {
@@ -25,35 +28,50 @@
25
28
  }
26
29
 
27
30
  .item {
28
- min-height: 40px;
29
- padding: var(--rs-space-3);
31
+ padding: var(--rs-space-5) var(--rs-space-4);
30
32
  gap: var(--rs-space-3);
31
- border-radius: var(--rs-radius-2);
33
+ border-radius: var(--rs-radius-3);
32
34
  cursor: pointer;
33
35
  }
34
36
 
35
-
36
37
  .item[data-selected="true"] {
37
38
  background: var(--rs-color-background-base-primary-hover);
38
39
  }
39
40
 
40
41
  .itemContent {
41
42
  display: flex;
42
- align-items: center;
43
- gap: 12px;
43
+ align-items: flex-start;
44
+ gap: var(--rs-space-3);
44
45
  flex: 1;
45
46
  }
46
47
 
47
- .sectionBadge {
48
- margin-left: auto;
49
- flex-shrink: 0;
50
- }
51
-
52
48
  .resultText {
53
49
  display: flex;
54
50
  flex-direction: column;
55
- gap: 2px;
51
+ gap: var(--rs-space-2);
56
52
  min-width: 0;
53
+ flex: 1;
54
+ }
55
+
56
+ .breadcrumb {
57
+ display: flex;
58
+ align-items: center;
59
+ gap: var(--rs-space-2);
60
+ }
61
+
62
+ .breadcrumbText {
63
+ font-family: var(--rs-font-body);
64
+ font-size: var(--rs-font-size-small);
65
+ font-weight: var(--rs-font-weight-medium);
66
+ line-height: var(--rs-line-height-small);
67
+ letter-spacing: var(--rs-letter-spacing-small);
68
+ color: var(--rs-color-foreground-base-primary);
69
+ white-space: nowrap;
70
+ }
71
+
72
+ .breadcrumbSeparator {
73
+ color: var(--rs-color-foreground-base-tertiary);
74
+ font-size: var(--rs-font-size-small);
57
75
  }
58
76
 
59
77
  .headingText {
@@ -64,10 +82,6 @@
64
82
  color: var(--rs-color-foreground-accent-primary-hover);
65
83
  }
66
84
 
67
- .separator {
68
- color: var(--rs-color-foreground-base-secondary);
69
- }
70
-
71
85
  .pageText {
72
86
  color: var(--rs-color-foreground-base-primary);
73
87
  }
@@ -77,14 +91,15 @@
77
91
  }
78
92
 
79
93
  .icon {
80
- width: 48px;
81
- height: 24px;
94
+ width: 16px;
95
+ height: 16px;
82
96
  color: var(--rs-color-foreground-base-secondary);
83
97
  flex-shrink: 0;
98
+ margin-top: 1px;
84
99
  }
85
100
 
86
101
  .itemContent :global([class*="badge-module"]) {
87
- min-width: 48px;
102
+ min-width: auto;
88
103
  justify-content: center;
89
104
  }
90
105
 
@@ -95,14 +110,15 @@
95
110
  .snippetText {
96
111
  font-size: var(--rs-font-size-mini);
97
112
  line-height: var(--rs-line-height-mini);
98
- color: var(--rs-color-foreground-base-tertiary);
113
+ letter-spacing: var(--rs-letter-spacing-mini);
114
+ color: var(--rs-color-foreground-base-secondary);
99
115
  overflow: hidden;
100
116
  text-overflow: ellipsis;
101
117
  white-space: nowrap;
102
118
  }
103
119
 
104
120
  .matchHighlight {
105
- color: var(--rs-color-foreground-accent-primary);
121
+ color: var(--rs-color-foreground-base-primary);
106
122
  font-weight: var(--rs-font-weight-medium);
107
123
  }
108
124
 
@@ -1,15 +1,18 @@
1
1
  import {
2
2
  DocumentIcon,
3
3
  HashtagIcon,
4
- MagnifyingGlassIcon
4
+ MagnifyingGlassIcon,
5
+ CodeBracketIcon,
6
+ ChevronRightIcon
5
7
  } from '@heroicons/react/24/outline';
6
- import { Badge, Command, IconButton, Text } from '@raystack/apsara';
8
+ import { Command, IconButton, Text } from '@raystack/apsara';
7
9
  import { keepPreviousData, useQuery } from '@tanstack/react-query';
8
10
  import { debounce } from 'lodash-es';
9
11
  import { useCallback, useEffect, useMemo, useState, type ChangeEvent } from 'react';
10
12
  import { useNavigate } from 'react-router';
11
13
  import { MethodBadge } from '@/components/api/method-badge';
12
14
  import { usePageContext } from '@/lib/page-context';
15
+ import { SearchMatchType } from '@/types';
13
16
  import styles from './search.module.css';
14
17
 
15
18
  interface SearchResult {
@@ -17,7 +20,7 @@ interface SearchResult {
17
20
  url: string;
18
21
  type: string;
19
22
  content: string;
20
- match?: 'title' | 'heading' | 'body';
23
+ match?: SearchMatchType;
21
24
  snippet?: string;
22
25
  section?: string;
23
26
  }
@@ -135,15 +138,7 @@ export function Search({ classNames }: SearchProps) {
135
138
  onClick={() => onSelect(result.url)}
136
139
  className={styles.item}
137
140
  >
138
- <div className={styles.itemContent}>
139
- {getResultIcon(result)}
140
- <Text className={styles.pageText}>
141
- <HighlightedText
142
- html={stripMethod(result.content)}
143
- />
144
- </Text>
145
- {result.section && <Badge size="small" className={styles.sectionBadge}>{result.section}</Badge>}
146
- </div>
141
+ <SearchResultItem result={result} query="" />
147
142
  </Command.Item>
148
143
  ))}
149
144
  </Command.Group>
@@ -156,25 +151,7 @@ export function Search({ classNames }: SearchProps) {
156
151
  onClick={() => onSelect(result.url)}
157
152
  className={styles.item}
158
153
  >
159
- <div className={styles.itemContent}>
160
- {getResultIcon(result)}
161
- <div className={styles.resultText}>
162
- <Text className={styles.pageText}>
163
- <HighlightQuery text={stripMethod(result.content)} query={search} />
164
- </Text>
165
- {result.snippet && result.match === 'heading' && (
166
- <Text className={styles.snippetText}>
167
- # <HighlightQuery text={result.snippet} query={search} />
168
- </Text>
169
- )}
170
- {result.snippet && result.match === 'body' && (
171
- <Text className={styles.snippetText}>
172
- <HighlightQuery text={result.snippet} query={search} />
173
- </Text>
174
- )}
175
- </div>
176
- {result.section && <Badge size="small" className={styles.sectionBadge}>{result.section}</Badge>}
177
- </div>
154
+ <SearchResultItem result={result} query={search} />
178
155
  </Command.Item>
179
156
  ))}
180
157
  </Command.Content>
@@ -185,6 +162,38 @@ export function Search({ classNames }: SearchProps) {
185
162
  );
186
163
  }
187
164
 
165
+ function SearchResultItem({ result, query }: { result: SearchResult; query: string }) {
166
+ const method = extractMethod(result.content);
167
+ const title = stripMethod(result.content);
168
+
169
+ return (
170
+ <div className={styles.itemContent}>
171
+ {getResultIcon(result)}
172
+ <div className={styles.resultText}>
173
+ <div className={styles.breadcrumb}>
174
+ {result.section && (
175
+ <>
176
+ <span className={styles.breadcrumbText}>{result.section}</span>
177
+ <ChevronRightIcon width={12} height={12} className={styles.breadcrumbSeparator} />
178
+ </>
179
+ )}
180
+ {method && <MethodBadge method={method} size='micro' />}
181
+ <Text className={styles.breadcrumbText}>
182
+ {query ? <HighlightQuery text={title} query={query} /> : <HighlightedText html={title} />}
183
+ </Text>
184
+ </div>
185
+ {result.snippet && (
186
+ <Text className={styles.snippetText}>
187
+ {query
188
+ ? <HighlightQuery text={result.snippet} query={query} />
189
+ : result.snippet}
190
+ </Text>
191
+ )}
192
+ </div>
193
+ </div>
194
+ );
195
+ }
196
+
188
197
  function deduplicateByUrl(results: SearchResult[]): SearchResult[] {
189
198
  const seen = new Set<string>();
190
199
  return results.filter(r => {
@@ -233,15 +242,13 @@ function HighlightQuery({ text, query }: { text: string; query: string }) {
233
242
  }
234
243
 
235
244
  function getResultIcon(result: SearchResult): React.ReactNode {
236
- if (!result.url.startsWith('/apis/')) {
237
- return result.type === 'page' ? (
238
- <DocumentIcon className={styles.icon} />
239
- ) : (
240
- <HashtagIcon className={styles.icon} />
241
- );
245
+ if (result.url.startsWith('/apis/')) {
246
+ return <CodeBracketIcon className={styles.icon} />;
242
247
  }
243
- const method = extractMethod(result.content);
244
- return method ? <MethodBadge method={method} size='micro' /> : null;
248
+ if (result.match === SearchMatchType.Heading) {
249
+ return <HashtagIcon className={styles.icon} />;
250
+ }
251
+ return <DocumentIcon className={styles.icon} />;
245
252
  }
246
253
 
247
254
  function getPageTitle(url: string): string {
@@ -7,6 +7,7 @@ import { getApiConfigsForVersion, loadConfig } from '@/lib/config';
7
7
  import { loadApiSpecs } from '@/lib/openapi';
8
8
  import { extractFrontmatter, getPageSearchContent, getPagesForVersion } from '@/lib/source';
9
9
  import { LATEST_CONTEXT, type VersionContext } from '@/lib/version-source';
10
+ import { SearchResultType, SearchMatchType } from '@/types';
10
11
 
11
12
  interface SearchDocument {
12
13
  id: string;
@@ -14,7 +15,7 @@ interface SearchDocument {
14
15
  title: string;
15
16
  headings: string;
16
17
  body: string;
17
- type: 'page' | 'api';
18
+ type: SearchResultType;
18
19
  section: string;
19
20
  }
20
21
 
@@ -104,7 +105,7 @@ async function buildDocs(ctx: VersionContext): Promise<SearchDocument[]> {
104
105
  title: fm.title,
105
106
  headings,
106
107
  body: [fm.description ?? '', body].join(' '),
107
- type: 'page',
108
+ type: SearchResultType.Page,
108
109
  section: entry?.label ?? dir ?? '',
109
110
  });
110
111
  }
@@ -128,7 +129,7 @@ async function buildDocs(ctx: VersionContext): Promise<SearchDocument[]> {
128
129
  title: `${method.toUpperCase()} ${op.summary ?? opId}`,
129
130
  headings: op.summary ?? opId,
130
131
  body: [op.description ?? '', pathStr, method.toUpperCase()].join(' '),
131
- type: 'api',
132
+ type: SearchResultType.Api,
132
133
  section: spec.name,
133
134
  });
134
135
  }
@@ -144,9 +145,9 @@ function findMatch(
144
145
  title: string,
145
146
  headings: string,
146
147
  body: string,
147
- ): { match: 'title' | 'heading' | 'body'; snippet: string; slug?: string } {
148
+ ): { match: SearchMatchType; snippet: string; slug?: string } {
148
149
  if (title.toLowerCase().includes(query)) {
149
- return { match: 'title', snippet: title };
150
+ return { match: SearchMatchType.Title, snippet: title };
150
151
  }
151
152
 
152
153
  const slugger = new GithubSlugger();
@@ -154,7 +155,7 @@ function findMatch(
154
155
  for (const h of headingList) {
155
156
  const slug = slugger.slug(h);
156
157
  if (h.toLowerCase().includes(query)) {
157
- return { match: 'heading', snippet: h, slug };
158
+ return { match: SearchMatchType.Heading, snippet: h, slug };
158
159
  }
159
160
  }
160
161
 
@@ -163,10 +164,10 @@ function findMatch(
163
164
  const start = Math.max(0, idx - 40);
164
165
  const end = Math.min(body.length, idx + query.length + 80);
165
166
  const snippet = (start > 0 ? '...' : '') + body.slice(start, end).trim() + (end < body.length ? '...' : '');
166
- return { match: 'body', snippet };
167
+ return { match: SearchMatchType.Body, snippet };
167
168
  }
168
169
 
169
- return { match: 'title', snippet: title };
170
+ return { match: SearchMatchType.Title, snippet: title };
170
171
  }
171
172
 
172
173
  function resolveCtx(tag: string | null): VersionContext {
@@ -218,8 +219,8 @@ export default defineHandler(async event => {
218
219
  const queryLower = query.toLowerCase();
219
220
  return Response.json((result.rows ?? []).map(r => {
220
221
  const { match, snippet, slug } = findMatch(queryLower, r.title as string, r.headings as string, r.body as string);
221
- const id = match === 'heading' && slug ? `${r.id}#${slug}` : r.id as string;
222
- const url = match === 'heading' && slug ? `${r.url}#${slug}` : r.url as string;
222
+ const id = match === SearchMatchType.Heading && slug ? `${r.id}#${slug}` : r.id as string;
223
+ const url = match === SearchMatchType.Heading && slug ? `${r.url}#${slug}` : r.url as string;
223
224
  return {
224
225
  id,
225
226
  url,
@@ -41,6 +41,11 @@ function generateApiMarkdown(
41
41
  lines.push(operation.description)
42
42
  lines.push('')
43
43
  }
44
+ if (operation.externalDocs?.url) {
45
+ const label = operation.externalDocs.description || 'external documentation'
46
+ lines.push(`Read more about this operation in the [${label}](${operation.externalDocs.url}).`)
47
+ lines.push('')
48
+ }
44
49
  lines.push(`\`${method}\` \`${path}\``)
45
50
  lines.push('')
46
51
 
@@ -171,11 +171,15 @@
171
171
  }
172
172
 
173
173
  .groupItems {
174
- padding-left: var(--rs-space-4);
174
+ padding-left: 0;
175
175
  padding-bottom: var(--rs-space-3);
176
176
  gap: 0;
177
177
  }
178
178
 
179
+ .navGroup:not([data-depth='0']) .groupItems {
180
+ padding-left: var(--rs-space-4);
181
+ }
182
+
179
183
  .navGroup {
180
184
  margin-top: 0;
181
185
  }
@@ -28,8 +28,11 @@ import type { ThemeLayoutProps } from '@/types';
28
28
  import styles from './Layout.module.css';
29
29
  import { OpenInAI } from './OpenInAI';
30
30
  import { SidebarLogo } from './SidebarLogo';
31
+
31
32
  import { VersionSwitcher } from './VersionSwitcher';
32
33
 
34
+ const MAX_SIDEBAR_DEPTH = 3;
35
+
33
36
  const iconMap: Record<string, React.ReactNode> = {
34
37
  'rectangle-stack': <RectangleStackIcon width={16} height={16} />,
35
38
  'method-get': <MethodBadge method='GET' size='micro' />,
@@ -249,7 +252,7 @@ function SidebarNode({
249
252
  }
250
253
 
251
254
  if (item.type === 'folder') {
252
- if (depth > 1) return null;
255
+ if (depth > MAX_SIDEBAR_DEPTH) return null;
253
256
  const icon = typeof item.icon === 'string' ? iconMap[item.icon] : item.icon;
254
257
  const hasActiveChild = hasActiveDescendant(item, pathname);
255
258
  return (
@@ -258,7 +261,7 @@ function SidebarNode({
258
261
  data-depth={depth}
259
262
  label={item.name?.toString() ?? ''}
260
263
  leadingIcon={icon ?? undefined}
261
- collapsible={depth === 1}
264
+ collapsible={depth >= 1}
262
265
  defaultOpen={hasActiveChild}
263
266
  classNames={{
264
267
  items: styles.groupItems,
@@ -39,15 +39,22 @@
39
39
  .content h1 {
40
40
  font-size: var(--rs-font-size-t4);
41
41
  line-height: var(--rs-line-height-t4);
42
- margin-top: 0;
43
- margin-bottom: var(--rs-space-10);
42
+ margin: var(--rs-space-10) 0;
44
43
  }
45
44
 
46
45
  .content h2 {
47
46
  font-size: var(--rs-font-size-t3);
48
47
  line-height: var(--rs-line-height-t3);
49
- margin-top: var(--rs-space-8);
50
- margin-bottom: var(--rs-space-8);
48
+ margin-top: var(--rs-space-10);
49
+ margin-bottom: var(--rs-space-7);
50
+ }
51
+
52
+ .content p + h2,
53
+ .content ul + h2,
54
+ .content ol + h2,
55
+ .content div + h2,
56
+ .content table + h2 {
57
+ margin-top: var(--rs-space-13);
51
58
  }
52
59
 
53
60
  .content h3 {
@@ -77,6 +84,7 @@
77
84
  font-style: normal;
78
85
  font-weight: var(--rs-font-weight-regular);
79
86
  line-height: 171.429%;
87
+ margin-bottom: var(--rs-space-7);
80
88
  }
81
89
 
82
90
  .content ul,
@@ -90,6 +98,15 @@
90
98
  margin: var(--rs-space-2) 0;
91
99
  }
92
100
 
101
+ .content table td {
102
+ font-size: var(--rs-font-size-regular);
103
+ }
104
+
105
+ .content table th {
106
+ font-size: var(--rs-font-size-regular);
107
+ font-weight: var(--rs-font-weight-medium);
108
+ }
109
+
93
110
  .content a {
94
111
  font-size: inherit;
95
112
  }
@@ -101,6 +118,7 @@
101
118
  .content img {
102
119
  max-width: 100%;
103
120
  height: auto;
121
+ margin: var(--rs-space-7) 0;
104
122
  }
105
123
 
106
124
  .content table {
@@ -213,6 +213,11 @@
213
213
  text-overflow: unset;
214
214
  word-wrap: break-word;
215
215
  vertical-align: top;
216
+ font-size: var(--rs-font-size-regular);
217
+ }
218
+
219
+ .content table th {
220
+ font-weight: var(--rs-font-weight-medium);
216
221
  }
217
222
 
218
223
  .content a {
@@ -24,6 +24,21 @@ export interface PageNav {
24
24
  next: PageNavLink | null
25
25
  }
26
26
 
27
+ export const SearchResultType = {
28
+ Page: 'page',
29
+ Api: 'api',
30
+ } as const;
31
+
32
+ export type SearchResultType = (typeof SearchResultType)[keyof typeof SearchResultType];
33
+
34
+ export const SearchMatchType = {
35
+ Title: 'title',
36
+ Heading: 'heading',
37
+ Body: 'body',
38
+ } as const;
39
+
40
+ export type SearchMatchType = (typeof SearchMatchType)[keyof typeof SearchMatchType];
41
+
27
42
  export interface Page extends PageNav {
28
43
  slug: string[]
29
44
  frontmatter: Frontmatter