feat(viewer): Add data source dialog
Introduce a dialog for switching between built-in content and a user-selected local folder. Persist the folder handle in IndexedDB so the selection survives page refreshes. Scan the folder for .md, .yarn, and .csv files to build the file index. Update App and Sidebar to pass file tree and heading data, and add TypeScript declarations for the File System Access API.
This commit is contained in:
+31
-17
@@ -6,6 +6,8 @@ import { FileTreeNode, HeadingNode } from "./FileTree";
|
||||
export interface SidebarProps {
|
||||
isOpen: boolean;
|
||||
onClose: () => void;
|
||||
fileTree?: FileNode[];
|
||||
pathHeadings?: Record<string, TocNode[]>;
|
||||
}
|
||||
|
||||
interface SidebarContentProps {
|
||||
@@ -25,7 +27,9 @@ const SidebarContent: Component<SidebarContentProps> = (props) => {
|
||||
// 响应式获取当前文件的标题列表
|
||||
const currentFileHeadings = createMemo(() => {
|
||||
const pathname = decodeURIComponent(location.pathname);
|
||||
return props.pathHeadings[pathname] || props.pathHeadings[`${pathname}.md`] || [];
|
||||
return (
|
||||
props.pathHeadings[pathname] || props.pathHeadings[`${pathname}.md`] || []
|
||||
);
|
||||
});
|
||||
|
||||
return (
|
||||
@@ -68,11 +72,7 @@ const SidebarContent: Component<SidebarContentProps> = (props) => {
|
||||
本页
|
||||
</h3>
|
||||
{currentFileHeadings().map((node) => (
|
||||
<HeadingNode
|
||||
node={node}
|
||||
basePath={location.pathname}
|
||||
depth={0}
|
||||
/>
|
||||
<HeadingNode node={node} basePath={location.pathname} depth={0} />
|
||||
))}
|
||||
</div>
|
||||
</Show>
|
||||
@@ -85,14 +85,20 @@ const SidebarContent: Component<SidebarContentProps> = (props) => {
|
||||
*/
|
||||
export const MobileSidebar: Component<SidebarProps> = (props) => {
|
||||
const location = useLocation();
|
||||
const [fileTree, setFileTree] = createSignal<FileNode[]>([]);
|
||||
const [pathHeadings, setPathHeadings] = createSignal<Record<string, TocNode[]>>({});
|
||||
const [selfFileTree, setSelfFileTree] = createSignal<FileNode[]>([]);
|
||||
const [selfPathHeadings, setSelfPathHeadings] = createSignal<
|
||||
Record<string, TocNode[]>
|
||||
>({});
|
||||
|
||||
// 加载目录数据
|
||||
const fileTree = () => props.fileTree ?? selfFileTree();
|
||||
const pathHeadings = () => props.pathHeadings ?? selfPathHeadings();
|
||||
|
||||
// 加载目录数据 (only if props not provided)
|
||||
onMount(async () => {
|
||||
if (props.fileTree && props.pathHeadings) return;
|
||||
const toc = await generateToc();
|
||||
setFileTree(toc.fileTree);
|
||||
setPathHeadings(toc.pathHeadings);
|
||||
setSelfFileTree(toc.fileTree);
|
||||
setSelfPathHeadings(toc.pathHeadings);
|
||||
});
|
||||
|
||||
return (
|
||||
@@ -124,16 +130,24 @@ export const MobileSidebar: Component<SidebarProps> = (props) => {
|
||||
/**
|
||||
* 桌面端固定侧边栏
|
||||
*/
|
||||
export const DesktopSidebar: Component<{}> = () => {
|
||||
export const DesktopSidebar: Component<{
|
||||
fileTree?: FileNode[];
|
||||
pathHeadings?: Record<string, TocNode[]>;
|
||||
}> = (props) => {
|
||||
const location = useLocation();
|
||||
const [fileTree, setFileTree] = createSignal<FileNode[]>([]);
|
||||
const [pathHeadings, setPathHeadings] = createSignal<Record<string, TocNode[]>>({});
|
||||
const [selfFileTree, setSelfFileTree] = createSignal<FileNode[]>([]);
|
||||
const [selfPathHeadings, setSelfPathHeadings] = createSignal<
|
||||
Record<string, TocNode[]>
|
||||
>({});
|
||||
|
||||
const fileTree = () => props.fileTree ?? selfFileTree();
|
||||
const pathHeadings = () => props.pathHeadings ?? selfPathHeadings();
|
||||
|
||||
// 加载目录数据
|
||||
onMount(async () => {
|
||||
if (props.fileTree && props.pathHeadings) return;
|
||||
const toc = await generateToc();
|
||||
setFileTree(toc.fileTree);
|
||||
setPathHeadings(toc.pathHeadings);
|
||||
setSelfFileTree(toc.fileTree);
|
||||
setSelfPathHeadings(toc.pathHeadings);
|
||||
});
|
||||
|
||||
return (
|
||||
|
||||
Reference in New Issue
Block a user