Merge Reader improvements

This commit is contained in:
Ozzie Isaacs
2025-12-07 14:31:16 +01:00
9 changed files with 689 additions and 175 deletions
+5
View File
@@ -13,6 +13,11 @@
color: #000;
}
.amberTheme {
background: #e8bf5e;
color: #000;
}
.blackTheme {
background: #000;
color: #fff;
+172 -79
View File
@@ -1,11 +1,10 @@
@font-face {
font-family: 'fontello';
src: url('fonts/fontello.eot?60518104');
src:
url('fonts/fontello.eot?60518104#iefix') format('embedded-opentype'),
url('fonts/fontello.woff?60518104') format('woff'),
url('fonts/fontello.ttf?60518104') format('truetype'),
url('fonts/fontello.svg?60518104#fontello') format('svg');
font-family: "fontello";
src: url("fonts/fontello.eot?60518104");
src: url("fonts/fontello.eot?60518104#iefix") format("embedded-opentype"),
url("fonts/fontello.woff?60518104") format("woff"),
url("fonts/fontello.ttf?60518104") format("truetype"),
url("fonts/fontello.svg?60518104#fontello") format("svg");
font-weight: normal;
font-style: normal;
}
@@ -16,7 +15,7 @@ body {
}
.myselect {
overflow: visible !important;
overflow: visible !important;
}
#main {
@@ -48,7 +47,7 @@ body {
-webkit-transition: opacity 0.5s;
-moz-transition: opacity 0.5s;
transition: opacity 0.5s;
z-index: 10;
z-index: 100;
}
#titlebar:hover {
@@ -93,14 +92,18 @@ body {
box-shadow: inset 0 0 6px rgba(155, 155, 155, 0.8);
}
#book-title { font-weight: 600; }
#title-seperator { display: none; }
#book-title {
font-weight: 600;
}
#title-seperator {
display: none;
}
#viewer {
width: 80%;
height: 80%;
margin: 0 auto;
z-index: 2;
z-index: 0;
position: relative;
overflow: hidden;
}
@@ -136,6 +139,7 @@ body {
user-select: none;
padding-top: 160px;
padding-bottom: 160px;
z-index: 10;
}
.arrow:hover {
@@ -200,10 +204,16 @@ body {
max-width: 80%;
}
#title-controls { float: right; }
#title-controls {
float: right;
}
#panels a::before { visibility: visible; }
#panels a:hover { color: #aaa; }
#panels a::before {
visibility: visible;
}
#panels a:hover {
color: #aaa;
}
.list_item.currentChapter > a,
.list_item a:hover {
@@ -237,8 +247,12 @@ body {
margin-top: -1px;
}
input::-webkit-input-placeholder { color: #454545; }
input:-moz-placeholder { color: #454545; }
input::-webkit-input-placeholder {
color: #454545;
}
input:-moz-placeholder {
color: #454545;
}
#divider {
position: absolute;
@@ -378,14 +392,14 @@ input:-moz-placeholder { color: #454545; }
}
.toc_toggle::before {
content: '▸';
content: "▸";
color: #fff;
margin-right: -4px;
}
.currentChapter > .toc_toggle::before,
.openChapter > .toc_toggle::before {
content: '▾';
content: "▾";
}
.view {
@@ -499,21 +513,33 @@ input:-moz-placeholder { color: #454545; }
list-style-type: none;
}
#settingsPanel .xsmall { font-size: x-small; }
#settingsPanel .small { font-size: small; }
#settingsPanel .medium { font-size: medium; }
#settingsPanel .large { font-size: large; }
#settingsPanel .xlarge { font-size: x-large; }
#settingsPanel .xsmall {
font-size: x-small;
}
#settingsPanel .small {
font-size: small;
}
#settingsPanel .medium {
font-size: medium;
}
#settingsPanel .large {
font-size: large;
}
#settingsPanel .xlarge {
font-size: x-large;
}
.highlight { background-color: yellow; }
.highlight {
background-color: yellow;
}
.modal {
position: fixed;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
width: 100vw;
height: auto;
width: 97vw;
height: 90dvh;
max-width: 630px;
z-index: 2000;
visibility: hidden;
@@ -539,7 +565,7 @@ input:-moz-placeholder { color: #454545; }
}
.md-show ~ .overlay {
opacity: 1;
/* opacity: 1; */
visibility: visible;
}
@@ -554,7 +580,7 @@ input:-moz-placeholder { color: #454545; }
}
.md-content > div {
padding: 15px 40px 30px;
padding: 10px 20px;
margin: 0;
font-weight: 300;
font-size: 14px;
@@ -581,6 +607,7 @@ input:-moz-placeholder { color: #454545; }
color: white;
}
.md-content .themes button.lightTheme,
.md-content .themes button.whiteTheme {
background-color: white;
color: black;
@@ -591,6 +618,11 @@ input:-moz-placeholder { color: #454545; }
color: black;
}
.md-content .themes button.amberTheme {
background-color: #e8bf5e;
color: black;
}
.md-content .themes button.blackTheme {
background-color: black;
color: white;
@@ -638,31 +670,47 @@ input:-moz-placeholder { color: #454545; }
@media only screen and (max-width: 900px) {
#viewer {
width: 60%;
margin-left: 20%;
width: 100%;
height: calc(100dvh - 99px);
margin-left: 0;
}
#viewer iframe {
width: 100%;
height: 100%;
}
#prev {
left: 20px;
z-index: 10;
}
#next {
right: 20px;
z-index: 10;
}
}
@media only screen and (max-width: 550px) {
#viewer {
width: 80%;
margin-left: 10%;
width: 100%;
height: calc(100dvh - 99px);
margin-left: 0;
}
#viewer iframe {
width: 100%;
height: 100%;
}
#prev {
left: 0;
z-index: 10;
}
#next {
right: 0;
z-index: 10;
}
.arrow {
@@ -712,11 +760,7 @@ input:-moz-placeholder { color: #454545; }
}
}
@media only screen
and (min-device-width: 768px)
and (max-device-width: 1024px)
and (orientation: landscape)
/* and (-webkit-min-device-pixel-ratio: 2)*/ {
@media only screen and (min-device-width: 768px) and (max-device-width: 1024px) and (orientation: landscape) /* and (-webkit-min-device-pixel-ratio: 2) */ {
#viewer {
width: 80%;
margin-left: 10%;
@@ -739,47 +783,52 @@ input:-moz-placeholder { color: #454545; }
/* For iPhone 6\6s portrait layouts only */
@media only screen and (min-device-width: 375px) and (max-device-width: 667px) and (orientation: portrait) {
#viewer {
width: 300px;
height: 480px;
width: 100%;
height: calc(100dvh - 99px);
}
#viewer iframe {
width: 300px;
height: 480px;
width: 100%;
height: 100%;
}
}
/* For iPhone 6\6s landscape layouts only */
@media only screen and (min-device-width: 375px) and (max-device-width: 667px) and (orientation: landscape) {
#viewer {
width: 450px;
height: 300px;
width: 100%;
height: calc(100dvh - 99px);
}
#viewer iframe {
width: 450px;
height: 300px;
width: 100%;
height: 100%;
}
}
/* For iPhone portrait layouts only */
@media only screen and (max-device-width: 374px) and (orientation: portrait) {
#viewer {
width: 256px;
height: 432px;
width: 100%;
height: calc(100dvh - 99px);
}
#viewer iframe {
width: 256px;
height: 432px;
width: 100%;
height: 100%;
}
}
/* For iPhone landscape layouts only */
@media only screen and (max-device-width: 374px) and (orientation: landscape) {
#viewer {
width: 100%;
height: calc(100dvh - 99px);
}
#viewer iframe {
width: 256px;
height: 124px;
width: 100%;
height: 100%;
}
}
@@ -803,32 +852,76 @@ input:-moz-placeholder { color: #454545; }
font-size: 112%;
}
.icon-search::before { content: '\e807'; } /* '' */
.icon-resize-full-1::before { content: '\e804'; } /* '' */
.icon-cancel-circled2::before { content: '\e80f'; } /* '' */
.icon-link::before { content: '\e80d'; } /* '' */
.icon-bookmark::before { content: '\e805'; } /* '' */
.icon-bookmark-empty::before { content: '\e806'; } /* '' */
.icon-download-cloud::before { content: '\e811'; } /* '' */
.icon-edit::before { content: '\e814'; } /* '' */
.icon-menu::before { content: '\e802'; } /* '' */
.icon-cog::before { content: '\e813'; } /* '' */
.icon-resize-full::before { content: '\e812'; } /* '' */
.icon-cancel-circled::before { content: '\e80e'; } /* '' */
.icon-up-dir::before { content: '\e80c'; } /* '' */
.icon-right-dir::before { content: '\e80b'; } /* '' */
.icon-angle-right::before { content: '\e809'; } /* '' */
.icon-angle-down::before { content: '\e80a'; } /* '' */
.icon-right::before { content: '\e815'; } /* '' */
.icon-list-1::before { content: '\e803'; } /* '' */
.icon-list-numbered::before { content: '\e801'; } /* '' */
.icon-columns::before { content: '\e810'; } /* '' */
.icon-list::before { content: '\e800'; } /* '' */
.icon-resize-small::before { content: '\e808'; } /* '' */
.icon-search::before {
content: "\e807";
} /* '' */
.icon-resize-full-1::before {
content: "\e804";
} /* '' */
.icon-cancel-circled2::before {
content: "\e80f";
} /* '' */
.icon-link::before {
content: "\e80d";
} /* '' */
.icon-bookmark::before {
content: "\e805";
} /* '' */
.icon-bookmark-empty::before {
content: "\e806";
} /* '' */
.icon-download-cloud::before {
content: "\e811";
} /* '' */
.icon-edit::before {
content: "\e814";
} /* '' */
.icon-menu::before {
content: "\e802";
} /* '' */
.icon-cog::before {
content: "\e813";
} /* '' */
.icon-resize-full::before {
content: "\e812";
} /* '' */
.icon-cancel-circled::before {
content: "\e80e";
} /* '' */
.icon-up-dir::before {
content: "\e80c";
} /* '' */
.icon-right-dir::before {
content: "\e80b";
} /* '' */
.icon-angle-right::before {
content: "\e809";
} /* '' */
.icon-angle-down::before {
content: "\e80a";
} /* '' */
.icon-right::before {
content: "\e815";
} /* '' */
.icon-list-1::before {
content: "\e803";
} /* '' */
.icon-list-numbered::before {
content: "\e801";
} /* '' */
.icon-columns::before {
content: "\e810";
} /* '' */
.icon-list::before {
content: "\e800";
} /* '' */
.icon-resize-small::before {
content: "\e808";
} /* '' */
#progress{
right: 4rem;
bottom: 4rem;
width: fit-content;
position: absolute;
#progress {
right: 4rem;
bottom: 2px;
width: fit-content;
position: absolute;
}
+62 -35
View File
@@ -1,47 +1,74 @@
.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: 14px;
font-weight: 600;
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,
#sidebar,
#tocView {
touch-action: pan-y;
}
.read-footer {
display: flex;
justify-content: space-between;
align-items: center;
padding: 0 12px;
}
.read-footer #pages-count {
font-size: 12px;
position: absolute;
left: 12px;
bottom: 2px;
}
#back-button {
width: 50px !important;
right: 50px;
// position: absolute;
}
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
+105 -25
View File
@@ -2,7 +2,7 @@
var reader;
(function() {
(function () {
"use strict";
EPUBJS.filePath = calibre.filePath;
@@ -10,7 +10,7 @@ var reader;
reader = ePubReader(calibre.bookUrl, {
restore: true,
bookmarks: calibre.bookmark ? [calibre.bookmark] : []
bookmarks: calibre.bookmark ? [calibre.bookmark] : [],
});
Object.keys(themes).forEach(function (theme) {
@@ -54,29 +54,92 @@ var reader;
// Update progress percentage
let progressDiv = document.getElementById("progress");
reader.book.ready.then((()=>{
let locations_key = reader.book.key()+'-locations';
// 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";
// Key to persist last-read position for this book in localStorage
let position_key = "calibre.reader.position." + reader.book.key();
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));
make_locations = Promise.resolve(
reader.book.locations.load(stored_locations)
);
// No-op because locations are already saved
save_locations = ()=>{};
save_locations = () => {};
} else {
make_locations = reader.book.locations.generate();
save_locations = ()=>{
localStorage.setItem(locations_key, reader.book.locations.save());
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+"%";
});
reader.rendition.reportLocation();
progressDiv.style.visibility = "visible";
}).then(save_locations);
}));
make_locations
.then(() => {
// Try to restore last position (CFI) from localStorage if present
try {
var _savedPos = localStorage.getItem(position_key);
if (_savedPos) {
try {
var _posObj = JSON.parse(_savedPos);
if (_posObj && _posObj.cfi) {
// Display the saved CFI location
try {
reader.rendition.display(_posObj.cfi);
} catch (e) {}
}
} catch (e) {}
}
} catch (e) {}
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;
if (total > 0) {
pagesDiv.textContent = current + "/" + total;
pagesDiv.style.visibility = "visible";
} else {
pagesDiv.textContent = "";
pagesDiv.style.visibility = "hidden";
}
// Persist last position (CFI + percentage) to localStorage so reader can restore on next open
try {
var posObj = {
cfi: location.start.cfi,
percentage: location.start.percentage,
};
localStorage.setItem(
position_key,
JSON.stringify(posObj)
);
} catch (e) {}
});
reader.rendition.reportLocation();
progressDiv.style.visibility = "visible";
})
.then(save_locations);
});
/**
* @param {string} action - Add or remove bookmark
@@ -85,26 +148,43 @@ var reader;
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));
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 }
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);
}
});
})();