mirror of
https://github.com/janeczku/calibre-web
synced 2024-11-15 22:34:56 +00:00
Merge pull request #1 from apollo1220/feature/custom-pages
Feature/custom pages
This commit is contained in:
commit
f20b0f3064
2
.gitignore
vendored
2
.gitignore
vendored
@ -35,3 +35,5 @@ gdrive_credentials
|
||||
client_secrets.json
|
||||
gmail.json
|
||||
/.key
|
||||
|
||||
pages/
|
||||
|
@ -1705,7 +1705,7 @@ def _db_configuration_update_helper():
|
||||
return _db_configuration_result('{}'.format(ex), gdrive_error)
|
||||
|
||||
if db_change or not db_valid or not config.db_configured \
|
||||
or config.config_calibre_dir != to_save["config_calibre_dir"]:
|
||||
or config.config_calibre_dir != to_save["config_calibre_dir"]:
|
||||
if not os.path.exists(metadata_db) or not to_save['config_calibre_dir']:
|
||||
return _db_configuration_result(_('DB Location is not Valid, Please Enter Correct Path'), gdrive_error)
|
||||
else:
|
||||
@ -1728,6 +1728,9 @@ def _db_configuration_update_helper():
|
||||
calibre_db.update_config(config)
|
||||
if not os.access(os.path.join(config.config_calibre_dir, "metadata.db"), os.W_OK):
|
||||
flash(_("DB is not Writeable"), category="warning")
|
||||
_config_string(to_save, "config_calibre_split_dir")
|
||||
config.config_calibre_split = to_save.get('config_calibre_split', 0) == "on"
|
||||
calibre_db.update_config(config)
|
||||
config.save()
|
||||
return _db_configuration_result(None, gdrive_error)
|
||||
|
||||
|
@ -69,6 +69,8 @@ class _Settings(_Base):
|
||||
|
||||
config_calibre_dir = Column(String)
|
||||
config_calibre_uuid = Column(String)
|
||||
config_calibre_split = Column(Boolean, default=False)
|
||||
config_calibre_split_dir = Column(String)
|
||||
config_port = Column(Integer, default=constants.DEFAULT_PORT)
|
||||
config_external_port = Column(Integer, default=constants.DEFAULT_PORT)
|
||||
config_certfile = Column(String)
|
||||
@ -389,6 +391,9 @@ class ConfigSQL(object):
|
||||
self.db_configured = False
|
||||
self.save()
|
||||
|
||||
def get_book_path(self):
|
||||
return self.config_calibre_split_dir if self.config_calibre_split_dir else self.config_calibre_dir
|
||||
|
||||
def store_calibre_uuid(self, calibre_db, Library_table):
|
||||
try:
|
||||
calibre_uuid = calibre_db.session.query(Library_table).one_or_none()
|
||||
|
@ -135,7 +135,7 @@ def edit_book(book_id):
|
||||
edited_books_id = book.id
|
||||
modify_date = True
|
||||
title_author_error = helper.update_dir_structure(edited_books_id,
|
||||
config.config_calibre_dir,
|
||||
config.get_book_path(),
|
||||
input_authors[0],
|
||||
renamed_author=renamed)
|
||||
if title_author_error:
|
||||
@ -280,7 +280,7 @@ def upload():
|
||||
meta.extension.lower())
|
||||
else:
|
||||
error = helper.update_dir_structure(book_id,
|
||||
config.config_calibre_dir,
|
||||
config.get_book_path(),
|
||||
input_authors[0],
|
||||
meta.file_path,
|
||||
title_dir + meta.extension.lower(),
|
||||
@ -330,7 +330,7 @@ def convert_bookformat(book_id):
|
||||
return redirect(url_for('edit-book.show_edit_book', book_id=book_id))
|
||||
|
||||
log.info('converting: book id: %s from: %s to: %s', book_id, book_format_from, book_format_to)
|
||||
rtn = helper.convert_book_format(book_id, config.config_calibre_dir, book_format_from.upper(),
|
||||
rtn = helper.convert_book_format(book_id, config.get_book_path(), book_format_from.upper(),
|
||||
book_format_to.upper(), current_user.name)
|
||||
|
||||
if rtn is None:
|
||||
@ -400,7 +400,7 @@ def edit_list_book(param):
|
||||
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.config_calibre_dir)
|
||||
rename_error = helper.update_dir_structure(book.id, config.get_book_path())
|
||||
if not rename_error:
|
||||
ret = Response(json.dumps({'success': True, 'newValue': book.title}),
|
||||
mimetype='application/json')
|
||||
@ -418,7 +418,7 @@ def edit_list_book(param):
|
||||
mimetype='application/json')
|
||||
elif param == 'authors':
|
||||
input_authors, __, renamed = handle_author_on_edit(book, vals['value'], vals.get('checkA', None) == "true")
|
||||
rename_error = helper.update_dir_structure(book.id, config.config_calibre_dir, input_authors[0],
|
||||
rename_error = helper.update_dir_structure(book.id, config.get_book_path(), input_authors[0],
|
||||
renamed_author=renamed)
|
||||
if not rename_error:
|
||||
ret = Response(json.dumps({
|
||||
@ -522,10 +522,10 @@ def merge_list_book():
|
||||
for element in from_book.data:
|
||||
if element.format not in to_file:
|
||||
# create new data entry with: book_id, book_format, uncompressed_size, name
|
||||
filepath_new = os.path.normpath(os.path.join(config.config_calibre_dir,
|
||||
filepath_new = os.path.normpath(os.path.join(config.get_book_path(),
|
||||
to_book.path,
|
||||
to_name + "." + element.format.lower()))
|
||||
filepath_old = os.path.normpath(os.path.join(config.config_calibre_dir,
|
||||
filepath_old = os.path.normpath(os.path.join(config.get_book_path(),
|
||||
from_book.path,
|
||||
element.name + "." + element.format.lower()))
|
||||
copyfile(filepath_old, filepath_new)
|
||||
@ -565,7 +565,7 @@ def table_xchange_author_title():
|
||||
|
||||
if edited_books_id:
|
||||
# toDo: Handle error
|
||||
edit_error = helper.update_dir_structure(edited_books_id, config.config_calibre_dir, input_authors[0],
|
||||
edit_error = helper.update_dir_structure(edited_books_id, config.get_book_path(), input_authors[0],
|
||||
renamed_author=renamed)
|
||||
if modify_date:
|
||||
book.last_modified = datetime.utcnow()
|
||||
@ -762,7 +762,7 @@ def move_coverfile(meta, db_book):
|
||||
cover_file = meta.cover
|
||||
else:
|
||||
cover_file = os.path.join(constants.STATIC_DIR, 'generic_cover.jpg')
|
||||
new_cover_path = os.path.join(config.config_calibre_dir, db_book.path)
|
||||
new_cover_path = os.path.join(config.get_book_path(), db_book.path)
|
||||
try:
|
||||
os.makedirs(new_cover_path, exist_ok=True)
|
||||
copyfile(cover_file, os.path.join(new_cover_path, "cover.jpg"))
|
||||
@ -848,7 +848,7 @@ def delete_book_from_table(book_id, book_format, json_response):
|
||||
book = calibre_db.get_book(book_id)
|
||||
if book:
|
||||
try:
|
||||
result, error = helper.delete_book(book, config.config_calibre_dir, book_format=book_format.upper())
|
||||
result, error = helper.delete_book(book, config.get_book_path(), book_format=book_format.upper())
|
||||
if not result:
|
||||
if json_response:
|
||||
return json.dumps([{"location": url_for("edit-book.show_edit_book", book_id=book_id),
|
||||
@ -1184,7 +1184,7 @@ def upload_single_file(file_request, book, book_id):
|
||||
return False
|
||||
|
||||
file_name = book.path.rsplit('/', 1)[-1]
|
||||
filepath = os.path.normpath(os.path.join(config.config_calibre_dir, book.path))
|
||||
filepath = os.path.normpath(os.path.join(config.get_book_path(), book.path))
|
||||
saved_filename = os.path.join(filepath, file_name + '.' + file_ext)
|
||||
|
||||
# check if file path exists, otherwise create it, copy file to calibre path and delete temp file
|
||||
|
108
cps/editpage.py
Normal file
108
cps/editpage.py
Normal file
@ -0,0 +1,108 @@
|
||||
import os
|
||||
import flask
|
||||
from flask import Blueprint, Flask, abort, request
|
||||
from functools import wraps
|
||||
from pathlib import Path
|
||||
from flask_login import current_user, login_required
|
||||
from werkzeug.exceptions import NotFound
|
||||
|
||||
from .render_template import render_title_template
|
||||
from . import logger, config, ub
|
||||
from .constants import CONFIG_DIR as _CONFIG_DIR
|
||||
|
||||
log = logger.create()
|
||||
|
||||
editpage = Blueprint('editpage', __name__)
|
||||
|
||||
def edit_required(f):
|
||||
@wraps(f)
|
||||
def inner(*args, **kwargs):
|
||||
if current_user.role_edit() or current_user.role_admin():
|
||||
return f(*args, **kwargs)
|
||||
abort(403)
|
||||
|
||||
return inner
|
||||
|
||||
def _get_checkbox(dictionary, field, default):
|
||||
new_value = dictionary.get(field, default)
|
||||
convertor = lambda y: y == "on"
|
||||
new_value = convertor(new_value)
|
||||
|
||||
return new_value
|
||||
|
||||
@editpage.route("/admin/page/<string:file>", methods=["GET", "POST"])
|
||||
@login_required
|
||||
@edit_required
|
||||
def edit_page(file):
|
||||
doc = ""
|
||||
title = ""
|
||||
name = ""
|
||||
icon = "file"
|
||||
is_enabled = True
|
||||
order = 0
|
||||
position = "0"
|
||||
|
||||
page = ub.session.query(ub.Page).filter(ub.Page.id == file).first()
|
||||
|
||||
try:
|
||||
title = page.title
|
||||
name = page.name
|
||||
icon = page.icon
|
||||
is_enabled = page.is_enabled
|
||||
order = page.order
|
||||
position = page.position
|
||||
except AttributeError:
|
||||
if file != "new":
|
||||
abort(404)
|
||||
|
||||
if request.method == "POST":
|
||||
to_save = request.form.to_dict()
|
||||
title = to_save.get("title", "").strip()
|
||||
name = to_save.get("name", "").strip()
|
||||
icon = to_save.get("icon", "").strip()
|
||||
position = to_save.get("position", "").strip()
|
||||
order = int(to_save.get("order", 0))
|
||||
content = to_save.get("content", "").strip()
|
||||
is_enabled = _get_checkbox(to_save, "is_enabled", True)
|
||||
|
||||
if page:
|
||||
page.title = title
|
||||
page.name = name
|
||||
page.icon = icon
|
||||
page.is_enabled = is_enabled
|
||||
page.order = order
|
||||
page.position = position
|
||||
ub.session_commit("Page edited {}".format(file))
|
||||
else:
|
||||
new_page = ub.Page(title=title, name=name, icon=icon, is_enabled=is_enabled, order=order, position=position)
|
||||
ub.session.add(new_page)
|
||||
ub.session_commit("Page added {}".format(file))
|
||||
|
||||
if (file == "new"):
|
||||
file = str(new_page.id)
|
||||
dir_config_path = os.path.join(_CONFIG_DIR, 'pages')
|
||||
file_name = Path(name + '.md')
|
||||
file_path = dir_config_path / file_name
|
||||
os.makedirs(dir_config_path, exist_ok=True)
|
||||
|
||||
try:
|
||||
with open(file_path, 'w') as f:
|
||||
f.write(content)
|
||||
f.close()
|
||||
except Exception as ex:
|
||||
log.error(ex)
|
||||
|
||||
if file != "new":
|
||||
try:
|
||||
dir_config_path = Path(_CONFIG_DIR) / 'pages'
|
||||
file_path = dir_config_path / f"{name}.md"
|
||||
|
||||
with open(file_path, 'r') as f:
|
||||
doc = f.read()
|
||||
except NotFound:
|
||||
log.error("'%s' was accessed but file doesn't exists." % file)
|
||||
|
||||
else:
|
||||
doc = "## New file\n\nInformation"
|
||||
|
||||
return render_title_template("edit_page.html", title=title, name=name, icon=icon, is_enabled=is_enabled, order=order, position=position, content=doc, file=file)
|
@ -48,7 +48,8 @@ def get_epub_layout(book, book_data):
|
||||
'n': 'urn:oasis:names:tc:opendocument:xmlns:container',
|
||||
'pkg': 'http://www.idpf.org/2007/opf',
|
||||
}
|
||||
file_path = os.path.normpath(os.path.join(config.config_calibre_dir, book.path, book_data.name + "." + book_data.format.lower()))
|
||||
file_path = os.path.normpath(os.path.join(config.get_book_path(),
|
||||
book.path, book_data.name + "." + book_data.format.lower()))
|
||||
|
||||
try:
|
||||
epubZip = zipfile.ZipFile(file_path)
|
||||
|
@ -781,7 +781,7 @@ def get_book_cover_internal(book, resolution=None):
|
||||
|
||||
# Send the book cover from the Calibre directory
|
||||
else:
|
||||
cover_file_path = os.path.join(config.config_calibre_dir, book.path)
|
||||
cover_file_path = os.path.join(config.get_book_path(), book.path)
|
||||
if os.path.isfile(os.path.join(cover_file_path, "cover.jpg")):
|
||||
return send_from_directory(cover_file_path, "cover.jpg")
|
||||
else:
|
||||
@ -934,7 +934,7 @@ def save_cover(img, book_path):
|
||||
else:
|
||||
return False, message
|
||||
else:
|
||||
return save_cover_from_filestorage(os.path.join(config.config_calibre_dir, book_path), "cover.jpg", img)
|
||||
return save_cover_from_filestorage(os.path.join(config.get_book_path(), book_path), "cover.jpg", img)
|
||||
|
||||
|
||||
def do_download_file(book, book_format, client, data, headers):
|
||||
@ -947,7 +947,7 @@ def do_download_file(book, book_format, client, data, headers):
|
||||
else:
|
||||
abort(404)
|
||||
else:
|
||||
filename = os.path.join(config.config_calibre_dir, book.path)
|
||||
filename = os.path.join(config.get_book_path(), book.path)
|
||||
if not os.path.isfile(os.path.join(filename, data.name + "." + book_format)):
|
||||
# ToDo: improve error handling
|
||||
log.error('File not found: %s', os.path.join(filename, data.name + "." + book_format))
|
||||
|
@ -205,7 +205,7 @@ def HandleSyncRequest():
|
||||
for book in books:
|
||||
formats = [data.format for data in book.Books.data]
|
||||
if 'KEPUB' not in formats and config.config_kepubifypath and 'EPUB' in formats:
|
||||
helper.convert_book_format(book.Books.id, config.config_calibre_dir, 'EPUB', 'KEPUB', current_user.name)
|
||||
helper.convert_book_format(book.Books.id, config.get_book_path(), 'EPUB', 'KEPUB', current_user.name)
|
||||
|
||||
kobo_reading_state = get_or_create_reading_state(book.Books.id)
|
||||
entitlement = {
|
||||
|
28
cps/listpages.py
Normal file
28
cps/listpages.py
Normal file
@ -0,0 +1,28 @@
|
||||
import flask
|
||||
import json
|
||||
from flask import Blueprint, jsonify, make_response,abort
|
||||
from flask_login import current_user, login_required
|
||||
from functools import wraps
|
||||
from flask_babel import gettext as _
|
||||
|
||||
from .render_template import render_title_template
|
||||
from . import ub, db
|
||||
|
||||
listpages = Blueprint('listpages', __name__)
|
||||
|
||||
def edit_required(f):
|
||||
@wraps(f)
|
||||
def inner(*args, **kwargs):
|
||||
if current_user.role_edit() or current_user.role_admin():
|
||||
return f(*args, **kwargs)
|
||||
abort(403)
|
||||
|
||||
return inner
|
||||
|
||||
@listpages.route("/admin/pages/", methods=["GET"])
|
||||
@login_required
|
||||
@edit_required
|
||||
def show_list():
|
||||
pages = ub.session.query(ub.Page).order_by(ub.Page.position).order_by(ub.Page.order).all()
|
||||
|
||||
return render_title_template('list_pages.html', title=_("Pages List"), page="book_table", pages=pages)
|
@ -36,6 +36,9 @@ def main():
|
||||
from .gdrive import gdrive
|
||||
from .editbooks import editbook
|
||||
from .about import about
|
||||
from .page import page
|
||||
from .listpages import listpages
|
||||
from .editpage import editpage
|
||||
from .search import search
|
||||
from .search_metadata import meta
|
||||
from .shelf import shelf
|
||||
@ -65,6 +68,9 @@ def main():
|
||||
limiter.limit("3/minute",key_func=request_username)(opds)
|
||||
app.register_blueprint(jinjia)
|
||||
app.register_blueprint(about)
|
||||
app.register_blueprint(page)
|
||||
app.register_blueprint(listpages)
|
||||
app.register_blueprint(editpage)
|
||||
app.register_blueprint(shelf)
|
||||
app.register_blueprint(admi)
|
||||
app.register_blueprint(remotelogin)
|
||||
|
@ -502,7 +502,7 @@ def render_element_index(database_column, linked_table, folder):
|
||||
entries = entries.join(linked_table).join(db.Books)
|
||||
entries = entries.filter(calibre_db.common_filters()).group_by(func.upper(func.substr(database_column, 1, 1))).all()
|
||||
elements = []
|
||||
if off == 0:
|
||||
if off == 0 and entries:
|
||||
elements.append({'id': "00", 'name': _("All")})
|
||||
shift = 1
|
||||
for entry in entries[
|
||||
|
38
cps/page.py
Normal file
38
cps/page.py
Normal file
@ -0,0 +1,38 @@
|
||||
import os
|
||||
import flask
|
||||
import markdown
|
||||
from flask import abort
|
||||
from pathlib import Path
|
||||
from flask_babel import gettext as _
|
||||
from werkzeug.exceptions import NotFound
|
||||
|
||||
from . import logger, config, ub
|
||||
from .render_template import render_title_template
|
||||
from .constants import CONFIG_DIR as _CONFIG_DIR
|
||||
|
||||
page = flask.Blueprint('page', __name__)
|
||||
|
||||
log = logger.create()
|
||||
|
||||
@page.route('/page/<string:file>', methods=['GET'])
|
||||
def get_page(file):
|
||||
page = ub.session.query(ub.Page)\
|
||||
.filter(ub.Page.name == file)\
|
||||
.filter(ub.Page.is_enabled)\
|
||||
.first()
|
||||
|
||||
if not page:
|
||||
log.error(f"'{file}' was accessed but is not enabled or it's not in database.")
|
||||
abort(404)
|
||||
|
||||
try:
|
||||
dir_config_path = Path(_CONFIG_DIR) / 'pages'
|
||||
file_path = dir_config_path / f"{file}.md"
|
||||
with open(file_path, 'r') as f:
|
||||
temp_md = f.read()
|
||||
body = markdown.markdown(temp_md)
|
||||
|
||||
return render_title_template('page.html', body=body, title=page.title, page=page.name)
|
||||
except NotFound:
|
||||
log.error("'%s' was accessed but file doesn't exists." % file)
|
||||
abort(404)
|
@ -104,14 +104,24 @@ def get_sidebar_config(kwargs=None):
|
||||
g.shelves_access = ub.session.query(ub.Shelf).filter(
|
||||
or_(ub.Shelf.is_public == 1, ub.Shelf.user_id == current_user.id)).order_by(ub.Shelf.name).all()
|
||||
|
||||
return sidebar, simple
|
||||
top_pages = ub.session.query(ub.Page)\
|
||||
.filter(ub.Page.position == "1")\
|
||||
.filter(ub.Page.is_enabled)\
|
||||
.order_by(ub.Page.order)
|
||||
bottom_pages = ub.session.query(ub.Page)\
|
||||
.filter(ub.Page.position == "0")\
|
||||
.filter(ub.Page.is_enabled)\
|
||||
.order_by(ub.Page.order)
|
||||
|
||||
return sidebar, simple, top_pages, bottom_pages
|
||||
|
||||
|
||||
# Returns the template for rendering and includes the instance name
|
||||
def render_title_template(*args, **kwargs):
|
||||
sidebar, simple = get_sidebar_config(kwargs)
|
||||
sidebar, simple, top_pages, bottom_pages = get_sidebar_config(kwargs)
|
||||
try:
|
||||
return render_template(instance=config.config_calibre_web_title, sidebar=sidebar, simple=simple,
|
||||
top_pages=top_pages, bottom_pages=bottom_pages,
|
||||
accept=constants.EXTENSIONS_UPLOAD,
|
||||
*args, **kwargs)
|
||||
except PermissionError:
|
||||
|
@ -179,8 +179,9 @@ kthoom.ImageFile = function(file) {
|
||||
};
|
||||
|
||||
function updateDirectionButtons(){
|
||||
var left, right = 1;
|
||||
if (currentImage == 0 ) {
|
||||
var left = 1;
|
||||
var right = 1;
|
||||
if (currentImage <= 0 ) {
|
||||
if (settings.direction === 0) {
|
||||
left = 0;
|
||||
} else {
|
||||
|
6
cps/tasks/convert.py
Executable file → Normal file
6
cps/tasks/convert.py
Executable file → Normal file
@ -62,11 +62,11 @@ class TaskConvert(CalibreTask):
|
||||
df = gdriveutils.getFileFromEbooksFolder(cur_book.path,
|
||||
data.name + "." + self.settings['old_book_format'].lower())
|
||||
if df:
|
||||
datafile = os.path.join(config.config_calibre_dir,
|
||||
datafile = os.path.join(config.get_book_path(),
|
||||
cur_book.path,
|
||||
data.name + "." + self.settings['old_book_format'].lower())
|
||||
if not os.path.exists(os.path.join(config.config_calibre_dir, cur_book.path)):
|
||||
os.makedirs(os.path.join(config.config_calibre_dir, cur_book.path))
|
||||
if not os.path.exists(os.path.join(config.get_book_path(), cur_book.path)):
|
||||
os.makedirs(os.path.join(config.get_book_path(), cur_book.path))
|
||||
df.GetContentFile(datafile)
|
||||
worker_db.session.close()
|
||||
else:
|
||||
|
2
cps/tasks/mail.py
Executable file → Normal file
2
cps/tasks/mail.py
Executable file → Normal file
@ -239,7 +239,7 @@ class TaskEmail(CalibreTask):
|
||||
@classmethod
|
||||
def _get_attachment(cls, book_path, filename):
|
||||
"""Get file as MIMEBase message"""
|
||||
calibre_path = config.config_calibre_dir
|
||||
calibre_path = config.get_book_path()
|
||||
if config.config_use_google_drive:
|
||||
df = gdriveutils.getFileFromEbooksFolder(book_path, filename)
|
||||
if df:
|
||||
|
@ -114,7 +114,7 @@ class TaskBackupMetadata(CalibreTask):
|
||||
True)
|
||||
else:
|
||||
# ToDo: Handle book folder not found or not readable
|
||||
book_metadata_filepath = os.path.join(config.config_calibre_dir, book.path, 'metadata.opf')
|
||||
book_metadata_filepath = os.path.join(config.get_book_path(), book.path, 'metadata.opf')
|
||||
# prepare finalize everything and output
|
||||
doc = etree.ElementTree(package)
|
||||
try:
|
||||
|
@ -209,7 +209,7 @@ class TaskGenerateCoverThumbnails(CalibreTask):
|
||||
if stream is not None:
|
||||
stream.close()
|
||||
else:
|
||||
book_cover_filepath = os.path.join(config.config_calibre_dir, book.path, 'cover.jpg')
|
||||
book_cover_filepath = os.path.join(config.get_book_path(), book.path, 'cover.jpg')
|
||||
if not os.path.isfile(book_cover_filepath):
|
||||
raise Exception('Book cover file not found')
|
||||
|
||||
@ -404,7 +404,7 @@ class TaskGenerateSeriesThumbnails(CalibreTask):
|
||||
if stream is not None:
|
||||
stream.close()
|
||||
|
||||
book_cover_filepath = os.path.join(config.config_calibre_dir, book.path, 'cover.jpg')
|
||||
book_cover_filepath = os.path.join(config.get_book_path(), book.path, 'cover.jpg')
|
||||
if not os.path.isfile(book_cover_filepath):
|
||||
raise Exception('Book cover file not found')
|
||||
|
||||
|
@ -161,6 +161,7 @@
|
||||
<a class="btn btn-default" id="db_config" href="{{url_for('admin.db_configuration')}}">{{_('Edit Calibre Database Configuration')}}</a>
|
||||
<a class="btn btn-default" id="basic_config" href="{{url_for('admin.configuration')}}">{{_('Edit Basic Configuration')}}</a>
|
||||
<a class="btn btn-default" id="view_config" href="{{url_for('admin.view_configuration')}}">{{_('Edit UI Configuration')}}</a>
|
||||
<a class="btn btn-default" id="list_pages" href="{{url_for('listpages.show_list')}}">{{_('List Pages')}}</a>
|
||||
</div>
|
||||
</div>
|
||||
{% if feature_support['scheduler'] %}
|
||||
|
@ -16,6 +16,18 @@
|
||||
<button type="button" data-toggle="modal" id="calibre_modal_path" data-link="config_calibre_dir" data-filefilter="metadata.db" data-target="#fileModal" id="library_path" class="btn btn-default"><span class="glyphicon glyphicon-folder-open"></span></button>
|
||||
</span>
|
||||
</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" id="book_path" 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 %} >
|
||||
|
45
cps/templates/edit_page.html
Normal file
45
cps/templates/edit_page.html
Normal file
@ -0,0 +1,45 @@
|
||||
{% extends "layout.html" %}
|
||||
{% block body %}
|
||||
<div class="discover">
|
||||
<div><a class="session" href="{{url_for('listpages.show_list')}}">{{_('Back')}}</a></div>
|
||||
<h2>{{_('Edit page')}}</h2>
|
||||
<form role="form" class="col-md-10 col-lg-6" method="POST" action="{{ url_for('editpage.edit_page', file=file) }}"
|
||||
autocomplete="off">
|
||||
<input type="hidden" name="csrf_token" value="{{ csrf_token() }}">
|
||||
<div class="form-group">
|
||||
<label for="title">{{_('Title')}}</label>
|
||||
<input type="text" class="form-control" name="title" id="title" value="{{ title }}" required>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label for="name">{{_('Name')}}</label>
|
||||
<input type="text" class="form-control" name="name" id="name" value="{{ name }}" required>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label for="icon">{{_('Icon')}}</label>
|
||||
<input type="text" class="form-control" name="icon" id="icon" value="{{ icon }}" required>
|
||||
<a href="https://www.w3schools.com/bootstrap/bootstrap_ref_comp_glyphs.asp" target="_blank" rel="noopener">{{_('Icons list')}}</a>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label for="content">{{_('Content')}}</label>
|
||||
<textarea class="form-control" name="content" id="content" rows="15">{{ content }}</textarea>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label for="position">{{_('Position')}}</label>
|
||||
<select name="position" id="position" class="form-control">
|
||||
<option value="0" {% if position=="0" %}selected{% endif %}>{{ _("Sidebar Bottom") }}</option>
|
||||
<option value="1" {% if position=="1" %}selected{% endif %}>{{ _("Sidebar Top") }}</option>
|
||||
</select>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<input type="checkbox" id="is_enabled" name="is_enabled" {% if is_enabled %}checked{% endif %}>
|
||||
<label for="is_enabled">{{_('Enabled')}}</label>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label for="order">{{_('Order')}}</label>
|
||||
<input type="number" class="form-control" name="order" id="order" value="{{ order }}" required>
|
||||
</div>
|
||||
<button type="submit" name="submit" id="page_submit" class="btn btn-default">{{_('Save')}}</button>
|
||||
<a href="{{ url_for('admin.admin') }}" id="config_back" class="btn btn-default">{{_('Cancel')}}</a>
|
||||
</form>
|
||||
</div>
|
||||
{% endblock %}
|
@ -142,6 +142,9 @@
|
||||
<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">
|
||||
{% for element in top_pages %}
|
||||
<li id="nav_{{element['name']}}" {% if page == element['name'] %}class="active"{% endif %}><a href="{{url_for('page.get_page', file=element['name'])}}"><span class="glyphicon glyphicon-{{element['icon']}}"></span> {{element['title']}}</a></li>
|
||||
{% endfor %}
|
||||
<li class="nav-head hidden-xs">{{_('Browse')}}</li>
|
||||
{% for element in sidebar %}
|
||||
{% if current_user.check_visibility(element['visibility']) and element['public'] %}
|
||||
@ -157,6 +160,9 @@
|
||||
<li id="nav_createshelf" class="create-shelf"><a href="{{url_for('shelf.create_shelf')}}">{{_('Create a Shelf')}}</a></li>
|
||||
<li id="nav_about" {% if page == 'stat' %}class="active"{% endif %}><a href="{{url_for('about.stats')}}"><span class="glyphicon glyphicon-info-sign"></span> {{_('About')}}</a></li>
|
||||
{% endif %}
|
||||
{% for element in bottom_pages %}
|
||||
<li id="nav_{{element['name']}}" {% if page == element['name'] %}class="active"{% endif %}><a href="{{url_for('page.get_page', file=element['name'])}}"><span class="glyphicon glyphicon-{{element['icon']}}"></span> {{element['title']}}</a></li>
|
||||
{% endfor %}
|
||||
{% endif %}
|
||||
|
||||
</ul>
|
||||
|
52
cps/templates/list_pages.html
Normal file
52
cps/templates/list_pages.html
Normal file
@ -0,0 +1,52 @@
|
||||
{% extends "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">
|
||||
<link href="{{ url_for('static', filename='css/libs/bootstrap-select.min.css') }}" rel="stylesheet" >
|
||||
{% endblock %}
|
||||
{% block body %}
|
||||
<h2 class="{{page}}">{{_(title)}}</h2>
|
||||
<table class="table table-striped" id="table_user">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>{{_('Name')}}</th>
|
||||
<th>{{_('Title')}}</th>
|
||||
<th>{{_('Icon')}}</th>
|
||||
<th>{{_('Position')}}</th>
|
||||
<th>{{_('Enabled')}}</th>
|
||||
<th>{{_('Order')}}</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{% for page in pages %}
|
||||
<tr>
|
||||
<td><a class="session" href="{{url_for('editpage.edit_page', file=page.id)}}">{{page.name}}</a></td>
|
||||
<td>{{page.title}}</td>
|
||||
<td>{{page.icon}}</td>
|
||||
<td>{{_('bottom') if page.position == "0" else _('top')}}</td>
|
||||
<td>
|
||||
{% if page.is_enabled %}
|
||||
<span class="glyphicon glyphicon-ok"></span>
|
||||
{% else %}
|
||||
<span class="glyphicon glyphicon-remove"></span>
|
||||
{% endif %}
|
||||
</td>
|
||||
<td>{{page.order}}</td>
|
||||
</tr>
|
||||
{% endfor %}
|
||||
</tbody>
|
||||
</table>
|
||||
<a class="session" id="new_page" href="{{url_for('editpage.edit_page', file="new")}}">{{_('New Page')}}</a>
|
||||
{% 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>
|
||||
<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>
|
||||
{% if not current_user.locale == 'en' %}
|
||||
<script
|
||||
src="{{ url_for('static', filename='js/libs/bootstrap-table/locale/bootstrap-table-' + current_user.locale + '.min.js') }}"
|
||||
charset="UTF-8"></script>
|
||||
{% endif %}
|
||||
<script src="{{ url_for('static', filename='js/table.js') }}"></script>
|
||||
{% endblock %}
|
4
cps/templates/page.html
Normal file
4
cps/templates/page.html
Normal file
@ -0,0 +1,4 @@
|
||||
{% extends "layout.html" %}
|
||||
{% block body %}
|
||||
<div>{{body|safe}}</div>
|
||||
{% endblock %}
|
13
cps/ub.py
13
cps/ub.py
@ -538,6 +538,16 @@ class Thumbnail(Base):
|
||||
generated_at = Column(DateTime, default=lambda: datetime.datetime.utcnow())
|
||||
expiration = Column(DateTime, nullable=True)
|
||||
|
||||
class Page(Base):
|
||||
__tablename__ = 'page'
|
||||
|
||||
id = Column(Integer, primary_key=True)
|
||||
title = Column(String)
|
||||
name = Column(String)
|
||||
icon = Column(String)
|
||||
order = Column(Integer)
|
||||
position = Column(String)
|
||||
is_enabled = Column(Boolean, default=True)
|
||||
|
||||
# Add missing tables during migration of database
|
||||
def add_missing_tables(engine, _session):
|
||||
@ -561,7 +571,8 @@ def add_missing_tables(engine, _session):
|
||||
trans = conn.begin()
|
||||
conn.execute("insert into registration (domain, allow) values('%.%',1)")
|
||||
trans.commit()
|
||||
|
||||
if not engine.dialect.has_table(engine.connect(), "page"):
|
||||
Page.__table__.create(bind=engine)
|
||||
|
||||
# migrate all settings missing in registration table
|
||||
def migrate_registration_table(engine, _session):
|
||||
|
6
cps/web.py
Executable file → Normal file
6
cps/web.py
Executable file → Normal file
@ -1192,7 +1192,7 @@ def serve_book(book_id, book_format, anyname):
|
||||
if book_format.upper() == 'TXT':
|
||||
log.info('Serving book: %s', data.name)
|
||||
try:
|
||||
rawdata = open(os.path.join(config.config_calibre_dir, book.path, data.name + "." + book_format),
|
||||
rawdata = open(os.path.join(config.get_book_path(), book.path, data.name + "." + book_format),
|
||||
"rb").read()
|
||||
result = chardet.detect(rawdata)
|
||||
return make_response(
|
||||
@ -1202,7 +1202,7 @@ def serve_book(book_id, book_format, anyname):
|
||||
return "File Not Found"
|
||||
# enable byte range read of pdf
|
||||
response = make_response(
|
||||
send_from_directory(os.path.join(config.config_calibre_dir, book.path), data.name + "." + book_format))
|
||||
send_from_directory(os.path.join(config.get_book_path(), book.path), data.name + "." + book_format))
|
||||
if not range_header:
|
||||
log.info('Serving book: %s', data.name)
|
||||
response.headers['Accept-Ranges'] = 'bytes'
|
||||
@ -1226,7 +1226,7 @@ def send_to_ereader(book_id, book_format, convert):
|
||||
response = [{'type': "danger", 'message': _("Please configure the SMTP mail settings first...")}]
|
||||
return Response(json.dumps(response), mimetype='application/json')
|
||||
elif current_user.kindle_mail:
|
||||
result = send_mail(book_id, book_format, convert, current_user.kindle_mail, config.config_calibre_dir,
|
||||
result = send_mail(book_id, book_format, convert, current_user.kindle_mail, config.get_book_path(),
|
||||
current_user.name)
|
||||
if result is None:
|
||||
ub.update_download(book_id, int(current_user.id))
|
||||
|
@ -9,7 +9,7 @@ iso-639>=0.4.5,<0.5.0
|
||||
PyPDF>=3.0.0,<3.16.0
|
||||
pytz>=2016.10
|
||||
requests>=2.28.0,<2.32.0
|
||||
SQLAlchemy>=1.3.0,<2.0.0
|
||||
SQLAlchemy>=1.3.0,<2.1.0
|
||||
tornado>=6.3,<6.4
|
||||
Wand>=0.4.4,<0.7.0
|
||||
unidecode>=0.04.19,<1.4.0
|
||||
@ -18,3 +18,4 @@ flask-wtf>=0.14.2,<1.3.0
|
||||
chardet>=3.0.0,<4.1.0
|
||||
advocate>=1.0.0,<1.1.0
|
||||
Flask-Limiter>=2.3.0,<3.6.0
|
||||
markdown>=3.5.1
|
||||
|
@ -41,7 +41,7 @@ install_requires =
|
||||
Werkzeug<3.0.0
|
||||
APScheduler>=3.6.3,<3.11.0
|
||||
Babel>=1.3,<3.0
|
||||
Flask-Babel>=0.11.1,<3.2.0
|
||||
Flask-Babel>=0.11.1,<4.1.0
|
||||
Flask-Login>=0.3.2,<0.6.3
|
||||
Flask-Principal>=0.3.2,<0.5.1
|
||||
Flask>=1.0.2,<2.4.0
|
||||
@ -49,15 +49,15 @@ install_requires =
|
||||
PyPDF>=3.0.0,<3.16.0
|
||||
pytz>=2016.10
|
||||
requests>=2.28.0,<2.32.0
|
||||
SQLAlchemy>=1.3.0,<2.0.0
|
||||
SQLAlchemy>=1.3.0,<2.1.0
|
||||
tornado>=6.3,<6.4
|
||||
Wand>=0.4.4,<0.7.0
|
||||
unidecode>=0.04.19,<1.4.0
|
||||
lxml>=3.8.0,<5.0.0
|
||||
flask-wtf>=0.14.2,<1.2.0
|
||||
flask-wtf>=0.14.2,<1.3.0
|
||||
chardet>=3.0.0,<4.1.0
|
||||
advocate>=1.0.0,<1.1.0
|
||||
Flask-Limiter>=2.3.0,<3.5.0
|
||||
Flask-Limiter>=2.3.0,<3.6.0
|
||||
|
||||
|
||||
[options.packages.find]
|
||||
|
File diff suppressed because it is too large
Load Diff
Loading…
Reference in New Issue
Block a user