@aravindc26/velu 0.10.0 → 0.11.1
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 +15 -6
- package/schema/velu.schema.json +1864 -30
- package/src/build.ts +1161 -180
- package/src/cli.ts +121 -16
- package/src/engine/_server.mjs +1708 -192
- package/src/engine/app/(docs)/[...slug]/layout.tsx +377 -0
- package/src/engine/app/(docs)/[...slug]/page.tsx +917 -0
- package/src/engine/app/(docs)/layout.tsx +1 -13
- package/src/engine/app/api/proxy/route.ts +23 -0
- package/src/engine/app/copy-page.css +59 -1
- package/src/engine/app/global.css +3487 -6
- package/src/engine/app/layout.tsx +59 -8
- package/src/engine/app/llms-file/route.ts +87 -0
- package/src/engine/app/llms-full-file/route.ts +62 -0
- package/src/engine/app/md-file/[...slug]/route.ts +409 -0
- package/src/engine/app/page.tsx +45 -0
- package/src/engine/app/robots.txt/route.ts +61 -0
- package/src/engine/app/rss-file/[...slug]/route.ts +176 -0
- package/src/engine/app/search.css +20 -0
- package/src/engine/app/sitemap.xml/route.ts +80 -0
- package/src/engine/components/assistant.tsx +16 -5
- package/src/engine/components/changelog-filters.tsx +114 -0
- package/src/engine/components/code-group.tsx +383 -0
- package/src/engine/components/color.tsx +118 -0
- package/src/engine/components/expandable.tsx +77 -0
- package/src/engine/components/icon.tsx +136 -0
- package/src/engine/components/image-zoom-fallback.tsx +147 -0
- package/src/engine/components/image.tsx +111 -0
- package/src/engine/components/lang-switcher.tsx +95 -0
- package/src/engine/components/manual-api-playground.tsx +154 -0
- package/src/engine/components/mermaid.tsx +142 -0
- package/src/engine/components/openapi-toc-sync.tsx +59 -0
- package/src/engine/components/openapi.tsx +1679 -0
- package/src/engine/components/page-feedback.tsx +153 -0
- package/src/engine/components/product-switcher.tsx +102 -0
- package/src/engine/components/prompt.tsx +90 -0
- package/src/engine/components/providers.tsx +21 -0
- package/src/engine/components/search.tsx +70 -3
- package/src/engine/components/sidebar-links.tsx +49 -0
- package/src/engine/components/synced-tabs.tsx +57 -0
- package/src/engine/components/theme-toggle.tsx +39 -0
- package/src/engine/components/toc-examples.tsx +110 -0
- package/src/engine/components/version-switcher.tsx +89 -0
- package/src/engine/components/view.tsx +344 -0
- package/src/engine/generated/redirects.ts +3 -0
- package/src/engine/lib/changelog.ts +246 -0
- package/src/engine/lib/layout.shared.ts +57 -7
- package/src/engine/lib/llms.ts +444 -0
- package/src/engine/lib/navigation-normalize.mjs +525 -0
- package/src/engine/lib/navigation-normalize.ts +695 -0
- package/src/engine/lib/redirects.ts +194 -0
- package/src/engine/lib/source.ts +121 -4
- package/src/engine/lib/velu.ts +635 -5
- package/src/engine/mdx-components.tsx +648 -0
- package/src/engine/middleware.ts +66 -0
- package/src/engine/next.config.mjs +2 -2
- package/src/engine/public/icons/cursor-dark.svg +12 -0
- package/src/engine/public/icons/cursor-light.svg +12 -0
- package/src/engine/source.config.ts +98 -1
- package/src/engine/src/components/PageTitle.astro +16 -5
- package/src/engine/src/lib/velu.ts +97 -16
- package/src/navigation-normalize.ts +686 -0
- package/src/themes.ts +6 -6
- package/src/validate.ts +235 -24
- package/src/engine/app/(docs)/[[...slug]]/page.tsx +0 -69
|
@@ -0,0 +1,142 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
|
|
3
|
+
import mermaid from 'mermaid';
|
|
4
|
+
import { useEffect, useMemo, useRef, useState } from 'react';
|
|
5
|
+
|
|
6
|
+
type MermaidPlacement = 'top-left' | 'top-right' | 'bottom-left' | 'bottom-right';
|
|
7
|
+
|
|
8
|
+
let mermaidInitialized = false;
|
|
9
|
+
|
|
10
|
+
function ensureMermaid() {
|
|
11
|
+
if (mermaidInitialized) return;
|
|
12
|
+
mermaid.initialize({
|
|
13
|
+
startOnLoad: false,
|
|
14
|
+
securityLevel: 'loose',
|
|
15
|
+
theme: 'base',
|
|
16
|
+
suppressErrorRendering: true,
|
|
17
|
+
});
|
|
18
|
+
mermaidInitialized = true;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
function buildSvgThemeCss() {
|
|
22
|
+
return [
|
|
23
|
+
':root{--m-fg:#0f172a;--m-bg:#ffffff;--m-border:#cbd5e1;--m-edge-bg:#e2e8f0;}',
|
|
24
|
+
':root[data-theme="dark"],.dark{--m-fg:#e5e7eb;--m-bg:#0b1220;--m-border:#334155;--m-edge-bg:#1f2937;}',
|
|
25
|
+
'.velu-mermaid-svg svg{font-family:inherit!important;}',
|
|
26
|
+
'.velu-mermaid-svg .label,.velu-mermaid-svg .label text,.velu-mermaid-svg .nodeLabel,.velu-mermaid-svg .nodeLabel p,.velu-mermaid-svg .edgeLabel,.velu-mermaid-svg .edgeLabel text,.velu-mermaid-svg .cluster-label text,.velu-mermaid-svg text{color:var(--m-fg)!important;fill:var(--m-fg)!important;}',
|
|
27
|
+
'.velu-mermaid-svg .node rect,.velu-mermaid-svg .node circle,.velu-mermaid-svg .node ellipse,.velu-mermaid-svg .node polygon,.velu-mermaid-svg .node path{stroke:var(--m-border)!important;fill:color-mix(in oklab,var(--m-bg) 92%,var(--m-fg) 8%)!important;}',
|
|
28
|
+
'.velu-mermaid-svg .edgePath .path,.velu-mermaid-svg .flowchart-link{stroke:var(--m-fg)!important;stroke-opacity:.95!important;}',
|
|
29
|
+
'.velu-mermaid-svg .edgeLabel rect,.velu-mermaid-svg .labelBkg{fill:var(--m-edge-bg)!important;stroke:var(--m-border)!important;opacity:1!important;}',
|
|
30
|
+
'.velu-mermaid-svg g.edgeLabel,.velu-mermaid-svg .edgeLabel{opacity:1!important;}',
|
|
31
|
+
'.velu-mermaid-svg .edgeLabel text{fill:var(--m-fg)!important;}',
|
|
32
|
+
'.velu-mermaid-svg .edgeLabel .label,.velu-mermaid-svg .edgeLabel .label *{color:var(--m-fg)!important;background:transparent!important;padding:0!important;border:0!important;box-shadow:none!important;}',
|
|
33
|
+
'.velu-mermaid-svg .edgeLabel foreignObject,.velu-mermaid-svg .edgeLabel foreignObject *{overflow:visible!important;}',
|
|
34
|
+
'.velu-mermaid-svg .cluster rect{fill:color-mix(in oklab,var(--m-bg) 88%,var(--m-fg) 12%)!important;stroke:var(--m-border)!important;}',
|
|
35
|
+
'.velu-mermaid-svg .marker,.velu-mermaid-svg marker path{fill:var(--m-fg)!important;stroke:var(--m-fg)!important;}',
|
|
36
|
+
].join('');
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
export function VeluMermaid({
|
|
40
|
+
chart,
|
|
41
|
+
children,
|
|
42
|
+
className,
|
|
43
|
+
actions,
|
|
44
|
+
placement = 'bottom-right',
|
|
45
|
+
}: {
|
|
46
|
+
chart?: string;
|
|
47
|
+
children?: unknown;
|
|
48
|
+
className?: string;
|
|
49
|
+
actions?: boolean;
|
|
50
|
+
placement?: MermaidPlacement;
|
|
51
|
+
}) {
|
|
52
|
+
const source = useMemo(() => {
|
|
53
|
+
if (typeof chart === 'string' && chart.trim()) return chart;
|
|
54
|
+
if (typeof children === 'string' && children.trim()) return children;
|
|
55
|
+
return '';
|
|
56
|
+
}, [chart, children]);
|
|
57
|
+
|
|
58
|
+
const [svg, setSvg] = useState<string>('');
|
|
59
|
+
const [error, setError] = useState<string>('');
|
|
60
|
+
const [scale, setScale] = useState<number>(1);
|
|
61
|
+
const [offset, setOffset] = useState<{ x: number; y: number }>({ x: 0, y: 0 });
|
|
62
|
+
const [autoActions, setAutoActions] = useState<boolean>(false);
|
|
63
|
+
const hostRef = useRef<HTMLDivElement | null>(null);
|
|
64
|
+
const uid = useMemo(() => `velu-mermaid-${Math.random().toString(36).slice(2, 10)}`, []);
|
|
65
|
+
|
|
66
|
+
useEffect(() => {
|
|
67
|
+
let cancelled = false;
|
|
68
|
+
|
|
69
|
+
async function run() {
|
|
70
|
+
if (!source) {
|
|
71
|
+
setSvg('');
|
|
72
|
+
setError('');
|
|
73
|
+
return;
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
try {
|
|
77
|
+
ensureMermaid();
|
|
78
|
+
const { svg: rendered } = await mermaid.render(uid, source);
|
|
79
|
+
if (cancelled) return;
|
|
80
|
+
setSvg(rendered);
|
|
81
|
+
setError('');
|
|
82
|
+
} catch {
|
|
83
|
+
if (cancelled) return;
|
|
84
|
+
setSvg('');
|
|
85
|
+
setError('Failed to render Mermaid diagram.');
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
run();
|
|
90
|
+
return () => {
|
|
91
|
+
cancelled = true;
|
|
92
|
+
};
|
|
93
|
+
}, [source, uid]);
|
|
94
|
+
|
|
95
|
+
useEffect(() => {
|
|
96
|
+
if (!hostRef.current || !svg) {
|
|
97
|
+
setAutoActions(false);
|
|
98
|
+
return;
|
|
99
|
+
}
|
|
100
|
+
const svgEl = hostRef.current.querySelector('svg');
|
|
101
|
+
const h = svgEl?.getBoundingClientRect().height ?? 0;
|
|
102
|
+
setAutoActions(h > 220);
|
|
103
|
+
}, [svg]);
|
|
104
|
+
|
|
105
|
+
const showActions = Boolean(actions ?? autoActions);
|
|
106
|
+
|
|
107
|
+
const controlsClass = `velu-mermaid-controls velu-mermaid-controls-${placement}`;
|
|
108
|
+
const transform = `translate(${offset.x}px, ${offset.y}px) scale(${scale})`;
|
|
109
|
+
|
|
110
|
+
return (
|
|
111
|
+
<div className={['velu-mermaid', className].filter(Boolean).join(' ')}>
|
|
112
|
+
<div
|
|
113
|
+
className="velu-mermaid-stage"
|
|
114
|
+
ref={hostRef}
|
|
115
|
+
style={{ transform }}
|
|
116
|
+
>
|
|
117
|
+
{error ? (
|
|
118
|
+
<pre className="velu-mermaid-error"><code>{source}</code></pre>
|
|
119
|
+
) : (
|
|
120
|
+
<div
|
|
121
|
+
className="velu-mermaid-svg"
|
|
122
|
+
dangerouslySetInnerHTML={{ __html: svg }}
|
|
123
|
+
/>
|
|
124
|
+
)}
|
|
125
|
+
</div>
|
|
126
|
+
|
|
127
|
+
<style dangerouslySetInnerHTML={{ __html: buildSvgThemeCss() }} />
|
|
128
|
+
|
|
129
|
+
{showActions ? (
|
|
130
|
+
<div className={controlsClass}>
|
|
131
|
+
<button className="velu-mermaid-btn-up" type="button" onClick={() => setOffset((p) => ({ ...p, y: p.y - 18 }))} aria-label="Pan up">↑</button>
|
|
132
|
+
<button className="velu-mermaid-btn-zoom-in" type="button" onClick={() => setScale((v) => Math.min(2.4, +(v + 0.1).toFixed(2)))} aria-label="Zoom in">+</button>
|
|
133
|
+
<button className="velu-mermaid-btn-left" type="button" onClick={() => setOffset((p) => ({ ...p, x: p.x - 18 }))} aria-label="Pan left">←</button>
|
|
134
|
+
<button className="velu-mermaid-btn-reset" type="button" onClick={() => { setScale(1); setOffset({ x: 0, y: 0 }); }} aria-label="Reset">↻</button>
|
|
135
|
+
<button className="velu-mermaid-btn-right" type="button" onClick={() => setOffset((p) => ({ ...p, x: p.x + 18 }))} aria-label="Pan right">→</button>
|
|
136
|
+
<button className="velu-mermaid-btn-down" type="button" onClick={() => setOffset((p) => ({ ...p, y: p.y + 18 }))} aria-label="Pan down">↓</button>
|
|
137
|
+
<button className="velu-mermaid-btn-zoom-out" type="button" onClick={() => setScale((v) => Math.max(0.6, +(v - 0.1).toFixed(2)))} aria-label="Zoom out">−</button>
|
|
138
|
+
</div>
|
|
139
|
+
) : null}
|
|
140
|
+
</div>
|
|
141
|
+
);
|
|
142
|
+
}
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
'use client';
|
|
2
|
+
|
|
3
|
+
import { useEffect } from 'react';
|
|
4
|
+
|
|
5
|
+
const TOC_HOST_ID = 'velu-api-toc-rail-host';
|
|
6
|
+
const SOURCE_SELECTOR = '[data-velu-openapi-example-source="true"]';
|
|
7
|
+
|
|
8
|
+
export function OpenApiTocSync({ enabled }: { enabled: boolean }) {
|
|
9
|
+
useEffect(() => {
|
|
10
|
+
if (!enabled) return;
|
|
11
|
+
|
|
12
|
+
let disconnected = false;
|
|
13
|
+
let cleanup: (() => void) | undefined;
|
|
14
|
+
const observer = new MutationObserver(() => {
|
|
15
|
+
if (cleanup || disconnected) return;
|
|
16
|
+
|
|
17
|
+
const host = document.getElementById(TOC_HOST_ID);
|
|
18
|
+
const source = document.querySelector<HTMLElement>(SOURCE_SELECTOR);
|
|
19
|
+
if (!host || !source) return;
|
|
20
|
+
|
|
21
|
+
const previousParent = source.parentNode;
|
|
22
|
+
const previousNextSibling = source.nextSibling;
|
|
23
|
+
host.appendChild(source);
|
|
24
|
+
cleanup = () => {
|
|
25
|
+
if (previousParent) {
|
|
26
|
+
if (previousNextSibling) previousParent.insertBefore(source, previousNextSibling);
|
|
27
|
+
else previousParent.appendChild(source);
|
|
28
|
+
}
|
|
29
|
+
};
|
|
30
|
+
observer.disconnect();
|
|
31
|
+
});
|
|
32
|
+
|
|
33
|
+
observer.observe(document.body, { childList: true, subtree: true });
|
|
34
|
+
// Try immediately in case both nodes already exist.
|
|
35
|
+
observer.takeRecords();
|
|
36
|
+
const host = document.getElementById(TOC_HOST_ID);
|
|
37
|
+
const source = document.querySelector<HTMLElement>(SOURCE_SELECTOR);
|
|
38
|
+
if (host && source) {
|
|
39
|
+
const previousParent = source.parentNode;
|
|
40
|
+
const previousNextSibling = source.nextSibling;
|
|
41
|
+
host.appendChild(source);
|
|
42
|
+
cleanup = () => {
|
|
43
|
+
if (previousParent) {
|
|
44
|
+
if (previousNextSibling) previousParent.insertBefore(source, previousNextSibling);
|
|
45
|
+
else previousParent.appendChild(source);
|
|
46
|
+
}
|
|
47
|
+
};
|
|
48
|
+
observer.disconnect();
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
return () => {
|
|
52
|
+
disconnected = true;
|
|
53
|
+
observer.disconnect();
|
|
54
|
+
cleanup?.();
|
|
55
|
+
};
|
|
56
|
+
}, [enabled]);
|
|
57
|
+
|
|
58
|
+
return null;
|
|
59
|
+
}
|