plc_control/web/feeder/js/docs.js

138 lines
3.4 KiB
JavaScript

import { apiFetch } from "./api.js";
import { dom } from "./dom.js";
import { state } from "./state.js";
function escapeHtml(text) {
return text
.replaceAll("&", "&")
.replaceAll("<", "&lt;")
.replaceAll(">", "&gt;");
}
function slugify(text) {
return text
.toLowerCase()
.trim()
.replace(/[^\w\u4e00-\u9fa5]+/g, "-")
.replace(/^-+|-+$/g, "");
}
function parseMarkdown(text) {
const lines = text.split(/\r?\n/);
const blocks = [];
const headings = [];
let inCode = false;
let codeBuffer = [];
let paragraph = [];
const flushParagraph = () => {
if (!paragraph.length) {
return;
}
blocks.push(`<p>${escapeHtml(paragraph.join(" "))}</p>`);
paragraph = [];
};
const flushCode = () => {
if (!codeBuffer.length) {
return;
}
blocks.push(`<pre><code>${escapeHtml(codeBuffer.join("\n"))}</code></pre>`);
codeBuffer = [];
};
lines.forEach((line) => {
if (line.startsWith("```")) {
if (inCode) {
flushCode();
} else {
flushParagraph();
}
inCode = !inCode;
return;
}
if (inCode) {
codeBuffer.push(line);
return;
}
const heading = line.match(/^(#{1,4})\s+(.*)$/);
if (heading) {
flushParagraph();
const level = heading[1].length;
const textValue = heading[2].trim();
const id = slugify(textValue);
headings.push({ level, text: textValue, id });
blocks.push(`<h${level} id="${id}">${escapeHtml(textValue)}</h${level}>`);
return;
}
if (!line.trim()) {
flushParagraph();
return;
}
paragraph.push(line.trim());
});
flushParagraph();
flushCode();
return { html: blocks.join(""), headings };
}
async function loadDoc(url, emptyMessage) {
const text = await apiFetch(url);
const { html, headings } = parseMarkdown(text || "");
dom.apiDocContent.innerHTML = html || `<p>${emptyMessage}</p>`;
dom.apiDocToc.innerHTML = headings.length
? headings
.map(
(item) =>
`<a class="doc-toc-item level-${item.level}" href="#${item.id}">${escapeHtml(item.text)}</a>`,
)
.join("")
: "<div class=\"muted\">未解析到标题</div>";
dom.apiDocToc.querySelectorAll("a").forEach((link) => {
link.addEventListener("click", (event) => {
event.preventDefault();
const id = link.getAttribute("href")?.slice(1);
if (!id) {
return;
}
const target = dom.apiDocContent.querySelector(`#${CSS.escape(id)}`);
if (target) {
const offset = target.getBoundingClientRect().top - dom.apiDocContent.getBoundingClientRect().top;
dom.apiDocContent.scrollBy({ top: offset, behavior: "smooth" });
}
});
});
}
export async function openApiDocDrawer() {
const title = dom.apiDocDrawer.querySelector("h3");
if (title) title.textContent = "API.md";
dom.apiDocDrawer.classList.remove("hidden");
if (state.docDrawerSource !== "api") {
state.docDrawerSource = "api";
await loadDoc("/api/docs/api-md", "API.md 为空");
}
}
export async function openReadmeDrawer() {
const title = dom.apiDocDrawer.querySelector("h3");
if (title) title.textContent = "README.md";
dom.apiDocDrawer.classList.remove("hidden");
if (state.docDrawerSource !== "readme") {
state.docDrawerSource = "readme";
await loadDoc("/api/docs/readme-md", "README.md 为空");
}
}
export function closeApiDocDrawer() {
dom.apiDocDrawer.classList.add("hidden");
}