260 lines
8.8 KiB
JavaScript
260 lines
8.8 KiB
JavaScript
/* global $, calibre, EPUBJS, ePubReader */
|
|
|
|
var reader;
|
|
|
|
(function () {
|
|
"use strict";
|
|
|
|
EPUBJS.filePath = calibre.filePath;
|
|
EPUBJS.cssPath = calibre.cssPath;
|
|
|
|
reader = ePubReader(calibre.bookUrl, {
|
|
restore: true,
|
|
bookmarks: calibre.bookmark ? [calibre.bookmark] : [],
|
|
});
|
|
|
|
Object.keys(themes).forEach(function (theme) {
|
|
reader.rendition.themes.register(theme, themes[theme].css_path);
|
|
});
|
|
|
|
if (calibre.useBookmarks) {
|
|
reader.on("reader:bookmarked", updateBookmark.bind(reader, "add"));
|
|
reader.on("reader:unbookmarked", updateBookmark.bind(reader, "remove"));
|
|
} else {
|
|
$("#bookmark, #show-Bookmarks").remove();
|
|
}
|
|
|
|
// Navigation mode: 'sides' or 'gestures'
|
|
var hammerManagers = [];
|
|
|
|
function hasActiveSelection(win) {
|
|
try {
|
|
var sel = win.getSelection && win.getSelection();
|
|
return sel && sel.type === "Range" && sel.toString().length > 0;
|
|
} catch (e) {
|
|
return false;
|
|
}
|
|
}
|
|
|
|
function bindHammer(target, isIframeDoc) {
|
|
if (typeof Hammer === "undefined") {
|
|
return;
|
|
}
|
|
var mc = new Hammer(target);
|
|
mc.get("swipe").set({
|
|
direction: Hammer.DIRECTION_HORIZONTAL,
|
|
threshold: 25,
|
|
velocity: 0.3,
|
|
});
|
|
mc.on("swipeleft swiperight", function (ev) {
|
|
if (!window.cwGesturesEnabled) return;
|
|
if (ev.pointers && ev.pointers.length > 1) return; // ignore multi-touch
|
|
var win = isIframeDoc ? target.defaultView : window;
|
|
if (hasActiveSelection(win)) return; // do not navigate when selecting text
|
|
// Mapping per requirement: L->R = PREV, R->L = NEXT (independent of RTL)
|
|
if (ev.type === "swipeleft") reader.rendition.next();
|
|
else reader.rendition.prev();
|
|
});
|
|
hammerManagers.push(mc);
|
|
}
|
|
|
|
function destroyHammers() {
|
|
while (hammerManagers.length) {
|
|
var mc = hammerManagers.pop();
|
|
try {
|
|
mc.destroy();
|
|
} catch (e) {}
|
|
}
|
|
}
|
|
|
|
function enableSideClicks() {
|
|
var prevBtn = document.getElementById("prev");
|
|
var nextBtn = document.getElementById("next");
|
|
if (prevBtn) {
|
|
prevBtn.style.display = "";
|
|
prevBtn.onclick = function () {
|
|
reader.rendition.prev();
|
|
};
|
|
}
|
|
if (nextBtn) {
|
|
nextBtn.style.display = "";
|
|
nextBtn.onclick = function () {
|
|
reader.rendition.next();
|
|
};
|
|
}
|
|
}
|
|
|
|
function disableSideClicks() {
|
|
var prevBtn = document.getElementById("prev");
|
|
var nextBtn = document.getElementById("next");
|
|
if (prevBtn) {
|
|
prevBtn.onclick = null;
|
|
prevBtn.style.display = "none";
|
|
}
|
|
if (nextBtn) {
|
|
nextBtn.onclick = null;
|
|
nextBtn.style.display = "none";
|
|
}
|
|
}
|
|
|
|
function enableGestures() {
|
|
// Bind to outer container
|
|
if (reader && reader.rendition && reader.rendition.container) {
|
|
bindHammer(reader.rendition.container, false);
|
|
}
|
|
// Bind swipe on sidebar to close/open main
|
|
try {
|
|
var sidebarEl = document.getElementById("tocView");
|
|
if (sidebarEl && typeof Hammer !== "undefined") {
|
|
var sidebarMc = new Hammer(sidebarEl);
|
|
sidebarMc.get("swipe").set({
|
|
direction: Hammer.DIRECTION_HORIZONTAL,
|
|
threshold: 25,
|
|
velocity: 0.3,
|
|
});
|
|
sidebarMc.on("swipeleft swiperight", function (ev) {
|
|
if ($("#sidebar").hasClass("open")) {
|
|
$("#slider").click();
|
|
}
|
|
});
|
|
hammerManagers.push(sidebarMc);
|
|
}
|
|
} catch (e) {}
|
|
// Bind to inner iframes when rendered
|
|
reader.rendition.on("rendered", function (section, contents) {
|
|
var docEl = contents.document;
|
|
if (docEl && docEl.documentElement && docEl.documentElement.style) {
|
|
docEl.documentElement.style.touchAction = "pan-y";
|
|
}
|
|
bindHammer(docEl, true);
|
|
});
|
|
}
|
|
|
|
window.applyNavigationMode = function (mode) {
|
|
if (!window.cwInitialized) {
|
|
destroyHammers();
|
|
enableGestures();
|
|
window.cwInitialized = true;
|
|
}
|
|
|
|
if (mode === "sides") {
|
|
enableSideClicks();
|
|
window.cwGesturesEnabled = false;
|
|
} else {
|
|
disableSideClicks();
|
|
window.cwGesturesEnabled = true;
|
|
}
|
|
};
|
|
|
|
// Apply saved or default mode on load
|
|
var savedMode = localStorage.getItem("calibre.reader.navMode") || "sizes";
|
|
window.applyNavigationMode(savedMode);
|
|
|
|
// Update progress percentage
|
|
let progressDiv = document.getElementById("progress");
|
|
// Pages counter (virtual pages via EPUB locations)
|
|
let pagesDiv = document.getElementById("pages-count");
|
|
// Honor saved visibility preference for pages counter
|
|
(function () {
|
|
try {
|
|
var pref = localStorage.getItem("calibre.reader.showPages");
|
|
var show = pref === null ? true : pref === "true";
|
|
if (pagesDiv)
|
|
pagesDiv.style.visibility = show ? "visible" : "hidden";
|
|
} catch (e) {}
|
|
})();
|
|
|
|
reader.book.ready.then(() => {
|
|
let locations_key = reader.book.key() + "-locations";
|
|
let stored_locations = localStorage.getItem(locations_key);
|
|
let make_locations, save_locations;
|
|
if (stored_locations) {
|
|
make_locations = Promise.resolve(
|
|
reader.book.locations.load(stored_locations)
|
|
);
|
|
// No-op because locations are already saved
|
|
save_locations = () => {};
|
|
} else {
|
|
make_locations = reader.book.locations.generate();
|
|
save_locations = () => {
|
|
localStorage.setItem(
|
|
locations_key,
|
|
reader.book.locations.save()
|
|
);
|
|
};
|
|
}
|
|
make_locations
|
|
.then(() => {
|
|
reader.rendition.on("relocated", (location) => {
|
|
let percentage = Math.round(location.end.percentage * 100);
|
|
progressDiv.textContent = percentage + "%";
|
|
|
|
// Pages based on generated EPUB locations (CFI positions)
|
|
const cfi = location.start.cfi;
|
|
const current =
|
|
reader.book.locations.locationFromCfi(cfi) || 0; // 1-based index typically
|
|
const total = reader.book.locations.length() || 0;
|
|
const remaining = Math.max(total - current, 0);
|
|
|
|
if (total > 0) {
|
|
pagesDiv.textContent = current + "/" + total;
|
|
pagesDiv.style.visibility = "visible";
|
|
} else {
|
|
pagesDiv.textContent = "";
|
|
pagesDiv.style.visibility = "hidden";
|
|
}
|
|
});
|
|
reader.rendition.reportLocation();
|
|
progressDiv.style.visibility = "visible";
|
|
})
|
|
.then(save_locations);
|
|
});
|
|
|
|
/**
|
|
* @param {string} action - Add or remove bookmark
|
|
* @param {string|int} location - Location or zero
|
|
*/
|
|
function updateBookmark(action, location) {
|
|
// Remove other bookmarks (there can only be one)
|
|
if (action === "add") {
|
|
this.settings.bookmarks
|
|
.filter(function (bookmark) {
|
|
return bookmark && bookmark !== location;
|
|
})
|
|
.map(
|
|
function (bookmark) {
|
|
this.removeBookmark(bookmark);
|
|
}.bind(this)
|
|
);
|
|
}
|
|
|
|
var csrftoken = $("input[name='csrf_token']").val();
|
|
|
|
// Save to database
|
|
$.ajax(calibre.bookmarkUrl, {
|
|
method: "post",
|
|
data: { bookmark: location || "" },
|
|
headers: { "X-CSRFToken": csrftoken },
|
|
}).fail(function (xhr, status, error) {
|
|
alert(error);
|
|
});
|
|
}
|
|
|
|
// Default settings load
|
|
const theme = localStorage.getItem("calibre.reader.theme") ?? "lightTheme";
|
|
selectTheme(theme);
|
|
|
|
// Restore saved font and font size after reader is ready
|
|
reader.book.ready.then(() => {
|
|
const savedFontSize = localStorage.getItem("calibre.reader.fontSize");
|
|
if (savedFontSize) {
|
|
reader.rendition.themes.fontSize(`${savedFontSize}%`);
|
|
}
|
|
|
|
const savedFont = localStorage.getItem("calibre.reader.font");
|
|
if (savedFont && window.selectFont) {
|
|
window.selectFont(savedFont);
|
|
}
|
|
});
|
|
})();
|