feat(docs): add README.md button opening shared doc drawer
Reuses the existing API.md drawer for README; switching between docs reloads content and updates the drawer title. Backend serves README.md via /api/docs/readme-md. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
parent
c2cac19f7e
commit
45b2317ee8
|
|
@ -21,3 +21,20 @@ pub async fn get_api_md() -> Result<impl IntoResponse, ApiErr> {
|
|||
|
||||
Ok((StatusCode::OK, headers, content))
|
||||
}
|
||||
|
||||
pub async fn get_readme_md() -> Result<impl IntoResponse, ApiErr> {
|
||||
let content = tokio::fs::read_to_string("README.md")
|
||||
.await
|
||||
.map_err(|err| {
|
||||
tracing::error!("Failed to read README.md: {}", err);
|
||||
ApiErr::NotFound("README.md not found".to_string(), None)
|
||||
})?;
|
||||
|
||||
let mut headers = HeaderMap::new();
|
||||
headers.insert(
|
||||
header::CONTENT_TYPE,
|
||||
HeaderValue::from_static("text/markdown; charset=utf-8"),
|
||||
);
|
||||
|
||||
Ok((StatusCode::OK, headers, content))
|
||||
}
|
||||
|
|
|
|||
|
|
@ -280,7 +280,8 @@ fn build_router(state: AppState) -> Router {
|
|||
)
|
||||
.route("/api/logs", get(handler::log::get_logs))
|
||||
.route("/api/logs/stream", get(handler::log::stream_logs))
|
||||
.route("/api/docs/api-md", get(handler::doc::get_api_md));
|
||||
.route("/api/docs/api-md", get(handler::doc::get_api_md))
|
||||
.route("/api/docs/readme-md", get(handler::doc::get_readme_md));
|
||||
|
||||
Router::new()
|
||||
.merge(all_route)
|
||||
|
|
|
|||
|
|
@ -5,6 +5,7 @@
|
|||
<button type="button" class="tab-btn" id="tabConfig">配置</button>
|
||||
</div>
|
||||
<div class="topbar-actions">
|
||||
<button type="button" class="secondary" id="openReadmeDoc">README.md</button>
|
||||
<button type="button" class="secondary" id="openApiDoc">API.md</button>
|
||||
<div class="status" id="statusText">
|
||||
<span class="ws-dot" id="wsDot"></span>
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
import { withStatus } from "./api.js";
|
||||
import { openChart, renderChart } from "./chart.js";
|
||||
import { dom } from "./dom.js";
|
||||
import { closeApiDocDrawer, openApiDocDrawer } from "./docs.js";
|
||||
import { closeApiDocDrawer, openApiDocDrawer, openReadmeDrawer } from "./docs.js";
|
||||
import { loadEvents } from "./events.js";
|
||||
import {
|
||||
applyBatchEquipmentUnit,
|
||||
|
|
@ -130,6 +130,7 @@ function bindEvents() {
|
|||
});
|
||||
});
|
||||
|
||||
dom.openReadmeDocBtn.addEventListener("click", () => withStatus(openReadmeDrawer()));
|
||||
dom.openApiDocBtn.addEventListener("click", () => withStatus(openApiDocDrawer()));
|
||||
dom.closeApiDocBtn.addEventListener("click", closeApiDocDrawer);
|
||||
dom.refreshEventBtn.addEventListener("click", () => withStatus(loadEvents()));
|
||||
|
|
|
|||
|
|
@ -82,11 +82,11 @@ function parseMarkdown(text) {
|
|||
return { html: blocks.join(""), headings };
|
||||
}
|
||||
|
||||
export async function loadApiDoc() {
|
||||
const text = await apiFetch("/api/docs/api-md");
|
||||
async function loadDoc(url, emptyMessage) {
|
||||
const text = await apiFetch(url);
|
||||
const { html, headings } = parseMarkdown(text || "");
|
||||
|
||||
dom.apiDocContent.innerHTML = html || "<p>API.md 为空</p>";
|
||||
dom.apiDocContent.innerHTML = html || `<p>${emptyMessage}</p>`;
|
||||
dom.apiDocToc.innerHTML = headings.length
|
||||
? headings
|
||||
.map(
|
||||
|
|
@ -110,14 +110,25 @@ export async function loadApiDoc() {
|
|||
}
|
||||
});
|
||||
});
|
||||
|
||||
state.apiDocLoaded = true;
|
||||
}
|
||||
|
||||
export async function openApiDocDrawer() {
|
||||
const title = dom.apiDocDrawer.querySelector("h3");
|
||||
if (title) title.textContent = "API.md";
|
||||
dom.apiDocDrawer.classList.remove("hidden");
|
||||
if (!state.apiDocLoaded) {
|
||||
await loadApiDoc();
|
||||
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 为空");
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -81,6 +81,7 @@ export const dom = {
|
|||
batchBindingSignalRole: byId("batchBindingSignalRole"),
|
||||
apiDocToc: byId("apiDocToc"),
|
||||
apiDocContent: byId("apiDocContent"),
|
||||
openReadmeDocBtn: byId("openReadmeDoc"),
|
||||
openApiDocBtn: byId("openApiDoc"),
|
||||
closeApiDocBtn: byId("closeApiDoc"),
|
||||
refreshChartBtn: byId("refreshChart"),
|
||||
|
|
|
|||
|
|
@ -19,7 +19,7 @@ export const state = {
|
|||
chartPointName: "",
|
||||
chartData: [],
|
||||
pointSocket: null,
|
||||
apiDocLoaded: false,
|
||||
docDrawerSource: null, // null | "api" | "readme"
|
||||
runtimes: new Map(), // unit_id -> UnitRuntime
|
||||
activeView: "ops", // "ops" | "config"
|
||||
opsPointEls: new Map(), // point_id -> { pillEl, syncBtns? }
|
||||
|
|
|
|||
Loading…
Reference in New Issue