118 lines
3.1 KiB
JavaScript
118 lines
3.1 KiB
JavaScript
(function() {
|
|
const textarea = document.getElementById('yaml-input');
|
|
const errorEl = document.getElementById('error');
|
|
const metaEl = document.getElementById('meta');
|
|
const renderBtn = document.getElementById('render-btn');
|
|
|
|
const editor = CodeMirror.fromTextArea(textarea, {
|
|
mode: 'yaml',
|
|
lineNumbers: true,
|
|
lineWrapping: true,
|
|
tabSize: 2,
|
|
indentUnit: 2,
|
|
});
|
|
|
|
const sample = `openapi: 3.0.0
|
|
info:
|
|
title: Sample API
|
|
version: 1.0.0
|
|
paths:
|
|
/api/test/bla:
|
|
get:
|
|
summary: Get bla
|
|
responses:
|
|
'200':
|
|
description: OK
|
|
/api/test/foo:
|
|
post:
|
|
summary: Create foo
|
|
responses:
|
|
'201':
|
|
description: Created
|
|
/api/users/{id}:
|
|
get:
|
|
summary: Get user
|
|
responses:
|
|
'200': { description: OK }
|
|
`;
|
|
editor.setValue(sample);
|
|
|
|
function selectWholeLine(lineNumber) {
|
|
const lineText = editor.getLine(lineNumber) || '';
|
|
const from = { line: lineNumber, ch: 0 };
|
|
const to = { line: lineNumber, ch: lineText.length };
|
|
editor.setSelection(from, to);
|
|
editor.scrollIntoView({ from, to }, 100);
|
|
editor.focus();
|
|
}
|
|
|
|
function jumpToEndpointInYaml(path) {
|
|
const lineCount = editor.lineCount();
|
|
const target = `${path}:`;
|
|
for (let i = 0; i < lineCount; i++) {
|
|
const line = editor.getLine(i);
|
|
if (!line) continue;
|
|
if (line.trimStart().startsWith(target)) {
|
|
selectWholeLine(i);
|
|
return true;
|
|
}
|
|
}
|
|
return false;
|
|
}
|
|
|
|
async function parseAndRender() {
|
|
errorEl.textContent = '';
|
|
metaEl.textContent = '';
|
|
|
|
const yamlText = editor.getValue();
|
|
try {
|
|
const res = await fetch('/api/parse', {
|
|
method: 'POST',
|
|
headers: { 'Content-Type': 'application/json' },
|
|
body: JSON.stringify({ yaml: yamlText })
|
|
});
|
|
const data = await res.json();
|
|
if (!res.ok) {
|
|
throw new Error(data && data.error ? data.error : 'Failed to parse');
|
|
}
|
|
|
|
const { graph, info } = data;
|
|
if (info && (info.title || info.version)) {
|
|
const title = info.title || 'Untitled';
|
|
const version = info.version ? ` v${info.version}` : '';
|
|
metaEl.textContent = `${title}${version}`;
|
|
}
|
|
|
|
const endpoints = graph.endpoints || [];
|
|
function onNodeClick(segmentId) {
|
|
const match = endpoints.find(ep => {
|
|
const parts = (ep.path || '').split('/').filter(Boolean);
|
|
return parts.includes(segmentId);
|
|
});
|
|
if (match) {
|
|
if (!jumpToEndpointInYaml(match.path)) {
|
|
// Fallback: best-effort contains check
|
|
const lineCount = editor.lineCount();
|
|
for (let i = 0; i < lineCount; i++) {
|
|
const line = editor.getLine(i) || '';
|
|
if (line.includes(match.path)) {
|
|
selectWholeLine(i);
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
renderGraph('#graph', graph, { onNodeClick });
|
|
} catch (e) {
|
|
errorEl.textContent = e.message || String(e);
|
|
}
|
|
}
|
|
|
|
renderBtn.addEventListener('click', parseAndRender);
|
|
|
|
// initial render
|
|
parseAndRender();
|
|
})();
|