1
0
mirror of https://github.com/janeczku/calibre-web synced 2026-01-28 13:51:23 +00:00
Files
calibre-web/cps/templates/read.html

496 lines
21 KiB
HTML
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<!DOCTYPE html>
<html class="no-js">
<head>
<meta charset="utf-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1">
<title>{{_('epub Reader')}} | {{title}}</title>
<meta name="description" content="">
<meta name="viewport" content="width=device-width, user-scalable=no">
<meta name="mobile-web-app-capable" content="yes">
{% if g.google_site_verification|length > 0 %}
<meta name="google-site-verification" content="{{g.google_site_verification}}">
{% endif %}
<link rel="apple-touch-icon" sizes="140x140" href="{{ url_for('static', filename='favicon.ico') }}">
<link rel="shortcut icon" href="{{ url_for('static', filename='favicon.ico') }}">
<link rel="stylesheet" href="{{ url_for('static', filename='css/libs/normalize.css') }}">
<link rel="stylesheet" href="{{ url_for('static', filename='css/main.css') }}">
<link rel="stylesheet" href="{{ url_for('static', filename='css/popup.css') }}">
<link rel="stylesheet" href="{{ url_for('static', filename='css/reader.css') }}">
</head>
<body>
<div id="sidebar">
<div id="panels">
<input type="hidden" name="csrf_token" value="{{ csrf_token() }}">
<!--input id="searchBox" placeholder="search" type="search"-->
<!--a id="show-Search" class="show_view icon-search" data-view="Search">Search</a-->
<a id="show-Toc" class="show_view icon-list-1 active" data-view="Toc">TOC</a>
<a id="show-Bookmarks" class="show_view icon-bookmark" data-view="Bookmarks">Bookmarks</a>
<a id="back-button" data-view="Back to Books" href="{{ url_for('web.index') }}">Books</a>
<!--a id="show-Notes" class="show_view icon-edit" data-view="Notes">Notes</a-->
</div>
<div id="tocView" class="view">
</div>
<!--div id="searchView" class="view">
<ul id="searchResults"></ul>
</div-->
<div id="bookmarksView" class="view">
<ul id="bookmarks"></ul>
</div>
<!--div id="notesView" class="view">
<div id="new-note">
<textarea id="note-text"></textarea>
<button id="note-anchor">Anchor</button>
</div>
<ol id="notes"></ol>
</div-->
</div>
<div id="main">
<div id="titlebar">
<div id="opener">
<a id="slider" class="icon-menu">Menu</a>
</div>
<div id="metainfo">
<span id="book-title"></span>
<span id="title-seperator">&nbsp;&nbsp;&nbsp;&nbsp;</span>
<span id="chapter-title"></span>
</div>
<div id="title-controls">
<a id="bookmark" class="icon-bookmark-empty">Bookmark</a>
<a id="setting" class="icon-cog">Settings</a>
<a id="fullscreen" class="icon-resize-full">Fullscreen</a>
</div>
</div>
<div id="divider"></div>
<div id="prev" class="arrow"></div>
<div id="viewer"></div>
<div id="next" class="arrow"></div>
<div class="read-footer">
<div id="pages-count"></div>
<div id="progress">0%</div>
</div>
<div id="loader">
<img src="{{ url_for('static', filename='img/loader.gif') }}">
</div>
</div>
<div class="modal md-effect-1" id="settings-modal">
<div class="md-content">
<h3>{{_('Settings')}}</h3>
<div class="form-group themes" id="themes">
{{_('Choose a theme below:')}}<br />
<!-- Hardcoded a tick in the light theme button because it is the "default" theme. Need to find a way to do this dynamically on startup-->
<button type="button" id="lightTheme" class="lightTheme" onclick="selectTheme(this.id)"><span
id="lightSelected"></span>{{_('Light')}}</button>
<button type="button" id="darkTheme" class="darkTheme" onclick="selectTheme(this.id)"><span
id="darkSelected"> </span>{{_('Dark')}}</button>
<button type="button" id="sepiaTheme" class="sepiaTheme" onclick="selectTheme(this.id)"><span
id="sepiaSelected"> </span>{{_('Sepia')}}</button>
<button type="button" id="amberTheme" class="amberTheme" onclick="selectTheme(this.id)"><span
id="amberSelected"> </span>{{_('Amber')}}</button>
<button type="button" id="blackTheme" class="blackTheme" onclick="selectTheme(this.id)"><span
id="blackSelected"> </span>{{_('Black')}}</button>
<!-- Custom theme: color input + pickr -->
<div id="customThemeWrapper" style="display: inline-flex; align-items: center;gap: 8px;margin: 5px 8px;">Custom:
<!-- Picker-only UI: swatch acts as the picker button, and a small span shows selection tick -->
<div id="customThemeSwatch" title="Pick color" style="width: 30px; height: 30px; border: 1px solid #ccc; border-radius: 3px;cursor: pointer;"></div>
<span id="customSelected"> </span>
</div>
</div>
<div class="form-group">
<p>
<input type="checkbox" id="sidebarReflow"
name="sidebarReflow">{{_('Reflow text when sidebars are open.')}}
</p>
</div>
<div class="form-group">
<p>
<input type="checkbox" id="showPagesCount" name="showPagesCount"/>{{_('Show pages count')}}
</p>
</div>
<div class="form-group fontSizeWrapper">
<label>{{ _('Font Size') }}</label>
<div class="font-size-controls" style="display: flex;align-items: center;gap: 10px;margin-top: 2px;">
<button type="button" id="fontSizeDecrease" style="padding: 8px 15px; font-size: 18px; cursor: pointer"></button>
<span id="fontSizeDisplay" style="min-width: 60px;text-align: center;font-size: 14px;font-weight: bold;">100%</span>
<button type="button" id="fontSizeIncrease" style="padding: 8px 15px; font-size: 18px; cursor: pointer">+</button>
</div>
</div>
<div class="font" id="font">
<label class="item">{{_('Font')}}:</label>
<button type="button" id="default" onclick="selectFont(this.id)"><span></span>{{_('Default')}}</button>
<button type="button" id="Yahei" onclick="selectFont(this.id)"><span></span>{{_('Yahei')}}</button>
<button type="button" id="SimSun" onclick="selectFont(this.id)"><span></span>{{_('SimSun')}}</button>
<button type="button" id="KaiTi" onclick="selectFont(this.id)"><span></span>{{_('KaiTi')}}</button>
<button type="button" id="Arial" onclick="selectFont(this.id)"><span></span>{{_('Arial')}}</button>
</div>
<div class="layou" id="layout">
<label class="item">{{ _('Spread') }}:</label>
<button type="button" id="spread" onclick="spread(this.id)"><span></span>{{_('Two columns')}}</button>
<button type="button" id="nonespread" onclick="spread(this.id)"><span></span>{{_('One column')}}</button>
</div>
<div class="closer icon-cancel-circled"></div>
</div>
</div>
<div class="overlay"></div>
<script src="{{ url_for('static', filename='js/libs/jquery.min.js') }}"></script>
<script src="{{ url_for('static', filename='js/compress/jszip_epub.min.js') }}"></script>
<script src="{{ url_for('static', filename='js/libs/epub.min.js') }}"></script>
<script type="text/javascript">
window.calibre = {
filePath: "{{ url_for('static', filename='js/libs/') }}",
cssPath: "{{ url_for('static', filename='css/') }}",
bookmarkUrl: "{{ url_for('web.set_bookmark', book_id=bookid, book_format=book_format) }}",
bookUrl: "{{ url_for('web.serve_book', book_id=bookid, book_format=book_format, anyname='file.epub') }}",
bookmark: "{{ bookmark.bookmark_key if bookmark != None }}",
useBookmarks: "{{ current_user.is_authenticated | tojson }}"
};
// load custom theme color from localStorage (if any)
var _savedCustomThemeColor = null;
try {
_savedCustomThemeColor = localStorage.getItem(
"calibre.reader.customTheme"
);
} catch (e) {}
window.themes = {
"darkTheme": {
"bgColor": "#202124",
"css_path": "{{ url_for('static', filename='css/epub_themes.css') }}",
"title-color": "#fff"
},
"lightTheme": {
"bgColor": "white",
"css_path": "{{ url_for('static', filename='css/epub_themes.css') }}",
"title-color": "#4f4f4f"
},
"sepiaTheme": {
"bgColor": "#ece1ca",
"css_path": "{{ url_for('static', filename='css/epub_themes.css') }}",
"title-color": "#4f4f4f"
},
"amberTheme": {
"bgColor": "#e8bf5e",
"css_path": "{{ url_for('static', filename='css/epub_themes.css') }}",
"title-color": "#2f2f2f"
},
"blackTheme": {
"bgColor": "black",
"css_path": "{{ url_for('static', filename='css/epub_themes.css') }}",
"title-color": "#fff"
},
};
function selectTheme (id) {
let tickSpans = document.getElementById("themes").querySelectorAll("span");
tickSpans.forEach(function (tickSpan) {
try {
tickSpan.textContent = "";
} catch (e) {}
});
// If the theme button exists, set its inner span to a tick. Otherwise set any span with the matching id.
let el = document.getElementById(id);
if (el) {
let sp = el.querySelector("span");
if (sp) sp.textContent = "✓";
} else {
let spById = document.getElementById(id + "Selected") || document.getElementById("customSelected");
if (spById) spById.textContent = "✓";
}
// Saving theme to local storage
localStorage.setItem("calibre.reader.theme", id);
// If selecting custom theme, ensure epubjs theme is registered with chosen bg color
if (id === "customTheme") {
let customColor = window.themes.customTheme.bgColor || "#ffffff";
try {
if (reader && reader.rendition && reader.rendition.themes) {
reader.rendition.themes.register("customTheme", {
body: {
background: customColor,
},
});
reader.rendition.themes.select("customTheme");
}
} catch (e) {
console.error("Failed to register/select customTheme", e);
}
} else {
// Apply theme to epubjs iframe
try {
reader.rendition.themes.select(id);
} catch (e) {}
}
// Apply theme to rest of the page.
document.getElementById("main").style.backgroundColor = themes[id]["bgColor"];
document.getElementById("titlebar").style.color = themes[id]["title-color"] || "#fff";
document.getElementById("progress").style.color = themes[id]["title-color"] || "#fff";
}
// font size settings logic
let currentFontSize = 100; // default 100%
const minFontSize = 50;
const maxFontSize = 300;
const stepSize = 5;
const fontSizeDisplay = document.getElementById("fontSizeDisplay");
const fontSizeDecrease = document.getElementById("fontSizeDecrease");
const fontSizeIncrease = document.getElementById("fontSizeIncrease");
function updateFontSize(newSize) {
if (newSize < minFontSize) newSize = minFontSize;
if (newSize > maxFontSize) newSize = maxFontSize;
currentFontSize = newSize;
fontSizeDisplay.textContent = newSize + "%";
localStorage.setItem("calibre.reader.fontSize", newSize);
if (reader && reader.rendition) {
reader.rendition.themes.fontSize(`${newSize}%`);
}
}
// Restore saved font size on load
const savedFontSize = localStorage.getItem("calibre.reader.fontSize");
if (savedFontSize) {
currentFontSize = parseInt(savedFontSize);
fontSizeDisplay.textContent = savedFontSize + "%";
}
fontSizeDecrease.addEventListener("click", function () {
updateFontSize(currentFontSize - stepSize);
});
fontSizeIncrease.addEventListener("click", function () {
updateFontSize(currentFontSize + stepSize);
});
let defaultFont;
window.selectFont = function (id) {
if (!defaultFont) {
defaultFont = reader.rendition.getContents()[0]?.css('font-family');
}
spans = document.getElementById("font").querySelectorAll("span");
for(var i = 0; i < spans.length; i++) {
spans[i].textContent = "";
}
document.getElementById(id).querySelector("span").textContent = "✓";
// Save font selection to localStorage
localStorage.setItem("calibre.reader.font", id);
if (id == "default") {
reader.rendition.themes.font(defaultFont);
return;
}
reader.rendition.themes.font(id);
};
function spread(id) {
spans = document.getElementById("layout").querySelectorAll("span");
for(var i = 0; i < spans.length; i++) {
spans[i].textContent = "";
}
document.getElementById(id).querySelector("span").textContent = "✓";
reader.rendition.spread(id==="spread" ? true : "none");
}
// Pages counter visibility setting
(function () {
var checkbox = document.getElementById("showPagesCount");
var pagesEl = document.getElementById("pages-count");
var key = "calibre.reader.showPages";
var saved = localStorage.getItem(key);
var show = saved === null ? true : saved === "true";
if (checkbox) checkbox.checked = show;
if (pagesEl) pagesEl.style.display = show ? "" : "none";
if (checkbox) {
checkbox.addEventListener("change", function () {
var val = checkbox.checked;
localStorage.setItem(key, String(val));
var target = document.getElementById("pages-count");
if (target) target.style.visibility = val ? "visible" : "hidden";
});
}
})();
</script>
<script type="text/javascript">
// Initialize custom theme UI and Pickr once Pickr is available
(function () {
var swatch = document.getElementById("customThemeSwatch");
var saved =
window.themes &&
window.themes.customTheme &&
window.themes.customTheme.bgColor
? window.themes.customTheme.bgColor
: "#ffffff";
if (swatch) swatch.style.background = saved;
function _hexToRgb(hex) {
hex = hex.replace("#", "");
if (hex.length === 3) {
hex = hex
.split("")
.map(function (h) {
return h + h;
})
.join("");
}
var bigint = parseInt(hex, 16);
return {
r: (bigint >> 16) & 255,
g: (bigint >> 8) & 255,
b: bigint & 255,
};
}
// Better contrast decision using WCAG relative luminance and contrast ratio
// Returns true if black text is the better choice (i.e. background is light)
function _isLight(hex) {
try {
var rgb = _hexToRgb(hex);
// convert 0-255 to 0-1
var srgb = { r: rgb.r / 255, g: rgb.g / 255, b: rgb.b / 255 };
function lin(c) {
return c <= 0.03928
? c / 12.92
: Math.pow((c + 0.055) / 1.055, 2.4);
}
var R = lin(srgb.r),
G = lin(srgb.g),
B = lin(srgb.b);
var L = 0.2126 * R + 0.7152 * G + 0.0722 * B; // relative luminance
// contrast ratios against black (L=0) and white (L=1)
var contrastWithBlack = (L + 0.05) / (0.0 + 0.05);
var contrastWithWhite = (1.0 + 0.05) / (L + 0.05);
// pick the text color that gives higher contrast. If black gives >= contrast, treat bg as light
return contrastWithBlack >= contrastWithWhite;
} catch (e) {
return true;
}
}
function applyCustomColor(hex) {
if (!hex) return;
if (hex[0] !== "#") hex = "#" + hex;
window.themes.customTheme.bgColor = hex;
try {
localStorage.setItem("calibre.reader.customTheme", hex);
} catch (e) {}
if (swatch) swatch.style.background = hex;
// Mark theme as selected
try {
localStorage.setItem("calibre.reader.theme", "customTheme");
} catch (e) {}
// Clear ticks and set custom tick
var tickSpans = document
.getElementById("themes")
.querySelectorAll("span");
tickSpans.forEach(function (ts) {
ts.textContent = "";
});
var customTick = document.getElementById("customSelected");
if (customTick) customTick.textContent = "✓";
// Compute title/text color based on contrast (black or white)
var titleColor = _isLight(hex) ? "#000000" : "#ffffff";
try {
document.getElementById("main").style.backgroundColor = hex;
document.getElementById("titlebar").style.color = titleColor;
document.getElementById("progress").style.color = titleColor;
} catch (e) {}
// Persist title-color in themes map so selectTheme reads a consistent value
try {
window.themes.customTheme["title-color"] = titleColor;
} catch (e) {}
// Register and select theme in epub rendition (include text color)
try {
if (reader && reader.rendition && reader.rendition.themes) {
reader.rendition.themes.register("customTheme", {
body: { background: hex, color: titleColor },
});
reader.rendition.themes.select("customTheme");
}
} catch (e) {
console.error("Failed to apply custom theme to reader", e);
}
}
// Delay init until Pickr is available
function ensurePickrAndInit() {
if (window.Pickr) {
try {
var pickr = Pickr.create({
el: "#customThemeSwatch",
theme: "classic",
default: saved,
components: {
preview: true,
opacity: false,
hue: true,
interaction: {
hex: true,
input: true,
save: true,
},
},
});
// Immediate apply when color changes in the picker
pickr.on("change", function (color, instance) {
try {
var hex = color.toHEXA().toString();
if (swatch) swatch.style.background = hex;
applyCustomColor(hex);
} catch (e) {}
});
// Also respond to save (some pickr configs use save)
pickr.on("save", function (color, instance) {
try {
var hex = color.toHEXA().toString();
if (swatch) swatch.style.background = hex;
applyCustomColor(hex);
pickr.hide();
} catch (e) {}
});
// Clicking swatch opens pickr
if (swatch)
swatch.addEventListener("click", function () {
pickr.show();
});
} catch (e) {
console.error("Pickr init failed", e);
}
return;
}
// wait a bit
setTimeout(ensurePickrAndInit, 150);
}
ensurePickrAndInit();
})();
</script>
<script src="{{ url_for('static', filename='js/libs/screenfull.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>
</html>