1
0
mirror of https://github.com/janeczku/calibre-web synced 2024-11-27 20:10:06 +00:00

Merge branch 'master' into Develop

This commit is contained in:
Ozzie Isaacs 2021-10-16 11:37:57 +02:00
commit b4262b1317
128 changed files with 3026 additions and 1969 deletions

11
cps.py
View File

@ -17,18 +17,13 @@
# You should have received a copy of the GNU General Public License # You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>. # along with this program. If not, see <http://www.gnu.org/licenses/>.
from __future__ import absolute_import, division, print_function, unicode_literals
import sys import sys
import os import os
# Insert local directories into path # Insert local directories into path
if sys.version_info < (3, 0): sys.path.append(os.path.dirname(os.path.abspath(__file__)))
sys.path.append(os.path.dirname(os.path.abspath(__file__.decode('utf-8')))) sys.path.append(os.path.join(os.path.dirname(os.path.abspath(__file__)), 'vendor'))
sys.path.append(os.path.join(os.path.dirname(os.path.abspath(__file__.decode('utf-8'))), 'vendor'))
else:
sys.path.append(os.path.dirname(os.path.abspath(__file__)))
sys.path.append(os.path.join(os.path.dirname(os.path.abspath(__file__)), 'vendor'))
from cps import create_app from cps import create_app
@ -49,7 +44,7 @@ try:
from cps.kobo import kobo, get_kobo_activated from cps.kobo import kobo, get_kobo_activated
from cps.kobo_auth import kobo_auth from cps.kobo_auth import kobo_auth
kobo_available = get_kobo_activated() kobo_available = get_kobo_activated()
except ImportError: except (ImportError, AttributeError): # Catch also error for not installed flask-WTF (missing csrf decorator)
kobo_available = False kobo_available = False
try: try:

34
cps/MyLoginManager.py Normal file
View File

@ -0,0 +1,34 @@
# -*- coding: utf-8 -*-
# This file is part of the Calibre-Web (https://github.com/janeczku/calibre-web)
# Copyright (C) 2018-2019 OzzieIsaacs, cervinko, jkrehm, bodybybuddha, ok11,
# andy29485, idalin, Kyosfonica, wuqi, Kennyl, lemmsh,
# falgh1, grunjol, csitko, ytils, xybydy, trasba, vrabe,
# ruben-herold, marblepebble, JackED42, SiphonSquirrel,
# apetresc, nanu-c, mutschler, GammaC0de, vuolter
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
from flask_login import LoginManager
from flask import session
class MyLoginManager(LoginManager):
def _session_protection_failed(self):
sess = session._get_current_object()
ident = self._session_identifier_generator()
if(sess and not (len(sess) == 1 and sess.get('csrf_token', None))) and ident != sess.get('_id', None):
return super(). _session_protection_failed()
return False

View File

@ -20,7 +20,6 @@
# You should have received a copy of the GNU General Public License # You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>. # along with this program. If not, see <http://www.gnu.org/licenses/>.
from __future__ import division, print_function, unicode_literals
import sys import sys
import os import os
import mimetypes import mimetypes
@ -29,7 +28,7 @@ from babel import Locale as LC
from babel import negotiate_locale from babel import negotiate_locale
from babel.core import UnknownLocaleError from babel.core import UnknownLocaleError
from flask import Flask, request, g from flask import Flask, request, g
from flask_login import LoginManager from .MyLoginManager import MyLoginManager
from flask_babel import Babel from flask_babel import Babel
from flask_principal import Principal from flask_principal import Principal
@ -43,6 +42,12 @@ try:
except ImportError: except ImportError:
lxml_present = False lxml_present = False
try:
from flask_wtf.csrf import CSRFProtect
wtf_present = True
except ImportError:
wtf_present = False
mimetypes.init() mimetypes.init()
mimetypes.add_type('application/xhtml+xml', '.xhtml') mimetypes.add_type('application/xhtml+xml', '.xhtml')
mimetypes.add_type('application/epub+zip', '.epub') mimetypes.add_type('application/epub+zip', '.epub')
@ -70,11 +75,17 @@ app.config.update(
) )
lm = LoginManager() lm = MyLoginManager()
lm.login_view = 'web.login' lm.login_view = 'web.login'
lm.anonymous_user = ub.Anonymous lm.anonymous_user = ub.Anonymous
lm.session_protection = 'strong' lm.session_protection = 'strong'
if wtf_present:
csrf = CSRFProtect()
csrf.init_app(app)
else:
csrf = None
ub.init_db(cli.settingspath) ub.init_db(cli.settingspath)
# pylint: disable=no-member # pylint: disable=no-member
config = config_sql.load_configuration(ub.session) config = config_sql.load_configuration(ub.session)
@ -105,12 +116,12 @@ def create_app():
log.info('*** "lxml" is needed for calibre-web to run. Please install it using pip: "pip install lxml" ***') log.info('*** "lxml" is needed for calibre-web to run. Please install it using pip: "pip install lxml" ***')
print('*** "lxml" is needed for calibre-web to run. Please install it using pip: "pip install lxml" ***') print('*** "lxml" is needed for calibre-web to run. Please install it using pip: "pip install lxml" ***')
sys.exit(6) sys.exit(6)
if not wtf_present:
log.info('*** "flask-WTF" is needed for calibre-web to run. Please install it using pip: "pip install flask-WTF" ***')
print('*** "flask-WTF" is needed for calibre-web to run. Please install it using pip: "pip install flask-WTF" ***')
# sys.exit(7)
app.wsgi_app = ReverseProxied(app.wsgi_app) app.wsgi_app = ReverseProxied(app.wsgi_app)
# For python2 convert path to unicode
if sys.version_info < (3, 0):
app.static_folder = app.static_folder.decode('utf-8')
app.root_path = app.root_path.decode('utf-8')
app.instance_path = app.instance_path.decode('utf-8')
if os.environ.get('FLASK_DEBUG'): if os.environ.get('FLASK_DEBUG'):
cache_buster.init_cache_busting(app) cache_buster.init_cache_busting(app)

View File

@ -20,7 +20,6 @@
# You should have received a copy of the GNU General Public License # You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>. # along with this program. If not, see <http://www.gnu.org/licenses/>.
from __future__ import division, print_function, unicode_literals
import sys import sys
import platform import platform
import sqlite3 import sqlite3
@ -29,6 +28,10 @@ from collections import OrderedDict
import babel, pytz, requests, sqlalchemy import babel, pytz, requests, sqlalchemy
import werkzeug, flask, flask_login, flask_principal, jinja2 import werkzeug, flask, flask_login, flask_principal, jinja2
from flask_babel import gettext as _ from flask_babel import gettext as _
try:
from flask_wtf import __version__ as flaskwtf_version
except ImportError:
flaskwtf_version = _(u'not installed')
from . import db, calibre_db, converter, uploader, server, isoLanguages, constants from . import db, calibre_db, converter, uploader, server, isoLanguages, constants
from .render_template import render_title_template from .render_template import render_title_template
@ -75,6 +78,7 @@ _VERSIONS = OrderedDict(
Flask=flask.__version__, Flask=flask.__version__,
Flask_Login=flask_loginVersion, Flask_Login=flask_loginVersion,
Flask_Principal=flask_principal.__version__, Flask_Principal=flask_principal.__version__,
Flask_WTF=flaskwtf_version,
Werkzeug=werkzeug.__version__, Werkzeug=werkzeug.__version__,
Babel=babel.__version__, Babel=babel.__version__,
Jinja2=jinja2.__version__, Jinja2=jinja2.__version__,
@ -84,14 +88,14 @@ _VERSIONS = OrderedDict(
SQLite=sqlite3.sqlite_version, SQLite=sqlite3.sqlite_version,
iso639=isoLanguages.__version__, iso639=isoLanguages.__version__,
pytz=pytz.__version__, pytz=pytz.__version__,
Unidecode = unidecode_version, Unidecode=unidecode_version,
Scholarly = scholarly_version, Scholarly=scholarly_version,
Flask_SimpleLDAP = u'installed' if bool(services.ldap) else None, Flask_SimpleLDAP=u'installed' if bool(services.ldap) else None,
python_LDAP = services.ldapVersion if bool(services.ldapVersion) else None, python_LDAP=services.ldapVersion if bool(services.ldapVersion) else None,
Goodreads = u'installed' if bool(services.goodreads_support) else None, Goodreads=u'installed' if bool(services.goodreads_support) else None,
jsonschema = services.SyncToken.__version__ if bool(services.SyncToken) else None, jsonschema=services.SyncToken.__version__ if bool(services.SyncToken) else None,
flask_dance = flask_danceVersion, flask_dance=flask_danceVersion,
greenlet = greenlet_Version greenlet=greenlet_Version
) )
_VERSIONS.update(uploader.get_versions()) _VERSIONS.update(uploader.get_versions())

View File

@ -20,7 +20,6 @@
# You should have received a copy of the GNU General Public License # You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>. # along with this program. If not, see <http://www.gnu.org/licenses/>.
from __future__ import division, print_function, unicode_literals
import os import os
import re import re
import base64 import base64
@ -41,7 +40,7 @@ from sqlalchemy.exc import IntegrityError, OperationalError, InvalidRequestError
from sqlalchemy.sql.expression import func, or_, text from sqlalchemy.sql.expression import func, or_, text
from . import constants, logger, helper, services from . import constants, logger, helper, services
from . import db, calibre_db, ub, web_server, get_locale, config, updater_thread, babel, gdriveutils from . import db, calibre_db, ub, web_server, get_locale, config, updater_thread, babel, gdriveutils, kobo_sync_status
from .helper import check_valid_domain, send_test_mail, reset_password, generate_password_hash, check_email, \ from .helper import check_valid_domain, send_test_mail, reset_password, generate_password_hash, check_email, \
valid_email, check_username valid_email, check_username
from .gdriveutils import is_gdrive_ready, gdrive_support from .gdriveutils import is_gdrive_ready, gdrive_support
@ -146,7 +145,7 @@ def shutdown():
else: else:
showtext['text'] = _(u'Performing shutdown of server, please close window') showtext['text'] = _(u'Performing shutdown of server, please close window')
# stop gevent/tornado server # stop gevent/tornado server
web_server.stop(task==0) web_server.stop(task == 0)
return json.dumps(showtext) return json.dumps(showtext)
if task == 2: if task == 2:
@ -236,8 +235,12 @@ def view_configuration():
.filter(and_(db.Custom_Columns.datatype == 'bool', db.Custom_Columns.mark_for_delete == 0)).all() .filter(and_(db.Custom_Columns.datatype == 'bool', db.Custom_Columns.mark_for_delete == 0)).all()
restrict_columns = calibre_db.session.query(db.Custom_Columns)\ restrict_columns = calibre_db.session.query(db.Custom_Columns)\
.filter(and_(db.Custom_Columns.datatype == 'text', db.Custom_Columns.mark_for_delete == 0)).all() .filter(and_(db.Custom_Columns.datatype == 'text', db.Custom_Columns.mark_for_delete == 0)).all()
languages = calibre_db.speaking_language()
translations = [LC('en')] + babel.list_translations()
return render_title_template("config_view_edit.html", conf=config, readColumns=read_column, return render_title_template("config_view_edit.html", conf=config, readColumns=read_column,
restrictColumns=restrict_columns, restrictColumns=restrict_columns,
languages=languages,
translations=translations,
title=_(u"UI Configuration"), page="uiconfig") title=_(u"UI Configuration"), page="uiconfig")
@admi.route("/admin/usertable") @admi.route("/admin/usertable")
@ -515,16 +518,12 @@ def check_valid_restricted_column(column):
return True return True
@admi.route("/admin/viewconfig", methods=["POST"]) @admi.route("/admin/viewconfig", methods=["POST"])
@login_required @login_required
@admin_required @admin_required
def update_view_configuration(): def update_view_configuration():
to_save = request.form.to_dict() to_save = request.form.to_dict()
# _config_string = lambda x: config.set_from_dictionary(to_save, x, lambda y: y.strip() if y else y)
# _config_int = lambda x: config.set_from_dictionary(to_save, x, int)
_config_string(to_save, "config_calibre_web_title") _config_string(to_save, "config_calibre_web_title")
_config_string(to_save, "config_columns_to_ignore") _config_string(to_save, "config_columns_to_ignore")
if _config_string(to_save, "config_title_regex"): if _config_string(to_save, "config_title_regex"):
@ -546,6 +545,8 @@ def update_view_configuration():
_config_int(to_save, "config_random_books") _config_int(to_save, "config_random_books")
_config_int(to_save, "config_books_per_page") _config_int(to_save, "config_books_per_page")
_config_int(to_save, "config_authors_max") _config_int(to_save, "config_authors_max")
_config_string(to_save, "config_default_language")
_config_string(to_save, "config_default_locale")
config.config_default_role = constants.selected_roles(to_save) config.config_default_role = constants.selected_roles(to_save)
@ -1431,8 +1432,13 @@ def _handle_edit_user(to_save, content, languages, translations, kobo_support):
else: else:
content.sidebar_view &= ~constants.DETAIL_RANDOM content.sidebar_view &= ~constants.DETAIL_RANDOM
old_state = content.kobo_only_shelves_sync
content.kobo_only_shelves_sync = int(to_save.get("kobo_only_shelves_sync") == "on") or 0 content.kobo_only_shelves_sync = int(to_save.get("kobo_only_shelves_sync") == "on") or 0
# 1 -> 0: nothing has to be done
# 0 -> 1: all synced books have to be added to archived books, + currently synced shelfs
# which don't have to be synced have to be removed (added to Shelf archive)
if old_state == 0 and content.kobo_only_shelves_sync == 1:
kobo_sync_status.update_on_sync_shelfs(content.id)
if to_save.get("default_language"): if to_save.get("default_language"):
content.default_language = to_save["default_language"] content.default_language = to_save["default_language"]
if to_save.get("locale"): if to_save.get("locale"):
@ -1488,6 +1494,8 @@ def new_user():
else: else:
content.role = config.config_default_role content.role = config.config_default_role
content.sidebar_view = config.config_default_show content.sidebar_view = config.config_default_show
content.locale = config.config_default_locale
content.default_language = config.config_default_language
return render_title_template("user_edit.html", new_user=1, content=content, translations=translations, return render_title_template("user_edit.html", new_user=1, content=content, translations=translations,
languages=languages, title=_(u"Add new user"), page="newuser", languages=languages, title=_(u"Add new user"), page="newuser",
kobo_support=kobo_support, registered_oauth=oauth_check) kobo_support=kobo_support, registered_oauth=oauth_check)
@ -1740,6 +1748,8 @@ def ldap_import_create_user(user, user_data):
content.password = '' # dummy password which will be replaced by ldap one content.password = '' # dummy password which will be replaced by ldap one
content.email = useremail content.email = useremail
content.kindle_mail = kindlemail content.kindle_mail = kindlemail
content.default_language = config.config_default_language
content.locale = config.config_default_locale
content.role = config.config_default_role content.role = config.config_default_role
content.sidebar_view = config.config_default_show content.sidebar_view = config.config_default_show
content.allowed_tags = config.config_allowed_tags content.allowed_tags = config.config_allowed_tags

View File

@ -19,7 +19,6 @@
# Inspired by https://github.com/ChrisTM/Flask-CacheBust # Inspired by https://github.com/ChrisTM/Flask-CacheBust
# Uses query strings so CSS font files are found without having to resort to absolute URLs # Uses query strings so CSS font files are found without having to resort to absolute URLs
from __future__ import division, print_function, unicode_literals
import os import os
import hashlib import hashlib

View File

@ -16,7 +16,6 @@
# You should have received a copy of the GNU General Public License # You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>. # along with this program. If not, see <http://www.gnu.org/licenses/>.
from __future__ import division, print_function, unicode_literals
import sys import sys
import os import os
import argparse import argparse
@ -48,19 +47,6 @@ parser.add_argument('-s', metavar='user:pass', help='Sets specific username to n
parser.add_argument('-f', action='store_true', help='Flag is depreciated and will be removed in next version') parser.add_argument('-f', action='store_true', help='Flag is depreciated and will be removed in next version')
args = parser.parse_args() args = parser.parse_args()
if sys.version_info < (3, 0):
if args.p:
args.p = args.p.decode('utf-8')
if args.g:
args.g = args.g.decode('utf-8')
if args.k:
args.k = args.k.decode('utf-8')
if args.c:
args.c = args.c.decode('utf-8')
if args.s:
args.s = args.s.decode('utf-8')
settingspath = args.p or os.path.join(_CONFIG_DIR, "app.db") settingspath = args.p or os.path.join(_CONFIG_DIR, "app.db")
gdpath = args.g or os.path.join(_CONFIG_DIR, "gdrive.db") gdpath = args.g or os.path.join(_CONFIG_DIR, "gdrive.db")

View File

@ -16,7 +16,6 @@
# You should have received a copy of the GNU General Public License # You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>. # along with this program. If not, see <http://www.gnu.org/licenses/>.
from __future__ import division, print_function, unicode_literals
import os import os
from . import logger, isoLanguages from . import logger, isoLanguages

View File

@ -16,8 +16,6 @@
# You should have received a copy of the GNU General Public License # You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>. # along with this program. If not, see <http://www.gnu.org/licenses/>.
from __future__ import division, print_function, unicode_literals
import os import os
import sys import sys
import json import json
@ -91,6 +89,8 @@ class _Settings(_Base):
config_default_role = Column(SmallInteger, default=0) config_default_role = Column(SmallInteger, default=0)
config_default_show = Column(SmallInteger, default=constants.ADMIN_USER_SIDEBAR) config_default_show = Column(SmallInteger, default=constants.ADMIN_USER_SIDEBAR)
config_default_language = Column(String(3), default="all")
config_default_locale = Column(String(2), default="en")
config_columns_to_ignore = Column(String) config_columns_to_ignore = Column(String)
config_denied_tags = Column(String, default="") config_denied_tags = Column(String, default="")
@ -361,10 +361,6 @@ def _migrate_table(session, orm_class):
session.query(column).first() session.query(column).first()
except OperationalError as err: except OperationalError as err:
log.debug("%s: %s", column_name, err.args[0]) log.debug("%s: %s", column_name, err.args[0])
if column.default is not None:
if sys.version_info < (3, 0):
if isinstance(column.default.arg, unicode):
column.default.arg = column.default.arg.encode('utf-8')
if column.default is None: if column.default is None:
column_default = "" column_default = ""
else: else:

View File

@ -16,7 +16,6 @@
# You should have received a copy of the GNU General Public License # You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>. # along with this program. If not, see <http://www.gnu.org/licenses/>.
from __future__ import division, print_function, unicode_literals
import sys import sys
import os import os
from collections import namedtuple from collections import namedtuple
@ -31,12 +30,7 @@ HOME_CONFIG = os.path.isfile(os.path.join(os.path.dirname(os.path.abspath(__file
UPDATER_AVAILABLE = True UPDATER_AVAILABLE = True
# Base dir is parent of current file, necessary if called from different folder # Base dir is parent of current file, necessary if called from different folder
if sys.version_info < (3, 0): BASE_DIR = os.path.abspath(os.path.join(os.path.dirname(os.path.abspath(__file__)),os.pardir))
BASE_DIR = os.path.abspath(os.path.join(
os.path.dirname(os.path.abspath(__file__)),os.pardir)).decode('utf-8')
else:
BASE_DIR = os.path.abspath(os.path.join(
os.path.dirname(os.path.abspath(__file__)),os.pardir))
STATIC_DIR = os.path.join(BASE_DIR, 'cps', 'static') STATIC_DIR = os.path.join(BASE_DIR, 'cps', 'static')
TEMPLATES_DIR = os.path.join(BASE_DIR, 'cps', 'templates') TEMPLATES_DIR = os.path.join(BASE_DIR, 'cps', 'templates')
TRANSLATIONS_DIR = os.path.join(BASE_DIR, 'cps', 'translations') TRANSLATIONS_DIR = os.path.join(BASE_DIR, 'cps', 'translations')
@ -157,7 +151,7 @@ def selected_roles(dictionary):
BookMeta = namedtuple('BookMeta', 'file_path, extension, title, author, cover, description, tags, series, ' BookMeta = namedtuple('BookMeta', 'file_path, extension, title, author, cover, description, tags, series, '
'series_id, languages, publisher') 'series_id, languages, publisher')
STABLE_VERSION = {'version': '0.6.13 Beta'} STABLE_VERSION = {'version': '0.6.14 Beta'}
NIGHTLY_VERSION = {} NIGHTLY_VERSION = {}
NIGHTLY_VERSION[0] = '$Format:%H$' NIGHTLY_VERSION[0] = '$Format:%H$'

View File

@ -16,7 +16,6 @@
# You should have received a copy of the GNU General Public License # You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>. # along with this program. If not, see <http://www.gnu.org/licenses/>.
from __future__ import division, print_function, unicode_literals
import os import os
import re import re
from flask_babel import gettext as _ from flask_babel import gettext as _

View File

@ -17,7 +17,6 @@
# You should have received a copy of the GNU General Public License # You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>. # along with this program. If not, see <http://www.gnu.org/licenses/>.
from __future__ import division, print_function, unicode_literals
import sys import sys
import os import os
import re import re
@ -387,8 +386,6 @@ class Custom_Columns(Base):
def get_display_dict(self): def get_display_dict(self):
display_dict = ast.literal_eval(self.display) display_dict = ast.literal_eval(self.display)
if sys.version_info < (3, 0):
display_dict['enum_values'] = [x.decode('unicode_escape') for x in display_dict['enum_values']]
return display_dict return display_dict
@ -740,6 +737,7 @@ class CalibreDB():
self.session.connection().connection.connection.create_function("lower", 1, lcase) self.session.connection().connection.connection.create_function("lower", 1, lcase)
entries = self.session.query(database).filter(tag_filter). \ entries = self.session.query(database).filter(tag_filter). \
filter(func.lower(database.name).ilike("%" + query + "%")).all() filter(func.lower(database.name).ilike("%" + query + "%")).all()
# json_dumps = json.dumps([dict(name=escape(r.name.replace(*replace))) for r in entries])
json_dumps = json.dumps([dict(name=r.name.replace(*replace)) for r in entries]) json_dumps = json.dumps([dict(name=r.name.replace(*replace)) for r in entries])
return json_dumps return json_dumps
@ -805,11 +803,16 @@ class CalibreDB():
.filter(self.common_filters()) \ .filter(self.common_filters()) \
.group_by(text('books_languages_link.lang_code')).all() .group_by(text('books_languages_link.lang_code')).all()
for lang in languages: for lang in languages:
try: lang.name = isoLanguages.get_language_name(get_locale(), lang.lang_code)
cur_l = LC.parse(lang.lang_code) #try:
lang.name = cur_l.get_language_name(get_locale()) # if lang.lang_code.lower() == "und":
except UnknownLocaleError: # lang.name = isoLanguages.get_language_name(get_locale(), lang.lang_code)
lang.name = _(isoLanguages.get(part3=lang.lang_code).name) # # lang.name = _("Undetermined")
# else:
# cur_l = LC.parse(lang.lang_code)
# lang.name = cur_l.get_language_name(get_locale())
#except UnknownLocaleError:
# lang.name = _(isoLanguages.get(part3=lang.lang_code).name)
return languages return languages
def update_title_sort(self, config, conn=None): def update_title_sort(self, config, conn=None):

View File

@ -20,7 +20,6 @@
# You should have received a copy of the GNU General Public License # You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>. # along with this program. If not, see <http://www.gnu.org/licenses/>.
from __future__ import division, print_function, unicode_literals
import os import os
from datetime import datetime from datetime import datetime
import json import json
@ -40,14 +39,12 @@ try:
except ImportError: except ImportError:
have_scholar = False have_scholar = False
from babel import Locale as LC
from babel.core import UnknownLocaleError
from flask import Blueprint, request, flash, redirect, url_for, abort, Markup, Response from flask import Blueprint, request, flash, redirect, url_for, abort, Markup, Response
from flask_babel import gettext as _ from flask_babel import gettext as _
from flask_login import current_user, login_required from flask_login import current_user, login_required
from sqlalchemy.exc import OperationalError, IntegrityError from sqlalchemy.exc import OperationalError, IntegrityError
from sqlite3 import OperationalError as sqliteOperationalError from sqlite3 import OperationalError as sqliteOperationalError
from . import constants, logger, isoLanguages, gdriveutils, uploader, helper from . import constants, logger, isoLanguages, gdriveutils, uploader, helper, kobo_sync_status
from . import config, get_locale, ub, db from . import config, get_locale, ub, db
from . import calibre_db from . import calibre_db
from .services.worker import WorkerThread from .services.worker import WorkerThread
@ -826,6 +823,8 @@ def edit_book(book_id):
if modif_date: if modif_date:
book.last_modified = datetime.utcnow() book.last_modified = datetime.utcnow()
kobo_sync_status.remove_synced_book(edited_books_id)
calibre_db.session.merge(book) calibre_db.session.merge(book)
calibre_db.session.commit() calibre_db.session.commit()
if config.config_use_google_drive: if config.config_use_google_drive:
@ -1131,10 +1130,11 @@ def edit_list_book(param):
else: else:
lang_names = list() lang_names = list()
for lang in book.languages: for lang in book.languages:
try: lang_names.append(isoLanguages.get_language_name(get_locale(), lang.lang_code))
lang_names.append(LC.parse(lang.lang_code).get_language_name(get_locale())) #try:
except UnknownLocaleError: # lang_names.append(LC.parse(lang.lang_code).get_language_name(get_locale()))
lang_names.append(_(isoLanguages.get(part3=lang.lang_code).name)) #except UnknownLocaleError:
# lang_names.append(_(isoLanguages.get(part3=lang.lang_code).name))
ret = Response(json.dumps({'success': True, 'newValue': ', '.join(lang_names)}), ret = Response(json.dumps({'success': True, 'newValue': ', '.join(lang_names)}),
mimetype='application/json') mimetype='application/json')
elif param =='author_sort': elif param =='author_sort':

View File

@ -16,7 +16,6 @@
# You should have received a copy of the GNU General Public License # You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>. # along with this program. If not, see <http://www.gnu.org/licenses/>.
from __future__ import division, print_function, unicode_literals
import os import os
import zipfile import zipfile
from lxml import etree from lxml import etree

View File

@ -16,7 +16,6 @@
# You should have received a copy of the GNU General Public License # You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>. # along with this program. If not, see <http://www.gnu.org/licenses/>.
from __future__ import division, print_function, unicode_literals
from lxml import etree from lxml import etree
from .constants import BookMeta from .constants import BookMeta

View File

@ -20,9 +20,7 @@
# You should have received a copy of the GNU General Public License # You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>. # along with this program. If not, see <http://www.gnu.org/licenses/>.
from __future__ import division, print_function, unicode_literals
import os import os
import sys
import hashlib import hashlib
import json import json
import tempfile import tempfile
@ -34,7 +32,7 @@ from flask import Blueprint, flash, request, redirect, url_for, abort
from flask_babel import gettext as _ from flask_babel import gettext as _
from flask_login import login_required from flask_login import login_required
from . import logger, gdriveutils, config, ub, calibre_db from . import logger, gdriveutils, config, ub, calibre_db, csrf
from .admin import admin_required from .admin import admin_required
gdrive = Blueprint('gdrive', __name__, url_prefix='/gdrive') gdrive = Blueprint('gdrive', __name__, url_prefix='/gdrive')
@ -118,6 +116,7 @@ def revoke_watch_gdrive():
return redirect(url_for('admin.db_configuration')) return redirect(url_for('admin.db_configuration'))
@csrf.exempt
@gdrive.route("/watch/callback", methods=['GET', 'POST']) @gdrive.route("/watch/callback", methods=['GET', 'POST'])
def on_received_watch_confirmation(): def on_received_watch_confirmation():
if not config.config_google_drive_watch_changes_response: if not config.config_google_drive_watch_changes_response:
@ -137,10 +136,7 @@ def on_received_watch_confirmation():
response = gdriveutils.getChangeById(gdriveutils.Gdrive.Instance().drive, j['id']) response = gdriveutils.getChangeById(gdriveutils.Gdrive.Instance().drive, j['id'])
log.debug('%r', response) log.debug('%r', response)
if response: if response:
if sys.version_info < (3, 0): dbpath = os.path.join(config.config_calibre_dir, "metadata.db").encode()
dbpath = os.path.join(config.config_calibre_dir, "metadata.db")
else:
dbpath = os.path.join(config.config_calibre_dir, "metadata.db").encode()
if not response['deleted'] and response['file']['title'] == 'metadata.db' \ if not response['deleted'] and response['file']['title'] == 'metadata.db' \
and response['file']['md5Checksum'] != hashlib.md5(dbpath): # nosec and response['file']['md5Checksum'] != hashlib.md5(dbpath): # nosec
tmp_dir = os.path.join(tempfile.gettempdir(), 'calibre_web') tmp_dir = os.path.join(tempfile.gettempdir(), 'calibre_web')

View File

@ -16,7 +16,6 @@
# You should have received a copy of the GNU General Public License # You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>. # along with this program. If not, see <http://www.gnu.org/licenses/>.
from __future__ import division, print_function, unicode_literals
import os import os
import json import json
import shutil import shutil

View File

@ -17,7 +17,6 @@
# You should have received a copy of the GNU General Public License # You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>. # along with this program. If not, see <http://www.gnu.org/licenses/>.
from __future__ import division, print_function, unicode_literals
import sys import sys
import os import os
import io import io
@ -490,10 +489,7 @@ def reset_password(user_id):
def generate_random_password(): def generate_random_password():
s = "abcdefghijklmnopqrstuvwxyz01234567890ABCDEFGHIJKLMNOPQRSTUVWXYZ!@#$%&*()?" s = "abcdefghijklmnopqrstuvwxyz01234567890ABCDEFGHIJKLMNOPQRSTUVWXYZ!@#$%&*()?"
passlen = 8 passlen = 8
if sys.version_info < (3, 0): return "".join(s[c % len(s)] for c in os.urandom(passlen))
return "".join(s[ord(c) % len(s)] for c in os.urandom(passlen))
else:
return "".join(s[c % len(s)] for c in os.urandom(passlen))
def uniq(inpt): def uniq(inpt):
@ -707,8 +703,6 @@ def check_unrar(unrarLocation):
return _('Unrar binary file not found') return _('Unrar binary file not found')
try: try:
if sys.version_info < (3, 0):
unrarLocation = unrarLocation.encode(sys.getfilesystemencoding())
unrarLocation = [unrarLocation] unrarLocation = [unrarLocation]
value = process_wait(unrarLocation, pattern='UNRAR (.*) freeware') value = process_wait(unrarLocation, pattern='UNRAR (.*) freeware')
if value: if value:

View File

@ -16,8 +16,6 @@
# You should have received a copy of the GNU General Public License # You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>. # along with this program. If not, see <http://www.gnu.org/licenses/>.
from __future__ import division, print_function, unicode_literals
from .iso_language_names import LANGUAGE_NAMES as _LANGUAGE_NAMES from .iso_language_names import LANGUAGE_NAMES as _LANGUAGE_NAMES

View File

@ -6,8 +6,6 @@
# pylint: disable=too-many-lines,bad-continuation # pylint: disable=too-many-lines,bad-continuation
from __future__ import unicode_literals
# This file is autogenerated, do NOT add, change, or delete ANY string # This file is autogenerated, do NOT add, change, or delete ANY string
# If you need help or assistance for adding a new language, please contact the project team # If you need help or assistance for adding a new language, please contact the project team
@ -972,6 +970,8 @@ LANGUAGE_NAMES = {
"gor": "Gorontalo", "gor": "Gorontalo",
"got": "Γοτθικά", "got": "Γοτθικά",
"grb": "Grebo", "grb": "Grebo",
"grc": "Ελληνικά; Αρχαία (to 1453)",
"ell": "Ελληνικά; Μοντέρνα (1453-)",
"grn": "Guarani", "grn": "Guarani",
"guj": "Gujarati", "guj": "Gujarati",
"gwi": "Gwichʼin", "gwi": "Gwichʼin",
@ -7853,4 +7853,4 @@ LANGUAGE_NAMES = {
"zxx": "No linguistic content", "zxx": "No linguistic content",
"zza": "Zaza" "zza": "Zaza"
} }
} }

View File

@ -22,7 +22,6 @@
# custom jinja filters # custom jinja filters
from __future__ import division, print_function, unicode_literals
import datetime import datetime
import mimetypes import mimetypes
from uuid import uuid4 from uuid import uuid4

View File

@ -19,7 +19,6 @@
import base64 import base64
import datetime import datetime
import sys
import os import os
import uuid import uuid
from time import gmtime, strftime from time import gmtime, strftime
@ -47,7 +46,8 @@ from sqlalchemy.exc import StatementError
from sqlalchemy.sql import select from sqlalchemy.sql import select
import requests import requests
from . import config, logger, kobo_auth, db, calibre_db, helper, shelf as shelf_lib, ub
from . import config, logger, kobo_auth, db, calibre_db, helper, shelf as shelf_lib, ub, csrf, kobo_sync_status
from .constants import sqlalchemy_version2 from .constants import sqlalchemy_version2
from .helper import get_download_link from .helper import get_download_link
from .services import SyncToken as SyncToken from .services import SyncToken as SyncToken
@ -170,9 +170,12 @@ def HandleSyncRequest():
ub.ArchivedBook.is_archived) ub.ArchivedBook.is_archived)
changed_entries = (changed_entries changed_entries = (changed_entries
.join(db.Data).outerjoin(ub.ArchivedBook, db.Books.id == ub.ArchivedBook.book_id) .join(db.Data).outerjoin(ub.ArchivedBook, db.Books.id == ub.ArchivedBook.book_id)
.filter(or_(db.Books.last_modified > sync_token.books_last_modified, .join(ub.KoboSyncedBooks, ub.KoboSyncedBooks.book_id == db.Books.id, isouter=True)
ub.BookShelf.date_added > sync_token.books_last_modified)) .filter(or_(ub.KoboSyncedBooks.user_id != current_user.id,
.filter(db.Data.format.in_(KOBO_FORMATS)).filter(calibre_db.common_filters()) ub.KoboSyncedBooks.book_id == None))
.filter(ub.BookShelf.date_added > sync_token.books_last_modified)
.filter(db.Data.format.in_(KOBO_FORMATS))
.filter(calibre_db.common_filters(allow_show_archived=True))
.order_by(db.Books.id) .order_by(db.Books.id)
.order_by(ub.ArchivedBook.last_modified) .order_by(ub.ArchivedBook.last_modified)
.join(ub.BookShelf, db.Books.id == ub.BookShelf.book_id) .join(ub.BookShelf, db.Books.id == ub.BookShelf.book_id)
@ -189,16 +192,16 @@ def HandleSyncRequest():
ub.ArchivedBook.last_modified, ub.ArchivedBook.last_modified,
ub.ArchivedBook.is_archived) ub.ArchivedBook.is_archived)
changed_entries = (changed_entries changed_entries = (changed_entries
.join(db.Data).outerjoin(ub.ArchivedBook, db.Books.id == ub.ArchivedBook.book_id) .join(db.Data).outerjoin(ub.ArchivedBook, db.Books.id == ub.ArchivedBook.book_id)
.filter(db.Books.last_modified > sync_token.books_last_modified) .join(ub.KoboSyncedBooks, ub.KoboSyncedBooks.book_id == db.Books.id, isouter=True)
.filter(calibre_db.common_filters()) .filter(or_(ub.KoboSyncedBooks.user_id != current_user.id,
.filter(db.Data.format.in_(KOBO_FORMATS)) ub.KoboSyncedBooks.book_id == None))
.order_by(db.Books.last_modified) .filter(calibre_db.common_filters())
.order_by(db.Books.id) .filter(db.Data.format.in_(KOBO_FORMATS))
.order_by(db.Books.last_modified)
.order_by(db.Books.id)
) )
if sync_token.books_last_id > -1:
changed_entries = changed_entries.filter(db.Books.id > sync_token.books_last_id)
reading_states_in_new_entitlements = [] reading_states_in_new_entitlements = []
if sqlalchemy_version2: if sqlalchemy_version2:
@ -206,6 +209,7 @@ def HandleSyncRequest():
else: else:
books = changed_entries.limit(SYNC_ITEM_LIMIT) books = changed_entries.limit(SYNC_ITEM_LIMIT)
for book in books: for book in books:
kobo_sync_status.add_synced_books(book.Books.id)
formats = [data.format for data in book.Books.data] formats = [data.format for data in book.Books.data]
if not 'KEPUB' in formats and config.config_kepubifypath and 'EPUB' in formats: if not 'KEPUB' in formats and config.config_kepubifypath and 'EPUB' in formats:
helper.convert_book_format(book.Books.id, config.config_calibre_dir, 'EPUB', 'KEPUB', current_user.name) helper.convert_book_format(book.Books.id, config.config_calibre_dir, 'EPUB', 'KEPUB', current_user.name)
@ -263,11 +267,10 @@ def HandleSyncRequest():
entries = calibre_db.session.execute(changed_entries).all() entries = calibre_db.session.execute(changed_entries).all()
book_count = len(entries) book_count = len(entries)
else: else:
entries = changed_entries.all()
book_count = changed_entries.count() book_count = changed_entries.count()
# last entry: # last entry:
books_last_id = entries[-1].Books.id or -1 if book_count else -1 cont_sync = bool(book_count)
log.debug("Remaining books to Sync: {}".format(book_count))
# generate reading state data # generate reading state data
changed_reading_states = ub.session.query(ub.KoboReadingState) changed_reading_states = ub.session.query(ub.KoboReadingState)
@ -278,18 +281,18 @@ def HandleSyncRequest():
.filter(current_user.id == ub.Shelf.user_id)\ .filter(current_user.id == ub.Shelf.user_id)\
.filter(ub.Shelf.kobo_sync, .filter(ub.Shelf.kobo_sync,
or_( or_(
func.datetime(ub.KoboReadingState.last_modified) > sync_token.reading_state_last_modified, ub.KoboReadingState.last_modified > sync_token.reading_state_last_modified,
func.datetime(ub.BookShelf.date_added) > sync_token.books_last_modified func.datetime(ub.BookShelf.date_added) > sync_token.books_last_modified
)).distinct() )).distinct()
else: else:
changed_reading_states = changed_reading_states.filter( changed_reading_states = changed_reading_states.filter(
func.datetime(ub.KoboReadingState.last_modified) > sync_token.reading_state_last_modified) ub.KoboReadingState.last_modified > sync_token.reading_state_last_modified)
changed_reading_states = changed_reading_states.filter( changed_reading_states = changed_reading_states.filter(
and_(ub.KoboReadingState.user_id == current_user.id, and_(ub.KoboReadingState.user_id == current_user.id,
ub.KoboReadingState.book_id.notin_(reading_states_in_new_entitlements))) ub.KoboReadingState.book_id.notin_(reading_states_in_new_entitlements)))
cont_sync |= bool(changed_reading_states.count() > SYNC_ITEM_LIMIT)
for kobo_reading_state in changed_reading_states.all(): for kobo_reading_state in changed_reading_states.limit(SYNC_ITEM_LIMIT).all():
book = calibre_db.session.query(db.Books).filter(db.Books.id == kobo_reading_state.book_id).one_or_none() book = calibre_db.session.query(db.Books).filter(db.Books.id == kobo_reading_state.book_id).one_or_none()
if book: if book:
sync_results.append({ sync_results.append({
@ -305,9 +308,9 @@ def HandleSyncRequest():
sync_token.books_last_modified = new_books_last_modified sync_token.books_last_modified = new_books_last_modified
sync_token.archive_last_modified = new_archived_last_modified sync_token.archive_last_modified = new_archived_last_modified
sync_token.reading_state_last_modified = new_reading_state_last_modified sync_token.reading_state_last_modified = new_reading_state_last_modified
sync_token.books_last_id = books_last_id # sync_token.books_last_id = books_last_id
return generate_sync_response(sync_token, sync_results, book_count) return generate_sync_response(sync_token, sync_results, cont_sync)
def generate_sync_response(sync_token, sync_results, set_cont=False): def generate_sync_response(sync_token, sync_results, set_cont=False):
@ -330,7 +333,7 @@ def generate_sync_response(sync_token, sync_results, set_cont=False):
extra_headers["x-kobo-sync"] = "continue" extra_headers["x-kobo-sync"] = "continue"
sync_token.to_headers(extra_headers) sync_token.to_headers(extra_headers)
log.debug("Kobo Sync Content: {}".format(sync_results)) # log.debug("Kobo Sync Content: {}".format(sync_results))
response = make_response(jsonify(sync_results), extra_headers) response = make_response(jsonify(sync_results), extra_headers)
return response return response
@ -483,10 +486,7 @@ def get_metadata(book):
metadata.update(get_author(book)) metadata.update(get_author(book))
if get_series(book): if get_series(book):
if sys.version_info < (3, 0): name = get_series(book)
name = get_series(book).encode("utf-8")
else:
name = get_series(book)
metadata["Series"] = { metadata["Series"] = {
"Name": get_series(book), "Name": get_series(book),
"Number": get_seriesindex(book), # ToDo Check int() ? "Number": get_seriesindex(book), # ToDo Check int() ?
@ -497,7 +497,7 @@ def get_metadata(book):
return metadata return metadata
@csrf.exempt
@kobo.route("/v1/library/tags", methods=["POST", "DELETE"]) @kobo.route("/v1/library/tags", methods=["POST", "DELETE"])
@requires_kobo_auth @requires_kobo_auth
# Creates a Shelf with the given items, and returns the shelf's uuid. # Creates a Shelf with the given items, and returns the shelf's uuid.
@ -532,6 +532,7 @@ def HandleTagCreate():
return make_response(jsonify(str(shelf.uuid)), 201) return make_response(jsonify(str(shelf.uuid)), 201)
@csrf.exempt
@kobo.route("/v1/library/tags/<tag_id>", methods=["DELETE", "PUT"]) @kobo.route("/v1/library/tags/<tag_id>", methods=["DELETE", "PUT"])
@requires_kobo_auth @requires_kobo_auth
def HandleTagUpdate(tag_id): def HandleTagUpdate(tag_id):
@ -587,6 +588,7 @@ def add_items_to_shelf(items, shelf):
return items_unknown_to_calibre return items_unknown_to_calibre
@csrf.exempt
@kobo.route("/v1/library/tags/<tag_id>/items", methods=["POST"]) @kobo.route("/v1/library/tags/<tag_id>/items", methods=["POST"])
@requires_kobo_auth @requires_kobo_auth
def HandleTagAddItem(tag_id): def HandleTagAddItem(tag_id):
@ -616,6 +618,7 @@ def HandleTagAddItem(tag_id):
return make_response('', 201) return make_response('', 201)
@csrf.exempt
@kobo.route("/v1/library/tags/<tag_id>/items/delete", methods=["POST"]) @kobo.route("/v1/library/tags/<tag_id>/items/delete", methods=["POST"])
@requires_kobo_auth @requires_kobo_auth
def HandleTagRemoveItem(tag_id): def HandleTagRemoveItem(tag_id):
@ -678,6 +681,9 @@ def sync_shelves(sync_token, sync_results, only_kobo_shelves=False):
} }
} }
}) })
ub.session.delete(shelf)
ub.session_commit()
extra_filters = [] extra_filters = []
if only_kobo_shelves: if only_kobo_shelves:
@ -757,7 +763,7 @@ def create_kobo_tag(shelf):
) )
return {"Tag": tag} return {"Tag": tag}
@csrf.exempt
@kobo.route("/v1/library/<book_uuid>/state", methods=["GET", "PUT"]) @kobo.route("/v1/library/<book_uuid>/state", methods=["GET", "PUT"])
@requires_kobo_auth @requires_kobo_auth
def HandleStateRequest(book_uuid): def HandleStateRequest(book_uuid):
@ -932,6 +938,7 @@ def TopLevelEndpoint():
return make_response(jsonify({})) return make_response(jsonify({}))
@csrf.exempt
@kobo.route("/v1/library/<book_uuid>", methods=["DELETE"]) @kobo.route("/v1/library/<book_uuid>", methods=["DELETE"])
@requires_kobo_auth @requires_kobo_auth
def HandleBookDeletionRequest(book_uuid): def HandleBookDeletionRequest(book_uuid):
@ -958,6 +965,7 @@ def HandleBookDeletionRequest(book_uuid):
# TODO: Implement the following routes # TODO: Implement the following routes
@csrf.exempt
@kobo.route("/v1/library/<dummy>", methods=["DELETE", "GET"]) @kobo.route("/v1/library/<dummy>", methods=["DELETE", "GET"])
def HandleUnimplementedRequest(dummy=None): def HandleUnimplementedRequest(dummy=None):
log.debug("Unimplemented Library Request received: %s", request.base_url) log.debug("Unimplemented Library Request received: %s", request.base_url)
@ -965,6 +973,7 @@ def HandleUnimplementedRequest(dummy=None):
# TODO: Implement the following routes # TODO: Implement the following routes
@csrf.exempt
@kobo.route("/v1/user/loyalty/<dummy>", methods=["GET", "POST"]) @kobo.route("/v1/user/loyalty/<dummy>", methods=["GET", "POST"])
@kobo.route("/v1/user/profile", methods=["GET", "POST"]) @kobo.route("/v1/user/profile", methods=["GET", "POST"])
@kobo.route("/v1/user/wishlist", methods=["GET", "POST"]) @kobo.route("/v1/user/wishlist", methods=["GET", "POST"])
@ -975,6 +984,7 @@ def HandleUserRequest(dummy=None):
return redirect_or_proxy_request() return redirect_or_proxy_request()
@csrf.exempt
@kobo.route("/v1/products/<dummy>/prices", methods=["GET", "POST"]) @kobo.route("/v1/products/<dummy>/prices", methods=["GET", "POST"])
@kobo.route("/v1/products/<dummy>/recommendations", methods=["GET", "POST"]) @kobo.route("/v1/products/<dummy>/recommendations", methods=["GET", "POST"])
@kobo.route("/v1/products/<dummy>/nextread", methods=["GET", "POST"]) @kobo.route("/v1/products/<dummy>/nextread", methods=["GET", "POST"])
@ -1008,6 +1018,7 @@ def make_calibre_web_auth_response():
) )
@csrf.exempt
@kobo.route("/v1/auth/device", methods=["POST"]) @kobo.route("/v1/auth/device", methods=["POST"])
@requires_kobo_auth @requires_kobo_auth
def HandleAuthRequest(): def HandleAuthRequest():

71
cps/kobo_sync_status.py Normal file
View File

@ -0,0 +1,71 @@
# -*- coding: utf-8 -*-
# This file is part of the Calibre-Web (https://github.com/janeczku/calibre-web)
# Copyright (C) 2021 OzzieIsaacs
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
from flask_login import current_user
from . import ub
import datetime
from sqlalchemy.sql.expression import or_
def add_synced_books(book_id):
synced_book = ub.KoboSyncedBooks()
synced_book.user_id = current_user.id
synced_book.book_id = book_id
ub.session.add(synced_book)
ub.session_commit()
def remove_synced_book(book_id):
ub.session.query(ub.KoboSyncedBooks).filter(ub.KoboSyncedBooks.book_id == book_id).delete()
ub.session_commit()
def add_archived_books(book_id):
archived_book = (
ub.session.query(ub.ArchivedBook)
.filter(ub.ArchivedBook.book_id == book_id)
.first()
)
if not archived_book:
archived_book = ub.ArchivedBook(user_id=current_user.id, book_id=book_id)
archived_book.is_archived = True
archived_book.last_modified = datetime.datetime.utcnow()
ub.session.merge(archived_book)
ub.session_commit()
# select all books which are synced by the current user and do not belong to a synced shelf and them to archive
# select all shelfs from current user which are synced and do not belong to the "only sync" shelfs
def update_on_sync_shelfs(content_id):
books_to_archive = (ub.session.query(ub.KoboSyncedBooks)
.join(ub.BookShelf, ub.KoboSyncedBooks.book_id == ub.BookShelf.book_id, isouter=True)
.join(ub.Shelf, ub.Shelf.user_id == content_id, isouter=True)
.filter(or_(ub.Shelf.kobo_sync == 0, ub.Shelf.kobo_sync == None))
.filter(ub.KoboSyncedBooks.user_id == content_id).all())
for b in books_to_archive:
add_archived_books(b.book_id)
ub.session.query(ub.KoboSyncedBooks).filter(ub.KoboSyncedBooks.book_id == b.book_id).filter(ub.KoboSyncedBooks.user_id == content_id).delete()
ub.session_commit()
shelfs_to_archive = ub.session.query(ub.Shelf).filter(ub.Shelf.user_id == content_id).filter(
ub.Shelf.kobo_sync == 0).all()
for a in shelfs_to_archive:
ub.session.add(ub.ShelfArchive(uuid=a.uuid, user_id=content_id))
ub.session_commit()

View File

@ -16,7 +16,6 @@
# You should have received a copy of the GNU General Public License # You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>. # along with this program. If not, see <http://www.gnu.org/licenses/>.
from __future__ import division, print_function, unicode_literals
import os import os
import sys import sys
import inspect import inspect

View File

@ -16,7 +16,6 @@
# You should have received a copy of the GNU General Public License # You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/> # along with this program. If not, see <http://www.gnu.org/licenses/>
from __future__ import division, print_function, unicode_literals
from flask import session from flask import session
try: try:

View File

@ -20,7 +20,6 @@
# You should have received a copy of the GNU General Public License # You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/> # along with this program. If not, see <http://www.gnu.org/licenses/>
from __future__ import division, print_function, unicode_literals
import json import json
from functools import wraps from functools import wraps

View File

@ -20,7 +20,6 @@
# You should have received a copy of the GNU General Public License # You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>. # along with this program. If not, see <http://www.gnu.org/licenses/>.
from __future__ import division, print_function, unicode_literals
import sys import sys
import datetime import datetime
from functools import wraps from functools import wraps
@ -433,16 +432,17 @@ def feed_languagesindex():
if current_user.filter_language() == u"all": if current_user.filter_language() == u"all":
languages = calibre_db.speaking_language() languages = calibre_db.speaking_language()
else: else:
try: #try:
cur_l = LC.parse(current_user.filter_language()) # cur_l = LC.parse(current_user.filter_language())
except UnknownLocaleError: #except UnknownLocaleError:
cur_l = None # cur_l = None
languages = calibre_db.session.query(db.Languages).filter( languages = calibre_db.session.query(db.Languages).filter(
db.Languages.lang_code == current_user.filter_language()).all() db.Languages.lang_code == current_user.filter_language()).all()
if cur_l: languages[0].name = isoLanguages.get_language_name(get_locale(), languages[0].lang_code)
languages[0].name = cur_l.get_language_name(get_locale()) #if cur_l:
else: # languages[0].name = cur_l.get_language_name(get_locale())
languages[0].name = _(isoLanguages.get(part3=languages[0].lang_code).name) #else:
# languages[0].name = _(isoLanguages.get(part3=languages[0].lang_code).name)
pagination = Pagination((int(off) / (int(config.config_books_per_page)) + 1), config.config_books_per_page, pagination = Pagination((int(off) / (int(config.config_books_per_page)) + 1), config.config_books_per_page,
len(languages)) len(languages))
return render_xml_template('feed.xml', listelements=languages, folder='opds.feed_languages', pagination=pagination) return render_xml_template('feed.xml', listelements=languages, folder='opds.feed_languages', pagination=pagination)
@ -536,11 +536,10 @@ def feed_search(term):
def check_auth(username, password): def check_auth(username, password):
if sys.version_info.major == 3: try:
try: username = username.encode('windows-1252')
username = username.encode('windows-1252') except UnicodeEncodeError:
except UnicodeEncodeError: username = username.encode('utf-8')
username = username.encode('utf-8')
user = ub.session.query(ub.User).filter(func.lower(ub.User.name) == user = ub.session.query(ub.User).filter(func.lower(ub.User.name) ==
username.decode('utf-8').lower()).first() username.decode('utf-8').lower()).first()
if bool(user and check_password_hash(str(user.password), password)): if bool(user and check_password_hash(str(user.password), password)):

View File

@ -20,7 +20,6 @@
# You should have received a copy of the GNU General Public License # You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>. # along with this program. If not, see <http://www.gnu.org/licenses/>.
from __future__ import division, print_function, unicode_literals
from math import ceil from math import ceil

View File

@ -27,7 +27,6 @@
# http://flask.pocoo.org/snippets/62/ # http://flask.pocoo.org/snippets/62/
from __future__ import division, print_function, unicode_literals
try: try:
from urllib.parse import urlparse, urljoin from urllib.parse import urlparse, urljoin
except ImportError: except ImportError:

View File

@ -36,8 +36,6 @@
# #
# Inspired by http://flask.pocoo.org/snippets/35/ # Inspired by http://flask.pocoo.org/snippets/35/
from __future__ import division, print_function, unicode_literals
class ReverseProxied(object): class ReverseProxied(object):
"""Wrap the application in this middleware and configure the """Wrap the application in this middleware and configure the

View File

@ -16,7 +16,6 @@
# You should have received a copy of the GNU General Public License # You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>. # along with this program. If not, see <http://www.gnu.org/licenses/>.
from __future__ import division, print_function, unicode_literals
import os import os
import json import json
import importlib import importlib

View File

@ -16,7 +16,6 @@
# You should have received a copy of the GNU General Public License # You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>. # along with this program. If not, see <http://www.gnu.org/licenses/>.
from __future__ import division, print_function, unicode_literals
import sys import sys
import os import os
import errno import errno

View File

@ -35,10 +35,7 @@ log = logger.create()
def b64encode_json(json_data): def b64encode_json(json_data):
if sys.version_info < (3, 0): return b64encode(json.dumps(json_data).encode())
return b64encode(json.dumps(json_data))
else:
return b64encode(json.dumps(json_data).encode())
# Python3 has a timestamp() method we could be calling, however it's not avaiable in python2. # Python3 has a timestamp() method we could be calling, however it's not avaiable in python2.
@ -85,8 +82,8 @@ class SyncToken:
"books_last_created": {"type": "string"}, "books_last_created": {"type": "string"},
"archive_last_modified": {"type": "string"}, "archive_last_modified": {"type": "string"},
"reading_state_last_modified": {"type": "string"}, "reading_state_last_modified": {"type": "string"},
"tags_last_modified": {"type": "string"}, "tags_last_modified": {"type": "string"}
"books_last_id": {"type": "integer", "optional": True} # "books_last_id": {"type": "integer", "optional": True}
}, },
} }
@ -97,8 +94,8 @@ class SyncToken:
books_last_modified=datetime.min, books_last_modified=datetime.min,
archive_last_modified=datetime.min, archive_last_modified=datetime.min,
reading_state_last_modified=datetime.min, reading_state_last_modified=datetime.min,
tags_last_modified=datetime.min, tags_last_modified=datetime.min
books_last_id=-1 # books_last_id=-1
): # nosec ): # nosec
self.raw_kobo_store_token = raw_kobo_store_token self.raw_kobo_store_token = raw_kobo_store_token
self.books_last_created = books_last_created self.books_last_created = books_last_created
@ -106,7 +103,7 @@ class SyncToken:
self.archive_last_modified = archive_last_modified self.archive_last_modified = archive_last_modified
self.reading_state_last_modified = reading_state_last_modified self.reading_state_last_modified = reading_state_last_modified
self.tags_last_modified = tags_last_modified self.tags_last_modified = tags_last_modified
self.books_last_id = books_last_id # self.books_last_id = books_last_id
@staticmethod @staticmethod
def from_headers(headers): def from_headers(headers):
@ -141,12 +138,12 @@ class SyncToken:
archive_last_modified = get_datetime_from_json(data_json, "archive_last_modified") archive_last_modified = get_datetime_from_json(data_json, "archive_last_modified")
reading_state_last_modified = get_datetime_from_json(data_json, "reading_state_last_modified") reading_state_last_modified = get_datetime_from_json(data_json, "reading_state_last_modified")
tags_last_modified = get_datetime_from_json(data_json, "tags_last_modified") tags_last_modified = get_datetime_from_json(data_json, "tags_last_modified")
books_last_id = data_json["books_last_id"] # books_last_id = data_json["books_last_id"]
except TypeError: except TypeError:
log.error("SyncToken timestamps don't parse to a datetime.") log.error("SyncToken timestamps don't parse to a datetime.")
return SyncToken(raw_kobo_store_token=raw_kobo_store_token) return SyncToken(raw_kobo_store_token=raw_kobo_store_token)
except KeyError: #except KeyError:
books_last_id = -1 # books_last_id = -1
return SyncToken( return SyncToken(
raw_kobo_store_token=raw_kobo_store_token, raw_kobo_store_token=raw_kobo_store_token,
@ -155,7 +152,7 @@ class SyncToken:
archive_last_modified=archive_last_modified, archive_last_modified=archive_last_modified,
reading_state_last_modified=reading_state_last_modified, reading_state_last_modified=reading_state_last_modified,
tags_last_modified=tags_last_modified, tags_last_modified=tags_last_modified,
books_last_id=books_last_id #books_last_id=books_last_id
) )
def set_kobo_store_header(self, store_headers): def set_kobo_store_header(self, store_headers):
@ -179,16 +176,16 @@ class SyncToken:
"archive_last_modified": to_epoch_timestamp(self.archive_last_modified), "archive_last_modified": to_epoch_timestamp(self.archive_last_modified),
"reading_state_last_modified": to_epoch_timestamp(self.reading_state_last_modified), "reading_state_last_modified": to_epoch_timestamp(self.reading_state_last_modified),
"tags_last_modified": to_epoch_timestamp(self.tags_last_modified), "tags_last_modified": to_epoch_timestamp(self.tags_last_modified),
"books_last_id":self.books_last_id #"books_last_id":self.books_last_id
}, },
} }
return b64encode_json(token) return b64encode_json(token)
def __str__(self): def __str__(self):
return "{},{},{},{},{},{},{}".format(self.raw_kobo_store_token, return "{},{},{},{},{},{}".format(self.raw_kobo_store_token,
self.books_last_created, self.books_last_created,
self.books_last_modified, self.books_last_modified,
self.archive_last_modified, self.archive_last_modified,
self.reading_state_last_modified, self.reading_state_last_modified,
self.tags_last_modified, self.tags_last_modified)
self.books_last_id) #self.books_last_id)

