refactor(renderer): Step 7 plain state object; fix(export): ZIP emits Markdown for native chapters
This commit is contained in:
parent
b80aa798b2
commit
f1ee73f2a1
4 changed files with 18 additions and 70 deletions
|
|
@ -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<Buffer> {
|
|||
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<Buffer> {
|
|||
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" });
|
||||
|
|
|
|||
|
|
@ -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";
|
||||
|
|
|
|||
|
|
@ -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<void> {
|
||||
if (!state.currentBook) return;
|
||||
const confirmed = await confirmMessage(
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue