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))
|
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", get(handler::log::get_logs))
|
||||||
.route("/api/logs/stream", get(handler::log::stream_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()
|
Router::new()
|
||||||
.merge(all_route)
|
.merge(all_route)
|
||||||
|
|
|
||||||
|
|
@ -5,6 +5,7 @@
|
||||||
<button type="button" class="tab-btn" id="tabConfig">配置</button>
|
<button type="button" class="tab-btn" id="tabConfig">配置</button>
|
||||||
</div>
|
</div>
|
||||||
<div class="topbar-actions">
|
<div class="topbar-actions">
|
||||||
|
<button type="button" class="secondary" id="openReadmeDoc">README.md</button>
|
||||||
<button type="button" class="secondary" id="openApiDoc">API.md</button>
|
<button type="button" class="secondary" id="openApiDoc">API.md</button>
|
||||||
<div class="status" id="statusText">
|
<div class="status" id="statusText">
|
||||||
<span class="ws-dot" id="wsDot"></span>
|
<span class="ws-dot" id="wsDot"></span>
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,7 @@
|
||||||
import { withStatus } from "./api.js";
|
import { withStatus } from "./api.js";
|
||||||
import { openChart, renderChart } from "./chart.js";
|
import { openChart, renderChart } from "./chart.js";
|
||||||
import { dom } from "./dom.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 { loadEvents } from "./events.js";
|
||||||
import {
|
import {
|
||||||
applyBatchEquipmentUnit,
|
applyBatchEquipmentUnit,
|
||||||
|
|
@ -130,6 +130,7 @@ function bindEvents() {
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
dom.openReadmeDocBtn.addEventListener("click", () => withStatus(openReadmeDrawer()));
|
||||||
dom.openApiDocBtn.addEventListener("click", () => withStatus(openApiDocDrawer()));
|
dom.openApiDocBtn.addEventListener("click", () => withStatus(openApiDocDrawer()));
|
||||||
dom.closeApiDocBtn.addEventListener("click", closeApiDocDrawer);
|
dom.closeApiDocBtn.addEventListener("click", closeApiDocDrawer);
|
||||||
dom.refreshEventBtn.addEventListener("click", () => withStatus(loadEvents()));
|
dom.refreshEventBtn.addEventListener("click", () => withStatus(loadEvents()));
|
||||||
|
|
|
||||||
|
|
@ -82,11 +82,11 @@ function parseMarkdown(text) {
|
||||||
return { html: blocks.join(""), headings };
|
return { html: blocks.join(""), headings };
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function loadApiDoc() {
|
async function loadDoc(url, emptyMessage) {
|
||||||
const text = await apiFetch("/api/docs/api-md");
|
const text = await apiFetch(url);
|
||||||
const { html, headings } = parseMarkdown(text || "");
|
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
|
dom.apiDocToc.innerHTML = headings.length
|
||||||
? headings
|
? headings
|
||||||
.map(
|
.map(
|
||||||
|
|
@ -110,14 +110,25 @@ export async function loadApiDoc() {
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
state.apiDocLoaded = true;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function openApiDocDrawer() {
|
export async function openApiDocDrawer() {
|
||||||
|
const title = dom.apiDocDrawer.querySelector("h3");
|
||||||
|
if (title) title.textContent = "API.md";
|
||||||
dom.apiDocDrawer.classList.remove("hidden");
|
dom.apiDocDrawer.classList.remove("hidden");
|
||||||
if (!state.apiDocLoaded) {
|
if (state.docDrawerSource !== "api") {
|
||||||
await loadApiDoc();
|
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"),
|
batchBindingSignalRole: byId("batchBindingSignalRole"),
|
||||||
apiDocToc: byId("apiDocToc"),
|
apiDocToc: byId("apiDocToc"),
|
||||||
apiDocContent: byId("apiDocContent"),
|
apiDocContent: byId("apiDocContent"),
|
||||||
|
openReadmeDocBtn: byId("openReadmeDoc"),
|
||||||
openApiDocBtn: byId("openApiDoc"),
|
openApiDocBtn: byId("openApiDoc"),
|
||||||
closeApiDocBtn: byId("closeApiDoc"),
|
closeApiDocBtn: byId("closeApiDoc"),
|
||||||
refreshChartBtn: byId("refreshChart"),
|
refreshChartBtn: byId("refreshChart"),
|
||||||
|
|
|
||||||
|
|
@ -19,7 +19,7 @@ export const state = {
|
||||||
chartPointName: "",
|
chartPointName: "",
|
||||||
chartData: [],
|
chartData: [],
|
||||||
pointSocket: null,
|
pointSocket: null,
|
||||||
apiDocLoaded: false,
|
docDrawerSource: null, // null | "api" | "readme"
|
||||||
runtimes: new Map(), // unit_id -> UnitRuntime
|
runtimes: new Map(), // unit_id -> UnitRuntime
|
||||||
activeView: "ops", // "ops" | "config"
|
activeView: "ops", // "ops" | "config"
|
||||||
opsPointEls: new Map(), // point_id -> { pillEl, syncBtns? }
|
opsPointEls: new Map(), // point_id -> { pillEl, syncBtns? }
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue