Attention: !!!! flask-themes2 is needed !!!!!

Merge caliblur is now a seperate theme
This commit is contained in:
Ozzie Isaacs
2026-08-16 11:46:35 +02:00
88 changed files with 3873 additions and 1273 deletions
+4 -2
View File
@@ -27,6 +27,7 @@ import mimetypes
from flask import Flask
from flask.sessions import SecureCookieSessionInterface
from flask_themes2 import Themes
from .MyLoginManager import MyLoginManager
from flask_principal import Principal
@@ -109,6 +110,7 @@ calibre_db = db.CalibreDB(app)
web_server = WebServer()
updater_thread = Updater()
themes_extension = Themes()
if limiter_present:
limiter = Limiter(key_func=True, headers_enabled=True, in_memory_fallback_enabled=True, default_limits=[],
@@ -179,6 +181,8 @@ def create_app():
res['target'],
res['found']))
app.wsgi_app = ReverseProxied(app.wsgi_app)
if not hasattr(app, "theme_manager"):
themes_extension.init_themes(app)
if os.environ.get('FLASK_DEBUG'):
cache_buster.init_cache_busting(app)
@@ -223,5 +227,3 @@ def create_app():
register_startup_tasks()
return app
+7 -1
View File
@@ -44,7 +44,7 @@ from sqlalchemy.orm.attributes import flag_modified
from sqlalchemy.exc import IntegrityError, OperationalError, InvalidRequestError, ArgumentError
from sqlalchemy.sql.expression import func, or_, text
from . import constants, logger, helper, services, cli_param
from . import constants, logger, helper, services, cli_param, themes
from . import db, calibre_db, ub, web_server, config, updater_thread, gdriveutils, \
kobo_sync_status, schedule
from .helper import check_valid_domain, send_test_mail, reset_password, generate_password_hash, check_email, \
@@ -121,6 +121,8 @@ def before_request():
g.allow_anonymous = config.config_anonbrowse
g.allow_upload = config.config_uploading
g.current_theme = config.config_theme
g.themes = themes.get_available_themes()
g.theme = themes.get_theme(config.config_theme)
g.config_authors_max = config.config_authors_max
if ('/static/' not in request.path and not config.db_configured and
request.endpoint not in ('admin.ajax_db_config',
@@ -589,6 +591,10 @@ def update_view_configuration():
return view_configuration()
_config_int(to_save, "config_restricted_column")
if not themes.is_valid_theme(to_save.get("config_theme", "0")):
flash(_("Invalid Theme"), category="error")
log.debug("Invalid Theme")
return view_configuration()
_config_int(to_save, "config_theme")
_config_int(to_save, "config_random_books")
_config_int(to_save, "config_books_per_page")
+29 -30
View File
@@ -18,7 +18,7 @@
import traceback
from flask import render_template, request, flash, make_response
from flask import request, flash, make_response
from flask_limiter import RateLimitExceeded
from flask_babel import gettext as _
from werkzeug.exceptions import default_exceptions
@@ -30,7 +30,7 @@ except ImportError:
from werkzeug.exceptions import UnprocessableEntity as FailedDependency
from . import config, app, logger, services
from .render_template import render_title_template
from .render_template import render_title_template, themed_render
from .web import render_login
from .usermanagement import auth
from cps.string_helper import strip_whitespaces
@@ -41,29 +41,29 @@ log = logger.create()
def error_http(error):
headers = {'WWW-Authenticate': 'Basic realm="calibre-web"'} if error.code == 401 else {}
return render_template('http_error.html',
error_code="Error {0}".format(error.code),
error_name=error.name,
issue=False,
goto_admin=False,
unconfigured=not config.db_configured,
instance=config.config_calibre_web_title
), error.code, headers
return themed_render('http_error.html',
error_code="Error {0}".format(error.code),
error_name=error.name,
issue=False,
goto_admin=False,
unconfigured=not config.db_configured,
instance=config.config_calibre_web_title
), error.code, headers
def internal_error(error):
if (isinstance(error.original_exception, AttributeError) and
error.original_exception.args[0] == "'NoneType' object has no attribute 'query'"
and error.original_exception.name == "query"):
return render_template('http_error.html',
error_code="Database Error",
error_name='The library used is invalid or has permission errors',
issue=False,
goto_admin=True,
unconfigured=False,
error_stack="",
instance=config.config_calibre_web_title
), 500
return themed_render('http_error.html',
error_code="Database Error",
error_name='The library used is invalid or has permission errors',
issue=False,
goto_admin=True,
unconfigured=False,
error_stack="",
instance=config.config_calibre_web_title
), 500
log.error("500 Internal Server Error: %s", traceback.format_exc())
error_stack = ""
try:
@@ -71,16 +71,16 @@ def internal_error(error):
error_stack = traceback.format_exc().split("\n")
except Exception:
pass
return render_template('http_error.html',
error_code="500 Internal Server Error",
error_name='The server encountered an internal error and was unable to complete your '
'request. There is an error in the application.',
issue=True,
goto_admin=False,
unconfigured=False,
error_stack=error_stack,
instance=config.config_calibre_web_title
), 500
return themed_render('http_error.html',
error_code="500 Internal Server Error",
error_name='The server encountered an internal error and was unable to complete your '
'request. There is an error in the application.',
issue=True,
goto_admin=False,
unconfigured=False,
error_stack=error_stack,
instance=config.config_calibre_web_title
), 500
def init_errorhandler():
@@ -116,4 +116,3 @@ def handle_rate_limit(__):
else:
return make_response('', 429)
+1 -1
View File
@@ -34,7 +34,7 @@ class MyWSGIHandler(WSGIHandler):
delta = '%.6f' % (self.time_finish - self.time_start)
else:
delta = '-'
forwarded = self.environ.get('HTTP_X_FORWARDED_FOR', None)
forwarded = self.environ.get('HTTP_X_FORWARDED_FOR', None) if self.environ else None
if forwarded:
client_address = forwarded
else:
+28 -6
View File
@@ -16,18 +16,37 @@
# 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 import render_template, g, abort, request
from flask import g, abort, request, render_template
from flask_themes2 import render_theme_template
from jinja2 import TemplateNotFound
from flask_babel import gettext as _
from werkzeug.local import LocalProxy
from .cw_login import current_user
from sqlalchemy.sql.expression import or_
from . import config, constants, logger, ub
from . import config, constants, logger, ub, themes
from .ub import User
log = logger.create()
def get_active_theme_identifier():
return themes.get_theme_identifier(config.config_theme, request.blueprint)
def themed_render(template_name, **kwargs):
active_theme = get_active_theme_identifier()
try:
return render_theme_template(active_theme, template_name, _fallback=False, **kwargs)
except TemplateNotFound:
default_theme = themes.get_default_theme()["identifier"]
if active_theme == default_theme:
raise
return render_template("_themes/{}/{}".format(default_theme, template_name),
_theme=active_theme,
**kwargs)
def get_sidebar_config(kwargs=None):
kwargs = kwargs or []
simple = bool([e for e in ['kindle', 'tolino', "kobo", "bookeen"]
@@ -68,7 +87,7 @@ def get_sidebar_config(kwargs=None):
sidebar.append({"glyph": "glyphicon-random", "text": _('Discover'), "link": 'web.books_list', "id": "rand",
"visibility": constants.SIDEBAR_RANDOM, 'public': True, "page": "discover",
"show_text": _('Show Random Books'), "config_show": True})
sidebar.append({"glyph": "glyphicon-inbox", "text": _('Categories'), "link": 'web.category_list', "id": "cat",
sidebar.append({"glyph": "glyphicon-tags", "text": _('Categories'), "link": 'web.category_list', "id": "cat",
"visibility": constants.SIDEBAR_CATEGORY, 'public': True, "page": "category", "no_param":True,
"show_text": _('Show Category Section'), "config_show": True})
sidebar.append({"glyph": "glyphicon-bookmark", "text": _('Series'), "link": 'web.series_list', "id": "serie",
@@ -110,9 +129,12 @@ def get_sidebar_config(kwargs=None):
def render_title_template(*args, **kwargs):
sidebar, simple = get_sidebar_config(kwargs)
try:
return render_template(instance=config.config_calibre_web_title, sidebar=sidebar, simple=simple,
accept=config.config_upload_formats.split(','),
*args, **kwargs)
return themed_render(args[0],
instance=config.config_calibre_web_title,
sidebar=sidebar,
simple=simple,
accept=config.config_upload_formats.split(','),
**kwargs)
except PermissionError:
log.error("No permission to access {} file.".format(args[0]))
abort(403)
File diff suppressed because one or more lines are too long
+217
View File
@@ -32,8 +32,225 @@ input.datepicker:focus + input {color: #555}
margin-right: 0;
}
.caliblur-filterheader {
margin: 40px 0;
padding: 0 10px 0 40px;
}
@media only screen and (max-width: 767px) {
.row-fluid > .col-sm-2 {
visibility: hidden;
}
}
.caliblur-login-panel .btn {
margin-right: 6px;
margin-bottom: 6px;
}
body.blur .modal-footer .btn,
body.blur .modal-body .btn,
body.blur #GeneralDeleteModal .modal-body .btn,
body.blur #GeneralChangeModal .modal-body .btn,
body.blur #deleteModal .modal-footer .btn {
margin-bottom: 0;
}
.advsearch-addtoshelf {
height: 40px;
}
body.advanced_search > div.container-fluid > div.row-fluid > div.col-sm-10 > div.col-md-10.col-lg-6 .twitter-typeahead .tt-hint {
background-color: hsla(0, 0%, 100%, .08) !important;
}
body.advanced_search > div.container-fluid > div.row-fluid > div.col-sm-10 > div.col-md-10.col-lg-6 .form-group > .row {
margin-left: 0;
margin-right: 0;
}
body.advanced_search > div.container-fluid > div.row-fluid > div.col-sm-10 > div.col-md-10.col-lg-6 .form-group > .row > .form-group.col-sm-6:first-child {
padding-left: 0;
}
body.advanced_search > div.container-fluid > div.row-fluid > div.col-sm-10 > div.col-md-10.col-lg-6 .form-group > .row > .form-group.col-sm-6:last-child {
padding-right: 0;
}
body.blur .input-group-btn > .datepicker_delete.btn.btn-default {
display: flex;
align-items: center;
justify-content: center;
height: 40px;
min-width: 40px;
margin-bottom: 0;
padding: 0 12px;
}
body.blur .rating-clear .glyphicon-remove {
color: #fff;
}
body.blur .datepicker table tr td.day:hover,
body.blur .datepicker table tr td span:hover,
body.blur .datepicker table tr td.active,
body.blur .datepicker table tr td.active:hover,
body.blur .datepicker table tr td span.active,
body.blur .datepicker table tr td span.active:hover {
background-color: var(--color-secondary) !important;
background-image: none !important;
border-color: var(--color-secondary) !important;
color: #fff !important;
}
.book-meta > .author > .author-prefix {
color: hsla(0, 0%, 100%, .45);
}
.form-upload-label,
.top-tasks-label {
display: none;
}
body.mailset > div.container-fluid > div > div.col-sm-10 > div.discover .editable-buttons .btn-primary {
background-color: var(--color-secondary);
border-color: var(--color-secondary);
color: #fff;
}
body.mailset > div.container-fluid > div > div.col-sm-10 > div.discover .editable-buttons .btn-primary:hover,
body.mailset > div.container-fluid > div > div.col-sm-10 > div.discover .editable-buttons .btn-primary:focus,
body.mailset > div.container-fluid > div > div.col-sm-10 > div.discover .editable-buttons .btn-primary:active {
background-color: var(--color-secondary-hover);
border-color: var(--color-secondary-hover);
color: #fff;
}
body.mailset > div.container-fluid > div > div.col-sm-10 > div.discover .editable-buttons .btn-default {
background-color: hsla(0, 0%, 100%, .25);
border-color: hsla(0, 0%, 100%, .25);
color: #fff;
}
body.mailset > div.container-fluid > div > div.col-sm-10 > div.discover .editable-buttons .btn-default:hover,
body.mailset > div.container-fluid > div > div.col-sm-10 > div.discover .editable-buttons .btn-default:focus,
body.mailset > div.container-fluid > div > div.col-sm-10 > div.discover .editable-buttons .btn-default:active {
background-color: hsla(0, 0%, 100%, .3);
border-color: hsla(0, 0%, 100%, .3);
color: #fff;
}
body.dbconfig.admin > div.container-fluid > div > div.col-sm-10 > div.discover > h2:first-of-type {
width: calc(100vw - 240px);
height: 60px;
margin: 0;
padding-left: 25px;
position: fixed;
top: 60px;
left: 240px;
background: rgba(0, 0, 0, .15);
color: hsla(0, 0%, 100%, .7);
font-family: "Open Sans Semibold", "Helvetica Neue", Helvetica, Arial, sans-serif;
font-size: 15px;
font-weight: 400;
line-height: 60px;
white-space: nowrap;
z-index: 1;
}
body.dbconfig.admin > div.container-fluid > div > div.col-sm-10 > div.discover > h2:first-of-type::before {
content: none;
}
body.dbconfig.admin > div.container-fluid > div > div.col-sm-10 > div.discover {
margin-bottom: 120px !important;
padding: 0 30px !important;
}
body.dbconfig.admin .caliblur-dbconfig-form .input-group-btn > .btn {
display: flex;
align-items: center;
justify-content: center;
height: 40px;
margin-bottom: 0;
padding: 0 16px;
}
body.dbconfig.admin .caliblur-dbconfig-actions {
display: flex;
justify-content: flex-end;
padding-right: 15px;
}
body.dbconfig.admin .caliblur-dbconfig-actions .pull-right {
display: flex;
gap: 10px;
}
body.dbconfig.admin .caliblur-dbconfig-actions .btn {
display: flex;
align-items: center;
height: 40px;
margin-bottom: 0;
}
body.blur .modal-header.bg-info,
body.blur .modal-header.bg-danger,
body.blur #InvalidDialog .modal-header {
background-color: #323232;
border-bottom: 1px solid #222;
color: #eee;
}
body.blur .modal-header.bg-info .modal-title,
body.blur .modal-header.bg-danger .modal-title,
body.blur .modal-header.bg-info span,
body.blur .modal-header.bg-danger span,
body.blur #InvalidDialog .modal-header .modal-title,
body.blur #InvalidDialog .modal-header span {
color: inherit;
}
body.blur #fileModal #file_confirm {
background-color: var(--color-secondary);
border-color: var(--color-secondary);
color: #fff;
}
body.blur #fileModal .modal-footer {
display: flex;
align-items: center;
justify-content: flex-end;
gap: 10px;
}
body.blur #fileModal #element_selected {
margin-right: auto;
}
body.blur #fileModal #file_abort,
body.blur #fileModal #file_confirm {
margin: 0;
}
body.blur #fileModal #file_confirm:hover,
body.blur #fileModal #file_confirm:focus,
body.blur #fileModal #file_confirm:active {
background-color: var(--color-secondary-hover);
border-color: var(--color-secondary-hover);
color: #fff;
}
body.blur #fileModal #file_abort {
background-color: hsla(0, 0%, 100%, .25);
border-color: hsla(0, 0%, 100%, .25);
color: #fff;
}
body.blur #fileModal #file_abort:hover,
body.blur #fileModal #file_abort:focus,
body.blur #fileModal #file_abort:active {
background-color: hsla(0, 0%, 100%, .3);
border-color: hsla(0, 0%, 100%, .3);
color: #fff;
}
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
+8
View File
@@ -0,0 +1,8 @@
<svg width="560" height="560" xmlns="http://www.w3.org/2000/svg">
<g fill="#FFF" fill-rule="nonzero">
<path d="M144.291 492.325C166.624 476.265 193.194 466.156 224 462c12.444 9.333 31.111 14 56 14s43.556-4.667 56-14c30.806 4.156 57.376 14.264 79.709 30.325C376.527 517.435 329.951 532 280 532c-49.951 0-96.527-14.565-135.709-39.675z" fill-opacity=".6"/>
<path d="M224 462l12.88-40.251C196.429 397.462 168 340.035 168 273.059c0-35.193 7.849-67.75 21.168-94.248 16.173 4.977 35.315 7.856 55.832 7.856 51.104 0 93.68-17.86 103.072-41.516C374.779 174.588 392 220.931 392 273.059c0 66.976-28.429 124.403-68.88 148.69L336 462c-12.444 9.333-31.111 14-56 14-24.694 0-43.263-4.594-55.707-13.782L224 462z" fill-opacity=".75"/>
<path d="M180.064 345.849C155.282 318.067 140 279.997 140 238c0-85.052 62.68-154 140-154s140 68.948 140 154c0 41.997-15.282 80.067-40.064 107.849 7.716-21.862 12.064-46.596 12.064-72.79 0-52.128-17.221-98.471-43.928-127.908-9.392 23.656-51.968 41.516-103.072 41.516-20.517 0-39.659-2.879-55.832-7.856C175.849 205.309 168 237.866 168 273.059c0 26.194 4.348 50.928 12.064 72.79z" fill-opacity=".45"/>
<path d="M280 532c139.176 0 252-112.824 252-252S419.176 28 280 28 28 140.824 28 280s112.824 252 252 252zm0 28C125.36 560 0 434.64 0 280S125.36 0 280 0s280 125.36 280 280-125.36 280-280 280z" fill-opacity=".75"/>
</g>
</svg>

After

Width:  |  Height:  |  Size: 1.3 KiB

