1
0
mirror of https://github.com/janeczku/calibre-web synced 2024-12-30 03:50:31 +00:00

Merged develop, fixed merged conflicts

This commit is contained in:
mmonkey 2020-12-28 23:41:49 -06:00
commit 35c60eaee5
38 changed files with 426 additions and 145 deletions

View File

@ -39,6 +39,7 @@ from sqlalchemy.exc import IntegrityError, OperationalError, InvalidRequestError
from sqlalchemy.sql.expression import func, or_
from . import constants, logger, helper, services, fs
from .cli import filepicker
from . import db, calibre_db, ub, web_server, get_locale, config, updater_thread, babel, gdriveutils
from .helper import check_valid_domain, send_test_mail, reset_password, generate_password_hash
from .gdriveutils import is_gdrive_ready, gdrive_support
@ -118,7 +119,7 @@ def before_request():
g.shelves_access = ub.session.query(ub.Shelf).filter(
or_(ub.Shelf.is_public == 1, ub.Shelf.user_id == current_user.id)).order_by(ub.Shelf.name).all()
if not config.db_configured and request.endpoint not in (
'admin.basic_configuration', 'login') and '/static/' not in request.path:
'admin.basic_configuration', 'login', 'admin.config_pathchooser') and '/static/' not in request.path:
return redirect(url_for('admin.basic_configuration'))
@ -209,7 +210,7 @@ def admin():
@admin_required
def configuration():
if request.method == "POST":
return _configuration_update_helper()
return _configuration_update_helper(True)
return _configuration_result()
@ -604,10 +605,11 @@ def list_restriction(res_type):
return response
@admi.route("/basicconfig/pathchooser/")
# @unconfigured
@login_required
@unconfigured
def config_pathchooser():
return pathchooser()
if filepicker:
return pathchooser()
abort(403)
@admi.route("/ajax/pathchooser/")
@login_required
@ -616,7 +618,7 @@ def ajax_pathchooser():
return pathchooser()
def pathchooser():
browse_for = "folder" # if request.endpoint == "admin.pathchooser" else "file"
browse_for = "folder"
folder_only = request.args.get('folder', False) == "true"
file_filter = request.args.get('filter', "")
path = os.path.normpath(request.args.get('path', ""))
@ -702,8 +704,8 @@ def pathchooser():
def basic_configuration():
logout_user()
if request.method == "POST":
return _configuration_update_helper()
return _configuration_result()
return _configuration_update_helper(configured=filepicker)
return _configuration_result(configured=filepicker)
def _config_int(to_save, x, func=int):
@ -858,7 +860,7 @@ def _configuration_ldap_helper(to_save, gdriveError):
return reboot_required, None
def _configuration_update_helper():
def _configuration_update_helper(configured):
reboot_required = False
db_change = False
to_save = request.form.to_dict()
@ -878,11 +880,15 @@ def _configuration_update_helper():
reboot_required |= _config_string(to_save, "config_keyfile")
if config.config_keyfile and not os.path.isfile(config.config_keyfile):
return _configuration_result(_('Keyfile Location is not Valid, Please Enter Correct Path'), gdriveError)
return _configuration_result(_('Keyfile Location is not Valid, Please Enter Correct Path'),
gdriveError,
configured)
reboot_required |= _config_string(to_save, "config_certfile")
if config.config_certfile and not os.path.isfile(config.config_certfile):
return _configuration_result(_('Certfile Location is not Valid, Please Enter Correct Path'), gdriveError)
return _configuration_result(_('Certfile Location is not Valid, Please Enter Correct Path'),
gdriveError,
configured)
_config_checkbox_int(to_save, "config_uploading")
# Reboot on config_anonbrowse with enabled ldap, as decoraters are changed in this case
@ -947,10 +953,10 @@ def _configuration_update_helper():
if "config_rarfile_location" in to_save:
unrar_status = helper.check_unrar(config.config_rarfile_location)
if unrar_status:
return _configuration_result(unrar_status, gdriveError)
return _configuration_result(unrar_status, gdriveError, configured)
except (OperationalError, InvalidRequestError):
ub.session.rollback()
_configuration_result(_(u"Settings DB is not Writeable"), gdriveError)
_configuration_result(_(u"Settings DB is not Writeable"), gdriveError, configured)
try:
metadata_db = os.path.join(config.config_calibre_dir, "metadata.db")
@ -958,11 +964,13 @@ def _configuration_update_helper():
gdriveutils.downloadFile(None, "metadata.db", metadata_db)
db_change = True
except Exception as e:
return _configuration_result('%s' % e, gdriveError)
return _configuration_result('%s' % e, gdriveError, configured)
if db_change:
if not calibre_db.setup_db(config, ub.app_DB_path):
return _configuration_result(_('DB Location is not Valid, Please Enter Correct Path'), gdriveError)
return _configuration_result(_('DB Location is not Valid, Please Enter Correct Path'),
gdriveError,
configured)
if not os.access(os.path.join(config.config_calibre_dir, "metadata.db"), os.W_OK):
flash(_(u"DB is not Writeable"), category="warning")
@ -971,10 +979,10 @@ def _configuration_update_helper():
if reboot_required:
web_server.stop(True)
return _configuration_result(None, gdriveError)
return _configuration_result(None, gdriveError, configured)
def _configuration_result(error_flash=None, gdriveError=None):
def _configuration_result(error_flash=None, gdriveError=None, configured=True):
gdrive_authenticate = not is_gdrive_ready()
gdrivefolders = []
if gdriveError is None:
@ -995,7 +1003,7 @@ def _configuration_result(error_flash=None, gdriveError=None):
return render_title_template("config_edit.html", config=config, provider=oauthblueprints,
show_back_button=show_back_button, show_login_button=show_login_button,
show_authenticate_google_drive=gdrive_authenticate,
show_authenticate_google_drive=gdrive_authenticate, filepicker=configured,
gdriveError=gdriveError, gdrivefolders=gdrivefolders, feature_support=feature_support,
title=_(u"Basic Configuration"), page="config")

View File

@ -45,6 +45,7 @@ parser.add_argument('-v', '--version', action='version', help='Shows version num
version=version_info())
parser.add_argument('-i', metavar='ip-address', help='Server IP-Address to listen')
parser.add_argument('-s', metavar='user:pass', help='Sets specific username to new password')
parser.add_argument('-f', action='store_true', help='Enables filepicker in unconfigured mode')
args = parser.parse_args()
if sys.version_info < (3, 0):
@ -110,3 +111,6 @@ if ipadress:
# handle and check user password argument
user_password = args.s or None
# Handles enableing of filepicker
filepicker = args.f or None

View File

@ -445,6 +445,8 @@ class CalibreDB():
cls.config = config
cls.dispose()
# toDo: if db changed -> delete shelfs, delete download books, delete read boks, kobo sync??
if not config.config_calibre_dir:
config.invalidate()
return False

View File

@ -20,11 +20,14 @@ from flask import render_template
from flask_babel import gettext as _
from flask import g
from werkzeug.local import LocalProxy
from flask_login import current_user
from . import config, constants
from . import config, constants, ub, logger, db, calibre_db
from .ub import User
log = logger.create()
def get_sidebar_config(kwargs=None):
kwargs = kwargs or []
if 'content' in kwargs:
@ -91,9 +94,23 @@ def get_sidebar_config(kwargs=None):
return sidebar
def get_readbooks_ids():
if not config.config_read_column:
readBooks = ub.session.query(ub.ReadBook).filter(ub.ReadBook.user_id == int(current_user.id))\
.filter(ub.ReadBook.read_status == ub.ReadBook.STATUS_FINISHED).all()
return frozenset([x.book_id for x in readBooks])
else:
try:
readBooks = calibre_db.session.query(db.cc_classes[config.config_read_column])\
.filter(db.cc_classes[config.config_read_column].value == True).all()
return frozenset([x.book for x in readBooks])
except KeyError:
log.error("Custom Column No.%d is not existing in calibre database", config.config_read_column)
return []
# Returns the template for rendering and includes the instance name
def render_title_template(*args, **kwargs):
sidebar = get_sidebar_config(kwargs)
return render_template(instance=config.config_calibre_web_title, sidebar=sidebar,
accept=constants.EXTENSIONS_UPLOAD,
accept=constants.EXTENSIONS_UPLOAD, read_book_ids=get_readbooks_ids(),
*args, **kwargs)

