mirror of
https://github.com/streetwriters/notesnook.git
synced 2026-08-29 10:09:26 +02:00
editor: serialize only the pages that changed
This commit is contained in:
@@ -27,10 +27,12 @@ import {
|
||||
countPages,
|
||||
flattenBlocks,
|
||||
fromFlatPosition,
|
||||
serializeDocumentHTML,
|
||||
toFlatPosition
|
||||
} from "../index.js";
|
||||
import { BlockId } from "../../block-id/block-id.js";
|
||||
import { getTableOfContents } from "../../../utils/toc.js";
|
||||
import { profiler } from "../../../utils/profiler.js";
|
||||
|
||||
const PagedDocument = Node.create({
|
||||
name: "doc",
|
||||
@@ -266,3 +268,58 @@ describe("paging", () => {
|
||||
editor.destroy();
|
||||
});
|
||||
});
|
||||
|
||||
describe("serialization cache", () => {
|
||||
test("matches the plain serializer exactly", async () => {
|
||||
const editor = createEditor(savedNoteHTML(BLOCKS));
|
||||
await created();
|
||||
|
||||
expect(serializeDocumentHTML(editor.state.doc, editor.schema)).toBe(
|
||||
editor.getHTML()
|
||||
);
|
||||
editor.destroy();
|
||||
});
|
||||
|
||||
test("reuses unedited pages and re-serializes only the edited one", async () => {
|
||||
const editor = createEditor(savedNoteHTML(BLOCKS));
|
||||
await created();
|
||||
|
||||
serializeDocumentHTML(editor.state.doc, editor.schema);
|
||||
profiler.enable();
|
||||
editor.commands.setTextSelection(3);
|
||||
editor.commands.insertContent("edited ");
|
||||
serializeDocumentHTML(editor.state.doc, editor.schema);
|
||||
|
||||
const counters = profiler.report().counters;
|
||||
expect(counters["serialize.cacheMiss"]).toBe(1);
|
||||
expect(counters["serialize.cacheHit"]).toBe(2);
|
||||
profiler.disable();
|
||||
profiler.reset();
|
||||
editor.destroy();
|
||||
});
|
||||
|
||||
test("still reflects the edit", async () => {
|
||||
const editor = createEditor(savedNoteHTML(BLOCKS));
|
||||
await created();
|
||||
|
||||
serializeDocumentHTML(editor.state.doc, editor.schema);
|
||||
editor.commands.setTextSelection(3);
|
||||
editor.commands.insertContent("edited ");
|
||||
|
||||
const html = serializeDocumentHTML(editor.state.doc, editor.schema);
|
||||
expect(html).toContain("edited");
|
||||
expect(html).toBe(editor.getHTML());
|
||||
expect(html.match(/<p/g)).toHaveLength(BLOCKS);
|
||||
editor.destroy();
|
||||
});
|
||||
|
||||
test("works without pages too", async () => {
|
||||
const editor = createEditor(savedNoteHTML(20), false);
|
||||
await created();
|
||||
|
||||
expect(serializeDocumentHTML(editor.state.doc, editor.schema)).toBe(
|
||||
editor.getHTML()
|
||||
);
|
||||
editor.destroy();
|
||||
});
|
||||
});
|
||||
|
||||
@@ -87,3 +87,4 @@ export {
|
||||
export { installFlatteningSerializer } from "./serializer.js";
|
||||
export { installPagingParser, uninstallPagingParser } from "./parser.js";
|
||||
export { fromFlatPosition, toFlatPosition } from "./positions.js";
|
||||
export { serializeDocumentHTML } from "./serialize.js";
|
||||
|
||||
72
packages/editor/src/extensions/paging/serialize.ts
Normal file
72
packages/editor/src/extensions/paging/serialize.ts
Normal file
@@ -0,0 +1,72 @@
|
||||
/*
|
||||
This file is part of the Notesnook project (https://notesnook.com/)
|
||||
|
||||
Copyright (C) 2023 Streetwriters (Private) Limited
|
||||
|
||||
This program is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
import {
|
||||
DOMSerializer,
|
||||
Fragment,
|
||||
Node as ProsemirrorNode,
|
||||
Schema
|
||||
} from "@tiptap/pm/model";
|
||||
import { profiler } from "../../utils/profiler.js";
|
||||
import { isPage } from "./split.js";
|
||||
|
||||
const cache = new WeakMap<ProsemirrorNode, string>();
|
||||
|
||||
function fragmentToHTML(fragment: Fragment, serializer: DOMSerializer): string {
|
||||
const container = document.createElement("div");
|
||||
container.appendChild(serializer.serializeFragment(fragment));
|
||||
return container.innerHTML;
|
||||
}
|
||||
|
||||
/**
|
||||
* Serializes the document a top-level node at a time and remembers the result
|
||||
* for each one. ProseMirror nodes are immutable and shared between document
|
||||
* versions, so an unedited node is the same object as before and its HTML can
|
||||
* be reused: a keystroke only re-serializes the node it landed in.
|
||||
*
|
||||
* Pages are serialized as their contents, keeping stored HTML page-free.
|
||||
*/
|
||||
export function serializeDocumentHTML(
|
||||
doc: ProsemirrorNode,
|
||||
schema: Schema
|
||||
): string {
|
||||
const end = profiler.start("serialize.document");
|
||||
const serializer = DOMSerializer.fromSchema(schema);
|
||||
const parts: string[] = [];
|
||||
|
||||
doc.forEach((node) => {
|
||||
const cached = cache.get(node);
|
||||
if (cached !== undefined) {
|
||||
profiler.count("serialize.cacheHit");
|
||||
parts.push(cached);
|
||||
return;
|
||||
}
|
||||
|
||||
profiler.count("serialize.cacheMiss");
|
||||
const html = isPage(node)
|
||||
? fragmentToHTML(node.content, serializer)
|
||||
: fragmentToHTML(Fragment.from(node), serializer);
|
||||
cache.set(node, html);
|
||||
parts.push(html);
|
||||
});
|
||||
|
||||
const html = parts.join("");
|
||||
end();
|
||||
return html;
|
||||
}
|
||||
@@ -494,7 +494,11 @@ export * from "./utils/word-counter.js";
|
||||
export * from "./utils/font.js";
|
||||
export * from "./utils/toc.js";
|
||||
export * from "./utils/profiler.js";
|
||||
export { fromFlatPosition, toFlatPosition } from "./extensions/paging/index.js";
|
||||
export {
|
||||
fromFlatPosition,
|
||||
serializeDocumentHTML,
|
||||
toFlatPosition
|
||||
} from "./extensions/paging/index.js";
|
||||
export * from "./utils/downloader.js";
|
||||
export {
|
||||
useTiptap,
|
||||
|
||||
Reference in New Issue
Block a user