View File

@ -16,8 +16,6 @@
# You should have received a copy of the GNU General Public License # You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>. # along with this program. If not, see <http://www.gnu.org/licenses/>.
from __future__ import division, print_function, unicode_literals
from .. import logger from .. import logger

View File

@ -1,4 +1,21 @@
from __future__ import print_function # -*- coding: utf-8 -*-
# This file is part of the Calibre-Web (https://github.com/janeczku/calibre-web)
# Copyright (C) 2021 OzzieIsaacs
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
import os.path import os.path
from google_auth_oauthlib.flow import InstalledAppFlow from google_auth_oauthlib.flow import InstalledAppFlow
from google.auth.transport.requests import Request from google.auth.transport.requests import Request

View File

@ -16,7 +16,6 @@
# You should have received a copy of the GNU General Public License # You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>. # along with this program. If not, see <http://www.gnu.org/licenses/>.
from __future__ import division, print_function, unicode_literals
import time import time
from functools import reduce from functools import reduce

View File

@ -16,7 +16,6 @@
# You should have received a copy of the GNU General Public License # You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>. # along with this program. If not, see <http://www.gnu.org/licenses/>.
from __future__ import division, print_function, unicode_literals
import base64 import base64
from flask_simpleldap import LDAP, LDAPException from flask_simpleldap import LDAP, LDAPException

View File

@ -1,5 +1,21 @@
# -*- coding: utf-8 -*-
# This file is part of the Calibre-Web (https://github.com/janeczku/calibre-web)
# Copyright (C) 2020 pwr
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
from __future__ import division, print_function, unicode_literals
import threading import threading
import abc import abc
import uuid import uuid

View File

@ -20,8 +20,6 @@
# You should have received a copy of the GNU General Public License # You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>. # along with this program. If not, see <http://www.gnu.org/licenses/>.
from __future__ import division, print_function, unicode_literals
import sys import sys
from datetime import datetime from datetime import datetime

View File

@ -35,7 +35,7 @@ body {
float: left; float: left;
width: 40px; width: 40px;
height: 40px; height: 40px;
background-image: url("img/toolbar-buttons.png"); background-image: url("../../js/libs/djvu_html5/img/toolbar-buttons.png");
background-repeat: no-repeat; background-repeat: no-repeat;
background-size: 500% 300%; background-size: 500% 300%;
} }
@ -185,10 +185,10 @@ _:-ms-lang(x), .textLayer {
width: 128px; width: 128px;
height: 128px; height: 128px;
margin: -72px 0 0 -64px; margin: -72px 0 0 -64px;
background-image: url("img/status.png"); background-image: url("../../js/libs/djvu_html5/img/status.png");
background-repeat: no-repeat; background-repeat: no-repeat;
} }
.blankImage { .blankImage {
background-image: url("img/blank.jpg"); background-image: url("../../js/libs/djvu_html5/img/blank.jpg");
} }

View File

@ -145,7 +145,7 @@ fieldset[disabled] .twitter-typeahead .tt-input {
cursor: not-allowed; cursor: not-allowed;
background-color: #eeeeee !important; background-color: #eeeeee !important;
} }
.tt-dropdown-menu { .tt-menu {
position: absolute; position: absolute;
top: 100%; top: 100%;
left: 0; left: 0;
@ -166,7 +166,7 @@ fieldset[disabled] .twitter-typeahead .tt-input {
*border-right-width: 2px; *border-right-width: 2px;
*border-bottom-width: 2px; *border-bottom-width: 2px;
} }
.tt-dropdown-menu .tt-suggestion { .tt-menu .tt-suggestion {
display: block; display: block;
padding: 3px 20px; padding: 3px 20px;
clear: both; clear: both;
@ -175,15 +175,15 @@ fieldset[disabled] .twitter-typeahead .tt-input {
color: #333333; color: #333333;
white-space: nowrap; white-space: nowrap;
} }
.tt-dropdown-menu .tt-suggestion.tt-cursor { .tt-menu .tt-suggestion.tt-cursor {
text-decoration: none; text-decoration: none;
outline: 0; outline: 0;
background-color: #f5f5f5; background-color: #f5f5f5;
color: #262626; color: #262626;
} }
.tt-dropdown-menu .tt-suggestion.tt-cursor a { .tt-menu .tt-suggestion.tt-cursor a {
color: #262626; color: #262626;
} }
.tt-dropdown-menu .tt-suggestion p { .tt-menu .tt-suggestion p {
margin: 0; margin: 0;
} }

44
cps/static/css/text.css Normal file
View File

@ -0,0 +1,44 @@
body {
background: white;
}
#readmain {
position: absolute;
width: 100%;
height: 100%;
}
#area {
width: 80%;
height: 80%;
margin: 5% auto;
max-width: 1250px;
}
#area iframe {
border: none;
}
xmp, pre, plaintext {
display: block;
font-family: -moz-fixed;
white-space: pre;
margin: 1em 0;
}
#area{
overflow:hidden;
}
pre {
white-space: pre-wrap;
word-wrap: break-word;
font-family: -moz-fixed;
column-count:2;
-webkit-columns:2;
-moz-columns:2;
column-gap:20px;
-moz-column-gap:20px;
-webkit-column-gap:20px;
position:relative;
}

View File

@ -150,11 +150,16 @@ if ($("body.book").length > 0) {
var splitText = $(this).text().split(':'); var splitText = $(this).text().split(':');
var label = splitText.shift().trim(); var label = splitText.shift().trim();
var value = splitText.join(':').trim(); var value = splitText.join(':').trim();
var class_value = ""
// Preserve Links // Preserve Links
if ($(this).find('a').length) { if ($(this).find('a').length) {
value = $(this).find('a').first().removeClass(); value = $(this).find('a').first().removeClass();
} }
$(this).html('<span>' + label + '</span><span></span>').find('span').last().append(value); // Preserve glyphicons
if ($(this).find('span').length) {
class_value = $(this).find('span').first().attr('class');
}
$(this).html('<span>' + label + '</span><span class="' + class_value + '"></span>').find('span').last().append(value);
}); });
$(".book-meta h2:first").clone() $(".book-meta h2:first").clone()

View File

