specra 0.2.7 → 0.2.8
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/config/svelte-config.js +32 -1
- package/dist/components/docs/CategoryIndex.svelte +5 -1
- package/dist/components/docs/MdxContent.svelte +3 -1
- package/dist/components/docs/SearchModal.svelte +4 -3
- package/dist/components/docs/SidebarMenuItems.svelte +23 -1
- package/dist/components/docs/api/ApiParams.svelte +94 -36
- package/dist/components/ui/Button.svelte +30 -11
- package/dist/components/ui/Button.svelte.d.ts +9 -3
- package/dist/mdx.js +77 -1
- package/dist/parsers/openapi-parser.js +6 -1
- package/package.json +1 -1
package/config/svelte-config.js
CHANGED
|
@@ -17,6 +17,8 @@ import remarkMath from 'remark-math'
|
|
|
17
17
|
import rehypeSlug from 'rehype-slug'
|
|
18
18
|
import rehypeKatex from 'rehype-katex'
|
|
19
19
|
import rehypeRaw from 'rehype-raw'
|
|
20
|
+
import fs from 'fs'
|
|
21
|
+
import path from 'path'
|
|
20
22
|
|
|
21
23
|
/**
|
|
22
24
|
* Get mdsvex preprocessor config with all Specra remark/rehype plugins
|
|
@@ -42,11 +44,33 @@ export function specraMdsvexConfig(options = {}) {
|
|
|
42
44
|
}
|
|
43
45
|
}
|
|
44
46
|
|
|
47
|
+
/**
|
|
48
|
+
* Scan the docs/ directory and return prerender entries for all version root pages.
|
|
49
|
+
* This ensures adapter-static discovers and prerenders every version, not just the active one.
|
|
50
|
+
*/
|
|
51
|
+
function discoverVersionEntries(docsDir = path.join(process.cwd(), 'docs')) {
|
|
52
|
+
const entries = ['/']
|
|
53
|
+
try {
|
|
54
|
+
if (!fs.existsSync(docsDir)) return entries
|
|
55
|
+
|
|
56
|
+
const items = fs.readdirSync(docsDir, { withFileTypes: true })
|
|
57
|
+
for (const item of items) {
|
|
58
|
+
if (item.isDirectory() && /^v\d/.test(item.name)) {
|
|
59
|
+
entries.push(`/docs/${item.name}`)
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
} catch {
|
|
63
|
+
// Ignore errors — fall back to just '/'
|
|
64
|
+
}
|
|
65
|
+
return entries
|
|
66
|
+
}
|
|
67
|
+
|
|
45
68
|
/**
|
|
46
69
|
* Create a full SvelteKit config with Specra defaults
|
|
47
70
|
*/
|
|
48
71
|
export function specraConfig(options = {}) {
|
|
49
72
|
const { vitePreprocess } = options.vitePreprocess || {}
|
|
73
|
+
const userPrerender = options.kit?.prerender || {}
|
|
50
74
|
|
|
51
75
|
return {
|
|
52
76
|
extensions: ['.svelte', '.md', '.svx', '.mdx'],
|
|
@@ -55,7 +79,14 @@ export function specraConfig(options = {}) {
|
|
|
55
79
|
mdsvex(specraMdsvexConfig(options.mdsvex || {}))
|
|
56
80
|
],
|
|
57
81
|
kit: {
|
|
58
|
-
...
|
|
82
|
+
...options.kit,
|
|
83
|
+
prerender: {
|
|
84
|
+
handleHttpError: 'warn',
|
|
85
|
+
handleMissingId: 'warn',
|
|
86
|
+
handleUnseenRoutes: 'warn',
|
|
87
|
+
entries: discoverVersionEntries(),
|
|
88
|
+
...userPrerender,
|
|
89
|
+
}
|
|
59
90
|
}
|
|
60
91
|
}
|
|
61
92
|
}
|
|
@@ -55,8 +55,12 @@
|
|
|
55
55
|
const baseUrl = $derived(
|
|
56
56
|
product && product !== '_default_'
|
|
57
57
|
? `/docs/${product}`
|
|
58
|
-
:
|
|
58
|
+
: '/docs'
|
|
59
59
|
);
|
|
60
|
+
|
|
61
|
+
// Note: We always use '/docs' as the base for non-product routes.
|
|
62
|
+
// Do NOT use config.site?.baseUrl here — that field (e.g. "/") refers to
|
|
63
|
+
// the site root, not the docs route prefix.
|
|
60
64
|
</script>
|
|
61
65
|
|
|
62
66
|
<div class="space-y-8">
|
|
@@ -26,7 +26,9 @@
|
|
|
26
26
|
{#if Comp}
|
|
27
27
|
{#if node.children && node.children.length > 0}
|
|
28
28
|
<svelte:component this={Comp} {...node.props}>
|
|
29
|
-
|
|
29
|
+
{#snippet children()}
|
|
30
|
+
<MdxContent nodes={node.children} {components} />
|
|
31
|
+
{/snippet}
|
|
30
32
|
</svelte:component>
|
|
31
33
|
{:else}
|
|
32
34
|
<svelte:component this={Comp} {...node.props} />
|
|
@@ -28,7 +28,8 @@
|
|
|
28
28
|
let inputEl = $state<HTMLInputElement | null>(null);
|
|
29
29
|
let debounceTimer: ReturnType<typeof setTimeout> | null = null;
|
|
30
30
|
|
|
31
|
-
const
|
|
31
|
+
const siteBaseUrl = $derived(config.site?.baseUrl || '/');
|
|
32
|
+
const docsBaseUrl = '/docs';
|
|
32
33
|
|
|
33
34
|
$effect(() => {
|
|
34
35
|
if (isOpen && inputEl) {
|
|
@@ -75,7 +76,7 @@
|
|
|
75
76
|
debounceTimer = setTimeout(async () => {
|
|
76
77
|
try {
|
|
77
78
|
const response = await fetch(
|
|
78
|
-
`${
|
|
79
|
+
`${siteBaseUrl.replace(/\/$/, '')}/api/search?q=${encodeURIComponent(value.trim())}`
|
|
79
80
|
);
|
|
80
81
|
if (response.ok) {
|
|
81
82
|
const data = await response.json();
|
|
@@ -93,7 +94,7 @@
|
|
|
93
94
|
|
|
94
95
|
function navigateToResult(result: SearchResult) {
|
|
95
96
|
const version = result.version || config.site?.activeVersion || 'v1';
|
|
96
|
-
const url = `${
|
|
97
|
+
const url = `${docsBaseUrl}/${version}/${result.slug}`;
|
|
97
98
|
goto(url);
|
|
98
99
|
onClose();
|
|
99
100
|
}
|
|
@@ -57,7 +57,28 @@
|
|
|
57
57
|
: `/docs/${version}`
|
|
58
58
|
);
|
|
59
59
|
|
|
60
|
-
|
|
60
|
+
const STORAGE_KEY = 'specra-sidebar-collapsed';
|
|
61
|
+
|
|
62
|
+
function loadCollapsedState(): Record<string, boolean> {
|
|
63
|
+
if (typeof window === 'undefined') return {};
|
|
64
|
+
try {
|
|
65
|
+
const stored = localStorage.getItem(STORAGE_KEY);
|
|
66
|
+
return stored ? JSON.parse(stored) : {};
|
|
67
|
+
} catch {
|
|
68
|
+
return {};
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
function saveCollapsedState(state: Record<string, boolean>) {
|
|
73
|
+
if (typeof window === 'undefined') return;
|
|
74
|
+
try {
|
|
75
|
+
localStorage.setItem(STORAGE_KEY, JSON.stringify(state));
|
|
76
|
+
} catch {
|
|
77
|
+
// localStorage unavailable
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
let collapsed: Record<string, boolean> = $state(loadCollapsedState());
|
|
61
82
|
let pathname = $derived($page.url.pathname.replace(/\/$/, ''));
|
|
62
83
|
|
|
63
84
|
// Filter docs by active tab group if tab groups are configured
|
|
@@ -180,6 +201,7 @@
|
|
|
180
201
|
|
|
181
202
|
function toggleSection(section: string) {
|
|
182
203
|
collapsed = { ...collapsed, [section]: !collapsed[section] };
|
|
204
|
+
saveCollapsedState(collapsed);
|
|
183
205
|
}
|
|
184
206
|
|
|
185
207
|
function isActiveInGroup(group: SidebarGroup): boolean {
|
|
@@ -13,62 +13,54 @@
|
|
|
13
13
|
}
|
|
14
14
|
|
|
15
15
|
let { title = 'Parameters', params }: Props = $props();
|
|
16
|
+
|
|
17
|
+
let filteredParams = $derived(
|
|
18
|
+
Array.isArray(params) ? params.filter(p => p && p.name) : []
|
|
19
|
+
);
|
|
16
20
|
</script>
|
|
17
21
|
|
|
18
|
-
{#if
|
|
22
|
+
{#if filteredParams.length > 0}
|
|
19
23
|
<div class="mb-6">
|
|
20
24
|
<h4 class="text-sm font-semibold text-foreground mb-3">{title}</h4>
|
|
21
|
-
<div class="
|
|
22
|
-
<table
|
|
25
|
+
<div class="specra-params-table">
|
|
26
|
+
<table>
|
|
23
27
|
<thead>
|
|
24
|
-
<tr
|
|
25
|
-
<th
|
|
26
|
-
|
|
27
|
-
</th>
|
|
28
|
-
<th
|
|
29
|
-
|
|
30
|
-
</th>
|
|
31
|
-
<th class="text-left py-2 px-3 text-xs font-semibold text-muted-foreground uppercase tracking-wider">
|
|
32
|
-
Required
|
|
33
|
-
</th>
|
|
34
|
-
<th class="text-left py-2 px-3 text-xs font-semibold text-muted-foreground uppercase tracking-wider">
|
|
35
|
-
Default
|
|
36
|
-
</th>
|
|
37
|
-
<th class="text-left py-2 px-3 text-xs font-semibold text-muted-foreground uppercase tracking-wider">
|
|
38
|
-
Description
|
|
39
|
-
</th>
|
|
28
|
+
<tr>
|
|
29
|
+
<th>Property</th>
|
|
30
|
+
<th>Type</th>
|
|
31
|
+
<th>Required</th>
|
|
32
|
+
<th>Default</th>
|
|
33
|
+
<th>Description</th>
|
|
40
34
|
</tr>
|
|
41
35
|
</thead>
|
|
42
36
|
<tbody>
|
|
43
|
-
{#each
|
|
44
|
-
<tr
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
<td class="py-2.5 px-3">
|
|
48
|
-
<code class="text-sm font-mono text-foreground">{param.name}</code>
|
|
37
|
+
{#each filteredParams as param, index}
|
|
38
|
+
<tr>
|
|
39
|
+
<td>
|
|
40
|
+
<code>{param.name}</code>
|
|
49
41
|
</td>
|
|
50
|
-
<td
|
|
51
|
-
<span class="
|
|
42
|
+
<td>
|
|
43
|
+
<span class="font-mono">{param.type}</span>
|
|
52
44
|
</td>
|
|
53
|
-
<td
|
|
45
|
+
<td>
|
|
54
46
|
{#if param.required}
|
|
55
|
-
<span class="text-
|
|
47
|
+
<span class="text-red-600 dark:text-red-400">Yes</span>
|
|
56
48
|
{:else}
|
|
57
|
-
|
|
49
|
+
No
|
|
58
50
|
{/if}
|
|
59
51
|
</td>
|
|
60
|
-
<td
|
|
52
|
+
<td>
|
|
61
53
|
{#if param.default}
|
|
62
|
-
<code
|
|
54
|
+
<code>{param.default}</code>
|
|
63
55
|
{:else}
|
|
64
|
-
|
|
56
|
+
-
|
|
65
57
|
{/if}
|
|
66
58
|
</td>
|
|
67
|
-
<td
|
|
59
|
+
<td>
|
|
68
60
|
{#if param.description}
|
|
69
|
-
|
|
61
|
+
{param.description}
|
|
70
62
|
{:else}
|
|
71
|
-
|
|
63
|
+
-
|
|
72
64
|
{/if}
|
|
73
65
|
</td>
|
|
74
66
|
</tr>
|
|
@@ -78,3 +70,69 @@
|
|
|
78
70
|
</div>
|
|
79
71
|
</div>
|
|
80
72
|
{/if}
|
|
73
|
+
|
|
74
|
+
<style>
|
|
75
|
+
.specra-params-table table {
|
|
76
|
+
border-collapse: separate;
|
|
77
|
+
border-spacing: 0;
|
|
78
|
+
border: 1px solid var(--border);
|
|
79
|
+
border-radius: 0.75rem;
|
|
80
|
+
overflow: hidden;
|
|
81
|
+
width: max-content;
|
|
82
|
+
max-width: 100%;
|
|
83
|
+
display: block;
|
|
84
|
+
overflow-x: auto;
|
|
85
|
+
-webkit-overflow-scrolling: touch;
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
.specra-params-table thead {
|
|
89
|
+
background: var(--muted);
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
.specra-params-table thead th {
|
|
93
|
+
border-bottom: 1px solid var(--border);
|
|
94
|
+
border-right: 1px solid var(--border);
|
|
95
|
+
padding: 0.625rem 1rem;
|
|
96
|
+
font-weight: 600;
|
|
97
|
+
font-size: 0.75rem;
|
|
98
|
+
text-transform: uppercase;
|
|
99
|
+
letter-spacing: 0.05em;
|
|
100
|
+
text-align: left;
|
|
101
|
+
color: var(--muted-foreground);
|
|
102
|
+
white-space: nowrap;
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
.specra-params-table thead th:last-child {
|
|
106
|
+
border-right: none;
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
.specra-params-table tbody td {
|
|
110
|
+
border-bottom: 1px solid var(--border);
|
|
111
|
+
border-right: 1px solid var(--border);
|
|
112
|
+
padding: 0.625rem 1rem;
|
|
113
|
+
font-size: 0.875rem;
|
|
114
|
+
color: var(--muted-foreground);
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
.specra-params-table tbody td:last-child {
|
|
118
|
+
border-right: none;
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
.specra-params-table tbody tr:last-child td {
|
|
122
|
+
border-bottom: none;
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
.specra-params-table tbody tr:hover {
|
|
126
|
+
background: var(--muted);
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
.specra-params-table code {
|
|
130
|
+
font-size: 0.8125rem;
|
|
131
|
+
font-family: var(--font-mono, ui-monospace, monospace);
|
|
132
|
+
color: var(--foreground);
|
|
133
|
+
background: var(--muted);
|
|
134
|
+
padding: 0.125rem 0.375rem;
|
|
135
|
+
border-radius: 0.25rem;
|
|
136
|
+
border: 1px solid var(--border);
|
|
137
|
+
}
|
|
138
|
+
</style>
|
|
@@ -36,23 +36,42 @@
|
|
|
36
36
|
|
|
37
37
|
<script lang="ts">
|
|
38
38
|
import { cn } from '../../utils.js';
|
|
39
|
-
import type { HTMLButtonAttributes } from 'svelte/elements';
|
|
39
|
+
import type { HTMLButtonAttributes, HTMLAnchorAttributes } from 'svelte/elements';
|
|
40
40
|
import type { Snippet } from 'svelte';
|
|
41
41
|
|
|
42
|
-
|
|
42
|
+
type ButtonProps = HTMLButtonAttributes & {
|
|
43
|
+
href?: never;
|
|
44
|
+
};
|
|
45
|
+
|
|
46
|
+
type AnchorProps = HTMLAnchorAttributes & {
|
|
47
|
+
href: string;
|
|
48
|
+
};
|
|
49
|
+
|
|
50
|
+
type Props = (ButtonProps | AnchorProps) & {
|
|
43
51
|
variant?: ButtonVariants['variant'];
|
|
44
52
|
size?: ButtonVariants['size'];
|
|
45
53
|
class?: string;
|
|
46
54
|
children?: Snippet;
|
|
47
|
-
}
|
|
55
|
+
};
|
|
48
56
|
|
|
49
|
-
let { variant = 'default', size = 'default', class: className, children, ...restProps }: Props = $props();
|
|
57
|
+
let { variant = 'default', size = 'default', class: className, children, href, ...restProps }: Props = $props();
|
|
50
58
|
</script>
|
|
51
59
|
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
60
|
+
{#if href}
|
|
61
|
+
<a
|
|
62
|
+
{href}
|
|
63
|
+
data-slot="button"
|
|
64
|
+
class={cn(buttonVariants({ variant, size, className }))}
|
|
65
|
+
{...restProps}
|
|
66
|
+
>
|
|
67
|
+
{@render children?.()}
|
|
68
|
+
</a>
|
|
69
|
+
{:else}
|
|
70
|
+
<button
|
|
71
|
+
data-slot="button"
|
|
72
|
+
class={cn(buttonVariants({ variant, size, className }))}
|
|
73
|
+
{...restProps}
|
|
74
|
+
>
|
|
75
|
+
{@render children?.()}
|
|
76
|
+
</button>
|
|
77
|
+
{/if}
|
|
@@ -4,14 +4,20 @@ export declare const buttonVariants: (props?: ({
|
|
|
4
4
|
size?: "icon" | "default" | "sm" | "lg" | "icon-sm" | "icon-lg" | null | undefined;
|
|
5
5
|
} & import("class-variance-authority/types").ClassProp) | undefined) => string;
|
|
6
6
|
export type ButtonVariants = VariantProps<typeof buttonVariants>;
|
|
7
|
-
import type { HTMLButtonAttributes } from 'svelte/elements';
|
|
7
|
+
import type { HTMLButtonAttributes, HTMLAnchorAttributes } from 'svelte/elements';
|
|
8
8
|
import type { Snippet } from 'svelte';
|
|
9
|
-
|
|
9
|
+
type ButtonProps = HTMLButtonAttributes & {
|
|
10
|
+
href?: never;
|
|
11
|
+
};
|
|
12
|
+
type AnchorProps = HTMLAnchorAttributes & {
|
|
13
|
+
href: string;
|
|
14
|
+
};
|
|
15
|
+
type Props = (ButtonProps | AnchorProps) & {
|
|
10
16
|
variant?: ButtonVariants['variant'];
|
|
11
17
|
size?: ButtonVariants['size'];
|
|
12
18
|
class?: string;
|
|
13
19
|
children?: Snippet;
|
|
14
|
-
}
|
|
20
|
+
};
|
|
15
21
|
declare const Button: import("svelte").Component<Props, {}, "">;
|
|
16
22
|
type Button = ReturnType<typeof Button>;
|
|
17
23
|
export default Button;
|
package/dist/mdx.js
CHANGED
|
@@ -379,7 +379,14 @@ function parseJsxExpression(expr) {
|
|
|
379
379
|
return JSON.parse(trimmed);
|
|
380
380
|
}
|
|
381
381
|
catch {
|
|
382
|
-
|
|
382
|
+
// Convert JS object notation inside arrays to JSON
|
|
383
|
+
const jsonStr = trimmed.replace(/(\w+)\s*:/g, '"$1":').replace(/:\s*'([^']*)'/g, ': "$1"');
|
|
384
|
+
try {
|
|
385
|
+
return JSON.parse(jsonStr);
|
|
386
|
+
}
|
|
387
|
+
catch {
|
|
388
|
+
return trimmed;
|
|
389
|
+
}
|
|
383
390
|
}
|
|
384
391
|
}
|
|
385
392
|
return trimmed;
|
|
@@ -449,6 +456,61 @@ function extractCodeBlockProps(node) {
|
|
|
449
456
|
const filename = node.properties?.['data-filename'] || codeChild.properties?.['data-filename'];
|
|
450
457
|
return { code, language, ...(filename ? { filename } : {}) };
|
|
451
458
|
}
|
|
459
|
+
/**
|
|
460
|
+
* Detect GitHub-style alert blockquotes: > [!WARNING] content
|
|
461
|
+
* Returns the alert type and remaining content children, or null if not an alert.
|
|
462
|
+
*/
|
|
463
|
+
const ALERT_TYPE_MAP = {
|
|
464
|
+
NOTE: 'note',
|
|
465
|
+
TIP: 'tip',
|
|
466
|
+
IMPORTANT: 'info',
|
|
467
|
+
WARNING: 'warning',
|
|
468
|
+
CAUTION: 'danger',
|
|
469
|
+
INFO: 'info',
|
|
470
|
+
SUCCESS: 'success',
|
|
471
|
+
ERROR: 'error',
|
|
472
|
+
DANGER: 'danger',
|
|
473
|
+
};
|
|
474
|
+
function extractBlockquoteAlert(node) {
|
|
475
|
+
if (node.type !== 'element' || node.tagName !== 'blockquote')
|
|
476
|
+
return null;
|
|
477
|
+
if (!node.children || node.children.length === 0)
|
|
478
|
+
return null;
|
|
479
|
+
// Find the first paragraph child
|
|
480
|
+
const firstP = node.children.find((c) => c.type === 'element' && c.tagName === 'p');
|
|
481
|
+
if (!firstP || !firstP.children || firstP.children.length === 0)
|
|
482
|
+
return null;
|
|
483
|
+
// Check first text node for [!TYPE] pattern
|
|
484
|
+
const firstText = firstP.children[0];
|
|
485
|
+
if (firstText.type !== 'text')
|
|
486
|
+
return null;
|
|
487
|
+
const match = firstText.value.match(/^\s*\[!(\w+)\]\s*\n?/);
|
|
488
|
+
if (!match)
|
|
489
|
+
return null;
|
|
490
|
+
const alertType = ALERT_TYPE_MAP[match[1].toUpperCase()];
|
|
491
|
+
if (!alertType)
|
|
492
|
+
return null;
|
|
493
|
+
// Build remaining content: modify the first paragraph to remove the alert marker
|
|
494
|
+
const remainingFirstPChildren = [...firstP.children];
|
|
495
|
+
const remainingText = firstText.value.slice(match[0].length);
|
|
496
|
+
if (remainingText.trim()) {
|
|
497
|
+
remainingFirstPChildren[0] = { ...firstText, value: remainingText };
|
|
498
|
+
}
|
|
499
|
+
else {
|
|
500
|
+
remainingFirstPChildren.shift();
|
|
501
|
+
}
|
|
502
|
+
const contentChildren = [];
|
|
503
|
+
if (remainingFirstPChildren.length > 0) {
|
|
504
|
+
contentChildren.push({ ...firstP, children: remainingFirstPChildren });
|
|
505
|
+
}
|
|
506
|
+
// Add any remaining blockquote children (paragraphs after the first)
|
|
507
|
+
for (const child of node.children) {
|
|
508
|
+
if (child !== firstP) {
|
|
509
|
+
contentChildren.push(child);
|
|
510
|
+
}
|
|
511
|
+
}
|
|
512
|
+
return { type: alertType, contentChildren };
|
|
513
|
+
}
|
|
452
514
|
/**
|
|
453
515
|
* Recursively extract text content from a hast node.
|
|
454
516
|
*/
|
|
@@ -720,6 +782,20 @@ async function hastChildrenToMdxNodes(children) {
|
|
|
720
782
|
children: childNodes,
|
|
721
783
|
});
|
|
722
784
|
}
|
|
785
|
+
else if (extractBlockquoteAlert(child)) {
|
|
786
|
+
// GitHub-style alert blockquotes: > [!WARNING] content
|
|
787
|
+
flushHtmlBuffer();
|
|
788
|
+
const alert = extractBlockquoteAlert(child);
|
|
789
|
+
const contentNodes = alert.contentChildren.length > 0
|
|
790
|
+
? await hastChildrenToMdxNodes(alert.contentChildren)
|
|
791
|
+
: [];
|
|
792
|
+
nodes.push({
|
|
793
|
+
type: 'component',
|
|
794
|
+
name: 'Callout',
|
|
795
|
+
props: { type: alert.type },
|
|
796
|
+
children: contentNodes,
|
|
797
|
+
});
|
|
798
|
+
}
|
|
723
799
|
else {
|
|
724
800
|
// Check if this regular element contains any component elements nested within
|
|
725
801
|
if (hasNestedComponent(child)) {
|
|
@@ -116,8 +116,13 @@ export class OpenApiParser {
|
|
|
116
116
|
parseParameters(parameters, spec) {
|
|
117
117
|
const result = { path: [], query: [], header: [] };
|
|
118
118
|
for (const param of parameters) {
|
|
119
|
+
if (!param)
|
|
120
|
+
continue;
|
|
119
121
|
// Resolve $ref if present
|
|
120
122
|
const resolved = param.$ref ? this.resolveRef(param.$ref, spec) : param;
|
|
123
|
+
// Skip params that failed to resolve or have no name
|
|
124
|
+
if (!resolved || !resolved.name || !resolved.in)
|
|
125
|
+
continue;
|
|
121
126
|
const apiParam = {
|
|
122
127
|
name: resolved.name,
|
|
123
128
|
type: resolved.schema?.type || resolved.type || "string",
|
|
@@ -202,7 +207,7 @@ export class OpenApiParser {
|
|
|
202
207
|
for (const segment of path) {
|
|
203
208
|
current = current[segment];
|
|
204
209
|
if (!current)
|
|
205
|
-
return
|
|
210
|
+
return null;
|
|
206
211
|
}
|
|
207
212
|
return current;
|
|
208
213
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "specra",
|
|
3
|
-
"version": "0.2.
|
|
3
|
+
"version": "0.2.8",
|
|
4
4
|
"description": "A modern documentation library for SvelteKit with built-in versioning, API reference generation, full-text search, and MDX support",
|
|
5
5
|
"svelte": "./dist/index.js",
|
|
6
6
|
"types": "./dist/index.d.ts",
|