feat(bookmarks): location identity, origin provenance, KOReader-style labels
- Drop UNIQUE(media_item_id,user_id,title): titles are display labels shared verbatim across clients; same-title bookmarks on different pages now coexist instead of 500ing (deleting over a tombstone no longer blocks future creates with that title) - Add origin_source column recording the creating client (android/web/ koreader), set once at insert, exposed in API responses - Web reader auto-title mirrors KOReader's 'in <chapter>' convention, falling back to 'Bookmark'; adds bookmark rename in the drawer
This commit is contained in:
@@ -472,6 +472,8 @@ document.addEventListener("alpine:init", () => {
|
||||
positionLabel: string;
|
||||
cfi: string;
|
||||
page: number | null;
|
||||
renameOpen?: boolean;
|
||||
renameText?: string;
|
||||
}[],
|
||||
tocItems: [] as any[],
|
||||
mediaItemId: "" as string,
|
||||
@@ -2079,6 +2081,14 @@ document.addEventListener("alpine:init", () => {
|
||||
const page = this.isFixedLayout
|
||||
? (this.renderer?.index ?? 0) + 1
|
||||
: 0;
|
||||
// Auto-label mirrors KOReader's convention ("in <chapter title>")
|
||||
// so every client names unnamed bookmarks identically; falls back
|
||||
// to a plain "Bookmark" for fixed-layout or TOC-less books.
|
||||
const chapter = this.lastRelocateDetail?.tocItem?.label as
|
||||
| string
|
||||
| undefined;
|
||||
const title =
|
||||
!this.isFixedLayout && chapter ? `in ${chapter}` : "Bookmark";
|
||||
|
||||
try {
|
||||
const resp = await fetch(
|
||||
@@ -2090,7 +2100,7 @@ document.addEventListener("alpine:init", () => {
|
||||
"Content-Type": "application/json",
|
||||
},
|
||||
body: JSON.stringify({
|
||||
title: `Bookmark at ${this.progressText || "current position"}`,
|
||||
title,
|
||||
position: this.isFixedLayout
|
||||
? `page:${page}`
|
||||
: cfi
|
||||
@@ -2130,6 +2140,38 @@ document.addEventListener("alpine:init", () => {
|
||||
/* ignore bookmark errors for now */
|
||||
}
|
||||
},
|
||||
startBookmarkRename(bookmark: (typeof this.bookmarkItems)[number]) {
|
||||
bookmark.renameOpen = true;
|
||||
bookmark.renameText = bookmark.title;
|
||||
},
|
||||
async renameBookmark(bookmark: (typeof this.bookmarkItems)[number]) {
|
||||
const token = getToken();
|
||||
const title = (bookmark.renameText ?? "").trim();
|
||||
if (!token || !this.mediaItemId || !title) return;
|
||||
try {
|
||||
const resp = await fetch(
|
||||
`/api/media-items/${this.mediaItemId}/bookmarks/${bookmark.id}`,
|
||||
{
|
||||
method: "PUT",
|
||||
headers: {
|
||||
Authorization: `Bearer ${token}`,
|
||||
"Content-Type": "application/json",
|
||||
},
|
||||
body: JSON.stringify({
|
||||
title,
|
||||
notes: "",
|
||||
position: bookmark.positionLabel || "",
|
||||
}),
|
||||
},
|
||||
);
|
||||
if (resp.ok) {
|
||||
bookmark.title = title;
|
||||
}
|
||||
bookmark.renameOpen = false;
|
||||
} catch (_e) {
|
||||
bookmark.renameOpen = false;
|
||||
}
|
||||
},
|
||||
chapterNumberForProgress(): number {
|
||||
const tocItem = this.lastRelocateDetail?.tocItem;
|
||||
if (!tocItem?.label) return 0;
|
||||
|
||||
Reference in New Issue
Block a user