import { Component, createContext, useContext, createEffect, createMemo, createSignal, } from "solid-js"; import { useLocation } from "@solidjs/router"; import { useJournalStream } from "./components/stores/journalStream"; // 导入组件以注册自定义元素 import "./components"; import { Article, MobileSidebar, DesktopSidebar, DocDialog, DataSourceDialog, RevealManager, } from "./components"; import { generateToc, type FileNode, type TocNode } from "./data-loader"; import { JournalPanel } from "./components/journal"; // --------------------------------------------------------------------------- // Scroll container context – lets child components (FileTree, RevealManager) // find the correct scrollable element instead of relying on window.scroll // --------------------------------------------------------------------------- const ScrollContainerCtx = createContext<() => HTMLElement | undefined>(); /** Access the main content scroll container from any descendant. */ export function useScrollContainer(): () => HTMLElement | undefined { const ctx = useContext(ScrollContainerCtx); return ctx ?? (() => undefined); } // --------------------------------------------------------------------------- // App // --------------------------------------------------------------------------- const App: Component = () => { const location = useLocation(); const stream = useJournalStream(); const [isSidebarOpen, setIsSidebarOpen] = createSignal(false); const [isDocOpen, setIsDocOpen] = createSignal(false); const [isDataSourceOpen, setIsDataSourceOpen] = createSignal(false); const [isJournalOpen, setIsJournalOpen] = createSignal(false); // Sidebar and TOC data — reload when source changes const [fileTree, setFileTree] = createSignal([]); const [pathHeadings, setPathHeadings] = createSignal< Record >({}); const [tocKey, setTocKey] = createSignal(0); let mainRef!: HTMLElement; const loadToc = async () => { const toc = await generateToc(); setFileTree(toc.fileTree); setPathHeadings(toc.pathHeadings); }; // Load TOC on mount and when source changes createEffect(() => { void tocKey(); void loadToc(); }); const handleSourceChanged = () => { setTocKey((k) => k + 1); }; const currentPath = createMemo(() => { // 根据路由加载对应的 markdown 文件 let path = decodeURIComponent(location.pathname); if (!path) path = "/content/"; if (path.endsWith("/")) path += "index"; if (!path.endsWith(".md")) path += ".md"; return path; }); return (
{/* Header */}
{/* 仅在移动端显示菜单按钮 */}

TTRPG Tools

{/* 移动端抽屉式侧边栏 (overlay) */} setIsSidebarOpen(false)} fileTree={fileTree()} pathHeadings={pathHeadings()} onDataSourceOpen={() => setIsDataSourceOpen(true)} /> {/* Body: sidebar + main + journal */}
{/* 桌面端固定侧边栏 (flex child) */} setIsDataSourceOpen(true)} /> {/* Main content — the scrollable region */} mainRef}>
{/* Journal panel wrapper — on desktop: flex child with width transition; on mobile: passes through to JournalPanel's own overlay */}
setIsJournalOpen(false)} />
setIsDocOpen(false)} /> setIsDataSourceOpen(false)} onSourceChanged={handleSourceChanged} />
); }; export default App;