@ -22,8 +22,10 @@ $(function() {
}); });
$("#have_read_cb").on("change", function() { $("#have_read_cb").on("change", function() {
$.post({ $.ajax({
url: this.closest("form").action, url: this.closest("form").action,
method:"post",
data: $(this).closest("form").serialize(),
error: function(response) { error: function(response) {
var data = [{type:"danger", message:response.responseText}] var data = [{type:"danger", message:response.responseText}]
$("#flash_success").remove(); $("#flash_success").remove();

View File

@ -23,7 +23,6 @@ if ($(".tiny_editor").length) {
$(".datepicker").datepicker({ $(".datepicker").datepicker({
format: "yyyy-mm-dd", format: "yyyy-mm-dd",
language: language
}).on("change", function () { }).on("change", function () {
// Show localized date over top of the standard YYYY-MM-DD date // Show localized date over top of the standard YYYY-MM-DD date
var pubDate; var pubDate;
@ -47,90 +46,20 @@ $(".datepicker_delete").click(function() {
Takes a prefix, query typeahead callback, Bloodhound typeahead adapter Takes a prefix, query typeahead callback, Bloodhound typeahead adapter
and returns the completions it gets from the bloodhound engine prefixed. and returns the completions it gets from the bloodhound engine prefixed.
*/ */
function prefixedSource(prefix, query, cb, bhAdapter) { function prefixedSource(prefix, query, cb, source) {
bhAdapter(query, function(retArray) { function async(retArray) {
retArray = retArray || [];
var matches = []; var matches = [];
for (var i = 0; i < retArray.length; i++) { for (var i = 0; i < retArray.length; i++) {
var obj = {name : prefix + retArray[i].name}; var obj = {name : prefix + retArray[i].name};
matches.push(obj); matches.push(obj);
} }
cb(matches); cb(matches);
}); }
source.search(query, cb, async);
} }
var authors = new Bloodhound({
name: "authors",
datumTokenizer: function datumTokenizer(datum) {
return [datum.name];
},
queryTokenizer: Bloodhound.tokenizers.whitespace,
remote: {
url: getPath() + "/get_authors_json?q=%QUERY"
}
});
var series = new Bloodhound({
name: "series",
datumTokenizer: function datumTokenizer(datum) {
return [datum.name];
},
queryTokenizer: function queryTokenizer(query) {
return [query];
},
remote: {
url: getPath() + "/get_series_json?q=",
replace: function replace(url, query) {
return url + encodeURIComponent(query);
}
}
});
var tags = new Bloodhound({
name: "tags",
datumTokenizer: function datumTokenizer(datum) {
return [datum.name];
},
queryTokenizer: function queryTokenizer(query) {
var tokens = query.split(",");
tokens = [tokens[tokens.length - 1].trim()];
return tokens;
},
remote: {
url: getPath() + "/get_tags_json?q=%QUERY"
}
});
var languages = new Bloodhound({
name: "languages",
datumTokenizer: function datumTokenizer(datum) {
return [datum.name];
},
queryTokenizer: function queryTokenizer(query) {
return [query];
},
remote: {
url: getPath() + "/get_languages_json?q=",
replace: function replace(url, query) {
return url + encodeURIComponent(query);
}
}
});
var publishers = new Bloodhound({
name: "publisher",
datumTokenizer: function datumTokenizer(datum) {
return [datum.name];
},
queryTokenizer: Bloodhound.tokenizers.whitespace,
remote: {
url: getPath() + "/get_publishers_json?q=%QUERY"
}
});
function sourceSplit(query, cb, split, source) { function sourceSplit(query, cb, split, source) {
var bhAdapter = source.ttAdapter();
var tokens = query.split(split); var tokens = query.split(split);
var currentSource = tokens[tokens.length - 1].trim(); var currentSource = tokens[tokens.length - 1].trim();
@ -145,84 +74,148 @@ function sourceSplit(query, cb, split, source) {
for (var i = 0; i < tokens.length; i++) { for (var i = 0; i < tokens.length; i++) {
prefix += tokens[i].trim() + newSplit; prefix += tokens[i].trim() + newSplit;
} }
prefixedSource(prefix, currentSource, cb, bhAdapter); prefixedSource(prefix, currentSource, cb, source);
} }
var promiseAuthors = authors.initialize(); var authors = new Bloodhound({
promiseAuthors.done(function() { name: "authors",
$("#bookAuthor").typeahead( identify: function(obj) { return obj.name; },
{ datumTokenizer: function datumTokenizer(datum) {
highlight: true, minLength: 1, return [datum.name];
hint: true },
}, { queryTokenizer: Bloodhound.tokenizers.whitespace,
name: "authors", remote: {
displayKey: "name", url: getPath() + "/get_authors_json?q=%QUERY",
source: function source(query, cb) { wildcard: '%QUERY',
return sourceSplit(query, cb, "&", authors); //sourceSplit //("&") },
}
}
);
}); });
var promiseSeries = series.initialize(); $(".form-group #bookAuthor").typeahead(
promiseSeries.done(function() { {
$("#series").typeahead( highlight: true,
{ minLength: 1,
highlight: true, minLength: 0, hint: true
hint: true }, {
}, { name: "authors",
name: "series", display: 'name',
displayKey: "name", source: function source(query, cb, asyncResults) {
source: series.ttAdapter() return sourceSplit(query, cb, "&", authors);
} }
); }
);
var series = new Bloodhound({
name: "series",
datumTokenizer: function datumTokenizer(datum) {
return [datum.name];
},
// queryTokenizer: Bloodhound.tokenizers.whitespace,
queryTokenizer: function queryTokenizer(query) {
return [query];
},
remote: {
url: getPath() + "/get_series_json?q=%QUERY",
wildcard: '%QUERY',
/*replace: function replace(url, query) {
return url + encodeURIComponent(query);
}*/
}
});
$(".form-group #series").typeahead(
{
highlight: true,
minLength: 0,
hint: true
}, {
name: "series",
displayKey: "name",
source: series
}
);
var tags = new Bloodhound({
name: "tags",
datumTokenizer: function datumTokenizer(datum) {
return [datum.name];
},
queryTokenizer: function queryTokenizer(query) {
var tokens = query.split(",");
tokens = [tokens[tokens.length - 1].trim()];
return tokens;
},
remote: {
url: getPath() + "/get_tags_json?q=%QUERY",
wildcard: '%QUERY'
}
}); });
var promiseTags = tags.initialize(); $(".form-group #tags").typeahead(
promiseTags.done(function() { {
$("#tags").typeahead( highlight: true,
{ minLength: 0,
highlight: true, minLength: 0, hint: true
hint: true }, {
}, { name: "tags",
name: "tags", display: "name",
displayKey: "name", source: function source(query, cb, asyncResults) {
source: function source(query, cb) { return sourceSplit(query, cb, ",", tags);
return sourceSplit(query, cb, ",", tags);
}
} }
); }
);
var languages = new Bloodhound({
name: "languages",
datumTokenizer: function datumTokenizer(datum) {
return [datum.name];
},
queryTokenizer: function queryTokenizer(query) {
return [query];
},
remote: {
url: getPath() + "/get_languages_json?q=%QUERY",
wildcard: '%QUERY'
/*replace: function replace(url, query) {
return url + encodeURIComponent(query);
}*/
}
}); });
var promiseLanguages = languages.initialize(); $(".form-group #languages").typeahead(
promiseLanguages.done(function() { {
$("#languages").typeahead( highlight: true, minLength: 0,
{ hint: true
highlight: true, minLength: 0, }, {
hint: true name: "languages",
}, { display: "name",
name: "languages", source: function source(query, cb, asyncResults) {
displayKey: "name", return sourceSplit(query, cb, ",", languages);
source: function source(query, cb) {
return sourceSplit(query, cb, ",", languages); //(",")
}
} }
); }
);
var publishers = new Bloodhound({
name: "publisher",
datumTokenizer: function datumTokenizer(datum) {
return [datum.name];
},
queryTokenizer: Bloodhound.tokenizers.whitespace,
remote: {
url: getPath() + "/get_publishers_json?q=%QUERY",
wildcard: '%QUERY'
}
}); });
var promisePublishers = publishers.initialize(); $(".form-group #publisher").typeahead(
promisePublishers.done(function() { {
$("#publisher").typeahead( highlight: true, minLength: 0,
{ hint: true
highlight: true, minLength: 0, }, {
hint: true name: "publishers",
}, { displayKey: "name",
name: "publishers", source: publishers
displayKey: "name", }
source: publishers.ttAdapter() );
}
);
});
$("#search").on("change input.typeahead:selected", function(event) { $("#search").on("change input.typeahead:selected", function(event) {
if (event.target.type === "search" && event.target.tagName === "INPUT") { if (event.target.type === "search" && event.target.tagName === "INPUT") {

File diff suppressed because it is too large Load Diff

View File

@ -15,7 +15,6 @@
* along with this program. If not, see <http://www.gnu.org/licenses/>. * along with this program. If not, see <http://www.gnu.org/licenses/>.
*/ */
function getPath() { function getPath() {
var jsFileLocation = $("script[src*=jquery]").attr("src"); // the js file path var jsFileLocation = $("script[src*=jquery]").attr("src"); // the js file path
return jsFileLocation.substr(0, jsFileLocation.search("/static/js/libs/jquery.min.js")); // the js folder path return jsFileLocation.substr(0, jsFileLocation.search("/static/js/libs/jquery.min.js")); // the js folder path
@ -113,6 +112,14 @@ $("#btn-upload").change(function() {
$("#form-upload").submit(); $("#form-upload").submit();
}); });
$("#form-upload").uploadprogress({
redirect_url: getPath() + "/", //"{{ url_for('web.index')}}",
uploadedMsg: $("#form-upload").data("message"), //"{{_('Upload done, processing, please wait...')}}",
modalTitle: $("#form-upload").data("title"), //"{{_('Uploading...')}}",
modalFooter: $("#form-upload").data("footer"), //"{{_('Close')}}",
modalTitleFailed: $("#form-upload").data("failed") //"{{_('Error')}}"
});
$(document).ready(function() { $(document).ready(function() {
var inp = $('#query').first() var inp = $('#query').first()
if (inp.length) { if (inp.length) {
@ -224,6 +231,16 @@ $(function() {
var preFilters = $.Callbacks(); var preFilters = $.Callbacks();
$.ajaxPrefilter(preFilters.fire); $.ajaxPrefilter(preFilters.fire);
// equip all post requests with csrf_token
var csrftoken = $("input[name='csrf_token']").val();
$.ajaxSetup({
beforeSend: function(xhr, settings) {
if (!/^(GET|HEAD|OPTIONS|TRACE)$/i.test(settings.type) && !this.crossDomain) {
xhr.setRequestHeader("X-CSRFToken", csrftoken)
}
}
});
function restartTimer() { function restartTimer() {
$("#spinner").addClass("hidden"); $("#spinner").addClass("hidden");
$("#RestartDialog").modal("hide"); $("#RestartDialog").modal("hide");
@ -246,7 +263,7 @@ $(function() {
function updateTimer() { function updateTimer() {
$.ajax({ $.ajax({
dataType: "json", dataType: "json",
url: window.location.pathname + "/../../get_updater_status", url: getPath() + "/get_updater_status",
success: function success(data) { success: function success(data) {
$("#DialogContent").html(updateText[data.status]); $("#DialogContent").html(updateText[data.status]);
if (data.status > 6) { if (data.status > 6) {
@ -445,8 +462,8 @@ $(function() {
$.ajax({ $.ajax({
type: "POST", type: "POST",
dataType: "json", dataType: "json",
data: { start: "True"}, data: { start: "True" },
url: window.location.pathname + "/../../get_updater_status", url: getPath() + "/get_updater_status",
success: function success(data) { success: function success(data) {
updateText = data.text; updateText = data.text;
$("#DialogContent").html(updateText[data.status]); $("#DialogContent").html(updateText[data.status]);
@ -577,7 +594,7 @@ $(function() {
method:"post", method:"post",
dataType: "json", dataType: "json",
url: window.location.pathname + "/../../ajax/simulatedbchange", url: window.location.pathname + "/../../ajax/simulatedbchange",
data: {config_calibre_dir: $("#config_calibre_dir").val()}, data: {config_calibre_dir: $("#config_calibre_dir").val(), csrf_token: $("input[name='csrf_token']").val()},
success: function success(data) { success: function success(data) {
if ( data.change ) { if ( data.change ) {
if ( data.valid ) { if ( data.valid ) {
@ -650,10 +667,10 @@ $(function() {
var folder = target.data("folderonly"); var folder = target.data("folderonly");
var filter = target.data("filefilter"); var filter = target.data("filefilter");
$("#element_selected").text(path); $("#element_selected").text(path);
$("#file_confirm")[0].attributes["data-link"].value = target.data("link"); $("#file_confirm").data("link", target.data("link"));
$("#file_confirm")[0].attributes["data-folderonly"].value = (typeof folder === 'undefined') ? false : true; $("#file_confirm").data("folderonly", (typeof folder === 'undefined') ? false : true);
$("#file_confirm")[0].attributes["data-filefilter"].value = (typeof filter === 'undefined') ? "" : filter; $("#file_confirm").data("filefilter", (typeof filter === 'undefined') ? "" : filter);
$("#file_confirm")[0].attributes["data-newfile"].value = target.data("newfile"); $("#file_confirm").data("newfile", target.data("newfile"));
fillFileTable(path,"dir", folder, filter); fillFileTable(path,"dir", folder, filter);
}); });
@ -667,7 +684,7 @@ $(function() {
var folder = $(file_confirm).data("folderonly"); var folder = $(file_confirm).data("folderonly");
var filter = $(file_confirm).data("filefilter"); var filter = $(file_confirm).data("filefilter");
var newfile = $(file_confirm).data("newfile"); var newfile = $(file_confirm).data("newfile");
if (newfile !== 'undefined') { if (newfile !== "") {
$("#element_selected").text(path + $("#new_file".text())); $("#element_selected").text(path + $("#new_file".text()));
} else { } else {
$("#element_selected").text(path); $("#element_selected").text(path);
@ -713,7 +730,7 @@ $(function() {
method:"post", method:"post",
contentType: "application/json; charset=utf-8", contentType: "application/json; charset=utf-8",
dataType: "json", dataType: "json",
url: window.location.pathname + "/../ajax/view", url: getPath() + "/ajax/view",
data: "{\"series\": {\"series_view\": \""+ view +"\"}}", data: "{\"series\": {\"series_view\": \""+ view +"\"}}",
success: function success() { success: function success() {
location.reload(); location.reload();

View File

@ -0,0 +1,21 @@
/* This file is part of the Calibre-Web (https://github.com/janeczku/calibre-web)
* Copyright (C) 2021 Ozzieisaacs
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
var DJVU_CONTEXT = {
background: "#666",
uiHideDelay: 1500,
};

View File

@ -0,0 +1,86 @@
/* This file is part of the Calibre-Web (https://github.com/janeczku/calibre-web)
* Copyright (C) 2021 Ozzieisaacs
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
$(document).ready(function() {
//to int
$("#area").width($("#area").width());
$("#content").width($("#content").width());
//bind text
$("#content").load($("#readmain").data('load'), function(textStr) {
$(this).height($(this).parent().height()*0.95);
$(this).text(textStr);
});
//keybind
$(document).keydown(function(event){
if(event.keyCode == 37){
prevPage();
}
if(event.keyCode == 39){
nextPage();
}
});
//click
$( "#left" ).click(function() {
prevPage();
});
$( "#right" ).click(function() {
nextPage();
});
$("#readmain").swipe( {
swipeRight:function() {
prevPage();
},
swipeLeft:function() {
nextPage();
},
});
//bind mouse
$(window).bind('DOMMouseScroll mousewheel', function(event) {
var delta = 0;
if (event.originalEvent.wheelDelta) {
delta = event.originalEvent.wheelDelta;
} else if (event.originalEvent.detail) {
delta = event.originalEvent.detail*-1;
}
if (delta >= 0) {
prevPage();
} else {
nextPage();
}
});
//page animate
var origwidth = $("#content")[0].getBoundingClientRect().width;
var gap = 20;
function prevPage() {
if($("#content").offset().left > 0) {
return;
}
leftoff = $("#content").offset().left;
leftoff = leftoff+origwidth+gap;
$("#content").offset({left:leftoff});
}
function nextPage() {
leftoff = $("#content").offset().left;
leftoff = leftoff-origwidth-gap;
if (leftoff + $("#content")[0].scrollWidth < 0) {
return;
}
$("#content").offset({left:leftoff});
}
});

View File

@ -0,0 +1,36 @@
/* This file is part of the Calibre-Web (https://github.com/janeczku/calibre-web)
* Copyright (C) 2017-2021 jkrehm, OzzieIsaacs
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
(function () {
// Poll the server to check if the user has authenticated
var t = setInterval(function () {
$.post(getPath() + "/ajax/verify_token", { token: $("#verify_url").data("token") })
.done(function(response) {
if (response.status === 'success') {
// Wait a tick so cookies are updated
setTimeout(function () {
window.location.href = getPath() + '/';
}, 0);
}
})
.fail(function (xhr) {
clearInterval(t);
var response = JSON.parse(xhr.responseText);
alert(response.message);
});
}, 5000);
})()

View File

@ -35,6 +35,7 @@ function sendData(path) {
var form = document.createElement("form"); var form = document.createElement("form");
form.setAttribute("method", "post"); form.setAttribute("method", "post");
form.setAttribute("action", path); form.setAttribute("action", path);
// form.setAttribute("csrf_token", );
for (counter = 0;counter < maxElements;counter++) { for (counter = 0;counter < maxElements;counter++) {
tmp[counter] = elements[counter].getAttribute("id"); tmp[counter] = elements[counter].getAttribute("id");
@ -44,6 +45,10 @@ function sendData(path) {
hiddenField.setAttribute("value", String(counter + 1)); hiddenField.setAttribute("value", String(counter + 1));
form.appendChild(hiddenField); form.appendChild(hiddenField);
} }
$("<input type='hidden'/>")
.attr("name", "csrf_token").val($("input[name='csrf_token']").val())
.appendTo(form);
document.body.appendChild(form); document.body.appendChild(form);
form.submit(); form.submit();
} }

View File

@ -16,7 +16,6 @@
# You should have received a copy of the GNU General Public License # You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>. # along with this program. If not, see <http://www.gnu.org/licenses/>.
from __future__ import division, print_function, unicode_literals
import sys import sys
import os import os
import subprocess import subprocess
@ -33,13 +32,8 @@ def process_open(command, quotes=(), env=None, sout=subprocess.PIPE, serr=subpro
if key in quotes: if key in quotes:
command[key] = '"' + element + '"' command[key] = '"' + element + '"'
exc_command = " ".join(command) exc_command = " ".join(command)
if sys.version_info < (3, 0):
exc_command = exc_command.encode(sys.getfilesystemencoding())
else: else:
if sys.version_info < (3, 0): exc_command = [x for x in command]
exc_command = [x.encode(sys.getfilesystemencoding()) for x in command]
else:
exc_command = [x for x in command]
return subprocess.Popen(exc_command, shell=False, stdout=sout, stderr=serr, universal_newlines=newlines, env=env) # nosec return subprocess.Popen(exc_command, shell=False, stdout=sout, stderr=serr, universal_newlines=newlines, env=env) # nosec

View File

@ -1,4 +1,21 @@
from __future__ import division, print_function, unicode_literals # -*- coding: utf-8 -*-
# This file is part of the Calibre-Web (https://github.com/janeczku/calibre-web)
# Copyright (C) 2020 pwr
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
import sys import sys
import os import os
import re import re
@ -161,8 +178,6 @@ class TaskConvert(CalibreTask):
while True: while True:
nextline = p.stdout.readlines() nextline = p.stdout.readlines()
nextline = [x.strip('\n') for x in nextline if x != '\n'] nextline = [x.strip('\n') for x in nextline if x != '\n']
if sys.version_info < (3, 0):
nextline = [x.decode('utf-8') for x in nextline]
for line in nextline: for line in nextline:
log.debug(line) log.debug(line)
if p.poll() is not None: if p.poll() is not None:
@ -207,10 +222,6 @@ class TaskConvert(CalibreTask):
while p.poll() is None: while p.poll() is None:
nextline = p.stdout.readline() nextline = p.stdout.readline()
if os.name == 'nt' and sys.version_info < (3, 0):
nextline = nextline.decode('windows-1252')
elif os.name == 'posix' and sys.version_info < (3, 0):
nextline = nextline.decode('utf-8')
log.debug(nextline.strip('\r\n')) log.debug(nextline.strip('\r\n'))
# parse progress string from calibre-converter # parse progress string from calibre-converter
progress = re.search(r"(\d+)%\s.*", nextline) progress = re.search(r"(\d+)%\s.*", nextline)
@ -224,8 +235,6 @@ class TaskConvert(CalibreTask):
calibre_traceback = p.stderr.readlines() calibre_traceback = p.stderr.readlines()
error_message = "" error_message = ""
for ele in calibre_traceback: for ele in calibre_traceback:
if sys.version_info < (3, 0):
ele = ele.decode('utf-8')
log.debug(ele.strip('\n')) log.debug(ele.strip('\n'))
if not ele.startswith('Traceback') and not ele.startswith(' File'): if not ele.startswith('Traceback') and not ele.startswith(' File'):
error_message = _("Calibre failed with error: %(error)s", error=ele.strip('\n')) error_message = _("Calibre failed with error: %(error)s", error=ele.strip('\n'))

View File

@ -1,11 +1,27 @@
from __future__ import division, print_function, unicode_literals # -*- coding: utf-8 -*-
# This file is part of the Calibre-Web (https://github.com/janeczku/calibre-web)
# Copyright (C) 2020 pwr
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
import sys import sys
import os import os
import smtplib import smtplib
import threading import threading
import socket import socket
import mimetypes import mimetypes
import base64
try: try:
from StringIO import StringIO from StringIO import StringIO
@ -162,17 +178,11 @@ class TaskEmail(CalibreTask):
log.debug_or_exception(ex) log.debug_or_exception(ex)
self._handleError(u'Error sending e-mail: {}'.format(ex)) self._handleError(u'Error sending e-mail: {}'.format(ex))
def send_standard_email(self, msg): def send_standard_email(self, msg):
use_ssl = int(self.settings.get('mail_use_ssl', 0)) use_ssl = int(self.settings.get('mail_use_ssl', 0))
timeout = 600 # set timeout to 5mins timeout = 600 # set timeout to 5mins
# redirect output to logfile on python2 on python3 debugoutput is caught with overwritten # on python3 debugoutput is caught with overwritten _print_debug function
# _print_debug function
if sys.version_info < (3, 0):
org_smtpstderr = smtplib.stderr
smtplib.stderr = logger.StderrLogger('worker.smtp')
log.debug("Start sending e-mail") log.debug("Start sending e-mail")
if use_ssl == 2: if use_ssl == 2:
self.asyncSMTP = EmailSSL(self.settings["mail_server"], self.settings["mail_port"], self.asyncSMTP = EmailSSL(self.settings["mail_server"], self.settings["mail_port"],
@ -198,9 +208,6 @@ class TaskEmail(CalibreTask):
self._handleSuccess() self._handleSuccess()
log.debug("E-mail send successfully") log.debug("E-mail send successfully")
if sys.version_info < (3, 0):
smtplib.stderr = org_smtpstderr
def send_gmail_email(self, message): def send_gmail_email(self, message):
return gmail.send_messsage(self.settings.get('mail_gmail_token', None), message) return gmail.send_messsage(self.settings.get('mail_gmail_token', None), message)
@ -218,7 +225,6 @@ class TaskEmail(CalibreTask):
self.asyncSMTP = None self.asyncSMTP = None
self._progress = x self._progress = x
@classmethod @classmethod
def _get_attachment(cls, bookpath, filename): def _get_attachment(cls, bookpath, filename):
"""Get file as MIMEBase message""" """Get file as MIMEBase message"""

View File

@ -1,4 +1,20 @@
from __future__ import division, print_function, unicode_literals # -*- coding: utf-8 -*-
# This file is part of the Calibre-Web (https://github.com/janeczku/calibre-web)
# Copyright (C) 2020 pwr
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
from datetime import datetime from datetime import datetime
from cps.services.worker import CalibreTask, STAT_FINISH_SUCCESS from cps.services.worker import CalibreTask, STAT_FINISH_SUCCESS

View File

@ -186,6 +186,7 @@
</table> </table>
{% if feature_support['updater'] %} {% if feature_support['updater'] %}
<input type="hidden" name="csrf_token" value="{{ csrf_token() }}">
<div class="hidden" id="update_error"> <span>{{update_error}}</span></div> <div class="hidden" id="update_error"> <span>{{update_error}}</span></div>
<div class="btn btn-primary" id="check_for_update">{{_('Check for Update')}}</div> <div class="btn btn-primary" id="check_for_update">{{_('Check for Update')}}</div>
<div class="btn btn-primary hidden" id="perform_update" data-toggle="modal" data-target="#StatusDialog">{{_('Perform Update')}}</div> <div class="btn btn-primary hidden" id="perform_update" data-toggle="modal" data-target="#StatusDialog">{{_('Perform Update')}}</div>

View File

@ -36,8 +36,8 @@
<div id="books" class="col-sm-3 col-lg-2 col-xs-6 book"> <div id="books" class="col-sm-3 col-lg-2 col-xs-6 book">
<div class="cover"> <div class="cover">
<a href="{{ url_for('web.show_book', book_id=entry.id) }}"> <a href="{{ url_for('web.show_book', book_id=entry.id) }}">
<span class="img"> <span class="img" title="{{entry.title|safe}}">
<img title="{{author.name|safe}}" src="{{ url_for('web.get_cover', book_id=entry.id) }}" /> <img src="{{ url_for('web.get_cover', book_id=entry.id) }}" />
{% if entry.id in read_book_ids %}<span class="badge read glyphicon glyphicon-ok"></span>{% endif %} {% if entry.id in read_book_ids %}<span class="badge read glyphicon glyphicon-ok"></span>{% endif %}
</span> </span>
</a> </a>

View File

@ -23,20 +23,21 @@
{% if source_formats|length > 0 and conversion_formats|length > 0 %} {% if source_formats|length > 0 and conversion_formats|length > 0 %}
<div class="text-center more-stuff"><h4>{{_('Convert book format:')}}</h4> <div class="text-center more-stuff"><h4>{{_('Convert book format:')}}</h4>
<form class="padded-bottom" action="{{ url_for('editbook.convert_bookformat', book_id=book.id) }}" method="post" id="book_convert_frm"> <form class="padded-bottom" action="{{ url_for('editbook.convert_bookformat', book_id=book.id) }}" method="post" id="book_convert_frm">
<input type="hidden" name="csrf_token" value="{{ csrf_token() }}">
<div class="form-group"> <div class="form-group">
<div class="text-left"> <div class="text-left">
<label class="control-label" for="book_format_from">{{_('Convert from:')}}</label> <label class="control-label" for="book_format_from">{{_('Convert from:')}}</label>
<select class="form-control" name="book_format_from" id="book_format_from"> <select class="form-control" name="book_format_from" id="book_format_from">
<option disabled selected value> -- {{_('select an option')}} -- </option> <option disabled selected value>-- {{_('select an option')}} --</option>
{% for format in source_formats %} {% for format in source_formats %}
<option>{{format|upper}} </option> <option>{{format|upper}}</option>
{% endfor %} {% endfor %}
</select> </select>
<label class="control-label" for="book_format_to">{{_('Convert to:')}}</label> <label class="control-label" for="book_format_to">{{_('Convert to:')}}</label>
<select class="form-control" name="book_format_to" id="book_format_to"> <select class="form-control" name="book_format_to" id="book_format_to">
<option disabled selected value> -- {{_('select an option')}} -- </option> <option disabled selected value>-- {{_('select an option')}} --</option>
{% for format in conversion_formats %} {% for format in conversion_formats %}
<option>{{format|upper}} </option> <option>{{format|upper}}</option>
{% endfor %} {% endfor %}
</select> </select>
</div> </div>
@ -48,6 +49,7 @@
</div> </div>
<form role="form" action="{{ url_for('editbook.edit_book', book_id=book.id) }}" method="post" enctype="multipart/form-data" id="book_edit_frm"> <form role="form" action="{{ url_for('editbook.edit_book', book_id=book.id) }}" method="post" enctype="multipart/form-data" id="book_edit_frm">
<input type="hidden" name="csrf_token" value="{{ csrf_token() }}">
<div class="col-sm-9 col-xs-12"> <div class="col-sm-9 col-xs-12">
<div class="form-group"> <div class="form-group">
<label for="book_title">{{_('Book Title')}}</label> <label for="book_title">{{_('Book Title')}}</label>
@ -56,11 +58,11 @@
<div class="text-center"> <div class="text-center">
<button type="button" class="btn btn-default" id="xchange" ><span class="glyphicon glyphicon-arrow-up"></span><span class="glyphicon glyphicon-arrow-down"></span></button> <button type="button" class="btn btn-default" id="xchange" ><span class="glyphicon glyphicon-arrow-up"></span><span class="glyphicon glyphicon-arrow-down"></span></button>
</div> </div>
<div id="author_div" class="form-group">
<div class="form-group">
<label for="bookAuthor">{{_('Author')}}</label> <label for="bookAuthor">{{_('Author')}}</label>
<input type="text" class="form-control typeahead" name="author_name" id="bookAuthor" value="{{' & '.join(authors)}}" autocomplete="off"> <input type="text" class="form-control typeahead" name="author_name" id="bookAuthor" value="{{' & '.join(authors)}}" autocomplete="off">
</div> </div>
<div class="form-group"> <div class="form-group">
<label for="description">{{_('Description')}}</label> <label for="description">{{_('Description')}}</label>
<textarea class="form-control" name="description" id="description" rows="7">{% if book.comments %}{{book.comments[0].text}}{%endif%}</textarea> <textarea class="form-control" name="description" id="description" rows="7">{% if book.comments %}{{book.comments[0].text}}{%endif%}</textarea>

View File

@ -20,6 +20,7 @@
{% endblock %} {% endblock %}
{% block body %} {% block body %}
<h2 class="{{page}}">{{_(title)}}</h2> <h2 class="{{page}}">{{_(title)}}</h2>
<input type="hidden" name="csrf_token" value="{{ csrf_token() }}">
<div class="col-xs-12 col-sm-6"> <div class="col-xs-12 col-sm-6">
<div class="row form-group"> <div class="row form-group">
<div class="btn btn-default disabled" id="merge_books" aria-disabled="true">{{_('Merge selected books')}}</div> <div class="btn btn-default disabled" id="merge_books" aria-disabled="true">{{_('Merge selected books')}}</div>

View File

@ -8,6 +8,7 @@
<div class="discover"> <div class="discover">
<h2>{{title}}</h2> <h2>{{title}}</h2>
<form role="form" method="POST" class="col-md-10 col-lg-6" action="{{ url_for('admin.db_configuration') }}" autocomplete="off"> <form role="form" method="POST" class="col-md-10 col-lg-6" action="{{ url_for('admin.db_configuration') }}" autocomplete="off">
<input type="hidden" name="csrf_token" value="{{ csrf_token() }}">
<label for="config_calibre_dir">{{_('Location of Calibre Database')}}</label> <label for="config_calibre_dir">{{_('Location of Calibre Database')}}</label>
<div class="form-group required input-group"> <div class="form-group required input-group">
<input type="text" class="form-control" id="config_calibre_dir" name="config_calibre_dir" value="{% if config.config_calibre_dir != None %}{{ config.config_calibre_dir }}{% endif %}" autocomplete="off"> <input type="text" class="form-control" id="config_calibre_dir" name="config_calibre_dir" value="{% if config.config_calibre_dir != None %}{{ config.config_calibre_dir }}{% endif %}" autocomplete="off">

View File

@ -8,6 +8,7 @@
<div class="discover"> <div class="discover">
<h2>{{title}}</h2> <h2>{{title}}</h2>
<form role="form" method="POST" autocomplete="off"> <form role="form" method="POST" autocomplete="off">
<input type="hidden" name="csrf_token" value="{{ csrf_token() }}">
<div class="panel-group col-md-10 col-lg-8"> <div class="panel-group col-md-10 col-lg-8">
<div class="panel panel-default"> <div class="panel panel-default">
<div class="panel-heading"> <div class="panel-heading">

View File

@ -6,8 +6,9 @@
{% block body %} {% block body %}
<div class="discover"> <div class="discover">
<h2>{{title}}</h2> <h2>{{title}}</h2>
<form role="form" method="POST" autocomplete="off" > <form role="form" method="POST" autocomplete="off" >
<div class="panel-group class="col-md-10 col-lg-6"> <input type="hidden" name="csrf_token" value="{{ csrf_token() }}">
<div class="panel-group" class="col-md-10 col-lg-6">
<div class="panel panel-default"> <div class="panel panel-default">
<div class="panel-heading"> <div class="panel-heading">
<h4 class="panel-title"> <h4 class="panel-title">
@ -114,6 +115,23 @@
<input type="checkbox" name="edit_shelf_role" id="edit_shelf_role" {% if conf.role_edit_shelfs() %}checked{% endif %}> <input type="checkbox" name="edit_shelf_role" id="edit_shelf_role" {% if conf.role_edit_shelfs() %}checked{% endif %}>
<label for="edit_shelf_role">{{_('Allow Editing Public Shelves')}}</label> <label for="edit_shelf_role">{{_('Allow Editing Public Shelves')}}</label>
</div> </div>
<div class="form-group">
<label for="config_default_locale">{{_('Default Language')}}</label>
<select name="config_default_locale" id="config_default_locale" class="form-control">
{% for translation in translations %}
<option value="{{translation}}" {% if translation|string == conf.config_default_locale %}selected{% endif %}>{{ translation.display_name|capitalize }}</option>
{% endfor %}
</select>
</div>
<div class="form-group">
<label for="config_default_language">{{_('Default Visible Language of Books')}}</label>
<select name="config_default_language" id="config_default_language" class="form-control">
<option value="all" {% if conf.config_default_language == "all" %}selected{% endif %}>{{ _('Show All') }}</option>
{% for language in languages %}
<option value="{{ language.lang_code }}" {% if conf.config_default_language == language.lang_code %}selected{% endif %}>{{ language.name }}</option>
{% endfor %}
</select>
</div>
</div> </div>
</div> </div>
</div> </div>

View File

@ -214,6 +214,7 @@
<div class="custom_columns"> <div class="custom_columns">
<p> <p>
<form id="have_read_form" action="{{ url_for('web.toggle_read', book_id=entry.id)}}" method="POST"> <form id="have_read_form" action="{{ url_for('web.toggle_read', book_id=entry.id)}}" method="POST">
<input type="hidden" name="csrf_token" value="{{ csrf_token() }}">
<label class="block-label"> <label class="block-label">
<input id="have_read_cb" data-checked="{{_('Mark As Unread')}}" data-unchecked="{{_('Mark As Read')}}" type="checkbox" {% if have_read %}checked{% endif %} > <input id="have_read_cb" data-checked="{{_('Mark As Unread')}}" data-unchecked="{{_('Mark As Read')}}" type="checkbox" {% if have_read %}checked{% endif %} >
<span>{{_('Read')}}</span> <span>{{_('Read')}}</span>
@ -223,6 +224,7 @@
{% if g.user.check_visibility(32768) %} {% if g.user.check_visibility(32768) %}
<p> <p>
<form id="archived_form" action="{{ url_for('web.toggle_archived', book_id=entry.id)}}" method="POST"> <form id="archived_form" action="{{ url_for('web.toggle_archived', book_id=entry.id)}}" method="POST">
<input type="hidden" name="csrf_token" value="{{ csrf_token() }}">
<label class="block-label"> <label class="block-label">
<input id="archived_cb" data-checked="{{_('Restore from archive')}}" data-unchecked="{{_('Add to archive')}}" type="checkbox" {% if is_archived %}checked{% endif %} > <input id="archived_cb" data-checked="{{_('Restore from archive')}}" data-unchecked="{{_('Add to archive')}}" type="checkbox" {% if is_archived %}checked{% endif %} >
<span>{{_('Archived')}}</span> <span>{{_('Archived')}}</span>

View File

@ -8,8 +8,8 @@
<div class="cover"> <div class="cover">
{% if entry.has_cover is defined %} {% if entry.has_cover is defined %}
<a href="{{ url_for('web.show_book', book_id=entry.id) }}" data-toggle="modal" data-target="#bookDetailsModal" data-remote="false"> <a href="{{ url_for('web.show_book', book_id=entry.id) }}" data-toggle="modal" data-target="#bookDetailsModal" data-remote="false">
<span class="img"> <span class="img" title="{{entry.title}}">
<img title="{{entry.title}}" src="{{ url_for('web.get_cover', book_id=entry.id) }}" alt="{{ entry.title }}" /> <img src="{{ url_for('web.get_cover', book_id=entry.id) }}" alt="{{ entry.title }}" />
{% if entry.id in read_book_ids %}<span class="badge read glyphicon glyphicon-ok"></span>{% endif %} {% if entry.id in read_book_ids %}<span class="badge read glyphicon glyphicon-ok"></span>{% endif %}
</span> </span>
</a> </a>

View File

@ -7,6 +7,7 @@
<div class="discover"> <div class="discover">
<h1>{{title}}</h1> <h1>{{title}}</h1>
<form role="form" class="col-md-10 col-lg-6" method="POST"> <form role="form" class="col-md-10 col-lg-6" method="POST">
<input type="hidden" name="csrf_token" value="{{ csrf_token() }}">
{% if feature_support['gmail'] %} {% if feature_support['gmail'] %}
<div class="form-group"> <div class="form-group">
<label for="config_email_type">{{_('Choose Server Type')}}</label> <label for="config_email_type">{{_('Choose Server Type')}}</label>
@ -72,6 +73,7 @@
<div class="col-md-10 col-lg-6"> <div class="col-md-10 col-lg-6">
<h2>{{_('Allowed Domains (Whitelist)')}}</h2> <h2>{{_('Allowed Domains (Whitelist)')}}</h2>
<form id="domain_add_allow" action="{{ url_for('admin.add_domain',allow=1)}}" method="POST"> <form id="domain_add_allow" action="{{ url_for('admin.add_domain',allow=1)}}" method="POST">
<input type="hidden" name="csrf_token" value="{{ csrf_token() }}">
<div class="form-group required"> <div class="form-group required">
<label for="domainname_allow">{{_('Add Domain')}}</label> <label for="domainname_allow">{{_('Add Domain')}}</label>
<input type="text" class="form-control" name="domainname" id="domainname_allow" > <input type="text" class="form-control" name="domainname" id="domainname_allow" >
@ -98,11 +100,12 @@
</thead> </thead>
</table> </table>
<form id="domain_add_deny" action="{{ url_for('admin.add_domain',allow=0)}}" method="POST"> <form id="domain_add_deny" action="{{ url_for('admin.add_domain',allow=0)}}" method="POST">
<div class="form-group required"> <input type="hidden" name="csrf_token" value="{{ csrf_token() }}">
<label for="domainname_deny">{{_('Add Domain')}}</label> <div class="form-group required">
<input type="text" class="form-control" name="domainname" id="domainname_deny" > <label for="domainname_deny">{{_('Add Domain')}}</label>
</div> <input type="text" class="form-control" name="domainname" id="domainname_deny" >
<button id="domain_deny_submit" class="btn btn-default">{{_('Add')}}</button> </div>
<button id="domain_deny_submit" class="btn btn-default">{{_('Add')}}</button>
</form> </form>
</div> </div>

View File

@ -27,8 +27,8 @@
<div class="col-sm-3 col-lg-2 col-xs-6 book sortable" {% if entry[0].sort %}data-name="{{entry[0].series[0].name}}"{% endif %} data-id="{% if entry[0].series[0].name %}{{entry[0].series[0].name}}{% endif %}"> <div class="col-sm-3 col-lg-2 col-xs-6 book sortable" {% if entry[0].sort %}data-name="{{entry[0].series[0].name}}"{% endif %} data-id="{% if entry[0].series[0].name %}{{entry[0].series[0].name}}{% endif %}">
<div class="cover"> <div class="cover">
<a href="{{url_for('web.books_list', data=data, sort_param='stored', book_id=entry[0].series[0].id )}}"> <a href="{{url_for('web.books_list', data=data, sort_param='stored', book_id=entry[0].series[0].id )}}">
<span class="img"> <span class="img" title="{{entry[0].series[0].name}}">
<img title="{{entry.title}}" src="{{ url_for('web.get_cover', book_id=entry[0].id) }}" alt="{{ entry[0].name }}"/> <img src="{{ url_for('web.get_cover', book_id=entry[0].id) }}" alt="{{ entry[0].name }}"/>
<span class="badge">{{entry.count}}</span> <span class="badge">{{entry.count}}</span>
</span> </span>
</a> </a>

View File

@ -1,5 +1,5 @@
<!DOCTYPE html> <!DOCTYPE html>
<html class="http-error" lang="{{ g.user.locale }}"> <html class="http-error">
<head> <head>
<title>{{ instance }} | HTTP Error ({{ error_code }})</title> <title>{{ instance }} | HTTP Error ({{ error_code }})</title>
<meta charset="utf-8"> <meta charset="utf-8">

View File

@ -8,8 +8,8 @@
<div class="col-sm-3 col-lg-2 col-xs-6 book" id="books_rand"> <div class="col-sm-3 col-lg-2 col-xs-6 book" id="books_rand">
<div class="cover"> <div class="cover">
<a href="{{ url_for('web.show_book', book_id=entry.id) }}" data-toggle="modal" data-target="#bookDetailsModal" data-remote="false"> <a href="{{ url_for('web.show_book', book_id=entry.id) }}" data-toggle="modal" data-target="#bookDetailsModal" data-remote="false">
<span class="img"> <span class="img" title="{{ entry.title }}">
<img title="{{ entry.title }}" src="{{ url_for('web.get_cover', book_id=entry.id) }}" alt="{{ entry.title }}" /> <img src="{{ url_for('web.get_cover', book_id=entry.id) }}" alt="{{ entry.title }}" />
{% if entry.id in read_book_ids %}<span class="badge read glyphicon glyphicon-ok"></span>{% endif %} {% if entry.id in read_book_ids %}<span class="badge read glyphicon glyphicon-ok"></span>{% endif %}
</span> </span>
</a> </a>
@ -85,8 +85,8 @@
<div class="col-sm-3 col-lg-2 col-xs-6 book" id="books"> <div class="col-sm-3 col-lg-2 col-xs-6 book" id="books">
<div class="cover"> <div class="cover">
<a href="{{ url_for('web.show_book', book_id=entry.id) }}" data-toggle="modal" data-target="#bookDetailsModal" data-remote="false"> <a href="{{ url_for('web.show_book', book_id=entry.id) }}" data-toggle="modal" data-target="#bookDetailsModal" data-remote="false">
<span class="img"> <span class="img" title="{{ entry.title }}">
<img title="{{ entry.title }}" src="{{ url_for('web.get_cover', book_id=entry.id) }}" alt="{{ entry.title }}"/> <img src="{{ url_for('web.get_cover', book_id=entry.id) }}" alt="{{ entry.title }}"/>
{% if entry.id in read_book_ids %}<span class="badge read glyphicon glyphicon-ok"></span>{% endif %} {% if entry.id in read_book_ids %}<span class="badge read glyphicon glyphicon-ok"></span>{% endif %}
</span> </span>
</a> </a>

View File

@ -61,7 +61,8 @@
{% if g.user.role_upload() or g.user.role_admin()%} {% if g.user.role_upload() or g.user.role_admin()%}
{% if g.allow_upload %} {% if g.allow_upload %}
<li> <li>
<form id="form-upload" class="navbar-form" action="{{ url_for('editbook.upload') }}" method="post" enctype="multipart/form-data"> <form id="form-upload" class="navbar-form" action="{{ url_for('editbook.upload') }}" data-title="{{_('Uploading...')}}" data-footer="{{_('Close')}}" data-failed="{{_('Error')}}" data-message="{{_('Upload done, processing, please wait...')}}" method="post" enctype="multipart/form-data">
<input type="hidden" name="csrf_token" value="{{ csrf_token() }}">
<div class="form-group"> <div class="form-group">
<span class="btn btn-default btn-file">{{_('Upload')}}<input id="btn-upload" name="btn-upload" <span class="btn btn-default btn-file">{{_('Upload')}}<input id="btn-upload" name="btn-upload"
type="file" accept="{% for format in accept %}.{% if format != ''%}{{format}}{% else %}*{% endif %}{{ ',' if not loop.last }}{% endfor %}" multiple></span> type="file" accept="{% for format in accept %}.{% if format != ''%}{{format}}{% else %}*{% endif %}{{ ',' if not loop.last }}{% endfor %}" multiple></span>
@ -135,7 +136,7 @@
{% if g.user.is_authenticated or g.allow_anonymous %} {% if g.user.is_authenticated or g.allow_anonymous %}
<li class="nav-head hidden-xs public-shelves">{{_('Shelves')}}</li> <li class="nav-head hidden-xs public-shelves">{{_('Shelves')}}</li>
{% for shelf in g.shelves_access %} {% for shelf in g.shelves_access %}
<li><a href="{{url_for('shelf.show_shelf', shelf_id=shelf.id)}}"><span class="glyphicon glyphicon-list shelf"></span>{{shelf.name|shortentitle(40)}}{% if shelf.is_public == 1 %} {{_('(Public)')}}{% endif %}</a></li> <li><a href="{{url_for('shelf.show_shelf', shelf_id=shelf.id)}}"><span class="glyphicon glyphicon-list shelf"></span> {{shelf.name|shortentitle(40)}}{% if shelf.is_public == 1 %} {{_('(Public)')}}{% endif %}</a></li>
{% endfor %} {% endfor %}
{% if not g.user.is_anonymous %} {% if not g.user.is_anonymous %}
<li id="nav_createshelf" class="create-shelf"><a href="{{url_for('shelf.create_shelf')}}">{{_('Create a Shelf')}}</a></li> <li id="nav_createshelf" class="create-shelf"><a href="{{url_for('shelf.create_shelf')}}">{{_('Create a Shelf')}}</a></li>
@ -200,17 +201,6 @@
<script src="{{ url_for('static', filename='js/libs/plugins.js') }}"></script> <script src="{{ url_for('static', filename='js/libs/plugins.js') }}"></script>
<script src="{{ url_for('static', filename='js/libs/jquery.form.min.js') }}"></script> <script src="{{ url_for('static', filename='js/libs/jquery.form.min.js') }}"></script>
<script src="{{ url_for('static', filename='js/uploadprogress.js') }}"> </script> <script src="{{ url_for('static', filename='js/uploadprogress.js') }}"> </script>
<script type="text/javascript">
$(function() {
$("#form-upload").uploadprogress({
redirect_url: "{{ url_for('web.index')}}",
uploadedMsg: "{{_('Upload done, processing, please wait...')}}",
modalTitle: "{{_('Uploading...')}}",
modalFooter: "{{_('Close')}}",
modalTitleFailed: "{{_('Error')}}"
});
});
</script>
<script src="{{ url_for('static', filename='js/main.js') }}"></script> <script src="{{ url_for('static', filename='js/main.js') }}"></script>
{% if g.current_theme == 1 %} {% if g.current_theme == 1 %}
<script src="{{ url_for('static', filename='js/libs/jquery.visible.min.js') }}"></script> <script src="{{ url_for('static', filename='js/libs/jquery.visible.min.js') }}"></script>

View File

@ -20,6 +20,7 @@
</div> </div>
{% if data == "series" %} {% if data == "series" %}
<input type="hidden" name="csrf_token" value="{{ csrf_token() }}">
<button class="update-view btn btn-primary" data-target="series_view" id="grid-button" data-view="grid">Grid</button> <button class="update-view btn btn-primary" data-target="series_view" id="grid-button" data-view="grid">Grid</button>
{% endif %} {% endif %}
</div> </div>

View File

@ -4,6 +4,7 @@
<h2 style="margin-top: 0">{{_('Login')}}</h2> <h2 style="margin-top: 0">{{_('Login')}}</h2>
<form method="POST" role="form"> <form method="POST" role="form">
<input type="hidden" name="next" value="{{next_url}}"> <input type="hidden" name="next" value="{{next_url}}">
<input type="hidden" name="csrf_token" value="{{ csrf_token() }}">
<div class="form-group"> <div class="form-group">
<label for="username">{{_('Username')}}</label> <label for="username">{{_('Username')}}</label>
<input type="text" class="form-control" id="username" name="username" placeholder="{{_('Username')}}"> <input type="text" class="form-control" id="username" name="username" placeholder="{{_('Username')}}">

View File

@ -1,35 +1,19 @@
<!doctype html> <!doctype html>
<html> <html>
<head> <head>
<meta http-equiv="content-type" content="text/html; charset=UTF-8"> <meta http-equiv="content-type" content="text/html; charset=UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1, user-scalable=no"> <title>{{_('DJVU Reader')}} | {{title}}</title>
<meta name="description" content="">
<meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1, user-scalable=no">
<meta name="apple-mobile-web-app-capable" content="yes">
<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 type="text/css" rel="stylesheet" href="{{ url_for('static', filename='js/libs/djvu_html5/Djvu_html5.css') }}"> <link type="text/css" rel="stylesheet" href="{{ url_for('static', filename='css/libs/Djvu_html5.css') }}">
<script src="{{ url_for('static', filename='js/libs/djvu_html5/djvu_html5/djvu_html5.nocache.js') }}"></script>
<title>{{_('DJVU Reader')}} | {{title}}</title> <script src="{{ url_for('static', filename='js/reading/djvu_reader.js') }}"></script>
</head>
<script type="text/javascript" language="javascript" <body>
src="{{ url_for('static', filename='js/libs/djvu_html5/djvu_html5/djvu_html5.nocache.js') }}"></script> <div id="djvuContainer" file="{{ url_for('web.serve_book', book_id=djvufile, book_format='djvu') }}"></div>
</head> </body>
<body>
<!-- RECOMMENDED if your web app will not function without JavaScript enabled -->
<noscript>
<div
style="width: 22em; position: absolute; left: 50%; margin-left: -11em; color: red; background-color: white; border: 1px solid red; padding: 4px; font-family: sans-serif">
Your web browser must have JavaScript enabled in order for this
application to display correctly.</div>
</noscript>
<div id="djvuContainer" file="{{ url_for('web.serve_book', book_id=djvufile,book_format='djvu') }}"></div>
<script type="text/javascript">
var DJVU_CONTEXT = {
background: "#666",
uiHideDelay: 1500,
};
</script>
</body>
</html> </html>

View File

@ -5,139 +5,26 @@
<meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1"> <meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1">
<title>{{_('txt Reader')}} | {{title}}</title> <title>{{_('txt Reader')}} | {{title}}</title>
<meta name="description" content=""> <meta name="description" content="">
<meta name="viewport" content="width=device-width"> <meta name="viewport" content="width=device-width, user-scalable=no">
<meta name="apple-mobile-web-app-capable" content="yes"> <meta name="apple-mobile-web-app-capable" content="yes">
<link rel="stylesheet" href="{{ url_for('static', filename='css/main.css') }}" type="text/css"/> <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/main.css') }}" type="text/css"/>
<link rel="stylesheet" href="{{ url_for('static', filename='css/text.css') }}" type="text/css"/>
<!-- EPUBJS Renderer -->
<!--<script src="../build/epub.js"></script>-->
<script src="{{ url_for('static', filename='js/libs/jquery.min.js') }}"></script> <script src="{{ url_for('static', filename='js/libs/jquery.min.js') }}"></script>
<script src="{{ url_for('static', filename='js/libs/plugins.js') }}"></script> <script src="{{ url_for('static', filename='js/libs/plugins.js') }}"></script>
<script>
<style type="text/css">
body {
background: white;
}
#readmain {
position: absolute;
width: 100%;
height: 100%;
}
#area {
width: 80%;
height: 80%;
margin: 5% auto;
max-width: 1250px;
}
#area iframe {
border: none;
}
xmp, pre, plaintext {
display: block;
font-family: -moz-fixed;
white-space: pre;
margin: 1em 0;
}
#area{
overflow:hidden;
}
pre {
white-space: pre-wrap;
word-wrap: break-word;
font-family: -moz-fixed;
column-count:2;
-webkit-columns:2;
-moz-columns:2;
column-gap:20px;
-moz-column-gap:20px;
-webkit-column-gap:20px;
position:relative;
}
</style>
<script>
"use strict"; "use strict";
</script> </script>
<script src="{{ url_for('static', filename='js/reading/txt_reader.js') }}"></script>
</head> </head>
<body> <body>
<div id="readmain"> <div id="readmain" data-load="{{ url_for('web.serve_book', book_id=txtfile, book_format='txt') }}">
<div id="left" class="arrow" ></div> <div id="left" class="arrow" ></div>
<div id="area"><pre id="content" class="content"></pre></div> <div id="area"><pre id="content" class="content"></pre></div>
<div id="right" class="arrow"></div> <div id="right" class="arrow"></div>
</div> </div>
<script>
$(document).ready(function() {
//to int
$("#area").width($("#area").width());
$("#content").width($("#content").width());
//bind text
$("#content").load("{{ url_for('web.serve_book', book_id=txtfile,book_format='txt') }}",function(textStr) {
$(this).height($(this).parent().height()*0.95);
$(this).text(textStr);
});
//keybind
$(document).keydown(function(event){
if(event.keyCode == 37){
prevPage();
}
if(event.keyCode == 39){
nextPage();
}
});
//click
$( "#left" ).click(function() {
prevPage();
});
$( "#right" ).click(function() {
nextPage();
});
$("#readmain").swipe( {
swipeRight:function() {
prevPage();
},
swipeLeft:function() {
nextPage();
},
});
//bind mouse
$(window).bind('DOMMouseScroll mousewheel', function(event) {
var delta = 0;
if (event.originalEvent.wheelDelta) {
delta = event.originalEvent.wheelDelta;
}else if (event.originalEvent.detail) {
delta = event.originalEvent.detail*-1;
}
if (delta >= 0) {
prevPage();
}
else {
nextPage();
}
});
//page animate
var origwidth = $("#content")[0].getBoundingClientRect().width;
var gap = 20;
function prevPage(){
if($("#content").offset().left > 0){ return;}
leftoff = $("#content").offset().left;
leftoff = leftoff+origwidth+gap;
$("#content").offset({left:leftoff});
}
function nextPage(){
leftoff = $("#content").offset().left;
leftoff = leftoff-origwidth-gap;
if ( leftoff + $("#content")[0].scrollWidth < 0) { return;}
$("#content").offset({left:leftoff});
}
});
</script>
</body> </body>
</html> </html>

View File

@ -3,6 +3,7 @@
<div class="well col-sm-6 col-sm-offset-2"> <div class="well col-sm-6 col-sm-offset-2">
<h2 style="margin-top: 0">{{_('Register New Account')}}</h2> <h2 style="margin-top: 0">{{_('Register New Account')}}</h2>
<form method="POST" role="form"> <form method="POST" role="form">
<input type="hidden" name="csrf_token" value="{{ csrf_token() }}">
{% if not config.config_register_email %} {% if not config.config_register_email %}
<div class="form-group required"> <div class="form-group required">
<label for="name">{{_('Username')}}</label> <label for="name">{{_('Username')}}</label>

View File

@ -1,10 +1,11 @@
{% extends "layout.html" %} {% extends "layout.html" %}
{% block body %} {% block body %}
<div class="well"> <div class="well">
<input type="hidden" name="csrf_token" value="{{ csrf_token() }}">
<h2 style="margin-top: 0">{{_('Magic Link - Authorise New Device')}}</h2> <h2 style="margin-top: 0">{{_('Magic Link - Authorise New Device')}}</h2>
<p> <p>
{{_('On another device, login and visit:')}} {{_('On another device, login and visit:')}}
<h4><a id="verify_url" href="{{verify_url}}">{{verify_url}}</a></b> <h4><a id="verify_url" data-token="{{token}}" href="{{verify_url}}">{{verify_url}}</a><b></b>
</h4> </h4>
<p> <p>
{{_('Once verified, you will automatically be logged in on this device.')}} {{_('Once verified, you will automatically be logged in on this device.')}}
@ -16,26 +17,5 @@
{% endblock %} {% endblock %}
{% block js %} {% block js %}
<script type="text/javascript"> <script src="{{ url_for('static', filename='js/remote_login.js') }}"></script>
(function () {
// Poll the server to check if the user has authenticated
var t = setInterval(function () {
$.post('{{url_for("remotelogin.token_verified")}}', { token: '{{token}}' })
.done(function(response) {
if (response.status === 'success') {
// Wait a tick so cookies are updated
setTimeout(function () {
window.location.href = '{{url_for("web.index")}}';
}, 0);
}
})
.fail(function (xhr) {
clearInterval(t);
var response = JSON.parse(xhr.responseText);
alert(response.message);
});
}, 5000);
})()
</script>
{% endblock %} {% endblock %}

View File

@ -43,8 +43,8 @@
<div class="cover"> <div class="cover">
{% if entry.has_cover is defined %} {% if entry.has_cover is defined %}
<a href="{{ url_for('web.show_book', book_id=entry.id) }}" data-toggle="modal" data-target="#bookDetailsModal" data-remote="false"> <a href="{{ url_for('web.show_book', book_id=entry.id) }}" data-toggle="modal" data-target="#bookDetailsModal" data-remote="false">
<span class="img"> <span class="img" title="{{entry.title}}" >
<img title="{{entry.title}}" src="{{ url_for('web.get_cover', book_id=entry.id) }}" alt="{{ entry.title }}" /> <img src="{{ url_for('web.get_cover', book_id=entry.id) }}" alt="{{ entry.title }}" />
{% if entry.id in read_book_ids %}<span class="badge read glyphicon glyphicon-ok"></span>{% endif %} {% if entry.id in read_book_ids %}<span class="badge read glyphicon glyphicon-ok"></span>{% endif %}
</span> </span>
</a> </a>

View File

@ -3,6 +3,7 @@
<h1 class="{{page}}">{{title}}</h1> <h1 class="{{page}}">{{title}}</h1>
<div class="col-md-10 col-lg-6"> <div class="col-md-10 col-lg-6">
<form role="form" id="search" action="{{ url_for('web.advanced_search_form') }}" method="POST"> <form role="form" id="search" action="{{ url_for('web.advanced_search_form') }}" method="POST">
<input type="hidden" name="csrf_token" value="{{ csrf_token() }}">
<div class="form-group"> <div class="form-group">
<label for="book_title">{{_('Book Title')}}</label> <label for="book_title">{{_('Book Title')}}</label>
<input type="text" class="form-control" name="book_title" id="book_title" value=""> <input type="text" class="form-control" name="book_title" id="book_title" value="">
@ -228,10 +229,6 @@
{% endblock %} {% endblock %}
{% block js %} {% block js %}
<script>
var language = '{{ g.user.locale }}';
</script>
<script src="{{ url_for('static', filename='js/libs/bootstrap-datepicker/bootstrap-datepicker.min.js') }}"></script> <script src="{{ url_for('static', filename='js/libs/bootstrap-datepicker/bootstrap-datepicker.min.js') }}"></script>
{% if not g.user.locale == 'en' %} {% if not g.user.locale == 'en' %}
<script src="{{ url_for('static', filename='js/libs/bootstrap-datepicker/locales/bootstrap-datepicker.' + g.user.locale + '.min.js') }}" charset="UTF-8"></script> <script src="{{ url_for('static', filename='js/libs/bootstrap-datepicker/locales/bootstrap-datepicker.' + g.user.locale + '.min.js') }}" charset="UTF-8"></script>

View File

@ -7,6 +7,7 @@
{% endif %} {% endif %}
{% if g.user.is_authenticated %} {% if g.user.is_authenticated %}
{% if (g.user.role_edit_shelfs() and shelf.is_public ) or not shelf.is_public %} {% if (g.user.role_edit_shelfs() and shelf.is_public ) or not shelf.is_public %}
<input type="hidden" name="csrf_token" value="{{ csrf_token() }}">
<div class="btn btn-danger" id="delete_shelf" data-value="{{ shelf.id }}">{{ _('Delete this Shelf') }}</div> <div class="btn btn-danger" id="delete_shelf" data-value="{{ shelf.id }}">{{ _('Delete this Shelf') }}</div>
<a id="edit_shelf" href="{{ url_for('shelf.edit_shelf', shelf_id=shelf.id) }}" class="btn btn-primary">{{ _('Edit Shelf Properties') }} </a> <a id="edit_shelf" href="{{ url_for('shelf.edit_shelf', shelf_id=shelf.id) }}" class="btn btn-primary">{{ _('Edit Shelf Properties') }} </a>
{% if entries.__len__() %} {% if entries.__len__() %}
@ -30,8 +31,8 @@
<div class="col-sm-3 col-lg-2 col-xs-6 book"> <div class="col-sm-3 col-lg-2 col-xs-6 book">
<div class="cover"> <div class="cover">
<a href="{{ url_for('web.show_book', book_id=entry.id) }}" data-toggle="modal" data-target="#bookDetailsModal" data-remote="false"> <a href="{{ url_for('web.show_book', book_id=entry.id) }}" data-toggle="modal" data-target="#bookDetailsModal" data-remote="false">
<span class="img"> <span class="img" title="{{entry.title}}" >
<img title="{{entry.title}}" src="{{ url_for('web.get_cover', book_id=entry.id) }}" alt="{{ entry.title }}" /> <img src="{{ url_for('web.get_cover', book_id=entry.id) }}" alt="{{ entry.title }}" />
{% if entry.id in read_book_ids %}<span class="badge read glyphicon glyphicon-ok"></span>{% endif %} {% if entry.id in read_book_ids %}<span class="badge read glyphicon glyphicon-ok"></span>{% endif %}
</span> </span>
</a> </a>

View File

@ -3,6 +3,7 @@
<div class="discover"> <div class="discover">
<h1>{{title}}</h1> <h1>{{title}}</h1>
<form role="form" method="POST"> <form role="form" method="POST">
<input type="hidden" name="csrf_token" value="{{ csrf_token() }}">
<div class="form-group"> <div class="form-group">
<label for="title">{{_('Title')}}</label> <label for="title">{{_('Title')}}</label>
<input type="text" class="form-control" name="title" id="title" value="{{ shelf.name if shelf.name != None }}"> <input type="text" class="form-control" name="title" id="title" value="{{ shelf.name if shelf.name != None }}">

View File

@ -3,15 +3,16 @@
<div class="col-sm-8 col-lg-8 col-xs-12"> <div class="col-sm-8 col-lg-8 col-xs-12">
<h2>{{title}}</h2> <h2>{{title}}</h2>
<div>{{_('Drag to Rearrange Order')}}</div> <div>{{_('Drag to Rearrange Order')}}</div>
<input type="hidden" name="csrf_token" value="{{ csrf_token() }}">
<div id="sortTrue" class="list-group"> <div id="sortTrue" class="list-group">
{% for entry in entries %} {% for entry in entries %}
<div id="{{entry['Books']['id']}}" class="list-group-item"> <div id="{{entry['Books']['id']}}" class="list-group-item">
<div class="row"> <div class="row">
<div class="col-lg-2 col-sm-4 hidden-xs"> <div class="col-lg-2 col-sm-4 hidden-xs">
{% if entry['visible'] %} {% if entry['visible'] %}
<img title="{{entry.title}}" class="cover-height" src="{{ url_for('web.get_cover', book_id=entry['Books']['id']) }}"> <img title="{{entry['Books']['title']}}" class="cover-height" src="{{ url_for('web.get_cover', book_id=entry['Books']['id']) }}">
{% else %} {% else %}
<img title="{{entry.title}}" class="cover-height" src="{{ url_for('static', filename='generic_cover.jpg') }}"> <img title="{{entry['Books']['title']}}" class="cover-height" src="{{ url_for('static', filename='generic_cover.jpg') }}">
{% endif %} {% endif %}
</div> </div>
<div class="col-lg-10 col-sm-8 col-xs-12"> <div class="col-lg-10 col-sm-8 col-xs-12">

View File

@ -26,8 +26,5 @@
{% block js %} {% block js %}
<script src="{{ url_for('static', filename='js/libs/bootstrap-table/bootstrap-table.min.js') }}"></script> <script src="{{ url_for('static', filename='js/libs/bootstrap-table/bootstrap-table.min.js') }}"></script>
<script src="{{ url_for('static', filename='js/table.js') }}"></script> <script src="{{ url_for('static', filename='js/table.js') }}"></script>
<script>
// ToDo: Move to js file
</script>
{% endblock %} {% endblock %}

View File

@ -3,6 +3,7 @@
<div class="discover"> <div class="discover">
<h1>{{title}}</h1> <h1>{{title}}</h1>
<form role="form" method="POST" autocomplete="off"> <form role="form" method="POST" autocomplete="off">
<input type="hidden" name="csrf_token" value="{{ csrf_token() }}">
<div class="col-md-10 col-lg-8"> <div class="col-md-10 col-lg-8">
{% if new_user or ( g.user and content.name != "Guest" and g.user.role_admin() ) %} {% if new_user or ( g.user and content.name != "Guest" and g.user.role_admin() ) %}
<div class="form-group required"> <div class="form-group required">
@ -32,7 +33,7 @@
<label for="locale">{{_('Language')}}</label> <label for="locale">{{_('Language')}}</label>
<select name="locale" id="locale" class="form-control"> <select name="locale" id="locale" class="form-control">
{% for translation in translations %} {% for translation in translations %}
<option value="{{translation}}" {% if translation|string == content.locale %}selected{% endif %} {% if new_user == 1 and loop.first %}selected{% endif %}>{{ translation.display_name|capitalize }}</option> <option value="{{translation}}" {% if translation|string == content.locale %}selected{% endif %}>{{ translation.display_name|capitalize }}</option>
{% endfor %} {% endfor %}
</select> </select>
</div> </div>
@ -41,7 +42,7 @@
<div class="form-group"> <div class="form-group">
<label for="default_language">{{_('Language of Books')}}</label> <label for="default_language">{{_('Language of Books')}}</label>
<select name="default_language" id="default_language" class="form-control"> <select name="default_language" id="default_language" class="form-control">
<option value="all" {% if new_user == 1 %}selected{% endif %}>{{ _('Show All') }}</option> <option value="all" {% if content.default_language == "all" %}selected{% endif %}>{{ _('Show All') }}</option>
{% for language in languages %} {% for language in languages %}
<option value="{{ language.lang_code }}" {% if content.default_language == language.lang_code %}selected{% endif %}>{{ language.name }}</option> <option value="{{ language.lang_code }}" {% if content.default_language == language.lang_code %}selected{% endif %}>{{ language.name }}</option>
{% endfor %} {% endfor %}

View File

@ -118,6 +118,7 @@
{% endblock %} {% endblock %}
{% block body %} {% block body %}
<h2 class="{{page}}">{{_(title)}}</h2> <h2 class="{{page}}">{{_(title)}}</h2>
<input type="hidden" name="csrf_token" value="{{ csrf_token() }}">
<div class="col-xs-12 col-sm-12"> <div class="col-xs-12 col-sm-12">
<div class="row"> <div class="row">
<div class="btn btn-default disabled" id="user_delete_selection" aria-disabled="true">{{_('Remove Selections')}}</div> <div class="btn btn-default disabled" id="user_delete_selection" aria-disabled="true">{{_('Remove Selections')}}</div>
@ -183,6 +184,9 @@
<script src="{{ url_for('static', filename='js/libs/bootstrap-table/bootstrap-table-editable.min.js') }}"></script> <script src="{{ url_for('static', filename='js/libs/bootstrap-table/bootstrap-table-editable.min.js') }}"></script>
<script src="{{ url_for('static', filename='js/libs/bootstrap-table/bootstrap-editable.min.js') }}"></script> <script src="{{ url_for('static', filename='js/libs/bootstrap-table/bootstrap-editable.min.js') }}"></script>
<script src="{{ url_for('static', filename='js/libs/bootstrap-select.min.js')}}"></script> <script src="{{ url_for('static', filename='js/libs/bootstrap-select.min.js')}}"></script>
{% if not g.user.locale == 'en' %}
<script src="{{ url_for('static', filename='js/libs/bootstrap-select/defaults-' + g.user.locale + '.min.js') }}" charset="UTF-8"></script>
{% endif %}
<script src="{{ url_for('static', filename='js/table.js') }}"></script> <script src="{{ url_for('static', filename='js/table.js') }}"></script>
{% endblock %} {% endblock %}

View File

@ -6,7 +6,7 @@ msgid ""
msgstr "" msgstr ""
"Project-Id-Version: Calibre-Web\n" "Project-Id-Version: Calibre-Web\n"
"Report-Msgid-Bugs-To: https://github.com/janeczku/Calibre-Web\n" "Report-Msgid-Bugs-To: https://github.com/janeczku/Calibre-Web\n"
"POT-Creation-Date: 2021-08-29 13:30+0200\n" "POT-Creation-Date: 2021-09-25 08:24+0200\n"
"PO-Revision-Date: 2020-06-09 21:11+0100\n" "PO-Revision-Date: 2020-06-09 21:11+0100\n"
"Last-Translator: Lukas Heroudek <lukas.heroudek@gmail.com>\n" "Last-Translator: Lukas Heroudek <lukas.heroudek@gmail.com>\n"
"Language: cs_CZ\n" "Language: cs_CZ\n"
@ -439,24 +439,29 @@ msgstr "Všeobecná chyba"
msgid "Update File Could Not be Saved in Temp Dir" msgid "Update File Could Not be Saved in Temp Dir"
msgstr "Aktualizační soubor nemohl být uložen do Temp Dir" msgstr "Aktualizační soubor nemohl být uložen do Temp Dir"
#: cps/admin.py:1746 #: cps/admin.py:1709
#, fuzzy
msgid "Failed to extract at least One LDAP User"
msgstr "Nepodařilo se vytvořit nejméně jednoho uživatele LDAP"
#: cps/admin.py:1752
msgid "Failed to Create at Least One LDAP User" msgid "Failed to Create at Least One LDAP User"
msgstr "Nepodařilo se vytvořit nejméně jednoho uživatele LDAP" msgstr "Nepodařilo se vytvořit nejméně jednoho uživatele LDAP"
#: cps/admin.py:1759 #: cps/admin.py:1765
#, python-format #, python-format
msgid "Error: %(ldaperror)s" msgid "Error: %(ldaperror)s"
msgstr "Chyba: %(ldaperror)s" msgstr "Chyba: %(ldaperror)s"
#: cps/admin.py:1763 #: cps/admin.py:1769
msgid "Error: No user returned in response of LDAP server" msgid "Error: No user returned in response of LDAP server"
msgstr "Chyba: Žádná reakce od uživatele LDAP serveru" msgstr "Chyba: Žádná reakce od uživatele LDAP serveru"
#: cps/admin.py:1796 #: cps/admin.py:1802
msgid "At Least One LDAP User Not Found in Database" msgid "At Least One LDAP User Not Found in Database"
msgstr "Nejméně jeden uživatel LDAP nenalezen v databázi" msgstr "Nejméně jeden uživatel LDAP nenalezen v databázi"
#: cps/admin.py:1798 #: cps/admin.py:1804
msgid "{} User Successfully Imported" msgid "{} User Successfully Imported"
msgstr "" msgstr ""

View File

@ -7,7 +7,7 @@ msgid ""
msgstr "" msgstr ""
"Project-Id-Version: Calibre-Web\n" "Project-Id-Version: Calibre-Web\n"
"Report-Msgid-Bugs-To: https://github.com/janeczku/Calibre-Web\n" "Report-Msgid-Bugs-To: https://github.com/janeczku/Calibre-Web\n"
"POT-Creation-Date: 2021-08-29 13:30+0200\n" "POT-Creation-Date: 2021-09-25 08:24+0200\n"
"PO-Revision-Date: 2021-08-01 17:24+0200\n" "PO-Revision-Date: 2021-08-01 17:24+0200\n"
"Last-Translator: Ozzie Isaacs\n" "Last-Translator: Ozzie Isaacs\n"
"Language: de\n" "Language: de\n"
@ -432,24 +432,29 @@ msgstr "Allgemeiner Fehler"
msgid "Update File Could Not be Saved in Temp Dir" msgid "Update File Could Not be Saved in Temp Dir"
msgstr "Updatedatei konnte nicht in Temporärem Ordner gespeichert werden" msgstr "Updatedatei konnte nicht in Temporärem Ordner gespeichert werden"
#: cps/admin.py:1746 #: cps/admin.py:1709
#, fuzzy
msgid "Failed to extract at least One LDAP User"
msgstr "Mindestens ein LDAP Benutzer konnte nicht erzeugt werden"
#: cps/admin.py:1752
msgid "Failed to Create at Least One LDAP User" msgid "Failed to Create at Least One LDAP User"
msgstr "Mindestens ein LDAP Benutzer konnte nicht erzeugt werden" msgstr "Mindestens ein LDAP Benutzer konnte nicht erzeugt werden"
#: cps/admin.py:1759 #: cps/admin.py:1765
#, python-format #, python-format
msgid "Error: %(ldaperror)s" msgid "Error: %(ldaperror)s"
msgstr "Fehler: %(ldaperror)s" msgstr "Fehler: %(ldaperror)s"
#: cps/admin.py:1763 #: cps/admin.py:1769
msgid "Error: No user returned in response of LDAP server" msgid "Error: No user returned in response of LDAP server"
msgstr "Fehler: Keine Benutzerinformationen von LDAP Server empfangen" msgstr "Fehler: Keine Benutzerinformationen von LDAP Server empfangen"
#: cps/admin.py:1796 #: cps/admin.py:1802
msgid "At Least One LDAP User Not Found in Database" msgid "At Least One LDAP User Not Found in Database"
msgstr "Mindestens ein LDAP Benutzer wurde nicht in der Datenbank gefudnen" msgstr "Mindestens ein LDAP Benutzer wurde nicht in der Datenbank gefudnen"
#: cps/admin.py:1798 #: cps/admin.py:1804
msgid "{} User Successfully Imported" msgid "{} User Successfully Imported"
msgstr "{} Benutzer erfolgreich importiert" msgstr "{} Benutzer erfolgreich importiert"
@ -3041,7 +3046,7 @@ msgstr "Kindle E-Mail"
#: cps/templates/user_table.html:136 #: cps/templates/user_table.html:136
msgid "Locale" msgid "Locale"
msgstr "Anuzeigesprache" msgstr "Anzeigesprache"
#: cps/templates/user_table.html:137 #: cps/templates/user_table.html:137
msgid "Visible Book Languages" msgid "Visible Book Languages"

View File

@ -6,7 +6,7 @@ msgid ""
msgstr "" msgstr ""
"Project-Id-Version: Calibre-Web\n" "Project-Id-Version: Calibre-Web\n"
"Report-Msgid-Bugs-To: https://github.com/janeczku/Calibre-Web\n" "Report-Msgid-Bugs-To: https://github.com/janeczku/Calibre-Web\n"
"POT-Creation-Date: 2021-08-29 13:30+0200\n" "POT-Creation-Date: 2021-09-25 08:24+0200\n"
"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
"Last-Translator: Depountis Georgios\n" "Last-Translator: Depountis Georgios\n"
"Language: el\n" "Language: el\n"
@ -439,24 +439,29 @@ msgstr "Γενικό σφάλμα"
msgid "Update File Could Not be Saved in Temp Dir" msgid "Update File Could Not be Saved in Temp Dir"
msgstr "Το Αρχείο Ενημέρωσης Δεν Μπόρεσε Να Αποθηκευτεί σε" msgstr "Το Αρχείο Ενημέρωσης Δεν Μπόρεσε Να Αποθηκευτεί σε"
#: cps/admin.py:1746 #: cps/admin.py:1709
#, fuzzy
msgid "Failed to extract at least One LDAP User"
msgstr "Αποτυχία Δημιουργίας Τουλάχιστον Ενός Χρήστη LDAP"
#: cps/admin.py:1752
msgid "Failed to Create at Least One LDAP User" msgid "Failed to Create at Least One LDAP User"
msgstr "Αποτυχία Δημιουργίας Τουλάχιστον Ενός Χρήστη LDAP" msgstr "Αποτυχία Δημιουργίας Τουλάχιστον Ενός Χρήστη LDAP"
#: cps/admin.py:1759 #: cps/admin.py:1765
#, python-format #, python-format
msgid "Error: %(ldaperror)s" msgid "Error: %(ldaperror)s"
msgstr "Σφάλμα: %(ldaperror)s" msgstr "Σφάλμα: %(ldaperror)s"
#: cps/admin.py:1763 #: cps/admin.py:1769
msgid "Error: No user returned in response of LDAP server" msgid "Error: No user returned in response of LDAP server"
msgstr "Σφάλμα: Δεν επιστράφηκε χρήστης σε απάντηση του διακομιστή LDAP" msgstr "Σφάλμα: Δεν επιστράφηκε χρήστης σε απάντηση του διακομιστή LDAP"
#: cps/admin.py:1796 #: cps/admin.py:1802
msgid "At Least One LDAP User Not Found in Database" msgid "At Least One LDAP User Not Found in Database"
msgstr "Τουλάχιστον Ένας Χρήστης LDAP Δεν Βρέθηκε Στη Βάση Δεδομένων" msgstr "Τουλάχιστον Ένας Χρήστης LDAP Δεν Βρέθηκε Στη Βάση Δεδομένων"
#: cps/admin.py:1798 #: cps/admin.py:1804
msgid "{} User Successfully Imported" msgid "{} User Successfully Imported"
msgstr "" msgstr ""

View File

@ -9,7 +9,7 @@ msgid ""
msgstr "" msgstr ""
"Project-Id-Version: Calibre-Web\n" "Project-Id-Version: Calibre-Web\n"
"Report-Msgid-Bugs-To: https://github.com/janeczku/Calibre-Web\n" "Report-Msgid-Bugs-To: https://github.com/janeczku/Calibre-Web\n"
"POT-Creation-Date: 2021-08-29 13:30+0200\n" "POT-Creation-Date: 2021-09-25 08:24+0200\n"
"PO-Revision-Date: 2020-05-25 17:22+0200\n" "PO-Revision-Date: 2020-05-25 17:22+0200\n"
"Last-Translator: minakmostoles <xxx@xxx.com>\n" "Last-Translator: minakmostoles <xxx@xxx.com>\n"
"Language: es\n" "Language: es\n"
@ -443,24 +443,29 @@ msgstr "Error general"
msgid "Update File Could Not be Saved in Temp Dir" msgid "Update File Could Not be Saved in Temp Dir"
msgstr "La actualización del archivo no pudo guardarse en el directorio temporal (Temp Dir)" msgstr "La actualización del archivo no pudo guardarse en el directorio temporal (Temp Dir)"
#: cps/admin.py:1746 #: cps/admin.py:1709
#, fuzzy
msgid "Failed to extract at least One LDAP User"
msgstr "Error al crear al menos un usuario LDAP"
#: cps/admin.py:1752
msgid "Failed to Create at Least One LDAP User" msgid "Failed to Create at Least One LDAP User"
msgstr "Error al crear al menos un usuario LDAP" msgstr "Error al crear al menos un usuario LDAP"
#: cps/admin.py:1759 #: cps/admin.py:1765
#, python-format #, python-format
msgid "Error: %(ldaperror)s" msgid "Error: %(ldaperror)s"
msgstr "Error: %(ldaperror)s" msgstr "Error: %(ldaperror)s"
#: cps/admin.py:1763 #: cps/admin.py:1769
msgid "Error: No user returned in response of LDAP server" msgid "Error: No user returned in response of LDAP server"
msgstr "Error: el servidor LDAP no ha devuelto ningún usuario" msgstr "Error: el servidor LDAP no ha devuelto ningún usuario"
#: cps/admin.py:1796 #: cps/admin.py:1802
msgid "At Least One LDAP User Not Found in Database" msgid "At Least One LDAP User Not Found in Database"
msgstr "Al menos, un usuario LDAP no se ha encontrado en la base de datos" msgstr "Al menos, un usuario LDAP no se ha encontrado en la base de datos"
#: cps/admin.py:1798 #: cps/admin.py:1804
msgid "{} User Successfully Imported" msgid "{} User Successfully Imported"
msgstr "{} Usuario importado con éxito" msgstr "{} Usuario importado con éxito"

View File

@ -7,7 +7,7 @@ msgid ""
msgstr "" msgstr ""
"Project-Id-Version: Calibre-Web\n" "Project-Id-Version: Calibre-Web\n"
"Report-Msgid-Bugs-To: https://github.com/janeczku/Calibre-Web\n" "Report-Msgid-Bugs-To: https://github.com/janeczku/Calibre-Web\n"
"POT-Creation-Date: 2021-08-29 13:30+0200\n" "POT-Creation-Date: 2021-09-25 08:24+0200\n"
"PO-Revision-Date: 2020-01-12 13:56+0100\n" "PO-Revision-Date: 2020-01-12 13:56+0100\n"
"Last-Translator: Samuli Valavuo <svalavuo@gmail.com>\n" "Last-Translator: Samuli Valavuo <svalavuo@gmail.com>\n"
"Language: fi\n" "Language: fi\n"
@ -439,24 +439,28 @@ msgstr "Yleinen virhe"
msgid "Update File Could Not be Saved in Temp Dir" msgid "Update File Could Not be Saved in Temp Dir"
msgstr "" msgstr ""
#: cps/admin.py:1746 #: cps/admin.py:1709
msgid "Failed to extract at least One LDAP User"
msgstr ""
#: cps/admin.py:1752
msgid "Failed to Create at Least One LDAP User" msgid "Failed to Create at Least One LDAP User"
msgstr "" msgstr ""
#: cps/admin.py:1759 #: cps/admin.py:1765
#, python-format #, python-format
msgid "Error: %(ldaperror)s" msgid "Error: %(ldaperror)s"
msgstr "" msgstr ""
#: cps/admin.py:1763 #: cps/admin.py:1769
msgid "Error: No user returned in response of LDAP server" msgid "Error: No user returned in response of LDAP server"
msgstr "" msgstr ""
#: cps/admin.py:1796 #: cps/admin.py:1802
msgid "At Least One LDAP User Not Found in Database" msgid "At Least One LDAP User Not Found in Database"
msgstr "" msgstr ""
#: cps/admin.py:1798 #: cps/admin.py:1804
msgid "{} User Successfully Imported" msgid "{} User Successfully Imported"
msgstr "" msgstr ""

View File

@ -22,7 +22,7 @@ msgid ""
msgstr "" msgstr ""
"Project-Id-Version: Calibre-Web\n" "Project-Id-Version: Calibre-Web\n"
"Report-Msgid-Bugs-To: EMAIL@ADDRESS\n" "Report-Msgid-Bugs-To: EMAIL@ADDRESS\n"
"POT-Creation-Date: 2021-08-29 13:30+0200\n" "POT-Creation-Date: 2021-09-25 08:24+0200\n"
"PO-Revision-Date: 2020-06-07 06:47+0200\n" "PO-Revision-Date: 2020-06-07 06:47+0200\n"
"Last-Translator: <thovi98@gmail.com>\n" "Last-Translator: <thovi98@gmail.com>\n"
"Language: fr\n" "Language: fr\n"
@ -455,24 +455,29 @@ msgstr "Erreur générale"
msgid "Update File Could Not be Saved in Temp Dir" msgid "Update File Could Not be Saved in Temp Dir"
msgstr "Le fichier de mise à jour ne peut pas être sauvegardé dans le répertoire temporaire" msgstr "Le fichier de mise à jour ne peut pas être sauvegardé dans le répertoire temporaire"
#: cps/admin.py:1746 #: cps/admin.py:1709
#, fuzzy
msgid "Failed to extract at least One LDAP User"
msgstr "Impossible de créer au moins un utilisateur LDAP"
#: cps/admin.py:1752
msgid "Failed to Create at Least One LDAP User" msgid "Failed to Create at Least One LDAP User"
msgstr "Impossible de créer au moins un utilisateur LDAP" msgstr "Impossible de créer au moins un utilisateur LDAP"
#: cps/admin.py:1759 #: cps/admin.py:1765
#, python-format #, python-format
msgid "Error: %(ldaperror)s" msgid "Error: %(ldaperror)s"
msgstr "Erreur : %(ldaperror)s" msgstr "Erreur : %(ldaperror)s"
#: cps/admin.py:1763 #: cps/admin.py:1769
msgid "Error: No user returned in response of LDAP server" msgid "Error: No user returned in response of LDAP server"
msgstr "Erreur : Aucun utilisateur renvoyé dans la réponse LDAP du serveur" msgstr "Erreur : Aucun utilisateur renvoyé dans la réponse LDAP du serveur"
#: cps/admin.py:1796 #: cps/admin.py:1802
msgid "At Least One LDAP User Not Found in Database" msgid "At Least One LDAP User Not Found in Database"
msgstr "Au moins un utilisateur LDAP n'a pas été trouvé dans la base de données" msgstr "Au moins un utilisateur LDAP n'a pas été trouvé dans la base de données"
#: cps/admin.py:1798 #: cps/admin.py:1804
msgid "{} User Successfully Imported" msgid "{} User Successfully Imported"
msgstr "{} utilisateur importé avec succès" msgstr "{} utilisateur importé avec succès"

View File

@ -7,7 +7,7 @@ msgid ""
msgstr "" msgstr ""
"Project-Id-Version: PROJECT VERSION\n" "Project-Id-Version: PROJECT VERSION\n"
"Report-Msgid-Bugs-To: EMAIL@ADDRESS\n" "Report-Msgid-Bugs-To: EMAIL@ADDRESS\n"
"POT-Creation-Date: 2021-08-29 13:30+0200\n" "POT-Creation-Date: 2021-09-25 08:24+0200\n"
"PO-Revision-Date: 2019-04-06 23:36+0200\n" "PO-Revision-Date: 2019-04-06 23:36+0200\n"
"Last-Translator: \n" "Last-Translator: \n"
"Language: hu\n" "Language: hu\n"
@ -439,24 +439,28 @@ msgstr "Általános hiba"
msgid "Update File Could Not be Saved in Temp Dir" msgid "Update File Could Not be Saved in Temp Dir"
msgstr "" msgstr ""
#: cps/admin.py:1746 #: cps/admin.py:1709
msgid "Failed to extract at least One LDAP User"
msgstr ""
#: cps/admin.py:1752
msgid "Failed to Create at Least One LDAP User" msgid "Failed to Create at Least One LDAP User"
msgstr "" msgstr ""
#: cps/admin.py:1759 #: cps/admin.py:1765
#, python-format #, python-format
msgid "Error: %(ldaperror)s" msgid "Error: %(ldaperror)s"
msgstr "" msgstr ""
#: cps/admin.py:1763 #: cps/admin.py:1769
msgid "Error: No user returned in response of LDAP server" msgid "Error: No user returned in response of LDAP server"
msgstr "" msgstr ""
#: cps/admin.py:1796 #: cps/admin.py:1802
msgid "At Least One LDAP User Not Found in Database" msgid "At Least One LDAP User Not Found in Database"
msgstr "" msgstr ""
#: cps/admin.py:1798 #: cps/admin.py:1804
msgid "{} User Successfully Imported" msgid "{} User Successfully Imported"
msgstr "" msgstr ""

View File

@ -6,7 +6,7 @@ msgid ""
msgstr "" msgstr ""
"Project-Id-Version: Calibre-Web\n" "Project-Id-Version: Calibre-Web\n"
"Report-Msgid-Bugs-To: https://github.com/janeczku/Calibre-Web\n" "Report-Msgid-Bugs-To: https://github.com/janeczku/Calibre-Web\n"
"POT-Creation-Date: 2021-08-29 13:30+0200\n" "POT-Creation-Date: 2021-09-25 08:24+0200\n"
"PO-Revision-Date: 2017-04-04 15:09+0200\n" "PO-Revision-Date: 2017-04-04 15:09+0200\n"
"Last-Translator: ElQuimm <quimm@webtaste.com>\n" "Last-Translator: ElQuimm <quimm@webtaste.com>\n"
"Language: it\n" "Language: it\n"
@ -436,24 +436,29 @@ msgstr "Errore generale"
msgid "Update File Could Not be Saved in Temp Dir" msgid "Update File Could Not be Saved in Temp Dir"
msgstr "Il file di aggiornamento non può essere salvato nella cartella temporanea" msgstr "Il file di aggiornamento non può essere salvato nella cartella temporanea"
#: cps/admin.py:1746 #: cps/admin.py:1709
#, fuzzy
msgid "Failed to extract at least One LDAP User"
msgstr "Fallita la creazione di almeno un utente LDAP"
#: cps/admin.py:1752
msgid "Failed to Create at Least One LDAP User" msgid "Failed to Create at Least One LDAP User"
msgstr "Fallita la creazione di almeno un utente LDAP" msgstr "Fallita la creazione di almeno un utente LDAP"
#: cps/admin.py:1759 #: cps/admin.py:1765
#, python-format #, python-format
msgid "Error: %(ldaperror)s" msgid "Error: %(ldaperror)s"
msgstr "Errore: %(ldaperror)s" msgstr "Errore: %(ldaperror)s"
#: cps/admin.py:1763 #: cps/admin.py:1769
msgid "Error: No user returned in response of LDAP server" msgid "Error: No user returned in response of LDAP server"
msgstr "Errore: nessun utente restituito in risposta dal server LDAP" msgstr "Errore: nessun utente restituito in risposta dal server LDAP"
#: cps/admin.py:1796 #: cps/admin.py:1802
msgid "At Least One LDAP User Not Found in Database" msgid "At Least One LDAP User Not Found in Database"
msgstr "Almeno un utente LDAP non è stato trovato nel database" msgstr "Almeno un utente LDAP non è stato trovato nel database"
#: cps/admin.py:1798 #: cps/admin.py:1804
msgid "{} User Successfully Imported" msgid "{} User Successfully Imported"
msgstr "{} utente importato con successo" msgstr "{} utente importato con successo"

Some files were not shown because too many files have changed in this diff Show More