Files
opsxapi/src/core/exporters/markdown.ts
T
logan 2bb4ef7a95 桌面 GUI 外链修复 + 主题顺序调整(含结构扁平化进行中的工作区基线)
- 集成 tauri-plugin-opener:Cargo/lib.rs/capabilities/npm 四处接线,新增 src/openExternal.ts(桌面走 opener,浏览器回退 window.open,相对路径绝对化)
- 关于页 4 个链接、活动栏官网菜单、代理错误横幅 2 处健康检查链接全部改走 openExternal,桌面端可正常跳转
- 设置→外观 主题顺序调整为 深色/浅色/跟随系统
- release 二进制重编并验证(浏览器实测关于页链接可开文档页)
- 附带工作区中未提交的扁平化重构现状(另一会话进行中)
- homepage 落地页与文档终检通过(8787 / 与 /docs 双 200)
2026-10-16 03:03:36 +08:00

96 lines
3.7 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
/**
* 集合 → Markdown 接口文档。
*/
import type { Collection, CollectionNode, HttpRequestData } from "../model";
import { buildUrlWithQuery } from "./curl";
export function exportMarkdown(collection: Collection): string {
const lines: string[] = [];
lines.push(`# ${collection.name}`, "");
if (collection.description) lines.push(collection.description, "");
lines.push("> 由 OpsxAPI 导出 · " + new Date().toLocaleString("zh-CN"), "");
const toc: string[] = [];
const visitToc = (nodes: CollectionNode[], prefix: string) => {
for (const node of nodes) {
if (node.type === "folder") {
toc.push(`${prefix}- 📁 ${node.name}`);
visitToc(node.children, `${prefix} `);
} else {
toc.push(`${prefix}- [${node.name}](#${anchor(node.name)})`);
}
}
};
visitToc(collection.children, "");
if (toc.length) lines.push("## 目录", "", ...toc, "");
const visit = (nodes: CollectionNode[], depth: number) => {
for (const node of nodes) {
const hashes = "#".repeat(Math.min(depth + 1, 6));
if (node.type === "folder") {
lines.push(`${hashes} ${node.name}`, "");
if (node.description) lines.push(node.description, "");
visit(node.children, depth + 1);
continue;
}
lines.push(`${hashes} ${node.name}`, "");
if (node.description) lines.push(node.description, "");
if (node.kind === "http") {
const d: HttpRequestData = node.data;
lines.push(
"",
`- **Method**:\`${d.method}\``,
`- **URL**:\`${buildUrlWithQuery(d)}\``,
);
if (d.auth.mode !== "none") {
lines.push(`- **鉴权**:\`${d.auth.mode}\``);
}
if (d.query.filter((q) => q.enabled && q.key).length) {
lines.push("", "**Query 参数**", "", "| 参数 | 示例值 | 说明 |", "| --- | --- | --- |");
for (const q of d.query) {
if (!q.enabled || !q.key) continue;
lines.push(`| \`${q.key}\` | \`${escapeTable(q.value)}\` | ${escapeTable(q.description ?? "")} |`);
}
}
if (d.headers.filter((h) => h.enabled && h.key).length) {
lines.push("", "**Headers**", "", "| Header | 示例值 | 说明 |", "| --- | --- | --- |");
for (const h of d.headers) {
if (!h.enabled || !h.key) continue;
lines.push(`| \`${h.key}\` | \`${escapeTable(h.value)}\` | ${escapeTable(h.description ?? "")} |`);
}
}
if (d.bodyMode !== "none") {
const lang = d.bodyMode === "json" ? "json" : d.bodyMode === "xml" ? "xml" : "";
if (d.bodyMode === "urlencoded" || d.bodyMode === "form-data") {
lines.push("", `**请求体(${d.bodyMode})**`, "", "| 字段 | 示例值 |", "| --- | --- |");
for (const f of d.bodyForm) {
if (!f.enabled || !f.key) continue;
lines.push(`| \`${f.key}\` | \`${escapeTable(f.value)}\` |`);
}
} else if (d.bodyRaw) {
lines.push("", `**请求体(${d.bodyMode})**`, "", "```" + lang, d.bodyRaw, "```");
}
}
lines.push("");
} else {
const d = node.data as { url?: string; brokerUrl?: string };
const url = "url" in d ? d.url : d.brokerUrl ?? "";
lines.push("", `- **协议**:\`${node.kind}\``, `- **地址**:\`${url}\``);
lines.push("");
}
}
};
visit(collection.children, 1);
return lines.join("\n");
}
function escapeTable(s: string): string {
return s.replaceAll("|", "\\|").replaceAll("\n", " ");
}
function anchor(name: string): string {
return name.toLowerCase().replace(/[^\p{L}\p{N}\s-]/gu, "").trim().replace(/\s+/g, "-");
}