mirror of
https://github.com/priyanshujain/margin.git
synced 2026-10-02 11:07:04 +00:00
fix(persist): schema-version guard and editor content check
A book file's declared schema was never read, so a file from a newer app version would be opened, normalized and autosaved over in the old format. Refuse to open books whose schema is newer than supported. Enable the editor content check so chapters with unreadable content warn the user instead of silently loading stripped content that the next edit would persist. Claude-Session: https://claude.ai/code/session_01RzTWrLiy4zGuw2hZSQGamk
This commit is contained in:
1 parent
b63951f814
commit
608ba2f21b
4 files changed
+24
-3
No files matched your search
@@ -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.")
|
||||
}
|
||||
/>
|
||||
</article>
|
||||
<FloatingToolbar editor={editor} />
|
||||
|
||||
@@ -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<ChapterPosition | null>(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()),
|
||||
});
|
||||
|
||||
|
||||
+7
-2
@@ -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<BookSummary[]> {
|
||||
|
||||
export async function loadBook(id: string): Promise<Book> {
|
||||
const contents = await invoke<string>("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<void> {
|
||||
|
||||
@@ -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 }] };
|
||||
}
|
||||
|
||||
Reference in new issue
Block a user