View File

@ -381,27 +381,53 @@ def order_shelf(shelf_id):
title=_(u"Change order of Shelf: '%(name)s'", name=shelf.name),
shelf=shelf, page="shelforder")
def change_shelf_order(shelf_id, order):
result = calibre_db.session.query(db.Books).join(ub.BookShelf,ub.BookShelf.book_id == db.Books.id)\
.filter(ub.BookShelf.shelf == shelf_id).order_by(*order).all()
for index, entry in enumerate(result):
book = ub.session.query(ub.BookShelf).filter(ub.BookShelf.shelf == shelf_id) \
.filter(ub.BookShelf.book_id == entry.id).first()
book.order = index
try:
ub.session.commit()
except OperationalError:
ub.session.rollback()
def render_show_shelf(shelf_type, shelf_id, page_no, sort_param):
shelf = ub.session.query(ub.Shelf).filter(ub.Shelf.id == shelf_id).first()
# check user is allowed to access shelf
if shelf and check_shelf_view_permissions(shelf):
if shelf_type == 1:
# order = [ub.BookShelf.order.asc()]
if sort_param == 'pubnew':
change_shelf_order(shelf_id, [db.Books.pubdate.desc()])
if sort_param == 'pubold':
change_shelf_order(shelf_id, [db.Books.pubdate])
if sort_param == 'abc':
change_shelf_order(shelf_id, [db.Books.sort])
if sort_param == 'zyx':
change_shelf_order(shelf_id, [db.Books.sort.desc()])
if sort_param == 'new':
change_shelf_order(shelf_id, [db.Books.timestamp.desc()])
if sort_param == 'old':
change_shelf_order(shelf_id, [db.Books.timestamp])
if sort_param == 'authaz':
change_shelf_order(shelf_id, [db.Books.author_sort.asc()])
if sort_param == 'authza':
change_shelf_order(shelf_id, [db.Books.author_sort.desc()])
page = "shelf.html"
pagesize = 0
order = [ub.BookShelf.order.asc()]
else:
pagesize = sys.maxsize
page = 'shelfdown.html'
order = [ub.BookShelf.order.asc()]
result, __, pagination = calibre_db.fill_indexpage(page_no, pagesize,
db.Books,
ub.BookShelf.shelf == shelf_id,
order,
[ub.BookShelf.order.asc()],
ub.BookShelf,ub.BookShelf.book_id == db.Books.id)
# delete chelf entries where book is not existent anymore, can happen if book is deleted outside calibre-web
wrong_entries = calibre_db.session.query(ub.BookShelf)\
.join(db.Books, ub.BookShelf.book_id == db.Books.id, isouter=True)\

View File

