mirror of
https://github.com/janeczku/calibre-web
synced 2024-11-24 18:47:23 +00:00
Merge branch 'master' into Develop
# Conflicts: # cps/services/SyncToken.py # cps/static/js/kthoom.js # cps/templates/readcbr.html
This commit is contained in:
commit
cfa309f0d1
@ -33,7 +33,7 @@ from functools import wraps
|
|||||||
from urllib.parse import urlparse
|
from urllib.parse import urlparse
|
||||||
|
|
||||||
from flask import Blueprint, flash, redirect, url_for, abort, request, make_response, send_from_directory, g, Response
|
from flask import Blueprint, flash, redirect, url_for, abort, request, make_response, send_from_directory, g, Response
|
||||||
from flask import Markup
|
from markupsafe import Markup
|
||||||
from flask_login import login_required, current_user, logout_user
|
from flask_login import login_required, current_user, logout_user
|
||||||
from flask_babel import gettext as _
|
from flask_babel import gettext as _
|
||||||
from flask_babel import get_locale, format_time, format_datetime, format_timedelta
|
from flask_babel import get_locale, format_time, format_datetime, format_timedelta
|
||||||
|
@ -663,7 +663,7 @@ class CalibreDB:
|
|||||||
|
|
||||||
cls.session_factory = scoped_session(sessionmaker(autocommit=False,
|
cls.session_factory = scoped_session(sessionmaker(autocommit=False,
|
||||||
autoflush=True,
|
autoflush=True,
|
||||||
bind=cls.engine))
|
bind=cls.engine, future=True))
|
||||||
for inst in cls.instances:
|
for inst in cls.instances:
|
||||||
inst.init_session()
|
inst.init_session()
|
||||||
|
|
||||||
|
@ -25,16 +25,15 @@ from datetime import datetime
|
|||||||
import json
|
import json
|
||||||
from shutil import copyfile
|
from shutil import copyfile
|
||||||
from uuid import uuid4
|
from uuid import uuid4
|
||||||
from markupsafe import escape # dependency of flask
|
from markupsafe import escape, Markup # dependency of flask
|
||||||
from functools import wraps
|
from functools import wraps
|
||||||
import re
|
|
||||||
|
|
||||||
try:
|
try:
|
||||||
from lxml.html.clean import clean_html, Cleaner
|
from lxml.html.clean import clean_html, Cleaner
|
||||||
except ImportError:
|
except ImportError:
|
||||||
clean_html = None
|
clean_html = None
|
||||||
|
|
||||||
from flask import Blueprint, request, flash, redirect, url_for, abort, Markup, Response
|
from flask import Blueprint, request, flash, redirect, url_for, abort, Response
|
||||||
from flask_babel import gettext as _
|
from flask_babel import gettext as _
|
||||||
from flask_babel import lazy_gettext as N_
|
from flask_babel import lazy_gettext as N_
|
||||||
from flask_babel import get_locale
|
from flask_babel import get_locale
|
||||||
|
24
cps/epub.py
24
cps/epub.py
@ -21,10 +21,11 @@ import zipfile
|
|||||||
from lxml import etree
|
from lxml import etree
|
||||||
|
|
||||||
from . import isoLanguages, cover
|
from . import isoLanguages, cover
|
||||||
from . import config
|
from . import config, logger
|
||||||
from .helper import split_authors
|
from .helper import split_authors
|
||||||
from .constants import BookMeta
|
from .constants import BookMeta
|
||||||
|
|
||||||
|
log = logger.create()
|
||||||
|
|
||||||
def _extract_cover(zip_file, cover_file, cover_path, tmp_file_name):
|
def _extract_cover(zip_file, cover_file, cover_path, tmp_file_name):
|
||||||
if cover_file is None:
|
if cover_file is None:
|
||||||
@ -49,15 +50,20 @@ def get_epub_layout(book, book_data):
|
|||||||
}
|
}
|
||||||
file_path = os.path.normpath(os.path.join(config.config_calibre_dir, book.path, book_data.name + "." + book_data.format.lower()))
|
file_path = os.path.normpath(os.path.join(config.config_calibre_dir, book.path, book_data.name + "." + book_data.format.lower()))
|
||||||
|
|
||||||
epubZip = zipfile.ZipFile(file_path)
|
try:
|
||||||
txt = epubZip.read('META-INF/container.xml')
|
epubZip = zipfile.ZipFile(file_path)
|
||||||
tree = etree.fromstring(txt)
|
txt = epubZip.read('META-INF/container.xml')
|
||||||
cfname = tree.xpath('n:rootfiles/n:rootfile/@full-path', namespaces=ns)[0]
|
tree = etree.fromstring(txt)
|
||||||
cf = epubZip.read(cfname)
|
cfname = tree.xpath('n:rootfiles/n:rootfile/@full-path', namespaces=ns)[0]
|
||||||
tree = etree.fromstring(cf)
|
cf = epubZip.read(cfname)
|
||||||
p = tree.xpath('/pkg:package/pkg:metadata', namespaces=ns)[0]
|
|
||||||
|
|
||||||
layout = p.xpath('pkg:meta[@property="rendition:layout"]/text()', namespaces=ns)
|
tree = etree.fromstring(cf)
|
||||||
|
p = tree.xpath('/pkg:package/pkg:metadata', namespaces=ns)[0]
|
||||||
|
|
||||||
|
layout = p.xpath('pkg:meta[@property="rendition:layout"]/text()', namespaces=ns)
|
||||||
|
except (etree.XMLSyntaxError, KeyError, IndexError) as e:
|
||||||
|
log.error("Could not parse epub metadata of book {} during kobo sync: {}".format(book.id, e))
|
||||||
|
layout = []
|
||||||
|
|
||||||
if len(layout) == 0:
|
if len(layout) == 0:
|
||||||
return None
|
return None
|
||||||
|
@ -732,28 +732,27 @@ def delete_book(book, calibrepath, book_format):
|
|||||||
return delete_book_file(book, calibrepath, book_format)
|
return delete_book_file(book, calibrepath, book_format)
|
||||||
|
|
||||||
|
|
||||||
def get_cover_on_failure(use_generic_cover):
|
def get_cover_on_failure():
|
||||||
if use_generic_cover:
|
try:
|
||||||
try:
|
return send_from_directory(_STATIC_DIR, "generic_cover.jpg")
|
||||||
return send_from_directory(_STATIC_DIR, "generic_cover.jpg")
|
except PermissionError:
|
||||||
except PermissionError:
|
log.error("No permission to access generic_cover.jpg file.")
|
||||||
log.error("No permission to access generic_cover.jpg file.")
|
abort(403)
|
||||||
abort(403)
|
|
||||||
abort(404)
|
|
||||||
|
|
||||||
|
|
||||||
def get_book_cover(book_id, resolution=None):
|
def get_book_cover(book_id, resolution=None):
|
||||||
book = calibre_db.get_filtered_book(book_id, allow_show_archived=True)
|
book = calibre_db.get_filtered_book(book_id, allow_show_archived=True)
|
||||||
return get_book_cover_internal(book, use_generic_cover_on_failure=True, resolution=resolution)
|
return get_book_cover_internal(book, resolution=resolution)
|
||||||
|
|
||||||
|
|
||||||
# Called only by kobo sync -> cover not found should be answered with 404 and not with default cover
|
|
||||||
def get_book_cover_with_uuid(book_uuid, resolution=None):
|
def get_book_cover_with_uuid(book_uuid, resolution=None):
|
||||||
book = calibre_db.get_book_by_uuid(book_uuid)
|
book = calibre_db.get_book_by_uuid(book_uuid)
|
||||||
return get_book_cover_internal(book, use_generic_cover_on_failure=False, resolution=resolution)
|
if not book:
|
||||||
|
return # allows kobo.HandleCoverImageRequest to proxy request
|
||||||
|
return get_book_cover_internal(book, resolution=resolution)
|
||||||
|
|
||||||
|
|
||||||
def get_book_cover_internal(book, use_generic_cover_on_failure, resolution=None):
|
def get_book_cover_internal(book, resolution=None):
|
||||||
if book and book.has_cover:
|
if book and book.has_cover:
|
||||||
|
|
||||||
# Send the book cover thumbnail if it exists in cache
|
# Send the book cover thumbnail if it exists in cache
|
||||||
@ -769,16 +768,16 @@ def get_book_cover_internal(book, use_generic_cover_on_failure, resolution=None)
|
|||||||
if config.config_use_google_drive:
|
if config.config_use_google_drive:
|
||||||
try:
|
try:
|
||||||
if not gd.is_gdrive_ready():
|
if not gd.is_gdrive_ready():
|
||||||
return get_cover_on_failure(use_generic_cover_on_failure)
|
return get_cover_on_failure()
|
||||||
path = gd.get_cover_via_gdrive(book.path)
|
path = gd.get_cover_via_gdrive(book.path)
|
||||||
if path:
|
if path:
|
||||||
return redirect(path)
|
return redirect(path)
|
||||||
else:
|
else:
|
||||||
log.error('{}/cover.jpg not found on Google Drive'.format(book.path))
|
log.error('{}/cover.jpg not found on Google Drive'.format(book.path))
|
||||||
return get_cover_on_failure(use_generic_cover_on_failure)
|
return get_cover_on_failure()
|
||||||
except Exception as ex:
|
except Exception as ex:
|
||||||
log.error_or_exception(ex)
|
log.error_or_exception(ex)
|
||||||
return get_cover_on_failure(use_generic_cover_on_failure)
|
return get_cover_on_failure()
|
||||||
|
|
||||||
# Send the book cover from the Calibre directory
|
# Send the book cover from the Calibre directory
|
||||||
else:
|
else:
|
||||||
@ -786,9 +785,9 @@ def get_book_cover_internal(book, use_generic_cover_on_failure, resolution=None)
|
|||||||
if os.path.isfile(os.path.join(cover_file_path, "cover.jpg")):
|
if os.path.isfile(os.path.join(cover_file_path, "cover.jpg")):
|
||||||
return send_from_directory(cover_file_path, "cover.jpg")
|
return send_from_directory(cover_file_path, "cover.jpg")
|
||||||
else:
|
else:
|
||||||
return get_cover_on_failure(use_generic_cover_on_failure)
|
return get_cover_on_failure()
|
||||||
else:
|
else:
|
||||||
return get_cover_on_failure(use_generic_cover_on_failure)
|
return get_cover_on_failure()
|
||||||
|
|
||||||
|
|
||||||
def get_book_cover_thumbnail(book, resolution):
|
def get_book_cover_thumbnail(book, resolution):
|
||||||
@ -811,7 +810,7 @@ def get_series_thumbnail_on_failure(series_id, resolution):
|
|||||||
.filter(db.Books.has_cover == 1) \
|
.filter(db.Books.has_cover == 1) \
|
||||||
.first()
|
.first()
|
||||||
|
|
||||||
return get_book_cover_internal(book, use_generic_cover_on_failure=True, resolution=resolution)
|
return get_book_cover_internal(book, resolution=resolution)
|
||||||
|
|
||||||
|
|
||||||
def get_series_cover_thumbnail(series_id, resolution=None):
|
def get_series_cover_thumbnail(series_id, resolution=None):
|
||||||
|
34
cps/kobo.py
34
cps/kobo.py
@ -930,20 +930,26 @@ def get_current_bookmark_response(current_bookmark):
|
|||||||
@kobo.route("/<book_uuid>/<width>/<height>/<Quality>/<isGreyscale>/image.jpg")
|
@kobo.route("/<book_uuid>/<width>/<height>/<Quality>/<isGreyscale>/image.jpg")
|
||||||
@requires_kobo_auth
|
@requires_kobo_auth
|
||||||
def HandleCoverImageRequest(book_uuid, width, height, Quality, isGreyscale):
|
def HandleCoverImageRequest(book_uuid, width, height, Quality, isGreyscale):
|
||||||
book_cover = helper.get_book_cover_with_uuid(book_uuid, resolution=COVER_THUMBNAIL_SMALL)
|
try:
|
||||||
if not book_cover:
|
resolution = None if int(height) > 1000 else COVER_THUMBNAIL_SMALL
|
||||||
if config.config_kobo_proxy:
|
except ValueError:
|
||||||
log.debug("Cover for unknown book: %s proxied to kobo" % book_uuid)
|
log.error("Requested height %s of book %s is invalid" % (book_uuid, height))
|
||||||
return redirect(KOBO_IMAGEHOST_URL +
|
resolution = COVER_THUMBNAIL_SMALL
|
||||||
"/{book_uuid}/{width}/{height}/false/image.jpg".format(book_uuid=book_uuid,
|
book_cover = helper.get_book_cover_with_uuid(book_uuid, resolution=resolution)
|
||||||
width=width,
|
if book_cover:
|
||||||
height=height), 307)
|
log.debug("Serving local cover image of book %s" % book_uuid)
|
||||||
else:
|
return book_cover
|
||||||
log.debug("Cover for unknown book: %s requested" % book_uuid)
|
|
||||||
# additional proxy request make no sense, -> direct return
|
if not config.config_kobo_proxy:
|
||||||
return make_response(jsonify({}))
|
log.debug("Returning 404 for cover image of unknown book %s" % book_uuid)
|
||||||
log.debug("Cover request received for book %s" % book_uuid)
|
# additional proxy request make no sense, -> direct return
|
||||||
return book_cover
|
return abort(404)
|
||||||
|
|
||||||
|
log.debug("Redirecting request for cover image of unknown book %s to Kobo" % book_uuid)
|
||||||
|
return redirect(KOBO_IMAGEHOST_URL +
|
||||||
|
"/{book_uuid}/{width}/{height}/false/image.jpg".format(book_uuid=book_uuid,
|
||||||
|
width=width,
|
||||||
|
height=height), 307)
|
||||||
|
|
||||||
|
|
||||||
@kobo.route("")
|
@kobo.route("")
|
||||||
|
@ -98,7 +98,7 @@ class Amazon(Metadata):
|
|||||||
try:
|
try:
|
||||||
match.authors = [next(
|
match.authors = [next(
|
||||||
filter(lambda i: i != " " and i != "\n" and not i.startswith("{"),
|
filter(lambda i: i != " " and i != "\n" and not i.startswith("{"),
|
||||||
x.findAll(text=True))).strip()
|
x.findAll(string=True))).strip()
|
||||||
for x in soup2.findAll("span", attrs={"class": "author"})]
|
for x in soup2.findAll("span", attrs={"class": "author"})]
|
||||||
except (AttributeError, TypeError, StopIteration):
|
except (AttributeError, TypeError, StopIteration):
|
||||||
match.authors = ""
|
match.authors = ""
|
||||||
|
@ -102,7 +102,7 @@ class LubimyCzytac(Metadata):
|
|||||||
PUBLISH_DATE = "//dt[contains(@title,'Data pierwszego wydania"
|
PUBLISH_DATE = "//dt[contains(@title,'Data pierwszego wydania"
|
||||||
FIRST_PUBLISH_DATE = f"{DETAILS}{PUBLISH_DATE} oryginalnego')]{SIBLINGS}[1]/text()"
|
FIRST_PUBLISH_DATE = f"{DETAILS}{PUBLISH_DATE} oryginalnego')]{SIBLINGS}[1]/text()"
|
||||||
FIRST_PUBLISH_DATE_PL = f"{DETAILS}{PUBLISH_DATE} polskiego')]{SIBLINGS}[1]/text()"
|
FIRST_PUBLISH_DATE_PL = f"{DETAILS}{PUBLISH_DATE} polskiego')]{SIBLINGS}[1]/text()"
|
||||||
TAGS = "//nav[@aria-label='breadcrumb']//a[contains(@href,'/ksiazki/k/')]/text()"
|
TAGS = "//nav[@aria-label='breadcrumbs']//a[contains(@href,'/ksiazki/k/')]/span/text()"
|
||||||
|
|
||||||
RATING = "//meta[@property='books:rating:value']/@content"
|
RATING = "//meta[@property='books:rating:value']/@content"
|
||||||
COVER = "//meta[@property='og:image']/@content"
|
COVER = "//meta[@property='og:image']/@content"
|
||||||
|
@ -7279,6 +7279,11 @@ body.edituser.admin > div.container-fluid > div.row-fluid > div.col-sm-10 > div.
|
|||||||
float: right
|
float: right
|
||||||
}
|
}
|
||||||
|
|
||||||
|
body.blur #main-nav + #scnd-nav .create-shelf, body.blur #main-nav + .col-sm-2 #scnd-nav .create-shelf {
|
||||||
|
float: none;
|
||||||
|
margin: 5px 0 10px -10px;
|
||||||
|
}
|
||||||
|
|
||||||
#main-nav + #scnd-nav .nav-head.hidden-xs {
|
#main-nav + #scnd-nav .nav-head.hidden-xs {
|
||||||
display: list-item !important;
|
display: list-item !important;
|
||||||
width: 225px
|
width: 225px
|
||||||
|
@ -40,6 +40,7 @@ $(".sendbtn-form").click(function() {
|
|||||||
$.ajax({
|
$.ajax({
|
||||||
method: 'post',
|
method: 'post',
|
||||||
url: $(this).data('href'),
|
url: $(this).data('href'),
|
||||||
|
data: {csrf_token: $("input[name='csrf_token']").val()},
|
||||||
success: function (data) {
|
success: function (data) {
|
||||||
handleResponse(data)
|
handleResponse(data)
|
||||||
}
|
}
|
||||||
|
@ -71,7 +71,8 @@ var settings = {
|
|||||||
fitMode: kthoom.Key.B,
|
fitMode: kthoom.Key.B,
|
||||||
theme: "light",
|
theme: "light",
|
||||||
direction: 0, // 0 = Left to Right, 1 = Right to Left
|
direction: 0, // 0 = Left to Right, 1 = Right to Left
|
||||||
scrollbar: 1, // 0 = Hide Scrollbar, 1 = Show Scrollbar
|
nextPage: 0, // 0 = Reset to Top, 1 = Remember Position
|
||||||
|
scrollbar: 1, // 0 = Hide Scrollbar, 1 = Show Scrollbar
|
||||||
pageDisplay: 0 // 0 = Single Page, 1 = Long Strip
|
pageDisplay: 0 // 0 = Single Page, 1 = Long Strip
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -131,7 +132,7 @@ var createURLFromArray = function(array, mimeType) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if ((typeof URL !== "function" && typeof URL !== "object") ||
|
if ((typeof URL !== "function" && typeof URL !== "object") ||
|
||||||
typeof URL.createObjectURL !== "function") {
|
typeof URL.createObjectURL !== "function") {
|
||||||
throw "Browser support for Object URLs is missing";
|
throw "Browser support for Object URLs is missing";
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -206,7 +207,7 @@ function initProgressClick() {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
function loadFromArrayBuffer(ab) {
|
function loadFromArrayBuffer(ab, lastCompletion = 0) {
|
||||||
const collator = new Intl.Collator('en', { numeric: true, sensitivity: 'base' });
|
const collator = new Intl.Collator('en', { numeric: true, sensitivity: 'base' });
|
||||||
loadArchiveFormats(['rar', 'zip', 'tar'], function() {
|
loadArchiveFormats(['rar', 'zip', 'tar'], function() {
|
||||||
// Open the file as an archive
|
// Open the file as an archive
|
||||||
@ -246,7 +247,7 @@ function loadFromArrayBuffer(ab) {
|
|||||||
} else {
|
} else {
|
||||||
$("#left").show();
|
$("#left").show();
|
||||||
}
|
}
|
||||||
updatePage();
|
updatePage(lastCompletion);
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
totalImages--;
|
totalImages--;
|
||||||
@ -261,17 +262,6 @@ function loadFromArrayBuffer(ab) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function scrollTocToActive() {
|
function scrollTocToActive() {
|
||||||
$(".page").text((currentImage + 1 ) + "/" + totalImages);
|
|
||||||
|
|
||||||
// Mark the current page in the TOC
|
|
||||||
$("#tocView a[data-page]")
|
|
||||||
// Remove the currently active thumbnail
|
|
||||||
.removeClass("active")
|
|
||||||
// Find the new one
|
|
||||||
.filter("[data-page=" + (currentImage + 1) + "]")
|
|
||||||
// Set it to active
|
|
||||||
.addClass("active");
|
|
||||||
|
|
||||||
// Scroll to the thumbnail in the TOC on page change
|
// Scroll to the thumbnail in the TOC on page change
|
||||||
$("#tocView").stop().animate({
|
$("#tocView").stop().animate({
|
||||||
scrollTop: $("#tocView a.active").position().top
|
scrollTop: $("#tocView a.active").position().top
|
||||||
@ -279,12 +269,31 @@ function scrollTocToActive() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function updatePage() {
|
function updatePage() {
|
||||||
|
$(".page").text((currentImage + 1 ) + "/" + totalImages);
|
||||||
|
|
||||||
|
// Mark the current page in the TOC
|
||||||
|
$("#tocView a[data-page]")
|
||||||
|
// Remove the currently active thumbnail
|
||||||
|
.removeClass("active")
|
||||||
|
// Find the new one
|
||||||
|
.filter("[data-page=" + (currentImage + 1) + "]")
|
||||||
|
// Set it to active
|
||||||
|
.addClass("active");
|
||||||
|
|
||||||
scrollTocToActive();
|
scrollTocToActive();
|
||||||
scrollCurrentImageIntoView();
|
|
||||||
updateProgress();
|
updateProgress();
|
||||||
pageDisplayUpdate();
|
pageDisplayUpdate();
|
||||||
setTheme();
|
setTheme();
|
||||||
|
|
||||||
|
if (imageFiles[currentImage]) {
|
||||||
|
setImage(imageFiles[currentImage].dataURI);
|
||||||
|
} else {
|
||||||
|
setImage("loading");
|
||||||
|
}
|
||||||
|
|
||||||
|
$("body").toggleClass("dark-theme", settings.theme === "dark");
|
||||||
|
$("#mainContent").toggleClass("disabled-scrollbar", settings.scrollbar === 0);
|
||||||
|
|
||||||
kthoom.setSettings();
|
kthoom.setSettings();
|
||||||
kthoom.saveSettings();
|
kthoom.saveSettings();
|
||||||
}
|
}
|
||||||
@ -359,6 +368,7 @@ function setImage(url, _canvas) {
|
|||||||
img.onerror = function() {
|
img.onerror = function() {
|
||||||
canvas.width = innerWidth - 100;
|
canvas.width = innerWidth - 100;
|
||||||
canvas.height = 300;
|
canvas.height = 300;
|
||||||
|
updateScale(true);
|
||||||
x.fillStyle = "black";
|
x.fillStyle = "black";
|
||||||
x.font = "50px sans-serif";
|
x.font = "50px sans-serif";
|
||||||
x.strokeStyle = "black";
|
x.strokeStyle = "black";
|
||||||
@ -412,6 +422,8 @@ function setImage(url, _canvas) {
|
|||||||
scrollTo(0, 0);
|
scrollTo(0, 0);
|
||||||
x.drawImage(img, 0, 0);
|
x.drawImage(img, 0, 0);
|
||||||
|
|
||||||
|
updateScale(false);
|
||||||
|
|
||||||
canvas.style.display = "";
|
canvas.style.display = "";
|
||||||
$("body").css("overflowY", "");
|
$("body").css("overflowY", "");
|
||||||
x.restore();
|
x.restore();
|
||||||
@ -450,6 +462,9 @@ function showPrevPage() {
|
|||||||
currentImage++;
|
currentImage++;
|
||||||
} else {
|
} else {
|
||||||
updatePage();
|
updatePage();
|
||||||
|
if (settings.nextPage === 0) {
|
||||||
|
$("#mainContent").scrollTop(0);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
updateDirectionButtons();
|
updateDirectionButtons();
|
||||||
}
|
}
|
||||||
@ -461,6 +476,9 @@ function showNextPage() {
|
|||||||
currentImage--;
|
currentImage--;
|
||||||
} else {
|
} else {
|
||||||
updatePage();
|
updatePage();
|
||||||
|
if (settings.nextPage === 0) {
|
||||||
|
$("#mainContent").scrollTop(0);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
updateDirectionButtons();
|
updateDirectionButtons();
|
||||||
}
|
}
|
||||||
@ -477,7 +495,7 @@ function scrollCurrentImageIntoView() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function updateScale() {
|
function updateScale(clear) {
|
||||||
var canvasArray = $("#mainContent > canvas");
|
var canvasArray = $("#mainContent > canvas");
|
||||||
var maxheight = innerHeight - 50;
|
var maxheight = innerHeight - 50;
|
||||||
|
|
||||||
@ -486,7 +504,7 @@ function updateScale() {
|
|||||||
canvasArray.css("maxWidth", "");
|
canvasArray.css("maxWidth", "");
|
||||||
canvasArray.css("maxHeight", "");
|
canvasArray.css("maxHeight", "");
|
||||||
|
|
||||||
if(settings.pageDisplay === 0) {
|
if(!clear) {
|
||||||
canvasArray.addClass("hide");
|
canvasArray.addClass("hide");
|
||||||
pageDisplayUpdate();
|
pageDisplayUpdate();
|
||||||
}
|
}
|
||||||
@ -653,7 +671,7 @@ function init(filename) {
|
|||||||
request.responseType = "arraybuffer";
|
request.responseType = "arraybuffer";
|
||||||
request.addEventListener("load", function() {
|
request.addEventListener("load", function() {
|
||||||
if (request.status >= 200 && request.status < 300) {
|
if (request.status >= 200 && request.status < 300) {
|
||||||
loadFromArrayBuffer(request.response);
|
loadFromArrayBuffer(request.response, currentImage);
|
||||||
} else {
|
} else {
|
||||||
console.warn(request.statusText, request.responseText);
|
console.warn(request.statusText, request.responseText);
|
||||||
}
|
}
|
||||||
@ -708,7 +726,7 @@ function init(filename) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
updatePage();
|
updatePage();
|
||||||
updateScale();
|
updateScale(false);
|
||||||
});
|
});
|
||||||
|
|
||||||
// Close modal
|
// Close modal
|
||||||
@ -721,6 +739,9 @@ function init(filename) {
|
|||||||
$("#thumbnails").on("click", "a", function() {
|
$("#thumbnails").on("click", "a", function() {
|
||||||
currentImage = $(this).data("page") - 1;
|
currentImage = $(this).data("page") - 1;
|
||||||
updatePage();
|
updatePage();
|
||||||
|
if (settings.nextPage === 0) {
|
||||||
|
$("#mainContent").scrollTop(0);
|
||||||
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
// Fullscreen mode
|
// Fullscreen mode
|
||||||
|
@ -43,30 +43,30 @@
|
|||||||
{% endif %}
|
{% endif %}
|
||||||
</div>
|
</div>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
{% endif %}
|
{% if current_user.kindle_mail and entry.email_share_list %}
|
||||||
{% if current_user.kindle_mail and entry.email_share_list %}
|
<input type="hidden" name="csrf_token" value="{{ csrf_token() }}">
|
||||||
<input type="hidden" name="csrf_token" value="{{ csrf_token() }}">
|
{% if entry.email_share_list.__len__() == 1 %}
|
||||||
{% if entry.email_share_list.__len__() == 1 %}
|
<div class="btn-group" role="group">
|
||||||
<div class="btn-group" role="group">
|
<button id="sendbtn" class="btn btn-primary sendbtn-form" data-href="{{url_for('web.send_to_ereader', book_id=entry.id, book_format=entry.email_share_list[0]['format'], convert=entry.email_share_list[0]['convert'])}}">
|
||||||
<button id="sendbtn" class="btn btn-primary sendbtn-form" data-href="{{url_for('web.send_to_ereader', book_id=entry.id, book_format=entry.email_share_list[0]['format'], convert=entry.email_share_list[0]['convert'])}}">
|
<span class="glyphicon glyphicon-send"></span> {{entry.email_share_list[0]['text']}}
|
||||||
<span class="glyphicon glyphicon-send"></span> {{entry.email_share_list[0]['text']}}
|
</button>
|
||||||
</button>
|
</div>
|
||||||
</div>
|
{% else %}
|
||||||
{% else %}
|
<div class="btn-group" role="group">
|
||||||
<div class="btn-group" role="group">
|
<button id="sendbtn2" type="button" class="btn btn-primary dropdown-toggle"
|
||||||
<button id="sendbtn2" type="button" class="btn btn-primary dropdown-toggle"
|
data-toggle="dropdown" aria-haspopup="true" aria-expanded="false">
|
||||||
data-toggle="dropdown" aria-haspopup="true" aria-expanded="false">
|
<span class="glyphicon glyphicon-send"></span>{{ _('Send to eReader') }}
|
||||||
<span class="glyphicon glyphicon-send"></span>{{ _('Send to eReader') }}
|
<span class="caret"></span>
|
||||||
<span class="caret"></span>
|
</button>
|
||||||
</button>
|
<ul class="dropdown-menu" aria-labelledby="send-to-ereader">
|
||||||
<ul class="dropdown-menu" aria-labelledby="send-to-ereader">
|
{% for format in entry.email_share_list %}
|
||||||
{% for format in entry.email_share_list %}
|
<li>
|
||||||
<li>
|
<a class="sendbtn-form" data-href="{{url_for('web.send_to_ereader', book_id=entry.id, book_format=format['format'], convert=format['convert'])}}">{{ format['text'] }}</a>
|
||||||
<a class="sendbtn-form" data-href="{{url_for('web.send_to_ereader', book_id=entry.id, book_format=format['format'], convert=format['convert'])}}">{{ format['text'] }}</a>
|
</li>
|
||||||
</li>
|
{% endfor %}
|
||||||
{% endfor %}
|
</ul>
|
||||||
</ul>
|
</div>
|
||||||
</div>
|
{% endif %}
|
||||||
{% endif %}
|
{% endif %}
|
||||||
{% endif %}
|
{% endif %}
|
||||||
{% if entry.reader_list and current_user.role_viewer() %}
|
{% if entry.reader_list and current_user.role_viewer() %}
|
||||||
|
@ -1,5 +1,6 @@
|
|||||||
<!DOCTYPE html>
|
<!DOCTYPE html>
|
||||||
<html>
|
<html>
|
||||||
|
|
||||||
<head>
|
<head>
|
||||||
<meta charset="utf-8">
|
<meta charset="utf-8">
|
||||||
<meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1">
|
<meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1">
|
||||||
@ -20,23 +21,6 @@
|
|||||||
<script src="{{ url_for('static', filename='js/libs/screenfull.min.js') }}"></script>
|
<script src="{{ url_for('static', filename='js/libs/screenfull.min.js') }}"></script>
|
||||||
<script src="{{ url_for('static', filename='js/compress/uncompress.js') }}"></script>
|
<script src="{{ url_for('static', filename='js/compress/uncompress.js') }}"></script>
|
||||||
<script src="{{ url_for('static', filename='js/kthoom.js') }}"></script>
|
<script src="{{ url_for('static', filename='js/kthoom.js') }}"></script>
|
||||||
<script>
|
|
||||||
var updateArrows = function() {
|
|
||||||
if ($('input[name="direction"]:checked').val() === "0") {
|
|
||||||
$("#prev_page_key").html("←");
|
|
||||||
$("#next_page_key").html("→");
|
|
||||||
} else {
|
|
||||||
$("#prev_page_key").html("→");
|
|
||||||
$("#next_page_key").html("←");
|
|
||||||
}
|
|
||||||
};
|
|
||||||
document.onreadystatechange = function () {
|
|
||||||
if (document.readyState == "complete") {
|
|
||||||
init("{{ url_for('web.serve_book', book_id=comicfile, book_format=extension) }}");
|
|
||||||
updateArrows();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
</script>
|
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
<div id="sidebar">
|
<div id="sidebar">
|
||||||
@ -77,8 +61,8 @@
|
|||||||
<div id="mainContent" tabindex="-1">
|
<div id="mainContent" tabindex="-1">
|
||||||
<div id="mainText" style="display:none"></div>
|
<div id="mainText" style="display:none"></div>
|
||||||
</div>
|
</div>
|
||||||
<div id="left" class="arrow" style="display:none" onclick="showLeftPage()">‹</div>
|
<div id="left" class="arrow" style="display:none" onclick="showLeftPage(); setBookmark();">‹</div>
|
||||||
<div id="right" class="arrow" style="display:none" onclick="showRightPage()">›</div>
|
<div id="right" class="arrow" style="display:none" onclick="showRightPage(); setBookmark();">›</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="modal md-effect-1" id="settings-modal">
|
<div class="modal md-effect-1" id="settings-modal">
|
||||||
@ -89,8 +73,8 @@
|
|||||||
<table>
|
<table>
|
||||||
<thead>
|
<thead>
|
||||||
<tr><th colspan="2">{{_('Keyboard Shortcuts')}}</th></tr>
|
<tr><th colspan="2">{{_('Keyboard Shortcuts')}}</th></tr>
|
||||||
</thead>
|
</thead>
|
||||||
<tbody>
|
<tbody>
|
||||||
<tr><td id="prev_page_key">←</td> <td>{{_('Previous Page')}}</td></tr>
|
<tr><td id="prev_page_key">←</td> <td>{{_('Previous Page')}}</td></tr>
|
||||||
<tr><td id="next_page_key">→</td> <td>{{_('Next Page')}}</td></tr>
|
<tr><td id="next_page_key">→</td> <td>{{_('Next Page')}}</td></tr>
|
||||||
<tr><td>S</td> <td>{{_('Single Page Display')}}</td></tr>
|
<tr><td>S</td> <td>{{_('Single Page Display')}}</td></tr>
|
||||||
@ -102,21 +86,21 @@
|
|||||||
<tr><td>R</td> <td>{{_('Rotate Right')}}</td></tr>
|
<tr><td>R</td> <td>{{_('Rotate Right')}}</td></tr>
|
||||||
<tr><td>L</td> <td>{{_('Rotate Left')}}</td></tr>
|
<tr><td>L</td> <td>{{_('Rotate Left')}}</td></tr>
|
||||||
<tr><td>F</td> <td>{{_('Flip Image')}}</td></tr>
|
<tr><td>F</td> <td>{{_('Flip Image')}}</td></tr>
|
||||||
</tbody>
|
</tbody>
|
||||||
</table>
|
</table>
|
||||||
</div>
|
</div>
|
||||||
<div class="settings-column">
|
<div class="settings-column">
|
||||||
<table id="settings">
|
<table id="settings">
|
||||||
<thead>
|
<thead>
|
||||||
<tr>
|
<tr>
|
||||||
<th>{{_('Settings')}}</th>
|
<th>{{_('Settings')}}</th>
|
||||||
</tr>
|
</tr>
|
||||||
</thead>
|
</thead>
|
||||||
<tbody>
|
<tbody>
|
||||||
<tr>
|
<tr>
|
||||||
<th>{{_('Theme')}}:</th>
|
<th>{{_('Theme')}}:</th>
|
||||||
<td>
|
<td>
|
||||||
<div class="inputs">
|
<div class="inputs">
|
||||||
<label for="lightTheme"><input type="radio" id="lightTheme" name="theme" value="light" /> {{_('Light')}}</label>
|
<label for="lightTheme"><input type="radio" id="lightTheme" name="theme" value="light" /> {{_('Light')}}</label>
|
||||||
<label for="darkTheme"><input type="radio" id="darkTheme" name="theme" value="dark" /> {{_('Dark')}}</label>
|
<label for="darkTheme"><input type="radio" id="darkTheme" name="theme" value="dark" /> {{_('Dark')}}</label>
|
||||||
</div>
|
</div>
|
||||||
@ -139,59 +123,118 @@
|
|||||||
<label for="fitWidth"><input type="radio" id="fitWidth" name="fitMode" value="87" /> {{_('Width')}}</label>
|
<label for="fitWidth"><input type="radio" id="fitWidth" name="fitMode" value="87" /> {{_('Width')}}</label>
|
||||||
<label for="fitHeight"><input type="radio" id="fitHeight" name="fitMode" value="72" /> {{_('Height')}}</label>
|
<label for="fitHeight"><input type="radio" id="fitHeight" name="fitMode" value="72" /> {{_('Height')}}</label>
|
||||||
<label for="fitNative"><input type="radio" id="fitNative" name="fitMode" value="78" /> {{_('Native')}}</label>
|
<label for="fitNative"><input type="radio" id="fitNative" name="fitMode" value="78" /> {{_('Native')}}</label>
|
||||||
</div>
|
</div>
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
<tr>
|
<tr>
|
||||||
<th>{{_('Rotate')}}:</th>
|
<th>{{_('Rotate')}}:</th>
|
||||||
<td>
|
<td>
|
||||||
<div class="inputs">
|
<div class="inputs">
|
||||||
<label for="r0"><input type="radio" id="r0" name="rotateTimes" value="0" /> 0°</label>
|
<label for="r0"><input type="radio" id="r0" name="rotateTimes" value="0" /> 0°</label>
|
||||||
<label for="r90"><input type="radio" id="r90" name="rotateTimes" value="1" /> 90°</label>
|
<label for="r90"><input type="radio" id="r90" name="rotateTimes" value="1" /> 90°</label>
|
||||||
<label for="r180"><input type="radio" id="r180" name="rotateTimes" value="2" /> 180°</label>
|
<label for="r180"><input type="radio" id="r180" name="rotateTimes" value="2" /> 180°</label>
|
||||||
<label for="r270"><input type="radio" id="r270" name="rotateTimes" value="3" /> 270°</label>
|
<label for="r270"><input type="radio" id="r270" name="rotateTimes" value="3" /> 270°</label>
|
||||||
</div>
|
</div>
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
<tr>
|
<tr>
|
||||||
<th>{{_('Flip')}}:</th>
|
<th>{{_('Flip')}}:</th>
|
||||||
<td>
|
<td>
|
||||||
<div class="inputs">
|
<div class="inputs">
|
||||||
<label for="vflip"><input type="checkbox" id="vflip" name="vflip" /> {{_('Horizontal')}}</label>
|
<label for="vflip"><input type="checkbox" id="vflip" name="vflip" /> {{_('Horizontal')}}</label>
|
||||||
<label for="hflip"><input type="checkbox" id="hflip" name="hflip" /> {{_('Vertical')}}</label>
|
<label for="hflip"><input type="checkbox" id="hflip" name="hflip" /> {{_('Vertical')}}</label>
|
||||||
</div>
|
</div>
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
<tr>
|
<tr>
|
||||||
<th>{{_('Direction')}}:</th>
|
<th>{{_('Direction')}}:</th>
|
||||||
<td>
|
<td>
|
||||||
<div class="inputs">
|
<div class="inputs">
|
||||||
<label for="leftToRight"><input type="radio" id="leftToRight" name="direction" value="0" /> {{_('Left to Right')}}</label>
|
<label for="leftToRight"><input type="radio" id="leftToRight" name="direction" value="0" /> {{_('Left to Right')}}</label>
|
||||||
<label for="rightToLeft"><input type="radio" id="rightToLeft" name="direction" value="1" /> {{_('Right to Left')}}</label>
|
<label for="rightToLeft"><input type="radio" id="rightToLeft" name="direction" value="1" /> {{_('Right to Left')}}</label>
|
||||||
</div>
|
</div>
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
<tr>
|
<tr>
|
||||||
|
<th>{{_('Next Page')}}:</th>
|
||||||
|
<td>
|
||||||
|
<div class="inputs">
|
||||||
|
<label for="resetToTop"><input type="radio" id="resetToTop" name="nextPage" value="0" /> {{_('Reset to Top')}}</label>
|
||||||
|
<label for="rememberPosition"><input type="radio" id="rememberPosition" name="nextPage" value="1" /> {{_('Remember Position')}}</label>
|
||||||
|
</div>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
<th>{{_('Scrollbar')}}:</th>
|
<th>{{_('Scrollbar')}}:</th>
|
||||||
<td>
|
<td>
|
||||||
<div class="inputs">
|
<div class="inputs">
|
||||||
<label for="showScrollbar"><input type="radio" id="showScrollbar" name="scrollbar" value="1" /> {{_('Show')}}</label>
|
<label for="showScrollbar"><input type="radio" id="showScrollbar" name="scrollbar" value="1" /> {{_('Show')}}</label>
|
||||||
<label for="hideScrollbar"><input type="radio" id="hideScrollbar" name="scrollbar" value="0" /> {{_('Hide')}}</label>
|
<label for="hideScrollbar"><input type="radio" id="hideScrollbar" name="scrollbar" value="0" /> {{_('Hide')}}</label>
|
||||||
</div>
|
</div>
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
</tbody>
|
</tbody>
|
||||||
</table>
|
</table>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
<div class="closer icon-cancel-circled"></div>
|
||||||
</div>
|
</div>
|
||||||
<div class="closer icon-cancel-circled"></div>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
<div class="overlay"></div>
|
||||||
<div class="overlay"></div>
|
<input type="hidden" name="csrf_token" value="{{ csrf_token() }}">
|
||||||
<script>
|
<script>
|
||||||
$('input[name="direction"]').change(function() {
|
var updateArrows = function () {
|
||||||
updateArrows();
|
if ($('input[name="direction"]:checked').val() === "0") {
|
||||||
});
|
$("#prev_page_key").html("←");
|
||||||
</script>
|
$("#next_page_key").html("→");
|
||||||
|
} else {
|
||||||
|
$("#prev_page_key").html("→");
|
||||||
|
$("#next_page_key").html("←");
|
||||||
|
}
|
||||||
|
};
|
||||||
|
</script>
|
||||||
|
<script>
|
||||||
|
function setBookmark() {
|
||||||
|
// get csrf_token
|
||||||
|
let csrf_token = $("input[name='csrf_token']").val();
|
||||||
|
//This sends a bookmark update to calibreweb.
|
||||||
|
$.ajax(calibre.bookmarkUrl, {
|
||||||
|
method: "post",
|
||||||
|
data: {
|
||||||
|
csrf_token: csrf_token,
|
||||||
|
bookmark: currentImage
|
||||||
|
}
|
||||||
|
}).fail(function (xhr, status, error) {
|
||||||
|
console.error(error);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
window.calibre = {
|
||||||
|
filePath: "{{ url_for('static', filename='js/libs/') }}",
|
||||||
|
cssPath: "{{ url_for('static', filename='css/') }}",
|
||||||
|
bookUrl: "{{ url_for('static', filename=comicfile) }}/",
|
||||||
|
bookmarkUrl: "{{ url_for('web.set_bookmark', book_id=comicfile, book_format=extension.upper()) }}",
|
||||||
|
bookmark: "{{ bookmark.bookmark_key if bookmark != None }}",
|
||||||
|
useBookmarks: "{{ current_user.is_authenticated | tojson }}"
|
||||||
|
};
|
||||||
|
if (calibre.useBookmarks) {
|
||||||
|
currentImage = eval(calibre.bookmark);
|
||||||
|
if (typeof currentImage !== 'number'){
|
||||||
|
currentImage = 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
document.onreadystatechange = function () {
|
||||||
|
if (document.readyState == "complete") {
|
||||||
|
init("{{ url_for('web.serve_book', book_id=comicfile, book_format=extension) }}");
|
||||||
|
updateArrows();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
<script>
|
||||||
|
$('input[name="direction"]').change(function() {
|
||||||
|
updateArrows();
|
||||||
|
});
|
||||||
|
</script>
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
|
@ -88,7 +88,7 @@ def process(tmp_file_path, original_file_name, original_file_extension, rar_exec
|
|||||||
log.warning('cannot parse metadata, using default: %s', ex)
|
log.warning('cannot parse metadata, using default: %s', ex)
|
||||||
|
|
||||||
if not meta.title.strip():
|
if not meta.title.strip():
|
||||||
meta = original_file_name
|
meta = meta._replace(title=original_file_name)
|
||||||
if not meta.author.strip() or meta.author.lower() == 'unknown':
|
if not meta.author.strip() or meta.author.lower() == 'unknown':
|
||||||
meta = meta._replace(author=_('Unknown'))
|
meta = meta._replace(author=_('Unknown'))
|
||||||
return meta
|
return meta
|
||||||
|
@ -1561,7 +1561,7 @@ def read_book(book_id, book_format):
|
|||||||
title = title + " #" + '{0:.2f}'.format(book.series_index).rstrip('0').rstrip('.')
|
title = title + " #" + '{0:.2f}'.format(book.series_index).rstrip('0').rstrip('.')
|
||||||
log.debug("Start comic reader for %d", book_id)
|
log.debug("Start comic reader for %d", book_id)
|
||||||
return render_title_template('readcbr.html', comicfile=all_name, title=title,
|
return render_title_template('readcbr.html', comicfile=all_name, title=title,
|
||||||
extension=fileExt)
|
extension=fileExt, bookmark=bookmark)
|
||||||
log.debug("Selected book is unavailable. File does not exist or is not accessible")
|
log.debug("Selected book is unavailable. File does not exist or is not accessible")
|
||||||
flash(_("Oops! Selected book is unavailable. File does not exist or is not accessible"),
|
flash(_("Oops! Selected book is unavailable. File does not exist or is not accessible"),
|
||||||
category="error")
|
category="error")
|
||||||
|
Loading…
Reference in New Issue
Block a user