+24
View File
@@ -241,6 +241,12 @@ span.glyphicon.glyphicon-tags {
vertical-align: text-top;
}
.navigation span.glyphicon.glyphicon-tags {
color: #444;
padding-right: 0px;
vertical-align: baseline;
}
.book-meta { padding-bottom: 20px; }
.navbar-default .navbar-toggle .icon-bar { background-color: #000; }
@@ -500,3 +506,21 @@ body > div.container-fluid > div.row-fluid > div.col-sm-2::-webkit-scrollbar {
body > div.container-fluid {
padding-right: 0px;
}
.input-group .form-control,
.input-group .toggle-password {
height: 40px;
}
.input-group .toggle-password {
min-width: 40px;
width: 40px;
padding-left: 0;
padding-right: 0;
}
.input-group .toggle-password .glyphicon {
font-size: 16px;
line-height: 1;
top: 0;
}
+14 -481
View File
@@ -14,17 +14,6 @@
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
// Move advanced search to side-menu
$("a[href*='advanced']").parent().insertAfter("#nav_new");
$("body.stat").addClass("stats");
$("body.config").addClass("admin");
$("body.uiconfig").addClass("admin");
$("body.advsearch").addClass("advanced_search");
$("body.newuser").addClass("admin");
$("body.mailset").addClass("admin");
$("body > div.container-fluid > div > div.col-sm-10 > div.filterheader").attr("style","margin: 40px 0 !important; padding: 0 10px 0 40px !important;");
// Back button
curHref = window.location.href.split("/");
prevHref = document.referrer.split("/");
@@ -62,18 +51,14 @@ if ($("body.book").length > 0) {
$(".languages").appendTo(".bookinfo");
$(".hr").detach();
if ($(".identifiers ").length > 0) {
console.log(".identifiers length " + $(".identifiers").length);
$('.identifiers').before('<div class="hr"></div>');
} else {
if ($(".bookinfo > p:first-child").length > 0) {
console.log(".bookinfo > p:first-child length " + $(".bookinfo > p").length);
$(".bookinfo > p:first-child").first().after('<div class="hr"></div>');
} else {
if ($('.bookinfo a[href*="/series/"]').length > 0) {
console.log("series text found; placing hr below series");
$('.bookinfo a[href*="/series/"]').parent().after('<div class="hr"></div>');
} else {
console.log("prepending hr div to top of .bookinfo");
$(".bookinfo").prepend('<div class="hr"></div>');
}
}
@@ -109,137 +94,7 @@ if ($("body.book").length > 0) {
$(".book-meta h2:first").clone()
.prependTo(".book-meta > .btn-toolbar:first");
// If only one download type exists still put the items into a drop-drown list.
downloads = $("a[id^=btnGroupDrop]").get();
if ($(downloads).length === 1) {
$('<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 leramslist aria-labelledby="btnGroupDrop1"></ul>').insertBefore(downloads[downloads.length - 1]);
$(downloads).detach();
$.each(downloads, function (i, val) {
$("<li>" + downloads[i].outerHTML + "</li>").appendTo(".leramslist");
});
$(".leramslist").find("span").remove();
$(".leramslist a").removeClass("btn btn-primary").removeAttr("role");
}
// Add classes to buttons
$("#sendbtn").parent().addClass("sendBtn");
$("[id*=btnGroupDrop]").parent().addClass("downloadBtn");
$("read-in-browser").parent().addClass("readBtn");
$("listen-in-browser").parent().addClass("listenBtn");
$(".downloadBtn button:first").addClass("download-text");
// Move all options in book details page to the same group
$("[aria-label*='Delete book']")
.prependTo('[aria-label^="Download, send"]')
.children().removeClass("btn-sm");
$(".custom_columns")
.addClass(" btn-group")
.attr("role", "group")
.removeClass("custom_columns")
.prependTo('[aria-label^="Download, send"]');
$("#have_read_cb")
.after('<label class="block-label readLbl" for="#have_read_cb"></label>');
$("#have_read_form").next("p").remove();
$("#have_read_form").next("p").remove();
$("#archived_cb")
.after('<label class="block-label readLbl" for="#archived_cb"></label>');
$("#shelf-actions").prependTo('[aria-label^="Download, send"]');
$(".more-stuff .col-sm-12 #back").hide()
/* .html("&laquo; Previous")
.addClass("page-link")
.removeClass("btn btn-default")
.prependTo('[aria-label^="Download, send"]');*/
// Move dropdown lists higher in dom, replace bootstrap toggle with own toggle.
$('ul[aria-labelledby="read-in-browser"]').insertBefore(".blur-wrapper").addClass("readinbrowser-drop");
$('ul[aria-labelledby="listen-in-browser"]').insertBefore(".blur-wrapper").addClass("readinbrowser-drop");
$('ul[aria-labelledby="send-to-ereader"]').insertBefore(".blur-wrapper").addClass("sendtoereader-drop");
$(".leramslist").insertBefore(".blur-wrapper");
$('ul[aria-labelledby="btnGroupDrop1"]').insertBefore(".blur-wrapper").addClass("leramslist");
$("#add-to-shelves").insertBefore(".blur-wrapper");
$("#back")
$("#read-in-browser").click(function () {
$(".readinbrowser-drop").toggle();
});
$("#listen-in-browser").click(function () {
$(".readinbrowser-drop").toggle();
});
$(".downloadBtn").click(function () {
$(".leramslist").toggle();
});
$("#sendbtn2").click(function () {
$(".sendtoereader-drop").toggle();
});
$('div[aria-label="Add to shelves"]').click(function () {
$("#add-to-shelves").toggle();
});
//Work to reposition dropdowns. Does not currently solve for
//screen resizing
function dropdownToggle() {
topPos = $(".book-meta > .btn-toolbar:first").offset().top
if ($("#read-in-browser").length > 0) {
position = $("#read-in-browser").offset().left
if (position + $(".readinbrowser-drop").width() > $(window).width()) {
positionOff = position + $(".readinbrowser-drop").width() - $(window).width();
ribPosition = position - positionOff - 5
$(".readinbrowser-drop").attr("style", "left: " + ribPosition + "px !important; right: auto; top: " + topPos + "px");
} else {
$(".readinbrowser-drop").attr("style", "left: " + position + "px !important; right: auto; top: " + topPos + "px");
}
}
if ($("#sendbtn2").length > 0) {
position = $("#sendbtn2").offset().left
if (position + $(".sendtoereader-drop").width() > $(window).width()) {
positionOff = position + $(".sendtoereader-drop").width() - $(window).width();
ribPosition = position - positionOff - 5
$(".sendtoereader-drop").attr("style", "left: " + ribPosition + "px !important; right: auto; top: " + topPos + "px");
} else {
$(".sendtoereader-drop").attr("style", "left: " + position + "px !important; right: auto; top: " + topPos + "px");
}
}
if ($(".downloadBtn").length > 0) {
position = $("#btnGroupDrop1").offset().left
if (position + $(".leramslist").width() > $(window).width()) {
positionOff = position + $(".leramslist").width() - $(window).width();
dlPosition = position - positionOff - 5
$(".leramslist").attr("style", "left: " + dlPosition + "px !important; right: auto; top: " + topPos + "px");
} else {
$(".leramslist").attr("style", "left: " + position + "px !important; right: auto; top: " + topPos + "px");
}
}
if ($('div[aria-label="Add to shelves"]').length > 0) {
position = $('div[aria-label="Add to shelves"]').offset().left
if (position + $("#add-to-shelves").width() > $(window).width()) {
positionOff = position + $("#add-to-shelves").width() - $(window).width();
adsPosition = position - positionOff - 5;
$("#add-to-shelves").attr("style", "left: " + adsPosition + "px !important; right: auto; top: " + topPos + "px");
} else {
$("#add-to-shelves").attr("style", "left: " + position + "px !important; right: auto; top: " + topPos + "px");
}
}
}
dropdownToggle();
$(window).on("resize", function () {
dropdownToggle();
});
// Clone book rating for mobile view.
$(".book-meta > .bookinfo > .rating").clone().insertBefore(".book-meta > .description").addClass("rating-mobile");
@@ -256,10 +111,6 @@ if ($("body.book").length > 0) {
// Hide dropdown and collapse menus on click-off
$(document).mouseup(function (e) {
var container = new Array();
container.push($('ul[aria-labelledby="read-in-browser"]'));
container.push($(".sendtoereader-drop"));
container.push($(".leramslist"));
container.push($("#add-to-shelves"));
container.push($(".navbar-collapse.collapse.in"));
$.each(container, function (key, value) {
@@ -277,12 +128,6 @@ $(document).mouseup(function (e) {
});
});
// Move create shelf
$("#nav_createshelf").prependTo(".your-shelves");
// Move About link it the profile dropdown
$(".profileDropli #top_user").parent().after($("#nav_about").addClass("dropdown"))
// Remove the modals except from some areas where they are needed
bodyClass = $("body").attr("class").split(" ");
modalWanted = ["admin", "editbook", "config", "uiconfig", "me", "edituser"];
@@ -294,11 +139,6 @@ if ($.inArray(bodyClass[0], modalWanted) != -1) {
}
// Add classes to global buttons
$("#top_tasks").parent().addClass("top_tasks");
$("#top_admin").parent().addClass("top_admin");
$("#form-upload").parent().addClass("form-upload");
// Search button work
$("input#query").focus(function () {
$('form[role="search"]').addClass("search-focus");
@@ -321,61 +161,19 @@ $(document).on("click", ".dropdown-toggle", function () {
});
// Collapse long text into read-more
var readMoreText = $("body").data("readmore-more") || "READ MORE";
var readLessText = $("body").data("readmore-less") || "READ LESS";
$("div.comments").readmore({
collapsedHeight: 134,
heightMargin: 45,
speed: 300,
moreLink: '<a href="#">READ MORE</a>', // ToDo: make translateable
lessLink: '<a href="#">READ LESS</a>', // ToDo: make translateable
moreLink: '<a href="#">' + readMoreText + '</a>',
lessLink: '<a href="#">' + readLessText + '</a>',
});
/////////////////////////////////
// End of Global Work //
///////////////////////////////
// Search Results
if($("body.search").length > 0) {
$('div[aria-label="Add to shelves"]').click(function () {
$("#add-to-shelves").toggle();
});
}
// Advanced Search Results
if($("body.advsearch").length > 0) {
$("#loader + .container-fluid")
.prepend("<div class='blur-wrapper'></div>");
$("#add-to-shelves").insertBefore(".blur-wrapper");
$('div[aria-label="Add to shelves"]').click(function () {
$("#add-to-shelves").toggle();
});
$('#add-to-shelf').height("40px");
function search_dropdownToggle() {
if( $("#add-to-shelf").length) {
topPos = $("#add-to-shelf").offset().top - 20;
} else {
topPos = 0
}
if ($('div[aria-label="Add to shelves"]').length > 0) {
position = $('div[aria-label="Add to shelves"]').offset().left
if (position + $("#add-to-shelves").width() > $(window).width()) {
positionOff = position + $("#add-to-shelves").width() - $(window).width();
adsPosition = position - positionOff - 5;
$("#add-to-shelves").attr("style", "left: " + adsPosition + "px !important; right: auto; top: " + topPos + "px");
} else {
$("#add-to-shelves").attr("style", "left: " + position + "px !important; right: auto; top: " + topPos + "px");
}
}
}
search_dropdownToggle();
$(window).on("resize", function () {
search_dropdownToggle();
});
}
// Author Page Background Blur
if ($("body.author").length > 0) {
cover = $(".author-bio img").attr("src");
@@ -396,8 +194,6 @@ backurl = "../../book/" + url[2]
$("body.epub #title-controls")
.append('<div class="epub-back"><input action="action" onclick="location.href=backurl; return false;" type="button" value="Back" /></div>')
$("body.stat .col-sm-10 p:first").insertAfter("#libs");
// Check if link is external and force _blank attribute
$(function () { // document ready
$("a").filter(function () {
@@ -407,225 +203,26 @@ $(function () { // document ready
});
});
// Check if lists are empty and add class to buttons
if ($.trim($("#add-to-shelves").html()).length === 0) {
$("#add-to-shelf").addClass("empty-ul");
// Keep add-to-shelf button state in sync with current dropdown content
function updateAddToShelfState() {
if ($("#add-to-shelves li").length === 0) {
$("#add-to-shelf").addClass("empty-ul");
} else {
$("#add-to-shelf").removeClass("empty-ul");
}
}
shelfLength = $("#add-to-shelves li").length;
emptyLength = 0;
updateAddToShelfState();
$("#add-to-shelves").on("click", "li a", function () {
console.log("#remove-from-shelves change registered");
emptyLength++;
setTimeout(function () {
if (emptyLength >= shelfLength) {
console.log("list is empty; adding empty-ul class");
$("#add-to-shelf").addClass("empty-ul");
} else {
console.log("list is not empty; removing empty-ul class");
$("#add-to-shelf").removeClass("empty-ul");
}
}, 100);
$("#add-to-shelves, #remove-from-shelves").on("click", "[data-shelf-action]", function () {
setTimeout(updateAddToShelfState, 100);
});
if ($.trim($('ul[aria-labelledby="read-in-browser"] li').html()).length === 0) {
$("#read-in-browser").addClass("empty-ul");
}
// Shelf Buttons and Tooltips
if ($("body.shelf").length > 0) {
$('div[data-target="#DeleteShelfDialog"]')
.before('<div class=".btn-group shelf-btn-group"></div>')
.appendTo(".shelf-btn-group")
.addClass("delete-shelf-btn");
$('a[href*="edit"]')
.appendTo(".shelf-btn-group")
.addClass("edit-shelf-btn");
$('a[href*="order"]')
.appendTo(".shelf-btn-group")
.addClass("order-shelf-btn");
$(".delete-shelf-btn").attr({
"data-toggle-two": "tooltip",
"title": $(".delete-shelf-btn").text(), // "Delete Shelf"
"data-placement": "bottom"
})
.addClass("delete-btn-tooltip");
$(".edit-shelf-btn").attr({
"data-toggle-two": "tooltip",
"title": $(".edit-shelf-btn").text(), // "Edit Shelf"
"data-placement": "bottom"
})
.addClass("edit-btn-tooltip");
$(".order-shelf-btn").attr({
"data-toggle-two": "tooltip",
"title": $(".order-shelf-btn").text(), //"Reorder Shelf"
"data-placement": "bottom"
})
.addClass("order-btn-tooltip");
}
// Rest of Tooltips
$(".home-btn > a").attr({
"data-toggle": "tooltip",
"href": $(".navbar-brand")[0].href,
"title": $(document.body).attr("data-text"), // Home
"data-placement": "bottom"
})
.addClass("home-btn-tooltip");
$(".plexBack > a").attr({
"data-toggle": "tooltip",
"title": $(document.body).attr("data-textback"), // Back
"data-placement": "bottom"
})
.addClass("back-btn-tooltip");
$("#top_tasks").attr({
"data-toggle": "tooltip",
"title": $("#top_tasks").text(), // "Tasks"
"data-placement": "bottom",
"data-viewport": "#main-nav"
})
.addClass("tasks-btn-tooltip");
$("#top_admin").attr({
"data-toggle": "tooltip",
"title": $("#top_admin").attr("data-text"), // Settings
"data-placement": "bottom",
"data-viewport": "#main-nav"
})
.addClass("admin-btn-tooltip");
$(".profileDrop").attr({
"title": $("#top_user").attr("data-text"), //Account
"data-placement": "bottom",
"data-toggle-two": "tooltip",
"data-viewport": "#main-nav"
})
.addClass("send-btn-tooltip dropdown");
$("#btn-upload").attr({
"data-toggle": "tooltip",
"title": $("#btn-upload").parent().text(), // "Upload"
"data-placement": "bottom",
"data-viewport": "#main-nav"
})
.addClass("upload-btn-tooltip");
$("#add-to-shelf").attr({
"data-toggle-two": "tooltip",
"title": $("#add-to-shelf").text(), // "Add to Shelf"
"data-placement": "bottom",
"data-viewport": ".btn-toolbar"
})
.addClass("addtoshelf-btn-tooltip");
$("#have_read_cb").attr({
"data-toggle": "tooltip",
"title": $("#have_read_cb").attr("data-unchecked"),
"data-placement": "bottom",
"data-viewport": ".btn-toolbar"
})
.addClass("readunread-btn-tooltip");
$("#have_read_cb:checked").attr({
"data-toggle": "tooltip",
"title": $("#have_read_cb").attr("data-checked"),
"data-placement": "bottom",
"data-viewport": ".btn-toolbar"
})
.addClass("readunread-btn-tooltip");
$("#archived_cb").attr({
"data-toggle": "tooltip",
"title": $("#archived_cb").attr("data-unchecked"),
"data-placement": "bottom",
"data-viewport": ".btn-toolbar"
})
.addClass("readunread-btn-tooltip");
$("#archived_cb:checked").attr({
"data-toggle": "tooltip",
"title": $("#archived_cb").attr("data-checked"),
"data-placement": "bottom",
"data-viewport": ".btn-toolbar"
})
.addClass("readunread-btn-tooltip");
$("button#delete").attr({
"data-toggle-two": "tooltip",
"title": $("button#delete").text(), //"Delete"
"data-placement": "bottom",
"data-viewport": ".btn-toolbar"
})
.addClass("delete-book-btn-tooltip");
$("#have_read_cb").click(function () {
if ($("#have_read_cb:checked").length > 0) {
$(this).attr("data-original-title", $("#have_read_cb").attr("data-checked"));
} else {
$(this).attr("data-original-title", $("#have_read_cb").attr("data-unchecked"));
}
});
$("#archived_cb").click(function () {
if ($("#archived_cb:checked").length > 0) {
$(this).attr("data-original-title", $("#archived_cb").attr("data-checked"));
} else {
$(this).attr("data-original-title", $("#archived_cb").attr("data-unchecked"));
}
});
$('.btn-group[aria-label="Edit/Delete book"] a').attr({
"data-toggle": "tooltip",
"title": $("#edit_book").text(), // "Edit"
"data-placement": "bottom",
"data-viewport": ".btn-toolbar"
})
.addClass("edit-btn-tooltip");
$("#sendbtn").attr({
"data-toggle": "tooltip",
"title": $("#sendbtn").attr("data-text"),
"data-placement": "bottom",
"data-viewport": ".btn-toolbar"
})
.addClass("send-btn-tooltip");
$("#sendbtn2").attr({
"data-toggle-two": "tooltip",
"title": $("#sendbtn2").text(), // "Send to eReader",
"data-placement": "bottom",
"data-viewport": ".btn-toolbar"
})
.addClass("send-btn-tooltip");
$("#read-in-browser").attr({
"data-toggle-two": "tooltip",
"title": $("#read-in-browser").text(),
"data-placement": "bottom",
"data-viewport": ".btn-toolbar"
})
.addClass("send-btn-tooltip");
$("#btnGroupDrop1").attr({
"data-toggle-two": "tooltip",
"title": $("#btnGroupDrop1").text(),
"data-placement": "bottom",
"data-viewport": ".btn-toolbar"
});
if ($("body.epub").length === 0) {
$(document).ready(function () {
$("[data-toggle='tooltip']").tooltip({container: "body", trigger: "hover"});
$("[data-toggle-two='tooltip']").tooltip({container: "body", trigger: "hover"});
$("#btn-upload").attr("title", " ");
});
@@ -638,9 +235,6 @@ if ($("body.epub").length === 0) {
});
}
$("#read-in-browser a").attr("target", "");
$("#listen-in-browser a").attr("target", "");
if ($(".edit-shelf-btn").length > 1) {
$(".edit-shelf-btn:first").remove();
}
@@ -648,68 +242,7 @@ if ($(".order-shelf-btn").length > 1) {
$(".order-shelf-btn:first").remove();
}
$("#top_user > span.hidden-sm").clone().insertBefore(".profileDropli");
$(".navbar-collapse.collapse.in").before('<div class="sidebar-backdrop"></div>');
// Get rid of leading white space
recentlyAdded = $("#nav_new a:contains('Recently')").text().trim();
$("#nav_new a:contains('Recently')").contents().filter(function () {
return this.nodeType === 3
}).each(function () {
this.textContent = this.textContent.replace(" Recently Added", recentlyAdded);
});
// Change shelf textValue
shelfText = $(".shelf .discover h2:first").text().replace(":", " —").replace(/\'/g, "");
$(".shelf .discover h2:first").text(shelfText);
shelfText = $(".shelforder .col-sm-10 .col-sm-6.col-lg-6.col-xs-6 h2:first").text().replace(':', ' —').replace(/\'/g, "");
$(".shelforder .col-sm-10 .col-sm-6.col-lg-6.col-xs-6 h2:first").text(shelfText);
function mobileSupport() {
if ($(window).width() <= 768) {
//Move menu to collapse
$(".row-fluid > .col-sm-2:first").appendTo(".navbar-collapse.collapse:first");
if ($(".sidebar-backdrop").length < 1) {
$(".navbar-collapse.collapse:first").after("<div class='sidebar-backdrop'></div>");
}
} else {
//Move menu out of collapse
$(".col-sm-2:first").insertBefore(".col-sm-10:first");
$(".sidebar-backdrop").remove();
}
}
// LayerCake plug
if ($(" body.stat p").length > 0) {
$(" body.stat p").append(" and <a href='https://github.com/leram84/layer.Cake/tree/master/caliBlur' target='_blank'>layer.Cake</a>");
str = $(" body.stat p").html().replace("</a>.", "</a>");
$(" body.stat p").html(str);
}
// Collect delete buttons in editbook to single dropdown
$(".editbook .text-center.more-stuff").prepend('<button id="deleteButton" type="button" class="btn btn-danger dropdown-toggle" data-toggle="dropdown" aria-haspopup="true" aria-expanded="false"><span class="glyphicon glyphicon-remove"></span>Delete Format<span class="caret"></span></button><ul class="dropdown-menu delete-dropdown"></ul>');
deleteButtons = $(".editbook .text-center.more-stuff a").removeClass("btn btn-danger").attr("type", "").get();
$(deleteButtons).detach();
$(".editbook .text-center.more-stuff h4").remove();
$.each(deleteButtons, function (i, val) {
$("<li>" + deleteButtons[i].outerHTML + "</li>").appendTo(".delete-dropdown");
});
// Turn off bootstrap animations
$(function () {
$.support.transition = false;
})
mobileSupport();
// Only call function once resize is complete
//var id;
$(window).on("resize", function () {
// clearTimeout(id);
// id = setTimeout(mobileSupport, 500);
mobileSupport();
});
+31
View File
@@ -89,6 +89,37 @@ $("#archived_cb").on("change", function() {
$(this).closest("form").submit();
});
$(document).on("click", ".downloadBtn > #btnGroupDrop1", function (e) {
var $button = $(this);
var $group = $button.closest(".downloadBtn");
var $menu = $group.children("ul.dropdown-menu");
var isOpen = $button.attr("aria-expanded") === "true";
e.preventDefault();
e.stopPropagation();
$(".downloadBtn.open").not($group).removeClass("open").children("ul.dropdown-menu").hide();
$(".downloadBtn > #btnGroupDrop1").not($button).attr("aria-expanded", "false");
if (isOpen) {
$group.removeClass("open");
$button.attr("aria-expanded", "false");
$menu.hide();
} else {
$group.addClass("open");
$button.attr("aria-expanded", "true");
$menu.removeClass("offscreen");
$menu.show();
}
});
$(document).on("click", function (e) {
if ($(e.target).closest(".downloadBtn").length === 0) {
$(".downloadBtn.open").removeClass("open").children("ul.dropdown-menu").hide();
$(".downloadBtn > #btnGroupDrop1").attr("aria-expanded", "false");
}
});
(function() {
var templates = {
add: _.template(
+27 -5
View File
@@ -166,6 +166,28 @@ $("#form-upload-format").uploadprogress({
modalTitleFailed: $("#form-upload-format").data("failed")
});
$(document).on('click', '.toggle-password', function (event) {
event.preventDefault();
var $button = $(this);
var selector = $button.data('target');
var $passwordInput = $(selector);
if (!$passwordInput.length) {
return;
}
var isPassword = $passwordInput.attr('type') === 'password';
$passwordInput.attr('type', isPassword ? 'text' : 'password');
var $icon = $button.find('.glyphicon');
if ($icon.length) {
$icon.toggleClass('glyphicon-eye-open', !isPassword);
$icon.toggleClass('glyphicon-eye-close', isPassword);
}
var label = isPassword ? _('Hide password') : _('Show password');
$button.attr({
'aria-label': label,
'title': label
});
});
$(document).ready(function() {
var inp = $('#query').first()
if (inp.length) {
@@ -384,14 +406,14 @@ $(function() {
});
}
$(".discover .row").isotope({
$(".discover .row.display-flex").isotope({
// options
itemSelector : ".book",
layoutMode : "fitRows"
});
if ($(".load-more").length && $(".next").length) {
var $loadMore = $(".load-more .row").infiniteScroll({
var $loadMore = $(".load-more .row.display-flex").infiniteScroll({
debug: false,
// selector for the paged navigation (it will be hidden)
path : ".next",
@@ -406,7 +428,7 @@ $(function() {
$(" a:not(.dropdown-toggle) ")
.removeAttr("data-toggle");
}
$(".load-more .row").isotope( "appended", $(data), null );
$(".load-more .row.display-flex").isotope( "appended", $(data), null );
});
// fix for infinite scroll on CaliBlur Theme (#981)
@@ -826,7 +848,7 @@ $(function() {
});
$(window).resize(function() {
$(".discover .row").isotope("layout");
$(".discover .row.display-flex").isotope("layout");
});
$("#import_ldap_users").click(function() {
@@ -851,7 +873,7 @@ $(function() {
$(this).parent().find("a.author-name").slice($(this).data("authors-max")).toggle();
$(this).parent().find("span.author-hidden-divider").toggle();
$(this).html() === $(this).data("collapse-caption") ? $(this).html("(...)") : $(this).html($(this).data("collapse-caption"));
$(".discover .row").isotope("layout");
$(".discover .row.display-flex").isotope("layout");
});
$(".update-view").click(function(e) {
-18
View File
@@ -23,9 +23,6 @@ var reload = false;
$(function() {
$('#tasktable').bootstrapTable({
formatNoMatches: function () {
return '';
},
striped: true
});
if ($('#tasktable').length) {
@@ -509,9 +506,6 @@ $(function() {
maintainMetaData: true,
responseHandler: responseHandler,
columns: column,
formatNoMatches: function () {
return "";
},
// eslint-disable-next-line no-unused-vars
onEditableSave: function (field, row, oldvalue, $el) {
if ($.inArray(field, [ "title", "sort" ]) !== -1 && $('#autoupdate_titlesort').prop('checked')
@@ -635,9 +629,6 @@ $(function() {
});
$("#domain-allow-table").bootstrapTable({
formatNoMatches: function () {
return "";
},
striped: false
});
$("#domain_deny_submit").click(function(event) {
@@ -655,9 +646,6 @@ $(function() {
});
});
$("#domain-deny-table").bootstrapTable({
formatNoMatches: function () {
return "";
},
striped: false
});
@@ -755,9 +743,6 @@ $(function() {
}
$("#restrict-elements-table").bootstrapTable({
formatNoMatches: function () {
return "";
},
url: getPath() + "/ajax/listrestriction/" + type + "/" + userId,
rowStyle: function(row) {
if (row.id.charAt(0) === "a") {
@@ -878,9 +863,6 @@ $(function() {
maintainMetaData: true,
responseHandler: responseHandler,
columns: user_column,
formatNoMatches: function () {
return "";
},
onPostBody () {
// Remove all checkboxes from Headers for showing the texts in the column selector
$('.columns [data-field]').each(function(){
+104
View File
@@ -0,0 +1,104 @@
# -*- coding: utf-8 -*-
# This file is part of the Calibre-Web (https://github.com/janeczku/calibre-web)
# Copyright (C) 2026
#
# 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/>.
THEMES = (
{
"id": 0,
"identifier": "standard",
"label": "Standard Theme",
"configurable": True,
"css_files": (),
"js_files": (),
"body_class": "",
"show_home_shortcuts": False,
"profile_dropdown": False,
"show_upload_loader": False
},
{
"id": 1,
"identifier": "caliblur",
"label": "caliBlur! Dark Theme",
"configurable": True,
"css_files": (
"css/caliBlur.css",
"css/caliBlur_override.css"
),
"js_files": (
"js/libs/jquery.visible.min.js",
"js/libs/compromise.min.js",
"js/libs/readmore.min.js",
"js/caliBlur.js"
),
"body_class": "blur",
"show_home_shortcuts": True,
"profile_dropdown": True,
"show_upload_loader": True
},
{
"id": 2,
"identifier": "simple",
"label": "Simple Theme",
"configurable": False,
"css_files": (),
"js_files": (),
"body_class": "",
"show_home_shortcuts": False,
"profile_dropdown": False,
"show_upload_loader": False
}
)
THEMES_BY_ID = {theme["id"]: theme for theme in THEMES}
THEMES_BY_IDENTIFIER = {theme["identifier"]: theme for theme in THEMES}
DEFAULT_THEME = THEMES_BY_ID[0]
SIMPLE_THEME_IDENTIFIER = THEMES_BY_ID[2]["identifier"]
def get_available_themes():
return [theme for theme in THEMES if theme["configurable"]]
def get_default_theme():
return DEFAULT_THEME
def get_theme(theme_id):
try:
theme_id = int(theme_id)
except (TypeError, ValueError):
return DEFAULT_THEME
return THEMES_BY_ID.get(theme_id, DEFAULT_THEME)
def is_valid_theme(theme_id):
try:
theme_id = int(theme_id)
except (TypeError, ValueError):
return False
theme = THEMES_BY_ID.get(theme_id)
return bool(theme and theme["configurable"])
def get_theme_identifier(theme_id, blueprint_name=None):
if blueprint_name == "basic":
return SIMPLE_THEME_IDENTIFIER
return get_theme(theme_id)["identifier"]
def get_theme_by_identifier(identifier):
return THEMES_BY_IDENTIFIER.get(identifier, DEFAULT_THEME)
+7
View File
@@ -0,0 +1,7 @@
{
"name": "caliBlur! Dark Theme",
"identifier": "caliblur",
"application": "cps",
"author": "Calibre-Web",
"description": "Dark caliBlur theme"
}
@@ -0,0 +1,334 @@
{% extends theme("layout.html") %}
{% block body %}
{% if book %}
<div class="col-sm-3 col-lg-3 col-xs-12">
<div class="cover">
<!-- Always use full-sized image for the book edit page -->
<img id="detailcover" title="{{book.title}}" src="{{url_for('web.get_cover', book_id=book.id, resolution='og', c=book|last_modified)}}" />
</div>
{% if current_user.role_delete_books() %}
<div class="text-center">
<button type="button" class="btn btn-danger delete-book-btn-tooltip" id="delete" data-toggle="modal" data-delete-id="{{ book.id }}" data-target="#deleteModal" data-toggle-two="tooltip" data-placement="bottom" data-viewport=".btn-toolbar" title="{{_('Delete Book')}}">{{_("Delete Book")}}</button>
</div>
{% if book.data|length > 1 %}
<div class="text-center more-stuff">
<button id="deleteButton" type="button" class="btn btn-danger dropdown-toggle" data-toggle="dropdown" aria-haspopup="true" aria-expanded="false"><span class="glyphicon glyphicon-remove"></span>{{_('Delete Format')}}<span class="caret"></span></button>
<ul class="dropdown-menu delete-dropdown">
{% for file in book.data %}
<li><button type="button" class="btn btn-danger" id="delete_format" data-toggle="modal" data-delete-id="{{ book.id }}" data-delete-format="{{ file.format }}" data-target="#deleteModal">{{_('Delete')}} - {{file.format}}</button></li>
{% endfor %}
</ul>
</div>
{% endif %}
{% endif %}
{% if source_formats|length > 0 and conversion_formats|length > 0 %}
<div class="text-center more-stuff"><h4>{{_('Convert book format:')}}</h4>
<form class="padded-bottom" action="{{ url_for('edit-book.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="text-left">
<label class="control-label" for="book_format_from">{{_('Convert from:')}}</label>
<select class="form-control" name="book_format_from" id="book_format_from">
<option disabled selected value>-- {{_('select an option')}} --</option>
{% for format in source_formats %}
<option>{{format|upper}}</option>
{% endfor %}
</select>
<label class="control-label" for="book_format_to">{{_('Convert to:')}}</label>
<select class="form-control" name="book_format_to" id="book_format_to">
<option disabled selected value>-- {{_('select an option')}} --</option>
{% for format in conversion_formats %}
<option>{{format|upper}}</option>
{% endfor %}
</select>
</div>
</div>
<button type="submit" class="btn btn-primary" id="btn-book-convert" name="btn-book-convert"><span class="glyphicon glyphicon-duplicate"></span> {{_('Convert book')}}</button>
</form>
</div>
{% endif %}
{% if current_user.role_upload() and g.allow_upload %}
<div class="text-center more-stuff"><!--h4 aria-label="Upload new book format"></h4-->
<form id="form-upload-format" action="{{ url_for('edit-book.upload') }}" data-title="{{_('Uploading...')}}" data-footer="{{_('Close')}}" data-failed="{{_('Error')}}" data-message="{{_('Upload done, processing, please wait...')}}" method="post" enctype="multipart/form-data">
<div class="text-center">
<input type="hidden" name="csrf_token" value="{{ csrf_token() }}">
<input type="hidden" name="book_id" value="{{ book.id }}">
<div role="group" aria-label="Upload new book format">
<label class="btn btn-primary btn-file" for="btn-upload-format">{{ _('Upload Format') }}</label>
<div class="upload-format-input-text" id="upload-format"></div>
<input id="btn-upload-format" name="btn-upload-format" type="file" accept="{% for format in accept %}.{% if format != ''%}{{format}}{% else %}*{% endif %}{{ ',' if not loop.last }}{% endfor %}" multiple>
</div>
</div>
</form>
</div>
{% endif %}
</div>
<form role="form" action="{{ url_for('edit-book.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">
<h2>{{_('Edit Metadata')}}</h2>
<div class="form-group">
<label for="title">{{_('Book Title')}}</label>
<input type="text" class="form-control" name="title" id="title" value="{{book.title}}">
</div>
<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>
</div>
<div id="author_div" class="form-group">
<label for="bookAuthor">{{_('Author')}}</label>
<input type="text" class="form-control typeahead" autocomplete="off" name="authors" id="authors" value="{{' & '.join(authors)}}">
</div>
<div class="form-group">
<label for="tags">{{_('Tags')}}</label>
<input type="text" class="form-control typeahead" autocomplete="off" name="tags" id="tags" value="{% for tag in book.tags %}{{tag.name.strip()}}{% if not loop.last %}, {% endif %}{% endfor %}">
</div>
<div class="form-group">
<label for="series">{{_('Series')}}</label>
<input type="text" class="form-control typeahead" autocomplete="off" name="series" id="series" value="{% if book.series %}{{book.series[0].name}}{% endif %}">
</div>
<div class="form-group">
<label for="series_index">{{_('Series ID')}}</label>
<input type="number" step="0.01" min="0" placeholder="1" class="form-control" name="series_index" id="series_index" value="{{book.series_index|formatfloat(2)}}">
</div>
<label for="pubdate">{{_('Published Date')}}</label>
<div class="form-group input-group">
<input type="text" class="datepicker form-control" name="pubdate" id="pubdate" value="{% if book.pubdate %}{{book.pubdate|formatdateinput}}{% endif %}">
<input type="text" class="form-control fake-input hidden" id="fake_pubdate" value="{% if book.pubdate %}{{book.pubdate|formatdate}}{% endif %}">
<span class="input-group-btn">
<button type="button" id="pubdate_delete" class="datepicker_delete btn btn-default"><span class="glyphicon glyphicon-remove-circle"></span></button>
</span>
</div>
<div class="form-group">
<label for="publisher">{{_('Publisher')}}</label>
<input type="text" class="form-control typeahead" autocomplete="off" name="publisher" id="publisher" value="{% if book.publishers|length > 0 %}{{book.publishers[0].name}}{% endif %}">
</div>
<div class="form-group">
<label for="languages">{{_('Language')}}</label>
<input type="text" class="form-control typeahead" autocomplete="off" name="languages" id="languages" value="{% for language in book.languages %}{{language.language_name.strip()}}{% if not loop.last %}, {% endif %}{% endfor %}">
</div>
<div class="form-group">
<label for="rating">{{_('Rating')}}</label>
<input type="number" name="rating" id="rating" class="rating input-lg" data-clearable="" value="{% if book.ratings %}{{(book.ratings[0].rating / 2)|int}}{% endif %}">
</div>
<div class="form-group">
<label for="comments">{{_('Description')}}</label>
<textarea class="form-control" name="comments" id="comments" rows="7">{% if book.comments %}{{book.comments[0].text}}{%endif%}</textarea>
</div>
<div class="form-group">
<label>{{_('Identifiers')}}</label>
<table class="table" id="identifier-table"><tbody>
{% for identifier in book.identifiers %}
<tr>
<td><input type="text" class="form-control" name="identifier-type-{{identifier.type}}" value="{{identifier.type}}" required="required" placeholder="{{_('Identifier Type')}}"></td>
<td><input type="text" class="form-control" name="identifier-val-{{identifier.type}}" value="{{identifier.val}}" required="required" placeholder="{{_('Identifier Value')}}"></td>
<td><a class="btn btn-default" onclick="removeIdentifierLine(this)">{{_('Remove')}}</a></td>
</tr>
{% endfor %}
</tbody>
</table>
<a id="add-identifier-line" class="btn btn-default">{{_('Add Identifier')}}</a>
</div>
{% if current_user.role_upload() and g.allow_upload %}
<div class="form-group">
<label for="cover_url">{{_('Fetch Cover from URL (JPEG - Image will be downloaded and stored in database)')}}</label>
<input type="text" class="form-control" name="cover_url" id="cover_url" value="">
</div>
<div class="form-group" aria-label="Upload cover from local drive">
<label class="btn btn-primary btn-file" for="btn-upload-cover">{{ _('Upload Cover from Local Disk') }}</label>
<div class="upload-cover-input-text" id="upload-cover"></div>
<input id="btn-upload-cover" name="btn-upload-cover" type="file" accept=".jpg, .jpeg, .png, .webp">
</div>
{% endif %}
{% if cc|length > 0 %}
{% for c in cc %}
<div class="form-group">
<label for="{{ 'custom_column_' ~ c.id }}">{{ c.name }}</label>
{% if c.datatype == 'bool' %}
<select name="{{ 'custom_column_' ~ c.id }}" id="{{ 'custom_column_' ~ c.id }}" class="form-control">
<option value="None" {% if book['custom_column_' ~ c.id]|length == 0 %} selected {% endif %}></option>
<option value="True" {% if book['custom_column_' ~ c.id]|length > 0 %}{% if book['custom_column_' ~ c.id][0].value ==true %}selected{% endif %}{% endif %} >{{_('Yes')}}</option>
<option value="False" {% if book['custom_column_' ~ c.id]|length > 0 %}{% if book['custom_column_' ~ c.id][0].value ==false %}selected{% endif %}{% endif %}>{{_('No')}}</option>
</select>
{% endif %}
{% if c.datatype == 'int' or c.datatype == 'float' %}
<input type="number" step="{% if c.datatype == 'float' %}0.01{% else %}1{% endif %}" class="form-control" name="{{ 'custom_column_' ~ c.id }}" id="{{ 'custom_column_' ~ c.id }}" value="{% if book['custom_column_' ~ c.id]|length > 0 %}{{ book['custom_column_' ~ c.id][0].value }}{% endif %}">
{% endif %}
{% if c.datatype == 'text' %}
<input type="text" class="form-control" name="{{ 'custom_column_' ~ c.id }}" id="{{ 'custom_column_' ~ c.id }}"
{% if book['custom_column_' ~ c.id]|length > 0 %}
value="{% for column in book['custom_column_' ~ c.id] %}{{ column.value.strip() }}{% if not loop.last %}, {% endif %}{% endfor %}"{% endif %}>
{% endif %}
{% if c.datatype == 'series' %}
<input type="text" class="form-control" name="{{ 'custom_column_' ~ c.id }}" id="{{ 'custom_column_' ~ c.id }}"
{% if book['custom_column_' ~ c.id]|length > 0 %}
value="{% for column in book['custom_column_' ~ c.id] %} {{ '%s [%s]' % (book['custom_column_' ~ c.id][0].value, book['custom_column_' ~ c.id][0].extra|formatfloat(2)) }}{% if not loop.last %}, {% endif %}{% endfor %}"
{% endif %}>
{% endif %}
{% if c.datatype == 'datetime' %}
<div class="input-group">
<input type="text" class="datepicker form-control" name="{{ 'custom_column_' ~ c.id }}" id="{{ 'custom_column_' ~ c.id }}"
{% if book['custom_column_' ~ c.id]|length > 0 %}
value="{% if book['custom_column_' ~ c.id][0].value %}{{ book['custom_column_' ~ c.id][0].value|formatdateinput}}{% endif %}"
{% endif %}>
<input type="text" class="fake_custom_column_{{ c.id }} form-control fake-input hidden" id="fake_pubdate_{{ c.id }}"
{% if book['custom_column_' ~ c.id]|length > 0 %}
value="{% if book['custom_column_' ~ c.id][0].value %}{{book['custom_column_' ~ c.id][0].value|formatdate}}{% endif %}"
{% endif %}>
<span class="input-group-btn">
<button type="button" id="{{ 'custom_column_' ~ c.id }}_delete" class="datepicker_delete btn btn-default"><span class="glyphicon glyphicon-remove-circle"></span></button>
</span>
</div>
{% endif %}
{% if c.datatype == 'comments' %}
<textarea class="form-control tiny_editor" name="{{ 'custom_column_' ~ c.id }}" id="{{ 'custom_column_' ~ c.id }}" rows="7">{% if book['custom_column_' ~ c.id]|length > 0 %}{{book['custom_column_' ~ c.id][0].value}}{%endif%}</textarea>
{% endif %}
{% if c.datatype == 'enumeration' %}
<select class="form-control" name="{{ 'custom_column_' ~ c.id }}" id="{{ 'custom_column_' ~ c.id }}">
<option></option>
{% for opt in c.get_display_dict().enum_values %}
<option
{% if book['custom_column_' ~ c.id]|length > 0 %}
{% if book['custom_column_' ~ c.id][0].value == opt %}selected="selected"{% endif %}
{% endif %}
>{{ opt }}</option>
{% endfor %}
</select>
{% endif %}
{% if c.datatype == 'rating' %}
<input type="number" min="1" max="5" step="0.5" class="form-control" name="{{ 'custom_column_' ~ c.id }}" id="{{ 'custom_column_' ~ c.id }}"
{% if book['custom_column_' ~ c.id]|length > 0 %}
value="{{ '%.1f' % (book['custom_column_' ~ c.id][0].value / 2) }}"
{% endif %}>
{% endif %}
</div>
{% endfor %}
{% endif %}
<div class="checkbox">
<label>
<input name="detail_view" type="checkbox" checked> {{_('View Book on Save')}}
</label>
</div>
<a href="#" id="get_meta" class="btn btn-default" data-toggle="modal" data-target="#metaModal">{{_('Fetch Metadata')}}</a>
<button type="submit" id="submit" class="btn btn-default">{{_('Save')}}</button>
<a href="{{ url_for('web.show_book', book_id=book.id) }}" id="edit_cancel" class="btn btn-default">{{_('Cancel')}}</a>
</div>
</form>
{% endif %}
{% endblock %}
{% block modal %}
{{ delete_book(current_user.role_delete_books()) }}
{{ delete_confirm_modal() }}
<div class="modal fade" id="metaModal" tabindex="-1" role="dialog" aria-labelledby="metaModalLabel">
<div class="modal-dialog modal-lg" role="document">
<div class="modal-content">
<div class="modal-header">
<button type="button" class="close" data-dismiss="modal" aria-label="Close"><span aria-hidden="true">&times;</span></button>
<h4 class="modal-title text-center" id="metaModalLabel">{{_('Fetch Metadata')}}</h4>
<form class="padded-bottom" id="meta-search">
<div class="input-group">
<label class="sr-only" for="keyword">{{_('Keyword')}}</label>
<input type="text" class="form-control" id="keyword" name="keyword" placeholder="{{_("Search keyword")}}">
<span class="input-group-btn">
<button type="submit" class="btn btn-primary" id="do-search">{{_("Search")}}</button>
</span>
</div>
</form>
<div class="text-center"><strong>{{_('Click the cover to load metadata to the form')}}</strong></div>
</div>
<div class="modal-body">
<div class="text-center padded-bottom" id="metadata_provider">
</div>
<div id="meta-info">
{{_("Loading...")}}
</div>
</div>
<div class="modal-footer">
<button id="meta_close" type="button" class="btn btn-default" data-dismiss="modal">{{_('Close')}}</button>
</div>
</div>
</div>
</div>
{% endblock %}
{% block js %}
<script type="text/template" id="template-book-result">
<li class="media" data-related="<%- source.id %>">
<img class="pull-left img-responsive"
data-toggle="modal"
data-target="#metaModal"
src="<%- cover || '{{ url_for('static', filename='img/academicpaper.svg') }}' %>"
alt="Cover"
>
<div class="media-body">
<h4 class="media-heading">
<a class="meta_title" href="<%- url %>" target="_blank" rel="noopener"><%- title %></a>
</h4>
<p class="meta_author">{{_('Author')}}:<%- authors.join(" & ") %></p>
<% if (publisher) { %>
<p class="meta_publisher">{{_('Publisher')}}:<%- publisher %></p>
<% } %>
<% if (description) { %>
<p class="meta_description">{{_('Description')}}: <%- description %></p>
<% } %>
<p>{{_('Source')}}:
<a class="meta_source" href="<%- source.link %>" target="_blank" rel="noopener"><%- source.description %></a>
</p>
</div>
</li>
</script>
<script>
var i18nMsg = {
'loading': {{_('Loading...')|safe|tojson}},
'search_error': {{_('Search error!')|safe|tojson}},
'no_result': {{_('No Result(s) found! Please try another keyword.')|safe|tojson}},
'author': {{_('Author')|safe|tojson}},
'publisher': {{_('Publisher')|safe|tojson}},
'comments': {{_('Description')|safe|tojson}},
'source': {{_('Source')|safe|tojson}},
};
var language = '{{ current_user.locale }}';
$("#add-identifier-line").click(function() {
// create a random identifier type to have a valid name in form. This will not be used when dealing with the form
var rand_id = Math.floor(Math.random() * 1000000).toString();
var line = '<tr>';
line += '<td><input type="text" class="form-control" name="identifier-type-'+ rand_id +'" required="required" placeholder={{_('Identifier Type')|safe|tojson}}></td>';
line += '<td><input type="text" class="form-control" name="identifier-val-'+ rand_id +'" required="required" placeholder={{_('Identifier Value')|safe|tojson}}></td>';
line += '<td><a class="btn btn-default" onclick="removeIdentifierLine(this)">{{_('Remove')}}</a></td>';
line += '</tr>';
$("#identifier-table").append(line);
});
function removeIdentifierLine(el) {
$(el).parent().parent().remove();
}
</script>
<script src="{{ url_for('static', filename='js/libs/typeahead.bundle.js') }}"></script>
<script src="{{ url_for('static', filename='js/libs/bootstrap-rating-input.min.js') }}"></script>
<script src="{{ url_for('static', filename='js/get_meta.js') }}"></script>
<script src="{{ url_for('static', filename='js/libs/tinymce/tinymce.min.js') }}"></script>
<script src="{{ url_for('static', filename='js/libs/bootstrap-datepicker/bootstrap-datepicker.min.js') }}"></script>
{% if not current_user.locale == 'en' %}
<script src="{{ url_for('static', filename='js/libs/bootstrap-datepicker/locales/bootstrap-datepicker.' + current_user.locale + '.min.js') }}" charset="UTF-8"></script>
{% endif %}
<script src="{{ url_for('static', filename='js/edit_books.js') }}"></script>
<script src="{{ url_for('static', filename='js/fullscreen.js') }}"></script>
{% endblock %}
{% block header %}
<meta name="referrer" content="never">
<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">
{% endblock %}
@@ -0,0 +1,91 @@
{% extends theme("layout.html") %}
{% block flash %}
<div id="spinning_success" class="row-fluid text-center" style="display:none;">
<div class="alert alert-info"><img id="img-spinner" src="{{ url_for('static', filename='css/libs/images/loading-icon.gif') }}" alt="" aria-hidden="true"></div>
</div>
{% endblock %}
{% block body %}
<div class="discover">
<h2>{{ title }}</h2>
<form id="db_config_form" role="form" method="POST" class="caliblur-dbconfig-form" action="{{ url_for('admin.db_configuration') }}" autocomplete="off">
<div class="col-md-10 col-lg-6">
<input type="hidden" name="csrf_token" value="{{ csrf_token() }}">
<label for="config_calibre_dir">{{ _('Location of Calibre Database') }}</label>
<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">
<span class="input-group-btn">
<button type="button" data-toggle="modal" id="calibre_modal_path" data-link="config_calibre_dir" data-filefilter="metadata.db" data-target="#fileModal" class="btn btn-default"><span class="glyphicon glyphicon-folder-open"></span></button>
</span>
</div>
<div class="form-group required">
<input type="checkbox" id="config_calibre_split" name="config_calibre_split" data-control="split_settings" data-t ="{{ config.config_calibre_split_dir }}" {% if config.config_calibre_split %}checked{% endif %}>
<label for="config_calibre_split">{{ _('Separate Book Files from Library') }}</label>
</div>
<div data-related="split_settings">
<div class="form-group required input-group">
<input type="text" class="form-control" id="config_calibre_split_dir" name="config_calibre_split_dir" value="{% if config.config_calibre_split_dir != None %}{{ config.config_calibre_split_dir }}{% endif %}" autocomplete="off">
<span class="input-group-btn">
<button type="button" data-toggle="modal" id="calibre_modal_split_path" data-link="config_calibre_split_dir" data-filefilter="" data-target="#fileModal" class="btn btn-default"><span class="glyphicon glyphicon-folder-open"></span></button>
</span>
</div>
</div>
{% 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 %}>
<label for="config_use_google_drive">{{ _('Use Google Drive?') }}</label>
</div>
{% if not gdriveError and config.config_use_google_drive %}
{% if show_authenticate_google_drive and config.config_use_google_drive %}
<div class="form-group required">
<a href="{{ url_for('gdrive.authenticate_google_drive') }}" id="gdrive_auth" class="btn btn-primary">{{ _('Authenticate Google Drive') }}</a>
</div>
{% else %}
{% if not show_authenticate_google_drive %}
<div class="form-group required">
<label for="config_google_drive_folder">{{ _('Google Drive Calibre folder') }}</label>
<select name="config_google_drive_folder" id="config_google_drive_folder" class="form-control">
{% for gdrivefolder in gdrivefolders %}
<option value="{{ gdrivefolder.title }}" {% if gdrivefolder.title == config.config_google_drive_folder %}selected{% endif %}>{{ gdrivefolder.title }}</option>
{% endfor %}
</select>
</div>
{% if config.config_google_drive_watch_changes_response %}
<label for="config_google_drive_watch_changes_response">{{ _('Metadata Watch Channel ID') }}</label>
<div class="form-group input-group required">
<input type="text" class="form-control" name="config_google_drive_watch_changes_response" id="config_google_drive_watch_changes_response" value="{{ config.config_google_drive_watch_changes_response['id'] }} expires on {{ config.config_google_drive_watch_changes_response['expiration'] | strftime }}" autocomplete="off" disabled="">
<span class="input-group-btn"><a href="{{ url_for('gdrive.revoke_watch_gdrive') }}" id="watch_revoke" class="btn btn-primary">{{ _('Revoke') }}</a></span>
</div>
{% else %}
<a href="{{ url_for('gdrive.watch_gdrive') }}" id="enable_gdrive_watch" class="btn btn-primary">Enable watch of metadata.db</a>
{% endif %}
{% endif %}
{% endif %}
{% endif %}
{% endif %}
</div>
<div class="col-sm-12 clearfix caliblur-dbconfig-actions">
<div class="pull-right">
<a href="{{ url_for('admin.admin') }}" id="config_back" class="btn btn-primary">{{ _('Cancel') }}</a>
<button id="db_submit" type="submit" form="db_config_form" class="btn btn-default">{{ _('Save') }}</button>
</div>
</div>
</form>
</div>
{% endblock %}
{% block modal %}
{{ filechooser_modal() }}
{{ change_confirm_modal() }}
<div id="InvalidDialog" class="modal fade" role="dialog">
<div class="modal-dialog modal-sm">
<div class="modal-content">
<div class="modal-header bg-info"></div>
<div class="modal-body text-center">
<p>{{ _('New db location is invalid, please enter valid path') }}</p>
<p></p>
<button type="button" class="btn btn-default" id="invalid_confirm" data-dismiss="modal">{{ _('OK') }}</button>
</div>
</div>
</div>
</div>
{% endblock %}
+374
View File
@@ -0,0 +1,374 @@
{% extends is_xhr|yesno("fragment.html", theme("layout.html")) %}
{% block header %}
<meta property="og:type" content="book" />
<meta property="og:title" content="{{ entry.title|truncate(35) }}" />
{% if entry.comments|length > 0 and entry.comments[0].text|length > 0 %}
<meta property="og:description" content="{{ entry.comments[0].text|striptags|truncate(65) }}" />
<meta property="og:image" content="{{url_for('web.get_cover', book_id=entry.id, resolution='og', c=entry|last_modified)}}" />
{% endif %}
{% endblock %}
{% block body %}
<div class="single">
<div class="row">
<div class="col-sm-3 col-lg-3 col-xs-5">
<div class="cover">
<!-- Always use full-sized image for the detail page -->
<img id="detailcover" title="{{ entry.title }}"
src="{{ url_for('web.get_cover', book_id=entry.id, resolution='og', c=entry|last_modified) }}"/>
</div>
</div>
<div class="col-sm-9 col-lg-9 book-meta">
<div class="btn-toolbar" role="toolbar">
<div class="btn-group" role="group" aria-label="Download, send to eReader, reading">
{% if not current_user.is_anonymous %}
{% if current_user.shelf.all() or g.shelves_access %}
<div id="shelf-actions" class="btn-toolbar" role="toolbar">
<div class="btn-group" role="group" aria-label="Add to shelves">
<button id="add-to-shelf" type="button"
class="btn btn-primary btn-sm dropdown-toggle addtoshelf-btn-tooltip"
data-toggle-two="tooltip" title="{{ _('Add to shelf') }}" data-placement="bottom" data-viewport=".btn-toolbar"
data-toggle="dropdown"
aria-haspopup="true" aria-expanded="false">
<span class="glyphicon glyphicon-list"></span> {{ _('Add to shelf') }}
<span class="caret"></span>
</button>
<ul id="add-to-shelves" class="dropdown-menu" aria-labelledby="add-to-shelf">
{% for shelf in g.shelves_access %}
{% if not shelf.id in books_shelfs and ( not shelf.is_public or current_user.role_edit_shelfs() ) %}
<li>
<a data-href="{{ url_for('shelf.add_to_shelf', book_id=entry.id, shelf_id=shelf.id) }}"
data-remove-href="{{ url_for('shelf.remove_from_shelf', book_id=entry.id, shelf_id=shelf.id) }}"
data-shelf-action="add"
>
{{ shelf.name }}{% if shelf.is_public == 1 %}
{{ _('(Public)') }}{% endif %}
</a>
</li>
{% endif %}
{% endfor %}
</ul>
</div>
<div id="remove-from-shelves" class="btn-group" role="group"
aria-label="Remove from shelves">
{% if books_shelfs %}
{% for shelf in g.shelves_access %}
{% if shelf.id in books_shelfs %}
<a data-href="{{ url_for('shelf.remove_from_shelf', book_id=entry.id, shelf_id=shelf.id) }}"
data-add-href="{{ url_for('shelf.add_to_shelf', book_id=entry.id, shelf_id=shelf.id) }}"
class="btn btn-sm btn-default" role="button"
data-shelf-action="remove"
>
<span {% if not shelf.is_public or current_user.role_edit_shelfs() %}
class="glyphicon glyphicon-remove"
{% endif %}></span> {{ shelf.name }}{% if shelf.is_public == 1 %} {{ _('(Public)') }}{% endif %}
</a>
{% endif %}
{% endfor %}
{% endif %}
</div>
<div id="shelf-action-errors" class="pull-left" role="alert"></div>
</div>
{% endif %}
<div class="btn-group" role="group" aria-label="Read status">
<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">
<input id="have_read_cb" class="readunread-btn-tooltip" type="checkbox"
data-toggle="tooltip" data-placement="bottom" data-viewport=".btn-toolbar"
title="{% if entry.read_status %}{{ _('Mark As Unread') }}{% else %}{{ _('Mark As Read') }}{% endif %}"
{% if entry.read_status %}checked{% endif %}>
<label class="block-label readLbl" for="have_read_cb"></label>
<span data-toggle="tooltip" title="{{_('Mark Book as Read or Unread')}}">{{ _('Read') }}</span>
</label>
</form>
</div>
{% endif %}
{% if current_user.role_edit() %}
<div class="btn-group" role="group" aria-label="Edit book">
<a href="{{ url_for('edit-book.show_edit_book', book_id=entry.id) }}"
class="btn btn-primary edit-btn-tooltip" id="edit_book" role="button"
data-toggle="tooltip" title="{{ _('Edit Metadata') }}" data-placement="bottom" data-viewport=".btn-toolbar">
<span class="edit-book-icon" data-toggle="tooltip" title="{{ _('Edit Metadata') }}"
data-placement="bottom" data-viewport=".btn-toolbar"></span> {{ _('Edit Metadata') }}
</a>
</div>
{% endif %}
{% if current_user.role_download() %}
{% if entry.data|length %}
<div class="btn-group downloadBtn" role="group">
{% if entry.data|length > 0 %}
<button id="btnGroupDrop1" type="button" class="btn btn-primary"
aria-haspopup="true" aria-expanded="false"
data-toggle-two="tooltip" title="" data-placement="bottom" data-viewport=".btn-toolbar"
data-original-title="Download :"><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|replace('kepub', 'kepub.epub')) }}"
id="btnGroupDrop1{{ format.format|lower }}">{{ format.format }}
({{ format.uncompressed_size|filesizeformat }})</a></li>
{% endfor %}
</ul>
{% endif %}
</div>
{% endif %}
{% if current_user.kindle_mail and entry.email_share_list %}
<input type="hidden" name="csrf_token" value="{{ csrf_token() }}">
{% if entry.email_share_list.__len__() == 1 %}
<div class="btn-group sendBtn" role="group">
<button id="sendbtn" class="btn btn-primary sendbtn-form send-btn-tooltip" data-href="{{url_for('web.send_to_ereader', book_id=entry.id, book_format=entry.email_share_list[0]['format'], convert=entry.email_share_list[0]['convert'])}}" data-toggle="tooltip" data-placement="bottom" data-viewport=".btn-toolbar" title="{{ entry.email_share_list[0]['text'] }}">
<span class="glyphicon glyphicon-send"></span> {{entry.email_share_list[0]['text']}}
</button>
</div>
{% else %}
<div class="btn-group sendBtn" role="group">
<button id="sendbtn2" type="button" class="btn btn-primary dropdown-toggle send-btn-tooltip"
data-toggle="dropdown" data-toggle-two="tooltip" data-placement="bottom" data-viewport=".btn-toolbar" title="{{ _('Send to eReader') }}" aria-haspopup="true" aria-expanded="false">
<span class="glyphicon glyphicon-send"></span>{{ _('Send to eReader') }}
<span class="caret"></span>
</button>
<ul class="dropdown-menu" aria-labelledby="send-to-ereader">
{% for format in entry.email_share_list %}
<li>
<a class="sendbtn-form" data-href="{{url_for('web.send_to_ereader', book_id=entry.id, book_format=format['format'], convert=format['convert'])}}">{{ format['text'] }}</a>
</li>
{% endfor %}
</ul>
</div>
{% endif %}
{% endif %}
{% endif %}
{% if entry.reader_list and current_user.role_viewer() %}
<div class="btn-group readBtn" role="group">
{% if entry.reader_list|length > 1 %}
<button id="read-in-browser" type="button" class="btn btn-primary dropdown-toggle send-btn-tooltip"
data-toggle="dropdown" data-toggle-two="tooltip" data-placement="bottom" data-viewport=".btn-toolbar" title="{{ _('Read in Browser') }}" aria-haspopup="true" aria-expanded="false">
<span class="glyphicon glyphicon-book"></span> {{ _('Read in Browser') }}
<span class="caret"></span>
</button>
<ul class="dropdown-menu" aria-labelledby="read-in-browser">
{% for format in entry.reader_list %}
<li><a
href="{{ url_for('web.read_book', book_id=entry.id, book_format=format) }}">{{ format }}</a>
</li>
{% endfor %}
</ul>
{% else %}
<a target="_blank"
href="{{ url_for('web.read_book', book_id=entry.id, book_format=entry.reader_list[0]) }}"
id="readbtn" class="btn btn-primary" role="button"
data-toggle-two="tooltip" data-placement="bottom" data-viewport=".btn-toolbar"
title="{{ _('Read in Browser') }}"><span
class="glyphicon glyphicon-book"></span> {{ _('Read in Browser') }}
- {{ entry.reader_list[0] }}</a>
{% endif %}
</div>
{% endif %}
{% if entry.audio_entries|length > 0 and current_user.role_viewer() %}
<div class="btn-group listenBtn" role="group">
{% if entry.audio_entries|length > 1 %}
<button id="listen-in-browser" type="button" class="btn btn-primary dropdown-toggle send-btn-tooltip" data-toggle="dropdown" data-toggle-two="tooltip" data-placement="bottom" data-viewport=".btn-toolbar" title="{{ _('Listen in Browser') }}" aria-haspopup="true" aria-expanded="false">
<span class="glyphicon glyphicon-music"></span> {{ _('Listen in Browser') }}
<span class="caret"></span>
</button>
<ul class="dropdown-menu" aria-labelledby="listen-in-browser">
{% for format in entry.reader_list %}
<li><a href="{{ url_for('web.read_book', book_id=entry.id, book_format=format) }}">{{ format }}</a>
</li>
{% endfor %}
</ul>
<ul class="dropdown-menu" aria-labelledby="listen-in-browser">
{% for format in entry.data %}
{% if format.format|lower in entry.audio_entries %}
<li><a
href="{{ url_for('web.read_book', book_id=entry.id, book_format=format.format|lower) }}">{{ format.format|lower }}</a>
</li>
{% endif %}
{% endfor %}
</ul>
{% else %}
<a target="_blank" href="{{ url_for('web.read_book', book_id=entry.id, book_format=entry.audio_entries[0]) }}" id="listenbtn" class="btn btn-primary" role="button"><span class="glyphicon glyphicon-music"></span> {{ _('Listen in Browser') }} - {{ entry.audio_entries[0] }}</a>
{% endif %}
</div>
{% endif %}
</div>
</div>
<h2 id="title">{{ entry.title }}</h2>
<p class="author">
<span class="author-prefix">{{ _('Author') }} — </span>
{% for author in entry.ordered_authors %}
<a href="{{ url_for('web.books_list', data='author', sort_param='stored', book_id=author.id ) }}">{{ author.name.replace('|',',') }}</a>
{% if not loop.last %}
&amp;
{% endif %}
{% endfor %}
</p>
{% if entry.ratings.__len__() > 0 %}
<div class="rating">
<p>
{% for number in range((entry.ratings[0].rating/2)|int(2)) %}
<span class="glyphicon glyphicon-star good"></span>
{% if loop.last and loop.index < 5 %}
{% for numer in range(5 - loop.index) %}
<span class="glyphicon glyphicon-star-empty"></span>
{% endfor %}
{% endif %}
{% endfor %}
</p>
</div>
{% endif %}
{% if entry.series|length > 0 %}
<p>{{ _("Book %(index)s of %(range)s", index=entry.series_index|formatfloat(2), range=(url_for('web.books_list', data='series', sort_param='stored', book_id=entry.series[0].id)|escapedlink(entry.series[0].name))|safe) }}</p>
{% endif %}
{% if entry.languages|length > 0 %}
<div class="languages">
<p>
<span class="label label-default">{{_('Language')}}: {% for language in entry.languages %}{{language.language_name}}{% if not loop.last %}, {% endif %}{% endfor %}</span>
</p>
</div>
{% endif %}
{% if entry.identifiers|length > 0 %}
<div class="identifiers">
<p>
<span class="glyphicon glyphicon-link"></span>
{% for identifier in entry.identifiers %}
{% set identifier_link = identifier.__repr__() %}
{% if identifier_link != identifier.val %}
<a href="{{ identifier_link|escape }}" target="_blank" class="btn btn-xs btn-success"
role="button">{{ identifier.format_type() }}</a>
{% else %}
<span class="btn btn-xs btn-success" role="button">{{ identifier.format_type() }}</span>
{% endif %}
{% endfor %}
</p>
</div>
{% endif %}
{% if entry.tags|length > 0 %}
<div class="tags">
<p>
<span class="glyphicon glyphicon-tags"></span>
{% for tag in entry.tags %}
<a href="{{ url_for('web.books_list', data='category', sort_param='stored', book_id=tag.id) }}"
class="btn btn-xs btn-info" role="button">{{ tag.name }}</a>
{% endfor %}
</p>
</div>
{% endif %}
{% if entry.publishers|length > 0 %}
<div class="publishers">
<p>
<span>{{ _('Publisher') }}:
<a href="{{ url_for('web.books_list', data='publisher', sort_param='stored', book_id=entry.publishers[0].id ) }}">{{ entry.publishers[0].name }}</a>
</span>
</p>
</div>
{% endif %}
{% if (entry.pubdate|string)[:10] != '0101-01-01' %}
<div class="publishing-date">
<p>{{ _('Published') }}: {{ entry.pubdate|formatdate }} </p>
</div>
{% endif %}
{% if cc|length > 0 %}
{% for c in cc %}
{% if entry['custom_column_' ~ c.id]|length > 0 %}
<div class="real_custom_columns">
{{ c.name }}:
{% for column in entry['custom_column_' ~ c.id] %}
{% if c.datatype == 'rating' %}
{{ (column.value / 2)|formatfloat }}
{% else %}
{% if c.datatype == 'bool' %}
{% if column.value == true %}
<span class="glyphicon glyphicon-ok"></span>
{% else %}
<span class="glyphicon glyphicon-remove"></span>
{% endif %}
{% else %}
{% if c.datatype == 'float' %}
{{ column.value|formatfloat(2) }}
{% elif c.datatype == 'datetime' %}
{{ column.value|formatdate }}
{% elif c.datatype == 'comments' %}
{{ column.value|clean_string|safe }}
{% elif c.datatype == 'series' %}
{{ '%s [%s]' % (column.value, column.extra|formatfloat(2)) }}
{% elif c.datatype == 'text' %}
{{ column.value.strip() }}{% if not loop.last %}, {% endif %}
{% else %}
{{ column.value }}
{% endif %}
{% endif %}
{% endif %}
{% endfor %}
</div>
{% endif %}
{% endfor %}
{% endif %}
{% if not current_user.is_anonymous %}
<div class="custom_columns">
{% if current_user.check_visibility(32768) %}
<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() }}">
</form>
{% endif %}
</div>
{% endif %}
{% if entry.comments|length > 0 and entry.comments[0].text|length > 0 %}
<div class="comments">
<h3 id="decription">{{ _('Description:') }}</h3>
{{ entry.comments[0].text|clean_string|safe }}
</div>
{% endif %}
<div class="more-stuff">
{% if current_user.role_edit() %}
<div class="col-sm-12">
<div class="btn btn-default" data-back="{{ url_for('web.index') }}" id="back">{{_('Cancel')}}</div>
</div>
{% endif %}
</div>
</div>
</div>
</div>
{% endblock %}
{% block js %}
<script type="text/template" id="template-shelf-add">
<li>
<a data-href="<%= add %>" data-remove-href="<%= remove %>" data-shelf-action="add">
<%= content %>
</a>
</li>
</script>
<script type="text/template" id="template-shelf-remove">
<a data-href="<%= remove %>" data-add-href="<%= add %>" class="btn btn-sm btn-default"
data-shelf-action="remove">
<span class="glyphicon glyphicon-remove"></span> <%= content %>
</a>
</script>
<script src="{{ url_for('static', filename='js/details.js') }}"></script>
<script src="{{ url_for('static', filename='js/fullscreen.js') }}"></script>
<script type="text/javascript">
</script>
{% endblock %}
@@ -0,0 +1,133 @@
{% extends theme("layout.html") %}
{% block header %}
<link href="{{ url_for('static', filename='css/libs/bootstrap-table.min.css') }}" rel="stylesheet">
<link href="{{ url_for('static', filename='css/libs/bootstrap-editable.css') }}" rel="stylesheet">
{% endblock %}
{% block body %}
<div class="discover">
<h1>{{title}}</h1>
<form role="form" class="col-md-10 col-lg-6" method="POST" autocomplete="off">
<input type="hidden" name="csrf_token" value="{{ csrf_token() }}">
{% if feature_support['gmail'] %}
<div>
<div class="form-group">
<label for="config_email_type">{{_('Email Account Type')}}</label>
<select name="mail_server_type" id="config_email_type" class="form-control" data-control="email-settings">
<option value="0" {% if content.mail_server_type == 0 %}selected{% endif %}>{{_('Standard Email Account')}}</option>
<option value="1" {% if content.mail_server_type == 1 %}selected{% endif %}>{{_('Gmail Account')}}</option>
</select>
</div>
</div>
<div data-related="email-settings-1">
<div class="form-group">
{% if content.mail_gmail_token == {} %}
<button type="submit" id="gmail_server" name="gmail" value="submit" class="btn btn-default">{{_('Setup Gmail Account')}}</button>
{% else %}
<button type="submit" id="invalidate_server" name="invalidate" value="submit" class="btn btn-danger">{{_('Revoke Gmail Access')}}</button>
{% endif %}
</div>
</div>
<div data-related="email-settings-0">
{% endif %}
<div class="form-group">
<label for="mail_server">{{_('SMTP Hostname')}}</label>
<input type="text" class="form-control" name="mail_server" id="mail_server" value="{{content.mail_server}}" required>
</div>
<div class="form-group">
<label for="mail_port">{{_('SMTP Port')}}</label>
<input type="number" min="1" max="65535" step="1" class="form-control" name="mail_port" id="mail_port" value="{% if content.mail_port != None %}{{ content.mail_port }}{% endif %}" autocomplete="off" required>
</div>
<div class="form-group">
<label for="mail_use_ssl">{{_('Encryption')}}</label>
<select name="mail_use_ssl" id="mail_use_ssl" class="form-control">
<option value="0" {% if content.mail_use_ssl == 0 %}selected{% endif %}>{{ _('None') }}</option>
<option value="1" {% if content.mail_use_ssl == 1 %}selected{% endif %}>{{ _('STARTTLS') }}</option>
<option value="2" {% if content.mail_use_ssl == 2 %}selected{% endif %}>{{ _('SSL/TLS') }}</option>
</select>
</div>
<div class="form-group">
<label for="mail_login">{{_('SMTP Login')}}</label>
<input type="text" class="form-control" name="mail_login" id="mail_login" value="{{content.mail_login}}">
</div>
<div class="form-group">
<label for="mail_password_e">{{_('SMTP Password')}}</label>
<input type="password" class="form-control" name="mail_password_e" id="mail_password_e" value="">
</div>
<div class="form-group">
<label for="mail_from">{{_('From Email')}}</label>
<input type="text" class="form-control" name="mail_from" id="mail_from" value="{{content.mail_from}}" required>
</div>
<label for="mail_size">{{_('Attachment Size Limit')}}</label>
<div class="form-group input-group">
<input type="number" min="1" max="600" step="1" class="form-control" name="mail_size" id="mail_size" value="{% if content.mail_size != None %}{{ (content.mail_size / 1024 / 1024)|int }}{% endif %}" style="height: 40px;" required>
<span class="input-group-btn">
<button type="button" id="attachement_size" class="btn btn-default" style="height: 40px; margin-bottom: 0;" disabled>MB</button>
</span>
</div>
{% if feature_support['gmail'] %}
</div>
{% endif %}
<div class="clearfix">
<div class="pull-right">
<a href="{{ url_for('admin.admin') }}" id="config_back" class="btn btn-primary" style="margin-bottom: 10px;">{{_('Cancel')}}</a>
<button type="submit" name="submit" value="submit" class="btn btn-default">{{_('Save')}}</button>
<button type="submit" name="test" value="test" class="btn btn-default">{{_('Save and Send Test Email')}}</button>
</div>
</div>
</form>
{% if g.allow_registration and not simple%}
<div class="col-md-10 col-lg-6">
<h2 style="color: hsla(0, 0%, 100%, .7);">{{_('Allowed Domains (Whitelist)')}}</h2>
<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">
<label for="domainname_allow">{{_('Add Domain')}}</label>
<input type="text" class="form-control" name="domainname" id="domainname_allow" >
</div>
<button id="domain_allow_submit" class="btn btn-default">{{_('Add')}}</button>
</form>
<table class="table table-no-bordered" id="domain-allow-table" data-url="{{url_for('admin.list_domain', allow=1)}}" data-id-field="id" data-show-header="false" data-editable-mode="inline" data-locale="{{ current_user.locale }}">
<thead>
<tr>
<th data-field="domain" id="domain-allow" data-escape="true" data-editable-type="text" data-editable-url="{{ url_for('admin.edit_domain', allow = 1)}}" data-editable="true" data-editable-title="{{_('Enter domainname')}}"></th>
<th data-field="id" id="id-allow" data-visible="false"></th>
<th data-align="right" data-formatter="TableActions"></th>
</tr>
</thead>
</table>
<h2 style="color: hsla(0, 0%, 100%, .7);">{{_('Denied Domains (Blacklist)')}}</h2>
<table class="table table-no-bordered" id="domain-deny-table" data-url="{{url_for('admin.list_domain', allow=0)}}" data-id-field="id" data-show-header="false" data-editable-mode="inline" data-locale="{{ current_user.locale }}">
<thead>
<tr>
<th data-field="domain" id="domain-deny" data-escape="true" data-editable-type="text" data-editable-url="{{ url_for('admin.edit_domain', allow = 0)}}" data-editable="true" data-editable-title="{{_('Enter domainname')}}"></th>
<th data-field="id" id="id-deny" data-visible="false"></th>
<th data-align="right" data-formatter="TableActions"></th>
</tr>
</thead>
</table>
<form id="domain_add_deny" action="{{ url_for('admin.add_domain',allow=0)}}" method="POST">
<input type="hidden" name="csrf_token" value="{{ csrf_token() }}">
<div class="form-group required">
<label for="domainname_deny">{{_('Add Domain')}}</label>
<input type="text" class="form-control" name="domainname" id="domainname_deny" >
</div>
<button id="domain_deny_submit" class="btn btn-default">{{_('Add')}}</button>
</form>
</div>
{% endif %}
</div>
{% endblock %}
{% block modal %}
{% if g.allow_registration %}
{{ delete_confirm_modal() }}
{% endif %}
{% endblock %}
{% block js %}
{% if g.allow_registration %}
<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-locale-all.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/table.js') }}"></script>
{% endif %}
{% endblock %}
@@ -0,0 +1,62 @@
<!DOCTYPE html>
<html class="http-error">
<head>
<title>{{ instance }} | HTTP Error ({{ error_code }})</title>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1">
<meta name="mobile-web-app-capable" content="yes">
{% if g.google_site_verification|length > 0 %}
<meta name="google-site-verification" content="{{g.google_site_verification}}">
{% endif %}
<!-- Bootstrap -->
<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 href="{{ url_for('static', filename='css/libs/bootstrap.min.css') }}" rel="stylesheet" media="screen">
<link href="{{ url_for('static', filename='css/style.css') }}" rel="stylesheet" media="screen">
<link href="{{ url_for('static', filename='css/caliBlur.css') }}" rel="stylesheet" media="screen">
<link href="{{ url_for('static', filename='css/caliBlur_override.css') }}" rel="stylesheet" media="screen">
</head>
<body>
<div class="container-fluid" style="overflow-y: auto">
<div class="row">
<div class="col">
<h1 class="text-center">{{instance}}</h1>
<h1 class="text-center">{{ error_code }}</h1>
<h3>{{ error_name }}</h3>
</div>
</div>
<div class="row">
<div class="col-md-offset-4 text-left">
{% if unconfigured %}
<div>{{_('Calibre-Web Instance is unconfigured, please contact your administrator')}}</div>
{% endif %}
{% for element in error_stack %}
<div>{{ element }}</div>
{% endfor %}
</div>
</div>
{% if issue %}
<div class="row">
<div class="col errorlink">Please report this issue with all related information:
<a href="https://github.com/janeczku/calibre-web/issues/new/choose">{{_('Create Issue')}}</a>
</div>
</div>
{% endif %}
<div class="row">
<div class="col errorlink">
{% if not unconfigured %}
{% if goto_admin %}
<a href="{{url_for('admin.db_configuration')}}" title="{{ _('Return to Database config') }}">{{_('Return to Database config')}}</a>
{% else %}
<a href="{{url_for('web.index')}}" title="{{ _('Return to Home') }}">{{_('Return to Home')}}</a>
{% endif %}
{% else %}
<a href="{{url_for('web.logout')}}" title="{{ _('Logout User') }}">{{ _('Logout User') }}</a>
{% endif %}
</div>
</div>
</div>
</body>
</html>
+156
View File
@@ -0,0 +1,156 @@
{% import 'image.html' as image %}
{% extends theme("layout.html") %}
{% block body %}
{% if current_user.show_detail_random() and page != "discover" %}
<div class="discover random-books caliblur-index">
<h2 class="random-books">{{_('Discover (Random Books)')}}</h2>
<div class="row display-flex">
{% for entry in random %}
<div class="col-sm-3 col-lg-2 col-xs-6 book session" id="books_rand">
<div class="cover">
<a href="{{ url_for('web.show_book', book_id=entry.Books.id) }}" {% if simple==false %}data-toggle="modal" data-target="#bookDetailsModal" data-remote="false"{% endif %}>
<span class="img" title="{{ entry.Books.title }}">
{{ image.book_cover(entry.Books) }}
{% if entry[2] == True %}<span class="badge read glyphicon glyphicon-ok"></span>{% endif %}
</span>
</a>
</div>
<div class="meta">
<a href="{{ url_for('web.show_book', book_id=entry.Books.id) }}" {% if simple==false %}data-toggle="modal" data-target="#bookDetailsModal" data-remote="false"{% endif %}>
<p title="{{entry.Books.title}}" class="title">{{entry.Books.title|shortentitle}}</p>
</a>
<p class="author">
{% for author in entry.Books.authors %}
{% if loop.index > g.config_authors_max and g.config_authors_max != 0 %}
{% if not loop.first %}
<span class="author-hidden-divider">&amp;</span>
{% endif %}
<a class="author-name author-hidden" href="{{url_for('web.books_list', data='author', sort_param='stored', book_id=author.id) }}">{{author.name.replace('|',',')|shortentitle(30)}}</a>
{% if loop.last %}
<a href="#" class="author-expand" data-authors-max="{{g.config_authors_max}}" data-collapse-caption="({{_('reduce')}})">(...)</a>
{% endif %}
{% else %}
{% if not loop.first %}
<span>&amp;</span>
{% endif %}
<a class="author-name" href="{{url_for('web.books_list', data='author', sort_param='stored', book_id=author.id) }}">{{author.name.replace('|',',')|shortentitle(30)}}</a>
{% endif %}
{% endfor %}
</p>
{% if entry.Books.series.__len__() > 0 %}
<p class="series">
<a href="{{url_for('web.books_list', data='series', sort_param='stored', book_id=entry.Books.series[0].id )}}">
{{entry.Books.series[0].name}}
</a>
({{entry.Books.series_index|formatfloat(2)}})
</p>
{% endif %}
{% if entry.Books.ratings.__len__() > 0 %}
<div class="rating">
{% for number in range((entry.Books.ratings[0].rating/2)|int(2)) %}
<span class="glyphicon glyphicon-star good"></span>
{% if loop.last and loop.index < 5 %}
{% for numer in range(5 - loop.index) %}
<span class="glyphicon glyphicon-star-empty"></span>
{% endfor %}
{% endif %}
{% endfor %}
</div>
{% endif %}
</div>
</div>
{% endfor %}
</div>
</div>
{% endif %}
<div class="discover load-more caliblur-index">
<h2 class="{{title}}">{{title}}</h2>
{% if page != 'discover' %}
<div class="filterheader caliblur-filterheader hidden-xs">
{% if page == 'hot' %}
<a data-toggle="tooltip" title="{{_('Sort ascending according to download count')}}" id="hot_asc" class="btn btn-primary{% if order == "hotasc" %} active{% endif%}" href="{{url_for('web.books_list', data=page, book_id=id, sort_param='hotasc')}}"><span class="glyphicon glyphicon-sort-by-order"></span></a>
<a data-toggle="tooltip" title="{{_('Sort descending according to download count')}}" id="hot_desc" class="btn btn-primary{% if order == "hotdesc" %} active{% endif%}" href="{{url_for('web.books_list', data=page, book_id=id, sort_param='hotdesc')}}"><span class="glyphicon glyphicon-sort-by-order-alt"></span></a>
{% else %}
<a data-toggle="tooltip" title="{{_('Sort according to book date, newest first')}}" id="new" class="btn btn-primary{% if order == "new" %} active{% endif%}" href="{{url_for('web.books_list', data=page, book_id=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{% if order == "old" %} active{% endif%}" href="{{url_for('web.books_list', data=page, book_id=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>
<a data-toggle="tooltip" title="{{_('Sort title in alphabetical order')}}" id="asc" class="btn btn-primary{% if order == "abc" %} active{% endif%}" href="{{url_for('web.books_list', data=page, book_id=id, sort_param='abc')}}"><span class="glyphicon glyphicon-font"></span><span class="glyphicon glyphicon-sort-by-alphabet"></span></a>
<a data-toggle="tooltip" title="{{_('Sort title in reverse alphabetical order')}}" id="desc" class="btn btn-primary{% if order == "zyx" %} active{% endif%}" href="{{url_for('web.books_list', data=page, book_id=id, sort_param='zyx')}}"><span class="glyphicon glyphicon-font"></span><span class="glyphicon glyphicon-sort-by-alphabet-alt"></span></a>
<a data-toggle="tooltip" title="{{_('Sort authors in alphabetical order')}}" id="auth_az" class="btn btn-primary{% if order == "authaz" %} active{% endif%}" href="{{url_for('web.books_list', data=page, book_id=id, sort_param='authaz')}}"><span class="glyphicon glyphicon-user"></span><span class="glyphicon glyphicon-sort-by-alphabet"></span></a>
<a data-toggle="tooltip" title="{{_('Sort authors in reverse alphabetical order')}}" id="auth_za" class="btn btn-primary{% if order == "authza" %} active{% endif%}" href="{{url_for('web.books_list', data=page, book_id=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{% if order == "pubnew" %} active{% endif%}" href="{{url_for('web.books_list', data=page, book_id=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{% if order == "pubold" %} active{% endif%}" href="{{url_for('web.books_list', data=page, book_id=id, sort_param='pubold')}}"><span class="glyphicon glyphicon-calendar"></span><span class="glyphicon glyphicon-sort-by-order-alt"></span></a>
{% if page == 'series' %}
<a data-toggle="tooltip" title="{{_('Sort ascending according to series index')}}" id="series_asc" class="btn btn-primary{% if order == "seriesasc" %} active{% endif%}" href="{{url_for('web.books_list', data=page, book_id=id, sort_param='seriesasc')}}"><span class="glyphicon glyphicon-sort-by-order"></span></a>
<a data-toggle="tooltip" title="{{_('Sort descending according to series index')}}" id="series_desc" class="btn btn-primary{% if order == "seriesdesc" %} active{% endif%}" href="{{url_for('web.books_list', data=page, book_id=id, sort_param='seriesdesc')}}"><span class="glyphicon glyphicon-sort-by-order-alt"></span></a>
{% endif %}
{% endif %}
</div>
{% endif %}
<div class="row display-flex">
{% if entries[0] %}
{% for entry in entries %}
<div class="col-sm-3 col-lg-2 col-xs-6 book session" id="books">
<div class="cover">
<a href="{{ url_for('web.show_book', book_id=entry.Books.id) }}" {% if simple==false %}data-toggle="modal" data-target="#bookDetailsModal" data-remote="false"{% endif %}>
<span class="img" title="{{ entry.Books.title }}">
{{ image.book_cover(entry.Books) }}
{% if entry[2] == True %}<span class="badge read glyphicon glyphicon-ok"></span>{% endif %}
</span>
</a>
</div>
<div class="meta">
<a href="{{ url_for('web.show_book', book_id=entry.Books.id) }}" {% if simple==false %}data-toggle="modal" data-target="#bookDetailsModal" data-remote="false"{% endif %}>
<p title="{{ entry.Books.title }}" class="title">{{entry.Books.title|shortentitle}}</p>
</a>
<p class="author">
{% for author in entry.Books.authors %}
{% if loop.index > g.config_authors_max and g.config_authors_max != 0 %}
{% if not loop.first %}
<span class="author-hidden-divider">&amp;</span>
{% endif %}
<a class="author-name author-hidden" href="{{url_for('web.books_list', data='author', book_id=author.id, sort_param='stored') }}">{{author.name.replace('|',',')|shortentitle(30)}}</a>
{% if loop.last %}
<a href="#" class="author-expand" data-authors-max="{{g.config_authors_max}}" data-collapse-caption="({{_('reduce')}})">(...)</a>
{% endif %}
{% else %}
{% if not loop.first %}
<span>&amp;</span>
{% endif %}
<a class="author-name" href="{{url_for('web.books_list', data='author', book_id=author.id, sort_param='stored') }}">{{author.name.replace('|',',')|shortentitle(30)}}</a>
{% endif %}
{% endfor %}
{% if entry.Books.data|music %}
<span class="glyphicon glyphicon-music"></span>
{% endif %}
</p>
{% if entry.Books.series.__len__() > 0 %}
<p class="series">
{% if page != "series" %}
<a href="{{url_for('web.books_list', data='series', sort_param='stored', book_id=entry.Books.series[0].id )}}">
{{entry.Books.series[0].name}}
</a>
{% else %}
<span>{{entry.Books.series[0].name}}</span>
{% endif %}
({{entry.Books.series_index|formatfloat(2)}})
</p>
{% endif %}
{% if entry.Books.ratings.__len__() > 0 %}
<div class="rating">
{% for number in range((entry.Books.ratings[0].rating/2)|int(2)) %}
<span class="glyphicon glyphicon-star good"></span>
{% if loop.last and loop.index < 5 %}
{% for numer in range(5 - loop.index) %}
<span class="glyphicon glyphicon-star-empty"></span>
{% endfor %}
{% endif %}
{% endfor %}
</div>
{% endif %}
</div>
</div>
{% endfor %}
{% endif %}
</div>
</div>
{% endblock %}
+212
View File
@@ -0,0 +1,212 @@
<!DOCTYPE html>
{% from theme('modal_dialogs.html') import restrict_modal, delete_book, filechooser_modal, delete_confirm_modal, change_confirm_modal %}
{% import 'image.html' as image %}
<html lang="{{ current_user.locale }}">
<head>
<title>{{instance}} | {{title}}</title>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1">
<meta name="mobile-web-app-capable" content="yes">
{% if g.google_site_verification|length > 0 %}
<meta name="google-site-verification" content="{{g.google_site_verification}}">
{% endif %}
<!-- Bootstrap -->
<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 href="{{ url_for('static', filename='css/libs/bootstrap.min.css') }}" rel="stylesheet" media="screen">
{% block header %}{% endblock %}
<link href="{{ url_for('static', filename='css/style.css') }}" rel="stylesheet" media="screen">
<link href="{{ url_for('static', filename='css/upload.css') }}" rel="stylesheet" media="screen">
<link href="{{ url_for('static', filename='css/caliBlur.css') }}" rel="stylesheet" media="screen">
<link href="{{ url_for('static', filename='css/caliBlur_override.css') }}" rel="stylesheet" media="screen">
</head>
<body class="{{ page }} {{ bodyClass }} blur{% if page == 'stat' %} stats{% endif %}{% if page in ['config', 'uiconfig', 'newuser', 'edituser', 'mailset', 'usertable', 'dbconfig'] %} admin{% endif %}{% if page == 'advsearch' %} advanced_search{% endif %}" data-readmore-more="{{_('Read more')}}" data-readmore-less="{{_('Read less')}}">
<!-- Static navbar -->
<div class="navbar navbar-default navbar-static-top" role="navigation">
<div class="container-fluid">
<div class="navbar-header">
<button type="button" class="navbar-toggle" data-toggle="collapse" data-target=".navbar-collapse">
<span class="sr-only">{{_('Toggle Navigation')}}</span>
<span class="icon-bar"></span>
<span class="icon-bar"></span>
<span class="icon-bar"></span>
</button>
<a class="navbar-brand" href="{{url_for('web.index')}}">{{instance}}</a>
</div>
<div class="home-btn"><a class="home-btn-tooltip" href="{{url_for('web.index', page=1)}}" data-toggle="tooltip" title="{{_('Home')}}" data-placement="bottom"></a></div>
<div class="plexBack"><a class="back-btn-tooltip" href="{{url_for('web.index')}}" data-toggle="tooltip" title="{{_('Back')}}" data-placement="bottom"></a></div>
{% if current_user.is_authenticated or g.allow_anonymous %}
<form class="navbar-form navbar-left" role="search" action="{{url_for('search.simple_search')}}" method="GET">
<div class="form-group input-group input-group-sm">
<label for="query" class="sr-only">{{_('Search')}}</label>
<span class="query-search-icon glyphicon glyphicon-search" aria-hidden="true"></span>
<input type="text" class="form-control" id="query" name="query" placeholder="{{_('Search Library')}}" value="{{searchterm}}">
<span class="input-group-btn">
<button type="submit" id="query_submit" class="btn btn-default"><span class="glyphicon glyphicon-arrow-right" aria-hidden="true"></span></button>
</span>
</div>
</form>
{% endif %}
<div class="navbar-collapse collapse">
{% if current_user.is_authenticated or g.allow_anonymous %}
<ul class="nav navbar-nav ">
{% if simple==true %} <li><a href="{{url_for('basic.index')}}" id="basic"><span class="glyphicon glyphicon-phone"></span><span>{{_('Simple Theme')}}</span></a><li>{% endif %}
</ul>
{% endif %}
<ul class="nav navbar-nav navbar-right" id="main-nav">
{% if current_user.is_authenticated or g.allow_anonymous %}
<li class="dropdown"><a href="#" class="dropdown-toggle profileDrop send-btn-tooltip dropdown" data-toggle="dropdown" role="button" aria-haspopup="true" aria-expanded="false" data-toggle-two="tooltip" title="{{_('Account')}}" data-placement="bottom" data-viewport="#main-nav"><span class="profileDropIcon" aria-hidden="true"><img src="{{ url_for('static', filename='css/img/user-icon.svg') }}" alt=""></span></a><span class="hidden-sm">{{current_user.name}}</span>
<ul class="dropdown-menu profileDropli">
<li><a id="top_user" href="{{url_for('web.profile')}}" ><span class="glyphicon glyphicon-user"></span> <span class="hidden-sm">{{current_user.name}}</span></a></li>
{% if not current_user.is_anonymous %}
<li class="dropdown"><a id="nav_about" href="{{url_for('about.stats')}}"><span class="glyphicon glyphicon-info-sign"></span> {{_('About')}}</a></li>
{% endif %}
{% if g.allow_registration and not current_user.is_authenticated %}
<li><a id="login" href="{{url_for('web.login')}}"><span class="glyphicon glyphicon-log-in"></span> {{_('Login')}}</a></li>
<li><a id="register" href="{{url_for('web.register')}}"><span class="glyphicon glyphicon-user"></span> {{_('Register')}}</a></li>
{% endif %}
{% if not current_user.is_anonymous %}
<li><a id="logout" href="{{url_for('web.logout', next=(request.path + '?' + request.query_string.decode('utf-8')).rstrip('?'))}}"><span class="glyphicon glyphicon-log-out"></span> <span class="hidden-sm">{{_('Logout')}}</span></a></li>
{% endif %}
</ul>
</li>
{% if current_user.role_upload() and g.allow_upload %}
<li class="form-upload">
<form id="form-upload" class="navbar-form" action="{{ url_for('edit-book.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">
<span class="form-upload-label">{{_('Upload Book')}}</span>
<span class="btn btn-default btn-file">{{_('Upload')}}<input id="btn-upload" class="upload-btn-tooltip" name="btn-upload"
data-toggle="tooltip" data-placement="bottom" data-viewport="#main-nav" title="{{_('Upload')}}"
type="file" accept="{% for format in accept %}.{% if format != ''%}{{format}}{% else %}*{% endif %}{{ ',' if not loop.last }}{% endfor %}" multiple></span>
<input class="hide" id="btn-upload2" name="btn-upload2" type="file" accept="{% for format in accept %}.{% if format != ''%}{{format}}{% else %}*{% endif %}{{ ',' if not loop.last }}{% endfor %}">
</div>
</form>
</li>
{% endif %}
{% if not current_user.is_anonymous and not simple%}
<li class="top_tasks"><span class="top-tasks-label">{{_('Manage')}}</span><a id="top_tasks" class="tasks-btn-tooltip" href="{{url_for('tasks.get_tasks_status')}}" data-toggle="tooltip" title="{{_('Tasks')}}" data-placement="bottom" data-viewport="#main-nav"><span class="glyphicon glyphicon-tasks"></span> <span class="hidden-sm">{{_('Tasks')}}</span></a></li>
{% endif %}
{% if current_user.role_admin() %}
<li class="top_admin"><a id="top_admin" class="admin-btn-tooltip" href="{{url_for('admin.admin')}}" data-toggle="tooltip" title="{{_('Settings')}}" data-placement="bottom" data-viewport="#main-nav"><span class="glyphicon glyphicon-dashboard"></span> <span class="hidden-sm">{{_('Admin')}}</span></a></li>
{% endif %}
{% endif %}
</ul>
</div><!--/.nav-collapse -->
<div class="sidebar-backdrop"></div>
</div>
</div>
{% for message in get_flashed_messages(with_categories=True) %}
{%if message[0] == "error" %}
<div class="row-fluid text-center" >
<div id="flash_danger" class="alert alert-danger">{{ message[1] }}</div>
</div>
{%endif%}
{%if message[0] == "info" %}
<div class="row-fluid text-center">
<div id="flash_info" class="alert alert-info">{{ message[1] }}</div>
</div>
{%endif%}
{%if message[0] == "warning" %}
<div class="row-fluid text-center">
<div id="flash_warning" class="alert alert-warning">{{ message[1] }}</div>
</div>
{%endif%}
{%if message[0] == "success" %}
<div class="row-fluid text-center">
<div id="flash_success" class="alert alert-success">{{ message[1] }}</div>
</div>
{%endif%}
{% endfor %}
{% block flash %}{% endblock %}
<div id="loader" hidden="true">
<h3>{{_('Uploading...')}}</h3>
<span>{{_("Please do not refresh the page")}}</span>.
</div>
<div class="container-fluid">
<div class="row-fluid">
{% if current_user.is_authenticated or g.allow_anonymous %}
<div class="col-sm-2">
<nav class="navigation">
<ul class="list-unstyled" id="scnd-nav" intent in-standard-append="nav.navigation" in-tablet-after="#main-nav" in-tablet-class="nav navbar-nav" in-mobile-after="#main-nav" in-mobile-class="nav navbar-nav">
<li class="nav-head hidden-xs">{{_('Browse')}}</li>
<li><a href="{{url_for('search.advanced_search')}}" id="advanced_search"><span class="glyphicon glyphicon-search"></span><span class="hidden-sm"> {{_('Advanced Search')}}</span></a></li>
{% for element in sidebar %}
{% if current_user.check_visibility(element['visibility']) and element['public'] %}
<li id="nav_{{element['id']}}" {% if page == element['page'] %}class="active"{% endif %}><a href="{% if element['no_param'] %}{{url_for(element['link'], data=element['page'])}}{%else%}{{url_for(element['link'], data=element['page'], sort_param='stored')}}{% endif %}"><span class="glyphicon {{element['glyph']}}"></span> <span class="nav-text">{{_(element['text'])|trim}}</span></a></li>
{% endif %}
{% endfor %}
{% if current_user.is_authenticated or g.allow_anonymous %}
<li class="nav-head hidden-xs public-shelves">{{_('Shelves')}}</li>
{% 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 %} <span class="badge badge-sm">{{shelf.books.all()|length}}</span></a></li>
{% endfor %}
{% if not current_user.is_anonymous %}
<li id="nav_createshelf" class="create-shelf"><a href="{{url_for('shelf.create_shelf')}}">{{_('Create a Shelf')}}</a></li>
{% endif %}
{% endif %}
</ul>
</nav>
</div>
{% endif %}
<div class="col-sm-10">
{% block body %}{% endblock %}
{% if pagination and (pagination.has_next or pagination.has_prev) %}
<div class="pagination">
{% if pagination.has_prev %}
<li class="page-item page-previous"><a class="page-link" aria-label="next page" href="{{ (pagination.page - 1)|url_for_other_page }}">&laquo; {{_('Previous')}}</a></li>
{% endif %}
{% for page in pagination.iter_pages() %}
{% if page %}
{% if page != pagination.page %}
<li class="page-item"><a class="page-link" aria-label="to page {{ page }}" href="{{ (page)|url_for_other_page }}">{{ page }}</a></li>
{% else %}
<li class="page-item active"><a class="page-link" aria-label="to page {{ page }}" href="{{ (page)|url_for_other_page }}">{{ page }}</a></li>
{% endif %}
{% else %}
<li class="page-item page-last-separator disabled"><a class="page-link" aria-label="">…</a></li>
{% endif %}
{% endfor %}
{% if pagination.has_next %}
<li class="page-item page-next"><a class="page-link next" aria-label="next page" href="{{ (pagination.page + 1)|url_for_other_page }}">{{_('Next')}} &raquo;</a></li>
{% endif %}
</div>
{% endif %}
</div>
</div>
</div>
<div class="modal fade" id="bookDetailsModal" tabindex="-1" role="dialog" aria-labelledby="bookDetailsModalLabel">
<div class="modal-dialog modal-lg" role="document">
<div class="modal-content">
<div class="modal-header">
<button type="button" class="close" data-dismiss="modal" aria-label="Close"><span aria-hidden="true">&times;</span></button>
<h4 class="modal-title" id="bookDetailsModalLabel">{{_('Book Details')}}</h4>
</div>
<div class="modal-body">...</div>
<div class="modal-footer">
<button type="button" id="details_close" class="btn btn-default" data-dismiss="modal">{{_('Close')}}</button>
</div>
</div>
</div>
</div>
{% block modal %}{% endblock %}
<!-- jQuery (necessary for Bootstrap's JavaScript plugins) -->
<script src="{{ url_for('static', filename='js/libs/jquery.min.js') }}"></script>
<!-- Include all compiled plugins (below), or include individual files as needed -->
<script src="{{ url_for('static', filename='js/libs/bootstrap.min.js') }}"></script>
<script src="{{ url_for('static', filename='js/libs/underscore-umd-min.js') }}"></script>
<script src="{{ url_for('static', filename='js/libs/intention.min.js') }}"></script>
<script src="{{ url_for('static', filename='js/libs/context.min.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/uploadprogress.js') }}"> </script>
<script src="{{ url_for('static', filename='js/main.js') }}"></script>
<script src="{{ url_for('static', filename='js/libs/jquery.visible.min.js') }}"></script>
<script src="{{ url_for('static', filename='js/libs/compromise.min.js') }}"></script>
<script src="{{ url_for('static', filename='js/libs/readmore.min.js') }}"></script>
<script src="{{ url_for('static', filename='js/caliBlur.js') }}"></script>
{% block js %}{% endblock %}
</body>
</html>
+88
View File
@@ -0,0 +1,88 @@
{% extends theme("layout.html") %}
{% block body %}
<div class="discover">
{% if page == 'authorlist' %}
<span class="authorlist-icon glyphicon glyphicon-user" aria-hidden="true"></span>
{% endif %}
{% if page == 'catlist' %}
<span class="catlist-icon glyphicon glyphicon-tags" aria-hidden="true"></span>
{% endif %}
{% if page == 'downloadlist' %}
<span class="downloadlist-icon glyphicon glyphicon-download" aria-hidden="true"></span>
{% endif %}
{% if page == 'langlist' %}
<span class="langlist-icon glyphicon glyphicon-flag" aria-hidden="true"></span>
{% endif %}
{% if page == 'publisherlist' %}
<span class="publisherlist-icon glyphicon glyphicon-text-size" aria-hidden="true"></span>
{% endif %}
{% if page == 'ratingslist' %}
<span class="ratingslist-icon glyphicon glyphicon-star-empty" aria-hidden="true"></span>
{% endif %}
{% if page == 'formatslist' %}
<span class="formatslist-icon glyphicon glyphicon-file" aria-hidden="true"></span>
{% endif %}
{% if page == 'serieslist' %}
<span class="serieslist-icon glyphicon glyphicon-bookmark" aria-hidden="true"></span>
{% endif %}
<h2 class="{{ page }}">{{ _(title)|replace(':', ' —')|replace("'", "") }}</h2>
<div class="filterheader caliblur-filterheader hidden-xs">
<input type="hidden" name="csrf_token" value="{{ csrf_token() }}">
{% if entries.__len__() and data == 'author' %}
<div id="sort_name" class="btn btn-primary"><b>B,A <-> A B</b></div>
{% endif %}
<div id="asc" data-order="{{ order }}" data-id="{{ data }}" class="btn btn-primary{% if order == 1 %} active{% endif %}"><span class="glyphicon glyphicon-sort-by-alphabet"></span></div>
<div id="desc" data-id="{{ data }}" class="btn btn-primary{% if order == 0 %} active{% endif %}"><span class="glyphicon glyphicon-sort-by-alphabet-alt"></span></div>
{% if charlist|length %}
<div id="all" class="active btn btn-primary {% if charlist|length > 9 %}hidden-sm{% endif %}">{{ _('All') }}</div>
{% endif %}
<div class="btn-group character {% if charlist|length > 9 %}hidden-sm{% endif %}" role="group">
{% for char in charlist %}
<div class="btn btn-primary char">{{ char[0] }}</div>
{% endfor %}
</div>
{% if data == "series" %}
<button class="update-view btn btn-primary" data-target="series_view" id="grid-button" data-view="grid">{{ _('Grid') }}</button>
{% endif %}
</div>
<div class="container1">
<div id="list" class="col-xs-12 col-sm-6">
{% for entry in entries %}
{% if loop.index0 == (loop.length / 2 + loop.length % 2)|int and loop.length > 20 %}
</div>
<div id="second" class="col-xs-12 col-sm-6">
{% endif %}
<div class="row" {% if entry[0].sort %}data-name="{{ entry[0].name }}"{% endif %} data-id="{% if entry[0].sort %}{{ entry[0].sort }}{% else %}{% if entry[0].format %}{{ entry[0].format }}{% else %}{% if entry[0].rating %}{{ entry[0].rating }}{% else %}{{ entry[0].name }}{% endif %}{% endif %}{% endif %}">
<div class="col-xs-2 col-sm-2 col-md-1" align="left"><span class="badge">{{ entry[1] }}</span></div>
<div class="col-xs-10 col-sm-10 col-md-11"><a id="list_{{ loop.index0 }}" href="{% if entry.format %}{{ url_for('web.books_list', data=data, sort_param='stored', book_id=entry.format) }}{% else %}{{ url_for('web.books_list', data=data, sort_param='stored', book_id=entry[0].id) }}{% endif %}">
{% if entry.name %}
<div class="rating">
{% for number in range(entry.name|int) %}
<span class="glyphicon glyphicon-star good"></span>
{% if loop.last and loop.index < 5 %}
{% for numer in range(5 - loop.index) %}
<span class="glyphicon glyphicon-star-empty"></span>
{% endfor %}
{% endif %}
{% endfor %}
</div>
{% else %}
{% if entry.format %}
{{ entry.format }}
{% else %}
{{ entry[0].name }}
{% endif %}
{% endif %}
</a></div>
</div>
{% endfor %}
</div>
</div>
</div>
{% endblock %}
{% block js %}
<script src="{{ url_for('static', filename='js/filter_list.js') }}"></script>
{% endblock %}
@@ -0,0 +1,352 @@
<!DOCTYPE html>
<html class="no-js">
<head>
<meta charset="utf-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1">
<title>{{ entry.title }}</title>
<meta name="description" content="">
<meta name="viewport" content="width=device-width">
<meta name="mobile-web-app-capable" content="yes">
{% if g.google_site_verification|length > 0 %}
<meta name="google-site-verification" content="{{g.google_site_verification}}">
{% endif %}
<script src="{{ url_for('static', filename='js/libs/jquery.min.js') }}"></script>
<script src="{{ url_for('static', filename='js/libs/soundmanager2.js') }}"></script>
<script src="{{ url_for('static', filename='js/libs/bar-ui.js') }}"></script>
<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 href="{{ url_for('static', filename='css/libs/bootstrap.min.css') }}" rel="stylesheet" media="screen">
<link rel="stylesheet" href="{{ url_for('static', filename='css/libs/bar-ui.css') }}" />
<script>
"use strict";
</script>
</head>
<body>
<div class="row" style="display: inline-block; width: 100%;">
<div class="col-sm-3 col-lg-3 col-xs-5">
</div>
<div class="col-sm-6 col-lg-6 book-meta" style="margin-bottom: 2%;">
<img id="detailcover" title="{{entry.title}}" src="{{url_for('web.get_cover', book_id=entry.id, resolution='og', c=entry|last_modified)}}" style="float: right; margin-top: 20px"/>
<h2 id="title">{{entry.title}}</h2>
<p class="author">
{% for author in entry.ordered_authors %}
<a href="{{url_for('web.books_list', data='author', sort_param='stored', book_id=author.id ) }}">{{author.name.replace('|',',')}}</a>
{% if not loop.last %}
&amp;
{% endif %}
{% endfor %}
</p>
{% if entry.ratings.__len__() > 0 %}
<div class="rating">
<p>
{% for number in range((entry.ratings[0].rating/2)|int(2)) %}
<span class="glyphicon glyphicon-star good"></span>
{% if loop.last and loop.index < 5 %}
{% for numer in range(5 - loop.index) %}
<span class="glyphicon glyphicon-star-empty"></span>
{% endfor %}
{% endif %}
{% endfor %}
</p>
</div>
{% endif %}
{% if entry.series|length > 0 %}
<p>{{_("Book %(index)s of %(range)s", index=entry.series_index|formatfloat(2), range=(url_for('web.books_list', data='series', sort_param='stored', book_id=entry.series[0].id)|escapedlink(entry.series[0].name))|safe)}}</p>
{% endif %}
{% if entry.languages.__len__() > 0 %}
<div class="languages">
<p>
<span class="label label-default">{{_('Language')}}: {% for language in entry.languages %}{{language.language_name}}{% if not loop.last %}, {% endif %}{% endfor %}</span>
</p>
</div>
{% endif %}
{% if entry.identifiers|length > 0 %}
<div class="identifiers">
<p>
<span class="glyphicon glyphicon-link"></span>
{% for identifier in entry.identifiers %}
<a href="{{identifier}}" target="_blank" class="btn btn-xs btn-success" role="button">{{identifier.format_type()}}</a>
{%endfor%}
</p>
</div>
{% endif %}
{% if entry.tags|length > 0 %}
<div class="tags">
<p>
<span class="glyphicon glyphicon-tags"></span>
{% for tag in entry.tags %}
<a href="{{ url_for('web.books_list', data='category', sort_param='stored', book_id=tag.id) }}" class="btn btn-xs btn-info" role="button">{{tag.name}}</a>
{%endfor%}
</p>
</div>
{% endif %}
{% if entry.publishers|length > 0 %}
<div class="publishers">
<p>
<span>{{_('Publisher')}}:
<a href="{{url_for('web.books_list', data='publisher', sort_param='stored', book_id=entry.publishers[0].id ) }}">{{entry.publishers[0].name}}</a>
</span>
</p>
</div>
{% endif %}
{% if (entry.pubdate|string)[:10] != '0101-01-01' %}
<div class="publishing-date">
<p>{{_('Published')}}: {{entry.pubdate|formatdate}} </p>
</div>
{% endif %}
{% if cc|length > 0 %}
{% for c in cc %}
<div class="real_custom_columns">
{% if entry['custom_column_' ~ c.id]|length > 0 %}
{{ c.name }}:
{% for column in entry['custom_column_' ~ c.id] %}
{% if c.datatype == 'rating' %}
{{ (column.value / 2)|formatfloat }}
{% else %}
{% if c.datatype == 'bool' %}
{% if column.value == true %}
<span class="glyphicon glyphicon-ok"></span>
{% else %}
<span class="glyphicon glyphicon-remove"></span>
{% endif %}
{% else %}
{% if c.datatype == 'float' %}
{{ column.value|formatfloat(2) }}
{% elif c.datatype == 'datetime' %}
{{ column.value|formatdate }}
{% elif c.datatype == 'comments' %}
{{column.value|clean_string|safe}}
{% elif c.datatype == 'series' %}
{{ '%s [%s]' % (column.value, column.extra|formatfloat(2)) }}
{% elif c.datatype == 'text' %}
{{ column.value.strip() }}{% if not loop.last %}, {% endif %}
{% else %}
{{ column.value }}
{% endif %}
{% endif %}
{% endif %}
{% endfor %}
{% endif %}
</div>
{% endfor %}
{% endif %}
{% if not current_user.is_anonymous %}
<div class="custom_columns">
<p>
<input type="hidden" name="csrf_token" value="{{ csrf_token() }}">
<label class="block-label">
<input id="have_read_cb" class="readunread-btn-tooltip" type="checkbox" {% if entry.read_status %}checked{% endif %} disabled data-toggle="tooltip" data-placement="bottom" data-viewport=".btn-toolbar" title="{% if entry.read_status %}{{_('Mark As Unread')}}{% else %}{{_('Mark As Read')}}{% endif %}">
<span>{{_('Read')}}</span>
</label>
</p>
{% if current_user.check_visibility(32768) %}
<p>
<input type="hidden" name="csrf_token" value="{{ csrf_token() }}">
<label class="block-label">
<input id="archived_cb" class="readunread-btn-tooltip" type="checkbox" {% if entry.is_archived %}checked{% endif %} disabled data-toggle="tooltip" data-placement="bottom" data-viewport=".btn-toolbar" title="{% if entry.is_archived %}{{_('Restore from archive')}}{% else %}{{_('Add to archive')}}{% endif %}">
<span>{{_('Archived')}}</span>
</label>
</p>
{% endif %}
</div>
{% endif %}
{% if entry.comments|length > 0 and entry.comments[0].text|length > 0%}
<div class="comments">
<h3 id="decription">{{_('Description:')}}</h3>
{{entry.comments[0].text|clean_string|safe}}
</div>
{% endif %}
<div class="more-stuff">
{% if current_user.is_authenticated %}
{% if current_user.shelf.all() or g.shelves_access %}
<div id="shelf-actions" class="btn-toolbar" role="toolbar">
<div class="btn-group" role="group" aria-label="Add to shelves">
<button id="add-to-shelf" type="button" class="btn btn-primary btn-sm dropdown-toggle" data-toggle="dropdown" aria-haspopup="true" aria-expanded="false">
<span class="glyphicon glyphicon-list"></span> {{_('Add to shelf')}}
<span class="caret"></span>
</button>
<ul id="add-to-shelves" class="dropdown-menu" aria-labelledby="add-to-shelf">
{% for shelf in g.shelves_access %}
{% if not shelf.id in books_shelfs and ( not shelf.is_public or current_user.role_edit_shelfs() ) %}
<li>
<a data-href="{{ url_for('shelf.add_to_shelf', book_id=entry.id, shelf_id=shelf.id) }}"
data-remove-href="{{ url_for('shelf.remove_from_shelf', book_id=entry.id, shelf_id=shelf.id) }}"
data-shelf-action="add"
>
{{shelf.name}}{% if shelf.is_public == 1 %} {{_('(Public)')}}{% endif %}
</a>
</li>
{% endif %}
{%endfor%}
</ul>
</div>
<div id="remove-from-shelves" class="btn-group" role="group" aria-label="Remove from shelves">
{% if books_shelfs %}
{% for shelf in g.shelves_access %}
{% if shelf.id in books_shelfs %}
<a data-href="{{ url_for('shelf.remove_from_shelf', book_id=entry.id, shelf_id=shelf.id) }}"
data-add-href="{{ url_for('shelf.add_to_shelf', book_id=entry.id, shelf_id=shelf.id) }}"
class="btn btn-sm btn-default" role="button" data-shelf-action="remove"
>
<span {% if not shelf.is_public or current_user.role_edit_shelfs() %}
class="glyphicon glyphicon-remove"
{% endif %}></span> {{shelf.name}}{% if shelf.is_public == 1 %} {{_('(Public)')}}{% endif %}
</a>
{% endif %}
{%endfor%}
{% endif %}
</div>
<div id="shelf-action-errors" class="pull-left" role="alert"></div>
</div>
{% endif %}
{% endif %}
</div>
</div>
</div>
<div class="sm2-bar-ui full-width fixed">
<div class="bd sm2-main-controls">
<div class="sm2-inline-texture"></div>
<div class="sm2-inline-gradient"></div>
<div class="sm2-inline-element sm2-button-element">
<div class="sm2-button-bd">
<a href="#play" class="sm2-inline-button sm2-icon-play-pause">Play / Pause</a>
</div>
</div>
<div class="sm2-inline-element sm2-inline-status">
<div class="sm2-playlist">
<div class="sm2-playlist-target">
<noscript><p>JavaScript Required.</p></noscript>
</div>
</div>
<div class="sm2-progress">
<div class="sm2-row">
<div class="sm2-inline-time">0:00</div>
<div class="sm2-progress-bd">
<div class="sm2-progress-track">
<div class="sm2-progress-bar"></div>
<div class="sm2-progress-ball"><div class="icon-overlay"></div></div>
</div>
</div>
<div class="sm2-inline-duration">0:00</div>
</div>
</div>
</div>
<div class="sm2-inline-element sm2-button-element sm2-volume">
<div class="sm2-button-bd">
<span class="sm2-inline-button sm2-volume-control volume-shade"></span>
<a href="#volume" class="sm2-inline-button sm2-volume-control">Volume</a>
</div>
</div>
<div class="sm2-inline-element sm2-button-element">
<div class="sm2-button-bd">
<a href="#prev" title="Previous" class="sm2-inline-button sm2-icon-previous">&lt; Previous</a>
</div>
</div>
<div class="sm2-inline-element sm2-button-element">
<div class="sm2-button-bd">
<a href="#next" title="Next" class="sm2-inline-button sm2-icon-next">&gt; Next</a>
</div>
</div>
<div class="sm2-inline-element sm2-button-element">
<div class="sm2-button-bd">
<a href="#repeat" title="Repeat playlist" class="sm2-inline-button sm2-icon-repeat">&infin; Repeat</a>
</div>
</div>
<div class="sm2-inline-element sm2-button-element sm2-menu">
<div class="sm2-button-bd">
<a href="#menu" class="sm2-inline-button sm2-icon-menu">Menu</a>
</div>
</div>
</div>
<div class="bd sm2-playlist-drawer sm2-element">
<div class="sm2-inline-texture">
<div class="sm2-box-shadow"></div>
</div>
<!-- playlist content is mirrored here -->
<div class="sm2-playlist-wrapper">
<ul class="sm2-playlist-bd">
<li><a href="{{ url_for('web.serve_book', book_id=mp3file,book_format=audioformat)}}"><b>{% for author in entry.ordered_authors %}{{author.name.replace('|',',')}}
{% if not loop.last %} & {% endif %} {% endfor %}</b> - {{entry.title}}</a></li>
</ul>
</div>
</div>
</div>
<input type="hidden" name="csrf_token" value="{{ csrf_token() }}">
<script>
soundManager.setup({
useHTML5Audio: true,
preferFlash: false,
url: '/path/to/swf-files/',
onready: function() {
var mySound = soundManager.createSound({
// id: 'aSound',
// url: "{{ url_for('web.serve_book', book_id=mp3file,book_format=audioformat)}}"
});
mySound.play();
},
ontimeout: function() {
// Hrmm, SM2 could not start. Missing SWF? Flash blocked? Show an error, etc.?
}
});
window.calibre = {
filePath: "{{ url_for('static', filename='js/libs/') }}",
cssPath: "{{ url_for('static', filename='css/') }}",
bookUrl: "{{ url_for('static', filename=mp3file) }}/",
bookmarkUrl: "{{ url_for('web.set_bookmark', book_id=mp3file, book_format=audioformat.upper()) }}",
bookmark: "{{ bookmark.bookmark_key if bookmark != None }}",
useBookmarks: "{{ current_user.is_authenticated | tojson }}"
};
</script>
</body>
</html>
+65
View File
@@ -0,0 +1,65 @@
{% extends theme("layout.html") %}
{% block body %}
<div class="well col-sm-6 col-sm-offset-2 caliblur-login-panel">
<h2 style="margin-top: 0">{{_('Login')}}</h2>
<form method="POST" role="form">
<input type="hidden" name="next" value="{{next_url}}">
<input type="hidden" name="csrf_token" value="{{ csrf_token() }}">
<div class="form-group">
<label for="username">{{_('Username')}}</label>
<input type="text" class="form-control" id="username" name="username" autocapitalize="off" placeholder="{{_('Username')}}" value="{{ username }}">
</div>
<div class="form-group">
<label for="password">{{_('Password')}}</label>
<div class="input-group">
<input type="password" class="form-control" id="password" name="password" placeholder="{{_('Password')}}" value="{{ password }}">
<span class="input-group-btn">
<button type="button" class="btn btn-default toggle-password" data-target="#password" aria-label="{{_('Show password')}}" title="{{_('Show password')}}">
<span class="glyphicon glyphicon-eye-open" aria-hidden="true"></span>
</button>
</span>
</div>
</div>
<div class="checkbox">
<label>
<input type="checkbox" name="remember_me" checked> {{_('Remember Me')}}
</label>
</div>
<button type="submit" name="submit" class="btn btn-default">{{_('Login')}}</button>
{% if config.config_login_type == 0 and mail%}
<button type="submit" name="forgot" value="forgot" class="btn btn-default">{{_('Forgot Password?')}}</button>
{% endif %}
{% if g.allow_registration %}
<a class="btn btn-default" id="register" href="{{url_for('web.register')}}"><span class="glyphicon glyphicon-user"></span> {{_('Register')}}</a>
{% endif %}
{% if config.config_remote_login %}
<a class="btn btn-default" id="remote_login" href="{{url_for('remotelogin.remote_login')}}">{{_('Log in with Magic Link')}}</a>
{% endif %}
{% if g.allow_anonymous %}
<a class="btn btn-default" href="{{url_for('web.index')}}">{{ _('Anonymous Browsing') }}</a>
{% endif %}
{% if config.config_login_type == 2 %}
{% if 1 in oauth_check %}
<a href="{{url_for('oauth.github_login')}}" class="pull-right github">
<svg height="32" class="octicon octicon-mark-github" viewBox="0 0 16 16" version="1.1" width="32" aria-hidden="true">
<path fill-rule="evenodd" d="M8 0C3.58 0 0 3.58 0 8c0 3.54 2.29 6.53 5.47 7.59.4.07.55-.17.55-.38 0-.19-.01-.82-.01-1.49-2.01.37-2.53-.49-2.69-.94-.09-.23-.48-.94-.82-1.13-.28-.15-.68-.52-.01-.53.63-.01 1.08.58 1.23.82.72 1.21 1.87.87 2.33.66.07-.52.28-.87.51-1.07-1.78-.2-3.64-.89-3.64-3.95 0-.87.31-1.59.82-2.15-.08-.2-.36-1.02.08-2.12 0 0 .67-.21 2.2.82.64-.18 1.32-.27 2-.27.68 0 1.36.09 2 .27 1.53-1.04 2.2-.82 2.2-.82.44 1.1.16 1.92.08 2.12.51.56.82 1.27.82 2.15 0 3.07-1.87 3.75-3.65 3.95.29.25.54.73.54 1.48 0 1.07-.01 1.93-.01 2.2 0 .21.15.46.55.38A8.013 8.013 0 0 0 16 8c0-4.42-3.58-8-8-8z"></path>
</svg>
</a>
{% endif %}
{% if 2 in oauth_check %}
<a href="{{url_for('oauth.google_login')}}" class="pull-right google">
<svg xmlns="http://www.w3.org/2000/svg" x="0px" y="0px"
width="40" height="40"
viewBox="0 3 48 49"
style="fill:#000000;"><g id="surface1"><path style=" fill:#FFC107;" d="M 43.609375 20.082031 L 42 20.082031 L 42 20 L 24 20 L 24 28 L 35.304688 28 C 33.652344 32.65625 29.222656 36 24 36 C 17.371094 36 12 30.628906 12 24 C 12 17.371094 17.371094 12 24 12 C 27.058594 12 29.84375 13.152344 31.960938 15.039063 L 37.617188 9.382813 C 34.046875 6.054688 29.269531 4 24 4 C 12.953125 4 4 12.953125 4 24 C 4 35.046875 12.953125 44 24 44 C 35.046875 44 44 35.046875 44 24 C 44 22.660156 43.863281 21.351563 43.609375 20.082031 Z "></path><path style=" fill:#FF3D00;" d="M 6.304688 14.691406 L 12.878906 19.511719 C 14.65625 15.109375 18.960938 12 24 12 C 27.058594 12 29.84375 13.152344 31.960938 15.039063 L 37.617188 9.382813 C 34.046875 6.054688 29.269531 4 24 4 C 16.316406 4 9.65625 8.335938 6.304688 14.691406 Z "></path><path style=" fill:#4CAF50;" d="M 24 44 C 29.164063 44 33.859375 42.023438 37.410156 38.808594 L 31.21875 33.570313 C 29.210938 35.089844 26.714844 36 24 36 C 18.796875 36 14.382813 32.683594 12.71875 28.054688 L 6.195313 33.078125 C 9.503906 39.554688 16.226563 44 24 44 Z "></path><path style=" fill:#1976D2;" d="M 43.609375 20.082031 L 42 20.082031 L 42 20 L 24 20 L 24 28 L 35.304688 28 C 34.511719 30.238281 33.070313 32.164063 31.214844 33.570313 C 31.21875 33.570313 31.21875 33.570313 31.21875 33.570313 L 37.410156 38.808594 C 36.972656 39.203125 44 34 44 24 C 44 22.660156 43.863281 21.351563 43.609375 20.082031 Z "></path></g></svg>
</a>
{% endif %}
{% endif %}
</form>
</div>
{% if error %}
<div class="col-sm-6 col-sm-offset-2">
<div class="alert alert-danger">{{error}}</div>
</div>
{% endif %}
{% endblock %}
@@ -0,0 +1,142 @@
{% macro restrict_modal() %}
<div class="modal fade" id="restrictModal" tabindex="-1" role="dialog" aria-labelledby="restrictModalLabel">
<div class="modal-dialog modal-lg" role="document">
<div class="modal-content">
<div class="modal-header">
<h4 class="modal-title hidden" id="h1">{{_('Select Allowed/Denied Tags')}}</h4>
<h4 class="modal-title hidden" id="h2">{{_('Select Allowed/Denied Custom Column Values')}}</h4>
<h4 class="modal-title hidden" id="h3">{{_('Select Allowed/Denied Tags of User')}}</h4>
<h4 class="modal-title hidden" id="h4">{{_('Select Allowed/Denied Custom Column Values of User')}}</h4>
</div>
<div class="modal-body">
<table class="table table-no-bordered" id="restrict-elements-table" data-id-field="id" data-show-header="false" data-editable-mode="inline">
<thead>
<tr>
<th data-field="Element" id="Element" data-escape="true" data-editable-type="text" data-editable="true" data-editable-title="{{_('Enter Tag')}}"></th>
<th data-field="type" id="type" data-visible="true"></th>
<th data-field="id" id="id" data-visible="false"></th>
<th data-align="right" data-formatter="RestrictionActions"></th>
</tr>
</thead>
</table>
<form id="add_restriction" action="" method="POST">
<div class="form-group required">
<label for="add_element">{{_('Add View Restriction')}}</label>
<input type="text" class="form-control" name="add_element" id="add_element">
</div>
<div class="form-group required">
<input type="button" class="btn btn-default" value="{{_('Allow')}}" name="submit_allow" id="submit_allow" data-dismiss="static">
<input type="button" class="btn btn-default" value="{{_('Deny')}}" name="submit_deny" id="submit_restrict" data-dismiss="static">
</div>
</form>
</div>
<div class="modal-footer">
<button type="button" id="restrict_close" class="btn btn-default" data-dismiss="modal">{{_('Close')}}</button>
</div>
</div>
</div>
</div>
{% endmacro %}
{% macro delete_book(allow) %}
{% if allow %}
<div class="modal fade" id="deleteModal" role="dialog" aria-labelledby="metaDeleteLabel">
<div class="modal-dialog">
<div class="modal-content">
<div class="modal-header bg-danger text-center">
<h4 class="modal-title">{{_('Delete Book')}}</h4>
</div>
<div class="modal-body text-center">
<p>
<span class="hidden" id="book_format">{{_('This book format will be permanently erased from database')}}</span>
<span class="hidden" id="book_complete">{{_('This book will be permanently erased from database and hard disk')}}</span>
</p>
{% if config.config_kobo_sync %}
<p>
<span>{{_('Important Kobo Note: deleted books will remain on any paired Kobo device.')}}</span>
<span>{{_('Books must first be archived and the device synced before a book can safely be deleted.')}}</span>
</p>
{% endif %}
</div>
<div class="modal-footer">
<button type="button" class="btn btn-primary" data-dismiss="modal">{{_('Cancel')}}</button>
<input type="button" class="btn btn-danger" value="{{_('Delete')}}" name="delete_confirm" id="delete_confirm" data-dismiss="modal">
</div>
</div>
</div>
</div>
{% endif %}
{% endmacro %}
{% macro filechooser_modal() %}
<div class="modal fade" id="fileModal" role="dialog" aria-labelledby="metafileLabel">
<div class="modal-dialog">
<div class="modal-content">
<div class="modal-header bg-info text-center">
<span>{{_('Choose File Location')}}</span>
</div>
<div class="modal-body">
<div id="file_table_wrapper">
<table id="file_table" class="table table-striped">
<thead>
<tr>
<th>{{_('type')}}</th>
<th>{{_('name')}}</th>
<th>{{_('size')}}</th>
</tr>
</thead>
<tbody id="tbody">
<tr class="tr-clickable hidden" id="parent" data-type="dir" data-path="..">
<td><span class="glyphicon glyphicon-folder-close"></span></td>
<td title="{{_('Parent Directory')}}"><span class="parentdir">..</span></td>
<td></td>
</tr>
</tbody>
</table>
</div>
</div>
<div class="modal-footer">
<div class="text-left" id="element_selected"></div>
<button type="button" id="file_abort" class="btn btn-default" data-dismiss="modal">{{_('Cancel')}}</button>
<input type="button" class="btn btn-primary" data-path="" data-link="" data-folderonly="" data-filefilter="" data-newfile="" value="{{_('Select')}}" name="file_confirm" id="file_confirm" data-dismiss="modal">
</div>
</div>
</div>
</div>
{% endmacro %}
{% macro delete_confirm_modal() %}
<div id="GeneralDeleteModal" class="modal fade" role="Dialog">
<div class="modal-dialog modal-sm">
<div class="modal-content">
<div class="modal-header bg-danger text-center">
<span id="header-GeneralDeleteModal"></span>
</div>
<div class="modal-body text-center">
<span id="text-GeneralDeleteModal"></span>
<p></p>
<button id="btnConfirmNo-GeneralDeleteModal" type="button" class="btn btn-primary">{{_('Cancel')}}</button>
<button id="btnConfirmYes-GeneralDeleteModal" type="button" class="btn btn btn-danger">{{_('Delete')}}</button>
</div>
</div>
</div>
</div>
{% endmacro %}
{% macro change_confirm_modal() %}
<div id="GeneralChangeModal" class="modal fade" role="Dialog">
<div class="modal-dialog modal-sm">
<div class="modal-content">
<div class="modal-header bg-info text-center">
<span id="header-GeneralChangeModal"></span>
</div>
<div class="modal-body text-center">
<span id="text-GeneralChangeModal"></span>
<p></p>
<button id="btnConfirmNo-GeneralChangeModal" type="button" class="btn btn-primary">{{_('Cancel')}}</button>
<button id="btnConfirmYes-GeneralChangeModal" type="button" class="btn btn btn-default">{{_('Ok')}}</button>
</div>
</div>
</div>
</div>
{% endmacro %}
@@ -0,0 +1,22 @@
{% extends theme("layout.html") %}
{% block body %}
<div class="well col-sm-6 col-sm-offset-2 caliblur-login-panel">
<form method="POST" role="form" class="caliblur-remote-login-form">
<h2 style="margin-top: 0">{{_('Magic Link - Authorise New Device')}}</h2>
<p>
{{_('On another device, login and visit:')}}
</p>
<h4><a id="verify_url" data-token="{{token}}" href="{{verify_url}}">{{verify_url}}</a></h4>
<p>
{{_('Once verified, you will automatically be logged in on this device.')}}
</p>
<p>
{{_('This verification link will expire in 10 minutes.')}}
</p>
</form>
</div>
{% endblock %}
{% block js %}
<script src="{{ url_for('static', filename='js/remote_login.js') }}"></script>
{% endblock %}
@@ -0,0 +1,19 @@
{% extends theme("layout.html") %}
{% block body %}
<div class="well col-sm-6 col-sm-offset-2 caliblur-login-panel">
<form method="POST" role="form" class="caliblur-remote-login-form">
<h2 style="margin-top: 0">{{ _('Approve Magic Link Login') }}</h2>
<p>{{ _('Review the device that requested access before approving this login.') }}</p>
<dl class="dl-horizontal">
<dt>{{ _('IP address') }}</dt>
<dd>{{ request_ip or _('Unknown') }}</dd>
<dt>{{ _('Browser') }}</dt>
<dd>{{ request_user_agent or _('Unknown') }}</dd>
</dl>
<p>{{ _('If you approve this request, the device that started it will be logged in as %(username)s.', username=current_user.name) }}</p>
<input type="hidden" name="csrf_token" value="{{ csrf_token() }}">
<button type="submit" class="btn btn-primary">{{ _('Approve Login') }}</button>
<a class="btn btn-default" href="{{ url_for('web.index') }}">{{ _('Cancel') }}</a>
</form>
</div>
{% endblock %}
+123
View File
@@ -0,0 +1,123 @@
{% import 'image.html' as image %}
{% extends theme("layout.html") %}
{% block body %}
<div class="discover">
{% if page == 'advsearch' %}
<div class="blur-wrapper"></div>
{% endif %}
{% if entries|length < 1 %}
<h2>{{_('No Results Found')}}</h2>
<p>{{_('Search Term:')}} {{adv_searchterm}}</p>
{% else %}
<h2>{{result_count}} {{_('Results for:')}} {{adv_searchterm}}</h2>
{% if current_user.is_authenticated %}
{% if current_user.shelf.all() or g.shelves_access %}
<div id="shelf-actions" class="btn-toolbar" role="toolbar">
<input type="hidden" name="csrf_token" value="{{ csrf_token() }}">
<div class="btn-group" role="group" aria-label="Add to shelves">
<button id="add-to-shelf" type="button" class="btn btn-primary btn-sm dropdown-toggle addtoshelf-btn-tooltip{% if page == 'advsearch' %} advsearch-addtoshelf{% endif %}" data-toggle-two="tooltip" title="{{_('Add to shelf')}}" data-placement="bottom" data-viewport=".btn-toolbar" data-toggle="dropdown" aria-haspopup="true" aria-expanded="false">
<span class="glyphicon glyphicon-list"></span> {{_('Add to shelf')}}
<span class="caret"></span>
</button>
<ul id="add-to-shelves" class="dropdown-menu" aria-labelledby="add-to-shelf">
{% for shelf in g.shelves_access %}
{% if not shelf.is_public or current_user.role_edit_shelfs() %}
<li><a class="postAction" role="button" data-action="{{ url_for('shelf.search_to_shelf', shelf_id=shelf.id) }}"> {{shelf.name}}{% if shelf.is_public == 1 %} {{_('(Public)')}}{% endif %}</a></li>
{% endif %}
{%endfor%}
</ul>
</div>
<div class="btn-group" role="group" aria-label="Remove from shelves" style="margin-right: 5px;">
<button id="remove-from-shelf" type="button" class="btn btn-primary btn-sm dropdown-toggle" data-toggle="dropdown" aria-haspopup="true" aria-expanded="false">
<span class="glyphicon glyphicon-list"></span> {{_('Remove from shelf')}}
<span class="caret"></span>
</button>
<ul id="remove-from-shelves" class="dropdown-menu" aria-labelledby="add-to-shelf">
{% for shelf in g.shelves_access %}
{% if not shelf.is_public or current_user.role_edit_shelfs() %}
<li><a class="postAction" role="button" data-action="{{ url_for('shelf.search_from_shelf', shelf_id=shelf.id) }}"> {{shelf.name}}{% if shelf.is_public == 1 %} {{_('(Public)')}}{% endif %}</a></li>
{% endif %}
{%endfor%}
</ul>
</div>
</div>
{% endif %}
{% endif %}
<div class="filterheader hidden-xs"><!-- ToDo: Implement filter for search results -->
<a id="new" data-toggle="tooltip" title="{{_('Sort according to book date, newest first')}}" class="btn btn-primary{% if order == "new" %} active{% endif%}" href="{{url_for('web.books_list', data=page, sort_param='new', query=query)}}"><span class="glyphicon glyphicon-sort-by-order"></span></a>
<a id="old" data-toggle="tooltip" title="{{_('Sort according to book date, oldest first')}}" class="btn btn-primary{% if order == "old" %} active{% endif%}" href="{{url_for('web.books_list', data=page, sort_param='old', query=query)}}"><span class="glyphicon glyphicon-sort-by-order-alt"></span></a>
<a id="asc" data-toggle="tooltip" title="{{_('Sort title in alphabetical order')}}" class="btn btn-primary{% if order == "abc" %} active{% endif%}" href="{{url_for('web.books_list', data=page, sort_param='abc', query=query)}}"><span class="glyphicon glyphicon-font"></span><span class="glyphicon glyphicon-sort-by-alphabet"></span></a>
<a id="desc" data-toggle="tooltip" title="{{_('Sort title in reverse alphabetical order')}}" class="btn btn-primary{% if order == "zyx" %} active{% endif%}" href="{{url_for('web.books_list', data=page, sort_param='zyx', query=query)}}"><span class="glyphicon glyphicon-font"></span><span class="glyphicon glyphicon-sort-by-alphabet-alt"></span></a>
<a id="auth_az" data-toggle="tooltip" title="{{_('Sort authors in alphabetical order')}}" class="btn btn-primary{% if order == "authaz" %} active{% endif%}" href="{{url_for('web.books_list', data=page, sort_param='authaz', query=query)}}"><span class="glyphicon glyphicon-user"></span><span class="glyphicon glyphicon-sort-by-alphabet"></span></a>
<a id="auth_za" data-toggle="tooltip" title="{{_('Sort authors in reverse alphabetical order')}}" class="btn btn-primary{% if order == "authza" %} active{% endif%}" href="{{url_for('web.books_list', data=page, sort_param='authza', query=query)}}"><span class="glyphicon glyphicon-user"></span><span class="glyphicon glyphicon-sort-by-alphabet-alt"></span></a>
<a id="pub_new" data-toggle="tooltip" title="{{_('Sort according to publishing date, newest first')}}" class="btn btn-primary{% if order == "pubnew" %} active{% endif%}" href="{{url_for('web.books_list', data=page, sort_param='pubnew', query=query)}}"><span class="glyphicon glyphicon-calendar"></span><span class="glyphicon glyphicon-sort-by-order"></span></a>
<a id="pub_old" data-toggle="tooltip" title="{{_('Sort according to publishing date, oldest first')}}" class="btn btn-primary{% if order == "pubold" %} active{% endif%}" href="{{url_for('web.books_list', data=page, sort_param='pubold', query=query)}}"><span class="glyphicon glyphicon-calendar"></span><span class="glyphicon glyphicon-sort-by-order-alt"></span></a>
</div>
{% endif %}
<div class="row display-flex">
{% for entry in entries %}
<div class="col-sm-3 col-lg-2 col-xs-6 book session">
<div class="cover">
{% if entry.Books.has_cover is defined %}
<a href="{{ url_for('web.show_book', book_id=entry.Books.id) }}" {% if simple==false %}data-toggle="modal" data-target="#bookDetailsModal" data-remote="false"{% endif %}>
<span class="img" title="{{entry.Books.title}}" >
{{ image.book_cover(entry.Books) }}
{% if entry[2] == True %}<span class="badge read glyphicon glyphicon-ok"></span>{% endif %}
</span>
</a>
{% endif %}
</div>
<div class="meta">
<a href="{{ url_for('web.show_book', book_id=entry.Books.id) }}" {% if simple==false %}data-toggle="modal" data-target="#bookDetailsModal" data-remote="false"{% endif %}>
<p title="{{entry.Books.title}}" class="title">{{entry.Books.title|shortentitle}}</p>
</a>
<p class="author">
{% for author in entry.Books.authors %}
{% if loop.index > g.config_authors_max and g.config_authors_max != 0 %}
{% if not loop.first %}
<span class="author-hidden-divider">&amp;</span>
{% endif %}
<a class="author-name author-hidden" href="{{url_for('web.books_list', data='author', sort_param='stored', book_id=author.id) }}">{{author.name.replace('|',',')|shortentitle(30)}}</a>
{% if loop.last %}
<a href="#" class="author-expand" data-authors-max="{{g.config_authors_max}}" data-collapse-caption="({{_('reduce')}})">(...)</a>
{% endif %}
{% else %}
{% if not loop.first %}
<span>&amp;</span>
{% endif %}
<a class="author-name" href="{{url_for('web.books_list', data='author', sort_param='stored', book_id=author.id) }}">{{author.name.replace('|',',')|shortentitle(30)}}</a>
{% endif %}
{% endfor %}
{% if entry.Books.data|music %}
<span class="glyphicon glyphicon-music"></span>
{% endif %}
</p>
{% if entry.Books.series.__len__() > 0 %}
<p class="series">
<a href="{{url_for('web.books_list', data='series', sort_param='stored', book_id=entry.Books.series[0].id )}}">
{{entry.Books.series[0].name}}
</a>
({{entry.Books.series_index|formatfloat(2)}})
</p>
{% endif %}
{% if entry.Books.ratings.__len__() > 0 %}
<div class="rating">
{% for number in range((entry.Books.ratings[0].rating/2)|int(2)) %}
<span class="glyphicon glyphicon-star good"></span>
{% if loop.last and loop.index < 5 %}
{% for numer in range(5 - loop.index) %}
<span class="glyphicon glyphicon-star-empty"></span>
{% endfor %}
{% endif %}
{% endfor %}
</div>
{% endif %}
</div>
</div>
{% endfor %}
</div>
</div>
{% endblock %}
+107
View File
@@ -0,0 +1,107 @@
{% import 'image.html' as image %}
{% extends theme("layout.html") %}
{% block body %}
<div class="discover">
<h2>{{ title|replace(':', ' —')|replace("'", "") }}</h2>
<!--form method="post"--->
<div class="shelf-controls">
<div class="shelf-action-row">
{% if current_user.is_authenticated %}
{% if (current_user.role_edit_shelfs() and shelf.is_public ) or not shelf.is_public %}
<input type="hidden" name="csrf_token" value="{{ csrf_token() }}">
<div class="shelf-btn-group">
{% if current_user.role_download() %}
<a id="shelf_down" href="{{ url_for('shelf.show_simpleshelf', shelf_id=shelf.id) }}" class="btn btn-default">{{ _('Download') }} </a>
{% endif %}
<div class="btn btn-danger delete-shelf-btn delete-btn-tooltip" data-action="{{url_for('shelf.delete_shelf', shelf_id=shelf.id)}}" id="delete_shelf" data-value="{{ shelf.id }}" data-toggle-two="tooltip" title="{{ _('Delete this Shelf') }}" data-placement="bottom">{{ _('Delete this Shelf') }}</div>
<a id="edit_shelf" href="{{ url_for('shelf.edit_shelf', shelf_id=shelf.id) }}" class="btn btn-default edit-shelf-btn edit-btn-tooltip" data-toggle-two="tooltip" title="{{ _('Edit Shelf Properties') }}" data-placement="bottom">{{ _('Edit Shelf Properties') }} </a>
{% if entries.__len__() %}
<a id="order_shelf" href="{{ url_for('shelf.order_shelf', shelf_id=shelf.id) }}" class="btn btn-default order-shelf-btn order-btn-tooltip" data-toggle-two="tooltip" title="{{ _('Arrange books manually') }}" data-placement="bottom">{{ _('Arrange books manually') }} </a>
{% endif %}
</div>
{% endif %}
{% endif %}
</div>
{% if current_user.is_authenticated %}
{% if entries.__len__() %}
<div class="shelf-toolbar-row">
<button id="toggle_order_shelf" data-alt-text="{% if status == "on" %}{{_('Disable Change order')}}{% else %}{{_('Enable Change order')}}{% endif %}" data-view="shelf" class="btn btn-primary{% if status == 'on' %} dummy">{{ _('Enable Change order') }}{% else%}">{{ _('Disable Change order') }}{% endif %}</button>
<div class="filterheader hidden-xs">
<a data-toggle="tooltip" title="{{_('Sort according to book date, newest first')}}" id="new" class="btn btn-primary {% if order == "new" %} active{% endif %}{% if status == "on" %} disabled{% endif %}" 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 {% if order == 'old' %} active{% endif %}{% if status == 'on' %} disabled{% endif %}" 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>
<a data-toggle="tooltip" title="{{_('Sort title in alphabetical order')}}" id="asc" class="btn btn-primary {% if order == 'abc' %} active{% endif %}{% if status == 'on' %} disabled{% endif %}" href="{{url_for('shelf.show_shelf', shelf_id=shelf.id, sort_param='abc')}}"><span class="glyphicon glyphicon-font"></span><span class="glyphicon glyphicon-sort-by-alphabet"></span></a>
<a data-toggle="tooltip" title="{{_('Sort title in reverse alphabetical order')}}" id="desc" class="btn btn-primary {% if order == 'zyx' %} active{% endif %}{% if status == 'on' %} disabled{% endif %}" href="{{url_for('shelf.show_shelf', shelf_id=shelf.id, sort_param='zyx')}}"><span class="glyphicon glyphicon-font"></span><span class="glyphicon glyphicon-sort-by-alphabet-alt"></span></a>
<a data-toggle="tooltip" title="{{_('Sort authors in alphabetical order')}}" id="auth_az" class="btn btn-primary {% if order == 'authaz' %} active{% endif %}{% if status == 'on' %} disabled{% endif %}" href="{{url_for('shelf.show_shelf', shelf_id=shelf.id, sort_param='authaz')}}"><span class="glyphicon glyphicon-user"></span><span class="glyphicon glyphicon-sort-by-alphabet"></span></a>
<a data-toggle="tooltip" title="{{_('Sort authors in reverse alphabetical order')}}" id="auth_za" class="btn btn-primary {% if order == 'authza' %} active{% endif %}{% if status == 'on' %} disabled{% endif %}" 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 {% if order == 'pubnew' %} active{% endif %}{% if status == 'on' %} disabled{% endif %}" 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 {% if order == 'pubold' %} active{% endif %}{% if status == 'on' %} disabled{% endif %}" 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 data-toggle="tooltip" title="{{_('Sort according to book added to shelf, newest first')}}" id="shelf_new" class="btn btn-primary {% if order == 'shelfnew' %} active{% endif %}{% if status == 'on' %} disabled{% endif %}" href="{{url_for('shelf.show_shelf', shelf_id=shelf.id, sort_param='shelfnew')}}"><span class="glyphicon glyphicon-list"></span><span class="glyphicon glyphicon-sort-by-order"></span></a>
<a data-toggle="tooltip" title="{{_('Sort according to book added to shelf, oldest first')}}" id="shelf_old" class="btn btn-primary {% if order == 'shelfold' %} active{% endif %}{% if status == 'on' %} disabled{% endif %}" href="{{url_for('shelf.show_shelf', shelf_id=shelf.id, sort_param='shelfold')}}"><span class="glyphicon glyphicon-list"></span><span class="glyphicon glyphicon-sort-by-order-alt"></span></a
>
</div>
</div>
{% endif %}
{% endif %}
</div>
<div class="row display-flex">
{% for entry in entries %}
<div class="col-sm-3 col-lg-2 col-xs-6 book session">
<div class="cover">
<a href="{{ url_for('web.show_book', book_id=entry.Books.id) }}" {% if simple==false %}data-toggle="modal" data-target="#bookDetailsModal" data-remote="false"{% endif %}>
<span class="img" title="{{entry.Books.title}}" >
{{ image.book_cover(entry.Books) }}
{% if entry[2] == True %}<span class="badge read glyphicon glyphicon-ok"></span>{% endif %}
</span>
</a>
</div>
<div class="meta">
<a href="{{ url_for('web.show_book', book_id=entry.Books.id) }}" {% if simple==false %}data-toggle="modal" data-target="#bookDetailsModal" data-remote="false"{% endif %}>
<p title="{{entry.Books.title}}" class="title">{{entry.Books.title|shortentitle}}</p>
</a>
<p class="author">
{% for author in entry.Books.authors %}
{% if loop.index > g.config_authors_max and g.config_authors_max != 0 %}
{% if not loop.first %}
<span class="author-hidden-divider">&amp;</span>
{% endif %}
<a class="author-name author-hidden" href="{{url_for('web.books_list', data='author', sort_param='stored', book_id=author.id) }}">{{author.name.replace('|',',')|shortentitle(30)}}</a>
{% if loop.last %}
<a href="#" class="author-expand" data-authors-max="{{g.config_authors_max}}" data-collapse-caption="({{_('reduce')}})">(...)</a>
{% endif %}
{% else %}
{% if not loop.first %}
<span>&amp;</span>
{% endif %}
<a class="author-name" href="{{url_for('web.books_list', data='author', sort_param='stored', book_id=author.id) }}">{{author.name.replace('|',',')|shortentitle(30)}}</a>
{% endif %}
{% endfor %}
</p>
{% if entry.Books.series.__len__() > 0 %}
<p class="series">
<a href="{{url_for('web.books_list', data='series', sort_param='stored', book_id=entry.Books.series[0].id )}}">
{{entry.Books.series[0].name}}
</a>
({{entry.Books.series_index|formatfloat(2)}})
</p>
{% endif %}
{% if entry.Books.ratings.__len__() > 0 %}
<div class="rating">
{% for number in range((entry.Books.ratings[0].rating/2)|int(2)) %}
<span class="glyphicon glyphicon-star good"></span>
{% if loop.last and loop.index < 5 %}
{% for numer in range(5 - loop.index) %}
<span class="glyphicon glyphicon-star-empty"></span>
{% endfor %}
{% endif %}
{% endfor %}
</div>
{% endif %}
</div>
</div>
{% endfor %}
</div>
</div>
{% endblock %}
{% block modal %}
{{ delete_confirm_modal() }}
{% endblock %}
@@ -0,0 +1,54 @@
{% extends theme("layout.html") %}
{% block body %}
<div class="discover">
<span class="shelforder-icon glyphicon glyphicon-sort-by-attributes" aria-hidden="true"></span>
<h2>{{ title|replace(':', ' —')|replace("'", "") }}</h2>
<div style="margin-left:20%; width:calc(80% - 30px);">
<div>{{_('Drag to Rearrange Order')}}</div>
<input type="hidden" name="csrf_token" value="{{ csrf_token() }}">
<div id="sortTrue" class="list-group">
{% for entry in entries %}
<div id="{{entry['Books']['id']}}" class="list-group-item">
<div class="row">
<div class="col-lg-2 col-sm-4 hidden-xs">
{% if entry['visible'] %}
<img title="{{entry['Books']['title']}}" class="cover-height" src="{{ url_for('web.get_cover', book_id=entry['Books']['id']) }}">
{% else %}
<img title="{{entry['Books']['title']}}" class="cover-height" src="{{ url_for('static', filename='generic_cover.jpg') }}">
{% endif %}
</div>
<div class="col-lg-10 col-sm-8 col-xs-12">
{% if entry['visible'] %}
{{entry['Books']['title']}}
{% if entry['Books']['series']|length > 0 %}
<br>
{{entry['Books']['series_index']|formatfloat(2)}} - {{entry['Books']['series'][0].name}}
{% endif %}
<br>
{% for author in entry['Books']['author'] %}
{{author.name.replace('|',',')}}
{% if not loop.last %}
&amp;
{% endif %}
{% endfor %}
{% else %}
{{_('Hidden Book')}}
<br>
{% endif %}
</div>
</div>
</div>
{% endfor %}
</div>
<div class="pull-right" style="display:flex; gap:10px; margin-top:30px; margin-bottom:60px;">
<a href="{{ url_for('shelf.show_shelf', shelf_id=shelf.id) }}" id="shelf_back" class="btn btn-primary">{{_('Back')}}</a>
<button onclick="sendData('{{ url_for('shelf.order_shelf', shelf_id=shelf.id) }}')" class="btn btn-default" id="ChangeOrder">{{_('Save')}}</button>
</div>
</div>
</div>
{% endblock %}
{% block js %}
<script src="{{ url_for('static', filename='js/libs/Sortable.min.js') }}"></script>
<script src="{{ url_for('static', filename='js/shelforder.js') }}"></script>
{% endblock %}
@@ -15,10 +15,8 @@
<link rel="shortcut icon" href="{{ url_for('static', filename='favicon.ico') }}">
<link href="{{ url_for('static', filename='css/libs/bootstrap.min.css') }}" rel="stylesheet" media="screen">
<link href="{{ url_for('static', filename='css/style.css') }}" rel="stylesheet" media="screen">
{% if g.current_theme == 1 %}
<link href="{{ url_for('static', filename='css/caliBlur.css') }}" rel="stylesheet" media="screen">
<link href="{{ url_for('static', filename='css/caliBlur_override.css') }}" rel="stylesheet" media="screen">
{% endif %}
<link href="{{ url_for('static', filename='css/caliBlur.css') }}" rel="stylesheet" media="screen">
<link href="{{ url_for('static', filename='css/caliBlur_override.css') }}" rel="stylesheet" media="screen">
{% block header %}{% endblock %}
</head>
<body class="{{ page }} shelf-down">
+54
View File
@@ -0,0 +1,54 @@
{% extends theme("layout.html") %}
{% block body %}
<h3>{{_('About')}} {{instance}}</h3>
{% if not current_user.role_admin() %}
<p>{{instance}} powered by
<a href="https://github.com/janeczku/calibre-web" title="Calibre-Web">Calibre-Web</a>.
</p>
{% endif %}
<h3>{{_('Library Statistics')}}</h3>
<table id="stats" class="table">
<tbody>
<tr>
<th>{{bookcounter}}</th>
<td>{{_('Books in this Library')}}</td>
</tr>
<tr>
<th>{{authorcounter}}</th>
<td>{{_('Authors in this Library')}}</td>
</tr>
<tr>
<th>{{categorycounter}}</th>
<td>{{_('Categories in this Library')}}</td>
</tr>
<tr>
<th>{{seriecounter}}</th>
<td>{{_('Series in this Library')}}</td>
</tr>
</tbody>
</table>
{% if current_user.role_admin() %}
<h3>{{_('System Statistics')}}</h3>
<table id="libs" class="table">
<thead>
<tr>
<th>{{_('Program')}}</th>
<th>{{_('Installed Version')}}</th>
</tr>
</thead>
<tbody>
{% for library,version in versions.items() %}
{% if version %}
<tr>
<th>{{library}}</th>
<td>{{version}}</td>
</tr>
{% endif %}
{% endfor %}
</tbody>
</table>
<p>{{instance}} powered by
<a href="https://github.com/janeczku/calibre-web" title="Calibre-Web">Calibre-Web</a>.
</p>
{% endif %}
{% endblock %}
@@ -0,0 +1,191 @@
{% extends theme("layout.html") %}
{% block body %}
<img class="admin-user-background" src="{{ url_for('static', filename='css/img/user-icon.svg') }}" alt="" aria-hidden="true">
<div class="discover">
<h1>{{title}}</h1>
<form role="form" method="POST" autocomplete="off">
<input type="hidden" name="csrf_token" value="{{ csrf_token() }}">
<div class="col-md-10 col-lg-8">
{% if new_user or ( current_user and content.name != "Guest" and current_user.role_admin() ) %}
<div class="form-group required">
<label for="name">{{_('Username')}}</label>
<input type="text" class="form-control" name="name" id="name" value="{{ content.name if content.name != None }}" autocomplete="off">
</div>
{% endif %}
<div class="form-group">
<label for="email">{{_('Email')}}</label>
<input type="email" class="form-control" name="email" id="email" value="{{ content.email if content.email != None }}" autocomplete="off">
</div>
{% if ( current_user and current_user.role_passwd() or current_user.role_admin() ) and not content.role_anonymous() %}
{% if current_user and current_user.role_admin() and not new_user and not profile and ( mail_configured and content.email if content.email != None ) %}
<a class="btn btn-default postAction" id="resend_password" role="button" data-action="{{url_for('admin.reset_user_password', user_id = content.id) }}">{{_('Reset user Password')}}</a>
{% endif %}
<div class="form-group">
<label for="password">{{_('Password')}}</label>
<input type="password" class="form-control" name="password" id="password" data-lang="{{ current_user.locale }}" data-verify="{{ config.config_password_policy }}" {% if config.config_password_policy %} data-min={{ config.config_password_min_length }} data-word={{ config.config_password_character }} data-special={{ config.config_password_special }} data-upper={{ config.config_password_upper }} data-lower={{ config.config_password_lower }} data-number={{ config.config_password_number }}{% endif %} value="" autocomplete="off">
</div>
{% endif %}
<div class="form-group">
<label for="kindle_mail">{{_('Send to eReader Email Address. Use comma to separate emails for multiple eReaders')}}</label>
<input type="email" class="form-control" name="kindle_mail" id="kindle_mail" value="{{ content.kindle_mail if content.kindle_mail != None }}">
</div>
{% if not content.role_anonymous() %}
<div class="form-group">
<label for="locale">{{_('Language')}}</label>
<select name="locale" id="locale" class="form-control">
{% for translation in translations %}
<option value="{{translation}}" {% if translation|string == content.locale %}selected{% endif %}>{{ translation.display_name|capitalize }}</option>
{% endfor %}
</select>
</div>
{% endif %}
<div class="form-group">
<label for="default_language">{{_('Language of Books')}}</label>
<select name="default_language" id="default_language" class="form-control">
<option value="all" {% if content.default_language == "all" %}selected{% endif %}>{{ _('Show All') }}</option>
{% for language in languages %}
<option value="{{ language.lang_code }}" {% if content.default_language == language.lang_code %}selected{% endif %}>{{ language.name }}</option>
{% endfor %}
</select>
</div>
{% if registered_oauth.keys()| length > 0 and not new_user and profile %}
{% for id, name in registered_oauth.items() %}
<div class="form-group">
<label>{{ name }} {{_('OAuth Settings')}}</label>
{% if id not in oauth_status %}
<a href="{{ url_for('oauth.'+ name +'_login') }}" id="config_{{ id }}_oauth" class="btn btn-primary">{{_('Link')}}</a>
{% else %}
<a href="{{ url_for('oauth.'+ name +'_login_unlink') }}" id="config_{{ id }}_oauth" class="btn btn-primary">{{_('Unlink')}}</a>
{% endif %}
{% endfor %}
</div>
{% endif %}
{% if kobo_support and not new_user %}
<label>{{ _('Kobo Sync Token')}}</label>
<div class="form-group col">
<a class="btn btn-default" id="config_create_kobo_token" style="margin-bottom: 10px;" data-toggle="modal" data-target="#modal_kobo_token" data-remote="false" href="{{ url_for('kobo_auth.generate_auth_token', user_id=content.id) }}">{{_('Create/View')}}</a>
<div class="btn btn-danger" id="config_delete_kobo_token" style="margin-bottom: 10px;{% if not content.remote_auth_token.first() %} display: none;{% endif %}" data-value="{{ content.id }}" data-remote="false">{{_('Delete')}}</div>
</div>
<div class="form-group col">
<div class="btn btn-default" id="kobo_full_sync" data-value="{% if current_user.role_admin() %}{{ content.id }}{% else %}0{% endif %}" {% if not content.remote_auth_token.first() %} style="display: none;" {% endif %}>{{_('Force full kobo sync')}}</div>
</div>
{% endif %}
<div class="col-sm-6">
{% for element in sidebar %}
{% if element['config_show'] %}
<div class="form-group">
<input type="checkbox" name="show_{{element['visibility']}}" id="show_{{element['visibility']}}" {% if content.check_visibility(element['visibility']) %}checked{% endif %}>
<label for="show_{{element['visibility']}}">{{element['show_text']}}</label>
</div>
{% endif %}
{% endfor %}
<div class="form-group">
<input type="checkbox" name="Show_detail_random" id="Show_detail_random" {% if content.show_detail_random() %}checked{% endif %}>
<label for="Show_detail_random">{{_('Show Random Books in Detail View')}}</label>
</div>
</div>
<div class="col-sm-6">
{% if current_user and current_user.role_admin() and not profile %}
{% if not content.role_anonymous() %}
<div class="form-group">
<input type="checkbox" name="admin_role" id="admin_role" {% if content.role_admin() %}checked{% endif %}>
<label for="admin_role">{{_('Admin User')}}</label>
</div>
{% endif %}
<div class="form-group">
<input type="checkbox" name="download_role" id="download_role" {% if content.role_download() %}checked{% endif %}>
<label for="download_role">{{_('Allow Downloads')}}</label>
</div>
<div class="form-group">
<input type="checkbox" name="viewer_role" id="viewer_role" {% if content.role_viewer() %}checked{% endif %}>
<label for="viewer_role">{{_('Allow eBook Viewer')}}</label>
</div>
{% if config.config_uploading %}
<div class="form-group">
<input type="checkbox" name="upload_role" id="upload_role" {% if content.role_upload() %}checked{% endif %}>
<label for="upload_role">{{_('Allow Uploads')}}</label>
</div>
{% endif %}
<div class="form-group">
<input type="checkbox" name="edit_role" data-control="edit_settings" id="edit_role" {% if content.role_edit() %}checked{% endif %}>
<label for="edit_role">{{_('Allow Edit')}}</label>
</div>
<div data-related="edit_settings">
<div class="form-group">
<input type="checkbox" name="delete_role" id="delete_role" {% if content.role_delete_books() %}checked{% endif %}>
<label for="delete_role">{{_('Allow Delete Books')}}</label>
</div>
</div>
{% if not content.role_anonymous() %}
<div class="form-group">
<input type="checkbox" name="passwd_role" id="passwd_role" {% if content.role_passwd() %}checked{% endif %}>
<label for="passwd_role">{{_('Allow Changing Password')}}</label>
</div>
<div class="form-group">
<input type="checkbox" name="edit_shelf_role" id="edit_shelf_role" {% if content.role_edit_shelfs() %}checked{% endif %}>
<label for="edit_shelf_role">{{_('Allow Editing Public Shelves')}}</label>
</div>
{% endif %}
{% endif %}
{% if kobo_support and not content.role_anonymous() and not simple%}
<div class="form-group">
<input type="checkbox" name="kobo_only_shelves_sync" id="kobo_only_shelves_sync" {% if content.kobo_only_shelves_sync %}checked{% endif %}>
<label for="kobo_only_shelves_sync">{{_('Sync only books in selected shelves with Kobo')}}</label>
</div>
{% endif %}
</div>
</div>
{% if ( current_user and current_user.role_admin() and not new_user ) and not simple %}
<div class="col-sm-12">
<div class="form-group clearfix">
<a href="#" id="get_user_tags" class="btn btn-default" data-id="{{content.id}}" data-toggle="modal" data-target="#restrictModal">{{_('Add Allowed/Denied Tags')}}</a>
<a href="#" id="get_user_column_values" data-id="{{content.id}}" class="btn btn-default" data-toggle="modal" data-target="#restrictModal">{{_('Add allowed/Denied Custom Column Values')}}</a>
</div>
</div>
{% endif %}
<div class="col-sm-12 clearfix">
{% if current_user and current_user.role_admin() and not profile and not new_user and not content.role_anonymous() %}
<div class="btn btn-danger pull-left" id="btndeluser" data-value="{{ content.id }}" data-remote="false" >{{_('Delete User')}}</div>
{% endif %}
<div class="pull-right">
{% if not profile %}
<div class="btn btn-default" data-back="{{ url_for('admin.admin') }}" id="back">{{_('Cancel')}}</div>
{% endif %}
<div id="user_submit" class="btn btn-default">{{_('Save')}}</div>
</div>
</div>
</form>
</div>
<div class="modal fade" id="modal_kobo_token" tabindex="-1" role="dialog" aria-labelledby="kobo_tokenModalLabel">
<div class="modal-dialog modal-lg" role="document">
<div class="modal-content">
<div class="modal-header">
<button type="button" class="close" data-dismiss="modal" aria-label="Close"><span aria-hidden="true">&times;</span></button>
<h4 class="modal-title" id="kobo_tokenModalLabel">{{_('Generate Kobo Auth URL')}}</h4>
</div>
<div class="modal-body">...</div>
<div class="modal-footer">
<button type="button" id="kobo_close" class="btn btn-primary" data-dismiss="modal">{{_('Close')}}</button>
</div>
</div>
</div>
</div>
{% endblock %}
{% block modal %}
{{ restrict_modal() }}
{{ delete_confirm_modal() }}
{% endblock %}
{% 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-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/pwstrength/i18next.min.js') }}"></script>
<script src="{{ url_for('static', filename='js/libs/pwstrength/i18nextHttpBackend.min.js') }}"></script>
<script src="{{ url_for('static', filename='js/libs/pwstrength/pwstrength-bootstrap.js') }}"></script>
<script src="{{ url_for('static', filename='js/password.js') }}"></script>
<script src="{{ url_for('static', filename='js/table.js') }}"></script>
{% endblock %}
+7
View File
@@ -0,0 +1,7 @@
{
"name": "Simple Theme",
"identifier": "simple",
"application": "cps",
"author": "Calibre-Web",
"description": "Simple reader-oriented theme"
}
+7
View File
@@ -0,0 +1,7 @@
{
"name": "Standard Theme",
"identifier": "standard",
"application": "cps",
"author": "Calibre-Web",
"description": "Standard Calibre-Web theme"
}
@@ -1,4 +1,4 @@
{% extends "layout.html" %}
{% extends theme("layout.html") %}
{% macro display_bool_setting(setting_value) -%}
{% if setting_value %}<span class="glyphicon glyphicon-ok"></span>{% else %}<span class="glyphicon glyphicon-remove"></span>{% endif %}
{%- endmacro %}
@@ -264,7 +264,9 @@
<div class="modal-dialog modal-sm">
<!-- Modal content-->
<div class="modal-content">
<div class="modal-header bg-info"></div>
<div class="modal-header bg-info">
<h4 class="modal-title">{{_('Restart Calibre-Web')}}</h4>
</div>
<div class="modal-body text-center">
<p>{{_('Are you sure you want to restart?')}}</p>
<div id="spinner" class="spinner" style="display:none;">
@@ -282,6 +284,7 @@
<!-- Modal content-->
<div class="modal-content">
<div class="modal-header bg-info">
<h4 class="modal-title">{{_('Stop Calibre-Web')}}</h4>
</div>
<div class="modal-body text-center">
<p>{{_('Are you sure you want to shutdown?')}}</p>
@@ -296,7 +299,7 @@
<!-- Modal content-->
<div class="modal-content">
<div class="modal-header bg-info text-center">
<span id="DialogHeader">{{_('Updating, please do not reload this page')}}</span>
<h4 class="modal-title">{{_('Update Calibre-Web')}}</h4>
</div>
<div class="modal-body text-center">
<div id="spinner2" class="spinner2" style="display:none;">
@@ -1,4 +1,4 @@
{% extends "layout.html" %}
{% extends theme("layout.html") %}
{% block body %}
<h2>{{title}}</h2>
@@ -1,4 +1,4 @@
{% extends "basic_layout.html" %}
{% extends theme("basic_layout.html") %}
{% block body %}
<div>
@@ -1,4 +1,4 @@
{% extends "basic_layout.html" %}
{% extends theme("basic_layout.html") %}
{% block body %}
<div class="pagination">
@@ -0,0 +1,47 @@
<!DOCTYPE html>
<html lang="{{ current_user.locale }}">
<head>
<title>{{instance}} | {{title}}</title>
<meta charset="utf-8">
<meta name='viewport' content='initial-scale=1,maximum-scale=5,user-scalable=no' />
<link href="{{ url_for('static', filename='css/basic.css') }}" rel="stylesheet" media="screen">
</head>
<body>
<div>
<div>
{% if current_user.is_authenticated or g.allow_anonymous %}
<nav>
<a href="{{url_for('basic.index')}}">
<span><h1>{{_('Home')}}</h1></span>
</a>
<div class="search">
<form role="search" action="{{url_for('basic.index')}}" method="GET">
<input type="text" id="query" name="query" placeholder="{{_('Search Library')}}" value="{{ searchterm }}">
<span>
<button type="submit" id="query_submit">{{_('Search')}}</button>
</span>
</form>
</div>
{% if not current_user.is_anonymous %}
<a href="{{url_for('web.logout')}}">
<span>{{_('Logout')}}</span>
</a>
{% endif %}
</nav>
<div class="theme">
<a href="{{url_for('web.index')}}">
<span>{{_('Normal Theme')}}</span>
</a>
</div>
{% endif %}
</div>
</div>
<div class="body">
{% block body %}
{% endblock %}
</div>
</body>
</html>
@@ -1,4 +1,4 @@
{% extends "layout.html" %}
{% extends theme("layout.html") %}
{% block body %}
{% if book %}
<div class="col-sm-3 col-lg-3 col-xs-12">
@@ -1,4 +1,4 @@
{% extends "layout.html" %}
{% extends theme("layout.html") %}
{% macro text_table_row(parameter, edit_text, show_text, validate, sort) -%}
<th data-field="{{ parameter }}" id="{{ parameter }}"
{% if sort %}data-sortable="true" {% endif %}
@@ -49,8 +49,9 @@
<link href="{{ url_for('static', filename='css/libs/bootstrap-wysihtml5-0.0.3.css') }}" rel="stylesheet">
{% endblock %}
{% block body %}
<h2 class="{{page}}">{{_(title)}}</h2>
<input type="hidden" name="csrf_token" value="{{ csrf_token() }}">
<div class="discover">
<h2 class="{{page}}">{{_(title)}}</h2>
<input type="hidden" name="csrf_token" value="{{ csrf_token() }}">
<div class="col-xs-12 col-sm-6">
<div class="row form-group">
<div class="btn btn-default disabled" id="merge_books" aria-disabled="true">
@@ -134,6 +135,7 @@
</tr>
</thead>
</table>
</div>
{% endblock %}
{% block modal %}
@@ -1,4 +1,4 @@
{% extends "layout.html" %}
{% extends theme("layout.html") %}
{% block flash %}
<div id="spinning_success" class="row-fluid text-center" style="display:none;">
<div class="alert alert-info"><img id="img-spinner" src="{{ url_for('static', filename='css/libs/images/loading-icon.gif') }}"/></div>
@@ -1,4 +1,4 @@
{% extends "layout.html" %}
{% extends theme("layout.html") %}
{% block flash %}
<div id="spinning_success" class="row-fluid text-center" style="display:none;">
<div class="alert alert-info"><img id="img-spinner" src="{{ url_for('static', filename='css/libs/images/loading-icon.gif') }}"/></div>
@@ -1,4 +1,4 @@
{% extends "layout.html" %}
{% extends theme("layout.html") %}
{% block header %}
<link href="{{ url_for('static', filename='css/libs/bootstrap-table.min.css') }}" rel="stylesheet">
<link href="{{ url_for('static', filename='css/libs/bootstrap-editable.css') }}" rel="stylesheet">
@@ -8,7 +8,7 @@
<h2>{{title}}</h2>
<form role="form" method="POST" autocomplete="off" >
<input type="hidden" name="csrf_token" value="{{ csrf_token() }}">
<div class="panel-group" class="col-md-10 col-lg-6">
<div class="panel-group col-md-11 col-lg-8">
<div class="panel panel-default">
<div class="panel-heading">
<h4 class="panel-title">
@@ -39,8 +39,9 @@
<div class="form-group">
<label for="config_theme">{{_('Theme')}}</label>
<select name="config_theme" id="config_theme" class="form-control">
<option value="0" {% if conf.config_theme == 0 %}selected{% endif %}>{{ _("Standard Theme") }}</option>
<option value="1" {% if conf.config_theme == 1 %}selected{% endif %}>{{ _("caliBlur! Dark Theme") }}</option>
{% for theme in g.themes %}
<option value="{{ theme['id'] }}" {% if conf.config_theme == theme['id'] %}selected{% endif %}>{{ _(theme['label']) }}</option>
{% endfor %}
</select>
</div>
<div class="form-group">
@@ -1,4 +1,4 @@
{% extends is_xhr|yesno("fragment.html", "layout.html") %}
{% extends is_xhr|yesno("fragment.html", theme("layout.html")) %}
{% block header %}
<meta property="og:type" content="book" />
<meta property="og:title" content="{{ entry.title|truncate(35) }}" />
@@ -172,8 +172,13 @@
<p>
<span class="glyphicon glyphicon-link"></span>
{% for identifier in entry.identifiers %}
<a href="{{ identifier|escape }}" target="_blank" class="btn btn-xs btn-success"
role="button">{{ identifier.format_type() }}</a>
{% set identifier_link = identifier.__repr__() %}
{% if identifier_link != identifier.val %}
<a href="{{ identifier_link|escape }}" target="_blank" class="btn btn-xs btn-success"
role="button">{{ identifier.format_type() }}</a>
{% else %}
<span class="btn btn-xs btn-success" role="button">{{ identifier.format_type() }}</span>
{% endif %}
{% endfor %}
</p>
</div>
@@ -256,8 +261,7 @@
method="POST">
<input type="hidden" name="csrf_token" value="{{ csrf_token() }}">
<label class="block-label">
<input id="have_read_cb" data-checked="{{ _('Mark As Unread') }}"
data-unchecked="{{ _('Mark As Read') }}" type="checkbox"
<input id="have_read_cb" type="checkbox"
{% if entry.read_status %}checked{% endif %}>
<span data-toggle="tooltip" title="{{_('Mark Book as Read or Unread')}}">{{ _('Read') }}</span>
</label>
@@ -269,8 +273,7 @@
method="POST">
<input type="hidden" name="csrf_token" value="{{ csrf_token() }}">
<label class="block-label">
<input id="archived_cb" data-checked="{{ _('Restore from archive') }}"
data-unchecked="{{ _('Add to archive') }}" type="checkbox"
<input id="archived_cb" type="checkbox"
{% if entry.is_archived %}checked{% endif %}>
<span data-toggle="tooltip" title="{{_('Mark Book as archived or not, to hide it in Calibre-Web and delete it from Kobo Reader')}}">{{ _('Archive') }}</span>
</label>
@@ -1,4 +1,4 @@
{% extends "layout.html" %}
{% extends theme("layout.html") %}
{% block header %}
<link href="{{ url_for('static', filename='css/libs/bootstrap-table.min.css') }}" rel="stylesheet">
<link href="{{ url_for('static', filename='css/libs/bootstrap-editable.css') }}" rel="stylesheet">
@@ -62,12 +62,12 @@
<button type="button" id="attachement_size" class="btn btn-default" disabled>MB</button>
</span>
</div>
<a href="{{ url_for('admin.admin') }}" id="email_back" class="btn btn-default">{{_('Back')}}</a>
<button type="submit" name="submit" value="submit" class="btn btn-default">{{_('Save')}}</button>
<button type="submit" name="test" value="test" class="btn btn-default">{{_('Save and Send Test Email')}}</button>
{% if feature_support['gmail'] %}
</div>
{% endif %}
<a href="{{ url_for('admin.admin') }}" id="email_back" class="btn btn-default">{{_('Back')}}</a>
</form>
{% if g.allow_registration and not simple%}
<div class="col-md-10 col-lg-6">
@@ -1,6 +1,7 @@
{% import 'image.html' as image %}
{% extends "layout.html" %}
{% extends theme("layout.html") %}
{% block body %}
<div class="discover">
<h1 class="{{page}}">{{_(title)}}</h1>
<div class="filterheader hidden-xs">
@@ -42,7 +43,7 @@
{% endfor %}
</div>
{% endif %}
</div>
{% endblock %}
{% block js %}
@@ -15,9 +15,6 @@
<link rel="shortcut icon" href="{{ url_for('static', filename='favicon.ico') }}">
<link href="{{ url_for('static', filename='css/libs/bootstrap.min.css') }}" rel="stylesheet" media="screen">
<link href="{{ url_for('static', filename='css/style.css') }}" rel="stylesheet" media="screen">
{% if g.current_theme == 1 %}
<link href="{{ url_for('static', filename='css/caliBlur.css') }}" rel="stylesheet" media="screen">
{% endif %}
</head>
<body>
<div class="container-fluid" style="overflow-y: auto">
@@ -1,5 +1,5 @@
{% import 'image.html' as image %}
{% extends "layout.html" %}
{% extends theme("layout.html") %}
{% block body %}
{% if current_user.show_detail_random() and page != "discover" %}
<div class="discover random-books">
@@ -1,6 +1,6 @@
{% from 'modal_dialogs.html' import restrict_modal, delete_book, filechooser_modal, delete_confirm_modal, change_confirm_modal %}
{% import 'image.html' as image %}
<!DOCTYPE html>
{% from theme('modal_dialogs.html') import restrict_modal, delete_book, filechooser_modal, delete_confirm_modal, change_confirm_modal %}
{% import 'image.html' as image %}
<html lang="{{ current_user.locale }}">
<head>
<title>{{instance}} | {{title}}</title>
@@ -18,12 +18,8 @@
{% block header %}{% endblock %}
<link href="{{ url_for('static', filename='css/style.css') }}" rel="stylesheet" media="screen">
<link href="{{ url_for('static', filename='css/upload.css') }}" rel="stylesheet" media="screen">
{% if g.current_theme == 1 %}
<link href="{{ url_for('static', filename='css/caliBlur.css') }}" rel="stylesheet" media="screen">
<link href="{{ url_for('static', filename='css/caliBlur_override.css') }}" rel="stylesheet" media="screen">
{% endif %}
</head>
<body class="{{ page }} {{ bodyClass }}{% if g.current_theme == 1 %} blur{% endif %}" data-text="{{_('Home')}}" data-textback="{{_('Back')}}">
<body class="{{ page }} {{ bodyClass }}">
<!-- Static navbar -->
<div class="navbar navbar-default navbar-static-top" role="navigation">
<div class="container-fluid">
@@ -36,10 +32,6 @@
</button>
<a class="navbar-brand" href="{{url_for('web.index')}}">{{instance}}</a>
</div>
{% if g.current_theme == 1 %}
<div class="home-btn"><a class="home-btn-tooltip" href="{{url_for("web.index", page=1)}}" data-toggle="tooltip" title="" data-placement="bottom" data-original-title="Home"></a></div>
<div class="plexBack"><a href="{{url_for('web.index')}}"></a></div>
{% endif %}
{% if current_user.is_authenticated or g.allow_anonymous %}
<form class="navbar-form navbar-left" role="search" action="{{url_for('search.simple_search')}}" method="GET">
<div class="form-group input-group input-group-sm">
@@ -60,20 +52,6 @@
{% endif %}
<ul class="nav navbar-nav navbar-right" id="main-nav">
{% if current_user.is_authenticated or g.allow_anonymous %}
{% if g.current_theme == 1 %}
<li class="dropdown"><a href="#" class="dropdown-toggle profileDrop" data-toggle="dropdown" role="button" aria-haspopup="true" aria-expanded="false"><span class="glyphicon glyphicon-user"></span></a>
<ul class="dropdown-menu profileDropli">
<li><a id="top_user" data-text="{{_('Account')}}" href="{{url_for('web.profile')}}" ><span class="glyphicon glyphicon-user"></span> <span class="hidden-sm">{{current_user.name}}</span></a></li>
{% if g.allow_registration and not current_user.is_authenticated %}
<li><a id="login" href="{{url_for('web.login')}}"><span class="glyphicon glyphicon-log-in"></span> {{_('Login')}}</a></li>
<li><a id="register" href="{{url_for('web.register')}}"><span class="glyphicon glyphicon-user"></span> {{_('Register')}}</a></li>
{% endif %}
{% if not current_user.is_anonymous %}
<li><a id="logout" href="{{url_for('web.logout', next=(request.path + "?" + request.query_string.decode("utf-8")).rstrip("?"))}}"><span class="glyphicon glyphicon-log-out"></span> <span class="hidden-sm">{{_('Logout')}}</span></a></li>
{% endif %}
</ul>
</li>
{% endif %}
{% if current_user.role_upload() and g.allow_upload %}
<li>
<form id="form-upload" class="navbar-form" action="{{ url_for('edit-book.upload') }}" data-title="{{_('Uploading...')}}" data-footer="{{_('Close')}}" data-failed="{{_('Error')}}" data-message="{{_('Upload done, processing, please wait...')}}" method="post" enctype="multipart/form-data">
@@ -90,16 +68,14 @@
<li class="top_tasks"><a id="top_tasks" href="{{url_for('tasks.get_tasks_status')}}"><span class="glyphicon glyphicon-tasks"></span> <span class="hidden-sm">{{_('Tasks')}}</span></a></li>
{% endif %}
{% if current_user.role_admin() %}
<li><a id="top_admin" data-text="{{_('Settings')}}" href="{{url_for('admin.admin')}}"><span class="glyphicon glyphicon-dashboard"></span> <span class="hidden-sm">{{_('Admin')}}</span></a></li>
<li><a id="top_admin" href="{{url_for('admin.admin')}}"><span class="glyphicon glyphicon-dashboard"></span> <span class="hidden-sm">{{_('Admin')}}</span></a></li>
{% endif %}
{% if g.current_theme == 0 %}
<li><a id="top_user" data-text="{{_('Account')}}" href="{% if not current_user.is_anonymous %}{{url_for('web.profile')}}{% else %}{{url_for('web.login', next=(request.path + "?" + request.query_string.decode("utf-8")).rstrip("?"))}}{% endif %}"><span class="glyphicon glyphicon-user"></span> <span class="hidden-sm">{{current_user.name}}</span></a></li>
<li><a id="top_user" href="{% if not current_user.is_anonymous %}{{url_for('web.profile')}}{% else %}{{url_for('web.login', next=(request.path + "?" + request.query_string.decode("utf-8")).rstrip("?"))}}{% endif %}"><span class="glyphicon glyphicon-user"></span> <span class="hidden-sm">{{current_user.name}}</span></a></li>
{% if not current_user.is_anonymous %}
<li><a id="logout" href="{{url_for('web.logout', next=(request.path + "?" + request.query_string.decode("utf-8")).rstrip("?"))}}"><span class="glyphicon glyphicon-log-out"></span> <span class="hidden-sm">{{_('Logout')}}</span></a></li>
{% endif %}
{% endif %}
{% endif %}
{% if g.allow_registration and not current_user.is_authenticated and g.current_theme == 0 %}
{% if g.allow_registration and not current_user.is_authenticated %}
<li><a id="login" href="{{url_for('web.login')}}"><span class="glyphicon glyphicon-log-in"></span> {{_('Login')}}</a></li>
<li><a id="register" href="{{url_for('web.register')}}"><span class="glyphicon glyphicon-user"></span> {{_('Register')}}</a></li>
{% endif %}
@@ -130,20 +106,12 @@
{%endif%}
{% endfor %}
{% block flash %}{% endblock %}
{% if g.current_theme == 1 %}
<div id="loader" hidden="true">
<center>
<h3>{{_('Uploading...')}}</h3>
<span>{{_("Please do not refresh the page")}}</span>.
</center>
</div>
{%endif%}
<div class="container-fluid">
<div class="row-fluid">
{% if current_user.is_authenticated or g.allow_anonymous %}
<div class="col-sm-2">
<nav class="navigation">
<ul class="list-unstyled" id="scnd-nav" intent in-standard-append="nav.navigation" in-mobile-after="#main-nav" in-mobile-class="nav navbar-nav">
<ul class="list-unstyled" id="scnd-nav" intent in-standard-append="nav.navigation" in-tablet-after="#main-nav" in-tablet-class="nav navbar-nav" in-mobile-after="#main-nav" in-mobile-class="nav navbar-nav">
<li class="nav-head hidden-xs">{{_('Browse')}}</li>
{% for element in sidebar %}
{% if current_user.check_visibility(element['visibility']) and element['public'] %}
@@ -217,12 +185,6 @@
<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/main.js') }}"></script>
{% 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/compromise.min.js') }}"></script>
<script src="{{ url_for('static', filename='js/libs/readmore.min.js') }}"></script>
<script src="{{ url_for('static', filename='js/caliBlur.js') }}"></script>
{% endif %}
{% block js %}{% endblock %}
</body>
</html>
@@ -1,4 +1,4 @@
{% extends "layout.html" %}
{% extends theme("layout.html") %}
{% block body %}
<h1 class="{{page}}">{{_(title)}}</h1>
@@ -155,7 +155,7 @@
<p>
<input type="hidden" name="csrf_token" value="{{ csrf_token() }}">
<label class="block-label">
<input id="have_read_cb" data-checked="{{_('Mark As Unread')}}" data-unchecked="{{_('Mark As Read')}}" type="checkbox" {% if entry.read_status %}checked{% endif %} disabled>
<input id="have_read_cb" type="checkbox" {% if entry.read_status %}checked{% endif %} disabled>
<span>{{_('Read')}}</span>
</label>
</p>
@@ -163,7 +163,7 @@
<p>
<input type="hidden" name="csrf_token" value="{{ csrf_token() }}">
<label class="block-label">
<input id="archived_cb" data-checked="{{_('Restore from archive')}}" data-unchecked="{{_('Add to archive')}}" type="checkbox" {% if entry.is_archived %}checked{% endif %} disabled>
<input id="archived_cb" type="checkbox" {% if entry.is_archived %}checked{% endif %} disabled>
<span>{{_('Archived')}}</span>
</label>
</p>
@@ -1,4 +1,4 @@
{% extends "layout.html" %}
{% extends theme("layout.html") %}
{% block body %}
<div class="well col-sm-6 col-sm-offset-2">
<h2 style="margin-top: 0">{{_('Login')}}</h2>
@@ -11,7 +11,14 @@
</div>
<div class="form-group">
<label for="password">{{_('Password')}}</label>
<input type="password" class="form-control" id="password" name="password" placeholder="{{_('Password')}}" value="{{ password }}">
<div class="input-group">
<input type="password" class="form-control" id="password" name="password" placeholder="{{_('Password')}}" value="{{ password }}">
<span class="input-group-btn">
<button type="button" class="btn btn-default toggle-password" data-target="#password" aria-label="{{_('Show password')}}" title="{{_('Show password')}}">
<span class="glyphicon glyphicon-eye-open" aria-hidden="true"></span>
</button>
</span>
</div>
</div>
<div class="checkbox">
<label>
@@ -22,14 +29,6 @@
{% if config.config_login_type == 0 and mail%}
<button type="submit" name="forgot" value="forgot" class="btn btn-default">{{_('Forgot Password?')}}</button>
{% endif %}
{% if g.current_theme == 1 %}
{% if g.allow_registration %}
<a class="btn btn-default" id="register" href="{{url_for('web.register')}}"><span class="glyphicon glyphicon-user"></span> {{_('Register')}}</a>
{% endif %}
{% if g.allow_anonymous %}
<a class="btn btn-default" href="{{url_for('web.index')}}">{{ _('Home') }}</a>
{% endif %}
{%endif%}
{% if config.config_remote_login %}
<a href="{{url_for('remotelogin.remote_login')}}" id="remote_login" class="pull-right">{{_('Log in with Magic Link')}}</a>
{% endif %}
@@ -1,4 +1,4 @@
{% extends "layout.html" %}
{% extends theme("layout.html") %}
{% block body %}
<div id="log_group" class="inputs">
{% if log_enable %}
@@ -43,7 +43,7 @@
<div class="modal-dialog">
<div class="modal-content">
<div class="modal-header bg-danger text-center">
<span>{{_('Are you really sure?')}}</span>
<h4 class="modal-title">{{_('Delete Book')}}</h4>
</div>
<div class="modal-body text-center">
<p>
@@ -96,8 +96,8 @@
</div>
<div class="modal-footer">
<div class="text-left" id="element_selected"></div>
<input type="button" class="btn btn-primary" data-path="" data-link="" data-folderonly="" data-filefilter="" data-newfile="" value="{{_('Select')}}" name="file_confirm" id="file_confirm" data-dismiss="modal">
<button type="button" id="file_abort" class="btn btn-default" data-dismiss="modal">{{_('Cancel')}}</button>
<input type="button" class="btn btn-primary" data-path="" data-link="" data-folderonly="" data-filefilter="" data-newfile="" value="{{_('Select')}}" name="file_confirm" id="file_confirm" data-dismiss="modal">
</div>
</div>
</div>
@@ -1,8 +1,8 @@
{% extends "layout.html" %}
{% extends theme("layout.html") %}
{% block body %}
<div class="well col-sm-6 col-sm-offset-2">
<h2 style="margin-top: 0">{{_('Register New Account')}}</h2>
<form method="POST" role="form">
<h2 style="margin-top: 0">{{_('Register New Account')}}</h2>
<input type="hidden" name="csrf_token" value="{{ csrf_token() }}">
{% if not config.config_register_email %}
<div class="form-group required">
@@ -1,4 +1,4 @@
{% extends "layout.html" %}
{% extends theme("layout.html") %}
{% block body %}
<div class="well">
<input type="hidden" name="csrf_token" value="{{ csrf_token() }}">
@@ -1,6 +1,7 @@
{% extends "layout.html" %}
{% extends theme("layout.html") %}
{% block body %}
<div class="well col-sm-6 col-sm-offset-2">
<form method="POST" role="form">
<h2 style="margin-top: 0">{{ _('Approve Magic Link Login') }}</h2>
<p>{{ _('Review the device that requested access before approving this login.') }}</p>
<dl class="dl-horizontal">
@@ -10,7 +11,6 @@
<dd>{{ request_user_agent or _('Unknown') }}</dd>
</dl>
<p>{{ _('If you approve this request, the device that started it will be logged in as %(username)s.', username=current_user.name) }}</p>
<form method="POST" role="form">
<input type="hidden" name="csrf_token" value="{{ csrf_token() }}">
<button type="submit" class="btn btn-primary">{{ _('Approve Login') }}</button>
<a class="btn btn-default" href="{{ url_for('web.index') }}">{{ _('Cancel') }}</a>
@@ -1,4 +1,4 @@
{% extends "layout.html" %}
{% extends theme("layout.html") %}
{% block header %}
<link href="{{ url_for('static', filename='css/libs/bootstrap-table.min.css') }}" rel="stylesheet">
<link href="{{ url_for('static', filename='css/libs/bootstrap-editable.css') }}" rel="stylesheet">
@@ -1,5 +1,5 @@
{% import 'image.html' as image %}
{% extends "layout.html" %}
{% extends theme("layout.html") %}
{% block body %}
<div class="discover">
{% if entries|length < 1 %}
@@ -1,4 +1,4 @@
{% extends "layout.html" %}
{% extends theme("layout.html") %}
{% block body %}
<h1 class="{{page}}">{{title}}</h1>
<div class="col-md-10 col-lg-6">
@@ -1,5 +1,5 @@
{% import 'image.html' as image %}
{% extends "layout.html" %}
{% extends theme("layout.html") %}
{% block body %}
<div class="discover">
<h2>{{title}}</h2>
@@ -1,4 +1,4 @@
{% extends "layout.html" %}
{% extends theme("layout.html") %}
{% block body %}
<div class="discover">
<h1>{{title}}</h1>
@@ -1,4 +1,4 @@
{% extends "layout.html" %}
{% extends theme("layout.html") %}
{% block body %}
<div class="col-sm-8 col-lg-8 col-xs-12">
<h2>{{title}}</h2>
@@ -0,0 +1,73 @@
<!DOCTYPE html>
<html lang="{{ current_user.locale }}">
<head>
<title>{{instance}} | {{title}}</title>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1">
<meta name="mobile-web-app-capable" content="yes">
{% if g.google_site_verification|length > 0 %}
<meta name="google-site-verification" content="{{g.google_site_verification}}">
{% endif %}
<!-- Bootstrap -->
<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 href="{{ url_for('static', filename='css/libs/bootstrap.min.css') }}" rel="stylesheet" media="screen">
<link href="{{ url_for('static', filename='css/style.css') }}" rel="stylesheet" media="screen">
{% for theme_css in g.theme['css_files'] %}
<link href="{{ url_for('static', filename=theme_css) }}" rel="stylesheet" media="screen">
{% endfor %}
{% block header %}{% endblock %}
</head>
<body class="{{ page }} shelf-down">
{% block body %}
<div class="discover">
<h2>{{title}}</h2>
<div class="row display-flex">
{% for entry in entries %}
<div class="col-sm-3 col-lg-2 col-xs-6 book">
<div class="meta">
<p title="{{entry.Books.title}}" class="title">{{entry.Books.title|shortentitle}}</p>
<p class="author">
{% for author in entry.Books.authors %}
<a href="{{url_for('web.books_list', data='author', sort_param='stored', book_id=author.id) }}">{{author.name.replace('|',',')}}</a>
{% if not loop.last %}
&amp;
{% endif %}
{% endfor %}
</p>
{% if entry.Books.series.__len__() > 0 %}
<p class="series">
<a href="{{url_for('web.books_list', data='series', sort_param='stored', book_id=entry.Books.series[0].id )}}">
{{entry.Books.series[0].name}}
</a>
({{entry.Books.series_index|formatfloat(2)}})
</p>
{% endif %}
</div>
<div class="btn-group" role="group" aria-label="Download, send to eReader, reading">
{% if current_user.role_download() %}
{% if entry.Books.data|length %}
<div class="btn-group" role="group">
{% for format in entry.Books.data %}
<a href="{{ url_for('web.download_link', book_id=entry.Books.id, book_format=format.format|lower, anyname=entry.Books.id|string+'.'+format.format|lower|replace('kepub', 'kepub.epub')) }}" id="btnGroupDrop{{entry.Books.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 %}
</div>
</div>
{% endfor %}
</div>
</div>
<script src="{{ url_for('static', filename='js/libs/jquery.min.js') }}"></script>
<script src="{{ url_for('static', filename='js/libs/bootstrap.min.js') }}"></script>
{% endblock %}
</body>
</html>
@@ -1,4 +1,4 @@
{% extends "layout.html" %}
{% extends theme("layout.html") %}
{% block body %}
<h3>{{_('About')}} {{instance}}</h3>
<p>{{instance}} powered by
@@ -1,11 +1,11 @@
{% extends "layout.html" %}
{% extends theme("layout.html") %}
{% block header %}
<link href="{{ url_for('static', filename='css/libs/bootstrap-table.min.css') }}" rel="stylesheet">
{% endblock %}
{% block body %}
<div class="discover">
<h2>{{_('Tasks')}}</h2>
<table class="table table-no-bordered" id="tasktable" data-url="{{ url_for('tasks.get_email_status_json') }}" data-sort-name="starttime" data-sort-order="asc" data-locale="{{ current_user.locale }}">
<table class="table table-no-bordered" id="tasktable" data-url="{{ url_for('tasks.get_email_status_json') }}" data-sort-name="starttime" data-sort-order="asc" data-locale="{{ current_user.locale }}" data-no-records-text="{{ _('No Records Found') }}">
<thead>
<tr>
{% if current_user.role_admin() %}
@@ -34,7 +34,7 @@
<div class="modal-dialog">
<div class="modal-content">
<div class="modal-header bg-danger text-center">
<span>{{_('Are you really sure?')}}</span>
<h4 class="modal-title">{{_('Are you really sure?')}}</h4>
</div>
<div class="modal-body text-center">
<p>
@@ -1,4 +1,4 @@
{% extends "layout.html" %}
{% extends theme("layout.html") %}
{% block body %}
<div class="discover">
<h1>{{title}}</h1>
@@ -1,4 +1,4 @@
{% extends "layout.html" %}
{% extends theme("layout.html") %}
{% macro user_table_row(parameter, edit_text, show_text, validate, elements=False) -%}
<th data-field="{{ parameter }}" id="{{ parameter }}"
data-name="{{ parameter }}"
@@ -117,62 +117,64 @@
<link href="{{ url_for('static', filename='css/libs/bootstrap-select.min.css') }}" rel="stylesheet" >
{% endblock %}
{% block body %}
<h2 class="{{page}}">{{_(title)}}</h2>
<input type="hidden" name="csrf_token" value="{{ csrf_token() }}">
<div class="col-xs-12 col-sm-12">
<div class="row">
<div class="btn btn-default disabled mass_selection" id="user_delete_selection" aria-disabled="true">{{_('Clear selections')}}</div>
</div>
</div>
<table id="user-table" class="table table-no-bordered table-striped"
data-url="{{url_for('admin.list_users')}}" data-locale="{{ current_user.locale }}">
<thead>
<tr>
<th data-name="edit" data-buttontext="{{_('Edit User')}}" data-visible="{{visiblility.get('edit')}}" data-formatter="singleUserFormatter">{{_('Edit')}}</th>
<th data-name="state" data-field="state" data-checkbox="true" data-visible="{{visiblility.get('state')}}" data-sortable="true"></th>
<th data-name="id" data-field="id" id="id" data-visible="false" data-switchable="false"></th>
{{ user_table_row('name', _('Enter Username'), _('Username'), true) }}
{{ user_table_row('email', _('Enter Email'), _('Email'), true) }}
{{ user_table_row('kindle_mail', _('Enter eReader Email'), _('eReader Email'), false) }}
{{ user_select_translations('locale', url_for('admin.table_get_locale'), _('Locale'), true) }}
{{ user_select_languages('default_language', url_for('admin.table_get_default_lang'), _('Visible Book Languages'), true) }}
{{ user_table_row('allowed_tags', _("Edit Allowed Tags"), _("Allowed Tags"), false, tags) }}
{{ user_table_row('denied_tags', _("Edit Denied Tags"), _("Denied Tags"), false, tags) }}
{{ user_table_row('allowed_column_value', _("Edit Allowed Column Values"), _("Allowed Column Values"), false, custom_values) }}
{{ user_table_row('denied_column_value', _("Edit Denied Column Values"), _("Denied Column Values"), false, custom_values) }}
{{ user_checkbox_row("role", "admin_role", _('Admin'), visiblility, all_roles)}}
{{ user_checkbox_row("role", "passwd_role", _('Change Password'), visiblility, all_roles)}}
{{ user_checkbox_row("role", "upload_role",_('Upload'), visiblility, all_roles)}}
{{ user_checkbox_row("role", "download_role", _('Download'), visiblility, all_roles)}}
{{ user_checkbox_row("role", "viewer_role", _('View'), visiblility, all_roles)}}
{{ user_checkbox_row("role", "edit_role", _('Edit'), visiblility, all_roles)}}
{{ user_checkbox_row("role", "delete_role", _('Delete'), visiblility, all_roles)}}
{{ user_checkbox_row("role", "edit_shelf_role", _('Edit Public Shelves'), visiblility, all_roles)}}
{% if kobo_support %}
{{ user_single_checkbox_row("kobo_only_shelves_sync", _('Sync selected Shelves with Kobo'))}}
{% endif %}
{{ user_checkbox_row("sidebar_view", "detail_random", _('Show Random Books in Detail View'), visiblility, sidebar_settings)}}
{{ user_checkbox_row("sidebar_view", "sidebar_language", _('Show Language Section'), visiblility, sidebar_settings)}}
{{ user_checkbox_row("sidebar_view", "sidebar_read_and_unread", _('Show Read/Unread Section'), visiblility, sidebar_settings)}}
{{ user_checkbox_row("sidebar_view", "sidebar_series", _('Show Series Section'), visiblility, sidebar_settings)}}
{{ user_checkbox_row("sidebar_view", "sidebar_category", _('Show Category Section'), visiblility, sidebar_settings)}}
{{ user_checkbox_row("sidebar_view", "sidebar_random", _('Show Random Books'), visiblility, sidebar_settings)}}
{{ user_checkbox_row("sidebar_view", "sidebar_author", _('Show Author Section'), visiblility, sidebar_settings)}}
{{ user_checkbox_row("sidebar_view", "sidebar_best_rated", _('Show Top Rated Books'), visiblility, sidebar_settings)}}
{{ user_checkbox_row("sidebar_view", "sidebar_read_and_unread", _('Show Random Books'), visiblility, sidebar_settings)}}
{{ user_checkbox_row("sidebar_view", "sidebar_publisher", _('Show Publisher Section'), visiblility, sidebar_settings)}}
{{ user_checkbox_row("sidebar_view", "sidebar_rating", _('Show Ratings Section'), visiblility, sidebar_settings)}}
{{ user_checkbox_row("sidebar_view", "sidebar_format", _('Show File Formats Section'), visiblility, sidebar_settings)}}
{{ user_checkbox_row("sidebar_view", "sidebar_archived", _('Show Archived Books'), visiblility, sidebar_settings)}}
{{ user_checkbox_row("sidebar_view", "sidebar_download", _('Show Downloaded Books'), visiblility, sidebar_settings)}}
{{ user_checkbox_row("sidebar_view", "sidebar_list", _('Show Books List'), visiblility, sidebar_settings)}}
<th data-align="right" data-formatter="UserActions" data-switchable="false"><div><div class="btn btn-default button_head disabled" aria-disabled="true">{{_('Delete User')}}</div></div><br>{{_('Delete User')}}</th>
</tr>
</thead>
</table>
<div class="errorlink">
<div class="btn btn-default" data-back="{{ url_for('admin.admin') }}" id="back">{{_('Back')}}</div>
<div class="discover">
<h2 class="{{page}}">{{_(title)}}</h2>
<input type="hidden" name="csrf_token" value="{{ csrf_token() }}">
<div class="col-xs-12 col-sm-12">
<div class="row">
<div class="btn btn-default disabled mass_selection" id="user_delete_selection" aria-disabled="true">{{_('Clear selections')}}</div>
</div>
</div>
<table id="user-table" class="table table-no-bordered table-striped"
data-url="{{url_for('admin.list_users')}}" data-locale="{{ current_user.locale }}">
<thead>
<tr>
<th data-name="edit" data-buttontext="{{_('Edit User')}}" data-visible="{{visiblility.get('edit')}}" data-formatter="singleUserFormatter">{{_('Edit')}}</th>
<th data-name="state" data-field="state" data-checkbox="true" data-visible="{{visiblility.get('state')}}" data-sortable="true"></th>
<th data-name="id" data-field="id" id="id" data-visible="false" data-switchable="false"></th>
{{ user_table_row('name', _('Enter Username'), _('Username'), true) }}
{{ user_table_row('email', _('Enter Email'), _('Email'), true) }}
{{ user_table_row('kindle_mail', _('Enter eReader Email'), _('eReader Email'), false) }}
{{ user_select_translations('locale', url_for('admin.table_get_locale'), _('Locale'), true) }}
{{ user_select_languages('default_language', url_for('admin.table_get_default_lang'), _('Visible Book Languages'), true) }}
{{ user_table_row('allowed_tags', _("Edit Allowed Tags"), _("Allowed Tags"), false, tags) }}
{{ user_table_row('denied_tags', _("Edit Denied Tags"), _("Denied Tags"), false, tags) }}
{{ user_table_row('allowed_column_value', _("Edit Allowed Column Values"), _("Allowed Column Values"), false, custom_values) }}
{{ user_table_row('denied_column_value', _("Edit Denied Column Values"), _("Denied Column Values"), false, custom_values) }}
{{ user_checkbox_row("role", "admin_role", _('Admin'), visiblility, all_roles)}}
{{ user_checkbox_row("role", "passwd_role", _('Change Password'), visiblility, all_roles)}}
{{ user_checkbox_row("role", "upload_role",_('Upload'), visiblility, all_roles)}}
{{ user_checkbox_row("role", "download_role", _('Download'), visiblility, all_roles)}}
{{ user_checkbox_row("role", "viewer_role", _('View'), visiblility, all_roles)}}
{{ user_checkbox_row("role", "edit_role", _('Edit'), visiblility, all_roles)}}
{{ user_checkbox_row("role", "delete_role", _('Delete'), visiblility, all_roles)}}
{{ user_checkbox_row("role", "edit_shelf_role", _('Edit Public Shelves'), visiblility, all_roles)}}
{% if kobo_support %}
{{ user_single_checkbox_row("kobo_only_shelves_sync", _('Sync selected Shelves with Kobo'))}}
{% endif %}
{{ user_checkbox_row("sidebar_view", "detail_random", _('Show Random Books in Detail View'), visiblility, sidebar_settings)}}
{{ user_checkbox_row("sidebar_view", "sidebar_language", _('Show Language Section'), visiblility, sidebar_settings)}}
{{ user_checkbox_row("sidebar_view", "sidebar_read_and_unread", _('Show Read/Unread Section'), visiblility, sidebar_settings)}}
{{ user_checkbox_row("sidebar_view", "sidebar_series", _('Show Series Section'), visiblility, sidebar_settings)}}
{{ user_checkbox_row("sidebar_view", "sidebar_category", _('Show Category Section'), visiblility, sidebar_settings)}}
{{ user_checkbox_row("sidebar_view", "sidebar_random", _('Show Random Books'), visiblility, sidebar_settings)}}
{{ user_checkbox_row("sidebar_view", "sidebar_author", _('Show Author Section'), visiblility, sidebar_settings)}}
{{ user_checkbox_row("sidebar_view", "sidebar_best_rated", _('Show Top Rated Books'), visiblility, sidebar_settings)}}
{{ user_checkbox_row("sidebar_view", "sidebar_read_and_unread", _('Show Random Books'), visiblility, sidebar_settings)}}
{{ user_checkbox_row("sidebar_view", "sidebar_publisher", _('Show Publisher Section'), visiblility, sidebar_settings)}}
{{ user_checkbox_row("sidebar_view", "sidebar_rating", _('Show Ratings Section'), visiblility, sidebar_settings)}}
{{ user_checkbox_row("sidebar_view", "sidebar_format", _('Show File Formats Section'), visiblility, sidebar_settings)}}
{{ user_checkbox_row("sidebar_view", "sidebar_archived", _('Show Archived Books'), visiblility, sidebar_settings)}}
{{ user_checkbox_row("sidebar_view", "sidebar_download", _('Show Downloaded Books'), visiblility, sidebar_settings)}}
{{ user_checkbox_row("sidebar_view", "sidebar_list", _('Show Books List'), visiblility, sidebar_settings)}}
<th data-align="right" data-formatter="UserActions" data-switchable="false"><div><div class="btn btn-default button_head disabled" aria-disabled="true">{{_('Delete User')}}</div></div><br>{{_('Delete User')}}</th>
</tr>
</thead>
</table>
<div class="errorlink">
<div class="btn btn-default" data-back="{{ url_for('admin.admin') }}" id="back">{{_('Back')}}</div>
</div>
</div>
{% endblock %}
{% block modal %}
{{ delete_confirm_modal() }}
+1
View File
@@ -33,6 +33,7 @@ dependencies = [
"Flask-Babel>=3.0.0,<4.1.0",
"Flask-Principal>=0.3.2,<0.5.1",
"Flask>=1.0.2,<3.2.0",
"Flask-Themes2>=1.0.1,<1.1.0",
"iso-639>=0.4.5,<0.5.0;python_version<'3.12'",
"pycountry>=20.0.0,<27.0.0;python_version>='3.12'",
"PyPDF>=6.1.3,<6.11.0",
+1
View File
@@ -3,6 +3,7 @@ Babel>=1.3,<3.0
Flask-Babel>=3.0.0,<4.1.0
Flask-Principal>=0.3.2,<0.5.1
Flask>=1.0.2,<3.2.0
Flask-Themes2>=1.0.1,<1.1.0
iso-639>=0.4.5,<0.5.0;python_version<'3.12'
pycountry>=20.0.0,<27.0.0;python_version>='3.12'
PyPDF>=6.1.3,<6.11.0
+28 -85
View File
@@ -37,20 +37,20 @@
<div class="row">
<div class="col-xs-6 col-md-6 col-sm-offset-3" style="margin-top:50px;">
<p class='text-justify attribute'><strong>Start Time: </strong>2026-08-08T11:08:24.000001</p>
<p class='text-justify attribute'><strong>Start Time: </strong>2026-08-10T16:13:52.000001</p>
</div>
</div>
<div class="row">
<div class="col-xs-6 col-md-6 col-sm-offset-3">
<p class='text-justify attribute'><strong>Stop Time: </strong>2026-08-08T12:58:11.000001</p>
<p class='text-justify attribute'><strong>Stop Time: </strong>2026-08-10T18:05:55.000001</p>
</div>
</div>
<div class="row">
<div class="col-xs-6 col-md-6 col-sm-offset-3">
<p class='text-justify attribute'><strong>Duration: </strong>6h 1 min</p>
<p class='text-justify attribute'><strong>Duration: </strong>6h 4 min</p>
</div>
</div>
</div>
@@ -234,11 +234,11 @@
<tr id="su" class="failClass">
<tr id="su" class="passClass">
<td>TestBackupMetadata</td>
<td class="text-center">21</td>
<td class="text-center">20</td>
<td class="text-center">1</td>
<td class="text-center">21</td>
<td class="text-center">0</td>
<td class="text-center">0</td>
<td class="text-center">0</td>
<td class="text-center">
@@ -356,32 +356,11 @@
<tr id="ft2.13" class="hiddenRow bg-danger">
<tr id='pt2.13' class='hiddenRow bg-success'>
<td>
<div class='testcase'>TestBackupMetadata - test_backup_change_custom_Enum</div>
</td>
<td colspan='6'>
<div class="text-center">
<a class="popup_link text-center" onfocus='blur()' onclick="showTestDetail('div_ft2.13')">FAIL</a>
</div>
<!--css div popup start-->
<div id="div_ft2.13" class="popup_window test_output" style="display:block;">
<div class='close_button pull-right'>
<button type="button" class="close" aria-label="Close" onfocus="this.blur();"
onclick="document.getElementById('div_ft2.13').style.display='none'"><span
aria-hidden="true">&times;</span></button>
</div>
<div class="text-left pull-left">
<pre class="text-left">Traceback (most recent call last):
File &#34;/home/ozzie/Development/calibre-web-test/test/test_backup_metadata.py&#34;, line 622, in test_backup_change_custom_Enum
self.assertEqual(custom[&#34;#value#&#34;], &#34;Huji&#34;)
AssertionError: None != &#39;Huji&#39;
</pre>
</div>
<div class="clearfix"></div>
</div>
<!--css div popup end-->
</td>
<td colspan='6' align='center'>PASS</td>
</tr>
@@ -1919,11 +1898,11 @@ AssertionError: None != &#39;Huji&#39;
<tr id="su" class="failClass">
<tr id="su" class="passClass">
<td>TestEditAuthors</td>
<td class="text-center">10</td>
<td class="text-center">9</td>
<td class="text-center">1</td>
<td class="text-center">10</td>
<td class="text-center">0</td>
<td class="text-center">0</td>
<td class="text-center">0</td>
<td class="text-center">
@@ -1978,32 +1957,11 @@ AssertionError: None != &#39;Huji&#39;
<tr id="ft17.6" class="hiddenRow bg-danger">
<tr id='pt17.6' class='hiddenRow bg-success'>
<td>
<div class='testcase'>TestEditAuthors - test_change_capital_rename_two_co_authors</div>
</td>
<td colspan='6'>
<div class="text-center">
<a class="popup_link text-center" onfocus='blur()' onclick="showTestDetail('div_ft17.6')">FAIL</a>
</div>
<!--css div popup start-->
<div id="div_ft17.6" class="popup_window test_output" style="display:block;">
<div class='close_button pull-right'>
<button type="button" class="close" aria-label="Close" onfocus="this.blur();"
onclick="document.getElementById('div_ft17.6').style.display='none'"><span
aria-hidden="true">&times;</span></button>
</div>
<div class="text-left pull-left">
<pre class="text-left">Traceback (most recent call last):
File &#34;/home/ozzie/Development/calibre-web-test/test/test_edit_books_author.py&#34;, line 408, in test_change_capital_rename_two_co_authors
self.assertEqual(200, ret_code)
AssertionError: 200 != 404
</pre>
</div>
<div class="clearfix"></div>
</div>
<!--css div popup end-->
</td>
<td colspan='6' align='center'>PASS</td>
</tr>
@@ -3001,7 +2959,7 @@ AssertionError: 200 != 404
<pre class="text-left">Traceback (most recent call last):
File &#34;/home/ozzie/Development/calibre-web-test/test/test_edit_ebooks_gdrive.py&#34;, line 911, in test_watch_metadata
self.assertNotIn(&#39;series&#39;, book)
AssertionError: &#39;series&#39; unexpectedly found in {&#39;id&#39;: 5, &#39;reader&#39;: [], &#39;title&#39;: &#39;testbook&#39;, &#39;author&#39;: [&#39;John Döe&#39;], &#39;rating&#39;: 0, &#39;languages&#39;: [&#39;English&#39;], &#39;identifier&#39;: [], &#39;cover&#39;: &#39;/cover/5/og?c=1786178654&#39;, &#39;tag&#39;: [], &#39;publisher&#39;: [&#39;Randomhäus&#39;], &#39;pubdate&#39;: &#39;Jan 19, 2017&#39;, &#39;comment&#39;: &#39;Lorem ipsum dolor sit amet, consectetuer adipiscing elit.Aenean commodo ligula eget dolor.Aenean massa.Cum sociis natoque penatibus et magnis dis parturient montes, nascetur ridiculus mus.Donec quam felis, ultricies nec, pellentesque eu, pretium quis, sem.Nulla consequat massa quis enim.Donec pede justo, fringilla vel, aliquet nec, vulputate&#39;, &#39;add_shelf&#39;: [], &#39;del_shelf&#39;: [], &#39;edit_enable&#39;: True, &#39;kindle&#39;: None, &#39;kindlebtn&#39;: None, &#39;download&#39;: [&#39;EPUB\n (6.7 kB)&#39;], &#39;read&#39;: False, &#39;archived&#39;: False, &#39;series_all&#39;: &#39;Book 1 of test&#39;, &#39;series_index&#39;: &#39;1&#39;, &#39;series&#39;: &#39;test&#39;, &#39;cust_columns&#39;: []}
AssertionError: &#39;series&#39; unexpectedly found in {&#39;id&#39;: 5, &#39;reader&#39;: [], &#39;title&#39;: &#39;testbook&#39;, &#39;author&#39;: [&#39;John Döe&#39;], &#39;rating&#39;: 0, &#39;languages&#39;: [&#39;English&#39;], &#39;identifier&#39;: [], &#39;cover&#39;: &#39;/cover/5/og?c=1786369888&#39;, &#39;tag&#39;: [], &#39;publisher&#39;: [&#39;Randomhäus&#39;], &#39;pubdate&#39;: &#39;Jan 19, 2017&#39;, &#39;comment&#39;: &#39;Lorem ipsum dolor sit amet, consectetuer adipiscing elit.Aenean commodo ligula eget dolor.Aenean massa.Cum sociis natoque penatibus et magnis dis parturient montes, nascetur ridiculus mus.Donec quam felis, ultricies nec, pellentesque eu, pretium quis, sem.Nulla consequat massa quis enim.Donec pede justo, fringilla vel, aliquet nec, vulputate&#39;, &#39;add_shelf&#39;: [], &#39;del_shelf&#39;: [], &#39;edit_enable&#39;: True, &#39;kindle&#39;: None, &#39;kindlebtn&#39;: None, &#39;download&#39;: [&#39;EPUB\n (6.7 kB)&#39;], &#39;read&#39;: False, &#39;archived&#39;: False, &#39;series_all&#39;: &#39;Book 1 of test&#39;, &#39;series_index&#39;: &#39;1&#39;, &#39;series&#39;: &#39;test&#39;, &#39;cust_columns&#39;: []}
</pre>
</div>
<div class="clearfix"></div>
@@ -4757,11 +4715,11 @@ AssertionError: &#39;series&#39; unexpectedly found in {&#39;id&#39;: 5, &#39;re
<tr id="su" class="failClass">
<tr id="su" class="passClass">
<td>TestSSL</td>
<td class="text-center">7</td>
<td class="text-center">6</td>
<td class="text-center">1</td>
<td class="text-center">7</td>
<td class="text-center">0</td>
<td class="text-center">0</td>
<td class="text-center">0</td>
<td class="text-center">
@@ -4825,32 +4783,11 @@ AssertionError: &#39;series&#39; unexpectedly found in {&#39;id&#39;: 5, &#39;re
<tr id="ft44.7" class="hiddenRow bg-danger">
<tr id='pt44.7' class='hiddenRow bg-success'>
<td>
<div class='testcase'>TestSSL - test_filepicker_two_file</div>
</td>
<td colspan='6'>
<div class="text-center">
<a class="popup_link text-center" onfocus='blur()' onclick="showTestDetail('div_ft44.7')">FAIL</a>
</div>
<!--css div popup start-->
<div id="div_ft44.7" class="popup_window test_output" style="display:block;">
<div class='close_button pull-right'>
<button type="button" class="close" aria-label="Close" onfocus="this.blur();"
onclick="document.getElementById('div_ft44.7').style.display='none'"><span
aria-hidden="true">&times;</span></button>
</div>
<div class="text-left pull-left">
<pre class="text-left">Traceback (most recent call last):
File &#34;/home/ozzie/Development/calibre-web-test/test/test_email_ssl.py&#34;, line 207, in test_filepicker_two_file
self.assertTrue(found, f&#34;files folder not found in {elements}&#34;)
AssertionError: False is not true : files folder not found in [&#39;..&#39;, &#39;.git&#39;, &#39;.idea&#39;, &#39;__pycache__&#39;, &#39;build_release&#39;, &#39;test&#39;, &#39;test_reports&#39;, &#39;venv&#39;, &#39;.gitignore&#39;, &#39;01_08_.log&#39;, &#39;aiosmtd.log&#39;, &#39;combine_results.py&#39;, &#39;gdrive.db&#39;, &#39;lauf_vorher.log&#39;, &#39;ldaptor.log&#39;, &#39;main.py&#39;, &#39;report_template.html&#39;, &#39;result.py&#39;]
</pre>
</div>
<div class="clearfix"></div>
</div>
<!--css div popup end-->
</td>
<td colspan='6' align='center'>PASS</td>
</tr>
@@ -6210,8 +6147,8 @@ AssertionError: False is not true : files folder not found in [&#39;..&#39;, &#3
<tr id='total_row' class="text-center bg-grey">
<td>Total</td>
<td>554</td>
<td>539</td>
<td>4</td>
<td>542</td>
<td>1</td>
<td>0</td>
<td>11</td>
<td>&nbsp;</td>
@@ -6299,6 +6236,12 @@ AssertionError: False is not true : files folder not found in [&#39;..&#39;, &#3
<td>Basic</td>
</tr>
<tr>
<th>Flask-Themes2</th>
<td>1.0.1</td>
<td>Basic</td>
</tr>
<tr>
<th>cryptography</th>
<td>48.0.0</td>
@@ -6421,7 +6364,7 @@ AssertionError: False is not true : files folder not found in [&#39;..&#39;, &#3
</div>
<script>
drawCircle(539, 4, 0, 11);
drawCircle(542, 1, 0, 11);
showCase(5);
</script>