diff --git a/userscript/manga-bookmark.user.js b/userscript/manga-bookmark.user.js index 544b391..24b9866 100644 --- a/userscript/manga-bookmark.user.js +++ b/userscript/manga-bookmark.user.js @@ -677,19 +677,6 @@ await syncUpsert(bm, "Progress updated"); } - // Manual override from the panel input — forces any value. - async function setChapterManual(key, label, num, url) { - const existing = state.byKey[key]; - if (!existing) return; - const bm = Object.assign({}, existing, { - last_chapter: label, - last_chapter_num: isNaN(num) ? existing.last_chapter_num : num, - last_chapter_url: url || existing.last_chapter_url, - updated_at: Date.now(), - }); - await syncUpsert(bm, "Progress set"); - } - async function syncUpsert(bm, okMsg) { upsertLocal(bm); // optimistic render(); @@ -1108,40 +1095,47 @@ const sub = behind ? "Read: " + (b.last_chapter || "—") + " · Latest: " + b.latest_chapter : (b.last_chapter || "—") + " · " + b.site; - const row = el("div", { class: "item" }, [ - b.cover - ? el("img", { class: "cover", src: b.cover, loading: "lazy", alt: "" }) - : el("div", { class: "cover ph" }), - el("div", { class: "meta" }, [ - el("div", { class: "t", text: b.title || b.series_id }), - el("div", { class: "c" + (behind ? " behind" : ""), text: sub }), - el("div", { class: "actions" }, [ - el("button", { - class: "btn small star" + (b.favorite ? " on" : ""), - text: b.favorite ? "★" : "☆", - title: b.favorite ? "Remove from favourites" : "Add to favourites", - onclick: () => toggleFavorite(b.key), - }), - el("button", { - class: "btn small", - text: statusOf(b) === "archived" ? "Unarchive" : "Archive", - onclick: () => toggleArchive(b.key), - }), - el("a", { class: "btn small", href: cont, text: "Continue" }), - el("button", { class: "btn small", text: "Edit", onclick: () => editChapter(b) }), - el("button", { class: "btn small danger", text: "Remove", onclick: () => removeBookmark(b.key) }), + // Cover and text are one link: the card body *is* the continue button, so + // there is no separate one. The action row is a sibling of that link, never + // a child — a thumb that misses ★ must not land on a navigation target, and + // Remove must never be one. + return el("div", { class: "item" }, [ + el("a", { class: "go", href: cont }, [ + b.cover + ? el("img", { class: "cover", src: b.cover, loading: "lazy", alt: "" }) + : el("div", { class: "cover ph" }), + el("div", { class: "meta" }, [ + el("div", { class: "t", text: b.title || b.series_id }), + el("div", { class: "c" + (behind ? " behind" : ""), text: sub }), ]), ]), + el("div", { class: "actions" }, [ + el("button", { + class: "btn small star" + (b.favorite ? " on" : ""), + text: b.favorite ? "★" : "☆", + title: b.favorite ? "Remove from favourites" : "Add to favourites", + onclick: () => toggleFavorite(b.key), + }), + el("button", { + class: "btn small", + text: statusOf(b) === "archived" ? "Unarchive" : "Archive", + onclick: () => toggleArchive(b.key), + }), + el("button", { + class: "btn small danger", + text: "Remove", + onclick: () => confirmRemove(b), + }), + ]), ]); - return row; } - function editChapter(b) { - const raw = prompt("Set last-read chapter for \"" + (b.title || b.series_id) + "\":", b.last_chapter || ""); - if (raw == null) return; - const label = raw.trim(); - const num = parseFloat((label.match(/[\d.]+/) || [])[0]); - setChapterManual(b.key, label, num, b.last_chapter_url); + // Removal syncs to every device, so it asks first. confirm() is the page's + // own dialog, not a shadow-root one — modal, and the site cannot style it. + function confirmRemove(b) { + if (confirm('Remove "' + (b.title || b.series_id) + '" from your bookmarks?')) { + removeBookmark(b.key); + } } // Toast inside the shadow root. @@ -1325,12 +1319,17 @@ .tab.active { color: #eee; border-bottom-color: #6d28d9; } #list { overflow-y: auto; flex: 1; padding: 8px 0; } .empty { color: #9a9aa5; text-align: center; padding: 30px 16px; font-size: 14px; } - .item { display: flex; gap: 10px; padding: 10px 16px; border-bottom: 1px solid #2a2a33; } + .item { + display: flex; flex-direction: column; gap: 8px; + padding: 10px 16px; border-bottom: 1px solid #2a2a33; + } + .go { display: flex; gap: 10px; text-decoration: none; color: inherit; } + .go:active { opacity: .7; } .cover { width: 46px; height: 62px; object-fit: cover; border-radius: 4px; flex: none; background: #333; } .cover.ph { display: block; } .meta { min-width: 0; flex: 1; } .t { font-weight: 600; font-size: 14px; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; } - .c { color: #9a9aa5; font-size: 12px; margin: 2px 0 8px; } + .c { color: #9a9aa5; font-size: 12px; margin: 2px 0 0; } .c.behind { color: #c4b5fd; } .actions { display: flex; gap: 6px; flex-wrap: wrap; } .btn {