- 六协议调试:HTTP/GraphQL/WebSocket/SSE/Socket.IO/MQTT
- 集合树/多标签/多环境变量/历史记录,localStorage 持久化
- 导入:OpenAPI 2/3/3.1、Postman、Apifox、Apipost、cURL、HAR、
Insomnia、Hoppscotch、Markdown(自动识别,21 个单测覆盖)
- 导出:OpenAPI 3.0 JSON/YAML、Postman v2.1、Markdown 文档、
全量备份;单请求生成 cURL/fetch/Python/Go 代码
- Gin 服务:HTTP 代理、NDJSON 流式代理(SSE 透传)、
WebSocket 隧道(浏览器自定义握手 Header)
- 内置产品官网 /home 与用户文档站 /docs(SSR,随二进制交付)
- lucide 图标体系 + 品牌标识(Logo/favicon),明暗双主题
- 环境变量:彩色徽标切换器、管理弹窗、{{ 自动补全
87 lines
2.7 KiB
TypeScript
87 lines
2.7 KiB
TypeScript
/**
|
||
* Markdown API 文档导入器:提取 curl 代码块(及可选 JSON 请求体示例)。
|
||
*/
|
||
import type { Collection } from "../model";
|
||
import { createCollection, createFolder } from "../factory";
|
||
import type { ImportResult } from "../model";
|
||
import { importCurl } from "./curl";
|
||
|
||
export function looksLikeMarkdown(text: string): boolean {
|
||
const t = text.trimStart();
|
||
if (!t.startsWith("#") && !t.includes("```")) return false;
|
||
return /^#{1,6}\s/m.test(text) || /```/.test(text);
|
||
}
|
||
|
||
export function importMarkdown(text: string): ImportResult {
|
||
const warnings: string[] = [];
|
||
const blocks: { heading: string; code: string }[] = [];
|
||
|
||
const lines = text.split(/\r?\n/);
|
||
let heading = "";
|
||
let inBlock = false;
|
||
let lang = "";
|
||
let buf: string[] = [];
|
||
|
||
for (const line of lines) {
|
||
const fence = line.match(/^\s*```(\w*)\s*$/);
|
||
if (fence) {
|
||
if (!inBlock) {
|
||
inBlock = true;
|
||
lang = fence[1].toLowerCase();
|
||
buf = [];
|
||
} else {
|
||
inBlock = false;
|
||
const code = buf.join("\n");
|
||
if (["curl", "bash", "sh", "shell", ""].includes(lang) && code.trim().startsWith("curl")) {
|
||
blocks.push({ heading: heading || `curl ${blocks.length + 1}`, code });
|
||
}
|
||
}
|
||
continue;
|
||
}
|
||
if (inBlock) {
|
||
buf.push(line);
|
||
continue;
|
||
}
|
||
const h = line.match(/^#{1,6}\s+(.*)$/);
|
||
if (h) heading = h[1].trim();
|
||
}
|
||
|
||
if (blocks.length === 0) {
|
||
warnings.push("未在 Markdown 中找到 curl 代码块(也支持直接粘贴 cURL 导入)");
|
||
}
|
||
|
||
const folders = new Map<string, ReturnType<typeof createFolder>>();
|
||
const rootChildren = [];
|
||
for (const block of blocks) {
|
||
try {
|
||
const temp = importCurl(block.code, "temp");
|
||
const node = temp.children[0];
|
||
if (!node) continue;
|
||
// 一级标题做目录,二级及以下做请求名
|
||
const parts = block.heading.split(/[::]/);
|
||
const folderName = parts[0]?.trim();
|
||
if (folderName && folders.has(folderName)) {
|
||
folders.get(folderName)!.children.push(node);
|
||
} else if (folderName) {
|
||
const f = createFolder(folderName, [node]);
|
||
folders.set(folderName, f);
|
||
} else {
|
||
rootChildren.push(node);
|
||
}
|
||
} catch (e) {
|
||
warnings.push(`解析代码块失败:${e instanceof Error ? e.message : String(e)}`);
|
||
}
|
||
}
|
||
|
||
const collection = createCollection(
|
||
firstHeading(text) || "Markdown 导入",
|
||
[...folders.values(), ...rootChildren],
|
||
);
|
||
return { collections: [collection], environmentVariables: [], warnings };
|
||
}
|
||
|
||
function firstHeading(text: string): string {
|
||
const m = text.match(/^#\s+(.+)$/m);
|
||
return m ? m[1].trim().slice(0, 60) : "";
|
||
}
|