raffel 1.1.48 → 1.1.50
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/dist/docs/ui/assets/raffel-docs.css +277 -12
- package/dist/docs/ui/assets/raffel-docs.js +413 -132
- package/dist/docs/ui/runtime/index.js +413 -132
- package/dist/docs/ui/runtime/index.js.map +1 -1
- package/dist/docs/ui/style-sections/layout-navigation.d.ts +1 -1
- package/dist/docs/ui/style-sections/layout-navigation.d.ts.map +1 -1
- package/dist/docs/ui/style-sections/layout-navigation.js +10 -0
- package/dist/docs/ui/style-sections/layout-navigation.js.map +1 -1
- package/dist/docs/ui/style-sections/schema-code.d.ts +1 -1
- package/dist/docs/ui/style-sections/schema-code.d.ts.map +1 -1
- package/dist/docs/ui/style-sections/schema-code.js +248 -0
- package/dist/docs/ui/style-sections/schema-code.js.map +1 -1
- package/dist/docs/ui/style-sections/try-it.d.ts +1 -1
- package/dist/docs/ui/style-sections/try-it.d.ts.map +1 -1
- package/dist/docs/ui/style-sections/try-it.js +19 -12
- package/dist/docs/ui/style-sections/try-it.js.map +1 -1
- package/dist/ui/docs/ui/style-sections/layout-navigation.d.ts +1 -1
- package/dist/ui/docs/ui/style-sections/layout-navigation.d.ts.map +1 -1
- package/dist/ui/docs/ui/style-sections/schema-code.d.ts +1 -1
- package/dist/ui/docs/ui/style-sections/schema-code.d.ts.map +1 -1
- package/dist/ui/docs/ui/style-sections/try-it.d.ts +1 -1
- package/dist/ui/docs/ui/style-sections/try-it.d.ts.map +1 -1
- package/package.json +1 -1
|
@@ -1214,60 +1214,247 @@ function generateExampleFromSchema(schema) {
|
|
|
1214
1214
|
default: return null;
|
|
1215
1215
|
}
|
|
1216
1216
|
}
|
|
1217
|
+
function httpBaseUrl() {
|
|
1218
|
+
return String(spec?.servers?.[0]?.url ?? 'http://localhost:3000').replace(/\/$/, '');
|
|
1219
|
+
}
|
|
1220
|
+
// Collect JSON-Schema validation constraints as readable chips.
|
|
1221
|
+
function schemaConstraintChips(schema) {
|
|
1222
|
+
if (!schema || typeof schema !== 'object')
|
|
1223
|
+
return [];
|
|
1224
|
+
const chips = [];
|
|
1225
|
+
if (schema.format)
|
|
1226
|
+
chips.push(`format: ${schema.format}`);
|
|
1227
|
+
if (typeof schema.minLength === 'number')
|
|
1228
|
+
chips.push(`min length: ${schema.minLength}`);
|
|
1229
|
+
if (typeof schema.maxLength === 'number')
|
|
1230
|
+
chips.push(`max length: ${schema.maxLength}`);
|
|
1231
|
+
if (typeof schema.minimum === 'number')
|
|
1232
|
+
chips.push(`>= ${schema.minimum}`);
|
|
1233
|
+
if (typeof schema.exclusiveMinimum === 'number')
|
|
1234
|
+
chips.push(`> ${schema.exclusiveMinimum}`);
|
|
1235
|
+
if (typeof schema.maximum === 'number')
|
|
1236
|
+
chips.push(`<= ${schema.maximum}`);
|
|
1237
|
+
if (typeof schema.exclusiveMaximum === 'number')
|
|
1238
|
+
chips.push(`< ${schema.exclusiveMaximum}`);
|
|
1239
|
+
if (typeof schema.multipleOf === 'number')
|
|
1240
|
+
chips.push(`multiple of ${schema.multipleOf}`);
|
|
1241
|
+
if (typeof schema.minItems === 'number')
|
|
1242
|
+
chips.push(`min items: ${schema.minItems}`);
|
|
1243
|
+
if (typeof schema.maxItems === 'number')
|
|
1244
|
+
chips.push(`max items: ${schema.maxItems}`);
|
|
1245
|
+
if (schema.uniqueItems)
|
|
1246
|
+
chips.push('unique items');
|
|
1247
|
+
if (typeof schema.pattern === 'string')
|
|
1248
|
+
chips.push(`pattern: ${schema.pattern}`);
|
|
1249
|
+
if (schema.nullable)
|
|
1250
|
+
chips.push('nullable');
|
|
1251
|
+
if (schema.readOnly)
|
|
1252
|
+
chips.push('read-only');
|
|
1253
|
+
if (schema.writeOnly)
|
|
1254
|
+
chips.push('write-only');
|
|
1255
|
+
return chips;
|
|
1256
|
+
}
|
|
1257
|
+
// Append constraint / default / enum chips to a row element.
|
|
1258
|
+
function appendConstraintChips(row, schema) {
|
|
1259
|
+
if (!schema || typeof schema !== 'object')
|
|
1260
|
+
return;
|
|
1261
|
+
const chips = schemaConstraintChips(schema);
|
|
1262
|
+
const hasDefault = schema.default !== undefined;
|
|
1263
|
+
const enumValues = Array.isArray(schema.enum) ? schema.enum : [];
|
|
1264
|
+
if (!chips.length && !hasDefault && enumValues.length === 0)
|
|
1265
|
+
return;
|
|
1266
|
+
const wrap = doc.createElement('div');
|
|
1267
|
+
wrap.className = 'constraint-chips';
|
|
1268
|
+
if (hasDefault) {
|
|
1269
|
+
const chip = doc.createElement('span');
|
|
1270
|
+
chip.className = 'constraint-chip';
|
|
1271
|
+
chip.textContent = `default: ${JSON.stringify(schema.default)}`;
|
|
1272
|
+
wrap.appendChild(chip);
|
|
1273
|
+
}
|
|
1274
|
+
chips.forEach(text => {
|
|
1275
|
+
const chip = doc.createElement('span');
|
|
1276
|
+
chip.className = 'constraint-chip';
|
|
1277
|
+
chip.textContent = text;
|
|
1278
|
+
wrap.appendChild(chip);
|
|
1279
|
+
});
|
|
1280
|
+
enumValues.forEach(value => {
|
|
1281
|
+
const chip = doc.createElement('span');
|
|
1282
|
+
chip.className = 'constraint-chip constraint-enum';
|
|
1283
|
+
chip.textContent = String(value);
|
|
1284
|
+
wrap.appendChild(chip);
|
|
1285
|
+
});
|
|
1286
|
+
row.appendChild(wrap);
|
|
1287
|
+
}
|
|
1288
|
+
// Render a JSON value as a Python literal (for the Python request sample).
|
|
1289
|
+
function pyLiteral(value) {
|
|
1290
|
+
if (value === null || value === undefined)
|
|
1291
|
+
return 'None';
|
|
1292
|
+
if (value === true)
|
|
1293
|
+
return 'True';
|
|
1294
|
+
if (value === false)
|
|
1295
|
+
return 'False';
|
|
1296
|
+
if (typeof value === 'number')
|
|
1297
|
+
return String(value);
|
|
1298
|
+
if (typeof value === 'string')
|
|
1299
|
+
return JSON.stringify(value);
|
|
1300
|
+
if (Array.isArray(value))
|
|
1301
|
+
return `[${value.map(pyLiteral).join(', ')}]`;
|
|
1302
|
+
if (typeof value === 'object') {
|
|
1303
|
+
return `{${Object.entries(value).map(([k, v]) => `${JSON.stringify(k)}: ${pyLiteral(v)}`).join(', ')}}`;
|
|
1304
|
+
}
|
|
1305
|
+
return 'None';
|
|
1306
|
+
}
|
|
1307
|
+
// Build request samples in cURL, JavaScript, Python and Rust.
|
|
1308
|
+
function buildHttpSamples(method, url, body) {
|
|
1309
|
+
const hasBody = body !== null && body !== undefined && ['POST', 'PUT', 'PATCH', 'DELETE'].includes(method);
|
|
1310
|
+
const jsonPretty = hasBody ? JSON.stringify(body, null, 2) : '';
|
|
1311
|
+
const samples = {};
|
|
1312
|
+
let curl = `curl -X ${method} "${url}"`;
|
|
1313
|
+
if (hasBody)
|
|
1314
|
+
curl += ` \\\n -H "Content-Type: application/json" \\\n -d '${JSON.stringify(body)}'`;
|
|
1315
|
+
samples.curl = curl;
|
|
1316
|
+
let js = `const res = await fetch(${JSON.stringify(url)}, {\n method: ${JSON.stringify(method)},`;
|
|
1317
|
+
if (hasBody)
|
|
1318
|
+
js += `\n headers: { "Content-Type": "application/json" },\n body: JSON.stringify(${jsonPretty.split('\n').join('\n ')}),`;
|
|
1319
|
+
js += `\n});\nconst data = await res.json();\nconsole.log(data);`;
|
|
1320
|
+
samples.javascript = js;
|
|
1321
|
+
let py = `import requests\n\nres = requests.${method.toLowerCase()}(\n ${JSON.stringify(url)},`;
|
|
1322
|
+
if (hasBody)
|
|
1323
|
+
py += `\n json=${pyLiteral(body)},`;
|
|
1324
|
+
py += `\n)\nprint(res.json())`;
|
|
1325
|
+
samples.python = py;
|
|
1326
|
+
let rust = `use reqwest::Client;\n\nlet client = Client::new();\nlet res = client\n .${method.toLowerCase()}(${JSON.stringify(url)})`;
|
|
1327
|
+
if (hasBody)
|
|
1328
|
+
rust += `\n .json(&serde_json::json!(${jsonPretty.split('\n').join('\n ')}))`;
|
|
1329
|
+
rust += `\n .send()\n .await?;\nlet body = res.text().await?;\nprintln!("{}", body);`;
|
|
1330
|
+
samples.rust = rust;
|
|
1331
|
+
return samples;
|
|
1332
|
+
}
|
|
1217
1333
|
function renderCodeExamples(endpoint, data) {
|
|
1218
|
-
const
|
|
1219
|
-
|
|
1220
|
-
|
|
1221
|
-
|
|
1222
|
-
|
|
1223
|
-
const
|
|
1224
|
-
const
|
|
1225
|
-
const
|
|
1226
|
-
|
|
1227
|
-
|
|
1228
|
-
|
|
1229
|
-
const
|
|
1230
|
-
|
|
1231
|
-
|
|
1232
|
-
|
|
1233
|
-
|
|
1234
|
-
|
|
1235
|
-
|
|
1236
|
-
|
|
1237
|
-
|
|
1238
|
-
const
|
|
1239
|
-
|
|
1240
|
-
|
|
1241
|
-
|
|
1242
|
-
|
|
1243
|
-
|
|
1244
|
-
|
|
1245
|
-
|
|
1246
|
-
|
|
1247
|
-
|
|
1248
|
-
|
|
1249
|
-
|
|
1250
|
-
|
|
1251
|
-
|
|
1252
|
-
|
|
1253
|
-
|
|
1254
|
-
|
|
1255
|
-
|
|
1256
|
-
|
|
1257
|
-
|
|
1258
|
-
|
|
1259
|
-
|
|
1260
|
-
|
|
1261
|
-
|
|
1262
|
-
|
|
1263
|
-
|
|
1264
|
-
|
|
1334
|
+
const method = (endpoint.method || 'GET').toUpperCase();
|
|
1335
|
+
const url = `${httpBaseUrl()}${endpoint.path || '/'}`;
|
|
1336
|
+
const reqContent = data.requestBody?.content;
|
|
1337
|
+
const reqSchema = reqContent ? reqContent[Object.keys(reqContent)[0]]?.schema : null;
|
|
1338
|
+
const bodyExample = reqSchema ? generateExampleFromSchema(reqSchema) : null;
|
|
1339
|
+
const samples = buildHttpSamples(method, url, bodyExample);
|
|
1340
|
+
const langs = [['curl', 'cURL'], ['javascript', 'JavaScript'], ['python', 'Python'], ['rust', 'Rust']];
|
|
1341
|
+
const wrap = doc.createElement('div');
|
|
1342
|
+
wrap.className = 'http-code-samples';
|
|
1343
|
+
const tabs = doc.createElement('div');
|
|
1344
|
+
tabs.className = 'code-tabs';
|
|
1345
|
+
const contents = doc.createElement('div');
|
|
1346
|
+
contents.className = 'code-contents';
|
|
1347
|
+
langs.forEach(([key, label], index) => {
|
|
1348
|
+
const tab = doc.createElement('button');
|
|
1349
|
+
tab.type = 'button';
|
|
1350
|
+
tab.className = `code-tab${index === 0 ? ' active' : ''}`;
|
|
1351
|
+
tab.textContent = label;
|
|
1352
|
+
const content = doc.createElement('div');
|
|
1353
|
+
content.className = `code-content${index === 0 ? ' active' : ''}`;
|
|
1354
|
+
const pre = doc.createElement('pre');
|
|
1355
|
+
pre.className = 'http-code-sample-pre';
|
|
1356
|
+
pre.textContent = samples[key];
|
|
1357
|
+
content.appendChild(pre);
|
|
1358
|
+
tab.onclick = () => {
|
|
1359
|
+
tabs.querySelectorAll('.code-tab').forEach((t) => t.classList.remove('active'));
|
|
1360
|
+
contents.querySelectorAll('.code-content').forEach((c) => c.classList.remove('active'));
|
|
1361
|
+
tab.classList.add('active');
|
|
1362
|
+
content.classList.add('active');
|
|
1363
|
+
};
|
|
1364
|
+
tabs.appendChild(tab);
|
|
1365
|
+
contents.appendChild(content);
|
|
1366
|
+
});
|
|
1367
|
+
const copy = doc.createElement('button');
|
|
1368
|
+
copy.type = 'button';
|
|
1369
|
+
copy.className = 'http-code-copy';
|
|
1370
|
+
copy.textContent = 'Copy';
|
|
1371
|
+
copy.onclick = () => {
|
|
1372
|
+
const active = contents.querySelector('.code-content.active pre');
|
|
1373
|
+
const text = active?.textContent ?? '';
|
|
1374
|
+
const clip = globalThis.navigator?.clipboard;
|
|
1375
|
+
if (clip?.writeText) {
|
|
1376
|
+
clip.writeText(text).then(() => {
|
|
1377
|
+
copy.textContent = 'Copied';
|
|
1378
|
+
setTimeout(() => { copy.textContent = 'Copy'; }, 1200);
|
|
1379
|
+
});
|
|
1380
|
+
}
|
|
1381
|
+
};
|
|
1382
|
+
tabs.appendChild(copy);
|
|
1383
|
+
wrap.appendChild(tabs);
|
|
1384
|
+
wrap.appendChild(contents);
|
|
1385
|
+
return wrap;
|
|
1386
|
+
}
|
|
1387
|
+
// Append a JSON example block to a container.
|
|
1388
|
+
// Syntax-highlight a JSON value into HTML using the .sample-json token classes.
|
|
1389
|
+
function highlightJsonHtml(value) {
|
|
1390
|
+
let json = JSON.stringify(value, null, 2);
|
|
1391
|
+
if (json === undefined)
|
|
1392
|
+
return '';
|
|
1393
|
+
json = json.replace(/&/g, '&').replace(/</g, '<').replace(/>/g, '>');
|
|
1394
|
+
return json.replace(/("(?:\\.|[^"\\])*"(\s*:)?|\b(?:true|false|null)\b|-?\d+(?:\.\d+)?(?:[eE][+-]?\d+)?)/g, (match, _token, colon) => {
|
|
1395
|
+
let cls = 'json-number';
|
|
1396
|
+
if (match[0] === '"')
|
|
1397
|
+
cls = colon ? 'json-key' : 'json-string';
|
|
1398
|
+
else if (match === 'true' || match === 'false')
|
|
1399
|
+
cls = 'json-boolean';
|
|
1400
|
+
else if (match === 'null')
|
|
1401
|
+
cls = 'json-null';
|
|
1402
|
+
return `<span class="${cls}">${match}</span>`;
|
|
1403
|
+
});
|
|
1404
|
+
}
|
|
1405
|
+
// Right-panel response samples: one status tab per response, each showing the
|
|
1406
|
+
// generated JSON example (colorized) — ReDoc's response sample switcher.
|
|
1407
|
+
function renderResponseSamples(responses) {
|
|
1408
|
+
const wrap = doc.createElement('div');
|
|
1409
|
+
const tabs = doc.createElement('div');
|
|
1410
|
+
tabs.className = 'sample-tabs';
|
|
1411
|
+
const contents = doc.createElement('div');
|
|
1412
|
+
contents.className = 'sample-contents';
|
|
1413
|
+
const entries = Object.entries(responses);
|
|
1414
|
+
entries.forEach(([status, resp], index) => {
|
|
1415
|
+
const statusClass = status.startsWith('2') ? 'status-2xx'
|
|
1416
|
+
: status.startsWith('4') ? 'status-4xx'
|
|
1417
|
+
: status.startsWith('5') ? 'status-5xx'
|
|
1418
|
+
: '';
|
|
1419
|
+
const tab = doc.createElement('button');
|
|
1420
|
+
tab.type = 'button';
|
|
1421
|
+
tab.className = `sample-tab ${statusClass}${index === 0 ? ' active' : ''}`;
|
|
1422
|
+
tab.textContent = status;
|
|
1423
|
+
const content = doc.createElement('div');
|
|
1424
|
+
content.className = `sample-content${index === 0 ? ' active' : ''}`;
|
|
1425
|
+
const ct = resp.content ? Object.keys(resp.content)[0] : null;
|
|
1426
|
+
const schema = ct ? resp.content[ct]?.schema : null;
|
|
1427
|
+
const example = schema ? generateExampleFromSchema(schema) : null;
|
|
1428
|
+
if (example !== null && example !== undefined) {
|
|
1429
|
+
if (ct) {
|
|
1430
|
+
const typeLine = doc.createElement('div');
|
|
1431
|
+
typeLine.className = 'sample-content-type';
|
|
1432
|
+
typeLine.textContent = ct;
|
|
1433
|
+
content.appendChild(typeLine);
|
|
1265
1434
|
}
|
|
1266
|
-
|
|
1267
|
-
|
|
1435
|
+
const pre = doc.createElement('pre');
|
|
1436
|
+
pre.className = 'sample-json';
|
|
1437
|
+
pre.innerHTML = highlightJsonHtml(example);
|
|
1438
|
+
content.appendChild(pre);
|
|
1268
1439
|
}
|
|
1269
|
-
|
|
1270
|
-
|
|
1440
|
+
else {
|
|
1441
|
+
const empty = doc.createElement('div');
|
|
1442
|
+
empty.className = 'no-example';
|
|
1443
|
+
empty.textContent = resp.description || 'No response body.';
|
|
1444
|
+
content.appendChild(empty);
|
|
1445
|
+
}
|
|
1446
|
+
tab.onclick = () => {
|
|
1447
|
+
tabs.querySelectorAll('.sample-tab').forEach((t) => t.classList.remove('active'));
|
|
1448
|
+
contents.querySelectorAll('.sample-content').forEach((c) => c.classList.remove('active'));
|
|
1449
|
+
tab.classList.add('active');
|
|
1450
|
+
content.classList.add('active');
|
|
1451
|
+
};
|
|
1452
|
+
tabs.appendChild(tab);
|
|
1453
|
+
contents.appendChild(content);
|
|
1454
|
+
});
|
|
1455
|
+
wrap.appendChild(tabs);
|
|
1456
|
+
wrap.appendChild(contents);
|
|
1457
|
+
return wrap;
|
|
1271
1458
|
}
|
|
1272
1459
|
function renderSchemaTree(parent, schema, depth = 0) {
|
|
1273
1460
|
if (!schema)
|
|
@@ -1290,8 +1477,7 @@ function renderSchemaTree(parent, schema, depth = 0) {
|
|
|
1290
1477
|
row.style.paddingLeft = depth === 0 ? '8px' : '0';
|
|
1291
1478
|
const type = prop.type || 'any';
|
|
1292
1479
|
const required = schema.required?.includes(key) ? '<span style="color: #ef4444; margin-left: 4px;">*</span>' : '';
|
|
1293
|
-
|
|
1294
|
-
row.innerHTML = `<div style="font-weight: 500; color: var(--text-primary); display: flex; align-items: center;">${esc(key)}${required} <span style="color: var(--text-secondary); font-weight: normal; margin-left: 8px;">${type}${format}</span></div>`;
|
|
1480
|
+
row.innerHTML = `<div style="font-weight: 500; color: var(--text-primary); display: flex; align-items: center;">${esc(key)}${required} <span class="schema-type type-${esc(prop.type || 'null')}" style="margin-left: 8px;">${esc(type)}</span></div>`;
|
|
1295
1481
|
if (prop.description) {
|
|
1296
1482
|
const desc = doc.createElement('div');
|
|
1297
1483
|
desc.style.fontSize = '12px';
|
|
@@ -1300,33 +1486,20 @@ function renderSchemaTree(parent, schema, depth = 0) {
|
|
|
1300
1486
|
desc.textContent = prop.description;
|
|
1301
1487
|
row.appendChild(desc);
|
|
1302
1488
|
}
|
|
1303
|
-
|
|
1304
|
-
const ex = doc.createElement('div');
|
|
1305
|
-
ex.style.fontSize = '11px';
|
|
1306
|
-
ex.style.color = '#888';
|
|
1307
|
-
ex.style.marginTop = '2px';
|
|
1308
|
-
ex.style.fontFamily = 'monospace';
|
|
1309
|
-
ex.textContent = `Example: ${JSON.stringify(prop.example)}`;
|
|
1310
|
-
row.appendChild(ex);
|
|
1311
|
-
}
|
|
1312
|
-
if (prop.enum) {
|
|
1313
|
-
const en = doc.createElement('div');
|
|
1314
|
-
en.style.fontSize = '11px';
|
|
1315
|
-
en.style.color = '#888';
|
|
1316
|
-
en.style.marginTop = '2px';
|
|
1317
|
-
en.textContent = `Values: ${prop.enum.join(', ')}`;
|
|
1318
|
-
row.appendChild(en);
|
|
1319
|
-
}
|
|
1489
|
+
appendConstraintChips(row, prop);
|
|
1320
1490
|
div.appendChild(row);
|
|
1321
|
-
if (prop.properties) {
|
|
1491
|
+
if (prop.type === 'object' && prop.properties) {
|
|
1322
1492
|
renderSchemaTree(div, prop, depth + 1);
|
|
1323
1493
|
}
|
|
1494
|
+
else if (prop.type === 'array' && prop.items?.properties) {
|
|
1495
|
+
renderSchemaTree(div, prop.items, depth + 1);
|
|
1496
|
+
}
|
|
1324
1497
|
});
|
|
1325
1498
|
}
|
|
1326
1499
|
else if (schema.type === 'array' && schema.items) {
|
|
1327
1500
|
const row = doc.createElement('div');
|
|
1328
1501
|
row.style.padding = '6px 0';
|
|
1329
|
-
row.innerHTML = `<span style="color: var(--text-primary); font-weight: 500;">array</span> <span style="color: var(--text-secondary);">of ${schema.items.type || 'object'}</span>`;
|
|
1502
|
+
row.innerHTML = `<span style="color: var(--text-primary); font-weight: 500;">array</span> <span style="color: var(--text-secondary);">of ${esc(schema.items.type || 'object')}</span>`;
|
|
1330
1503
|
div.appendChild(row);
|
|
1331
1504
|
if (schema.items.properties) {
|
|
1332
1505
|
renderSchemaTree(div, schema.items, depth + 1);
|
|
@@ -1336,10 +1509,107 @@ function renderSchemaTree(parent, schema, depth = 0) {
|
|
|
1336
1509
|
const row = doc.createElement('div');
|
|
1337
1510
|
row.style.padding = '6px 0';
|
|
1338
1511
|
row.innerHTML = `<span style="color: var(--text-secondary);">${esc(schema.type || 'any')}</span>`;
|
|
1512
|
+
appendConstraintChips(row, schema);
|
|
1339
1513
|
div.appendChild(row);
|
|
1340
1514
|
}
|
|
1341
1515
|
parent.appendChild(div);
|
|
1342
1516
|
}
|
|
1517
|
+
// Render one HTTP parameter group (path/query/header/cookie) ReDoc-style.
|
|
1518
|
+
function renderParamGroup(title, params) {
|
|
1519
|
+
const group = doc.createElement('div');
|
|
1520
|
+
group.className = 'http-param-group';
|
|
1521
|
+
const heading = doc.createElement('div');
|
|
1522
|
+
heading.className = 'http-param-group-title';
|
|
1523
|
+
heading.textContent = title;
|
|
1524
|
+
group.appendChild(heading);
|
|
1525
|
+
const list = doc.createElement('div');
|
|
1526
|
+
list.className = 'http-params';
|
|
1527
|
+
params.forEach(param => {
|
|
1528
|
+
const item = doc.createElement('div');
|
|
1529
|
+
item.className = 'http-param';
|
|
1530
|
+
const schema = param.schema || {};
|
|
1531
|
+
const typeName = schema.type === 'array' ? `${schema.items?.type || 'any'}[]` : (schema.type || 'string');
|
|
1532
|
+
const head = doc.createElement('div');
|
|
1533
|
+
head.className = 'http-param-head';
|
|
1534
|
+
head.innerHTML = `<span class="http-param-name">${esc(param.name)}</span>` +
|
|
1535
|
+
`<span class="schema-type type-${esc(schema.type || 'string')}">${esc(typeName)}</span>` +
|
|
1536
|
+
(param.required ? '<span class="http-param-required">required</span>' : '') +
|
|
1537
|
+
(param.deprecated ? '<span class="http-param-deprecated">deprecated</span>' : '');
|
|
1538
|
+
item.appendChild(head);
|
|
1539
|
+
const description = param.description || schema.description;
|
|
1540
|
+
if (description) {
|
|
1541
|
+
const desc = doc.createElement('div');
|
|
1542
|
+
desc.className = 'http-param-desc';
|
|
1543
|
+
desc.textContent = description;
|
|
1544
|
+
item.appendChild(desc);
|
|
1545
|
+
}
|
|
1546
|
+
appendConstraintChips(item, schema);
|
|
1547
|
+
list.appendChild(item);
|
|
1548
|
+
});
|
|
1549
|
+
group.appendChild(list);
|
|
1550
|
+
return group;
|
|
1551
|
+
}
|
|
1552
|
+
// Render a single response as a collapsible accordion (ReDoc-style).
|
|
1553
|
+
function renderResponseAccordion(status, resp, openByDefault) {
|
|
1554
|
+
const statusClass = status.startsWith('2') ? 'status-2xx'
|
|
1555
|
+
: status.startsWith('3') ? 'status-3xx'
|
|
1556
|
+
: status.startsWith('4') ? 'status-4xx'
|
|
1557
|
+
: 'status-5xx';
|
|
1558
|
+
const acc = doc.createElement('div');
|
|
1559
|
+
acc.className = `response-accordion${openByDefault ? ' open' : ''}`;
|
|
1560
|
+
const header = doc.createElement('button');
|
|
1561
|
+
header.type = 'button';
|
|
1562
|
+
header.className = 'response-accordion-header';
|
|
1563
|
+
header.innerHTML = `<span class="response-accordion-caret">▶</span>` +
|
|
1564
|
+
`<span class="response-status-dot ${statusClass}"></span>` +
|
|
1565
|
+
`<span class="response-status-code">${esc(status)}</span>` +
|
|
1566
|
+
`<span class="response-status-desc">${esc(resp.description || '')}</span>`;
|
|
1567
|
+
const body = doc.createElement('div');
|
|
1568
|
+
body.className = 'response-accordion-body';
|
|
1569
|
+
if (resp.headers && Object.keys(resp.headers).length > 0) {
|
|
1570
|
+
const block = doc.createElement('div');
|
|
1571
|
+
block.className = 'response-block';
|
|
1572
|
+
const sub = doc.createElement('div');
|
|
1573
|
+
sub.className = 'response-subhead';
|
|
1574
|
+
sub.textContent = 'Response Headers';
|
|
1575
|
+
block.appendChild(sub);
|
|
1576
|
+
const list = doc.createElement('div');
|
|
1577
|
+
list.className = 'http-params';
|
|
1578
|
+
Object.entries(resp.headers).forEach(([name, def]) => {
|
|
1579
|
+
const item = doc.createElement('div');
|
|
1580
|
+
item.className = 'http-param';
|
|
1581
|
+
const hschema = def.schema || {};
|
|
1582
|
+
item.innerHTML = `<div class="http-param-head"><span class="http-param-name">${esc(name)}</span><span class="schema-type type-${esc(hschema.type || 'string')}">${esc(hschema.type || 'string')}</span></div>` +
|
|
1583
|
+
(def.description ? `<div class="http-param-desc">${esc(def.description)}</div>` : '');
|
|
1584
|
+
list.appendChild(item);
|
|
1585
|
+
});
|
|
1586
|
+
block.appendChild(list);
|
|
1587
|
+
body.appendChild(block);
|
|
1588
|
+
}
|
|
1589
|
+
const content = resp.content;
|
|
1590
|
+
const contentType = content ? Object.keys(content)[0] : null;
|
|
1591
|
+
const schema = contentType ? content[contentType]?.schema : null;
|
|
1592
|
+
if (schema) {
|
|
1593
|
+
const block = doc.createElement('div');
|
|
1594
|
+
block.className = 'response-block';
|
|
1595
|
+
const sub = doc.createElement('div');
|
|
1596
|
+
sub.className = 'response-subhead';
|
|
1597
|
+
sub.textContent = `Response Body${contentType ? ` · ${contentType}` : ''}`;
|
|
1598
|
+
block.appendChild(sub);
|
|
1599
|
+
renderSchemaTree(block, schema);
|
|
1600
|
+
body.appendChild(block);
|
|
1601
|
+
}
|
|
1602
|
+
if (!body.children.length) {
|
|
1603
|
+
const empty = doc.createElement('div');
|
|
1604
|
+
empty.className = 'response-desc-only';
|
|
1605
|
+
empty.textContent = resp.description || 'No content.';
|
|
1606
|
+
body.appendChild(empty);
|
|
1607
|
+
}
|
|
1608
|
+
header.onclick = () => acc.classList.toggle('open');
|
|
1609
|
+
acc.appendChild(header);
|
|
1610
|
+
acc.appendChild(body);
|
|
1611
|
+
return acc;
|
|
1612
|
+
}
|
|
1343
1613
|
function renderEndpointDetails(endpoint) {
|
|
1344
1614
|
const container = doc.createElement('div');
|
|
1345
1615
|
container.className = 'endpoint-details';
|
|
@@ -1347,82 +1617,81 @@ function renderEndpointDetails(endpoint) {
|
|
|
1347
1617
|
appendProtocolConsole(container, { doc, spec, wsSpec, streamsSpec, jsonrpcSpec, activeProtocol, endpoint, data, esc, escapeAttr });
|
|
1348
1618
|
const appendMany = (items) => items.forEach(([title, value]) => appendSchemaSubsection(container, title, value));
|
|
1349
1619
|
if (activeProtocol === 'http') {
|
|
1620
|
+
// Two-column operation layout (ReDoc-style): the left column carries the
|
|
1621
|
+
// contract (params + schemas + responses); the right column is a sticky
|
|
1622
|
+
// dark panel with the request samples and response examples.
|
|
1623
|
+
const content = doc.createElement('div');
|
|
1624
|
+
content.className = 'endpoint-content';
|
|
1625
|
+
const left = doc.createElement('div');
|
|
1626
|
+
left.className = 'endpoint-left';
|
|
1627
|
+
const right = doc.createElement('div');
|
|
1628
|
+
right.className = 'endpoint-right';
|
|
1350
1629
|
const params = (data.parameters ?? []);
|
|
1351
1630
|
if (params.length > 0) {
|
|
1352
1631
|
const section = doc.createElement('div');
|
|
1353
1632
|
section.className = 'endpoint-subsection';
|
|
1354
1633
|
section.innerHTML = '<div class="subsection-label">PARAMETERS</div>';
|
|
1355
|
-
const
|
|
1356
|
-
|
|
1357
|
-
|
|
1358
|
-
|
|
1634
|
+
const groups = [
|
|
1635
|
+
['path', 'Path Parameters'],
|
|
1636
|
+
['query', 'Query Parameters'],
|
|
1637
|
+
['header', 'Header Parameters'],
|
|
1638
|
+
['cookie', 'Cookie Parameters'],
|
|
1639
|
+
];
|
|
1640
|
+
groups.forEach(([location, title]) => {
|
|
1641
|
+
const inGroup = params.filter(p => (p.in || 'query') === location);
|
|
1642
|
+
if (inGroup.length > 0)
|
|
1643
|
+
section.appendChild(renderParamGroup(title, inGroup));
|
|
1359
1644
|
});
|
|
1360
|
-
|
|
1361
|
-
container.appendChild(section);
|
|
1645
|
+
left.appendChild(section);
|
|
1362
1646
|
}
|
|
1363
1647
|
const reqBody = data.requestBody;
|
|
1364
1648
|
if (reqBody?.content) {
|
|
1365
1649
|
const section = doc.createElement('div');
|
|
1366
1650
|
section.className = 'endpoint-subsection';
|
|
1367
|
-
|
|
1368
|
-
|
|
1369
|
-
|
|
1370
|
-
|
|
1371
|
-
|
|
1372
|
-
|
|
1651
|
+
const contentType = Object.keys(reqBody.content)[0];
|
|
1652
|
+
section.innerHTML = `<div class="subsection-label">REQUEST BODY${reqBody.required ? ' <span style="color:#ef4444">required</span>' : ''}${contentType ? ` · ${esc(contentType)}` : ''}</div>`;
|
|
1653
|
+
const bodyContent = reqBody.content[contentType];
|
|
1654
|
+
if (bodyContent?.schema)
|
|
1655
|
+
renderSchemaTree(section, bodyContent.schema);
|
|
1656
|
+
left.appendChild(section);
|
|
1373
1657
|
}
|
|
1374
1658
|
const responses = data.responses;
|
|
1375
1659
|
if (responses && Object.keys(responses).length > 0) {
|
|
1376
1660
|
const section = doc.createElement('div');
|
|
1377
1661
|
section.className = 'endpoint-subsection';
|
|
1378
1662
|
section.innerHTML = '<div class="subsection-label">RESPONSES</div>';
|
|
1379
|
-
Object.entries(responses)
|
|
1380
|
-
|
|
1381
|
-
|
|
1382
|
-
|
|
1383
|
-
|
|
1384
|
-
|
|
1385
|
-
|
|
1386
|
-
item.innerHTML = `<div style="color: var(--text-secondary); font-size: 14px; margin-bottom: 12px; display: flex; align-items: center; gap: 8px;"><span class="badge badge-${statusClass}">${status}</span> <strong>${esc(resp.description || '')}</strong></div>`;
|
|
1387
|
-
// Headers
|
|
1388
|
-
if (resp.headers && Object.keys(resp.headers).length > 0) {
|
|
1389
|
-
const headersDiv = doc.createElement('div');
|
|
1390
|
-
headersDiv.style.marginBottom = '12px';
|
|
1391
|
-
headersDiv.innerHTML = '<div style="font-size: 12px; font-weight: 600; color: var(--text-secondary); margin-bottom: 6px;">HEADERS</div>';
|
|
1392
|
-
const headersList = doc.createElement('div');
|
|
1393
|
-
headersList.style.fontSize = '12px';
|
|
1394
|
-
Object.entries(resp.headers).forEach(([headerName, headerDef]) => {
|
|
1395
|
-
const headerRow = doc.createElement('div');
|
|
1396
|
-
headerRow.style.padding = '4px 0';
|
|
1397
|
-
headerRow.style.borderLeft = '2px solid var(--border-color)';
|
|
1398
|
-
headerRow.style.paddingLeft = '8px';
|
|
1399
|
-
const headerType = headerDef.schema?.type || 'string';
|
|
1400
|
-
headerRow.innerHTML = `<div style="font-weight: 500; color: var(--text-primary);">${esc(headerName)}</div><div style="color: var(--text-muted); font-size: 11px;">${headerType}</div>${headerDef.description ? `<div style="color: var(--text-muted); font-size: 11px; margin-top: 2px;">${esc(headerDef.description)}</div>` : ''}`;
|
|
1401
|
-
headersList.appendChild(headerRow);
|
|
1402
|
-
});
|
|
1403
|
-
headersDiv.appendChild(headersList);
|
|
1404
|
-
item.appendChild(headersDiv);
|
|
1405
|
-
}
|
|
1406
|
-
// Schema
|
|
1407
|
-
if (resp.content) {
|
|
1408
|
-
const contentType = Object.keys(resp.content)[0];
|
|
1409
|
-
const schema = resp.content[contentType]?.schema;
|
|
1410
|
-
if (schema) {
|
|
1411
|
-
const schemaDiv = doc.createElement('div');
|
|
1412
|
-
schemaDiv.innerHTML = '<div style="font-size: 12px; font-weight: 600; color: var(--text-secondary); margin-bottom: 6px;">SCHEMA</div>';
|
|
1413
|
-
renderSchemaTree(schemaDiv, schema);
|
|
1414
|
-
item.appendChild(schemaDiv);
|
|
1415
|
-
}
|
|
1416
|
-
}
|
|
1417
|
-
section.appendChild(item);
|
|
1663
|
+
const entries = Object.entries(responses);
|
|
1664
|
+
let opened = false;
|
|
1665
|
+
entries.forEach(([status, resp]) => {
|
|
1666
|
+
const open = !opened && status.startsWith('2');
|
|
1667
|
+
if (open)
|
|
1668
|
+
opened = true;
|
|
1669
|
+
section.appendChild(renderResponseAccordion(status, resp, open));
|
|
1418
1670
|
});
|
|
1419
|
-
|
|
1671
|
+
if (!opened) {
|
|
1672
|
+
const first = section.querySelector('.response-accordion');
|
|
1673
|
+
if (first)
|
|
1674
|
+
first.classList.add('open');
|
|
1675
|
+
}
|
|
1676
|
+
left.appendChild(section);
|
|
1677
|
+
}
|
|
1678
|
+
// Right column: request samples (cURL / JavaScript / Python / Rust).
|
|
1679
|
+
const samplesSection = doc.createElement('div');
|
|
1680
|
+
samplesSection.className = 'endpoint-right-section';
|
|
1681
|
+
samplesSection.innerHTML = '<div class="endpoint-right-header">Request samples</div>';
|
|
1682
|
+
samplesSection.appendChild(renderCodeExamples(endpoint, data));
|
|
1683
|
+
right.appendChild(samplesSection);
|
|
1684
|
+
// Right column: response samples (JSON examples per status code).
|
|
1685
|
+
if (responses && Object.keys(responses).length > 0) {
|
|
1686
|
+
const respSamples = doc.createElement('div');
|
|
1687
|
+
respSamples.className = 'endpoint-right-section';
|
|
1688
|
+
respSamples.innerHTML = '<div class="endpoint-right-header">Response samples</div>';
|
|
1689
|
+
respSamples.appendChild(renderResponseSamples(responses));
|
|
1690
|
+
right.appendChild(respSamples);
|
|
1420
1691
|
}
|
|
1421
|
-
|
|
1422
|
-
|
|
1423
|
-
|
|
1424
|
-
examplesSection.appendChild(renderCodeExamples(endpoint, data));
|
|
1425
|
-
container.appendChild(examplesSection);
|
|
1692
|
+
content.appendChild(left);
|
|
1693
|
+
content.appendChild(right);
|
|
1694
|
+
container.appendChild(content);
|
|
1426
1695
|
}
|
|
1427
1696
|
if (activeProtocol === 'websocket') {
|
|
1428
1697
|
appendInfoGrid(container, [['Channel Type', data.type], ['Path', endpoint.path]]);
|
|
@@ -1705,23 +1974,35 @@ function pageNavCard(entry, label, direction) {
|
|
|
1705
1974
|
button.onclick = () => setDocsPage(entry.path);
|
|
1706
1975
|
return button;
|
|
1707
1976
|
}
|
|
1977
|
+
function setTocColumn(visible) {
|
|
1978
|
+
const shell = doc.querySelector?.('.main-shell');
|
|
1979
|
+
if (shell)
|
|
1980
|
+
shell.classList.toggle('main-shell-no-toc', !visible);
|
|
1981
|
+
}
|
|
1708
1982
|
function renderToc(root) {
|
|
1709
1983
|
const toc = byId('pageToc');
|
|
1710
1984
|
if (!toc)
|
|
1711
1985
|
return;
|
|
1712
1986
|
toc.textContent = '';
|
|
1713
|
-
if (tocConfig.enabled === false)
|
|
1987
|
+
if (tocConfig.enabled === false) {
|
|
1988
|
+
setTocColumn(false);
|
|
1714
1989
|
return;
|
|
1715
|
-
|
|
1990
|
+
}
|
|
1991
|
+
if (root.querySelector?.('[data-markdown-ignore-all="true"]')) {
|
|
1992
|
+
setTocColumn(false);
|
|
1716
1993
|
return;
|
|
1994
|
+
}
|
|
1717
1995
|
const min = Number(tocConfig.minLevel ?? 2);
|
|
1718
1996
|
const max = Number(tocConfig.maxLevel ?? 3);
|
|
1719
1997
|
const headings = Array.from(root.querySelectorAll('h1,h2,h3,h4,h5,h6') ?? []).filter((heading) => {
|
|
1720
1998
|
const level = Number(heading.tagName.slice(1));
|
|
1721
1999
|
return heading.id && level >= min && level <= max && heading.dataset?.markdownIgnore !== 'true';
|
|
1722
2000
|
});
|
|
1723
|
-
if (headings.length === 0)
|
|
2001
|
+
if (headings.length === 0) {
|
|
2002
|
+
setTocColumn(false);
|
|
1724
2003
|
return;
|
|
2004
|
+
}
|
|
2005
|
+
setTocColumn(true);
|
|
1725
2006
|
const title = doc.createElement('div');
|
|
1726
2007
|
title.className = 'toc-title';
|
|
1727
2008
|
title.textContent = 'On this page';
|