mirror of
https://github.com/janeczku/calibre-web
synced 2025-09-13 00:05:59 +00:00
Merge branch 'Develop'
This commit is contained in:
23
cps/admin.py
23
cps/admin.py
@@ -40,7 +40,7 @@ from flask_babel import gettext as _
|
||||
from flask_babel import get_locale, format_time, format_datetime, format_timedelta
|
||||
from sqlalchemy import and_
|
||||
from sqlalchemy.orm.attributes import flag_modified
|
||||
from sqlalchemy.exc import IntegrityError, OperationalError, InvalidRequestError
|
||||
from sqlalchemy.exc import IntegrityError, OperationalError, InvalidRequestError, ArgumentError
|
||||
from sqlalchemy.sql.expression import func, or_, text
|
||||
|
||||
from . import constants, logger, helper, services, cli_param
|
||||
@@ -386,13 +386,12 @@ def list_users():
|
||||
@user_login_required
|
||||
@admin_required
|
||||
def delete_user():
|
||||
user_ids = request.form.to_dict(flat=False)
|
||||
users = None
|
||||
user_ids = request.get_json().get("userid")
|
||||
message = ""
|
||||
if "userid[]" in user_ids:
|
||||
users = ub.session.query(ub.User).filter(ub.User.id.in_(user_ids['userid[]'])).all()
|
||||
elif "userid" in user_ids:
|
||||
users = ub.session.query(ub.User).filter(ub.User.id == user_ids['userid'][0]).all()
|
||||
try:
|
||||
users = ub.session.query(ub.User).filter(ub.User.id.in_(user_ids)).all()
|
||||
except (ArgumentError):
|
||||
users = None
|
||||
count = 0
|
||||
errors = list()
|
||||
success = list()
|
||||
@@ -408,10 +407,10 @@ def delete_user():
|
||||
errors.append({'type': "danger", 'message': str(ex)})
|
||||
|
||||
if count == 1:
|
||||
log.info("User {} deleted".format(user_ids))
|
||||
log.info("User {} deleted".format(user_ids[0]))
|
||||
success = [{'type': "success", 'message': message}]
|
||||
elif count > 1:
|
||||
log.info("Users {} deleted".format(user_ids))
|
||||
log.info("Users {} deleted".format(", ".join([str(user_id) for user_id in user_ids])))
|
||||
success = [{'type': "success", 'message': _("{} users deleted successfully").format(count)}]
|
||||
success.extend(errors)
|
||||
return make_response(jsonify(success))
|
||||
@@ -618,6 +617,8 @@ def load_dialogtexts(element_id):
|
||||
texts["main"] = _('Do you really want to delete this domain?')
|
||||
elif element_id == "btndeluser":
|
||||
texts["main"] = _('Do you really want to delete this user?')
|
||||
elif element_id == "btndelbook":
|
||||
texts["main"] = _('Do you really want to delete this book?')
|
||||
elif element_id == "delete_shelf":
|
||||
texts["main"] = _('Are you sure you want to delete this shelf?')
|
||||
elif element_id == "select_locale":
|
||||
@@ -626,6 +627,10 @@ def load_dialogtexts(element_id):
|
||||
texts["main"] = _('Are you sure you want to change visible book languages for selected user(s)?')
|
||||
elif element_id == "role":
|
||||
texts["main"] = _('Are you sure you want to change the selected role for the selected user(s)?')
|
||||
elif element_id == "archive_books":
|
||||
texts["main"] = _('Are you sure you want to change the archive status for the selected book(s)?')
|
||||
elif element_id == "read_books":
|
||||
texts["main"] = _('Are you sure you want to change the read status for the selected book(s)?')
|
||||
elif element_id == "restrictions":
|
||||
texts["main"] = _('Are you sure you want to change the selected restrictions for the selected user(s)?')
|
||||
elif element_id == "sidebar_view":
|
||||
|
546
cps/editbooks.py
546
cps/editbooks.py
@@ -73,17 +73,18 @@ def edit_required(f):
|
||||
return inner
|
||||
|
||||
|
||||
@editbook.route("/ajax/delete/<int:book_id>", methods=["POST"])
|
||||
@editbook.route("/ajax/deletebook", methods=["POST"])
|
||||
@user_login_required
|
||||
def delete_book_from_details(book_id):
|
||||
return delete_book_from_table(book_id, "", True)
|
||||
def delete_books_ajax():
|
||||
book_ids = request.get_json().get("bookid")
|
||||
return check_delete_book(book_ids, "", True)
|
||||
|
||||
|
||||
@editbook.route("/delete/<int:book_id>", defaults={'book_format': ""}, methods=["POST"])
|
||||
@editbook.route("/delete/<int:book_id>/<string:book_format>", methods=["POST"])
|
||||
@user_login_required
|
||||
def delete_book_ajax(book_id, book_format):
|
||||
return delete_book_from_table(book_id, book_format, False, request.form.to_dict().get('location', ""))
|
||||
def delete_book(book_id, book_format):
|
||||
return check_delete_book(book_id, book_format, False, request.form.to_dict().get('location', ""))
|
||||
|
||||
|
||||
@editbook.route("/admin/book/<int:book_id>", methods=['GET'])
|
||||
@@ -161,7 +162,7 @@ def upload():
|
||||
return make_response(jsonify(resp))
|
||||
else:
|
||||
resp = {"location": url_for('web.show_book', book_id=book_id)}
|
||||
return make_response(jsonify(resp))
|
||||
return Response(json.dumps(resp), mimetype='application/json')
|
||||
except (OperationalError, IntegrityError, StaleDataError) as e:
|
||||
calibre_db.session.rollback()
|
||||
log.error_or_exception("Database error: {}".format(e))
|
||||
@@ -213,96 +214,240 @@ def table_get_custom_enum(c_id):
|
||||
@login_required_if_no_ano
|
||||
@edit_required
|
||||
def edit_list_book(param):
|
||||
vals = request.form.to_dict()
|
||||
book = calibre_db.get_book(vals['pk'])
|
||||
calibre_db.create_functions(config)
|
||||
sort_param = ""
|
||||
ret = ""
|
||||
try:
|
||||
if param == 'series_index':
|
||||
edit_book_series_index(vals['value'], book)
|
||||
ret = make_response(jsonify(success=True, newValue=book.series_index))
|
||||
elif param == 'tags':
|
||||
edit_book_tags(vals['value'], book)
|
||||
ret = make_response(jsonify(success=True, newValue=', '.join([tag.name for tag in book.tags])))
|
||||
elif param == 'series':
|
||||
edit_book_series(vals['value'], book)
|
||||
ret = make_response(jsonify(success=True, newValue=', '.join([serie.name for serie in book.series])))
|
||||
elif param == 'publishers':
|
||||
edit_book_publisher(vals['value'], book)
|
||||
ret = make_response(jsonify(success=True,
|
||||
newValue=', '.join([publisher.name for publisher in book.publishers])))
|
||||
elif param == 'languages':
|
||||
invalid = list()
|
||||
edit_book_languages(vals['value'], book, invalid=invalid)
|
||||
if invalid:
|
||||
ret = make_response(jsonify(success=False,
|
||||
msg='Invalid languages in request: {}'.format(','.join(invalid))))
|
||||
else:
|
||||
lang_names = list()
|
||||
for lang in book.languages:
|
||||
lang_names.append(isoLanguages.get_language_name(get_locale(), lang.lang_code))
|
||||
ret = make_response(jsonify(success=True, newValue=', '.join(lang_names)))
|
||||
elif param == 'author_sort':
|
||||
book.author_sort = vals['value']
|
||||
ret = make_response(jsonify(success=True, newValue=book.author_sort))
|
||||
elif param == 'title':
|
||||
sort_param = book.sort
|
||||
if handle_title_on_edit(book, vals.get('value', "")):
|
||||
rename_error = helper.update_dir_structure(book.id, config.get_book_path())
|
||||
if not rename_error:
|
||||
ret = make_response(jsonify(success=True, newValue=book.title))
|
||||
else:
|
||||
ret = make_response(jsonify(success=False, msg=rename_error))
|
||||
elif param == 'sort':
|
||||
book.sort = vals['value']
|
||||
ret = make_response(jsonify(success=True,newValue=book.sort))
|
||||
elif param == 'comments':
|
||||
edit_book_comments(vals['value'], book)
|
||||
ret = make_response(jsonify(success=True, newValue=book.comments[0].text))
|
||||
elif param == 'authors':
|
||||
input_authors, __ = handle_author_on_edit(book, vals['value'], vals.get('checkA', None) == "true")
|
||||
rename_error = helper.update_dir_structure(book.id, config.get_book_path(), input_authors[0])
|
||||
if not rename_error:
|
||||
ret = make_response(jsonify(
|
||||
success=True,
|
||||
newValue=' & '.join([author.replace('|', ',') for author in input_authors])))
|
||||
else:
|
||||
ret = make_response(jsonify(success=False, msg=rename_error))
|
||||
elif param == 'is_archived':
|
||||
is_archived = change_archived_books(book.id, vals['value'] == "True",
|
||||
message="Book {} archive bit set to: {}".format(book.id, vals['value']))
|
||||
if is_archived:
|
||||
kobo_sync_status.remove_synced_book(book.id)
|
||||
return ""
|
||||
elif param == 'read_status':
|
||||
ret = helper.edit_book_read_status(book.id, vals['value'] == "True")
|
||||
if ret:
|
||||
return ret, 400
|
||||
elif param.startswith("custom_column_"):
|
||||
new_val = dict()
|
||||
new_val[param] = vals['value']
|
||||
edit_single_cc_data(book.id, book, param[14:], new_val)
|
||||
# ToDo: Very hacky find better solution
|
||||
if vals['value'] in ["True", "False"]:
|
||||
ret = ""
|
||||
else:
|
||||
ret = make_response(jsonify(success=True, newValue=vals['value']))
|
||||
else:
|
||||
return _("Parameter not found"), 400
|
||||
book.last_modified = datetime.now(timezone.utc)
|
||||
vals = request.get_json()
|
||||
multi = vals.get('multi', False) == "True"
|
||||
ret_value = edit_book_param(param, vals, multi)
|
||||
if isinstance(ret_value, dict):
|
||||
return jsonify(ret_value)
|
||||
else:
|
||||
return ret_value
|
||||
|
||||
@editbook.route("/ajax/editselectedbooks", methods=['POST'])
|
||||
@login_required_if_no_ano
|
||||
@edit_required
|
||||
def edit_selected_books():
|
||||
d = request.get_json()
|
||||
selections = d.get('selections')
|
||||
title = d.get('title')
|
||||
title_sort = d.get('title_sort')
|
||||
author_sort = d.get('author_sort')
|
||||
authors = d.get('authors')
|
||||
categories = d.get('categories')
|
||||
series = d.get('series')
|
||||
languages = d.get('languages')
|
||||
publishers = d.get('publishers')
|
||||
comments = d.get('comments')
|
||||
|
||||
if not (
|
||||
title or title_sort or authors or categories or series or languages or publishers or comments) or not selections:
|
||||
return _("Parameter not found"), 400
|
||||
vals = {
|
||||
"pk": selections,
|
||||
"value": None,
|
||||
"checkA": d.get('checkA'),
|
||||
"checkT": d.get('checkT'),
|
||||
}
|
||||
res = list()
|
||||
if title:
|
||||
vals['value'] = title
|
||||
out = edit_book_param('title', vals, True)
|
||||
if out[0].get('success') != True:
|
||||
res.extend(out)
|
||||
if title_sort:
|
||||
vals['value'] = title_sort
|
||||
out = edit_book_param('sort', vals, True)
|
||||
if out[0].get('success') != True:
|
||||
res.extend(out)
|
||||
if author_sort:
|
||||
vals['value'] = author_sort
|
||||
out = edit_book_param('author_sort', vals, True)
|
||||
if out[0].get('success') != True:
|
||||
res.extend(out)
|
||||
if authors:
|
||||
vals['value'] = authors
|
||||
out = edit_book_param('authors', vals, True)
|
||||
if out[0].get('success') != True:
|
||||
res.extend(out)
|
||||
if categories:
|
||||
vals['value'] = categories
|
||||
out = edit_book_param('tags', vals, True)
|
||||
if out[0].get('success') != True:
|
||||
res.extend(out)
|
||||
if series:
|
||||
vals['value'] = series
|
||||
out = edit_book_param('series', vals, True)
|
||||
if out[0].get('success') != True:
|
||||
res.extend(out)
|
||||
if languages:
|
||||
vals['value'] = languages
|
||||
out = edit_book_param('languages', vals, True)
|
||||
if out[0].get('success') != True:
|
||||
res.extend(out)
|
||||
if publishers:
|
||||
vals['value'] = publishers
|
||||
out = edit_book_param('publishers', vals, True)
|
||||
if out[0].get('success') != True:
|
||||
res.extend(out)
|
||||
if comments:
|
||||
vals['value'] = comments
|
||||
out = edit_book_param('comments', vals, True)
|
||||
if out[0].get('success') != True:
|
||||
res.extend(out)
|
||||
if len(res) == 0:
|
||||
return jsonify([{'success': True, "msg": _("Changes successfully applied")}])
|
||||
else:
|
||||
return jsonify(res)
|
||||
|
||||
# Separated from /editbooks so that /editselectedbooks can also use this
|
||||
#
|
||||
# param: the property of the book to be changed
|
||||
# vals - JSON Object:
|
||||
# {
|
||||
# 'pk': "the book id",
|
||||
# 'value': "changes value of param to what's passed here"
|
||||
# 'checkA': "Optional. Used to check if autosort author is enabled. Assumed as true if not passed"
|
||||
# 'checkT': "Optional. Used to check if autotitle author is enabled. Assumed as true if not passed"
|
||||
# }
|
||||
#
|
||||
@login_required_if_no_ano
|
||||
@edit_required
|
||||
def edit_book_param(param, vals, multi=False):
|
||||
elements = vals.get('pk',[])
|
||||
if vals.get('value', None) is None:
|
||||
return {'success':False, 'msg':_("Value is missing on request")}
|
||||
if not elements or len(elements) > 1 and multi == False:
|
||||
return {"success":False, "msg":_("Oops! Selected book is unavailable. File does not exist or is not accessible")}
|
||||
ret = {}
|
||||
out = list()
|
||||
for elem in elements:
|
||||
book = calibre_db.get_book(elem)
|
||||
if not book:
|
||||
ret = {"success": False,
|
||||
"msg": _("Oops! Selected book is unavailable. File does not exist or is not accessible")}
|
||||
if multi:
|
||||
out.append(ret)
|
||||
continue
|
||||
else:
|
||||
return ret
|
||||
calibre_db.create_functions(config)
|
||||
sort_param = ""
|
||||
try:
|
||||
if param == 'series_index':
|
||||
edit_book_series_index(vals['value'], book)
|
||||
ret = {"success":True,
|
||||
"newValue":book.series_index}
|
||||
elif param == 'tags':
|
||||
edit_book_tags(vals['value'], book)
|
||||
ret = {"success":True,
|
||||
"newValue":', '.join([tag.name for tag in book.tags])}
|
||||
elif param == 'series':
|
||||
edit_book_series(vals['value'], book)
|
||||
ret = {"success":True,
|
||||
"newValue":', '.join([serie.name for serie in book.series])}
|
||||
elif param == 'publishers':
|
||||
edit_book_publisher(vals['value'], book)
|
||||
ret = {"success":True,
|
||||
"newValue":', '.join([publisher.name for publisher in book.publishers])}
|
||||
elif param == 'languages':
|
||||
invalid = list()
|
||||
edit_book_languages(vals['value'], book, invalid=invalid)
|
||||
if invalid:
|
||||
ret = {"success": False, "msg": 'Invalid languages in request: {}'.format(','.join(invalid))}
|
||||
if multi:
|
||||
out.append(ret)
|
||||
else:
|
||||
lang_names = list()
|
||||
for lang in book.languages:
|
||||
lang_names.append(isoLanguages.get_language_name(get_locale(), lang.lang_code))
|
||||
ret = {"success":True,
|
||||
"newValue":', '.join(lang_names)}
|
||||
elif param == 'author_sort':
|
||||
book.author_sort = vals['value']
|
||||
ret = {"success":True,
|
||||
"newValue":book.author_sort}
|
||||
elif param == 'title':
|
||||
sort_param = book.sort
|
||||
if handle_title_on_edit(book, vals.get('value', "")):
|
||||
rename_error = helper.update_dir_structure(book.id, config.get_book_path())
|
||||
if not rename_error:
|
||||
calibre_db.session.commit()
|
||||
ret = {"success":True,
|
||||
"newValue":book.title}
|
||||
else:
|
||||
calibre_db.session.rollback()
|
||||
ret = {"success":False, "msg":rename_error}
|
||||
if multi:
|
||||
out.append(ret)
|
||||
elif param == 'sort':
|
||||
book.sort = vals['value']
|
||||
ret = {"success":True,
|
||||
"newValue":book.sort}
|
||||
elif param == 'comments':
|
||||
edit_book_comments(vals['value'], book)
|
||||
ret = {"success":True,
|
||||
"newValue":book.comments[0].text}
|
||||
elif param == 'authors':
|
||||
input_authors, __ = handle_author_on_edit(book, vals['value'], vals.get('checkA', None) == True)
|
||||
rename_error = helper.update_dir_structure(book.id, config.get_book_path(), input_authors[0])
|
||||
if not rename_error:
|
||||
calibre_db.session.commit()
|
||||
ret = {"success":True,
|
||||
"newValue":' & '.join([author.replace('|', ',') for author in input_authors])}
|
||||
else:
|
||||
calibre_db.session.rollback()
|
||||
ret = {"success":False, "msg":rename_error}
|
||||
if multi:
|
||||
out.append(ret)
|
||||
elif param == 'is_archived':
|
||||
is_archived = change_archived_books(book.id, vals['value'] == "True",
|
||||
message="Book {} archive bit set to: {}".format(book.id,
|
||||
vals['value']))
|
||||
if is_archived:
|
||||
kobo_sync_status.remove_synced_book(book.id)
|
||||
continue
|
||||
elif param == 'read_status':
|
||||
error = helper.edit_book_read_status(book.id, vals['value'] == "True")
|
||||
if error:
|
||||
if multi:
|
||||
out.append({"success":False, "msg":error})
|
||||
continue
|
||||
else:
|
||||
return error, 400
|
||||
continue
|
||||
elif param.startswith("custom_column_"):
|
||||
new_val = dict()
|
||||
new_val[param] = vals['value']
|
||||
edit_single_cc_data(book.id, book, param[14:], new_val)
|
||||
# ToDo: Very hacky find better solution
|
||||
if vals['value'] in ["True", "False"]:
|
||||
ret = {}
|
||||
else:
|
||||
ret = {"success":True, "newValue":vals['value']}
|
||||
else:
|
||||
if multi:
|
||||
out.append({"success":False, "msg":_("Parameter not found")})
|
||||
continue
|
||||
return _("Parameter not found"), 400
|
||||
book.last_modified = datetime.now(timezone.utc)
|
||||
|
||||
calibre_db.session.commit()
|
||||
# revert change for sort if automatic fields link is deactivated
|
||||
if param == 'title' and vals.get('checkT') == "false":
|
||||
book.sort = sort_param
|
||||
calibre_db.session.commit()
|
||||
except (OperationalError, IntegrityError, StaleDataError) as e:
|
||||
calibre_db.session.rollback()
|
||||
log.error_or_exception("Database error: {}".format(e))
|
||||
ret = make_response(jsonify(success=False,
|
||||
msg='Database error: {}'.format(e.orig if hasattr(e, "orig") else e)))
|
||||
return ret
|
||||
# revert change for sort if automatic fields link is deactivated
|
||||
if param == 'title' and vals.get('checkT') == False:
|
||||
book.sort = sort_param
|
||||
calibre_db.session.commit()
|
||||
except (OperationalError, IntegrityError, StaleDataError, AttributeError) as e:
|
||||
calibre_db.session.rollback()
|
||||
log.error_or_exception("Database error: {}".format(e))
|
||||
ret = {"success":False, "msg":'Database error: {}'.format(e.orig if hasattr(e, "orig") else e)}
|
||||
if multi:
|
||||
out.append(ret)
|
||||
if multi:
|
||||
if len(out) > 0:
|
||||
return out
|
||||
else:
|
||||
return [ret]
|
||||
else:
|
||||
return ret
|
||||
|
||||
|
||||
@editbook.route("/ajax/sort_value/<field>/<int:bookid>")
|
||||
@@ -337,6 +482,55 @@ def simulate_merge_list_book():
|
||||
return make_response(jsonify({'to': to_book, 'from': from_book}))
|
||||
return ""
|
||||
|
||||
@editbook.route("/ajax/displayselectedbooks", methods=['POST'])
|
||||
@user_login_required
|
||||
@edit_required
|
||||
def display_selected_books():
|
||||
vals = request.get_json().get('selections')
|
||||
books = []
|
||||
if vals:
|
||||
for book_id in vals:
|
||||
books.append(calibre_db.get_book(book_id).title)
|
||||
return json.dumps({'books': books})
|
||||
return ""
|
||||
|
||||
@editbook.route("/ajax/archiveselectedbooks", methods=['POST'])
|
||||
@login_required_if_no_ano
|
||||
@edit_required
|
||||
def archive_selected_books():
|
||||
vals = request.get_json().get('selections')
|
||||
state = request.get_json().get('archive')
|
||||
if vals:
|
||||
for book_id in vals:
|
||||
is_archived = change_archived_books(book_id, state,
|
||||
message="Book {} archive bit set to: {}".format(book_id, state))
|
||||
if is_archived:
|
||||
kobo_sync_status.remove_synced_book(book_id)
|
||||
return json.dumps({'success': True})
|
||||
return ""
|
||||
|
||||
|
||||
@editbook.route("/ajax/readselectedbooks", methods=['POST'])
|
||||
@user_login_required
|
||||
@edit_required
|
||||
def read_selected_books():
|
||||
vals = request.get_json().get('selections')
|
||||
markAsRead = request.get_json().get('markAsRead')
|
||||
if vals:
|
||||
try:
|
||||
for book_id in vals:
|
||||
ret = helper.edit_book_read_status(book_id, markAsRead)
|
||||
|
||||
except (OperationalError, IntegrityError, StaleDataError) as e:
|
||||
calibre_db.session.rollback()
|
||||
log.error_or_exception("Database error: {}".format(e))
|
||||
ret = Response(json.dumps({'success': False,
|
||||
'msg': 'Database error: {}'.format(e.orig if hasattr(e, "orig") else e)}),
|
||||
mimetype='application/json')
|
||||
|
||||
return json.dumps({'success': True})
|
||||
return ""
|
||||
|
||||
|
||||
@editbook.route("/ajax/mergebooks", methods=['POST'])
|
||||
@user_login_required
|
||||
@@ -371,7 +565,7 @@ def merge_list_book():
|
||||
element.format,
|
||||
element.uncompressed_size,
|
||||
to_name))
|
||||
delete_book_from_table(from_book.id, "", True)
|
||||
check_delete_book([from_book.id], "", True)
|
||||
return make_response(jsonify(success=True))
|
||||
return ""
|
||||
|
||||
@@ -648,8 +842,9 @@ def prepare_authors(authr, calibre_path, gdrive=False):
|
||||
all_new_name = helper.get_valid_filename(one_book.title, chars=42) + ' - ' \
|
||||
+ helper.get_valid_filename(renamed_author.name, chars=42)
|
||||
# change location in database to new author/title path
|
||||
helper.rename_all_files_on_change(one_book, new_path, new_path, all_new_name, gdrive)
|
||||
|
||||
error = helper.rename_all_files_on_change(one_book, new_path, new_path, all_new_name, gdrive)
|
||||
if error:
|
||||
flash(error)
|
||||
return input_authors
|
||||
|
||||
|
||||
@@ -841,86 +1036,109 @@ def delete_whole_book(book_id, book):
|
||||
calibre_db.session.query(db.Books).filter(db.Books.id == book_id).delete()
|
||||
|
||||
|
||||
def render_delete_book_result(book_format, json_response, warning, book_id, location=""):
|
||||
def render_delete_book_result(book_format, book_id, location=""):
|
||||
if book_format:
|
||||
if json_response:
|
||||
return jsonify([warning, {"location": url_for("edit-book.show_edit_book", book_id=book_id),
|
||||
"type": "success",
|
||||
"format": book_format,
|
||||
"message": _('Book Format Successfully Deleted')}])
|
||||
else:
|
||||
flash(_('Book Format Successfully Deleted'), category="success")
|
||||
return redirect(url_for('edit-book.show_edit_book', book_id=book_id))
|
||||
flash(_('Book Format Successfully Deleted'), category="success")
|
||||
return redirect(url_for('edit-book.show_edit_book', book_id=book_id))
|
||||
else:
|
||||
if json_response:
|
||||
return jsonify([warning, {"location": get_redirect_location(location, "web.index"),
|
||||
"type": "success",
|
||||
"format": book_format,
|
||||
"message": _('Book Successfully Deleted')}])
|
||||
else:
|
||||
flash(_('Book Successfully Deleted'), category="success")
|
||||
return redirect(get_redirect_location(location, "web.index"))
|
||||
flash(_('Book Successfully Deleted'), category="success")
|
||||
return redirect(get_redirect_location(location, "web.index"))
|
||||
|
||||
|
||||
def delete_book_from_table(book_id, book_format, json_response, location=""):
|
||||
warning = {}
|
||||
def check_delete_book(book_id, book_format, json_response, location=""):
|
||||
if current_user.role_delete_books():
|
||||
book = calibre_db.get_book(book_id)
|
||||
if book:
|
||||
try:
|
||||
result, error = helper.delete_book(book, config.get_book_path(), book_format=book_format.upper())
|
||||
if not result:
|
||||
if json_response:
|
||||
return jsonify([{"location": url_for("edit-book.show_edit_book", book_id=book_id),
|
||||
"type": "danger",
|
||||
"format": "",
|
||||
"message": error}])
|
||||
else:
|
||||
flash(error, category="error")
|
||||
return redirect(url_for('edit-book.show_edit_book', book_id=book_id))
|
||||
if error:
|
||||
if json_response:
|
||||
warning = {"location": url_for("edit-book.show_edit_book", book_id=book_id),
|
||||
"type": "warning",
|
||||
"format": "",
|
||||
"message": error}
|
||||
else:
|
||||
flash(error, category="warning")
|
||||
if not book_format:
|
||||
delete_whole_book(book_id, book)
|
||||
else:
|
||||
calibre_db.session.query(db.Data).filter(db.Data.book == book.id).\
|
||||
filter(db.Data.format == book_format).delete()
|
||||
if book_format.upper() in ['KEPUB', 'EPUB', 'EPUB3']:
|
||||
kobo_sync_status.remove_synced_book(book.id, True)
|
||||
calibre_db.session.commit()
|
||||
except Exception as ex:
|
||||
log.error_or_exception(ex)
|
||||
calibre_db.session.rollback()
|
||||
if json_response:
|
||||
return jsonify([{"location": url_for("edit-book.show_edit_book", book_id=book_id),
|
||||
"type": "danger",
|
||||
"format": "",
|
||||
"message": ex}])
|
||||
else:
|
||||
flash(str(ex), category="error")
|
||||
return redirect(url_for('edit-book.show_edit_book', book_id=book_id))
|
||||
|
||||
if json_response:
|
||||
# if json response is set, it's possible to delete more than one book, but never a format is deleted
|
||||
res = list()
|
||||
for b in book_id:
|
||||
ret = delete_book_from_table(b)
|
||||
if ret:
|
||||
res.extend([ret])
|
||||
if len(res) == 0:
|
||||
return [{"location": get_redirect_location(location, "web.index"),
|
||||
"type": "success",
|
||||
"format": "",
|
||||
"message": _('Book Successfully Deleted')}]
|
||||
return jsonify(res)
|
||||
else:
|
||||
# book not found
|
||||
log.error('Book with id "%s" could not be deleted: not found', book_id)
|
||||
return render_delete_book_result(book_format, json_response, warning, book_id, location)
|
||||
return delete_book_from_UI(book_id, book_format, location)
|
||||
message = _("You are missing permissions to delete books")
|
||||
if json_response:
|
||||
return jsonify({"location": url_for("edit-book.show_edit_book", book_id=book_id),
|
||||
"type": "danger",
|
||||
"format": "",
|
||||
"message": message})
|
||||
try:
|
||||
return jsonify({"location": url_for("edit-book.show_edit_book", book_id=int(book_id)),
|
||||
"type": "danger",
|
||||
"format": "",
|
||||
"message": message})
|
||||
except TypeError as e:
|
||||
return jsonify({"location": url_for("web.index"), "type": "danger", "format": "",
|
||||
"message": str(e)})
|
||||
else:
|
||||
flash(message, category="error")
|
||||
return redirect(url_for('edit-book.show_edit_book', book_id=book_id))
|
||||
|
||||
|
||||
def delete_book_from_UI(book_id, book_format, location=""):
|
||||
book = calibre_db.get_book(book_id)
|
||||
if book:
|
||||
try:
|
||||
result, error = helper.delete_book(book, config.get_book_path(), book_format=book_format.upper())
|
||||
if not result:
|
||||
flash(error, category="error")
|
||||
return redirect(url_for('edit-book.show_edit_book', book_id=book_id))
|
||||
if error:
|
||||
flash(error, category="warning")
|
||||
if not book_format:
|
||||
delete_whole_book(book_id, book)
|
||||
else:
|
||||
calibre_db.session.query(db.Data).filter(db.Data.book == book.id). \
|
||||
filter(db.Data.format == book_format).delete()
|
||||
if book_format.upper() in ['KEPUB', 'EPUB', 'EPUB3']:
|
||||
kobo_sync_status.remove_synced_book(book.id, True)
|
||||
calibre_db.session.commit()
|
||||
except Exception as ex:
|
||||
log.error_or_exception(ex)
|
||||
calibre_db.session.rollback()
|
||||
flash(str(ex), category="error")
|
||||
return redirect(url_for('edit-book.show_edit_book', book_id=book_id))
|
||||
else:
|
||||
# book not found
|
||||
log.error('Book with id "%s" could not be deleted: not found', book_id)
|
||||
return render_delete_book_result(book_format, book_id, location)
|
||||
|
||||
|
||||
def delete_book_from_table(book_id):
|
||||
book = calibre_db.get_book(book_id)
|
||||
if book:
|
||||
try:
|
||||
result, error = helper.delete_book(book, config.get_book_path(), book_format="")
|
||||
if not result:
|
||||
return {"location": url_for("edit-book.show_edit_book", book_id=book_id),
|
||||
"type": "danger",
|
||||
"format": "",
|
||||
"message": error}
|
||||
delete_whole_book(book_id, book)
|
||||
calibre_db.session.commit()
|
||||
if error:
|
||||
return {"location": url_for("edit-book.show_edit_book", book_id=book_id),
|
||||
"type": "warning",
|
||||
"format": "",
|
||||
"message": error}
|
||||
except Exception as ex:
|
||||
log.error_or_exception(ex)
|
||||
calibre_db.session.rollback()
|
||||
return {"location": url_for("edit-book.show_edit_book", book_id=book_id),
|
||||
"type": "danger",
|
||||
"format": "",
|
||||
"message": ex}
|
||||
else:
|
||||
# book not found
|
||||
log.error('Book with id "%s" could not be deleted: not found', book_id)
|
||||
return {"location": url_for("edit-book.show_edit_book", book_id=book_id),
|
||||
"type": "danger",
|
||||
"format": "",
|
||||
"message": _('Book with id "{}" could not be deleted: not found'.format(book_id))}
|
||||
|
||||
|
||||
def render_edit_book(book_id):
|
||||
cc = calibre_db.session.query(db.CustomColumns).filter(db.CustomColumns.datatype.notin_(db.cc_exceptions)).all()
|
||||
book = calibre_db.get_filtered_book(book_id, allow_show_archived=True)
|
||||
|
@@ -307,18 +307,16 @@ def edit_book_read_status(book_id, read_status=None):
|
||||
if not config.config_read_column:
|
||||
book = ub.session.query(ub.ReadBook).filter(and_(ub.ReadBook.user_id == int(current_user.id),
|
||||
ub.ReadBook.book_id == book_id)).first()
|
||||
if book:
|
||||
if read_status is None:
|
||||
if book.read_status == ub.ReadBook.STATUS_FINISHED:
|
||||
book.read_status = ub.ReadBook.STATUS_UNREAD
|
||||
else:
|
||||
book.read_status = ub.ReadBook.STATUS_FINISHED
|
||||
else:
|
||||
book.read_status = ub.ReadBook.STATUS_FINISHED if read_status else ub.ReadBook.STATUS_UNREAD
|
||||
else:
|
||||
if not book:
|
||||
read_book = ub.ReadBook(user_id=current_user.id, book_id=book_id)
|
||||
read_book.read_status = ub.ReadBook.STATUS_FINISHED
|
||||
book = read_book
|
||||
if read_status is None:
|
||||
if book.read_status == ub.ReadBook.STATUS_FINISHED:
|
||||
book.read_status = ub.ReadBook.STATUS_UNREAD
|
||||
else:
|
||||
book.read_status = ub.ReadBook.STATUS_FINISHED
|
||||
else:
|
||||
book.read_status = ub.ReadBook.STATUS_FINISHED if read_status == True else ub.ReadBook.STATUS_UNREAD
|
||||
if not book.kobo_reading_state:
|
||||
kobo_reading_state = ub.KoboReadingState(user_id=current_user.id, book_id=book_id)
|
||||
kobo_reading_state.current_bookmark = ub.KoboBookmark()
|
||||
@@ -396,10 +394,16 @@ def delete_book_file(book, calibrepath, book_format=None):
|
||||
def rename_all_files_on_change(one_book, new_path, old_path, all_new_name, gdrive=False):
|
||||
for file_format in one_book.data:
|
||||
if not gdrive:
|
||||
if not os.path.exists(new_path):
|
||||
os.makedirs(new_path)
|
||||
shutil.move(os.path.join(old_path, file_format.name + '.' + file_format.format.lower()),
|
||||
os.path.join(new_path, all_new_name + '.' + file_format.format.lower()))
|
||||
try:
|
||||
if not os.path.exists(new_path):
|
||||
os.makedirs(new_path)
|
||||
shutil.move(os.path.join(old_path, file_format.name + '.' + file_format.format.lower()),
|
||||
os.path.join(new_path, all_new_name + '.' + file_format.format.lower()))
|
||||
except (PermissionError, FileNotFoundError) as ex:
|
||||
log.error("Moving book-id %s folder %s failed: %s", one_book.id, new_path, ex)
|
||||
return _("Moving book path of Book %(book_id)s to: '%(src)s' failed with error: %(error)s",
|
||||
book_id=one_book.id, src=new_path, error=str(ex))
|
||||
|
||||
else:
|
||||
g_file = gd.getFileFromEbooksFolder(old_path,
|
||||
file_format.name + '.' + file_format.format.lower())
|
||||
@@ -412,6 +416,7 @@ def rename_all_files_on_change(one_book, new_path, old_path, all_new_name, gdriv
|
||||
|
||||
# change name in Database
|
||||
file_format.name = all_new_name
|
||||
return False
|
||||
|
||||
|
||||
def rename_author_path(first_author, old_author_dir, renamed_author, calibre_path="", gdrive=False):
|
||||
@@ -466,14 +471,15 @@ def update_dir_structure_file(book_id, calibre_path, original_filepath, new_auth
|
||||
db_filename,
|
||||
original_filepath,
|
||||
path)
|
||||
new_path = os.path.join(calibre_path, new_author_dir, new_title_dir).replace('\\', '/')
|
||||
all_new_name = get_valid_filename(local_book.title, chars=42) + ' - ' \
|
||||
+ get_valid_filename(new_author, chars=42)
|
||||
# Book folder already moved, only files need to be renamed
|
||||
rename_all_files_on_change(local_book, new_path, new_path, all_new_name)
|
||||
if not error:
|
||||
new_path = os.path.join(calibre_path, new_author_dir, new_title_dir).replace('\\', '/')
|
||||
all_new_name = get_valid_filename(local_book.title, chars=42) + ' - ' \
|
||||
+ get_valid_filename(new_author, chars=42)
|
||||
# Book folder already moved, only files need to be renamed
|
||||
renameerror = rename_all_files_on_change(local_book, new_path, new_path, all_new_name)
|
||||
|
||||
if error:
|
||||
return error
|
||||
if error or renameerror:
|
||||
return error or renameerror
|
||||
return False
|
||||
|
||||
|
||||
@@ -517,7 +523,7 @@ def update_dir_structure_gdrive(book_id, first_author):
|
||||
if titledir != new_titledir or authordir != new_authordir :
|
||||
all_new_name = get_valid_filename(book.title, chars=42) + ' - ' \
|
||||
+ get_valid_filename(new_authordir, chars=42)
|
||||
rename_all_files_on_change(book, book.path, book.path, all_new_name, gdrive=True) # todo: Move filenames on gdrive
|
||||
return rename_all_files_on_change(book, book.path, book.path, all_new_name, gdrive=True) # todo: Move filenames on gdrive
|
||||
return False
|
||||
|
||||
|
||||
@@ -553,33 +559,13 @@ def move_files_on_change(calibre_path, new_author_dir, new_titledir, localbook,
|
||||
log.error("Deleting authorpath for book %s failed: %s", localbook.id, ex)
|
||||
# change location in database to new author/title path
|
||||
localbook.path = os.path.join(new_author_dir, new_titledir).replace('\\', '/')
|
||||
except OSError as ex:
|
||||
except (OSError, FileNotFoundError) as ex:
|
||||
log.error_or_exception("Rename title from {} to {} failed with error: {}".format(path, new_path, ex))
|
||||
return _("Rename title from: '%(src)s' to '%(dest)s' failed with error: %(error)s",
|
||||
src=path, dest=new_path, error=str(ex))
|
||||
return False
|
||||
|
||||
|
||||
def rename_files_on_change(first_author,
|
||||
renamed_author,
|
||||
local_book,
|
||||
original_filepath="",
|
||||
path="",
|
||||
calibre_path="",
|
||||
gdrive=False):
|
||||
# Rename all files from old names to new names
|
||||
#try:
|
||||
#clean_author_database(renamed_author, calibre_path, gdrive=gdrive)
|
||||
#if first_author and first_author not in renamed_author:
|
||||
# clean_author_database([first_author], calibre_path, local_book, gdrive)
|
||||
#if not gdrive and not renamed_author and not original_filepath and len(os.listdir(os.path.dirname(path))) == 0:
|
||||
# shutil.rmtree(os.path.dirname(path))
|
||||
#except (OSError, FileNotFoundError) as ex:
|
||||
# log.error_or_exception("Error in rename file in path {}".format(ex))
|
||||
# return _("Error in rename file in path: {}".format(str(ex)))
|
||||
return False
|
||||
|
||||
|
||||
def delete_book_gdrive(book, book_format):
|
||||
error = None
|
||||
if book_format:
|
||||
|
@@ -51,13 +51,15 @@ def remove_synced_book(book_id, all=False, session=None):
|
||||
ub.session_commit(_session=session)
|
||||
|
||||
|
||||
# If state == none, it will toggle the archive state of the passed book_id.
|
||||
# state = true archives it, state = false unarchives it
|
||||
def change_archived_books(book_id, state=None, message=None):
|
||||
archived_book = ub.session.query(ub.ArchivedBook).filter(and_(ub.ArchivedBook.user_id == int(current_user.id),
|
||||
ub.ArchivedBook.book_id == book_id)).first()
|
||||
if not archived_book:
|
||||
if not archived_book: # and (state == True or state == None):
|
||||
archived_book = ub.ArchivedBook(user_id=current_user.id, book_id=book_id)
|
||||
|
||||
archived_book.is_archived = state if state else not archived_book.is_archived
|
||||
archived_book.is_archived = state if state != None else not archived_book.is_archived
|
||||
archived_book.last_modified = datetime.now(timezone.utc) # toDo. Check utc timestamp
|
||||
|
||||
ub.session.merge(archived_book)
|
||||
|
@@ -26,3 +26,14 @@ body.serieslist.grid-view div.container-fluid > div > div.col-sm-10::before {
|
||||
input.datepicker {color: transparent}
|
||||
input.datepicker:focus {color: transparent}
|
||||
input.datepicker:focus + input {color: #555}
|
||||
|
||||
.col-sm-3.col-lg-2.col-xs-6.book.session {
|
||||
margin-left: 0;
|
||||
margin-right: 0;
|
||||
}
|
||||
|
||||
@media only screen and (max-width: 767px) {
|
||||
.row-fluid > .col-sm-2 {
|
||||
visibility: hidden;
|
||||
}
|
||||
}
|
||||
|
@@ -229,10 +229,12 @@ $("#delete_confirm").click(function(event) {
|
||||
postButton(event, getPath() + "/delete/" + deleteId + "/" + bookFormat);
|
||||
} else {
|
||||
if (ajaxResponse) {
|
||||
path = getPath() + "/ajax/delete/" + deleteId;
|
||||
$.ajax({
|
||||
method:"post",
|
||||
url: path,
|
||||
url: getPath() + "/ajax/deletebook",
|
||||
method: "post",
|
||||
contentType: "application/json; charset=utf-8",
|
||||
dataType: "json",
|
||||
data: JSON.stringify({"bookid": [deleteId]}),
|
||||
timeout: 900,
|
||||
success:function(data) {
|
||||
data.forEach(function(item) {
|
||||
|
@@ -81,20 +81,78 @@ $(function() {
|
||||
$("#merge_books").addClass("disabled");
|
||||
$("#merge_books").attr("aria-disabled", true);
|
||||
}
|
||||
if (selections.length >= 1) {
|
||||
$("#delete_selected_books").removeClass("disabled");
|
||||
$("#delete_selected_books").attr("aria-disabled", false);
|
||||
|
||||
$("#archive_selected_books").removeClass("disabled");
|
||||
$("#archive_selected_books").attr("aria-disabled", false);
|
||||
|
||||
$("#unarchive_selected_books").removeClass("disabled");
|
||||
$("#unarchive_selected_books").attr("aria-disabled", false);
|
||||
|
||||
$("#read_selected_books").removeClass("disabled");
|
||||
$("#read_selected_books").attr("aria-disabled", false);
|
||||
|
||||
$("#unread_selected_books").removeClass("disabled");
|
||||
$("#unread_selected_books").attr("aria-disabled", false);
|
||||
|
||||
$("#edit_selected_books").removeClass("disabled");
|
||||
$("#edit_selected_books").attr("aria-disabled", false);
|
||||
} else {
|
||||
$("#delete_selected_books").addClass("disabled");
|
||||
$("#delete_selected_books").attr("aria-disabled", true);
|
||||
|
||||
$("#archive_selected_books").addClass("disabled");
|
||||
$("#archive_selected_books").attr("aria-disabled", true);
|
||||
|
||||
$("#unarchive_selected_books").addClass("disabled");
|
||||
$("#unarchive_selected_books").attr("aria-disabled", true);
|
||||
|
||||
$("#read_selected_books").addClass("disabled");
|
||||
$("#read_selected_books").attr("aria-disabled", true);
|
||||
|
||||
$("#unread_selected_books").addClass("disabled");
|
||||
$("#unread_selected_books").attr("aria-disabled", true);
|
||||
|
||||
$("#edit_selected_books").addClass("disabled");
|
||||
$("#edit_selected_books").attr("aria-disabled", true);
|
||||
}
|
||||
if (selections.length < 1) {
|
||||
$("#delete_selection").addClass("disabled");
|
||||
$("#delete_selection").attr("aria-disabled", true);
|
||||
// $("#book_delete_selection").addClass("disabled");
|
||||
// $("#book_delete_selection").attr("aria-disabled", true);
|
||||
$("#table_xchange").addClass("disabled");
|
||||
$("#table_xchange").attr("aria-disabled", true);
|
||||
} else {
|
||||
$("#delete_selection").removeClass("disabled");
|
||||
$("#delete_selection").attr("aria-disabled", false);
|
||||
// $("#book_delete_selection").removeClass("disabled");
|
||||
// $("#book_delete_selection").attr("aria-disabled", false);
|
||||
$("#table_xchange").removeClass("disabled");
|
||||
$("#table_xchange").attr("aria-disabled", false);
|
||||
|
||||
}
|
||||
handle_header_buttons();
|
||||
});
|
||||
$("#delete_selection").click(function() {
|
||||
|
||||
// Small block to initialize the state of the author/title sort inputs in metadata form
|
||||
{
|
||||
let checkA = $('#autoupdate_authorsort').prop('checked');
|
||||
$('#author_sort_input').prop('disabled', checkA);
|
||||
let checkT = $('#autoupdate_titlesort').prop('checked');
|
||||
$('#title_sort_input').prop('disabled', checkT);
|
||||
}
|
||||
|
||||
// Disable/enable author and title sort input in respect to auto-update title/author sort being checked on or not
|
||||
$("#autoupdate_authorsort").on('change', function(event) {
|
||||
let checkA = $('#autoupdate_authorsort').prop('checked');
|
||||
$('#author_sort_input').prop('disabled', checkA);
|
||||
})
|
||||
|
||||
$("#autoupdate_titlesort").on('change', function(event) {
|
||||
let checkT = $('#autoupdate_titlesort').prop('checked');
|
||||
$('#title_sort_input').prop('disabled', checkT);
|
||||
})
|
||||
/////
|
||||
|
||||
$("#book_delete_selection").click(function () {
|
||||
$("#books-table").bootstrapTable("uncheckAll");
|
||||
});
|
||||
|
||||
@@ -135,6 +193,250 @@ $(function() {
|
||||
});
|
||||
});
|
||||
|
||||
$("#edit_selected_books").click(function(event) {
|
||||
if ($(this).hasClass("disabled")) {
|
||||
event.stopPropagation()
|
||||
} else {
|
||||
$('#edit_selected_modal').modal("show");
|
||||
}
|
||||
});
|
||||
|
||||
$("#edit_selected_confirm").click(function(event) {
|
||||
$.ajax({
|
||||
method:"post",
|
||||
contentType: "application/json; charset=utf-8",
|
||||
dataType: "json",
|
||||
url: getPath() + "/ajax/editselectedbooks",
|
||||
data: JSON.stringify({
|
||||
"selections": selections,
|
||||
"title": $("#title_input").val(),
|
||||
"title_sort": $("#title_sort_input").val(),
|
||||
"author_sort": $("#author_sort_input").val(),
|
||||
"authors": $("#authors_input").val(),
|
||||
"categories": $("#categories_input").val(),
|
||||
"series": $("#series_input").val(),
|
||||
"languages": $("#languages_input").val(),
|
||||
"publishers": $("#publishers_input").val(),
|
||||
"comments": $("#comments_input").val().toString(),
|
||||
"checkA": $('#autoupdate_authorsort').prop('checked'),
|
||||
"checkT": $('#autoupdate_titlesort').prop('checked')
|
||||
}),
|
||||
success: function success(data) {
|
||||
let result = "";
|
||||
$("#books-table").bootstrapTable("refresh");
|
||||
$("#books-table").bootstrapTable("uncheckAll");
|
||||
|
||||
$("#title_input").val("");
|
||||
$("#title_sort_input").val("");
|
||||
$("#author_sort_input").val("");
|
||||
$("#authors_input").val("");
|
||||
$("#categories_input").val("");
|
||||
$("#series_input").val("");
|
||||
$("#languages_input").val("");
|
||||
$("#publishers_input").val("");
|
||||
$("#comments_input").val("");
|
||||
|
||||
$("#flash_success").remove();
|
||||
$("#flash_danger").remove();
|
||||
|
||||
if (!jQuery.isEmptyObject(data)) {
|
||||
data.forEach(function(item) {
|
||||
if (item.success === true) {
|
||||
result = "success";
|
||||
} else {
|
||||
result = "danger";
|
||||
}
|
||||
$(".navbar").after('<div class="row-fluid text-center">' +
|
||||
'<div id="flash_' + result + '" class="alert alert-' + result + '">' + item.msg + '</div>' +
|
||||
'</div>');
|
||||
});
|
||||
}
|
||||
$(".table.table-striped").bootstrapTable("refresh");
|
||||
// handleListServerResponse(data);
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
$(document).on('click', '#archive_selected_books', function(event) {
|
||||
if ($(this).hasClass("disabled")) {
|
||||
event.stopPropagation()
|
||||
} else {
|
||||
$('#archive_selected_modal').modal("show");
|
||||
}
|
||||
$.ajax({
|
||||
method:"post",
|
||||
contentType: "application/json; charset=utf-8",
|
||||
dataType: "json",
|
||||
url: getPath() + "/ajax/displayselectedbooks",
|
||||
data: JSON.stringify({"selections":selections}),
|
||||
success: function success(booTitles) {
|
||||
$('#display-archive-selected-books').empty();
|
||||
$.each(booTitles.books, function(i, item) {
|
||||
$("<span>- " + item + "</span><p></p>").appendTo("#display-archive-selected-books");
|
||||
});
|
||||
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
/*$(document).on('click', '#archive_selected_confirm', function(event) {
|
||||
$.ajax({
|
||||
method:"post",
|
||||
contentType: "application/json; charset=utf-8",
|
||||
dataType: "json",
|
||||
url: getPath() + "/ajax/archiveselectedbooks",
|
||||
data: JSON.stringify({"selections":selections, "archive": true}),
|
||||
success: function success(booTitles) {
|
||||
$("#books-table").bootstrapTable("refresh");
|
||||
$("#books-table").bootstrapTable("uncheckAll");
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
$(document).on('click', '#unarchive_selected_books', function(event) {
|
||||
if ($(this).hasClass("disabled")) {
|
||||
event.stopPropagation()
|
||||
} else {
|
||||
$('#unarchive_selected_modal').modal("show");
|
||||
}
|
||||
$.ajax({
|
||||
method:"post",
|
||||
contentType: "application/json; charset=utf-8",
|
||||
dataType: "json",
|
||||
url: getPath() + "/ajax/displayselectedbooks",
|
||||
data: JSON.stringify({"selections":selections}),
|
||||
success: function success(booTitles) {
|
||||
$('#display-unarchive-selected-books').empty();
|
||||
$.each(booTitles.books, function(i, item) {
|
||||
$("<span>- " + item + "</span><p></p>").appendTo("#display-unarchive-selected-books");
|
||||
});
|
||||
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
$(document).on('click', '#unarchive_selected_confirm', function(event) {
|
||||
$.ajax({
|
||||
method:"post",
|
||||
contentType: "application/json; charset=utf-8",
|
||||
dataType: "json",
|
||||
url: getPath() + "/ajax/archiveselectedbooks",
|
||||
data: JSON.stringify({"selections":selections, "archive": false}),
|
||||
success: function success(booTitles) {
|
||||
$("#books-table").bootstrapTable("refresh");
|
||||
$("#books-table").bootstrapTable("uncheckAll");
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
$(document).on('click', '#delete_selected_books', function(event) {
|
||||
if ($(this).hasClass("disabled")) {
|
||||
event.stopPropagation()
|
||||
} else {
|
||||
$('#delete_selected_modal').modal("show");
|
||||
}
|
||||
$.ajax({
|
||||
method:"post",
|
||||
contentType: "application/json; charset=utf-8",
|
||||
dataType: "json",
|
||||
url: getPath() + "/ajax/displayselectedbooks",
|
||||
data: JSON.stringify({"selections":selections}),
|
||||
success: function success(booTitles) {
|
||||
$('#display-delete-selected-books').empty();
|
||||
$.each(booTitles.books, function(i, item) {
|
||||
$("<span>- " + item + "</span><p></p>").appendTo("#display-delete-selected-books");
|
||||
});
|
||||
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
$(document).on('click', '#delete_selected_confirm', function(event) {
|
||||
$.ajax({
|
||||
method:"post",
|
||||
contentType: "application/json; charset=utf-8",
|
||||
dataType: "json",
|
||||
url: getPath() + "/ajax/deleteselectedbooks",
|
||||
data: JSON.stringify({"selections":selections}),
|
||||
success: function success(booTitles) {
|
||||
$("#books-table").bootstrapTable("refresh");
|
||||
$("#books-table").bootstrapTable("uncheckAll");
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
$(document).on('click', '#read_selected_books', function(event) {
|
||||
if ($(this).hasClass("disabled")) {
|
||||
event.stopPropagation()
|
||||
} else {
|
||||
$('#read_selected_modal').modal("show");
|
||||
}
|
||||
$.ajax({
|
||||
method:"post",
|
||||
contentType: "application/json; charset=utf-8",
|
||||
dataType: "json",
|
||||
url: getPath() + "/ajax/displayselectedbooks",
|
||||
data: JSON.stringify({"selections":selections}),
|
||||
success: function success(booTitles) {
|
||||
$('#display-read-selected-books').empty();
|
||||
$.each(booTitles.books, function(i, item) {
|
||||
$("<span>- " + item + "</span><p></p>").appendTo("#display-read-selected-books");
|
||||
});
|
||||
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
$(document).on('click', '#read_selected_confirm', function(event) {
|
||||
$.ajax({
|
||||
method:"post",
|
||||
contentType: "application/json; charset=utf-8",
|
||||
dataType: "json",
|
||||
url: getPath() + "/ajax/readselectedbooks",
|
||||
data: JSON.stringify({"selections":selections, "markAsRead": true}),
|
||||
success: function success(booTitles) {
|
||||
$("#books-table").bootstrapTable("refresh");
|
||||
$("#books-table").bootstrapTable("uncheckAll");
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
$(document).on('click', '#unread_selected_books', function(event) {
|
||||
if ($(this).hasClass("disabled")) {
|
||||
event.stopPropagation()
|
||||
} else {
|
||||
$('#unread_selected_modal').modal("show");
|
||||
}
|
||||
$.ajax({
|
||||
method:"post",
|
||||
contentType: "application/json; charset=utf-8",
|
||||
dataType: "json",
|
||||
url: getPath() + "/ajax/displayselectedbooks",
|
||||
data: JSON.stringify({"selections":selections}),
|
||||
success: function success(booTitles) {
|
||||
$('#display-unread-selected-books').empty();
|
||||
$.each(booTitles.books, function(i, item) {
|
||||
$("<span>- " + item + "</span><p></p>").appendTo("#display-unread-selected-books");
|
||||
});
|
||||
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
$(document).on('click', '#unread_selected_confirm', function(event) {
|
||||
$.ajax({
|
||||
method:"post",
|
||||
contentType: "application/json; charset=utf-8",
|
||||
dataType: "json",
|
||||
url: getPath() + "/ajax/readselectedbooks",
|
||||
data: JSON.stringify({"selections":selections, "markAsRead": false}),
|
||||
success: function success(booTitles) {
|
||||
$("#books-table").bootstrapTable("refresh");
|
||||
$("#books-table").bootstrapTable("uncheckAll");
|
||||
}
|
||||
});
|
||||
});*/
|
||||
|
||||
$("#table_xchange").click(function() {
|
||||
$.ajax({
|
||||
method:"post",
|
||||
@@ -157,6 +459,10 @@ $(function() {
|
||||
editable: {
|
||||
mode: "inline",
|
||||
emptytext: "<span class='glyphicon glyphicon-plus'></span>",
|
||||
ajaxOptions: {
|
||||
contentType: "application/json; charset=utf-8",
|
||||
dataType: "json",
|
||||
},
|
||||
success: function (response, __) {
|
||||
if (!response.success) return response.msg;
|
||||
return {newValue: response.newValue};
|
||||
@@ -164,7 +470,8 @@ $(function() {
|
||||
params: function (params) {
|
||||
params.checkA = $('#autoupdate_authorsort').prop('checked');
|
||||
params.checkT = $('#autoupdate_titlesort').prop('checked');
|
||||
return params
|
||||
params.pk = [params.pk];
|
||||
return JSON.stringify(params);
|
||||
}
|
||||
}
|
||||
};
|
||||
@@ -198,7 +505,7 @@ $(function() {
|
||||
searchAlign: "left",
|
||||
showSearchButton : true,
|
||||
searchOnEnterKey: true,
|
||||
checkboxHeader: false,
|
||||
checkboxHeader: true,
|
||||
maintainMetaData: true,
|
||||
responseHandler: responseHandler,
|
||||
columns: column,
|
||||
@@ -212,7 +519,7 @@ $(function() {
|
||||
$.ajax({
|
||||
method:"get",
|
||||
dataType: "json",
|
||||
url: window.location.pathname + "/../ajax/sort_value/" + field + "/" + row.id,
|
||||
url: getPath() + "/ajax/sort_value/" + field + "/" + row.id,
|
||||
success: function success(data) {
|
||||
var key = Object.keys(data)[0];
|
||||
$("#books-table").bootstrapTable("updateCellByUniqueId", {
|
||||
@@ -224,6 +531,66 @@ $(function() {
|
||||
});
|
||||
}
|
||||
},
|
||||
onPostBody () {
|
||||
// Remove all checkboxes from Headers for showing the texts in the column selector
|
||||
$('.columns [data-field]').each(function(){
|
||||
var elText = $(this).next().text();
|
||||
$(this).next().empty();
|
||||
var index = elText.lastIndexOf('\n', elText.length - 2);
|
||||
if ( index > -1) {
|
||||
elText = elText.substr(index);
|
||||
}
|
||||
$(this).next().text(elText);
|
||||
});
|
||||
},
|
||||
onPostHeader() {
|
||||
$(".form-check").each(function () {
|
||||
var item = $(this).parent();
|
||||
var parent = item.parent().parent();
|
||||
if (parent.prop('nodeName') === "TH") {
|
||||
item.prependTo(parent);
|
||||
}
|
||||
});
|
||||
|
||||
if ($(".button_head").length) {
|
||||
if (!$._data($(".button_head").get(0), "events")) {
|
||||
$(".button_head").on("click", function () {
|
||||
var result = $('#books-table').bootstrapTable('getSelections').map(a => a.id);
|
||||
confirmDialog(
|
||||
"btndelbook",
|
||||
"GeneralDeleteModal",
|
||||
0,
|
||||
function () {
|
||||
$.ajax({
|
||||
method: "post",
|
||||
url: getPath() + "/ajax/deletebook",
|
||||
contentType: "application/json; charset=utf-8",
|
||||
dataType: "json",
|
||||
data: JSON.stringify({"bookid": result}),
|
||||
success: function (data) {
|
||||
selections = selections.filter((el) => !result.includes(el));
|
||||
handleListServerResponse(data);
|
||||
},
|
||||
error: function (data) {
|
||||
handleListServerResponse([{type: "danger", message: data.responseText}])
|
||||
},
|
||||
});
|
||||
}
|
||||
);
|
||||
});
|
||||
}
|
||||
}
|
||||
if ($(".check_head").length) {
|
||||
if (!$._data($(".check_head").get(0), "events")) {
|
||||
$(".check_head").on("change", function () {
|
||||
var val = $(this).data("set");
|
||||
var name = $(this).data("name");
|
||||
var data = $(this).data("val");
|
||||
bookCheckboxHeader(val, name, data);
|
||||
});
|
||||
}
|
||||
}
|
||||
},
|
||||
// eslint-disable-next-line no-unused-vars
|
||||
onColumnSwitch: function (field, checked) {
|
||||
var visible = $("#books-table").bootstrapTable("getVisibleColumns");
|
||||
@@ -240,10 +607,16 @@ $(function() {
|
||||
method:"post",
|
||||
contentType: "application/json; charset=utf-8",
|
||||
dataType: "json",
|
||||
url: window.location.pathname + "/../ajax/table_settings",
|
||||
url: getPath() + "/ajax/table_settings",
|
||||
data: "{" + st + "}",
|
||||
});
|
||||
handle_header_buttons();
|
||||
},
|
||||
onLoadSuccess: function() {
|
||||
$("input:radio.check_head:checked").each(function () {
|
||||
$(this).prop('checked', false);
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
$("#domain_allow_submit").click(function(event) {
|
||||
@@ -252,7 +625,7 @@ $(function() {
|
||||
$(this).closest("form").submit();
|
||||
$.ajax ({
|
||||
method:"get",
|
||||
url: window.location.pathname + "/../../ajax/domainlist/1",
|
||||
url: getPath() + "/ajax/domainlist/1",
|
||||
async: true,
|
||||
timeout: 900,
|
||||
success:function(data) {
|
||||
@@ -273,7 +646,7 @@ $(function() {
|
||||
$(this).closest("form").submit();
|
||||
$.ajax ({
|
||||
method:"get",
|
||||
url: window.location.pathname + "/../../ajax/domainlist/0",
|
||||
url: getPath() + "/ajax/domainlist/0",
|
||||
async: true,
|
||||
timeout: 900,
|
||||
success:function(data) {
|
||||
@@ -291,12 +664,12 @@ $(function() {
|
||||
function domainHandle(domainId) {
|
||||
$.ajax({
|
||||
method:"post",
|
||||
url: window.location.pathname + "/../../ajax/deletedomain",
|
||||
url: getPath() + "/ajax/deletedomain",
|
||||
data: {"domainid":domainId}
|
||||
});
|
||||
$.ajax({
|
||||
method:"get",
|
||||
url: window.location.pathname + "/../../ajax/domainlist/1",
|
||||
url: getPath() + "/ajax/domainlist/1",
|
||||
async: true,
|
||||
timeout: 900,
|
||||
success:function(data) {
|
||||
@@ -305,7 +678,7 @@ $(function() {
|
||||
});
|
||||
$.ajax({
|
||||
method:"get",
|
||||
url: window.location.pathname + "/../../ajax/domainlist/0",
|
||||
url: getPath() + "/ajax/domainlist/0",
|
||||
async: true,
|
||||
timeout: 900,
|
||||
success:function(data) {
|
||||
@@ -541,7 +914,7 @@ $(function() {
|
||||
method:"post",
|
||||
contentType: "application/json; charset=utf-8",
|
||||
dataType: "json",
|
||||
url: window.location.pathname + "/../../ajax/user_table_settings",
|
||||
url: getPath() + "/ajax/user_table_settings",
|
||||
data: "{" + st + "}",
|
||||
});
|
||||
handle_header_buttons();
|
||||
@@ -567,8 +940,8 @@ $(function() {
|
||||
|
||||
function handle_header_buttons () {
|
||||
if (selections.length < 1) {
|
||||
$("#user_delete_selection").addClass("disabled");
|
||||
$("#user_delete_selection").attr("aria-disabled", true);
|
||||
$(".mass_selection").addClass("disabled");
|
||||
$(".mass_selection").attr("aria-disabled", true);
|
||||
$(".check_head").attr("aria-disabled", true);
|
||||
$(".check_head").attr("disabled", true);
|
||||
$(".check_head").prop('checked', false);
|
||||
@@ -580,8 +953,8 @@ function handle_header_buttons () {
|
||||
$(".multi_selector").attr("disabled", true);
|
||||
$(".header_select").attr("disabled", true);
|
||||
} else {
|
||||
$("#user_delete_selection").removeClass("disabled");
|
||||
$("#user_delete_selection").attr("aria-disabled", false);
|
||||
$(".mass_selection").removeClass("disabled");
|
||||
$(".mass_selection").attr("aria-disabled", false);
|
||||
$(".check_head").attr("aria-disabled", false);
|
||||
$(".check_head").removeAttr("disabled");
|
||||
$(".button_head").attr("aria-disabled", false);
|
||||
@@ -590,8 +963,10 @@ function handle_header_buttons () {
|
||||
$(".multi_head").removeClass("hidden");
|
||||
$(".multi_selector").attr("aria-disabled", false);
|
||||
$(".multi_selector").removeAttr("disabled");
|
||||
$('.multi_selector').selectpicker('refresh');
|
||||
$(".header_select").removeAttr("disabled");
|
||||
if (typeof $.fn.selectpicker === "function") {
|
||||
$('.multi_selector').selectpicker('refresh');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -718,7 +1093,7 @@ function loadSuccess() {
|
||||
$("input[data-name='passwd_role'][data-pk='"+guest.data("pk")+"']").prop("disabled", true);
|
||||
$("input[data-name='edit_shelf_role'][data-pk='"+guest.data("pk")+"']").prop("disabled", true);
|
||||
$("input[data-name='sidebar_read_and_unread'][data-pk='"+guest.data("pk")+"']").prop("disabled", true);
|
||||
$(".user-remove[data-pk='"+guest.data("pk")+"']").hide();
|
||||
$(".user-remove[data-pk='" + guest.data("pk") + "']").hide();
|
||||
}
|
||||
|
||||
function move_header_elements() {
|
||||
@@ -760,7 +1135,7 @@ function move_header_elements() {
|
||||
function () {
|
||||
$.ajax({
|
||||
method: "post",
|
||||
url: window.location.pathname + "/../../ajax/editlistusers/" + field,
|
||||
url: getPath() + "/ajax/editlistusers/" + field,
|
||||
data: {"pk": result, "value": values, "action": val},
|
||||
success: function (data) {
|
||||
handleListServerResponse(data);
|
||||
@@ -774,7 +1149,6 @@ function move_header_elements() {
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
$("#user_delete_selection").click(function () {
|
||||
$("#user-table").bootstrapTable("uncheckAll");
|
||||
});
|
||||
@@ -805,8 +1179,10 @@ function move_header_elements() {
|
||||
function () {
|
||||
$.ajax({
|
||||
method: "post",
|
||||
url: window.location.pathname + "/../../ajax/deleteuser",
|
||||
data: {"userid": result},
|
||||
url: getPath() + "/ajax/deleteuser",
|
||||
contentType: "application/json; charset=utf-8",
|
||||
dataType: "json",
|
||||
data: JSON.stringify({"userid": result}),
|
||||
success: function (data) {
|
||||
selections = selections.filter((el) => !result.includes(el));
|
||||
handleListServerResponse(data);
|
||||
@@ -832,7 +1208,7 @@ function handleListServerResponse (data) {
|
||||
'</div>');
|
||||
});
|
||||
}
|
||||
$("#user-table").bootstrapTable("refresh");
|
||||
$(".table.table-striped").bootstrapTable("refresh");
|
||||
}
|
||||
|
||||
function checkboxChange(checkbox, userId, field, field_index) {
|
||||
@@ -847,30 +1223,30 @@ function checkboxChange(checkbox, userId, field, field_index) {
|
||||
});
|
||||
}
|
||||
|
||||
function BookCheckboxChange(checkbox, userId, field) {
|
||||
function BookCheckboxChange(checkbox, bookId, field) {
|
||||
var value = checkbox.checked ? "True" : "False";
|
||||
var element = checkbox;
|
||||
$.ajax({
|
||||
method: "post",
|
||||
url: getPath() + "/ajax/editbooks/" + field,
|
||||
data: {"pk": userId, "value": value},
|
||||
data: JSON.stringify({"pk": [bookId], "value": value}),
|
||||
contentType: "application/json; charset=utf-8",
|
||||
dataType: "json",
|
||||
error: function(data) {
|
||||
element.checked = !element.checked;
|
||||
handleListServerResponse([{type:"danger", message:data.responseText}])
|
||||
},
|
||||
success: handleListServerResponse
|
||||
});
|
||||
console.log("test");
|
||||
}
|
||||
|
||||
|
||||
function selectHeader(element, field) {
|
||||
if (element.value !== "None") {
|
||||
confirmDialog(element.id, "GeneralChangeModal", 0, function () {
|
||||
var result = $('#user-table').bootstrapTable('getSelections').map(a => a.id);
|
||||
$.ajax({
|
||||
method: "post",
|
||||
url: window.location.pathname + "/../../ajax/editlistusers/" + field,
|
||||
url: getPath() + "/ajax/editlistusers/" + field,
|
||||
data: {"pk": result, "value": element.value},
|
||||
error: function (data) {
|
||||
handleListServerResponse([{type:"danger", message:data.responseText}])
|
||||
@@ -883,12 +1259,35 @@ function selectHeader(element, field) {
|
||||
}
|
||||
}
|
||||
|
||||
function bookCheckboxHeader(CheckboxState, text, field_index) {
|
||||
confirmDialog(text, "GeneralChangeModal", 0, function() {
|
||||
var result = $('#books-table').bootstrapTable('getSelections').map(a => a.id);
|
||||
$.ajax({
|
||||
method: "post",
|
||||
url: getPath() + "/ajax/editbooks/" + field_index,
|
||||
data: JSON.stringify({"pk": result, "field_index": field_index, "value": CheckboxState, multi: "True"}),
|
||||
contentType: "application/json; charset=utf-8",
|
||||
dataType: "json",
|
||||
error: function (data) {
|
||||
handleListServerResponse([{type:"danger", message:data.responseText}])
|
||||
},
|
||||
success: function (data) {
|
||||
handleListServerResponse (data, true)
|
||||
},
|
||||
});
|
||||
},function() {
|
||||
$("input:radio.check_head:checked").each(function() {
|
||||
$(this).prop('checked', false);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
function checkboxHeader(CheckboxState, field, field_index) {
|
||||
confirmDialog(field, "GeneralChangeModal", 0, function() {
|
||||
var result = $('#user-table').bootstrapTable('getSelections').map(a => a.id);
|
||||
$.ajax({
|
||||
method: "post",
|
||||
url: window.location.pathname + "/../../ajax/editlistusers/" + field,
|
||||
url: getPath() + "/ajax/editlistusers/" + field,
|
||||
data: {"pk": result, "field_index": field_index, "value": CheckboxState},
|
||||
error: function (data) {
|
||||
handleListServerResponse([{type:"danger", message:data.responseText}])
|
||||
@@ -904,7 +1303,7 @@ function checkboxHeader(CheckboxState, field, field_index) {
|
||||
});
|
||||
}
|
||||
|
||||
function deleteUser(a,id){
|
||||
function deleteUser(a, id){
|
||||
confirmDialog(
|
||||
"btndeluser",
|
||||
"GeneralDeleteModal",
|
||||
@@ -912,8 +1311,10 @@ function deleteUser(a,id){
|
||||
function() {
|
||||
$.ajax({
|
||||
method:"post",
|
||||
url: window.location.pathname + "/../../ajax/deleteuser",
|
||||
data: {"userid":id},
|
||||
url: getPath() + "/ajax/deleteuser",
|
||||
contentType: "application/json; charset=utf-8",
|
||||
dataType: "json",
|
||||
data: JSON.stringify({"userid": [id]}),
|
||||
success: function (data) {
|
||||
userId = parseInt(id, 10);
|
||||
selections = selections.filter(item => item !== userId);
|
||||
@@ -940,8 +1341,10 @@ function storeLocation() {
|
||||
function user_handle (userId) {
|
||||
$.ajax({
|
||||
method:"post",
|
||||
url: window.location.pathname + "/../../ajax/deleteuser",
|
||||
data: {"userid":userId}
|
||||
contentType: "application/json; charset=utf-8",
|
||||
dataType: "json",
|
||||
data: JSON.stringify({"userid": [userId]}),
|
||||
url: getPath() + "/ajax/deleteuser",
|
||||
});
|
||||
$("#user-table").bootstrapTable("refresh");
|
||||
}
|
||||
@@ -949,6 +1352,5 @@ function user_handle (userId) {
|
||||
function shorten_html(value, response) {
|
||||
if(value) {
|
||||
$(this).html("[...]");
|
||||
// value.split('\n').slice(0, 2).join("") +
|
||||
}
|
||||
}
|
||||
|
@@ -19,6 +19,25 @@
|
||||
{% if sort %}data-sortable="true" {% endif %}
|
||||
data-visible="{{visiblility.get(parameter)}}"
|
||||
data-formatter="bookCheckboxFormatter">
|
||||
{% if parameter == "is_archived" %}
|
||||
<div class="form-check">
|
||||
<div>
|
||||
<input type="radio" class="check_head" data-set="True" data-val="{{ parameter }}" name="options_archive_selected_books" id="false_archive_selected_books" data-name="archive_books" disabled>{{_('Archive selected books')}}
|
||||
</div>
|
||||
<div>
|
||||
<input type="radio" class="check_head" data-set="False" data-val="{{ parameter }}" name="options_unarchive_selected_books" id="true_archive_selected_books" data-name="archive_books" disabled>{{_('Unarchive selected books')}}
|
||||
</div>
|
||||
</div>
|
||||
{% elif parameter == "read_status" %}
|
||||
<div class="form-check">
|
||||
<div>
|
||||
<input type="radio" class="check_head" data-set="True" data-val="{{ parameter }}" name="options_read_selected_books" id="false_read_selected_books" data-name="read_books" disabled>{{_('Mark selected books as read')}}
|
||||
</div>
|
||||
<div>
|
||||
<input type="radio" class="check_head" data-set="False" data-val="{{ parameter }}" name="options_unread_selected_books" id="true_read_selected_books" data-name="read_books" disabled>{{_('Mark selected books as unread')}}</div>
|
||||
</div>
|
||||
</div>
|
||||
{% endif %}
|
||||
{{show_text}}
|
||||
</th>
|
||||
{%- endmacro %}
|
||||
@@ -34,8 +53,15 @@
|
||||
<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">{{_('Merge selected books')}}</div>
|
||||
<div class="btn btn-default disabled" id="delete_selection" aria-disabled="true">{{_('Remove Selections')}}</div>
|
||||
<div class="btn btn-default disabled" id="merge_books" aria-disabled="true">
|
||||
{{_('Merge selected books')}}
|
||||
</div>
|
||||
<div class="btn btn-default disabled mass_selection" id="book_delete_selection" aria-disabled="true">
|
||||
{{_('Clear selections')}}
|
||||
</div>
|
||||
<div class="btn btn-default disabled" id="edit_selected_books" aria-disabled="true">
|
||||
{{_('Edit selected books')}}
|
||||
</div>
|
||||
</div>
|
||||
<div class="row form-group">
|
||||
<div class="btn btn-default disabled" id="table_xchange" ><span class="glyphicon glyphicon-arrow-up"></span><span class="glyphicon glyphicon-arrow-down"></span>{{_('Exchange author and title')}}</div>
|
||||
@@ -57,7 +83,7 @@
|
||||
<thead>
|
||||
<tr>
|
||||
{% if current_user.role_edit() %}
|
||||
<th data-field="state" data-checkbox="true" data-sortable="true"></th>
|
||||
<th data-field="state" data-checkbox="true" data-visible="true" data-sortable="true"></th>
|
||||
{% endif %}
|
||||
<th data-field="id" id="id" data-visible="false" data-switchable="false"></th>
|
||||
{{ text_table_row('title', _('Enter Title'),_('Title'), true, true) }}
|
||||
@@ -97,14 +123,23 @@
|
||||
{% endif %}
|
||||
{% endfor %}
|
||||
{% if current_user.role_delete_books() and current_user.role_edit()%}
|
||||
<th data-align="right" data-formatter="EbookActions" data-switchable="false">{{_('Delete')}}</th>
|
||||
<th data-align="right" data-formatter="EbookActions" data-switchable="false">
|
||||
<div><div class="btn btn-default button_head disabled" aria-disabled="true">
|
||||
{{_('Delete selected books')}}
|
||||
</div></div>
|
||||
<br>
|
||||
{{_('Delete')}}
|
||||
</th>
|
||||
{% endif %}
|
||||
</tr>
|
||||
</thead>
|
||||
</table>
|
||||
|
||||
{% endblock %}
|
||||
{% block modal %}
|
||||
{{ delete_book(current_user.role_delete_books()) }}
|
||||
{{ delete_confirm_modal() }}
|
||||
{{ change_confirm_modal() }}
|
||||
{% if current_user.role_edit() %}
|
||||
<div class="modal fade" id="mergeModal" role="dialog" aria-labelledby="metaMergeLabel">
|
||||
<div class="modal-dialog">
|
||||
@@ -123,15 +158,170 @@
|
||||
<div class="text-left" id="merge_to"></div>
|
||||
</div>
|
||||
<div class="modal-footer">
|
||||
<input id="merge_confirm" type="button" class="btn btn-danger" value="{{_('Merge')}}" name="merge_confirm" id="merge_confirm" data-dismiss="modal">
|
||||
<input id="merge_confirm" type="button" class="btn btn-danger" value="{{_('Merge')}}" name="merge_confirm" data-dismiss="modal">
|
||||
<button id="merge_abort" type="button" class="btn btn-default" data-dismiss="modal">{{_('Cancel')}}</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{% endif %}
|
||||
|
||||
<!--div class="modal fade" id="delete_selected_modal" role="dialog" aria-labelledby="metaDeleteSelectedLabel">
|
||||
<div class="modal-dialog">
|
||||
<div class="modal-content">
|
||||
<div class="modal-header bg-danger text-center">
|
||||
<span>{{_('Are you really sure?')}}</span>
|
||||
</div>
|
||||
<div class="modal-body">
|
||||
<p></p>
|
||||
<div class="text-left">{{_('The following books will be deleted:')}}</div>
|
||||
<p></p>
|
||||
<div class="text-left" id="display-delete-selected-books"></div>
|
||||
<div class="modal-footer">
|
||||
<input id="delete_selected_confirm" type="button" class="btn btn-danger" value="{{_('Delete')}}" name="delete_selected_confirm" data-dismiss="modal">
|
||||
<button id="delete_selected_abort" type="button" class="btn btn-default" data-dismiss="modal">{{_('Cancel')}}</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div-->
|
||||
|
||||
<div class="modal fade" id="archive_selected_modal" role="dialog" aria-labelledby="metaArchiveSelectedLabel">
|
||||
<div class="modal-dialog">
|
||||
<div class="modal-content">
|
||||
<div class="modal-header bg-danger text-center">
|
||||
<span>{{_('Are you really sure?')}}</span>
|
||||
</div>
|
||||
<div class="modal-body">
|
||||
<p></p>
|
||||
<div class="text-left">{{_('The following books will be archived:')}}</div>
|
||||
<p></p>
|
||||
<div class="text-left" id="display-archive-selected-books"></div>
|
||||
<div class="modal-footer">
|
||||
<input id="archive_selected_confirm" type="button" class="btn btn-danger" value="{{_('Archive')}}" name="archive_selected_confirm" data-dismiss="modal">
|
||||
<button id="archive_selected_abort" type="button" class="btn btn-default" data-dismiss="modal">{{_('Cancel')}}</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="modal fade" id="unarchive_selected_modal" role="dialog" aria-labelledby="metaUnArchiveSelectedLabel">
|
||||
<div class="modal-dialog">
|
||||
<div class="modal-content">
|
||||
<div class="modal-header bg-danger text-center">
|
||||
<span>{{_('Are you really sure?')}}</span>
|
||||
</div>
|
||||
<div class="modal-body">
|
||||
<p></p>
|
||||
<div class="text-left">{{_('The following books will be unarchived:')}}</div>
|
||||
<p></p>
|
||||
<div class="text-left" id="display-unarchive-selected-books"></div>
|
||||
<div class="modal-footer">
|
||||
<input id="unarchive_selected_confirm" type="button" class="btn btn-danger" value="{{_('Unarchive')}}" name="unarchive_selected_confirm" data-dismiss="modal">
|
||||
<button id="unarchive_selected_abort" type="button" class="btn btn-default" data-dismiss="modal">{{_('Cancel')}}</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="modal fade" id="read_selected_modal" role="dialog" aria-labelledby="metaReadSelectedLabel">
|
||||
<div class="modal-dialog">
|
||||
<div class="modal-content">
|
||||
<div class="modal-header bg-danger text-center">
|
||||
<span>{{_('Are you really sure?')}}</span>
|
||||
</div>
|
||||
<div class="modal-body">
|
||||
<p></p>
|
||||
<div class="text-left">{{_('The following books will be marked read:')}}</div>
|
||||
<p></p>
|
||||
<div class="text-left" id="display-read-selected-books"></div>
|
||||
<div class="modal-footer">
|
||||
<input id="read_selected_confirm" type="button" class="btn btn-danger" value="{{_('Mark as read')}}" name="read_selected_confirm" data-dismiss="modal">
|
||||
<button id="read_selected_abort" type="button" class="btn btn-default" data-dismiss="modal">{{_('Cancel')}}</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="modal fade" id="unread_selected_modal" role="dialog" aria-labelledby="metaReadSelectedLabel">
|
||||
<div class="modal-dialog">
|
||||
<div class="modal-content">
|
||||
<div class="modal-header bg-danger text-center">
|
||||
<span>{{_('Are you really sure?')}}</span>
|
||||
</div>
|
||||
<div class="modal-body">
|
||||
<p></p>
|
||||
<div class="text-left">{{_('The following books will be marked unread:')}}</div>
|
||||
<p></p>
|
||||
<div class="text-left" id="display-unread-selected-books"></div>
|
||||
<div class="modal-footer">
|
||||
<input id="unread_selected_confirm" type="button" class="btn btn-danger" value="{{_('Mark as unread')}}" name="unread_selected_confirm" data-dismiss="modal">
|
||||
<button id="read_selected_abort" type="button" class="btn btn-default" data-dismiss="modal">{{_('Cancel')}}</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="modal fade" id="edit_selected_modal" role="dialog" aria-labelledby="metaEditSelectedLabel">
|
||||
<div class="modal-dialog">
|
||||
<div class="modal-content">
|
||||
<div class="modal-header bg-info text-center">
|
||||
<span>{{_('Edit Metadata')}}</span>
|
||||
</div>
|
||||
<div class="modal-body">
|
||||
<div class="text-left">{{_('Edit the fields you want changed. Blank fields will be ignored:')}}</div>
|
||||
<br>
|
||||
Title:
|
||||
<input class="form-control" id="title_input">
|
||||
<p></p>
|
||||
|
||||
Title Sort:
|
||||
<input class="form-control" id="title_sort_input">
|
||||
<p></p>
|
||||
|
||||
Author Sort:
|
||||
<input class="form-control" id="author_sort_input">
|
||||
<p></p>
|
||||
|
||||
Authors:
|
||||
<input class="form-control" id="authors_input">
|
||||
<p></p>
|
||||
|
||||
Categories:
|
||||
<input class="form-control" id="categories_input">
|
||||
<p></p>
|
||||
|
||||
Series:
|
||||
<input class="form-control" id="series_input">
|
||||
<p></p>
|
||||
|
||||
Languages:
|
||||
<input class="form-control" id="languages_input">
|
||||
<p></p>
|
||||
|
||||
Publishers:
|
||||
<input class="form-control" id="publishers_input">
|
||||
<p></p>
|
||||
|
||||
Comments:
|
||||
<input class="form-control" id="comments_input">
|
||||
<p></p>
|
||||
</div>
|
||||
|
||||
<div class="modal-footer">
|
||||
<input id="edit_selected_confirm" type="button" class="btn btn-danger" value="{{_('Edit')}}" name="edit_selected_confirm" id="edit_selected_confirm" data-dismiss="modal">
|
||||
<button id="edit_selected_abort" type="button" class="btn btn-default" data-dismiss="modal">{{_('Cancel')}}</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{% endif %}
|
||||
{% 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-locale-all.min.js') }}"></script>
|
||||
|
@@ -53,7 +53,7 @@
|
||||
data-visible="{{element.get(array_field)}}"
|
||||
data-column="{{value.get(array_field)}}"
|
||||
data-formatter="checkboxFormatter">
|
||||
<div class="form-check">
|
||||
<div class="form-check">
|
||||
<div>
|
||||
<input type="radio" class="check_head" data-set="false" data-val="{{value.get(array_field)}}" name="options_{{array_field}}" id="false_{{array_field}}" data-name="{{parameter}}" disabled>{{_('Deny')}}
|
||||
</div>
|
||||
@@ -121,7 +121,7 @@
|
||||
<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" id="user_delete_selection" aria-disabled="true">{{_('Remove Selections')}}</div>
|
||||
<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"
|
||||
|
@@ -1,10 +1,10 @@
|
||||
# GDrive Integration
|
||||
google-api-python-client>=2.73.00,<2.200.0
|
||||
gevent>20.6.0,<24.12.0
|
||||
greenlet>=0.4.17,<3.2.0
|
||||
greenlet>=0.4.17,<3.3.0
|
||||
httplib2>=0.9.2,<0.23.0
|
||||
oauth2client>=4.0.0,<4.1.4
|
||||
uritemplate>=3.0.0,<4.2.0
|
||||
uritemplate>=3.0.0,<4.3.0
|
||||
pyasn1-modules>=0.0.8,<0.7.0
|
||||
pyasn1>=0.1.9,<0.7.0
|
||||
PyDrive2>=1.15.0,<1.22.0
|
||||
|
@@ -71,10 +71,10 @@ content-type = "text/markdown"
|
||||
gdrive = [
|
||||
"google-api-python-client>=1.7.11,<2.200.0",
|
||||
"gevent>20.6.0,<24.12.0",
|
||||
"greenlet>=0.4.17,<3.2.0",
|
||||
"greenlet>=0.4.17,<3.3.0",
|
||||
"httplib2>=0.9.2,<0.23.0",
|
||||
"oauth2client>=4.0.0,<4.1.4",
|
||||
"uritemplate>=3.0.0,<4.2.0",
|
||||
"uritemplate>=3.0.0,<4.3.0",
|
||||
"pyasn1-modules>=0.0.8,<0.7.0",
|
||||
"pyasn1>=0.1.9,<0.7.0",
|
||||
"PyDrive2>=1.3.1,<1.22.0",
|
||||
|
File diff suppressed because it is too large
Load Diff
Reference in New Issue
Block a user