diff --git a/src/components/EditorView.tsx b/src/components/EditorView.tsx
index e98d81b..ec03970 100644
--- a/src/components/EditorView.tsx
+++ b/src/components/EditorView.tsx
@@ -274,6 +274,9 @@ export function EditorView() {
content={chapter.content}
onChange={(content) => setChapterContent(chapter.id, content)}
onReady={setEditor}
+ onContentError={() =>
+ setNotice("This chapter contains content Margin couldn't fully read; editing may drop the unrecognized parts.")
+ }
/>
diff --git a/src/editor/Editor.tsx b/src/editor/Editor.tsx
index 972f96f..0a03738 100644
--- a/src/editor/Editor.tsx
+++ b/src/editor/Editor.tsx
@@ -10,11 +10,14 @@ interface EditorProps {
content: JSONContent;
onChange: (content: JSONContent) => void;
onReady: (editor: TiptapEditor | null) => void;
+ onContentError: (error: Error) => void;
}
-export function Editor({ bookId, chapterId, content, onChange, onReady }: EditorProps) {
+export function Editor({ bookId, chapterId, content, onChange, onReady, onContentError }: EditorProps) {
const onChangeRef = useRef(onChange);
onChangeRef.current = onChange;
+ const onContentErrorRef = useRef(onContentError);
+ onContentErrorRef.current = onContentError;
const latest = useRef(null);
@@ -22,7 +25,9 @@ export function Editor({ bookId, chapterId, content, onChange, onReady }: Editor
extensions: editorExtensions,
content,
immediatelyRender: false,
+ enableContentCheck: true,
editorProps: { attributes: { class: "prose" } },
+ onContentError: ({ error }) => onContentErrorRef.current(error),
onUpdate: ({ editor }) => onChangeRef.current(editor.getJSON()),
});
diff --git a/src/library.ts b/src/library.ts
index cdd84ef..e3a145e 100644
--- a/src/library.ts
+++ b/src/library.ts
@@ -1,6 +1,6 @@
import { invoke } from "@tauri-apps/api/core";
import { isDesktop } from "./ipc";
-import { type Book, createBook, starterBook } from "./model/book";
+import { type Book, SCHEMA_VERSION, createBook, schemaVersion, starterBook } from "./model/book";
export interface BookSummary {
id: string;
@@ -16,11 +16,16 @@ export async function listBooks(): Promise {
export async function loadBook(id: string): Promise {
const contents = await invoke("load_book", { id });
+ let book: Book;
try {
- return JSON.parse(contents) as Book;
+ book = JSON.parse(contents) as Book;
} catch {
throw new Error("the book file is corrupt or unreadable");
}
+ if (schemaVersion(book.schema) > SCHEMA_VERSION) {
+ throw new Error("this book was made with a newer version of Margin; update the app to open it");
+ }
+ return book;
}
export async function saveBook(book: Book): Promise {
diff --git a/src/model/book.ts b/src/model/book.ts
index dc8017e..4e78be3 100644
--- a/src/model/book.ts
+++ b/src/model/book.ts
@@ -115,6 +115,14 @@ export interface Book {
chapters: Chapter[];
}
+export const SCHEMA_VERSION = 1;
+
+export function schemaVersion(schema: unknown): number {
+ if (typeof schema !== "string") return 0;
+ const match = /^margin\/(\d+)$/.exec(schema);
+ return match ? Number(match[1]) : 0;
+}
+
function paragraph(text: string): JSONContent {
return { type: "paragraph", content: [{ type: "text", text }] };
}