1
0
mirror of https://github.com/janeczku/calibre-web synced 2026-01-08 07:09:03 +00:00

add swipes by gestures in settings as an option, hammer.js for mobile

This commit is contained in:
IgorKurkov
2025-10-29 20:37:15 +01:00
parent d8fbf967b3
commit 560e8e132d
4 changed files with 1430 additions and 59 deletions

View File

@@ -1,47 +1,52 @@
.fontSizeWrapper {
position: relative;
position: relative;
}
.slider {
position: absolute;
top: 50%;
transform: translate(0,-50%);
width: 90%;
height: 60px;
background: transparent;
border-radius: 20px;
display: flex;
align-items: center;
box-shadow: 0px 15px 40px #7E6D5766;
position: absolute;
top: 50%;
transform: translate(0, -50%);
width: 90%;
height: 60px;
background: transparent;
border-radius: 20px;
display: flex;
align-items: center;
box-shadow: 0px 15px 40px #7e6d5766;
}
.slider label {
font-size: 20px;
font-weight: 400;
font-family: Open Sans;
padding-right: 10px;
color: white;
font-size: 20px;
font-weight: 400;
font-family: Open Sans;
padding-right: 10px;
color: white;
}
.slider input[type="range"] {
width: 80%;
height: 5px;
background: black;
border: none;
outline: none;
width: 80%;
height: 5px;
background: black;
border: none;
outline: none;
}
.item {
font-size: 20px;
font-weight: 400;
font-family: Open Sans;
padding-right: 10px;
color: white;
font-size: 20px;
font-weight: 400;
font-family: Open Sans;
padding-right: 10px;
color: white;
}
.item~button {
display: inline-block;
border: none;
text-align: center;
text-decoration: none;
margin-top: 5%;
margin-right: 1%;
font-size: 16px;
}
.item ~ button {
display: inline-block;
border: none;
text-align: center;
text-decoration: none;
margin-top: 5%;
margin-right: 1%;
font-size: 16px;
}
#main,
#viewer {
touch-action: pan-y;
}

1255
cps/static/js/libs/hammer.min.js vendored Normal file

File diff suppressed because it is too large Load Diff

View File

@@ -24,33 +24,114 @@ var reader;
$("#bookmark, #show-Bookmarks").remove();
}
// Enable swipe support
// I have no idea why swiperRight/swiperLeft from plugins is not working, events just don't get fired
var touchStart = 0;
var touchEnd = 0;
// Navigation mode: 'sides' or 'gestures'
var hammerManagers = [];
reader.rendition.on("touchstart", function (event) {
touchStart = event.changedTouches[0].screenX;
});
reader.rendition.on("touchend", function (event) {
touchEnd = event.changedTouches[0].screenX;
if (touchStart < touchEnd) {
if (reader.book.package.metadata.direction === "rtl") {
reader.rendition.next();
} else {
reader.rendition.prev();
}
// Swiped Right
function hasActiveSelection(win) {
try {
var sel = win.getSelection && win.getSelection();
return sel && sel.type === "Range" && sel.toString().length > 0;
} catch (e) {
return false;
}
if (touchStart > touchEnd) {
if (reader.book.package.metadata.direction === "rtl") {
reader.rendition.prev();
} else {
reader.rendition.next();
}
// Swiped Left
}
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 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") || "gestures";
window.applyNavigationMode(savedMode);
// Update progress percentage
let progressDiv = document.getElementById("progress");

View File

@@ -157,6 +157,19 @@
/>{{_('Reflow text when sidebars are open.')}}
</p>
</div>
<div class="form-group" id="navigationMode">
<label class="item">{{ _('Navigation') }}:</label>
<button type="button" id="navSides" onclick="selectNavMode('sides')">
<span id="navSidesTick"> </span>{{_('Sides')}}
</button>
<button
type="button"
id="navGestures"
onclick="selectNavMode('gestures')"
>
<span id="navGesturesTick"> </span>{{_('Gestures')}}
</button>
</div>
<div class="form-group fontSizeWrapper">
<label>{{ _('Font Size') }}</label>
<div
@@ -364,8 +377,25 @@
document.getElementById(id).querySelector("span").textContent = "✓";
reader.rendition.spread(id === "spread" ? true : "none");
}
function selectNavMode(mode) {
var tickSides = document.getElementById("navSidesTick");
var tickGest = document.getElementById("navGesturesTick");
tickSides.textContent = mode === "sides" ? "✓" : "";
tickGest.textContent = mode === "gestures" ? "✓" : "";
localStorage.setItem("calibre.reader.navMode", mode);
if (window.applyNavigationMode) {
window.applyNavigationMode(mode);
}
}
// Initialize nav mode on load
(function () {
var savedMode =
localStorage.getItem("calibre.reader.navMode") || "gestures";
selectNavMode(savedMode);
})();
</script>
<script src="{{ url_for('static', filename='js/libs/screenfull.min.js') }}"></script>
<script src="{{ url_for('static', filename='js/libs/hammer.min.js') }}"></script>
<script src="{{ url_for('static', filename='js/libs/reader.min.js') }}"></script>
<script src="{{ url_for('static', filename='js/reading/epub.js') }}"></script>
</body>