Merge branch 'master' into Develop
This commit is contained in:
@@ -193,7 +193,7 @@ class Identifiers(Base):
|
||||
elif format_type == "issn":
|
||||
return "https://portal.issn.org/resource/ISSN/{0}".format(self.val)
|
||||
elif format_type == "isfdb":
|
||||
return "http://www.isfdb.org/cgi-bin/pl.cgi?{0}".format(self.val)
|
||||
return "https://www.isfdb.org/cgi-bin/pl.cgi?{0}".format(self.val)
|
||||
elif format_type == "databazeknih":
|
||||
return "https://www.databazeknih.cz/knihy/{0}".format(self.val)
|
||||
elif format_type == "storygraph":
|
||||
|
||||
+1
-1
@@ -461,7 +461,7 @@ def get_sorted_entry(field, bookid):
|
||||
return make_response(jsonify(authors=" & ".join([a.name for a in calibre_db.order_authors([book])])))
|
||||
return ""
|
||||
|
||||
|
||||
|
||||
@editbook.route("/ajax/simulatemerge", methods=['POST'])
|
||||
@user_login_required
|
||||
@edit_required
|
||||
|
||||
+2
-2
@@ -328,7 +328,7 @@ def generate_sync_response(sync_token, sync_results, set_cont=False):
|
||||
sync_token.to_headers(extra_headers)
|
||||
|
||||
# log.debug("Kobo Sync Content: {}".format(sync_results))
|
||||
# jsonify decodes the unicode string different to what kobo expects
|
||||
# jsonify decodes the Unicode string different to what kobo expects
|
||||
response = make_response(json.dumps(sync_results), extra_headers)
|
||||
response.headers["Content-Type"] = "application/json; charset=utf-8"
|
||||
return response
|
||||
@@ -732,7 +732,7 @@ def sync_shelves(sync_token, sync_results, only_kobo_shelves=False):
|
||||
ub.session_commit()
|
||||
|
||||
|
||||
# Creates a Kobo "Tag" object from a ub.Shelf object
|
||||
# Creates a Kobo "Tag" object from an ub.Shelf object
|
||||
def create_kobo_tag(shelf):
|
||||
tag = {
|
||||
"Created": convert_to_kobo_timestamp_string(shelf.created),
|
||||
|
||||
+2
-2
@@ -22,7 +22,7 @@
|
||||
This module also includes research notes into the auth protocol used by Kobo devices.
|
||||
|
||||
Log-in:
|
||||
When first booting a Kobo device the user must sign into a Kobo (or affiliate) account.
|
||||
When first booting a Kobo device the user must log in to a Kobo (or affiliate) account.
|
||||
Upon successful sign-in, the user is redirected to
|
||||
https://auth.kobobooks.com/CrossDomainSignIn?id=<some id>
|
||||
which serves the following response:
|
||||
@@ -41,7 +41,7 @@ issue for a few years now https://www.mobileread.com/forums/showpost.php?p=34768
|
||||
will still grant access given the userkey.)
|
||||
|
||||
Official Kobo Store Api authorization:
|
||||
* For most of the endpoints we care about (sync, metadata, tags, etc), the userKey is
|
||||
* For most of the endpoints we care about (sync, metadata, tags, etc.), the userKey is
|
||||
passed in the x-kobo-userkey header, and is sufficient to authorize the API call.
|
||||
* Some endpoints (e.g: AnnotationService) instead make use of Bearer tokens pass through
|
||||
an authorization header. To get a BearerToken, the device makes a POST request to the
|
||||
|
||||
+1
-1
@@ -32,7 +32,7 @@ class OAuthBackend(SQLAlchemyBackend):
|
||||
Stores and retrieves OAuth tokens using a relational database through
|
||||
the `SQLAlchemy`_ ORM.
|
||||
|
||||
.. _SQLAlchemy: https://www.sqlalchemy.org/
|
||||
_SQLAlchemy: https://www.sqlalchemy.org/
|
||||
"""
|
||||
def __init__(self, model, session, provider_id,
|
||||
user=None, user_id=None, user_required=None, anon_user=None,
|
||||
|
||||
+2
-2
@@ -41,9 +41,9 @@ class ReverseProxied(object):
|
||||
"""Wrap the application in this middleware and configure the
|
||||
front-end server to add these headers, to let you quietly bind
|
||||
this to a URL other than / and to an HTTP scheme that is
|
||||
different than what is used locally.
|
||||
different from what is used locally.
|
||||
|
||||
Code courtesy of: http://flask.pocoo.org/snippets/35/
|
||||
Code courtesy of: https://flask.pocoo.org/snippets/35/
|
||||
|
||||
In nginx:
|
||||
location /myprefix {
|
||||
|
||||
@@ -92,7 +92,7 @@ def send_messsage(token, msg):
|
||||
if creds and creds.expired and creds.refresh_token:
|
||||
creds.refresh(Request())
|
||||
service = build('gmail', 'v1', credentials=creds)
|
||||
message_as_bytes = msg.as_bytes() # the message should converted from string to bytes.
|
||||
message_as_bytes = msg.as_bytes() # the message should be converted from string to bytes.
|
||||
message_as_base64 = base64.urlsafe_b64encode(message_as_bytes) # encode in base64 (printable letters coding)
|
||||
raw = message_as_base64.decode() # convert to something JSON serializable
|
||||
body = {'raw': raw}
|
||||
|
||||
@@ -117,7 +117,7 @@ def get_author_info(author_name):
|
||||
|
||||
|
||||
def get_other_books(author_info, library_books=None):
|
||||
# Get all identifiers (ISBN, Goodreads, etc) and filter author's books by that list so we show fewer duplicates
|
||||
# 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
|
||||
|
||||
|
||||
@@ -235,7 +235,7 @@ class CalibreTask:
|
||||
|
||||
@property
|
||||
def dead(self):
|
||||
"""Determines whether or not this task can be garbage collected
|
||||
"""Determines whether this task can be garbage collected
|
||||
|
||||
We have a separate dictating this because there may be certain tasks that want to override this
|
||||
"""
|
||||
|
||||
Vendored
-1
@@ -157,7 +157,6 @@ fieldset[disabled] .twitter-typeahead .tt-input {
|
||||
list-style: none;
|
||||
font-size: 14px;
|
||||
background-color: #ffffff;
|
||||
border: 1px solid #cccccc;
|
||||
border: 1px solid rgba(0, 0, 0, 0.15);
|
||||
border-radius: 4px;
|
||||
-webkit-box-shadow: 0 6px 12px rgba(0, 0, 0, 0.175);
|
||||
|
||||
Vendored
+1
-1
File diff suppressed because one or more lines are too long
@@ -183,7 +183,7 @@ try {
|
||||
];
|
||||
$.each(sequences, function(ignore, sequence) {
|
||||
for (j = 0; j < word.length - 2; j += 1) {
|
||||
// iterate the word trough a sliding window of size 3:
|
||||
// iterate the word through a sliding window of size 3:
|
||||
if (
|
||||
sequence.indexOf(
|
||||
word.toLowerCase().substring(j, j + 3)
|
||||
|
||||
@@ -49,7 +49,7 @@ function elementSorter(a, b) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
// Generic control/related handler to show/hide fields based on a checkbox' value
|
||||
// Generic control/related handler to show/hide fields based on a 'checkbox' value
|
||||
// e.g.
|
||||
// <input type="checkbox" data-control="stuff-to-show">
|
||||
// <div data-related="stuff-to-show">...</div>
|
||||
@@ -63,7 +63,7 @@ $(document).on("change", "input[type=\"checkbox\"][data-control]", function () {
|
||||
});
|
||||
});
|
||||
|
||||
// Generic control/related handler to show/hide fields based on a select' value
|
||||
// Generic control/related handler to show/hide fields based on a 'select' value
|
||||
$(document).on("change", "select[data-control]", function() {
|
||||
var $this = $(this);
|
||||
var name = $this.data("control");
|
||||
@@ -79,7 +79,7 @@ $(document).on("change", "select[data-control]", function() {
|
||||
}
|
||||
});
|
||||
|
||||
// Generic control/related handler to show/hide fields based on a select' value
|
||||
// Generic control/related handler to show/hide fields based on a 'select' value
|
||||
// this one is made to show all values if select value is not 0
|
||||
$(document).on("change", "select[data-controlall]", function() {
|
||||
var $this = $(this);
|
||||
|
||||
@@ -1,54 +0,0 @@
|
||||
/**
|
||||
* waits until queue is finished, meaning the book is done loading
|
||||
* @param callback
|
||||
*/
|
||||
function qFinished(callback){
|
||||
let timeout=setInterval(()=>{
|
||||
if(reader.rendition.q.running===undefined)
|
||||
clearInterval(timeout);
|
||||
callback();
|
||||
},300
|
||||
)
|
||||
}
|
||||
|
||||
function calculateProgress(){
|
||||
let data=reader.rendition.location.end;
|
||||
return Math.round(epub.locations.percentageFromCfi(data.cfi)*100);
|
||||
}
|
||||
|
||||
// register new event emitter locationchange that fires on urlchange
|
||||
// source: https://stackoverflow.com/a/52809105/21941129
|
||||
(() => {
|
||||
let oldPushState = history.pushState;
|
||||
history.pushState = function pushState() {
|
||||
let ret = oldPushState.apply(this, arguments);
|
||||
window.dispatchEvent(new Event('locationchange'));
|
||||
return ret;
|
||||
};
|
||||
|
||||
let oldReplaceState = history.replaceState;
|
||||
history.replaceState = function replaceState() {
|
||||
let ret = oldReplaceState.apply(this, arguments);
|
||||
window.dispatchEvent(new Event('locationchange'));
|
||||
return ret;
|
||||
};
|
||||
|
||||
window.addEventListener('popstate', () => {
|
||||
window.dispatchEvent(new Event('locationchange'));
|
||||
});
|
||||
})();
|
||||
|
||||
window.addEventListener('locationchange',()=>{
|
||||
let newPos=calculateProgress();
|
||||
progressDiv.textContent=newPos+"%";
|
||||
});
|
||||
|
||||
var epub=ePub(calibre.bookUrl)
|
||||
|
||||
let progressDiv=document.getElementById("progress");
|
||||
|
||||
qFinished(()=>{
|
||||
epub.locations.generate().then(()=> {
|
||||
window.dispatchEvent(new Event('locationchange'))
|
||||
});
|
||||
})
|
||||
@@ -52,6 +52,32 @@ var reader;
|
||||
}
|
||||
});
|
||||
|
||||
// Update progress percentage
|
||||
let progressDiv = document.getElementById("progress");
|
||||
reader.book.ready.then((()=>{
|
||||
let locations_key = reader.book.key()+'-locations';
|
||||
let stored_locations = localStorage.getItem(locations_key);
|
||||
let make_locations, save_locations;
|
||||
if (stored_locations) {
|
||||
make_locations = Promise.resolve(reader.book.locations.load(stored_locations));
|
||||
// No-op because locations are already saved
|
||||
save_locations = ()=>{};
|
||||
} else {
|
||||
make_locations = reader.book.locations.generate();
|
||||
save_locations = ()=>{
|
||||
localStorage.setItem(locations_key, reader.book.locations.save());
|
||||
};
|
||||
}
|
||||
make_locations.then(()=>{
|
||||
reader.rendition.on('relocated', (location)=>{
|
||||
let percentage = Math.round(location.end.percentage*100);
|
||||
progressDiv.textContent=percentage+"%";
|
||||
});
|
||||
reader.rendition.reportLocation();
|
||||
progressDiv.style.visibility = "visible";
|
||||
}).then(save_locations);
|
||||
}));
|
||||
|
||||
/**
|
||||
* @param {string} action - Add or remove bookmark
|
||||
* @param {string|int} location - Location or zero
|
||||
|
||||
@@ -0,0 +1,21 @@
|
||||
// register new event emitter locationchange that fires on urlchange
|
||||
// source: https://stackoverflow.com/a/52809105/21941129
|
||||
(() => {
|
||||
let oldPushState = history.pushState;
|
||||
history.pushState = function pushState() {
|
||||
let ret = oldPushState.apply(this, arguments);
|
||||
window.dispatchEvent(new Event('locationchange'));
|
||||
return ret;
|
||||
};
|
||||
|
||||
let oldReplaceState = history.replaceState;
|
||||
history.replaceState = function replaceState() {
|
||||
let ret = oldReplaceState.apply(this, arguments);
|
||||
window.dispatchEvent(new Event('locationchange'));
|
||||
return ret;
|
||||
};
|
||||
|
||||
window.addEventListener('popstate', () => {
|
||||
window.dispatchEvent(new Event('locationchange'));
|
||||
});
|
||||
})();
|
||||
+2
-2
@@ -55,7 +55,7 @@ class EmailBase:
|
||||
return (code, resp)
|
||||
|
||||
def send(self, strg):
|
||||
"""Send `strg' to the server."""
|
||||
"""Send 'strg' to the server."""
|
||||
log.debug_no_auth('send: {}'.format(strg[:300]), stacklevel=2)
|
||||
if hasattr(self, 'sock') and self.sock:
|
||||
try:
|
||||
@@ -102,7 +102,7 @@ class Email(EmailBase, smtplib.SMTP):
|
||||
smtplib.SMTP.__init__(self, *args, **kwargs)
|
||||
|
||||
|
||||
# Class for sending ssl encrypted email with ability to get current progress, , derived from emailbase class
|
||||
# Class for sending ssl encrypted email with ability to get current progress, derived from emailbase class
|
||||
class EmailSSL(EmailBase, smtplib.SMTP_SSL):
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
|
||||
@@ -175,6 +175,7 @@
|
||||
// Apply theme to rest of the page.
|
||||
document.getElementById("main").style.backgroundColor = themes[id]["bgColor"];
|
||||
document.getElementById("titlebar").style.color = themes[id]["title-color"] || "#fff";
|
||||
document.getElementById("progress").style.color = themes[id]["title-color"] || "#fff";
|
||||
}
|
||||
|
||||
// font size settings logic
|
||||
@@ -215,6 +216,6 @@
|
||||
<script src="{{ url_for('static', filename='js/libs/screenfull.min.js') }}"></script>
|
||||
<script src="{{ url_for('static', filename='js/libs/reader.min.js') }}"></script>
|
||||
<script src="{{ url_for('static', filename='js/reading/epub.js') }}"></script>
|
||||
<script src="{{ url_for('static', filename='js/reading/epub-progress.js') }}"></script>
|
||||
<!--script src="{{ url_for('static', filename='js/reading/locationchange-polyfill.js') }}"></script-->
|
||||
</body>
|
||||
</html>
|
||||
|
||||
@@ -233,7 +233,7 @@ class UserBase:
|
||||
return '<User %r>' % self.name
|
||||
|
||||
|
||||
# Baseclass for Users in Calibre-Web, settings which are depending on certain users are stored here. It is derived from
|
||||
# Baseclass for Users in Calibre-Web, settings which depend on certain users are stored here. It is derived from
|
||||
# User Base (all access methods are declared there)
|
||||
class User(UserBase, Base):
|
||||
__tablename__ = 'user'
|
||||
@@ -601,7 +601,7 @@ def migrate_user_session_table(engine, _session):
|
||||
trans.commit()
|
||||
|
||||
|
||||
# Migrate database to current version, has to be updated after every database change. Currently migration from
|
||||
# Migrate database to current version, has to be updated after every database change. Currently, migration from
|
||||
# maybe 4/5 versions back to current should work.
|
||||
# Migration is done by checking if relevant columns are existing, and then adding rows with SQL commands
|
||||
def migrate_Database(_session):
|
||||
@@ -636,7 +636,7 @@ def update_download(book_id, user_id):
|
||||
session.rollback()
|
||||
|
||||
|
||||
# Delete non existing downloaded books in calibre-web's own database
|
||||
# Delete non-existing downloaded books in calibre-web's own database
|
||||
def delete_download(book_id):
|
||||
session.query(Downloads).filter(book_id == Downloads.book_id).delete()
|
||||
try:
|
||||
|
||||
+2
-2
@@ -60,7 +60,7 @@ from .services.worker import WorkerThread
|
||||
from .tasks_status import render_task_status
|
||||
from .usermanagement import user_login_required
|
||||
from .string_helper import strip_whitespaces
|
||||
import traceback
|
||||
|
||||
|
||||
feature_support = {
|
||||
'ldap': bool(services.ldap),
|
||||
@@ -112,7 +112,7 @@ def add_security_headers(resp):
|
||||
resp.headers['X-Content-Type-Options'] = 'nosniff'
|
||||
resp.headers['X-Frame-Options'] = 'SAMEORIGIN'
|
||||
resp.headers['X-XSS-Protection'] = '1; mode=block'
|
||||
resp.headers['Strict-Transport-Security'] = 'max-age=31536000';
|
||||
resp.headers['Strict-Transport-Security'] = 'max-age=31536000'
|
||||
return resp
|
||||
|
||||
|
||||
|
||||
Reference in New Issue
Block a user