From 3d0a2b82ba51758ce88054fffad835936b80a599 Mon Sep 17 00:00:00 2001 From: Huang Xin Date: Wed, 25 Dec 2024 22:02:01 +0100 Subject: [PATCH] Fix Bookmarks restore from deletion (#43) --- .../app/reader/components/BookmarkToggler.tsx | 29 ++++++++++++------- .../src/app/reader/hooks/useNotesSync.ts | 12 ++++++-- apps/readest-app/src/pages/api/sync.ts | 4 +++ apps/readest-app/src/services/appService.ts | 2 +- apps/readest-app/src/types/book.ts | 4 +-- apps/readest-app/src/utils/transform.ts | 9 +++--- 6 files changed, 41 insertions(+), 19 deletions(-) diff --git a/apps/readest-app/src/app/reader/components/BookmarkToggler.tsx b/apps/readest-app/src/app/reader/components/BookmarkToggler.tsx index ff49fe1e..43ee2515 100644 --- a/apps/readest-app/src/app/reader/components/BookmarkToggler.tsx +++ b/apps/readest-app/src/app/reader/components/BookmarkToggler.tsx @@ -41,7 +41,15 @@ const BookmarkToggler: React.FC = ({ bookKey }) => { createdAt: Date.now(), updatedAt: Date.now(), }; - bookmarks.push(bookmark); + const existingBookmark = bookmarks.find( + (item) => item.type === 'bookmark' && item.cfi === cfi, + ); + if (existingBookmark) { + existingBookmark.deletedAt = null; + existingBookmark.updatedAt = Date.now(); + } else { + bookmarks.push(bookmark); + } const updatedConfig = updateBooknotes(bookKey, bookmarks); if (updatedConfig) { saveConfig(envConfig, bookKey, updatedConfig, settings); @@ -50,14 +58,15 @@ const BookmarkToggler: React.FC = ({ bookKey }) => { setIsBookmarked(false); const start = CFI.collapse(cfi); const end = CFI.collapse(cfi, true); - const updatedConfig = updateBooknotes( - bookKey, - bookmarks.filter( - (item) => - item.type !== 'bookmark' || - CFI.compare(start, item.cfi) * CFI.compare(end, item.cfi) > 0, - ), - ); + bookmarks.forEach((item) => { + if ( + item.type === 'bookmark' && + CFI.compare(start, item.cfi) * CFI.compare(end, item.cfi) <= 0 + ) { + item.deletedAt = Date.now(); + } + }); + const updatedConfig = updateBooknotes(bookKey, bookmarks); if (updatedConfig) { saveConfig(envConfig, bookKey, updatedConfig, settings); } @@ -72,7 +81,7 @@ const BookmarkToggler: React.FC = ({ bookKey }) => { const start = CFI.collapse(cfi); const end = CFI.collapse(cfi, true); const locationBookmarked = booknotes - .filter((booknote) => booknote.type === 'bookmark') + .filter((booknote) => booknote.type === 'bookmark' && !booknote.deletedAt) .some((item) => CFI.compare(start, item.cfi) * CFI.compare(end, item.cfi) <= 0); setIsBookmarked(locationBookmarked); setBookmarkRibbonVisibility(bookKey, locationBookmarked); diff --git a/apps/readest-app/src/app/reader/hooks/useNotesSync.ts b/apps/readest-app/src/app/reader/hooks/useNotesSync.ts index e96ac67e..13fcc768 100644 --- a/apps/readest-app/src/app/reader/hooks/useNotesSync.ts +++ b/apps/readest-app/src/app/reader/hooks/useNotesSync.ts @@ -20,8 +20,9 @@ export const useNotesSync = (bookKey: string) => { const lastSyncTime = useRef(0); const syncTimeoutRef = useRef | null>(null); - useEffect(() => { - if (!config?.location || !user) return; + + const getNewNotes = () => { + if (!config?.location || !user) return []; const bookNotes = config.booknotes ?? []; const newNotes = bookNotes.filter( (note) => lastSyncedAtNotes < note.updatedAt || lastSyncedAtNotes < (note.deletedAt ?? 0), @@ -29,16 +30,23 @@ export const useNotesSync = (bookKey: string) => { newNotes.forEach((note) => { note.bookHash = bookHash; }); + return newNotes; + }; + + useEffect(() => { + if (!config?.location || !user) return; const now = Date.now(); const timeSinceLastSync = now - lastSyncTime.current; if (timeSinceLastSync > SYNC_NOTES_INTERVAL_SEC * 1000) { lastSyncTime.current = now; + const newNotes = getNewNotes(); syncNotes(newNotes, bookHash, 'both'); } else { if (syncTimeoutRef.current) clearTimeout(syncTimeoutRef.current); syncTimeoutRef.current = setTimeout( () => { lastSyncTime.current = Date.now(); + const newNotes = getNewNotes(); syncNotes(newNotes, bookHash, 'both'); syncTimeoutRef.current = null; }, diff --git a/apps/readest-app/src/pages/api/sync.ts b/apps/readest-app/src/pages/api/sync.ts index 37c58f3e..4dd0de7a 100644 --- a/apps/readest-app/src/pages/api/sync.ts +++ b/apps/readest-app/src/pages/api/sync.ts @@ -150,6 +150,8 @@ export async function POST(req: NextRequest) { } if (!serverData) { + // use server updated_at for new records + dbRec.updated_at = new Date().toISOString(); const { data: inserted, error: insertError } = await supabase .from(table) .insert(dbRec) @@ -171,6 +173,8 @@ export async function POST(req: NextRequest) { clientDeletedAt > serverDeletedAt || clientUpdatedAt > serverUpdatedAt; if (clientIsNewer) { + // use server updated_at for updated records + dbRec.updated_at = new Date().toISOString(); const { data: updated, error: updateError } = await supabase .from(table) .update(dbRec) diff --git a/apps/readest-app/src/services/appService.ts b/apps/readest-app/src/services/appService.ts index d5121bfd..50adc86a 100644 --- a/apps/readest-app/src/services/appService.ts +++ b/apps/readest-app/src/services/appService.ts @@ -132,7 +132,7 @@ export abstract class BaseAppService implements AppService { const existingBook = books.filter((b) => b.hash === hash)[0]; if (existingBook) { if (existingBook.deletedAt) { - delete existingBook.deletedAt; + existingBook.deletedAt = null; } existingBook.updatedAt = Date.now(); } diff --git a/apps/readest-app/src/types/book.ts b/apps/readest-app/src/types/book.ts index b42b78f2..d6ad182b 100644 --- a/apps/readest-app/src/types/book.ts +++ b/apps/readest-app/src/types/book.ts @@ -16,7 +16,7 @@ export interface Book { createdAt: number; updatedAt: number; - deletedAt?: number; + deletedAt?: number | null; lastUpdated?: number; // deprecated in favor of updatedAt } @@ -39,7 +39,7 @@ export interface BookNote { createdAt: number; updatedAt: number; - deletedAt?: number; + deletedAt?: number | null; } export interface BookLayout { diff --git a/apps/readest-app/src/utils/transform.ts b/apps/readest-app/src/utils/transform.ts index e637e49d..6264cfcc 100644 --- a/apps/readest-app/src/utils/transform.ts +++ b/apps/readest-app/src/utils/transform.ts @@ -50,7 +50,7 @@ export const transformBookToDB = (book: unknown, userId: string): DBBook => { tags: tags && JSON.stringify(tags), created_at: new Date(createdAt).toISOString(), updated_at: new Date(updatedAt).toISOString(), - deleted_at: deletedAt ? new Date(deletedAt).toISOString() : undefined, + deleted_at: deletedAt ? new Date(deletedAt).toISOString() : null, }; }; @@ -67,7 +67,7 @@ export const transformBookFromDB = (dbBook: DBBook): Book => { tags: tags && JSON.parse(tags), createdAt: new Date(created_at!).getTime(), updatedAt: new Date(updated_at!).getTime(), - deletedAt: deleted_at ? new Date(deleted_at!).getTime() : undefined, + deletedAt: deleted_at ? new Date(deleted_at!).getTime() : null, }; }; @@ -87,7 +87,8 @@ export const transformBookNoteToDB = (bookNote: unknown, userId: string): DBBook note, created_at: new Date(createdAt).toISOString(), updated_at: new Date(updatedAt).toISOString(), - deleted_at: deletedAt ? new Date(deletedAt).toISOString() : undefined, + // note that only null deleted_at is updated to the database, undefined is not + deleted_at: deletedAt ? new Date(deletedAt).toISOString() : null, }; }; @@ -106,6 +107,6 @@ export const transformBookNoteFromDB = (dbBookNote: DBBookNote): BookNote => { note, createdAt: new Date(created_at!).getTime(), updatedAt: new Date(updated_at!).getTime(), - deletedAt: deleted_at ? new Date(deleted_at!).getTime() : undefined, + deletedAt: deleted_at ? new Date(deleted_at!).getTime() : null, }; };