@ -1,11 +1,11 @@
body.serieslist.grid-view div.container-fluid>div>div.col-sm-10:before{
display: none;
}
.cover .badge{
position: absolute;
top: 0;
left: 0;
color: #fff;
background-color: #cc7b19;
border-radius: 0;
padding: 0 8px;
@ -15,3 +15,8 @@ body.serieslist.grid-view div.container-fluid>div>div.col-sm-10:before{
.cover{
box-shadow: 0 0 4px rgba(0,0,0,.6);
}
.cover .read{
padding: 0 0px;
line-height: 15px;
}

File diff suppressed because one or more lines are too long

View File

@ -116,6 +116,7 @@ a, .danger,.book-remove, .editable-empty, .editable-empty:hover { color: #45b29d
display: block;
max-width: 100%;
height: auto;
max-height: 100%;
}
.container-fluid .discover{ margin-bottom: 50px; }
@ -132,12 +133,19 @@ a, .danger,.book-remove, .editable-empty, .editable-empty:hover { color: #45b29d
position: relative;
}
.container-fluid .book .cover img {
.container-fluid .book .cover span.img {
bottom: 0;
height: 100%;
position: absolute;
}
.container-fluid .book .cover span img {
position: relative;
top: 0;
left: 0;
height: 100%;
border: 1px solid #fff;
box-sizing: border-box;
height: 100%;
bottom: 0;
position: absolute;
-webkit-box-shadow: 0 5px 8px -6px #777;
-moz-box-shadow: 0 5px 8px -6px #777;
box-shadow: 0 5px 8px -6px #777;
@ -206,11 +214,22 @@ span.glyphicon.glyphicon-tags {
.navbar-default .navbar-toggle .icon-bar {background-color: #000; }
.navbar-default .navbar-toggle {border-color: #000; }
.cover { margin-bottom: 10px; }
.cover .badge{
position: absolute;
top: 2px;
left: 2px;
background-color: #777;
color: #000;
border-radius: 10px;
background-color: #fff;
}
.cover .read{
left: auto;
right: 2px;
width: 17px;
height: 17px;
display: inline-block;
padding: 2px;
}
.cover-height { max-height: 100px;}

View File

@ -249,18 +249,26 @@ promisePublishers.done(function() {
);
});
$("#search").on("change input.typeahead:selected", function() {
$("#search").on("change input.typeahead:selected", function(event) {
if (event.target.type == "search" && event.target.tagName == "INPUT") {
return;
}
var form = $("form").serialize();
$.getJSON( getPath() + "/get_matching_tags", form, function( data ) {
$(".tags_click").each(function() {
if ($.inArray(parseInt($(this).children("input").first().val(), 10), data.tags) === -1 ) {
if (!($(this).hasClass("active"))) {
$(this).addClass("disabled");
if ($.inArray(parseInt($(this).val(), 10), data.tags) === -1) {
if(!$(this).prop("selected")) {
$(this).prop("disabled", true);
}
} else {
$(this).removeClass("disabled");
$(this).prop("disabled", false);
}
});
$("#include_tag option:selected").each(function () {
$("#exclude_tag").find("[value="+$(this).val()+"]").prop("disabled", true);
});
$('#include_tag').selectpicker("refresh");
$('#exclude_tag').selectpicker("refresh");
});
});

File diff suppressed because one or more lines are too long

View File

@ -0,0 +1,8 @@
/*!
* Bootstrap-select v1.13.14 (https://developer.snapappointments.com/bootstrap-select)
*
* Copyright 2012-2020 SnapAppointments, LLC
* Licensed under MIT (https://github.com/snapappointments/bootstrap-select/blob/master/LICENSE)
*/
!function(e,n){void 0===e&&void 0!==window&&(e=window),"function"==typeof define&&define.amd?define(["jquery"],function(e){return n(e)}):"object"==typeof module&&module.exports?module.exports=n(require("jquery")):n(e.jQuery)}(this,function(e){e.fn.selectpicker.defaults={noneSelectedText:"Vyberte ze seznamu",noneResultsText:"Pro hled\xe1n\xed {0} nebyly nalezeny \u017e\xe1dn\xe9 v\xfdsledky",countSelectedText:"Vybran\xe9 {0} z {1}",maxOptionsText:["Limit p\u0159ekro\u010den ({n} {var} max)","Limit skupiny p\u0159ekro\u010den ({n} {var} max)",["polo\u017eek","polo\u017eka"]],multipleSeparator:", ",selectAllText:"Vybrat v\u0161e",deselectAllText:"Zru\u0161it v\xfdb\u011br"}});

View File

@ -0,0 +1,8 @@
/*!
* Bootstrap-select v1.13.14 (https://developer.snapappointments.com/bootstrap-select)
*
* Copyright 2012-2020 SnapAppointments, LLC
* Licensed under MIT (https://github.com/snapappointments/bootstrap-select/blob/master/LICENSE)
*/
!function(e,t){void 0===e&&void 0!==window&&(e=window),"function"==typeof define&&define.amd?define(["jquery"],function(e){return t(e)}):"object"==typeof module&&module.exports?module.exports=t(require("jquery")):t(e.jQuery)}(this,function(e){e.fn.selectpicker.defaults={noneSelectedText:"Bitte w\xe4hlen...",noneResultsText:"Keine Ergebnisse f\xfcr {0}",countSelectedText:function(e,t){return 1==e?"{0} Element ausgew\xe4hlt":"{0} Elemente ausgew\xe4hlt"},maxOptionsText:function(e,t){return[1==e?"Limit erreicht ({n} Element max.)":"Limit erreicht ({n} Elemente max.)",1==t?"Gruppen-Limit erreicht ({n} Element max.)":"Gruppen-Limit erreicht ({n} Elemente max.)"]},selectAllText:"Alles ausw\xe4hlen",deselectAllText:"Nichts ausw\xe4hlen",multipleSeparator:", "}});

View File

@ -0,0 +1,8 @@
/*!
* Bootstrap-select v1.13.14 (https://developer.snapappointments.com/bootstrap-select)
*
* Copyright 2012-2020 SnapAppointments, LLC
* Licensed under MIT (https://github.com/snapappointments/bootstrap-select/blob/master/LICENSE)
*/
!function(e,o){void 0===e&&void 0!==window&&(e=window),"function"==typeof define&&define.amd?define(["jquery"],function(e){return o(e)}):"object"==typeof module&&module.exports?module.exports=o(require("jquery")):o(e.jQuery)}(this,function(e){e.fn.selectpicker.defaults={noneSelectedText:"No hay selecci\xf3n",noneResultsText:"No hay resultados {0}",countSelectedText:"Seleccionados {0} de {1}",maxOptionsText:["L\xedmite alcanzado ({n} {var} max)","L\xedmite del grupo alcanzado({n} {var} max)",["elementos","element"]],multipleSeparator:", ",selectAllText:"Seleccionar Todos",deselectAllText:"Desmarcar Todos"}});

View File

@ -0,0 +1,8 @@
/*!
* Bootstrap-select v1.13.14 (https://developer.snapappointments.com/bootstrap-select)
*
* Copyright 2012-2020 SnapAppointments, LLC
* Licensed under MIT (https://github.com/snapappointments/bootstrap-select/blob/master/LICENSE)
*/
!function(e,t){void 0===e&&void 0!==window&&(e=window),"function"==typeof define&&define.amd?define(["jquery"],function(e){return t(e)}):"object"==typeof module&&module.exports?module.exports=t(require("jquery")):t(e.jQuery)}(this,function(e){e.fn.selectpicker.defaults={noneSelectedText:"Ei valintoja",noneResultsText:"Ei hakutuloksia {0}",countSelectedText:function(e,t){return 1==e?"{0} valittu":"{0} valitut"},maxOptionsText:function(e,t){return["Valintojen maksimim\xe4\xe4r\xe4 ({n} saavutettu)","Ryhm\xe4n maksimim\xe4\xe4r\xe4 ({n} saavutettu)"]},selectAllText:"Valitse kaikki",deselectAllText:"Poista kaikki",multipleSeparator:", "}});

View File

@ -0,0 +1,8 @@
/*!
* Bootstrap-select v1.13.14 (https://developer.snapappointments.com/bootstrap-select)
*
* Copyright 2012-2020 SnapAppointments, LLC
* Licensed under MIT (https://github.com/snapappointments/bootstrap-select/blob/master/LICENSE)
*/
!function(e,t){void 0===e&&void 0!==window&&(e=window),"function"==typeof define&&define.amd?define(["jquery"],function(e){return t(e)}):"object"==typeof module&&module.exports?module.exports=t(require("jquery")):t(e.jQuery)}(this,function(e){e.fn.selectpicker.defaults={noneSelectedText:"Aucune s\xe9lection",noneResultsText:"Aucun r\xe9sultat pour {0}",countSelectedText:function(e,t){return 1<e?"{0} \xe9l\xe9ments s\xe9lectionn\xe9s":"{0} \xe9l\xe9ment s\xe9lectionn\xe9"},maxOptionsText:function(e,t){return[1<e?"Limite atteinte ({n} \xe9l\xe9ments max)":"Limite atteinte ({n} \xe9l\xe9ment max)",1<t?"Limite du groupe atteinte ({n} \xe9l\xe9ments max)":"Limite du groupe atteinte ({n} \xe9l\xe9ment max)"]},multipleSeparator:", ",selectAllText:"Tout s\xe9lectionner",deselectAllText:"Tout d\xe9s\xe9lectionner"}});

View File

@ -0,0 +1,8 @@
/*!
* Bootstrap-select v1.13.14 (https://developer.snapappointments.com/bootstrap-select)
*
* Copyright 2012-2020 SnapAppointments, LLC
* Licensed under MIT (https://github.com/snapappointments/bootstrap-select/blob/master/LICENSE)
*/
!function(e,t){void 0===e&&void 0!==window&&(e=window),"function"==typeof define&&define.amd?define(["jquery"],function(e){return t(e)}):"object"==typeof module&&module.exports?module.exports=t(require("jquery")):t(e.jQuery)}(this,function(e){e.fn.selectpicker.defaults={noneSelectedText:"V\xe1lasszon!",noneResultsText:"Nincs tal\xe1lat {0}",countSelectedText:function(e,t){return"{0} elem kiv\xe1lasztva"},maxOptionsText:function(e,t){return["Legfeljebb {n} elem v\xe1laszthat\xf3","A csoportban legfeljebb {n} elem v\xe1laszthat\xf3"]},selectAllText:"Mind",deselectAllText:"Egyik sem",multipleSeparator:", "}});

View File

@ -0,0 +1,8 @@
/*!
* Bootstrap-select v1.13.14 (https://developer.snapappointments.com/bootstrap-select)
*
* Copyright 2012-2020 SnapAppointments, LLC
* Licensed under MIT (https://github.com/snapappointments/bootstrap-select/blob/master/LICENSE)
*/
!function(e,t){void 0===e&&void 0!==window&&(e=window),"function"==typeof define&&define.amd?define(["jquery"],function(e){return t(e)}):"object"==typeof module&&module.exports?module.exports=t(require("jquery")):t(e.jQuery)}(this,function(e){e.fn.selectpicker.defaults={noneSelectedText:"Nessuna selezione",noneResultsText:"Nessun risultato per {0}",countSelectedText:function(e,t){return 1==e?"Selezionato {0} di {1}":"Selezionati {0} di {1}"},maxOptionsText:["Limite raggiunto ({n} {var} max)","Limite del gruppo raggiunto ({n} {var} max)",["elementi","elemento"]],multipleSeparator:", ",selectAllText:"Seleziona Tutto",deselectAllText:"Deseleziona Tutto"}});

View File

@ -0,0 +1,8 @@
/*!
* Bootstrap-select v1.13.14 (https://developer.snapappointments.com/bootstrap-select)
*
* Copyright 2012-2020 SnapAppointments, LLC
* Licensed under MIT (https://github.com/snapappointments/bootstrap-select/blob/master/LICENSE)
*/
!function(e,t){void 0===e&&void 0!==window&&(e=window),"function"==typeof define&&define.amd?define(["jquery"],function(e){return t(e)}):"object"==typeof module&&module.exports?module.exports=t(require("jquery")):t(e.jQuery)}(this,function(e){e.fn.selectpicker.defaults={noneSelectedText:"\u9078\u629e\u3055\u308c\u3066\u3044\u307e\u305b\u3093",noneResultsText:"'{0}'\u306f\u898b\u3064\u304b\u308a\u307e\u305b\u3093",countSelectedText:"{0}/{1} \u9078\u629e\u4e2d",maxOptionsText:["\u9078\u629e\u4e0a\u9650\u6570\u3092\u8d85\u3048\u3066\u3044\u307e\u3059(\u6700\u5927{n}{var})","\u30b0\u30eb\u30fc\u30d7\u306e\u9078\u629e\u4e0a\u9650\u6570\u3092\u8d85\u3048\u3066\u3044\u307e\u3059(\u6700\u5927{n}{var})",["\u30a2\u30a4\u30c6\u30e0","\u30a2\u30a4\u30c6\u30e0"]],selectAllText:"\u5168\u3066\u9078\u629e",deselectAllText:"\u9078\u629e\u3092\u30af\u30ea\u30a2",multipleSeparator:", "}});

View File

@ -0,0 +1,8 @@
/*!
* Bootstrap-select v1.13.14 (https://developer.snapappointments.com/bootstrap-select)
*
* Copyright 2012-2020 SnapAppointments, LLC
* Licensed under MIT (https://github.com/snapappointments/bootstrap-select/blob/master/LICENSE)
*/
!function(e,t){void 0===e&&void 0!==window&&(e=window),"function"==typeof define&&define.amd?define(["jquery"],function(e){return t(e)}):"object"==typeof module&&module.exports?module.exports=t(require("jquery")):t(e.jQuery)}(this,function(e){e.fn.selectpicker.defaults={noneSelectedText:"\u1798\u17b7\u1793\u1798\u17b6\u1793\u17a2\u17d2\u179c\u17b8\u1794\u17b6\u1793\u1787\u17d2\u179a\u17be\u179f\u179a\u17be\u179f",noneResultsText:"\u1798\u17b7\u1793\u1798\u17b6\u1793\u179b\u1791\u17d2\u1792\u1795\u179b {0}",countSelectedText:function(e,t){return"{0} \u1792\u17b6\u178f\u17bb\u178a\u17c2\u179b\u1794\u17b6\u1793\u1787\u17d2\u179a\u17be\u179f"},maxOptionsText:function(e,t){return[1==e?"\u1788\u17b6\u1793\u178a\u179b\u17cb\u178a\u17c2\u1793\u1780\u17c6\u178e\u178f\u17cb ( {n} \u1792\u17b6\u178f\u17bb\u17a2\u178f\u17b7\u1794\u179a\u1798\u17b6)":"\u17a2\u178f\u17b7\u1794\u179a\u1798\u17b6\u1788\u17b6\u1793\u178a\u179b\u17cb\u178a\u17c2\u1793\u1780\u17c6\u178e\u178f\u17cb ( {n} \u1792\u17b6\u178f\u17bb)",1==t?"\u178a\u17c2\u1793\u1780\u17c6\u178e\u178f\u17cb\u1780\u17d2\u179a\u17bb\u1798\u1788\u17b6\u1793\u178a\u179b\u17cb ( {n} \u17a2\u178f\u17b7\u1794\u179a\u1798\u17b6\u1792\u17b6\u178f\u17bb)":"\u17a2\u178f\u17b7\u1794\u179a\u1798\u17b6\u1780\u17d2\u179a\u17bb\u1798\u1788\u17b6\u1793\u178a\u179b\u17cb\u178a\u17c2\u1793\u1780\u17c6\u178e\u178f\u17cb ( {n} \u1792\u17b6\u178f\u17bb)"]},selectAllText:"\u1787\u17d2\u179a\u17be\u179f\u200b\u1799\u1780\u200b\u1791\u17b6\u17c6\u1784\u17a2\u179f\u17cb",deselectAllText:"\u1798\u17b7\u1793\u1787\u17d2\u179a\u17be\u179f\u200b\u1799\u1780\u200b\u1791\u17b6\u17c6\u1784\u17a2\u179f",multipleSeparator:", "}});

View File

@ -0,0 +1,8 @@
/*!
* Bootstrap-select v1.13.14 (https://developer.snapappointments.com/bootstrap-select)
*
* Copyright 2012-2020 SnapAppointments, LLC
* Licensed under MIT (https://github.com/snapappointments/bootstrap-select/blob/master/LICENSE)
*/
!function(e,t){void 0===e&&void 0!==window&&(e=window),"function"==typeof define&&define.amd?define(["jquery"],function(e){return t(e)}):"object"==typeof module&&module.exports?module.exports=t(require("jquery")):t(e.jQuery)}(this,function(e){e.fn.selectpicker.defaults={noneSelectedText:"Niets geselecteerd",noneResultsText:"Geen resultaten gevonden voor {0}",countSelectedText:"{0} van {1} geselecteerd",maxOptionsText:["Limiet bereikt ({n} {var} max)","Groep limiet bereikt ({n} {var} max)",["items","item"]],selectAllText:"Alles selecteren",deselectAllText:"Alles deselecteren",multipleSeparator:", "}});

View File

@ -0,0 +1,8 @@
/*!
* Bootstrap-select v1.13.14 (https://developer.snapappointments.com/bootstrap-select)
*
* Copyright 2012-2020 SnapAppointments, LLC
* Licensed under MIT (https://github.com/snapappointments/bootstrap-select/blob/master/LICENSE)
*/
!function(e,n){void 0===e&&void 0!==window&&(e=window),"function"==typeof define&&define.amd?define(["jquery"],function(e){return n(e)}):"object"==typeof module&&module.exports?module.exports=n(require("jquery")):n(e.jQuery)}(this,function(e){e.fn.selectpicker.defaults={noneSelectedText:"Nic nie zaznaczono",noneResultsText:"Brak wynik\xf3w wyszukiwania {0}",countSelectedText:"Zaznaczono {0} z {1}",maxOptionsText:["Osi\u0105gni\u0119to limit ({n} {var} max)","Limit grupy osi\u0105gni\u0119ty ({n} {var} max)",["elementy","element"]],selectAllText:"Zaznacz wszystkie",deselectAllText:"Odznacz wszystkie",multipleSeparator:", "}});

View File

@ -0,0 +1,8 @@
/*!
* Bootstrap-select v1.13.14 (https://developer.snapappointments.com/bootstrap-select)
*
* Copyright 2012-2020 SnapAppointments, LLC
* Licensed under MIT (https://github.com/snapappointments/bootstrap-select/blob/master/LICENSE)
*/
!function(e,t){void 0===e&&void 0!==window&&(e=window),"function"==typeof define&&define.amd?define(["jquery"],function(e){return t(e)}):"object"==typeof module&&module.exports?module.exports=t(require("jquery")):t(e.jQuery)}(this,function(e){e.fn.selectpicker.defaults={noneSelectedText:"\u041d\u0438\u0447\u0435\u0433\u043e \u043d\u0435 \u0432\u044b\u0431\u0440\u0430\u043d\u043e",noneResultsText:"\u0421\u043e\u0432\u043f\u0430\u0434\u0435\u043d\u0438\u0439 \u043d\u0435 \u043d\u0430\u0439\u0434\u0435\u043d\u043e {0}",countSelectedText:"\u0412\u044b\u0431\u0440\u0430\u043d\u043e {0} \u0438\u0437 {1}",maxOptionsText:["\u0414\u043e\u0441\u0442\u0438\u0433\u043d\u0443\u0442 \u043f\u0440\u0435\u0434\u0435\u043b ({n} {var} \u043c\u0430\u043a\u0441\u0438\u043c\u0443\u043c)","\u0414\u043e\u0441\u0442\u0438\u0433\u043d\u0443\u0442 \u043f\u0440\u0435\u0434\u0435\u043b \u0432 \u0433\u0440\u0443\u043f\u043f\u0435 ({n} {var} \u043c\u0430\u043a\u0441\u0438\u043c\u0443\u043c)",["\u0448\u0442.","\u0448\u0442."]],doneButtonText:"\u0417\u0430\u043a\u0440\u044b\u0442\u044c",selectAllText:"\u0412\u044b\u0431\u0440\u0430\u0442\u044c \u0432\u0441\u0435",deselectAllText:"\u041e\u0442\u043c\u0435\u043d\u0438\u0442\u044c \u0432\u0441\u0435",multipleSeparator:", "}});

View File

@ -0,0 +1,8 @@
/*!
* Bootstrap-select v1.13.14 (https://developer.snapappointments.com/bootstrap-select)
*
* Copyright 2012-2020 SnapAppointments, LLC
* Licensed under MIT (https://github.com/snapappointments/bootstrap-select/blob/master/LICENSE)
*/
!function(e,t){void 0===e&&void 0!==window&&(e=window),"function"==typeof define&&define.amd?define(["jquery"],function(e){return t(e)}):"object"==typeof module&&module.exports?module.exports=t(require("jquery")):t(e.jQuery)}(this,function(e){e.fn.selectpicker.defaults={noneSelectedText:"Inget valt",noneResultsText:"Inget s\xf6kresultat matchar {0}",countSelectedText:function(e,t){return 1===e?"{0} alternativ valt":"{0} alternativ valda"},maxOptionsText:function(e,t){return["Gr\xe4ns uppn\xe5d (max {n} alternativ)","Gr\xe4ns uppn\xe5d (max {n} gruppalternativ)"]},selectAllText:"Markera alla",deselectAllText:"Avmarkera alla",multipleSeparator:", "}});

View File

@ -0,0 +1,8 @@
/*!
* Bootstrap-select v1.13.14 (https://developer.snapappointments.com/bootstrap-select)
*
* Copyright 2012-2020 SnapAppointments, LLC
* Licensed under MIT (https://github.com/snapappointments/bootstrap-select/blob/master/LICENSE)
*/
!function(e,i){void 0===e&&void 0!==window&&(e=window),"function"==typeof define&&define.amd?define(["jquery"],function(e){return i(e)}):"object"==typeof module&&module.exports?module.exports=i(require("jquery")):i(e.jQuery)}(this,function(e){e.fn.selectpicker.defaults={noneSelectedText:"Hi\xe7biri se\xe7ilmedi",noneResultsText:"Hi\xe7bir sonu\xe7 bulunamad\u0131 {0}",countSelectedText:function(e,i){return"{0} \xf6\u011fe se\xe7ildi"},maxOptionsText:function(e,i){return[1==e?"Limit a\u015f\u0131ld\u0131 (maksimum {n} say\u0131da \xf6\u011fe )":"Limit a\u015f\u0131ld\u0131 (maksimum {n} say\u0131da \xf6\u011fe)","Grup limiti a\u015f\u0131ld\u0131 (maksimum {n} say\u0131da \xf6\u011fe)"]},selectAllText:"T\xfcm\xfcn\xfc Se\xe7",deselectAllText:"Se\xe7iniz",multipleSeparator:", "}});

View File

@ -0,0 +1,8 @@
/*!
* Bootstrap-select v1.13.14 (https://developer.snapappointments.com/bootstrap-select)
*
* Copyright 2012-2020 SnapAppointments, LLC
* Licensed under MIT (https://github.com/snapappointments/bootstrap-select/blob/master/LICENSE)
*/
!function(e,t){void 0===e&&void 0!==window&&(e=window),"function"==typeof define&&define.amd?define(["jquery"],function(e){return t(e)}):"object"==typeof module&&module.exports?module.exports=t(require("jquery")):t(e.jQuery)}(this,function(e){e.fn.selectpicker.defaults={noneSelectedText:"\u6ca1\u6709\u9009\u4e2d\u4efb\u4f55\u9879",noneResultsText:"\u6ca1\u6709\u627e\u5230\u5339\u914d\u9879",countSelectedText:"\u9009\u4e2d{1}\u4e2d\u7684{0}\u9879",maxOptionsText:["\u8d85\u51fa\u9650\u5236 (\u6700\u591a\u9009\u62e9{n}\u9879)","\u7ec4\u9009\u62e9\u8d85\u51fa\u9650\u5236(\u6700\u591a\u9009\u62e9{n}\u7ec4)"],multipleSeparator:", ",selectAllText:"\u5168\u9009",deselectAllText:"\u53d6\u6d88\u5168\u9009"}});

View File

@ -509,6 +509,19 @@ $(function() {
);
});
$("#toggle_order_shelf").click(function() {
$("#new").toggleClass("disabled");
$("#old").toggleClass("disabled");
$("#asc").toggleClass("disabled");
$("#desc").toggleClass("disabled");
$("#auth_az").toggleClass("disabled");
$("#auth_za").toggleClass("disabled");
$("#pub_new").toggleClass("disabled");
$("#pub_old").toggleClass("disabled");
var alternative_text = $("#toggle_order_shelf").data('alt-text');
$("#toggle_order_shelf")[0].attributes['data-alt-text'].value = $("#toggle_order_shelf").html();
$("#toggle_order_shelf").html(alternative_text);
});
$("#btndeluser").click(function() {
ConfirmDialog(

View File

@ -36,7 +36,10 @@
<div id="books" class="col-sm-3 col-lg-2 col-xs-6 book">
<div class="cover">
<a href="{{ url_for('web.show_book', book_id=entry.id) }}">
{{ book_cover_image(entry, entry.id|get_book_thumbnails(thumbnails)) }}
<span class="img">
{ book_cover_image(entry, entry.id|get_book_thumbnails(thumbnails)) }}
{% if entry.id in read_book_ids %}<span class="badge read glyphicon glyphicon-ok"></span>{% endif %}
</span>
</a>
</div>
<div class="meta">

View File

@ -16,12 +16,19 @@
<div id="collapseOne" class="panel-collapse collapse in">
<div class="panel-body">
<label for="config_calibre_dir">{{_('Location of Calibre Database')}}</label>
<div class="form-group required input-group">
<div class="form-group required{% if filepicker %} input-group{% endif %}">
<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">
{% if filepicker %}
<span class="input-group-btn">
<button type="button" data-toggle="modal" id="converter_modal_path" data-link="config_calibre_dir" data-filefilter="metadata.db" data-target="#fileModal" id="library_path" class="btn btn-default"><span class="glyphicon glyphicon-folder-open"></span></button>
</span>
{% endif %}
</div>
{% if not filepicker %}
<div class="form-group">
<label id="filepicker-hint">{{_('To activate serverside filepicker start Calibre-Web with -f optionn')}}</label>
</div>
{% endif %}
{% if feature_support['gdrive'] %}
<div class="form-group required">
<input type="checkbox" id="config_use_google_drive" name="config_use_google_drive" data-control="gdrive_settings" {% if config.config_use_google_drive %}checked{% endif %} >

View File

@ -9,7 +9,10 @@
<div class="cover">
{% 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">
<span class="img">
{{ book_cover_image(entry, entry.id|get_book_thumbnails(thumbnails)) }}
{% if entry.id in read_book_ids %}<span class="badge read glyphicon glyphicon-ok"></span>{% endif %}
</span>
</a>
{% endif %}
</div>

View File

@ -29,8 +29,10 @@
<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">
<a href="{{url_for('web.books_list', data=data, sort_param='new', book_id=entry[0].series[0].id )}}">
{{ book_cover_image(entry[0], entry[0].id|get_book_thumbnails(thumbnails)) }}
<span class="badge">{{entry.count}}</span>
<span class="img">
{{ book_cover_image(entry[0], entry[0].id|get_book_thumbnails(thumbnails)) }}
<span class="badge">{{entry.count}}</span>
</span>
</a>
</div>
<div class="meta">

View File

@ -9,7 +9,10 @@
<div class="col-sm-3 col-lg-2 col-xs-6 book" id="books_rand">
<div class="cover">
<a href="{{ url_for('web.show_book', book_id=entry.id) }}" data-toggle="modal" data-target="#bookDetailsModal" data-remote="false">
{{ book_cover_image(entry, entry.id|get_book_thumbnails(thumbnails)) }}
<span class="img">
{{ book_cover_image(entry, entry.id|get_book_thumbnails(thumbnails)) }}
{% if entry.id in read_book_ids %}<span class="badge read glyphicon glyphicon-ok"></span>{% endif %}
</span>
</a>
</div>
<div class="meta">
@ -83,7 +86,10 @@
<div class="col-sm-3 col-lg-2 col-xs-6 book" id="books">
<div class="cover">
<a href="{{ url_for('web.show_book', book_id=entry.id) }}" data-toggle="modal" data-target="#bookDetailsModal" data-remote="false">
<span class="img">
{{ book_cover_image(entry, entry.id|get_book_thumbnails(thumbnails)) }}
{% if entry.id in read_book_ids %}<span class="badge read glyphicon glyphicon-ok"></span>{% endif %}
</span>
</a>
</div>
<div class="meta">

View File

@ -44,7 +44,10 @@
<div class="cover">
{% 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">
{{ book_cover_image(entry, entry.id|get_book_thumbnails(thumbnails)) }}
<span class="img">
{{ book_cover_image(entry, entry.id|get_book_thumbnails(thumbnails)) }}
{% if entry.id in read_book_ids %}<span class="badge read glyphicon glyphicon-ok"></span>{% endif %}
</span>
</a>
{% endif %}
</div>

View File

@ -31,87 +31,87 @@
</div>
</div>
</div>
<label for="include_tag">{{_('Tags')}}</label>
<div class="form-group" id="tag">
<div class="btn-toolbar btn-toolbar-lg" data-toggle="buttons">
{% for tag in tags %}
<label id="tag_{{tag.id}}" class="btn btn-primary tags_click">
<input type="checkbox" autocomplete="off" name="include_tag" id="include_tag" value="{{tag.id}}">{{tag.name}}</input>
</label>
{% endfor %}
<div class="form-group">
<label for="read_status">{{_('Read Status')}}</label>
<select name="read_status" id="read_status" class="form-control">
<option value="" selected></option>
<option value="True" >{{_('Yes')}}</option>
<option value="False" >{{_('No')}}</option>
</select>
</div>
<div class="row">
<div class="form-group col-sm-6" id="tag">
<div><label for="include_tag">{{_('Tags')}}</label></div>
<select class="selectpicker" name="include_tag" id="include_tag" data-live-search="true" data-style="btn-primary" multiple>
{% for tag in tags %}
<option class="tags_click" value="{{tag.id}}">{{tag.name}}</option>
{% endfor %}
</select>
</div>
<div class="form-group col-sm-6">
<div><label for="exclude_tag">{{_('Exclude Tags')}}</label></div>
<select class="selectpicker" name="exclude_tag" id="exclude_tag" data-live-search="true" data-style="btn-danger" multiple>
{% for tag in tags %}
<option class="tags_click" value="{{tag.id}}">{{tag.name}}</option>
{% endfor %}
</select>
</div>
</div>
<label for="exclude_tag">{{_('Exclude Tags')}}</label>
<div class="form-group">
<div class="btn-toolbar btn-toolbar-lg" data-toggle="buttons">
{% for tag in tags %}
<label id="exclude_tag_{{tag.id}}" class="btn btn-danger tags_click">
<input type="checkbox" autocomplete="off" name="exclude_tag" id="exclude_tag" value="{{tag.id}}">{{tag.name}}</input>
</label>
{% endfor %}
<div class="row">
<div class="form-group col-sm-6">
<div><label for="include_serie">{{_('Series')}}</label></div>
<select class="selectpicker" name="include_serie" id="include_serie" data-live-search="true" data-style="btn-primary" multiple>
{% for serie in series %}
<option value="{{serie.id}}">{{serie.name}}</option>
{% endfor %}
</select>
</div>
</div>
<label for="include_serie">{{_('Series')}}</label>
<div class="form-group">
<div class="btn-toolbar btn-toolbar-lg" data-toggle="buttons">
{% for serie in series %}
<label id="serie_{{serie.id}}" class="btn btn-primary serie_click">
<input type="checkbox" autocomplete="off" name="include_serie" id="include_serie" value="{{serie.id}}">{{serie.name}}</input>
</label>
{% endfor %}
</div>
</div>
<label for="exclude_serie">{{_('Exclude Series')}}</label>
<div class="form-group">
<div class="btn-toolbar btn-toolbar-lg" data-toggle="buttons">
{% for serie in series %}
<label id="exclude_serie_{{serie.id}}" class="btn btn-danger serie_click">
<input type="checkbox" autocomplete="off" name="exclude_serie" id="exclude_serie" value="{{serie.id}}">{{serie.name}}</input>
</label>
{% endfor %}
<div class="form-group col-sm-6">
<div><label for="exclude_serie">{{_('Exclude Series')}}</label></div>
<select class="selectpicker" name="exclude_serie" id="exclude_serie" data-live-search="true" data-style="btn-danger" multiple>
{% for serie in series %}
<option value="{{serie.id}}">{{serie.name}}</option>
{% endfor %}
</select>
</div>
</div>
{% if languages %}
<label for="include_language">{{_('Languages')}}</label>
<div class="form-group">
<div class="btn-toolbar btn-toolbar-lg" data-toggle="buttons">
<div class="row">
<div class="form-group col-sm-6">
<div><label for="include_language">{{_('Languages')}}</label></div>
<select class="selectpicker" name="include_language" id="include_language" data-live-search="true" data-style="btn-primary" multiple>
{% for language in languages %}
<label id="language_{{language.id}}" class="btn btn-primary serie_click">
<input type="checkbox" autocomplete="off" name="include_language" id="include_language" value="{{language.id}}">{{language.name}}</input>
</label>
<option value="{{language.id}}">{{language.name}}</option>
{% endfor %}
</div>
</select>
</div>
<label for="exclude_language">{{_('Exclude Languages')}}</label>
<div class="form-group">
<div class="btn-toolbar btn-toolbar-lg" data-toggle="buttons">
<div class="form-group col-sm-6">
<div><label for="exclude_language">{{_('Exclude Languages')}}</label></div>
<select class="selectpicker" name="exclude_language" id="exclude_language" data-live-search="true" data-style="btn-danger" multiple>
{% for language in languages %}
<label id="exclude_language_{{language.id}}" class="btn btn-danger language_click">
<input type="checkbox" autocomplete="off" name="exclude_language" id="exclude_language" value="{{language.id}}">{{language.name}}</input>
</label>
<option value="{{language.id}}">{{language.name}}</option>
{% endfor %}
</div>
</div>
{% endif%}
<label for="include_extension">{{_('Extensions')}}</label>
<div class="form-group" id="extension">
<div class="btn-toolbar btn-toolbar-lg" data-toggle="buttons">
{% for extension in extensions %}
<label id="extension_{{extension.format}}" class="btn btn-primary extension_click">
<input type="checkbox" autocomplete="off" name="include_extension" id="include_extension" value="{{extension.format}}">{{extension.format}}</input>
</label>
{% endfor %}
</select>
</div>
</div>
<label for="exclude_extension">{{_('Exclude Extensions')}}</label>
<div class="form-group">
<div class="btn-toolbar btn-toolbar-lg" data-toggle="buttons">
{% endif%}
<div class="row">
<div class="form-group col-sm-6">
<div><label for="include_extension">{{_('Extensions')}}</label></div>
<select id="include_extension" class="selectpicker" name="include_extension" id="include_extension" data-live-search="true" data-style="btn-primary" multiple>
{% for extension in extensions %}
<label id="exclude_extension_{{extension.format}}" class="btn btn-danger extension_click">
<input type="checkbox" autocomplete="off" name="exclude_extension" id="exclude_extension" value="{{extension.format}}">{{extension.format}}</input>
</label>
{% endfor %}
</div>
<option value="{{extension.format}}">{{extension.format}}</option>
{% endfor %}
</select>
</div>
<div class="form-group col-sm-6">
<div><label for="exclude_extension">{{_('Exclude Extensions')}}</label></div>
<select id="exclude_extension" class="selectpicker" name="exclude_extension" id="exclude_extension" data-live-search="true" data-style="btn-danger" multiple>
{% for extension in extensions %}
<option value="{{extension.format}}">{{extension.format}}</option>
{% endfor %}
</select>
</div>
</div>
<div class="row">
<div class="form-group col-sm-6">
@ -189,10 +189,13 @@
<script src="{{ url_for('static', filename='js/libs/bootstrap-rating-input.min.js') }}"></script>
<script src="{{ url_for('static', filename='js/libs/typeahead.bundle.js') }}"></script>
<script src="{{ url_for('static', filename='js/edit_books.js') }}"></script>
<script>
</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 %}
{% endblock %}
{% block header %}
<link href="{{ url_for('static', filename='css/libs/typeahead.css') }}" rel="stylesheet" media="screen">
<link href="{{ url_for('static', filename='css/libs/bootstrap-datepicker3.min.css') }}" rel="stylesheet" media="screen">
<link href="{{ url_for('static', filename='css/libs/bootstrap-select.min.css') }}" rel="stylesheet" >
{% endblock %}

View File

@ -9,8 +9,10 @@
{% if g.user.is_authenticated %}
{% if (g.user.role_edit_shelfs() and shelf.is_public ) or not shelf.is_public %}
<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') }} </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__() %}
<a id="order_shelf" href="{{ url_for('shelf.order_shelf', shelf_id=shelf.id) }}" class="btn btn-primary">{{ _('Arrange books manually') }} </a>
<button id="toggle_order_shelf" type="button" data-alt-text="{{ _('Disable Change order') }}" class="btn btn-primary">{{ _('Enable Change order') }}</button>
<div class="filterheader hidden-xs hidden-sm">
<a data-toggle="tooltip" title="{{_('Sort according to book date, newest first')}}" id="new" class="btn btn-primary disabled" href="{{url_for('shelf.show_shelf', shelf_id=shelf.id, sort_param='new')}}"><span class="glyphicon glyphicon-book"></span> <span class="glyphicon glyphicon-calendar"></span><span class="glyphicon glyphicon-sort-by-order"></span></a>
<a data-toggle="tooltip" title="{{_('Sort according to book date, oldest first')}}" id="old" class="btn btn-primary disabled" href="{{url_for('shelf.show_shelf', shelf_id=shelf.id, sort_param='old')}}"><span class="glyphicon glyphicon-book"></span> <span class="glyphicon glyphicon-calendar"></span><span class="glyphicon glyphicon-sort-by-order-alt"></span></a>
@ -20,8 +22,6 @@
<a data-toggle="tooltip" title="{{_('Sort authors in reverse alphabetical order')}}" id="auth_za" class="btn btn-primary disabled" href="{{url_for('shelf.show_shelf', shelf_id=shelf.id, sort_param='authza')}}"><span class="glyphicon glyphicon-user"></span><span class="glyphicon glyphicon-sort-by-alphabet-alt"></span></a>
<a data-toggle="tooltip" title="{{_('Sort according to publishing date, newest first')}}" id="pub_new" class="btn btn-primary disabled" href="{{url_for('shelf.show_shelf', shelf_id=shelf.id, sort_param='pubnew')}}"><span class="glyphicon glyphicon-calendar"></span><span class="glyphicon glyphicon-sort-by-order"></span></a>
<a data-toggle="tooltip" title="{{_('Sort according to publishing date, oldest first')}}" id="pub_old" class="btn btn-primary disabled" href="{{url_for('shelf.show_shelf', shelf_id=shelf.id, sort_param='pubold')}}"><span class="glyphicon glyphicon-calendar"></span><span class="glyphicon glyphicon-sort-by-order-alt"></span></a>
<a id="order_shelf" href="{{ url_for('shelf.order_shelf', shelf_id=shelf.id) }}" class="btn btn-primary disabled">{{ _('Arrange books') }} </a>
<button id="enable_order_shelf" type="button" class="btn btn-primary">{{ _('Change order') }}</button>
</div>
{% endif %}
{% endif %}
@ -31,7 +31,10 @@
<div class="col-sm-3 col-lg-2 col-xs-6 book">
<div class="cover">
<a href="{{ url_for('web.show_book', book_id=entry.id) }}" data-toggle="modal" data-target="#bookDetailsModal" data-remote="false">
<span class="img">
{{ book_cover_image(entry, entry.id|get_book_thumbnails(thumbnails)) }}
{% if entry.id in read_book_ids %}<span class="badge read glyphicon glyphicon-ok"></span>{% endif %}
</span>
</a>
</div>
<div class="meta">

View File

@ -37,7 +37,7 @@
</div>
{% endfor %}
</div>
<button onclick="sendData('{{ url_for('shelf.order_shelf', shelf_id=shelf.id) }}')" class="btn btn-default" id="ChangeOrder">{{_('Change order')}}</button>
<button onclick="sendData('{{ url_for('shelf.order_shelf', shelf_id=shelf.id) }}')" class="btn btn-default" id="ChangeOrder">{{_('Save')}}</button>
<a href="{{ url_for('shelf.show_shelf', shelf_id=shelf.id) }}" id="shelf_back" class="btn btn-default">{{_('Back')}}</a>
</div>
{% endblock %}

View File

@ -55,27 +55,14 @@
</div>
<div class="btn-group" role="group" aria-label="Download, send to Kindle, reading">
{% if g.user.role_download() %}
{% if g.user.role_download() %}
{% if entry.data|length %}
<div class="btn-group" role="group">
{% if entry.data|length < 2 %}
{% for format in entry.data %}
<a href="{{ url_for('web.download_link', book_id=entry.id, book_format=format.format|lower, anyname=entry.id|string+'.'+format.format|lower) }}" id="btnGroupDrop1{{format.format|lower}}" class="btn btn-primary" role="button">
<span class="glyphicon glyphicon-download"></span>{{format.format}} ({{ format.uncompressed_size|filesizeformat }})
</a>
{% endfor %}
{% else %}
<button id="btnGroupDrop1" type="button" class="btn btn-primary dropdown-toggle" data-toggle="dropdown" aria-haspopup="true" aria-expanded="false">
<span class="glyphicon glyphicon-download"></span> {{_('Download')}}
<span class="caret"></span>
</button>
<ul class="dropdown-menu" aria-labelledby="btnGroupDrop1">
{% for format in entry.data %}
<li><a href="{{ url_for('web.download_link', book_id=entry.id, book_format=format.format|lower, anyname=entry.id|string+'.'+format.format|lower) }}">{{format.format}} ({{ format.uncompressed_size|filesizeformat }})</a></li>
{% endfor %}
</ul>
{% endif %}
{% for format in entry.data %}
<a href="{{ url_for('web.download_link', book_id=entry.id, book_format=format.format|lower, anyname=entry.id|string+'.'+format.format|lower) }}" id="btnGroupDrop{{entry.id}}{{format.format|lower}}" class="btn btn-primary" role="button">
<span class="glyphicon glyphicon-download"></span>{{format.format}} ({{ format.uncompressed_size|filesizeformat }})
</a>
{% endfor %}
</div>
{% endif %}
{% endif %}

View File

@ -476,7 +476,7 @@ def migrate_Database(session):
if not engine.dialect.has_table(engine.connect(), "thumbnail"):
Thumbnail.__table__.create(bind=engine)
if not engine.dialect.has_table(engine.connect(), "registration"):
ReadBook.__table__.create(bind=engine)
Registration.__table__.create(bind=engine)
with engine.connect() as conn:
conn.execute("insert into registration (domain, allow) values('%.%',1)")
session.commit()
@ -525,12 +525,16 @@ def migrate_Database(session):
for book_shelf in session.query(BookShelf).all():
book_shelf.date_added = datetime.datetime.now()
session.commit()
# Handle table exists, but no content
cnt = session.query(Registration).count()
if not cnt:
with engine.connect() as conn:
conn.execute("insert into registration (domain, allow) values('%.%',1)")
session.commit()
try:
# Handle table exists, but no content
cnt = session.query(Registration).count()
if not cnt:
with engine.connect() as conn:
conn.execute("insert into registration (domain, allow) values('%.%',1)")
session.commit()
except exc.OperationalError: # Database is not writeable
print('Settings database is not writeable. Exiting...')
sys.exit(2)
try:
session.query(exists().where(BookShelf.order)).scalar()
except exc.OperationalError: # Database is not compatible, some columns are missing
@ -615,7 +619,7 @@ def migrate_Database(session):
session.commit()
except exc.OperationalError:
print('Settings database is not writeable. Exiting...')
sys.exit(1)
sys.exit(2)
def clean_database(session):

View File

@ -336,8 +336,6 @@ def get_matching_tags():
title_input = request.args.get('book_title') or ''
include_tag_inputs = request.args.getlist('include_tag') or ''
exclude_tag_inputs = request.args.getlist('exclude_tag') or ''
# include_extension_inputs = request.args.getlist('include_extension') or ''
# exclude_extension_inputs = request.args.getlist('exclude_extension') or ''
q = q.filter(db.Books.authors.any(func.lower(db.Authors.name).ilike("%" + author_input + "%")),
func.lower(db.Books.title).ilike("%" + title_input + "%"))
if len(include_tag_inputs) > 0:
@ -637,7 +635,8 @@ def render_read_books(page, are_read, as_xml=False, order=None):
db_filter = and_(ub.ReadBook.user_id == int(current_user.id),
ub.ReadBook.read_status == ub.ReadBook.STATUS_FINISHED)
else:
db_filter = coalesce(ub.ReadBook.read_status, 0) != ub.ReadBook.STATUS_FINISHED
db_filter = and_(ub.ReadBook.user_id == int(current_user.id),
coalesce(ub.ReadBook.read_status, 0) != ub.ReadBook.STATUS_FINISHED)
entries, random, pagination = calibre_db.fill_indexpage(page, 0,
db.Books,
db_filter,
@ -1059,6 +1058,7 @@ def render_adv_search_results(term, offset=None, order=None, limit=None):
rating_low = term.get("ratinghigh")
rating_high = term.get("ratinglow")
description = term.get("comment")
read_status = term.get("read_status")
if author_name:
author_name = author_name.strip().lower().replace(',', '|')
if book_title:
@ -1076,7 +1076,7 @@ def render_adv_search_results(term, offset=None, order=None, limit=None):
if include_tag_inputs or exclude_tag_inputs or include_series_inputs or exclude_series_inputs or \
include_languages_inputs or exclude_languages_inputs or author_name or book_title or \
publisher or pub_start or pub_end or rating_low or rating_high or description or cc_present or \
include_extension_inputs or exclude_extension_inputs:
include_extension_inputs or exclude_extension_inputs or read_status:
searchterm.extend((author_name.replace('|', ','), book_title, publisher))
if pub_start:
try:
@ -1094,8 +1094,12 @@ def render_adv_search_results(term, offset=None, order=None, limit=None):
pub_start = u""
tag_names = calibre_db.session.query(db.Tags).filter(db.Tags.id.in_(include_tag_inputs)).all()
searchterm.extend(tag.name for tag in tag_names)
tag_names = calibre_db.session.query(db.Tags).filter(db.Tags.id.in_(exclude_tag_inputs)).all()
searchterm.extend(tag.name for tag in tag_names)
serie_names = calibre_db.session.query(db.Series).filter(db.Series.id.in_(include_series_inputs)).all()
searchterm.extend(serie.name for serie in serie_names)
serie_names = calibre_db.session.query(db.Series).filter(db.Series.id.in_(exclude_series_inputs)).all()
searchterm.extend(serie.name for serie in serie_names)
language_names = calibre_db.session.query(db.Languages).\
filter(db.Languages.id.in_(include_languages_inputs)).all()
if language_names:
@ -1105,6 +1109,8 @@ def render_adv_search_results(term, offset=None, order=None, limit=None):
searchterm.extend([_(u"Rating <= %(rating)s", rating=rating_high)])
if rating_low:
searchterm.extend([_(u"Rating >= %(rating)s", rating=rating_low)])
if read_status:
searchterm.extend([_(u"Read Status = %(status)s", status=read_status)])
searchterm.extend(ext for ext in include_extension_inputs)
searchterm.extend(ext for ext in exclude_extension_inputs)
# handle custom columns
@ -1121,6 +1127,23 @@ def render_adv_search_results(term, offset=None, order=None, limit=None):
q = q.filter(db.Books.pubdate >= pub_start)
if pub_end:
q = q.filter(db.Books.pubdate <= pub_end)
if read_status:
if config.config_read_column:
if read_status=="True":
q = q.join(db.cc_classes[config.config_read_column], isouter=True) \
.filter(db.cc_classes[config.config_read_column].value == True)
else:
q = q.join(db.cc_classes[config.config_read_column], isouter=True) \
.filter(coalesce(db.cc_classes[config.config_read_column].value, False) != True)
else:
if read_status == "True":
q = q.join(ub.ReadBook, db.Books.id==ub.ReadBook.book_id, isouter=True)\
.filter(ub.ReadBook.user_id == int(current_user.id),
ub.ReadBook.read_status == ub.ReadBook.STATUS_FINISHED)
else:
q = q.join(ub.ReadBook, db.Books.id == ub.ReadBook.book_id, isouter=True) \
.filter(ub.ReadBook.user_id == int(current_user.id),
coalesce(ub.ReadBook.read_status, 0) != ub.ReadBook.STATUS_FINISHED)
if publisher:
q = q.filter(db.Books.publishers.any(func.lower(db.Publishers.name).ilike("%" + publisher + "%")))
for tag in include_tag_inputs: