feat: OpsxAPI v1.0 全协议接口工作台

- 六协议调试: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),明暗双主题
- 环境变量:彩色徽标切换器、管理弹窗、{{ 自动补全
This commit is contained in:
logan committed 2026-09-18 00:16:59 +08:00
commit dec66db5aa
182 files changed
+23377

No files matched your search

+86
View File
@@ -0,0 +1,86 @@
/**
* 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) : "";
}