From 51bb6cdab9658db2288709b181a4e7301b9609c0 Mon Sep 17 00:00:00 2001 From: Youwes09 Date: Thu, 2 Apr 2026 18:07:49 -0500 Subject: [PATCH] Feat: Markers --- Todo | 1 + src/components/pages/SeriesDetail.svelte | 22 ++- src/components/reader/Reader.svelte | 206 ++++++++++++++++++++-- src/components/shared/MarkersPanel.svelte | 198 +++++++++++++++++++++ src/lib/keybinds.ts | 3 + src/store/state.svelte.ts | 62 ++++++- 6 files changed, 476 insertions(+), 16 deletions(-) create mode 100644 src/components/shared/MarkersPanel.svelte diff --git a/Todo b/Todo index d02d9c2..befe78e 100644 --- a/Todo +++ b/Todo @@ -17,6 +17,7 @@ Minor Revisions: Priority Bugs: - Cache ALL Cover Pictures & Details for Manga in Library - Fix Library Build not Updating + - Check Auth System (Only Supports Basic-Auth) General/Misc Bugs: diff --git a/src/components/pages/SeriesDetail.svelte b/src/components/pages/SeriesDetail.svelte index eeaffb6..da5a623 100644 --- a/src/components/pages/SeriesDetail.svelte +++ b/src/components/pages/SeriesDetail.svelte @@ -1,11 +1,11 @@ + +
+
+
+ + Markers + {#if markers.length > 0} + {markers.length} + {/if} +
+ +
+ +
+ {#if grouped.length === 0} +
+ +

No markers yet

+

Mark pages while reading with the marker button or keybind

+
+ {:else} + {#each grouped as group} +
+
+ {group.chapterName} + {group.items.length} +
+ {#each group.items as m (m.id)} +
+
+
+ {#if editingId === m.id} +
+
+ {#each Object.entries(COLOR_HEX) as [c, hex]} + + {/each} +
+ +
+ + +
+
+ {:else} + +
+ + +
+ {/if} +
+
+ {/each} +
+ {/each} + {/if} +
+
+ + diff --git a/src/lib/keybinds.ts b/src/lib/keybinds.ts index bb4f25e..51e1353 100644 --- a/src/lib/keybinds.ts +++ b/src/lib/keybinds.ts @@ -13,6 +13,7 @@ export interface Keybinds { toggleFullscreen: string; openSettings: string; toggleBookmark: string; + toggleMarker: string; } export const DEFAULT_KEYBINDS: Keybinds = { @@ -28,6 +29,7 @@ export const DEFAULT_KEYBINDS: Keybinds = { toggleFullscreen: "f", openSettings: "o", toggleBookmark: "m", + toggleMarker: "n", }; export const KEYBIND_LABELS: Record = { @@ -43,6 +45,7 @@ export const KEYBIND_LABELS: Record = { toggleFullscreen: "Toggle fullscreen", openSettings: "Open settings", toggleBookmark: "Toggle bookmark", + toggleMarker: "Toggle marker", }; export function eventToKeybind(e: KeyboardEvent): string { diff --git a/src/store/state.svelte.ts b/src/store/state.svelte.ts index 330032f..3fb614c 100644 --- a/src/store/state.svelte.ts +++ b/src/store/state.svelte.ts @@ -32,7 +32,8 @@ export type LibraryContentFilter = | "unread" | "started" | "downloaded" - | "bookmarked"; + | "bookmarked" + | "marked"; export type BuiltinTheme = "dark" | "high-contrast" | "light" | "light-contrast" | "midnight" | "warm"; export type Theme = BuiltinTheme | string; @@ -119,6 +120,22 @@ export interface BookmarkEntry { label?: string; } +export type MarkerColor = "yellow" | "red" | "blue" | "green" | "purple"; + +export interface MarkerEntry { + id: string; + mangaId: number; + mangaTitle: string; + thumbnailUrl: string; + chapterId: number; + chapterName: string; + pageNumber: number; + note: string; + color: MarkerColor; + createdAt: number; + updatedAt?: number; +} + export interface ReadLogEntry { mangaId: number; chapterId: number; @@ -407,6 +424,7 @@ class Store { history: HistoryEntry[] = $state(saved?.history ?? []); readLog: ReadLogEntry[] = $state(saved?.readLog ?? []); bookmarks: BookmarkEntry[] = $state(saved?.bookmarks ?? []); + markers: MarkerEntry[] = $state(saved?.markers ?? []); readingStats: ReadingStats = $state(mergeStats(saved)); settings: Settings = $state(mergeSettings(saved)); readerSessionId: number = $state(0); @@ -437,6 +455,7 @@ class Store { $effect(() => { persist({ history: this.history }); }); $effect(() => { persist({ readLog: this.readLog }); }); $effect(() => { persist({ bookmarks: this.bookmarks }); }); + $effect(() => { persist({ markers: this.markers }); }); $effect(() => { persist({ readingStats: this.readingStats }); }); $effect(() => { persist({ settings: this.settings }); }); }); @@ -524,6 +543,39 @@ class Store { return this.bookmarks.find(b => b.chapterId === chapterId); } + addMarker(entry: Omit): string { + const id = genId(); + const marker: MarkerEntry = { ...entry, id, createdAt: Date.now() }; + this.markers = [marker, ...this.markers].slice(0, 2000); + return id; + } + + updateMarker(id: string, patch: Partial>) { + this.markers = this.markers.map(m => + m.id === id ? { ...m, ...patch, updatedAt: Date.now() } : m + ); + } + + removeMarker(id: string) { + this.markers = this.markers.filter(m => m.id !== id); + } + + getMarkersForPage(chapterId: number, page: number): MarkerEntry[] { + return this.markers.filter(m => m.chapterId === chapterId && m.pageNumber === page); + } + + getMarkersForChapter(chapterId: number): MarkerEntry[] { + return this.markers.filter(m => m.chapterId === chapterId); + } + + getMarkersForManga(mangaId: number): MarkerEntry[] { + return this.markers.filter(m => m.mangaId === mangaId); + } + + clearMarkersForManga(mangaId: number) { + this.markers = this.markers.filter(m => m.mangaId !== mangaId); + } + clearHistory() { this.history = []; this.readLog = []; } clearHistoryForManga(mangaId: number) { @@ -543,6 +595,7 @@ class Store { wipeAllData() { this.history = []; this.readLog = []; + this.markers = []; this.readingStats = { ...DEFAULT_READING_STATS }; this.settings = { ...this.settings, heroSlots: [null, null, null, null], mangaLinks: {} }; } @@ -676,6 +729,13 @@ export function addBookmark(entry: Omit, label?: strin export function removeBookmark(chapterId: number) { store.removeBookmark(chapterId); } export function clearBookmarks() { store.clearBookmarks(); } export function getBookmark(chapterId: number) { return store.getBookmark(chapterId); } +export function addMarker(entry: Omit): string { return store.addMarker(entry); } +export function updateMarker(id: string, patch: Partial>) { store.updateMarker(id, patch); } +export function removeMarker(id: string) { store.removeMarker(id); } +export function getMarkersForPage(chapterId: number, page: number) { return store.getMarkersForPage(chapterId, page); } +export function getMarkersForChapter(chapterId: number) { return store.getMarkersForChapter(chapterId); } +export function getMarkersForManga(mangaId: number) { return store.getMarkersForManga(mangaId); } +export function clearMarkersForManga(mangaId: number) { store.clearMarkersForManga(mangaId); } export function toggleHiddenCategory(id: number) { store.toggleHiddenCategory(id); } export function saveCustomTheme(theme: CustomTheme) { store.saveCustomTheme(theme); } export function deleteCustomTheme(id: string) { store.deleteCustomTheme(id); }