diff --git a/src/main/export.ts b/src/main/export.ts index 85988f7..b4ebb96 100644 --- a/src/main/export.ts +++ b/src/main/export.ts @@ -1,5 +1,4 @@ import { ZipArchive } from "archiver"; -import fs from "fs"; import path from "path"; import { loadBook } from "./project.js"; import { listChaptersInOrder, loadChapterDoc } from "./chapters.js"; @@ -52,13 +51,17 @@ export async function buildZip(bookPath: string): Promise { archive.append(combineMarkdown(bookPath), { name: "book.md" }); for (const ch of chapters) { + // Per-chapter entries are readable Markdown generated from the native + // document model (same source as book.md). Raw storage files are never + // leaked: a native chapter's .json would otherwise land in the bundle + // as opaque JSON next to readable .md files. let content = ""; try { - content = fs.readFileSync(path.join(bookPath, ch.file), "utf-8"); + content = nativeToMarkdown(loadChapterDoc(bookPath, ch.id)); } catch { content = ""; } - const entryName = path.posix.join("chapters", path.basename(ch.file)); + const entryName = path.posix.join("chapters", `${ch.id}.md`); archive.append(content, { name: entryName }); } @@ -71,7 +74,7 @@ export async function buildZip(bookPath: string): Promise { chapters: chapters.map((c) => ({ id: c.id, title: c.title, - file: path.basename(c.file), + file: `${c.id}.md`, })), }; archive.append(JSON.stringify(manifest, null, 2), { name: "manifest.json" }); diff --git a/src/renderer/book/editor.ts b/src/renderer/book/editor.ts index 3474d82..0459e31 100644 --- a/src/renderer/book/editor.ts +++ b/src/renderer/book/editor.ts @@ -1,10 +1,6 @@ // The chapter editing surface: main Tiptap editor instance, dirty-tracking + // autosave engine, chapter selection (with stale-load guard), zoom, and the // writing-statistics modal. -// -// Storage note: state.dirty / state.loadedDocJson are the shared contract -// fields; until Step 7 they are backed by the orchestrator's closure locals -// through the accessor bridge, so writes here are visible to every module. import type { Editor } from "@tiptap/core"; import type { BookCtx } from "./types.js"; diff --git a/src/renderer/book/exports.ts b/src/renderer/book/exports.ts index 60e4c3f..993baa6 100644 --- a/src/renderer/book/exports.ts +++ b/src/renderer/book/exports.ts @@ -96,8 +96,7 @@ export function createExportsFeature(ctx: BookCtx): ExportsFeature { // Explicitly migrate the whole book to the native document format. The main // process performs a recovery backup and leaves the original .md files in - // place; after this, chapters are stored as native JSON but the (still - // Markdown-based) editor and exports keep working via the format bridge. + // place; after this, chapters are stored as native JSON. async function migrateBookNative(): Promise { if (!state.currentBook) return; const confirmed = await confirmMessage( diff --git a/src/renderer/book/index.ts b/src/renderer/book/index.ts index c35df40..1cf79ed 100644 --- a/src/renderer/book/index.ts +++ b/src/renderer/book/index.ts @@ -1,6 +1,5 @@ -import type { BookMeta, ChapterComment } from "../../main/project.js"; -import { alertMessage, confirmMessage, promptInput } from "../dialog.js"; -import type { ChapterEntry, FolioAPI, Recent } from "./types.js"; +import type { FolioAPI } from "./types.js"; +import { alertMessage, confirmMessage } from "../dialog.js"; import type { BookCtx, BookHooks, SharedState } from "./types.js"; import { createEditorFeature } from "./editor.js"; import { createToolbarFeature } from "./toolbar.js"; @@ -14,41 +13,21 @@ import { createSettingsFeature } from "./settings.js"; import { $, basename, - closeEl, - hideUndoToast, - openEl, - selectedText, - showUndoToast, showToast, } from "./ui.js"; export type { ChapterSnapshot, FolioAPI } from "./types.js"; export function initBook(api: FolioAPI) { - let currentBook: { path: string; title: string } | null = null; - let meta: BookMeta | null = null; - let ordered: ChapterEntry[] = []; - let selectedId: string | null = null; - let dirty = false; - let loadedDocJson = ""; - - // ---- P7 decomposition bridge ------------------------------------------- - // Temporary: exposes these closure locals through the SharedState contract so - // extracted feature modules share one source of truth. Removed in Step 7 when - // the last feature leaves and state becomes a plain object literal. + // Shared mutable session state. Plain object literal: every feature module + // receives the same reference via ctx, so mutations stay visible everywhere. const state: SharedState = { - get currentBook() { return currentBook; }, - set currentBook(v) { currentBook = v; }, - get meta() { return meta; }, - set meta(v) { meta = v; }, - get ordered() { return ordered; }, - set ordered(v) { ordered = v; }, - get selectedId() { return selectedId; }, - set selectedId(v) { selectedId = v; }, - get dirty() { return dirty; }, - set dirty(v) { dirty = v; }, - get loadedDocJson() { return loadedDocJson; }, - set loadedDocJson(v) { loadedDocJson = v; }, + currentBook: null, + meta: null, + ordered: [], + selectedId: null, + dirty: false, + loadedDocJson: "", }; const hooks = {} as BookHooks; const ctx: BookCtx = { api, state, hooks }; @@ -110,35 +89,6 @@ export function initBook(api: FolioAPI) { // Wire comment facade from the extracted module. hooks.updateCommentsBadge = comments.updateCommentsBadge; - // ---- element consts (kept from the original file) ---- - const editorPlaceholder = $("editorPlaceholder"); - const chapterView = $("chapterView"); - const chapterTitleEl = $("chapterTitle"); - const saveStateEl = $("saveState"); - const exportBtn = $("exportBtn") as HTMLButtonElement; - const exportZipBtn = $("exportZipBtn") as HTMLButtonElement; - const projectBar = $("projectBar"); - const fullBookNav = $("fullBookNav"); - const fullBookBtn = $("fullBookBtn") as HTMLButtonElement; - const exportMenuToggle = $("exportMenuToggle") as HTMLButtonElement; - const exportMenu = $("exportMenu"); - const exportDropdown = $("exportDropdown"); - - const welcomeEl = $("welcome"); - const recentList = $("recentList") as HTMLUListElement; - - // ---- welcome / recents logic (moved from inline into createWelcomeFeature) ---- - // The welcome module owns its own DOM chrome and wiring; here we only keep the - // top-level constructor call and the bridge assignments it already set above. - - // ---- export workflow (whole book vs selected chapters) ---- - // The exports module owns its own DOM chrome and wiring; here we only keep the - // top-level constructor call and the bridge assignments it already set above. - - // ---- comments / annotations ---- - // The comments module owns its own DOM chrome and wiring; here we only keep the - // top-level constructor call and the bridge assignments it already set above. - // ---- update flow ---- // "Check for Updates…" (File menu / welcome button): query the release // server, then offer a one-click install. The app downloads the tarball,