feat: migrate to react-markdown for robust markdown rendering

- Replace custom markdown parser with react-markdown + remark-gfm
- Fix document link navigation (./ and ../ references)
- Simplify doc viewing flow (direct markdown content instead of parsed structure)
- Update electron main process to only use api folder for docs
- Add blueprint loading from docs/blueprint.md dynamically
- Fix sidebar file selection path matching
- Update preload scripts for new API structure
This commit is contained in:
2026-03-18 18:50:26 +08:00
parent 4898ee5434
commit d66f5b09e6
16 changed files with 2003 additions and 451 deletions

View File

@@ -1,17 +1,24 @@
import { useState, useEffect } from 'react';
import { FileText, Box, FolderOpen } from 'lucide-react';
import { FileText, Box } from 'lucide-react';
import { ApiDocViewer } from './components/ApiDocViewer';
import BlueprintPage from './components/blueprint/BlueprintPage';
import { config } from './config';
const _origLog = console.log;
const _origError = console.error;
const _origWarn = console.warn;
console.log = (...args) => { window.electronAPI?.log('log', ...args); _origLog.apply(console, args); };
console.error = (...args) => { window.electronAPI?.log('error', ...args); _origError.apply(console, args); };
console.warn = (...args) => { window.electronAPI?.log('warn', ...args); _origWarn.apply(console, args); };
type Page = 'docs' | 'blueprint';
declare global {
interface Window {
electronAPI?: {
selectFolder: () => Promise<string | null>;
getDocsPath: () => Promise<string>;
onDocsPathChanged: (callback: (path: string) => void) => void;
listDocsFiles: (basePath: string) => Promise<{ name: string; path: string; relativePath: string }[]>;
readDocFile: (filePath: string) => Promise<string | null>;
log: (level: string, ...args: unknown[]) => void;
};
}
}
@@ -22,41 +29,15 @@ function App() {
useEffect(() => {
document.title = config.projectName;
if (window.electronAPI) {
window.electronAPI.getDocsPath().then(setDocsPath);
window.electronAPI.onDocsPathChanged(setDocsPath);
}
}, []);
const handleSelectFolder = async () => {
if (window.electronAPI) {
const path = await window.electronAPI.selectFolder();
if (path) {
setDocsPath(path);
}
}
};
return (
<div className="h-screen flex flex-col">
<header className="h-12 bg-zinc-900 border-b border-zinc-800 flex items-center px-4 justify-between">
<div className="flex items-center gap-3">
<h1 className="text-sm font-medium text-white">{config.projectName}</h1>
{docsPath && (
<span className="text-xs text-zinc-500">📁 {docsPath}</span>
)}
</div>
<div className="flex items-center gap-2">
{window.electronAPI && (
<button
onClick={handleSelectFolder}
className="flex items-center gap-2 px-3 py-1.5 rounded text-sm text-zinc-400 hover:text-white hover:bg-zinc-800/50"
title="选择文档文件夹"
>
<FolderOpen size={16} />
</button>
)}
<nav className="flex gap-1">
<button
onClick={() => setCurrentPage('docs')}
@@ -84,7 +65,11 @@ function App() {
</div>
</header>
<main className="flex-1 overflow-hidden">
{currentPage === 'docs' ? <ApiDocViewer /> : <BlueprintPage />}
{currentPage === 'docs' ? (
<ApiDocViewer onDocsPathChange={setDocsPath} />
) : (
<BlueprintPage docsPath={docsPath} />
)}
</main>
</div>
);