feat: add bookmarks with toolbar button, sidebar list, and keyboard shortcut
- bookmark button 🔖 in toolbar (Ctrl+B / Cmd+B shortcut)
- addBookmark() saves current position (CFI for epub, page for pdf, scroll% for text)
- bookmarks stored in localStorage per-file (bookish_bookmarks_<filename>)
- sidebar Segnalibri tab shows clickable list with delete button
- gotoBookmark() restores the exact position per format type
- CSS styles for bm-entry, bm-label, bm-time, bm-delete
This commit is contained in:
1 parent
af27d47d58
commit
069d059ef1
3 files changed
+189
-1
No files matched your search
@@ -584,10 +584,55 @@ body {
|
||||
}
|
||||
|
||||
#bookmarksList li {
|
||||
padding: 0.5rem;
|
||||
padding: 0.4rem 0.5rem;
|
||||
font-size: 0.85rem;
|
||||
border-bottom: 1px solid #f0f0f0;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
gap: 0.3rem;
|
||||
}
|
||||
|
||||
#bookmarksList li:hover {
|
||||
background: #f8f8fa;
|
||||
}
|
||||
|
||||
.bm-entry {
|
||||
flex: 1;
|
||||
cursor: pointer;
|
||||
padding: 0.1rem 0;
|
||||
min-width: 0;
|
||||
}
|
||||
|
||||
.bm-label {
|
||||
display: block;
|
||||
color: #333;
|
||||
white-space: nowrap;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
}
|
||||
|
||||
.bm-time {
|
||||
display: block;
|
||||
font-size: 0.7rem;
|
||||
color: #aaa;
|
||||
}
|
||||
|
||||
.bm-delete {
|
||||
background: none;
|
||||
border: none;
|
||||
cursor: pointer;
|
||||
color: #ccc;
|
||||
font-size: 0.8rem;
|
||||
padding: 0.2rem 0.3rem;
|
||||
border-radius: 3px;
|
||||
flex-shrink: 0;
|
||||
transition: color 0.2s;
|
||||
}
|
||||
|
||||
.bm-delete:hover {
|
||||
color: #e74c3c;
|
||||
background: #fef0ef;
|
||||
}
|
||||
|
||||
|
||||
|
||||
@@ -307,6 +307,20 @@
|
||||
});
|
||||
}
|
||||
|
||||
// Bookmark button
|
||||
const bookmarkToggle = document.getElementById("bookmarkToggle");
|
||||
if (bookmarkToggle) {
|
||||
bookmarkToggle.addEventListener("click", () => addBookmark());
|
||||
}
|
||||
|
||||
// Keyboard shortcut for bookmark (Ctrl+B / Cmd+B)
|
||||
document.addEventListener("keydown", (e) => {
|
||||
if ((e.ctrlKey || e.metaKey) && e.key === "b") {
|
||||
e.preventDefault();
|
||||
addBookmark();
|
||||
}
|
||||
});
|
||||
|
||||
// Load saved theme
|
||||
const savedTheme = localStorage.getItem("bookish_theme");
|
||||
if (savedTheme) {
|
||||
@@ -315,6 +329,9 @@
|
||||
applyTheme(t);
|
||||
} catch (e) {}
|
||||
}
|
||||
|
||||
// Init bookmarks list
|
||||
renderBookmarks();
|
||||
}
|
||||
|
||||
function applyTheme(t) {
|
||||
@@ -611,6 +628,131 @@
|
||||
}
|
||||
}
|
||||
|
||||
// === Bookmarks ===
|
||||
|
||||
function getBookmarksKey() {
|
||||
return "bookish_bookmarks_" + (BOOKISH_FILE || "");
|
||||
}
|
||||
|
||||
function getBookmarks() {
|
||||
try {
|
||||
return JSON.parse(localStorage.getItem(getBookmarksKey()) || "[]");
|
||||
} catch (e) {
|
||||
return [];
|
||||
}
|
||||
}
|
||||
|
||||
function saveBookmarks(list) {
|
||||
try {
|
||||
localStorage.setItem(getBookmarksKey(), JSON.stringify(list));
|
||||
} catch (e) {}
|
||||
}
|
||||
|
||||
function addBookmark() {
|
||||
const label = document.getElementById("chapterTitle");
|
||||
const chapter = label ? label.textContent : "Posizione corrente";
|
||||
|
||||
let data = { label: chapter || "Segnalibro", created: Date.now() };
|
||||
|
||||
if (ext === "epub" && window._rendition) {
|
||||
const loc = window._rendition.currentLocation();
|
||||
if (loc && loc.start) {
|
||||
data.type = "epub";
|
||||
data.cfi = loc.start.cfi;
|
||||
data.href = loc.start.href;
|
||||
data.page = loc.start.displayed ? loc.start.displayed.page : 0;
|
||||
data.label = chapter + " — Pag. " + data.page;
|
||||
} else {
|
||||
return;
|
||||
}
|
||||
} else if (ext === "pdf" && window._pdfViewer) {
|
||||
const page = window._pdfViewer.getCurrentPage();
|
||||
data.type = "pdf";
|
||||
data.page = page;
|
||||
data.totalPages = window._pdfViewer.totalPages;
|
||||
data.label = "Pag. " + page + " di " + data.totalPages;
|
||||
} else if (["md", "txt", "html", "fb2", "docx"].includes(ext)) {
|
||||
const rv = document.getElementById("readerView");
|
||||
if (!rv) return;
|
||||
const maxScroll = rv.scrollHeight - rv.clientHeight;
|
||||
const pct = maxScroll > 0 ? Math.round((rv.scrollTop / maxScroll) * 100) : 0;
|
||||
data.type = "text";
|
||||
data.scrollPct = pct;
|
||||
data.label = chapter + " — " + pct + "%";
|
||||
} else {
|
||||
return;
|
||||
}
|
||||
|
||||
data.id = Date.now().toString(36) + Math.random().toString(36).slice(2, 5);
|
||||
const bookmarks = getBookmarks();
|
||||
bookmarks.push(data);
|
||||
saveBookmarks(bookmarks);
|
||||
renderBookmarks();
|
||||
}
|
||||
|
||||
function removeBookmark(id) {
|
||||
let bookmarks = getBookmarks();
|
||||
bookmarks = bookmarks.filter((b) => b.id !== id);
|
||||
saveBookmarks(bookmarks);
|
||||
renderBookmarks();
|
||||
}
|
||||
|
||||
function gotoBookmark(bm) {
|
||||
if (bm.type === "epub" && window._rendition && bm.cfi) {
|
||||
window._rendition.display(bm.cfi);
|
||||
} else if (bm.type === "pdf" && window._pdfViewer && bm.page) {
|
||||
window._pdfViewer.currentPage = bm.page;
|
||||
window._pdfViewer.renderPage(bm.page);
|
||||
updatePageInfoPdf(bm.page, window._pdfViewer.totalPages);
|
||||
} else if (bm.type === "text" && bm.scrollPct !== undefined) {
|
||||
const rv = document.getElementById("readerView");
|
||||
if (rv) {
|
||||
const maxScroll = rv.scrollHeight - rv.clientHeight;
|
||||
rv.scrollTop = (bm.scrollPct / 100) * maxScroll;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function renderBookmarks() {
|
||||
const list = document.getElementById("bookmarksList");
|
||||
if (!list) return;
|
||||
const bookmarks = getBookmarks();
|
||||
|
||||
if (bookmarks.length === 0) {
|
||||
list.innerHTML = '<li style="color:#999;cursor:default;font-style:italic;display:block;padding:0.5rem">Nessun segnalibro</li>';
|
||||
return;
|
||||
}
|
||||
|
||||
list.innerHTML = bookmarks
|
||||
.map(
|
||||
(bm) =>
|
||||
`<li data-id="${bm.id}">
|
||||
<span class="bm-entry" title="Vai a questo segnalibro">
|
||||
<span class="bm-label">${escapeHtml(bm.label || "Segnalibro")}</span>
|
||||
<span class="bm-time">${new Date(bm.created).toLocaleDateString("it")}</span>
|
||||
</span>
|
||||
<button class="bm-delete" title="Elimina">✕</button>
|
||||
</li>`
|
||||
)
|
||||
.join("");
|
||||
|
||||
list.querySelectorAll(".bm-entry").forEach((el) => {
|
||||
el.addEventListener("click", () => {
|
||||
const id = el.closest("li").dataset.id;
|
||||
const bm = bookmarks.find((b) => b.id === id);
|
||||
if (bm) gotoBookmark(bm);
|
||||
});
|
||||
});
|
||||
|
||||
list.querySelectorAll(".bm-delete").forEach((btn) => {
|
||||
btn.addEventListener("click", (e) => {
|
||||
e.stopPropagation();
|
||||
const id = btn.closest("li").dataset.id;
|
||||
removeBookmark(id);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
// === Utils ===
|
||||
function debounce(fn, ms) {
|
||||
let timer;
|
||||
|
||||
@@ -26,6 +26,7 @@
|
||||
<button class="tool-btn" id="zoomIn" title="Zoom +">A+</button>
|
||||
<button class="tool-btn" id="themeToggle" title="Tema">☀️</button>
|
||||
<button class="tool-btn" id="searchToggle" title="Cerca">🔍</button>
|
||||
<button class="tool-btn" id="bookmarkToggle" title="Segnalibro (Ctrl+B)">🔖</button>
|
||||
<button class="tool-btn" id="fullscreenToggle" title="Schermo intero">⛶</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
Reference in new issue
Block a user