commit
4c030700fb
9
.editorconfig
Normal file
9
.editorconfig
Normal file
|
@ -0,0 +1,9 @@
|
|||
root = true
|
||||
|
||||
[*]
|
||||
end_of_line = lf
|
||||
insert_final_newline = true
|
||||
indent_style = space
|
||||
|
||||
[*.{js,py}]
|
||||
indent_size = 4
|
96
.eslintrc
Normal file
96
.eslintrc
Normal file
|
@ -0,0 +1,96 @@
|
|||
{
|
||||
"env": {
|
||||
"browser": true,
|
||||
"jquery": true
|
||||
},
|
||||
"globals": {
|
||||
"alert": true
|
||||
},
|
||||
"rules": {
|
||||
"arrow-parens": 2,
|
||||
"block-scoped-var": 1,
|
||||
"brace-style": 2,
|
||||
"camelcase": 1,
|
||||
"comma-spacing": 2,
|
||||
"curly": [2, "multi-line", "consistent"],
|
||||
"eqeqeq": 2,
|
||||
"indent": [
|
||||
2,
|
||||
4,
|
||||
{
|
||||
"SwitchCase": 1
|
||||
}
|
||||
],
|
||||
"keyword-spacing": 2,
|
||||
"linebreak-style": 2,
|
||||
"new-cap": 2,
|
||||
"no-dupe-args": 2,
|
||||
"no-dupe-class-members": 2,
|
||||
"no-dupe-keys": 2,
|
||||
"no-duplicate-case": 2,
|
||||
"no-caller": 2,
|
||||
"no-class-assign": 2,
|
||||
"no-cond-assign": 2,
|
||||
"no-const-assign": 2,
|
||||
"no-console": 2,
|
||||
"no-debugger": 2,
|
||||
"no-delete-var": 2,
|
||||
"no-empty": 2,
|
||||
"no-eval": 2,
|
||||
"no-extend-native": 2,
|
||||
"no-extra-boolean-cast": 2,
|
||||
"no-extra-semi": 2,
|
||||
"no-fallthrough": [
|
||||
2,
|
||||
{
|
||||
"commentPattern": "break[\\s\\w]*omitted"
|
||||
}
|
||||
],
|
||||
"no-implied-eval": 2,
|
||||
"no-invalid-regexp": 2,
|
||||
"no-irregular-whitespace": 2,
|
||||
"no-iterator": 2,
|
||||
"no-loop-func": 2,
|
||||
"no-mixed-operators": 2,
|
||||
"no-mixed-spaces-and-tabs": 2,
|
||||
"no-multi-str": 2,
|
||||
"no-new": 2,
|
||||
"no-obj-calls": 2,
|
||||
"no-octal": 2,
|
||||
"no-redeclare": 2,
|
||||
"no-regex-spaces": 2,
|
||||
"no-script-url": 2,
|
||||
"no-sparse-arrays": 2,
|
||||
"no-undef": 2,
|
||||
"no-undefined": 2,
|
||||
"no-unreachable": 2,
|
||||
"no-unsafe-negation": 2,
|
||||
"no-unused-vars": 2,
|
||||
"no-use-before-define": [
|
||||
2,
|
||||
{
|
||||
"classes": false,
|
||||
"functions": false
|
||||
}
|
||||
],
|
||||
"quotes": [
|
||||
2,
|
||||
"double"
|
||||
],
|
||||
"require-yield": 2,
|
||||
"semi": [
|
||||
2,
|
||||
"always"
|
||||
],
|
||||
"space-before-blocks": 2,
|
||||
"space-infix-ops": 2,
|
||||
"space-unary-ops": 2,
|
||||
"use-isnan": 2,
|
||||
"valid-typeof": 2,
|
||||
"wrap-iife": [
|
||||
2,
|
||||
"any"
|
||||
],
|
||||
"yield-star-spacing": 2
|
||||
}
|
||||
}
|
3
.gitignore
vendored
3
.gitignore
vendored
|
@ -27,5 +27,4 @@ tags
|
|||
settings.yaml
|
||||
gdrive_credentials
|
||||
|
||||
#kindlegen
|
||||
vendor/kindlegen
|
||||
vendor
|
||||
|
|
60
cps/cache_buster.py
Normal file
60
cps/cache_buster.py
Normal file
|
@ -0,0 +1,60 @@
|
|||
# Inspired by https://github.com/ChrisTM/Flask-CacheBust
|
||||
# Uses query strings so CSS font files are found without having to resort to absolute URLs
|
||||
|
||||
import hashlib
|
||||
import os
|
||||
|
||||
|
||||
def init_cache_busting(app):
|
||||
"""
|
||||
Configure `app` to so that `url_for` adds a unique query string to URLs generated
|
||||
for the `'static'` endpoint.
|
||||
|
||||
This allows setting long cache expiration values on static resources
|
||||
because whenever the resource changes, so does its URL.
|
||||
"""
|
||||
|
||||
static_folder = os.path.join(app.static_folder, '') # path to the static file folder, with trailing slash
|
||||
|
||||
hash_table = {} # map of file hashes
|
||||
|
||||
app.logger.debug('Computing cache-busting values...')
|
||||
# compute file hashes
|
||||
for dirpath, dirnames, filenames in os.walk(static_folder):
|
||||
for filename in filenames:
|
||||
# compute version component
|
||||
rooted_filename = os.path.join(dirpath, filename)
|
||||
with open(rooted_filename, 'r') as f:
|
||||
file_hash = hashlib.md5(f.read()).hexdigest()[:7]
|
||||
|
||||
# save version to tables
|
||||
file_path = rooted_filename.replace(static_folder, "")
|
||||
file_path = file_path.replace("\\", "/") # Convert Windows path to web path
|
||||
hash_table[file_path] = file_hash
|
||||
app.logger.debug('Finished computing cache-busting values')
|
||||
|
||||
def bust_filename(filename):
|
||||
return hash_table.get(filename, "")
|
||||
|
||||
def unbust_filename(filename):
|
||||
return filename.split("?", 1)[0]
|
||||
|
||||
@app.url_defaults
|
||||
def reverse_to_cache_busted_url(endpoint, values):
|
||||
"""
|
||||
Make `url_for` produce busted filenames when using the 'static' endpoint.
|
||||
"""
|
||||
if endpoint == "static":
|
||||
file_hash = bust_filename(values["filename"])
|
||||
if file_hash:
|
||||
values["q"] = file_hash
|
||||
|
||||
def debusting_static_view(filename):
|
||||
"""
|
||||
Serve a request for a static file having a busted name.
|
||||
"""
|
||||
return original_static_view(filename=unbust_filename(filename))
|
||||
|
||||
# Replace the default static file view with our debusting view.
|
||||
original_static_view = app.view_functions["static"]
|
||||
app.view_functions["static"] = debusting_static_view
|
23
cps/epub.py
23
cps/epub.py
|
@ -43,13 +43,16 @@ def get_epub_info(tmp_file_path, original_file_name, original_file_extension):
|
|||
|
||||
epub_metadata = {}
|
||||
|
||||
for s in ['title', 'description', 'creator', 'language']:
|
||||
for s in ['title', 'description', 'creator', 'language', 'subject']:
|
||||
tmp = p.xpath('dc:%s/text()' % s, namespaces=ns)
|
||||
if len(tmp) > 0:
|
||||
epub_metadata[s] = p.xpath('dc:%s/text()' % s, namespaces=ns)[0]
|
||||
else:
|
||||
epub_metadata[s] = "Unknown"
|
||||
|
||||
if epub_metadata['subject'] == "Unknown":
|
||||
epub_metadata['subject'] = ''
|
||||
|
||||
if epub_metadata['description'] == "Unknown":
|
||||
description = tree.xpath("//*[local-name() = 'description']/text()")
|
||||
if len(description) > 0:
|
||||
|
@ -68,6 +71,18 @@ def get_epub_info(tmp_file_path, original_file_name, original_file_extension):
|
|||
else:
|
||||
epub_metadata['language'] = ""
|
||||
|
||||
series = tree.xpath("/pkg:package/pkg:metadata/pkg:meta[@name='calibre:series']/@content", namespaces=ns)
|
||||
if len(series) > 0:
|
||||
epub_metadata['series'] = series[0]
|
||||
else:
|
||||
epub_metadata['series'] = ''
|
||||
|
||||
series_id = tree.xpath("/pkg:package/pkg:metadata/pkg:meta[@name='calibre:series_index']/@content", namespaces=ns)
|
||||
if len(series_id) > 0:
|
||||
epub_metadata['series_id'] = series_id[0]
|
||||
else:
|
||||
epub_metadata['series_id'] = '1'
|
||||
|
||||
coversection = tree.xpath("/pkg:package/pkg:manifest/pkg:item[@id='cover-image']/@href", namespaces=ns)
|
||||
coverfile = None
|
||||
if len(coversection) > 0:
|
||||
|
@ -101,7 +116,7 @@ def get_epub_info(tmp_file_path, original_file_name, original_file_extension):
|
|||
author=epub_metadata['creator'].encode('utf-8').decode('utf-8'),
|
||||
cover=coverfile,
|
||||
description=epub_metadata['description'],
|
||||
tags="",
|
||||
series="",
|
||||
series_id="",
|
||||
tags=epub_metadata['subject'].encode('utf-8').decode('utf-8'),
|
||||
series=epub_metadata['series'].encode('utf-8').decode('utf-8'),
|
||||
series_id=epub_metadata['series_id'].encode('utf-8').decode('utf-8'),
|
||||
languages=epub_metadata['language'])
|
||||
|
|
|
@ -278,7 +278,7 @@ def get_valid_filename(value, replace_whitespace=True):
|
|||
else:
|
||||
value = unicode(re_slugify.sub('', value).strip())
|
||||
if replace_whitespace:
|
||||
#*+:\"/<>? werden durch _ ersetzt
|
||||
#*+:\"/<>? are replaced by _
|
||||
value = re.sub('[\*\+:\\\"/<>\?]+', u'_', value, flags=re.U)
|
||||
|
||||
value = value[:128]
|
||||
|
|
|
@ -55,10 +55,38 @@ span.glyphicon.glyphicon-tags {padding-right: 5px;color: #999;vertical-align: te
|
|||
.block-label {display: block;}
|
||||
.fake-input {position: absolute; pointer-events: none; top: 0;}
|
||||
|
||||
input.pill { position: absolute; opacity: 0; }
|
||||
input.pill + label {
|
||||
border: 2px solid #45b29d;
|
||||
border-radius: 15px;
|
||||
color: #45b29d;
|
||||
cursor: pointer;
|
||||
display: inline-block;
|
||||
padding: 3px 15px;
|
||||
user-select: none;
|
||||
-webkit-font-smoothing: antialiased;
|
||||
-moz-osx-font-smoothing: grayscale;
|
||||
}
|
||||
input.pill:checked + label {
|
||||
background-color: #45b29d;
|
||||
border-color: #fff;
|
||||
color: #fff;
|
||||
}
|
||||
input.pill:not(:checked) + label .glyphicon {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.author-bio img {margin: 0 1em 1em 0;}
|
||||
.author-link img {display: inline-block;max-width: 100px;}
|
||||
.author-link {display: inline-block; margin-top: 10px; width: 100px;}
|
||||
.author-link img {display: block; height: 100%;}
|
||||
|
||||
#remove-from-shelves .btn,
|
||||
#shelf-action-errors {
|
||||
margin-left: 5px;
|
||||
}
|
||||
|
||||
.tags_click, .serie_click, .language_click {margin-right: 5px;}
|
||||
|
||||
#meta-info img { max-height: 150px; max-width: 100px; cursor: pointer; }
|
||||
|
||||
.padded-bottom { margin-bottom: 15px; }
|
||||
|
|
|
@ -1,4 +1,6 @@
|
|||
$( document ).ready(function() {
|
||||
/* global _ */
|
||||
|
||||
$(function() {
|
||||
$("#have_read_form").ajaxForm();
|
||||
});
|
||||
|
||||
|
@ -6,34 +8,51 @@ $("#have_read_cb").on("change", function() {
|
|||
$(this).closest("form").submit();
|
||||
});
|
||||
|
||||
(function() {
|
||||
var templates = {
|
||||
add: _.template(
|
||||
$("#template-shelf-add").html()
|
||||
),
|
||||
remove: _.template(
|
||||
$("#template-shelf-remove").html()
|
||||
)
|
||||
};
|
||||
|
||||
$("#shelf-actions").on("click", "[data-shelf-action]", function (e) {
|
||||
e.preventDefault();
|
||||
|
||||
$.get(this.href)
|
||||
.done(() => {
|
||||
const $this = $(this);
|
||||
.done(function() {
|
||||
var $this = $(this);
|
||||
switch ($this.data("shelf-action")) {
|
||||
case "add":
|
||||
$("#remove-from-shelves").append(`<a href="${$this.data("remove-href")}"
|
||||
data-add-href="${this.href}"
|
||||
class="btn btn-sm btn-default" data-shelf-action="remove"
|
||||
><span class="glyphicon glyphicon-remove"></span> ${this.textContent}</a>`);
|
||||
$("#remove-from-shelves").append(
|
||||
templates.remove({
|
||||
add: this.href,
|
||||
remove: $this.data("remove-href"),
|
||||
content: this.textContent
|
||||
})
|
||||
);
|
||||
break;
|
||||
case "remove":
|
||||
$("#add-to-shelves").append(`<li><a href="${$this.data("add-href")}"
|
||||
data-remove-href="${this.href}"
|
||||
data-shelf-action="add"
|
||||
>${this.textContent}</a></li>`);
|
||||
$("#add-to-shelves").append(
|
||||
templates.add({
|
||||
add: $this.data("add-href"),
|
||||
remove: this.href,
|
||||
content: this.textContent
|
||||
})
|
||||
);
|
||||
break;
|
||||
}
|
||||
this.parentNode.removeChild(this);
|
||||
})
|
||||
.fail((xhr) => {
|
||||
const $msg = $("<span/>", { "class": "text-danger"}).text(xhr.responseText);
|
||||
}.bind(this))
|
||||
.fail(function(xhr) {
|
||||
var $msg = $("<span/>", { "class": "text-danger"}).text(xhr.responseText);
|
||||
$("#shelf-action-status").html($msg);
|
||||
|
||||
setTimeout(() => {
|
||||
setTimeout(function() {
|
||||
$msg.remove();
|
||||
}, 10000);
|
||||
});
|
||||
});
|
||||
})();
|
||||
|
|
|
@ -3,6 +3,7 @@
|
|||
*/
|
||||
/* global Bloodhound, language, Modernizr, tinymce */
|
||||
|
||||
if ($("#description").length) {
|
||||
tinymce.init({
|
||||
selector: "#description",
|
||||
branding: false,
|
||||
|
@ -10,6 +11,7 @@ tinymce.init({
|
|||
language
|
||||
});
|
||||
|
||||
|
||||
if (!Modernizr.inputtypes.date) {
|
||||
$("#pubdate").datepicker({
|
||||
format: "yyyy-mm-dd",
|
||||
|
@ -26,7 +28,7 @@ if (!Modernizr.inputtypes.date) {
|
|||
.removeClass("hidden");
|
||||
}).trigger("change");
|
||||
}
|
||||
|
||||
}
|
||||
/*
|
||||
Takes a prefix, query typeahead callback, Bloodhound typeahead adapter
|
||||
and returns the completions it gets from the bloodhound engine prefixed.
|
||||
|
@ -49,7 +51,7 @@ function getPath(){
|
|||
|
||||
var authors = new Bloodhound({
|
||||
name: "authors",
|
||||
datumTokenizer(datum) {
|
||||
datumTokenizer: function datumTokenizer(datum) {
|
||||
return [datum.name];
|
||||
},
|
||||
queryTokenizer: Bloodhound.tokenizers.whitespace,
|
||||
|
@ -60,15 +62,15 @@ var authors = new Bloodhound({
|
|||
|
||||
var series = new Bloodhound({
|
||||
name: "series",
|
||||
datumTokenizer(datum) {
|
||||
datumTokenizer: function datumTokenizer(datum) {
|
||||
return [datum.name];
|
||||
},
|
||||
queryTokenizer(query) {
|
||||
queryTokenizer: function queryTokenizer(query) {
|
||||
return [query];
|
||||
},
|
||||
remote: {
|
||||
url: getPath()+"/get_series_json?q=",
|
||||
replace(url, query) {
|
||||
replace: function replace(url, query) {
|
||||
return url+encodeURIComponent(query);
|
||||
}
|
||||
}
|
||||
|
@ -77,10 +79,10 @@ var series = new Bloodhound({
|
|||
|
||||
var tags = new Bloodhound({
|
||||
name: "tags",
|
||||
datumTokenizer(datum) {
|
||||
datumTokenizer: function datumTokenizer(datum) {
|
||||
return [datum.name];
|
||||
},
|
||||
queryTokenizer(query) {
|
||||
queryTokenizer: function queryTokenizer(query) {
|
||||
var tokens = query.split(",");
|
||||
tokens = [tokens[tokens.length-1].trim()];
|
||||
return tokens;
|
||||
|
@ -92,15 +94,15 @@ var tags = new Bloodhound({
|
|||
|
||||
var languages = new Bloodhound({
|
||||
name: "languages",
|
||||
datumTokenizer(datum) {
|
||||
datumTokenizer: function datumTokenizer(datum) {
|
||||
return [datum.name];
|
||||
},
|
||||
queryTokenizer(query) {
|
||||
queryTokenizer: function queryTokenizer(query) {
|
||||
return [query];
|
||||
},
|
||||
remote: {
|
||||
url: getPath()+"/get_languages_json?q=",
|
||||
replace(url, query) {
|
||||
replace: function replace(url, query) {
|
||||
return url+encodeURIComponent(query);
|
||||
}
|
||||
}
|
||||
|
@ -135,10 +137,11 @@ var promiseAuthors = authors.initialize();
|
|||
}, {
|
||||
name: "authors",
|
||||
displayKey: "name",
|
||||
source(query, cb){
|
||||
source: function source(query, cb) {
|
||||
return sourceSplit(query, cb, "&", authors); //sourceSplit //("&")
|
||||
}
|
||||
});
|
||||
}
|
||||
);
|
||||
});
|
||||
|
||||
var promiseSeries = series.initialize();
|
||||
|
@ -164,10 +167,11 @@ var promiseTags = tags.initialize();
|
|||
}, {
|
||||
name: "tags",
|
||||
displayKey: "name",
|
||||
source(query, cb){
|
||||
source: function source(query, cb) {
|
||||
return sourceSplit(query, cb, ",", tags);
|
||||
}
|
||||
});
|
||||
}
|
||||
);
|
||||
});
|
||||
|
||||
var promiseLanguages = languages.initialize();
|
||||
|
@ -179,10 +183,11 @@ var promiseLanguages = languages.initialize();
|
|||
}, {
|
||||
name: "languages",
|
||||
displayKey: "name",
|
||||
source(query, cb){
|
||||
source: function source(query, cb) {
|
||||
return sourceSplit(query, cb, ",", languages); //(",")
|
||||
}
|
||||
});
|
||||
}
|
||||
);
|
||||
});
|
||||
|
||||
$("#search").on("change input.typeahead:selected", function() {
|
||||
|
@ -193,8 +198,7 @@ $("#search").on("change input.typeahead:selected", function(){
|
|||
if (!($(this).hasClass("active"))) {
|
||||
$(this).addClass("disabled");
|
||||
}
|
||||
}
|
||||
else {
|
||||
} else {
|
||||
$(this).removeClass("disabled");
|
||||
}
|
||||
});
|
||||
|
|
|
@ -4,11 +4,11 @@
|
|||
* Google Books api document: https://developers.google.com/books/docs/v1/using
|
||||
* Douban Books api document: https://developers.douban.com/wiki/?title=book_v2 (Chinese Only)
|
||||
*/
|
||||
/* global i18nMsg, tinymce */
|
||||
/* global _, i18nMsg, tinymce */
|
||||
var dbResults = [];
|
||||
var ggResults = [];
|
||||
|
||||
$(document).ready(function () {
|
||||
$(function () {
|
||||
var msg = i18nMsg;
|
||||
var douban = "https://api.douban.com";
|
||||
var dbSearch = "/v2/book/search";
|
||||
|
@ -22,113 +22,138 @@ $(document).ready(function () {
|
|||
var ggDone = false;
|
||||
|
||||
var showFlag = 0;
|
||||
String.prototype.replaceAll = function (s1, s2) {
|
||||
return this.replace(new RegExp(s1, "gm"), s2);
|
||||
|
||||
var templates = {
|
||||
bookResult: _.template(
|
||||
$("#template-book-result").html()
|
||||
)
|
||||
};
|
||||
|
||||
function populateForm (book) {
|
||||
tinymce.get("description").setContent(book.description);
|
||||
$("#bookAuthor").val(book.authors);
|
||||
$("#book_title").val(book.title);
|
||||
$("#tags").val(book.tags.join(","));
|
||||
$("#rating").data("rating").setValue(Math.round(book.rating));
|
||||
$(".cover img").attr("src", book.cover);
|
||||
$("#cover_url").val(book.cover);
|
||||
}
|
||||
|
||||
function showResult () {
|
||||
var book;
|
||||
var i;
|
||||
var bookHtml;
|
||||
showFlag++;
|
||||
if (showFlag === 1) {
|
||||
$("#metaModal #meta-info").html("<ul id=\"book-list\" class=\"media-list\"></ul>");
|
||||
$("#meta-info").html("<ul id=\"book-list\" class=\"media-list\"></ul>");
|
||||
}
|
||||
if (ggDone && dbDone) {
|
||||
if (!ggResults && !dbResults) {
|
||||
$("#metaModal #meta-info").html("<p class=\"text-danger\">"+ msg.no_result +"</p>");
|
||||
$("#meta-info").html("<p class=\"text-danger\">" + msg.no_result + "</p>");
|
||||
return;
|
||||
}
|
||||
}
|
||||
if (ggDone && ggResults.length > 0) {
|
||||
for (i = 0; i < ggResults.length; i++) {
|
||||
book = ggResults[i];
|
||||
var bookCover;
|
||||
if (book.volumeInfo.imageLinks) {
|
||||
bookCover = book.volumeInfo.imageLinks.thumbnail;
|
||||
} else {
|
||||
bookCover = "/static/generic_cover.jpg";
|
||||
}
|
||||
bookHtml = "<li class=\"media\">" +
|
||||
"<img class=\"pull-left img-responsive\" data-toggle=\"modal\" data-target=\"#metaModal\" src=\"" +
|
||||
bookCover + "\" alt=\"Cover\" style=\"width:100px;height:150px\" onclick='getMeta(\"google\"," +
|
||||
i + ")'>" +
|
||||
"<div class=\"media-body\">" +
|
||||
"<h4 class=\"media-heading\"><a href=\"https://books.google.com/books?id=" +
|
||||
book.id + "\" target=\"_blank\">" + book.volumeInfo.title + "</a></h4>" +
|
||||
"<p>"+ msg.author +":" + book.volumeInfo.authors + "</p>" +
|
||||
"<p>"+ msg.publisher + ":" + book.volumeInfo.publisher + "</p>" +
|
||||
"<p>"+ msg.description + ":" + book.volumeInfo.description + "</p>" +
|
||||
"<p>"+ msg.source + ":<a href=\"https://books.google.com\" target=\"_blank\">Google Books</a></p>" +
|
||||
"</div>" +
|
||||
"</li>";
|
||||
$("#metaModal #book-list").append(bookHtml);
|
||||
ggResults.forEach(function(result) {
|
||||
var book = {
|
||||
id: result.id,
|
||||
title: result.volumeInfo.title,
|
||||
authors: result.volumeInfo.authors || [],
|
||||
description: result.volumeInfo.description || "",
|
||||
publisher: result.volumeInfo.publisher || "",
|
||||
publishedDate: result.volumeInfo.publishedDate || "",
|
||||
tags: result.volumeInfo.categories || [],
|
||||
rating: result.volumeInfo.averageRating || 0,
|
||||
cover: result.volumeInfo.imageLinks ?
|
||||
result.volumeInfo.imageLinks.thumbnail :
|
||||
"/static/generic_cover.jpg",
|
||||
url: "https://books.google.com/books?id=" + result.id,
|
||||
source: {
|
||||
id: "google",
|
||||
description: "Google Books",
|
||||
url: "https://books.google.com/"
|
||||
}
|
||||
};
|
||||
|
||||
var $book = $(templates.bookResult(book));
|
||||
$book.find("img").on("click", function () {
|
||||
populateForm(book);
|
||||
});
|
||||
|
||||
$("#book-list").append($book);
|
||||
});
|
||||
ggDone = false;
|
||||
}
|
||||
if (dbDone && dbResults.length > 0) {
|
||||
for (i = 0; i < dbResults.length; i++) {
|
||||
book = dbResults[i];
|
||||
bookHtml = "<li class=\"media\">" +
|
||||
"<img class=\"pull-left img-responsive\" data-toggle=\"modal\" data-target=\"#metaModal\" src=\"" +
|
||||
book.image + "\" alt=\"Cover\" style=\"width:100px;height: 150px\" onclick='getMeta(\"douban\"," +
|
||||
i + ")'>" +
|
||||
"<div class=\"media-body\">" +
|
||||
"<h4 class=\"media-heading\"><a href=\"https://book.douban.com/subject/" +
|
||||
book.id + "\" target=\"_blank\">" + book.title + "</a></h4>" +
|
||||
"<p>" + msg.author + ":" + book.author + "</p>" +
|
||||
"<p>" + msg.publisher + ":" + book.publisher + "</p>" +
|
||||
"<p>" + msg.description + ":" + book.summary + "</p>" +
|
||||
"<p>" + msg.source + ":<a href=\"https://book.douban.com\" target=\"_blank\">Douban Books</a></p>" +
|
||||
"</div>" +
|
||||
"</li>";
|
||||
$("#metaModal #book-list").append(bookHtml);
|
||||
dbResults.forEach(function(result) {
|
||||
var book = {
|
||||
id: result.id,
|
||||
title: result.title,
|
||||
authors: result.author || [],
|
||||
description: result.summary,
|
||||
publisher: result.publisher || "",
|
||||
publishedDate: result.pubdate || "",
|
||||
tags: result.tags.map(function(tag) {
|
||||
return tag.title;
|
||||
}),
|
||||
rating: result.rating.average || 0,
|
||||
cover: result.image,
|
||||
url: "https://book.douban.com/subject/" + result.id,
|
||||
source: {
|
||||
id: "douban",
|
||||
description: "Douban Books",
|
||||
url: "https://book.douban.com/"
|
||||
}
|
||||
};
|
||||
|
||||
var $book = $(templates.bookResult(book));
|
||||
$book.find("img").on("click", function () {
|
||||
populateForm(book);
|
||||
});
|
||||
|
||||
$("#book-list").append($book);
|
||||
});
|
||||
dbDone = false;
|
||||
}
|
||||
}
|
||||
|
||||
function ggSearchBook (title) {
|
||||
title = title.replaceAll(/\s+/, "+");
|
||||
var url = google + ggSearch + "?q=" + title;
|
||||
$.ajax({
|
||||
url,
|
||||
url: google + ggSearch + "?q=" + title.replace(/\s+/gm, "+"),
|
||||
type: "GET",
|
||||
dataType: "jsonp",
|
||||
jsonp: "callback",
|
||||
success (data) {
|
||||
success: function success(data) {
|
||||
ggResults = data.items;
|
||||
},
|
||||
complete () {
|
||||
complete: function complete() {
|
||||
ggDone = true;
|
||||
showResult();
|
||||
$("#show-google").trigger("change");
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
function dbSearchBook (title) {
|
||||
var url = douban + dbSearch + "?q=" + title + "&fields=all&count=10";
|
||||
$.ajax({
|
||||
url,
|
||||
url: douban + dbSearch + "?q=" + title + "&fields=all&count=10",
|
||||
type: "GET",
|
||||
dataType: "jsonp",
|
||||
jsonp: "callback",
|
||||
success (data) {
|
||||
success: function success(data) {
|
||||
dbResults = data.books;
|
||||
},
|
||||
error () {
|
||||
$("#metaModal #meta-info").html("<p class=\"text-danger\">"+ msg.search_error+"!</p>");
|
||||
error: function error() {
|
||||
$("#meta-info").html("<p class=\"text-danger\">" + msg.search_error + "!</p>");
|
||||
},
|
||||
complete () {
|
||||
complete: function complete() {
|
||||
dbDone = true;
|
||||
showResult();
|
||||
$("#show-douban").trigger("change");
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
function doSearch (keyword) {
|
||||
showFlag = 0;
|
||||
$("#metaModal #meta-info").text(msg.loading);
|
||||
$("#meta-info").text(msg.loading);
|
||||
// var keyword = $("#keyword").val();
|
||||
if (keyword) {
|
||||
dbSearchBook(keyword);
|
||||
|
@ -136,7 +161,8 @@ $(document).ready(function () {
|
|||
}
|
||||
}
|
||||
|
||||
$("#do-search").click(function () {
|
||||
$("#meta-search").on("submit", function (e) {
|
||||
e.preventDefault();
|
||||
var keyword = $("#keyword").val();
|
||||
if (keyword) {
|
||||
doSearch(keyword);
|
||||
|
@ -152,35 +178,3 @@ $(document).ready(function () {
|
|||
});
|
||||
|
||||
});
|
||||
|
||||
function getMeta (source, id) {
|
||||
var meta;
|
||||
var tags;
|
||||
if (source === "google") {
|
||||
meta = ggResults[id];
|
||||
tinymce.get("description").setContent(meta.volumeInfo.description);
|
||||
$("#bookAuthor").val(meta.volumeInfo.authors.join(" & "));
|
||||
$("#book_title").val(meta.volumeInfo.title);
|
||||
if (meta.volumeInfo.categories) {
|
||||
tags = meta.volumeInfo.categories.join(",");
|
||||
$("#tags").val(tags);
|
||||
}
|
||||
if (meta.volumeInfo.averageRating) {
|
||||
$("#rating").val(Math.round(meta.volumeInfo.averageRating));
|
||||
}
|
||||
return;
|
||||
}
|
||||
if (source === "douban") {
|
||||
meta = dbResults[id];
|
||||
tinymce.get("description").setContent(meta.summary);
|
||||
$("#bookAuthor").val(meta.author.join(" & "));
|
||||
$("#book_title").val(meta.title);
|
||||
tags = "";
|
||||
for (var i = 0; i < meta.tags.length; i++) {
|
||||
tags = tags + meta.tags[i].title + ",";
|
||||
}
|
||||
$("#tags").val(tags);
|
||||
$("#rating").val(Math.round(meta.rating.average / 2));
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1 +1,2 @@
|
|||
!function(a){"use strict";function b(a){return"[data-value"+(a?"="+a:"")+"]"}function c(a,b,c){var d=c.activeIcon,e=c.inactiveIcon;a.removeClass(b?e:d).addClass(b?d:e)}function d(b,c){var d=a.extend({},i,b.data(),c);return d.inline=""===d.inline||d.inline,d.readonly=""===d.readonly||d.readonly,d.clearable===!1?d.clearableLabel="":d.clearableLabel=d.clearable,d.clearable=""===d.clearable||d.clearable,d}function e(b,c){if(c.inline)var d=a('<span class="rating-input"></span>');else var d=a('<div class="rating-input"></div>');d.addClass(b.attr("class")),d.removeClass("rating");for(var e=c.min;e<=c.max;e++)d.append('<i class="'+c.iconLib+'" data-value="'+e+'"></i>');return c.clearable&&!c.readonly&&d.append(" ").append('<a class="'+f+'"><i class="'+c.iconLib+" "+c.clearableIcon+'"/>'+c.clearableLabel+"</a>"),d}var f="rating-clear",g="."+f,h="hidden",i={min:1,max:5,"empty-value":0,iconLib:"glyphicon",activeIcon:"glyphicon-star",inactiveIcon:"glyphicon-star-empty",clearable:!1,clearableIcon:"glyphicon-remove",clearableRemain:!1,inline:!1,readonly:!1},j=function(a,b){var c=this.$input=a;this.options=d(c,b);var f=this.$el=e(c,this.options);c.addClass(h).before(f),c.attr("type","hidden"),this.highlight(c.val())};j.VERSION="0.4.0",j.DEFAULTS=i,j.prototype={clear:function(){this.setValue(this.options["empty-value"])},setValue:function(a){this.highlight(a),this.updateInput(a)},highlight:function(a,d){var e=this.options,f=this.$el;if(a>=this.options.min&&a<=this.options.max){var i=f.find(b(a));c(i.prevAll("i").andSelf(),!0,e),c(i.nextAll("i"),!1,e)}else c(f.find(b()),!1,e);d||(this.options.clearableRemain?f.find(g).removeClass(h):a&&a!=this.options["empty-value"]?f.find(g).removeClass(h):f.find(g).addClass(h))},updateInput:function(a){var b=this.$input;b.val()!=a&&b.val(a).change()}};var k=a.fn.rating=function(c){return this.filter("input[type=number]").each(function(){var d=a(this),e="object"==typeof c&&c||{},f=new j(d,e);f.options.readonly||f.$el.on("mouseenter",b(),function(){f.highlight(a(this).data("value"),!0)}).on("mouseleave",b(),function(){f.highlight(d.val(),!0)}).on("click",b(),function(){f.setValue(a(this).data("value"))}).on("click",g,function(){f.clear()})})};k.Constructor=j,a(function(){a("input.rating[type=number]").each(function(){a(this).rating()})})}(jQuery);
|
||||
/** @link https://github.com/javiertoledo/bootstrap-rating-input */
|
||||
!function(a){"use strict";function n(a){return"[data-value"+(a?"="+a:"")+"]"}function e(a,n,e){var i=e.activeIcon,t=e.inactiveIcon;a.removeClass(n?t:i).addClass(n?i:t)}function i(n,e){var i=a.extend({},s,n.data(),e);return i.inline=""===i.inline||i.inline,i.readonly=""===i.readonly||i.readonly,!1===i.clearable?i.clearableLabel="":i.clearableLabel=i.clearable,i.clearable=""===i.clearable||i.clearable,i}function t(n,e){if(e.inline)i=a('<span class="rating-input"></span>');else var i=a('<div class="rating-input"></div>');i.addClass(n.attr("class")),i.removeClass("rating");for(var t=e.min;t<=e.max;t++)i.append('<i class="'+e.iconLib+'" data-value="'+t+'"></i>');return e.clearable&&!e.readonly&&i.append(" ").append('<a class="'+l+'"><i class="'+e.iconLib+" "+e.clearableIcon+'"/>'+e.clearableLabel+"</a>"),i}var l="rating-clear",o="."+l,s={min:1,max:5,"empty-value":0,iconLib:"glyphicon",activeIcon:"glyphicon-star",inactiveIcon:"glyphicon-star-empty",clearable:!1,clearableIcon:"glyphicon-remove",clearableRemain:!1,inline:!1,readonly:!1},r=function(a,n){var e=this.$input=a;this.options=i(e,n);var l=this.$el=t(e,this.options);e.addClass("hidden").before(l),e.attr("type","hidden"),this.highlight(e.val())};r.VERSION="0.4.0",r.DEFAULTS=s,r.prototype={clear:function(){this.setValue(this.options["empty-value"])},setValue:function(a){this.highlight(a),this.updateInput(a)},highlight:function(a,i){var t=this.options,l=this.$el;if(a>=this.options.min&&a<=this.options.max){var s=l.find(n(a));e(s.prevAll("i").addBack(),!0,t),e(s.nextAll("i"),!1,t)}else e(l.find(n()),!1,t);i||(this.options.clearableRemain?l.find(o).removeClass("hidden"):a&&a!=this.options["empty-value"]?l.find(o).removeClass("hidden"):l.find(o).addClass("hidden"))},updateInput:function(a){var n=this.$input;n.val()!=a&&n.val(a).change()}},(a.fn.rating=function(e){return this.filter("input[type=number]").each(function(){var i=a(this),t=new r(i,"object"==typeof e&&e||{});t.options.readonly||(t.$el.on("mouseenter",n(),function(){t.highlight(a(this).data("value"),!0)}).on("mouseleave",n(),function(){t.highlight(i.val(),!0)}).on("click",n(),function(){t.setValue(a(this).data("value"))}).on("click",o,function(){t.clear()}),i.data("rating",t))})}).Constructor=r,a(function(){a("input.rating[type=number]").each(function(){a(this).rating()})})}(jQuery);
|
||||
|
|
|
@ -1,7 +1,3 @@
|
|||
var displaytext;
|
||||
var updateTimerID;
|
||||
var updateText;
|
||||
|
||||
// Generic control/related handler to show/hide fields based on a checkbox' value
|
||||
// e.g.
|
||||
// <input type="checkbox" data-control="stuff-to-show">
|
||||
|
@ -17,10 +13,12 @@ $(document).on("change", "input[type=\"checkbox\"][data-control]", function () {
|
|||
});
|
||||
|
||||
$(function() {
|
||||
var updateTimerID;
|
||||
var updateText;
|
||||
|
||||
// Allow ajax prefilters to be added/removed dynamically
|
||||
// eslint-disable-next-line new-cap
|
||||
const preFilters = $.Callbacks();
|
||||
var preFilters = $.Callbacks();
|
||||
$.ajaxPrefilter(preFilters.fire);
|
||||
|
||||
function restartTimer() {
|
||||
|
@ -32,23 +30,23 @@ $(function() {
|
|||
$.ajax({
|
||||
dataType: "json",
|
||||
url: window.location.pathname + "/../../get_updater_status",
|
||||
success(data) {
|
||||
success: function success(data) {
|
||||
// console.log(data.status);
|
||||
$("#UpdateprogressDialog #Updatecontent").html(updateText[data.status]);
|
||||
$("#Updatecontent").html(updateText[data.status]);
|
||||
if (data.status > 6) {
|
||||
clearInterval(updateTimerID);
|
||||
$("#spinner2").hide();
|
||||
$("#UpdateprogressDialog #updateFinished").removeClass("hidden");
|
||||
$("#updateFinished").removeClass("hidden");
|
||||
$("#check_for_update").removeClass("hidden");
|
||||
$("#perform_update").addClass("hidden");
|
||||
}
|
||||
},
|
||||
error() {
|
||||
error: function error() {
|
||||
// console.log('Done');
|
||||
clearInterval(updateTimerID);
|
||||
$("#spinner2").hide();
|
||||
$("#UpdateprogressDialog #Updatecontent").html(updateText[7]);
|
||||
$("#UpdateprogressDialog #updateFinished").removeClass("hidden");
|
||||
$("#Updatecontent").html(updateText[7]);
|
||||
$("#updateFinished").removeClass("hidden");
|
||||
$("#check_for_update").removeClass("hidden");
|
||||
$("#perform_update").addClass("hidden");
|
||||
},
|
||||
|
@ -70,7 +68,7 @@ $(function() {
|
|||
// selector for the NEXT link (to page 2)
|
||||
itemSelector : ".load-more .book",
|
||||
animate : true,
|
||||
extraScrollPx: 300,
|
||||
extraScrollPx: 300
|
||||
// selector for all items you'll retrieve
|
||||
}, function(data) {
|
||||
$(".load-more .row").isotope( "appended", $(data), null );
|
||||
|
@ -86,10 +84,10 @@ $(function() {
|
|||
dataType: "json",
|
||||
url: window.location.pathname + "/../../shutdown",
|
||||
data: {"parameter":0},
|
||||
success(data) {
|
||||
success: function success() {
|
||||
$("#spinner").show();
|
||||
displaytext=data.text;
|
||||
setTimeout(restartTimer, 3000);}
|
||||
setTimeout(restartTimer, 3000);
|
||||
}
|
||||
});
|
||||
});
|
||||
$("#shutdown").click(function() {
|
||||
|
@ -97,23 +95,26 @@ $(function() {
|
|||
dataType: "json",
|
||||
url: window.location.pathname + "/../../shutdown",
|
||||
data: {"parameter":1},
|
||||
success(data) {
|
||||
return alert(data.text);}
|
||||
success: function success(data) {
|
||||
return alert(data.text);
|
||||
}
|
||||
});
|
||||
});
|
||||
$("#check_for_update").click(function() {
|
||||
var buttonText = $("#check_for_update").html();
|
||||
$("#check_for_update").html("...");
|
||||
var $this = $(this);
|
||||
var buttonText = $this.html();
|
||||
$this.html("...");
|
||||
$.ajax({
|
||||
dataType: "json",
|
||||
url: window.location.pathname + "/../../get_update_status",
|
||||
success(data) {
|
||||
$("#check_for_update").html(buttonText);
|
||||
success: function success(data) {
|
||||
$this.html(buttonText);
|
||||
if (data.status === true) {
|
||||
$("#check_for_update").addClass("hidden");
|
||||
$("#perform_update").removeClass("hidden");
|
||||
$("#update_info").removeClass("hidden");
|
||||
$("#update_info").find("span").html(data.commit);
|
||||
$("#update_info")
|
||||
.removeClass("hidden")
|
||||
.find("span").html(data.commit);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
@ -132,11 +133,12 @@ $(function() {
|
|||
dataType: "json",
|
||||
data: { start: "True"},
|
||||
url: window.location.pathname + "/../../get_updater_status",
|
||||
success(data) {
|
||||
success: function success(data) {
|
||||
updateText = data.text;
|
||||
$("#UpdateprogressDialog #Updatecontent").html(updateText[data.status]);
|
||||
$("#Updatecontent").html(updateText[data.status]);
|
||||
// console.log(data.status);
|
||||
updateTimerID=setInterval(updateTimer, 2000);}
|
||||
updateTimerID = setInterval(updateTimer, 2000);
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
|
@ -144,10 +146,10 @@ $(function() {
|
|||
|
||||
$("#bookDetailsModal")
|
||||
.on("show.bs.modal", function(e) {
|
||||
const $modalBody = $(this).find(".modal-body");
|
||||
var $modalBody = $(this).find(".modal-body");
|
||||
|
||||
// Prevent static assets from loading multiple times
|
||||
const useCache = (options) => {
|
||||
var useCache = function(options) {
|
||||
options.async = true;
|
||||
options.cache = true;
|
||||
};
|
||||
|
@ -162,7 +164,7 @@ $(function() {
|
|||
$(this).find(".modal-body").html("...");
|
||||
});
|
||||
|
||||
$(window).resize(function(event) {
|
||||
$(window).resize(function() {
|
||||
$(".discover .row").isotope("reLayout");
|
||||
});
|
||||
});
|
|
@ -1,10 +1,11 @@
|
|||
/* global Sortable,sortTrue */
|
||||
|
||||
var sortable = Sortable.create(sortTrue, {
|
||||
Sortable.create(sortTrue, {
|
||||
group: "sorting",
|
||||
sort: true
|
||||
});
|
||||
|
||||
// eslint-disable-next-line no-unused-vars
|
||||
function sendData(path) {
|
||||
var elements;
|
||||
var counter;
|
||||
|
@ -18,13 +19,12 @@ function sendData(path){
|
|||
form.setAttribute("method", "post");
|
||||
form.setAttribute("action", path);
|
||||
|
||||
|
||||
for (counter = 0;counter < maxElements;counter++) {
|
||||
tmp[counter] = elements[counter].getAttribute("id");
|
||||
var hiddenField = document.createElement("input");
|
||||
hiddenField.setAttribute("type", "hidden");
|
||||
hiddenField.setAttribute("name", elements[counter].getAttribute("id"));
|
||||
hiddenField.setAttribute("value", counter+1);
|
||||
hiddenField.setAttribute("value", String(counter + 1));
|
||||
form.appendChild(hiddenField);
|
||||
}
|
||||
document.body.appendChild(form);
|
||||
|
|
|
@ -11,16 +11,17 @@
|
|||
{%if author.about is not none %}
|
||||
<p>{{author.about|safe}}</p>
|
||||
{% endif %}
|
||||
</section>
|
||||
|
||||
<a href="{{author.link}}" class="author-link" target="_blank">
|
||||
<img src="{{ url_for('static', filename='img/goodreads.svg') }}" alt="Goodreads">
|
||||
</a>
|
||||
- {{_("via")}} <a href="{{author.link}}" class="author-link" target="_blank" rel="noopener">Goodreads</a>
|
||||
</section>
|
||||
|
||||
<div class="clearfix"></div>
|
||||
{% endif %}
|
||||
|
||||
<div class="discover load-more">
|
||||
{% if author is not none %}
|
||||
<h3>{{_("In Library")}}</h3>
|
||||
{% endif %}
|
||||
<div class="row">
|
||||
{% if entries[0] %}
|
||||
{% for entry in entries %}
|
||||
|
@ -62,4 +63,48 @@
|
|||
{% endif %}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{% if other_books is not none %}
|
||||
<div class="discover">
|
||||
<h3>{{_("More by")}} {{ author.name|safe }}</h3>
|
||||
<div class="row">
|
||||
{% for entry in other_books %}
|
||||
<div class="col-sm-3 col-lg-2 col-xs-6 book">
|
||||
<div class="cover">
|
||||
<a href="https://www.goodreads.com/book/show/{{ entry.gid['#text'] }}" target="_blank" rel="noopener">
|
||||
<img src="{{ entry.image_url }}" />
|
||||
</a>
|
||||
</div>
|
||||
<div class="meta">
|
||||
<p class="title">{{entry.title|shortentitle}}</p>
|
||||
<p class="author">
|
||||
{% for author in entry.authors %}
|
||||
<a href="https://www.goodreads.com/author/show/{{ author.gid }}" target="_blank" rel="noopener">
|
||||
{{author.name}}
|
||||
</a>
|
||||
{% if not loop.last %}
|
||||
&
|
||||
{% endif %}
|
||||
{% endfor %}
|
||||
</p>
|
||||
<div class="rating">
|
||||
{% for number in range((entry.average_rating)|float|round|int(2)) %}
|
||||
<span class="glyphicon glyphicon-star good"></span>
|
||||
{% if loop.last and loop.index < 5 %}
|
||||
{% for numer in range(5 - loop.index) %}
|
||||
<span class="glyphicon glyphicon-star"></span>
|
||||
{% endfor %}
|
||||
{% endif %}
|
||||
{% endfor %}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{% endfor %}
|
||||
</div>
|
||||
|
||||
<a href="{{author.link}}" class="author-link" target="_blank" rel="noopener">
|
||||
<img src="{{ url_for('static', filename='img/goodreads.svg') }}" alt="Goodreads">
|
||||
</a>
|
||||
</div>
|
||||
{% endif %}
|
||||
{% endblock %}
|
||||
|
|
|
@ -139,7 +139,7 @@
|
|||
<div class="modal-header bg-danger text-center">
|
||||
<span>{{_('Are really you sure?')}}</span>
|
||||
</div>
|
||||
<div class="modal-body text-center" id="meta-info">
|
||||
<div class="modal-body text-center">
|
||||
<span>{{_('Book will be deleted from Calibre database')}}</span>
|
||||
<span>{{_('and from hard disk')}}</span>
|
||||
</div>
|
||||
|
@ -154,23 +154,36 @@
|
|||
{% endif %}
|
||||
|
||||
<div class="modal fade" id="metaModal" tabindex="-1" role="dialog" aria-labelledby="metaModalLabel">
|
||||
<div class="modal-dialog" role="document">
|
||||
<div class="modal-dialog modal-lg" role="document">
|
||||
<div class="modal-content">
|
||||
<div class="modal-header">
|
||||
<button type="button" class="close" data-dismiss="modal" aria-label="Close"><span aria-hidden="true">×</span></button>
|
||||
<h4 class="modal-title" id="metaModalLabel">{{_('Get metadata')}}</h4>
|
||||
<form class="form-inline">
|
||||
<div class="form-group">
|
||||
<form class="padded-bottom" id="meta-search">
|
||||
<div class="input-group">
|
||||
<label class="sr-only" for="keyword">{{_('Keyword')}}</label>
|
||||
<input type="text" class="form-control" id="keyword" placeholder="{{_(" Search keyword ")}}">
|
||||
<input type="text" class="form-control" id="keyword" name="keyword" placeholder="{{_(" Search keyword ")}}">
|
||||
<span class="input-group-btn">
|
||||
<button type="submit" class="btn btn-primary" id="do-search">{{_("Go!")}}</button>
|
||||
</span>
|
||||
</div>
|
||||
<button type="button" class="btn btn-default" id="do-search">{{_("Go!")}}</button>
|
||||
<span>{{_('Click the cover to load metadata to the form')}}</span>
|
||||
</form>
|
||||
<div>{{_('Click the cover to load metadata to the form')}}</div>
|
||||
</div>
|
||||
<div class="modal-body" id="meta-info">
|
||||
<div class="modal-body">
|
||||
<div class="text-center padded-bottom">
|
||||
<input type="checkbox" id="show-douban" class="pill" data-control="douban" checked>
|
||||
<label for="show-douban">Douban <span class="glyphicon glyphicon-ok"></span></label>
|
||||
|
||||
<input type="checkbox" id="show-google" class="pill" data-control="google" checked>
|
||||
<label for="show-google">Google <span class="glyphicon glyphicon-ok"></span></label>
|
||||
</div>
|
||||
|
||||
<div id="meta-info">
|
||||
{{_("Loading...")}}
|
||||
</div>
|
||||
<ul id="book-list" class="media-list"></ul>
|
||||
</div>
|
||||
<div class="modal-footer">
|
||||
<button type="button" class="btn btn-default" data-dismiss="modal">{{_('Close')}}</button>
|
||||
</div>
|
||||
|
@ -180,6 +193,31 @@
|
|||
{% endblock %}
|
||||
|
||||
{% block js %}
|
||||
<script type="text/template" id="template-book-result">
|
||||
<li class="media" data-related="<%= source.id %>">
|
||||
<img class="pull-left img-responsive"
|
||||
data-toggle="modal"
|
||||
data-target="#metaModal"
|
||||
src="<%= cover %>"
|
||||
alt="Cover"
|
||||
>
|
||||
<div class="media-body">
|
||||
<h4 class="media-heading">
|
||||
<a href="<%= url %>" target="_blank" rel="noopener"><%= title %></a>
|
||||
</h4>
|
||||
<p>{{_('Author')}}:<%= authors.join(" & ") %></p>
|
||||
<% if (publisher) { %>
|
||||
<p>{{_('Publisher')}}:<%= publisher %></p>
|
||||
<% } %>
|
||||
<% if (description) { %>
|
||||
<p>{{_('Description')}}: <%= description %></p>
|
||||
<% } %>
|
||||
<p>{{_('Source')}}:
|
||||
<a href="<%= source.url %>" target="_blank" rel="noopener"><%= source.description %></a>
|
||||
</p>
|
||||
</div>
|
||||
</li>
|
||||
</script>
|
||||
<script>
|
||||
var i18nMsg = {
|
||||
'loading': {{_('Loading...')|safe|tojson}},
|
||||
|
|
|
@ -72,6 +72,13 @@
|
|||
<label for="config_title_regex">{{_('Regular expression for title sorting')}}</label>
|
||||
<input type="text" class="form-control" name="config_title_regex" id="config_title_regex" value="{% if content.config_title_regex != None %}{{ content.config_title_regex }}{% endif %}" autocomplete="off">
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label for="config_mature_content_tags">{{_('Tags for Mature Content')}}</label>
|
||||
<input type="text" class="form-control" name="config_mature_content_tags" id="config_mature_content_tags"
|
||||
value="{% if content.config_mature_content_tags != None%}{{ content.config_mature_content_tags }}{% endif %}"
|
||||
autocomplete="off"
|
||||
>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label for="config_log_level">{{_('Log Level')}}</label>
|
||||
<select name="config_log_level" id="config_log_level" class="form-control">
|
||||
|
|
|
@ -259,5 +259,17 @@
|
|||
{% endblock %}
|
||||
|
||||
{% block js %}
|
||||
<script type="text/template" id="template-shelf-add">
|
||||
<li>
|
||||
<a href="<%= add %>" data-remove-href="<%= remove %>" data-shelf-action="add">
|
||||
<%= content %>
|
||||
</a>
|
||||
</li>
|
||||
</script>
|
||||
<script type="text/template" id="template-shelf-remove">
|
||||
<a href="<%= remove %>" data-add-href="<%= add %>" class="btn btn-sm btn-default" data-shelf-action="remove">
|
||||
<span class="glyphicon glyphicon-remove"></span> <%= content %>
|
||||
</a>
|
||||
</script>
|
||||
<script src="{{ url_for('static', filename='js/details.js') }}"></script>
|
||||
{% endblock %}
|
||||
|
|
|
@ -26,9 +26,7 @@
|
|||
href="{{request.script_root + request.path}}?offset={{ pagination.previous_offset }}"
|
||||
type="application/atom+xml;profile=opds-catalog;type=feed;kind=navigation"/>
|
||||
{% endif %}
|
||||
<link rel="search"
|
||||
href="{{url_for('feed_osd')}}"
|
||||
type="application/opensearchdescription+xml"/>
|
||||
<link title="{{_('Search')}}" type="application/atom+xml" href="{{url_for('feed_normal_search')}}?query={searchTerms}" rel="search"/>
|
||||
<title>{{instance}}</title>
|
||||
<author>
|
||||
<name>{{instance}}</name>
|
||||
|
|
|
@ -4,8 +4,7 @@
|
|||
<link rel="self" href="{{url_for('feed_index')}}" type="application/atom+xml;profile=opds-catalog;kind=navigation"/>
|
||||
<link rel="start" title="{{_('Start')}}" href="{{url_for('feed_index')}}"
|
||||
type="application/atom+xml;profile=opds-catalog;kind=navigation"/>
|
||||
<link rel="search" title="{{_('Search')}}" href="{{url_for('feed_osd')}}"
|
||||
type="application/opensearchdescription+xml"/>
|
||||
<link title="{{_('Search')}}" type="application/atom+xml" href="{{url_for('feed_normal_search')}}?query={searchTerms}" rel="search"/>
|
||||
<title>{{instance}}</title>
|
||||
<author>
|
||||
<name>{{instance}}</name>
|
||||
|
|
|
@ -41,7 +41,10 @@
|
|||
</select>
|
||||
</div>
|
||||
<div class="col-sm-6">
|
||||
|
||||
<div class="form-group">
|
||||
<input type="checkbox" name="show_mature_content" id="show_mature_content" {% if content.mature_content %}checked{% endif %}>
|
||||
<label for="show_mature_content">{{_('Show mature content')}}</label>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<input type="checkbox" name="show_random" id="show_random" {% if content.show_random_books() %}checked{% endif %}>
|
||||
<label for="show_random">{{_('Show random books')}}</label>
|
||||
|
|
Binary file not shown.
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
Binary file not shown.
File diff suppressed because it is too large
Load Diff
20
cps/ub.py
20
cps/ub.py
|
@ -157,6 +157,7 @@ class User(UserBase, Base):
|
|||
locale = Column(String(2), default="en")
|
||||
sidebar_view = Column(Integer, default=1)
|
||||
default_language = Column(String(3), default="all")
|
||||
mature_content = Column(Boolean, default=True)
|
||||
|
||||
|
||||
# Class for anonymous user is derived from User base and complets overrides methods and properties for the
|
||||
|
@ -166,13 +167,14 @@ class Anonymous(AnonymousUserMixin, UserBase):
|
|||
self.loadSettings()
|
||||
|
||||
def loadSettings(self):
|
||||
data = session.query(User).filter(User.role.op('&')(ROLE_ANONYMOUS) == ROLE_ANONYMOUS).first()
|
||||
data = session.query(User).filter(User.role.op('&')(ROLE_ANONYMOUS) == ROLE_ANONYMOUS).first() # type: User
|
||||
settings = session.query(Settings).first()
|
||||
self.nickname = data.nickname
|
||||
self.role = data.role
|
||||
self.sidebar_view = data.sidebar_view
|
||||
self.default_language = data.default_language
|
||||
self.locale = data.locale
|
||||
self.mature_content = data.mature_content
|
||||
self.anon_browse = settings.config_anonbrowse
|
||||
|
||||
def role_admin(self):
|
||||
|
@ -266,6 +268,7 @@ class Settings(Base):
|
|||
config_use_goodreads = Column(Boolean)
|
||||
config_goodreads_api_key = Column(String)
|
||||
config_goodreads_api_secret = Column(String)
|
||||
config_mature_content_tags = Column(String) # type: str
|
||||
|
||||
def __repr__(self):
|
||||
pass
|
||||
|
@ -297,7 +300,7 @@ class Config:
|
|||
self.loadSettings()
|
||||
|
||||
def loadSettings(self):
|
||||
data = session.query(Settings).first()
|
||||
data = session.query(Settings).first() # type: Settings
|
||||
self.config_calibre_dir = data.config_calibre_dir
|
||||
self.config_port = data.config_port
|
||||
self.config_calibre_web_title = data.config_calibre_web_title
|
||||
|
@ -326,6 +329,7 @@ class Config:
|
|||
self.config_use_goodreads = data.config_use_goodreads
|
||||
self.config_goodreads_api_key = data.config_goodreads_api_key
|
||||
self.config_goodreads_api_secret = data.config_goodreads_api_secret
|
||||
self.config_mature_content_tags = data.config_mature_content_tags
|
||||
|
||||
@property
|
||||
def get_main_dir(self):
|
||||
|
@ -371,6 +375,8 @@ class Config:
|
|||
return bool((self.config_default_role is not None) and
|
||||
(self.config_default_role & ROLE_DELETE_BOOKS == ROLE_DELETE_BOOKS))
|
||||
|
||||
def mature_content_tags(self):
|
||||
return list(map(unicode.lstrip, self.config_mature_content_tags.split(",")))
|
||||
|
||||
def get_Log_Level(self):
|
||||
ret_value=""
|
||||
|
@ -470,6 +476,11 @@ def migrate_Database():
|
|||
'side_lang': SIDEBAR_LANGUAGE, 'side_series': SIDEBAR_SERIES, 'side_category': SIDEBAR_CATEGORY,
|
||||
'side_hot': SIDEBAR_HOT, 'side_autor': SIDEBAR_AUTHOR, 'detail_random': DETAIL_RANDOM})
|
||||
session.commit()
|
||||
try:
|
||||
session.query(exists().where(User.mature_content)).scalar()
|
||||
except exc.OperationalError:
|
||||
conn = engine.connect()
|
||||
conn.execute("ALTER TABLE user ADD column `mature_content` INTEGER DEFAULT 1")
|
||||
if session.query(User).filter(User.role.op('&')(ROLE_ANONYMOUS) == ROLE_ANONYMOUS).first() is None:
|
||||
create_anonymous_user()
|
||||
try:
|
||||
|
@ -484,6 +495,11 @@ def migrate_Database():
|
|||
conn.execute("ALTER TABLE Settings ADD column `config_use_goodreads` INTEGER DEFAULT 0")
|
||||
conn.execute("ALTER TABLE Settings ADD column `config_goodreads_api_key` String DEFAULT ''")
|
||||
conn.execute("ALTER TABLE Settings ADD column `config_goodreads_api_secret` String DEFAULT ''")
|
||||
try:
|
||||
session.query(exists().where(Settings.config_mature_content_tags)).scalar()
|
||||
except exc.OperationalError:
|
||||
conn = engine.connect()
|
||||
conn.execute("ALTER TABLE Settings ADD column `config_mature_content_tags` String DEFAULT ''")
|
||||
|
||||
def clean_database():
|
||||
# Remove expired remote login tokens
|
||||
|
|
230
cps/web.py
230
cps/web.py
|
@ -8,11 +8,16 @@ except ImportError:
|
|||
gdrive_support = False
|
||||
|
||||
try:
|
||||
from goodreads import client as gr_client
|
||||
from goodreads.client import GoodreadsClient
|
||||
goodreads_support = True
|
||||
except ImportError:
|
||||
goodreads_support = False
|
||||
|
||||
try:
|
||||
from functools import reduce
|
||||
except ImportError:
|
||||
pass # We're not using Python 3
|
||||
|
||||
import mimetypes
|
||||
import logging
|
||||
from logging.handlers import RotatingFileHandler
|
||||
|
@ -21,6 +26,7 @@ from flask import (Flask, render_template, request, Response, redirect,
|
|||
url_for, send_from_directory, make_response, g, flash,
|
||||
abort, Markup, stream_with_context)
|
||||
from flask import __version__ as flaskVersion
|
||||
import cache_buster
|
||||
import ub
|
||||
from ub import config
|
||||
import helper
|
||||
|
@ -200,6 +206,7 @@ mimetypes.add_type('image/vnd.djvu', '.djvu')
|
|||
|
||||
app = (Flask(__name__))
|
||||
app.wsgi_app = ReverseProxied(app.wsgi_app)
|
||||
cache_buster.init_cache_busting(app)
|
||||
|
||||
gevent_server = None
|
||||
|
||||
|
@ -499,21 +506,30 @@ def edit_required(f):
|
|||
return inner
|
||||
|
||||
|
||||
# Fill indexpage with all requested data from database
|
||||
def fill_indexpage(page, database, db_filter, order):
|
||||
# Language and content filters
|
||||
def common_filters():
|
||||
if current_user.filter_language() != "all":
|
||||
lang_filter = db.Books.languages.any(db.Languages.lang_code == current_user.filter_language())
|
||||
else:
|
||||
lang_filter = True
|
||||
content_rating_filter = false() if current_user.mature_content else \
|
||||
db.Books.tags.any(db.Tags.name.in_(config.mature_content_tags()))
|
||||
return and_(lang_filter, ~content_rating_filter)
|
||||
|
||||
|
||||
# Fill indexpage with all requested data from database
|
||||
def fill_indexpage(page, database, db_filter, order):
|
||||
if current_user.show_detail_random():
|
||||
random = db.session.query(db.Books).filter(lang_filter).order_by(func.random()).limit(config.config_random_books)
|
||||
random = db.session.query(db.Books).filter(common_filters())\
|
||||
.order_by(func.random()).limit(config.config_random_books)
|
||||
else:
|
||||
random = false
|
||||
off = int(int(config.config_books_per_page) * (page - 1))
|
||||
pagination = Pagination(page, config.config_books_per_page,
|
||||
len(db.session.query(database).filter(db_filter).filter(lang_filter).all()))
|
||||
entries = db.session.query(database).filter(db_filter).filter(lang_filter).order_by(order).offset(off).limit(
|
||||
config.config_books_per_page)
|
||||
len(db.session.query(database)
|
||||
.filter(db_filter).filter(common_filters()).all()))
|
||||
entries = db.session.query(database).filter(common_filters())\
|
||||
.order_by(order).offset(off).limit(config.config_books_per_page)
|
||||
return entries, random, pagination
|
||||
|
||||
|
||||
|
@ -634,16 +650,13 @@ def feed_normal_search():
|
|||
|
||||
|
||||
def feed_search(term):
|
||||
if current_user.filter_language() != "all":
|
||||
lang_filter = db.Books.languages.any(db.Languages.lang_code == current_user.filter_language())
|
||||
else:
|
||||
lang_filter = True
|
||||
if term:
|
||||
entries = db.session.query(db.Books).filter(db.or_(db.Books.tags.any(db.Tags.name.like("%" + term + "%")),
|
||||
db.Books.series.any(db.Series.name.like("%" + term + "%")),
|
||||
db.Books.authors.any(db.Authors.name.like("%" + term + "%")),
|
||||
db.Books.publishers.any(db.Publishers.name.like("%" + term + "%")),
|
||||
db.Books.title.like("%" + term + "%"))).filter(lang_filter).all()
|
||||
db.Books.title.like("%" + term + "%")))\
|
||||
.filter(common_filters()).all()
|
||||
entriescount = len(entries) if len(entries) > 0 else 1
|
||||
pagination = Pagination(1, entriescount, entriescount)
|
||||
xml = render_title_template('feed.xml', searchterm=term, entries=entries, pagination=pagination)
|
||||
|
@ -671,11 +684,8 @@ def feed_new():
|
|||
@app.route("/opds/discover")
|
||||
@requires_basic_auth_if_no_ano
|
||||
def feed_discover():
|
||||
if current_user.filter_language() != "all":
|
||||
lang_filter = db.Books.languages.any(db.Languages.lang_code == current_user.filter_language())
|
||||
else:
|
||||
lang_filter = True
|
||||
entries = db.session.query(db.Books).filter(lang_filter).order_by(func.random()).limit(config.config_books_per_page)
|
||||
entries = db.session.query(db.Books).filter(common_filters()).order_by(func.random())\
|
||||
.limit(config.config_books_per_page)
|
||||
pagination = Pagination(1, config.config_books_per_page, int(config.config_books_per_page))
|
||||
xml = render_title_template('feed.xml', entries=entries, pagination=pagination)
|
||||
response = make_response(xml)
|
||||
|
@ -703,10 +713,6 @@ def feed_hot():
|
|||
off = request.args.get("offset")
|
||||
if not off:
|
||||
off = 0
|
||||
if current_user.filter_language() != "all":
|
||||
lang_filter = db.Books.languages.any(db.Languages.lang_code == current_user.filter_language())
|
||||
else:
|
||||
lang_filter = True
|
||||
all_books = ub.session.query(ub.Downloads, ub.func.count(ub.Downloads.book_id)).order_by(
|
||||
ub.func.count(ub.Downloads.book_id).desc()).group_by(ub.Downloads.book_id)
|
||||
hot_books = all_books.offset(off).limit(config.config_books_per_page)
|
||||
|
@ -715,7 +721,9 @@ def feed_hot():
|
|||
downloadBook = db.session.query(db.Books).filter(db.Books.id == book.Downloads.book_id).first()
|
||||
if downloadBook:
|
||||
entries.append(
|
||||
db.session.query(db.Books).filter(lang_filter).filter(db.Books.id == book.Downloads.book_id).first())
|
||||
db.session.query(db.Books).filter(common_filters())
|
||||
.filter(db.Books.id == book.Downloads.book_id).first()
|
||||
)
|
||||
else:
|
||||
ub.session.query(ub.Downloads).filter(book.Downloads.book_id == ub.Downloads.book_id).delete()
|
||||
ub.session.commit()
|
||||
|
@ -733,11 +741,7 @@ def feed_authorindex():
|
|||
off = request.args.get("offset")
|
||||
if not off:
|
||||
off = 0
|
||||
if current_user.filter_language() != "all":
|
||||
lang_filter = db.Books.languages.any(db.Languages.lang_code == current_user.filter_language())
|
||||
else:
|
||||
lang_filter = True
|
||||
entries = db.session.query(db.Authors).join(db.books_authors_link).join(db.Books).filter(lang_filter)\
|
||||
entries = db.session.query(db.Authors).join(db.books_authors_link).join(db.Books).filter(common_filters())\
|
||||
.group_by('books_authors_link.author').order_by(db.Authors.sort).limit(config.config_books_per_page).offset(off)
|
||||
pagination = Pagination((int(off) / (int(config.config_books_per_page)) + 1), config.config_books_per_page,
|
||||
len(db.session.query(db.Authors).all()))
|
||||
|
@ -767,12 +771,8 @@ def feed_categoryindex():
|
|||
off = request.args.get("offset")
|
||||
if not off:
|
||||
off = 0
|
||||
if current_user.filter_language() != "all":
|
||||
lang_filter = db.Books.languages.any(db.Languages.lang_code == current_user.filter_language())
|
||||
else:
|
||||
lang_filter = True
|
||||
entries = db.session.query(db.Tags).join(db.books_tags_link).join(db.Books).filter(lang_filter).\
|
||||
group_by('books_tags_link.tag').order_by(db.Tags.name).offset(off).limit(config.config_books_per_page)
|
||||
entries = db.session.query(db.Tags).join(db.books_tags_link).join(db.Books).filter(common_filters())\
|
||||
.group_by('books_tags_link.tag').order_by(db.Tags.name).offset(off).limit(config.config_books_per_page)
|
||||
pagination = Pagination((int(off) / (int(config.config_books_per_page)) + 1), config.config_books_per_page,
|
||||
len(db.session.query(db.Tags).all()))
|
||||
xml = render_title_template('feed.xml', listelements=entries, folder='feed_category', pagination=pagination)
|
||||
|
@ -801,12 +801,8 @@ def feed_seriesindex():
|
|||
off = request.args.get("offset")
|
||||
if not off:
|
||||
off = 0
|
||||
if current_user.filter_language() != "all":
|
||||
lang_filter = db.Books.languages.any(db.Languages.lang_code == current_user.filter_language())
|
||||
else:
|
||||
lang_filter = True
|
||||
entries = db.session.query(db.Series).join(db.books_series_link).join(db.Books).filter(lang_filter).\
|
||||
group_by('books_series_link.series').order_by(db.Series.sort).offset(off).all()
|
||||
entries = db.session.query(db.Series).join(db.books_series_link).join(db.Books).filter(common_filters())\
|
||||
.group_by('books_series_link.series').order_by(db.Series.sort).offset(off).all()
|
||||
pagination = Pagination((int(off) / (int(config.config_books_per_page)) + 1), config.config_books_per_page,
|
||||
len(db.session.query(db.Series).all()))
|
||||
xml = render_title_template('feed.xml', listelements=entries, folder='feed_series', pagination=pagination)
|
||||
|
@ -1071,12 +1067,9 @@ def titles_descending(page):
|
|||
@app.route('/hot/page/<int:page>')
|
||||
@login_required_if_no_ano
|
||||
def hot_books(page):
|
||||
if current_user.filter_language() != "all":
|
||||
lang_filter = db.Books.languages.any(db.Languages.lang_code == current_user.filter_language())
|
||||
else:
|
||||
lang_filter = True
|
||||
if current_user.show_detail_random():
|
||||
random = db.session.query(db.Books).filter(lang_filter).order_by(func.random()).limit(config.config_random_books)
|
||||
random = db.session.query(db.Books).filter(common_filters())\
|
||||
.order_by(func.random()).limit(config.config_random_books)
|
||||
else:
|
||||
random = false
|
||||
off = int(int(config.config_books_per_page) * (page - 1))
|
||||
|
@ -1088,7 +1081,9 @@ def hot_books(page):
|
|||
downloadBook = db.session.query(db.Books).filter(db.Books.id == book.Downloads.book_id).first()
|
||||
if downloadBook:
|
||||
entries.append(
|
||||
db.session.query(db.Books).filter(lang_filter).filter(db.Books.id == book.Downloads.book_id).first())
|
||||
db.session.query(db.Books).filter(common_filters())
|
||||
.filter(db.Books.id == book.Downloads.book_id).first()
|
||||
)
|
||||
else:
|
||||
ub.session.query(ub.Downloads).filter(book.Downloads.book_id == ub.Downloads.book_id).delete()
|
||||
ub.session.commit()
|
||||
|
@ -1120,13 +1115,9 @@ def discover(page):
|
|||
@app.route("/author")
|
||||
@login_required_if_no_ano
|
||||
def author_list():
|
||||
if current_user.filter_language() != "all":
|
||||
lang_filter = db.Books.languages.any(db.Languages.lang_code == current_user.filter_language())
|
||||
else:
|
||||
lang_filter = True
|
||||
entries = db.session.query(db.Authors, func.count('books_authors_link.book').label('count')).join(
|
||||
db.books_authors_link).join(db.Books).filter(
|
||||
lang_filter).group_by('books_authors_link.author').order_by(db.Authors.sort).all()
|
||||
entries = db.session.query(db.Authors, func.count('books_authors_link.book').label('count'))\
|
||||
.join(db.books_authors_link).join(db.Books).filter(common_filters())\
|
||||
.group_by('books_authors_link.author').order_by(db.Authors.sort).all()
|
||||
return render_title_template('list.html', entries=entries, folder='author', title=_(u"Author list"))
|
||||
|
||||
|
||||
|
@ -1136,31 +1127,34 @@ def author_list():
|
|||
def author(book_id, page):
|
||||
entries, random, pagination = fill_indexpage(page, db.Books, db.Books.authors.any(db.Authors.id == book_id),
|
||||
db.Books.timestamp.desc())
|
||||
if entries:
|
||||
if entries is None:
|
||||
flash(_(u"Error opening eBook. File does not exist or file is not accessible:"), category="error")
|
||||
return redirect(url_for("index"))
|
||||
|
||||
name = db.session.query(db.Authors).filter(db.Authors.id == book_id).first().name
|
||||
|
||||
author_info = None
|
||||
other_books = None
|
||||
if goodreads_support and config.config_use_goodreads:
|
||||
gc = gr_client.GoodreadsClient(config.config_goodreads_api_key, config.config_goodreads_api_secret)
|
||||
gc = GoodreadsClient(config.config_goodreads_api_key, config.config_goodreads_api_secret)
|
||||
author_info = gc.find_author(author_name=name)
|
||||
|
||||
# Get all identifiers (ISBN, Goodreads, etc) and filter author's books by that list so we show fewer duplicates
|
||||
# Note: Not all images will be shown, even though they're available on Goodreads.com.
|
||||
# See https://www.goodreads.com/topic/show/18213769-goodreads-book-images
|
||||
identifiers = reduce(lambda acc, book: acc + map(lambda identifier: identifier.val, book.identifiers), entries.all(), [])
|
||||
other_books = filter(lambda book: book.isbn not in identifiers and book.gid["#text"] not in identifiers, author_info.books)
|
||||
|
||||
return render_title_template('author.html', entries=entries, pagination=pagination,
|
||||
title=name, author=author_info)
|
||||
else:
|
||||
flash(_(u"Error opening eBook. File does not exist or file is not accessible:"), category="error")
|
||||
return redirect(url_for("index"))
|
||||
title=name, author=author_info, other_books=other_books)
|
||||
|
||||
|
||||
@app.route("/series")
|
||||
@login_required_if_no_ano
|
||||
def series_list():
|
||||
if current_user.filter_language() != "all":
|
||||
lang_filter = db.Books.languages.any(db.Languages.lang_code == current_user.filter_language())
|
||||
else:
|
||||
lang_filter = True
|
||||
entries = db.session.query(db.Series, func.count('books_series_link.book').label('count')).join(
|
||||
db.books_series_link).join(db.Books).filter(
|
||||
lang_filter).group_by('books_series_link.series').order_by(db.Series.sort).all()
|
||||
entries = db.session.query(db.Series, func.count('books_series_link.book').label('count'))\
|
||||
.join(db.books_series_link).join(db.Books).filter(common_filters())\
|
||||
.group_by('books_series_link.series').order_by(db.Series.sort).all()
|
||||
return render_title_template('list.html', entries=entries, folder='series', title=_(u"Series list"))
|
||||
|
||||
|
||||
|
@ -1227,13 +1221,9 @@ def language(name, page):
|
|||
@app.route("/category")
|
||||
@login_required_if_no_ano
|
||||
def category_list():
|
||||
if current_user.filter_language() != "all":
|
||||
lang_filter = db.Books.languages.any(db.Languages.lang_code == current_user.filter_language())
|
||||
else:
|
||||
lang_filter = True
|
||||
entries = db.session.query(db.Tags, func.count('books_tags_link.book').label('count')).join(
|
||||
db.books_tags_link).join(db.Books).filter(
|
||||
lang_filter).group_by('books_tags_link.tag').all()
|
||||
entries = db.session.query(db.Tags, func.count('books_tags_link.book').label('count'))\
|
||||
.join(db.books_tags_link).join(db.Books).filter(common_filters())\
|
||||
.group_by('books_tags_link.tag').all()
|
||||
return render_title_template('list.html', entries=entries, folder='category', title=_(u"Category list"))
|
||||
|
||||
|
||||
|
@ -1270,11 +1260,7 @@ def toggle_read(book_id):
|
|||
@app.route("/book/<int:book_id>")
|
||||
@login_required_if_no_ano
|
||||
def show_book(book_id):
|
||||
if current_user.filter_language() != "all":
|
||||
lang_filter = db.Books.languages.any(db.Languages.lang_code == current_user.filter_language())
|
||||
else:
|
||||
lang_filter = True
|
||||
entries = db.session.query(db.Books).filter(db.Books.id == book_id).filter(lang_filter).first()
|
||||
entries = db.session.query(db.Books).filter(db.Books.id == book_id).filter(common_filters()).first()
|
||||
if entries:
|
||||
for index in range(0, len(entries.languages)):
|
||||
try:
|
||||
|
@ -1547,15 +1533,12 @@ def update():
|
|||
def search():
|
||||
term = request.args.get("query").strip()
|
||||
if term:
|
||||
if current_user.filter_language() != "all":
|
||||
lang_filter = db.Books.languages.any(db.Languages.lang_code == current_user.filter_language())
|
||||
else:
|
||||
lang_filter = True
|
||||
entries = db.session.query(db.Books).filter(db.or_(db.Books.tags.any(db.Tags.name.like("%" + term + "%")),
|
||||
db.Books.series.any(db.Series.name.like("%" + term + "%")),
|
||||
db.Books.authors.any(db.Authors.name.like("%" + term + "%")),
|
||||
db.Books.publishers.any(db.Publishers.name.like("%" + term + "%")),
|
||||
db.Books.title.like("%" + term + "%"))).filter(lang_filter).all()
|
||||
db.Books.title.like("%" + term + "%")))\
|
||||
.filter(common_filters()).all()
|
||||
return render_title_template('search.html', searchterm=term, entries=entries)
|
||||
else:
|
||||
return render_title_template('search.html', searchterm="")
|
||||
|
@ -2273,8 +2256,9 @@ def profile():
|
|||
content.sidebar_view += ub.SIDEBAR_READ_AND_UNREAD
|
||||
if "show_detail_random" in to_save:
|
||||
content.sidebar_view += ub.DETAIL_RANDOM
|
||||
if "default_language" in to_save:
|
||||
content.default_language = to_save["default_language"]
|
||||
|
||||
content.mature_content = "show_mature_content" in to_save
|
||||
|
||||
try:
|
||||
ub.session.commit()
|
||||
except IntegrityError:
|
||||
|
@ -2319,7 +2303,7 @@ def configuration_helper(origin):
|
|||
success = False
|
||||
if request.method == "POST":
|
||||
to_save = request.form.to_dict()
|
||||
content = ub.session.query(ub.Settings).first()
|
||||
content = ub.session.query(ub.Settings).first() # type: ub.Settings
|
||||
if "config_calibre_dir" in to_save:
|
||||
if content.config_calibre_dir != to_save["config_calibre_dir"]:
|
||||
content.config_calibre_dir = to_save["config_calibre_dir"]
|
||||
|
@ -2393,6 +2377,9 @@ def configuration_helper(origin):
|
|||
if "config_goodreads_api_secret" in to_save:
|
||||
content.config_goodreads_api_secret = to_save["config_goodreads_api_secret"]
|
||||
|
||||
# Mature Content configuration
|
||||
if "config_mature_content_tags" in to_save:
|
||||
content.config_mature_content_tags = to_save["config_mature_content_tags"].strip()
|
||||
|
||||
content.config_default_role = 0
|
||||
if "admin_role" in to_save:
|
||||
|
@ -2470,6 +2457,7 @@ def new_user():
|
|||
content.nickname = to_save["nickname"]
|
||||
content.email = to_save["email"]
|
||||
content.default_language = to_save["default_language"]
|
||||
content.mature_content = "show_mature_content" in to_save
|
||||
if "locale" in to_save:
|
||||
content.locale = to_save["locale"]
|
||||
content.sidebar_view = 0
|
||||
|
@ -2557,7 +2545,7 @@ def edit_mailsettings():
|
|||
@login_required
|
||||
@admin_required
|
||||
def edit_user(user_id):
|
||||
content = ub.session.query(ub.User).filter(ub.User.id == int(user_id)).first()
|
||||
content = ub.session.query(ub.User).filter(ub.User.id == int(user_id)).first() # type: ub.User
|
||||
downloads = list()
|
||||
languages = db.session.query(db.Languages).all()
|
||||
for lang in languages:
|
||||
|
@ -2665,6 +2653,8 @@ def edit_user(user_id):
|
|||
elif "show_detail_random" not in to_save and content.show_detail_random():
|
||||
content.sidebar_view -= ub.DETAIL_RANDOM
|
||||
|
||||
content.mature_content = "show_mature_content" in to_save
|
||||
|
||||
if "default_language" in to_save:
|
||||
content.default_language = to_save["default_language"]
|
||||
if "locale" in to_save and to_save["locale"]:
|
||||
|
@ -2691,11 +2681,8 @@ def edit_book(book_id):
|
|||
# create the function for sorting...
|
||||
db.session.connection().connection.connection.create_function("title_sort", 1, db.title_sort)
|
||||
cc = db.session.query(db.Custom_Columns).filter(db.Custom_Columns.datatype.notin_(db.cc_exceptions)).all()
|
||||
if current_user.filter_language() != "all":
|
||||
lang_filter = db.Books.languages.any(db.Languages.lang_code == current_user.filter_language())
|
||||
else:
|
||||
lang_filter = True
|
||||
book = db.session.query(db.Books).filter(db.Books.id == book_id).filter(lang_filter).first()
|
||||
book = db.session.query(db.Books)\
|
||||
.filter(db.Books.id == book_id).filter(common_filters()).first()
|
||||
author_names = []
|
||||
|
||||
# Book not found
|
||||
|
@ -2738,18 +2725,7 @@ def edit_book(book_id):
|
|||
edited_books_id.add(book.id)
|
||||
book.author_sort = helper.get_sorted_author(input_authors[0])
|
||||
|
||||
if to_save["cover_url"] and os.path.splitext(to_save["cover_url"])[1].lower() == ".jpg":
|
||||
img = requests.get(to_save["cover_url"])
|
||||
if config.config_use_google_drive:
|
||||
tmpDir = tempfile.gettempdir()
|
||||
f = open(os.path.join(tmpDir, "uploaded_cover.jpg"), "wb")
|
||||
f.write(img.content)
|
||||
f.close()
|
||||
gdriveutils.uploadFileToEbooksFolder(Gdrive.Instance().drive, os.path.join(book.path, 'cover.jpg'), os.path.join(tmpDir, f.name))
|
||||
else:
|
||||
f = open(os.path.join(config.config_calibre_dir, book.path, "cover.jpg"), "wb")
|
||||
f.write(img.content)
|
||||
f.close()
|
||||
if to_save["cover_url"] and save_cover(to_save["cover_url"], book.path):
|
||||
book.has_cover = 1
|
||||
|
||||
if book.series_index != to_save["series_index"]:
|
||||
|
@ -2901,6 +2877,25 @@ def edit_book(book_id):
|
|||
title=_(u"edit metadata"))
|
||||
|
||||
|
||||
def save_cover(url, book_path):
|
||||
img = requests.get(url)
|
||||
if img.headers.get('content-type') != 'image/jpeg':
|
||||
return false
|
||||
|
||||
if config.config_use_google_drive:
|
||||
tmpDir = tempfile.gettempdir()
|
||||
f = open(os.path.join(tmpDir, "uploaded_cover.jpg"), "wb")
|
||||
f.write(img.content)
|
||||
f.close()
|
||||
gdriveutils.uploadFileToEbooksFolder(Gdrive.Instance().drive, os.path.join(book_path, 'cover.jpg'), os.path.join(tmpDir, f.name))
|
||||
return true
|
||||
|
||||
f = open(os.path.join(config.config_calibre_dir, book_path, "cover.jpg"), "wb")
|
||||
f.write(img.content)
|
||||
f.close()
|
||||
return true
|
||||
|
||||
|
||||
@app.route("/upload", methods=["GET", "POST"])
|
||||
@login_required_if_no_ano
|
||||
@upload_required
|
||||
|
@ -2928,12 +2923,14 @@ def upload():
|
|||
|
||||
title = meta.title
|
||||
author = meta.author
|
||||
|
||||
title_dir = helper.get_valid_filename(title, False)
|
||||
author_dir = helper.get_valid_filename(author, False)
|
||||
tags = meta.tags
|
||||
series = meta.series
|
||||
series_index = meta.series_id
|
||||
title_dir = helper.get_valid_filename(title)
|
||||
author_dir = helper.get_valid_filename(author)
|
||||
data_name = title_dir
|
||||
filepath = config.config_calibre_dir + os.sep + author_dir + os.sep + title_dir
|
||||
saved_filename = filepath + os.sep + data_name + meta.extension
|
||||
saved_filename = filepath + os.sep + data_name + meta.extension.lower()
|
||||
|
||||
if not os.path.exists(filepath):
|
||||
try:
|
||||
|
@ -2967,6 +2964,14 @@ def upload():
|
|||
db_author = db.Authors(author, helper.get_sorted_author(author), "")
|
||||
db.session.add(db_author)
|
||||
|
||||
db_series = None
|
||||
is_series = db.session.query(db.Series).filter(db.Series.name == series).first()
|
||||
if is_series:
|
||||
db_series = is_series
|
||||
elif series != '':
|
||||
db_series = db.Series(series, "")
|
||||
db.session.add(db_series)
|
||||
|
||||
# add language actually one value in list
|
||||
input_language = meta.languages
|
||||
db_language = None
|
||||
|
@ -2980,9 +2985,11 @@ def upload():
|
|||
db.session.add(db_language)
|
||||
# combine path and normalize path from windows systems
|
||||
path = os.path.join(author_dir, title_dir).replace('\\', '/')
|
||||
db_book = db.Books(title, "", db_author.sort, datetime.datetime.now(), datetime.datetime(101, 1, 1), 1,
|
||||
datetime.datetime.now(), path, has_cover, db_author, [], db_language)
|
||||
db_book = db.Books(title, "", db_author.sort, datetime.datetime.now(), datetime.datetime(101, 1, 1),
|
||||
series_index, datetime.datetime.now(), path, has_cover, db_author, [], db_language)
|
||||
db_book.authors.append(db_author)
|
||||
if db_series:
|
||||
db_book.series.append(db_series)
|
||||
if db_language is not None:
|
||||
db_book.languages.append(db_language)
|
||||
db_data = db.Data(db_book, meta.extension.upper()[1:], file_size, data_name)
|
||||
|
@ -2995,6 +3002,11 @@ def upload():
|
|||
if upload_comment != "":
|
||||
db.session.add(db.Comments(upload_comment, db_book.id))
|
||||
db.session.commit()
|
||||
|
||||
input_tags = tags.split(',')
|
||||
input_tags = map(lambda it: it.strip(), input_tags)
|
||||
modify_database_object(input_tags, db_book.tags, db.Tags, db.session, 'tags')
|
||||
|
||||
if db_language is not None: # display Full name instead of iso639.part3
|
||||
db_book.languages[0].language_name = _(meta.languages)
|
||||
author_names = []
|
||||
|
|
410
messages.pot
410
messages.pot
File diff suppressed because it is too large
Load Diff
|
@ -30,7 +30,7 @@ Calibre Web is a web app providing a clean interface for browsing, reading and d
|
|||
|
||||
## Quick start
|
||||
|
||||
1. Install required dependencies by executing `pip install -r requirements.txt`
|
||||
1. Install dependencies by running `pip install --target vendor -r requirements.txt`.
|
||||
2. Execute the command: `python cps.py` (or `nohup python cps.py` - recommended if you want to exit the terminal window)
|
||||
3. Point your browser to `http://localhost:8083` or `http://localhost:8083/opds` for the OPDS catalog
|
||||
4. Set `Location of Calibre database` to the path of the folder where your Calibre library (metadata.db) lives, push "submit" button
|
||||
|
@ -70,7 +70,7 @@ Optionally, to enable on-the-fly conversion from EPUB to MOBI when using the sen
|
|||
|
||||
## Using Google Drive integration
|
||||
|
||||
Additional optional dependencys are necessary to get this work. Please install all optional requirements by executing `pip install -r optional-requirements.txt`
|
||||
Additional optional dependencys are necessary to get this work. Please install all optional requirements by executing `pip install --target vendor -r optional-requirements.txt`
|
||||
|
||||
To use google drive integration, you have to use the google developer console to create a new app. https://console.developers.google.com
|
||||
|
||||
|
|
0
vendor/.gitempty
vendored
0
vendor/.gitempty
vendored
Loading…
Reference in New Issue
Block a user