mirror of
https://github.com/kovidgoyal/calibre.git
synced 2025-07-09 03:04:10 -04:00
Encode the current book position in the URL
Allows direct linking to arbitrary locations in a book hosted in the calibre server
This commit is contained in:
parent
49d1ea8bb2
commit
3ca4670ce9
@ -4,6 +4,17 @@ from __python__ import hash_literals
|
|||||||
|
|
||||||
from gettext import gettext as _
|
from gettext import gettext as _
|
||||||
|
|
||||||
|
def encode_query_component(x):
|
||||||
|
ans = encodeURIComponent(x)
|
||||||
|
# The following exceptions are to make epubcfi() look better
|
||||||
|
ans = ans.replace(/%2[fF]/g, '/')
|
||||||
|
ans = ans.replace(/%40/g, '@')
|
||||||
|
ans = ans.replace(/%5[bB]/g, '[')
|
||||||
|
ans = ans.replace(/%5[dD]/g, ']')
|
||||||
|
ans = ans.replace(/%5[eE]/g, '^')
|
||||||
|
ans = ans.replace(/%3[aA]/g, ':')
|
||||||
|
return ans
|
||||||
|
|
||||||
def encode_query(query):
|
def encode_query(query):
|
||||||
if not query:
|
if not query:
|
||||||
return ''
|
return ''
|
||||||
@ -15,7 +26,7 @@ def encode_query(query):
|
|||||||
val = query[k]
|
val = query[k]
|
||||||
if val is undefined or val is None:
|
if val is undefined or val is None:
|
||||||
continue
|
continue
|
||||||
path += ('&' if has_query else '?') + encodeURIComponent(k) + '=' + encodeURIComponent(val.toString())
|
path += ('&' if has_query else '?') + encodeURIComponent(k) + '=' + encode_query_component(val.toString())
|
||||||
has_query = True
|
has_query = True
|
||||||
return path
|
return path
|
||||||
|
|
||||||
|
@ -42,10 +42,16 @@ class Boss:
|
|||||||
if not data.mode or data.mode is 'book_list':
|
if not data.mode or data.mode is 'book_list':
|
||||||
if data.panel is not self.ui.current_panel:
|
if data.panel is not self.ui.current_panel:
|
||||||
self.ui.show_panel(data.panel, push_state=False)
|
self.ui.show_panel(data.panel, push_state=False)
|
||||||
elif data.mode is 'read_book':
|
if data.mode is 'read_book':
|
||||||
self.current_mode = data.mode
|
try:
|
||||||
self.apply_mode()
|
book_id = int(data.book_id)
|
||||||
self.read_book(int(data.book_id), data.fmt)
|
except Exception:
|
||||||
|
book_id = None
|
||||||
|
if book_id is None:
|
||||||
|
if data.panel is not self.ui.current_panel:
|
||||||
|
self.ui.show_panel(data.panel, push_state=False)
|
||||||
|
else:
|
||||||
|
self.read_book(book_id, data.fmt)
|
||||||
setTimeout(def():
|
setTimeout(def():
|
||||||
window.onpopstate = self.onpopstate.bind(self)
|
window.onpopstate = self.onpopstate.bind(self)
|
||||||
, 0) # We do this after event loop ticks over to avoid catching popstate events that some browsers send on page load
|
, 0) # We do this after event loop ticks over to avoid catching popstate events that some browsers send on page load
|
||||||
@ -103,7 +109,6 @@ class Boss:
|
|||||||
def read_book(self, book_id, fmt, metadata):
|
def read_book(self, book_id, fmt, metadata):
|
||||||
self.current_mode = 'read_book'
|
self.current_mode = 'read_book'
|
||||||
self.apply_mode()
|
self.apply_mode()
|
||||||
self.push_state(extra_query_data={'book_id':book_id, 'fmt':fmt})
|
|
||||||
self.read_ui.load_book(book_id, fmt, metadata)
|
self.read_ui.load_book(book_id, fmt, metadata)
|
||||||
|
|
||||||
def change_books(self, data):
|
def change_books(self, data):
|
||||||
@ -131,6 +136,10 @@ class Boss:
|
|||||||
query.search = sq
|
query.search = sq
|
||||||
else:
|
else:
|
||||||
query.mode = self.current_mode
|
query.mode = self.current_mode
|
||||||
|
if self.current_mode is 'read_book':
|
||||||
|
eqd = self.read_ui.url_data
|
||||||
|
for k in eqd:
|
||||||
|
query[k] = eqd[k]
|
||||||
if idata.library_id is not idata.default_library:
|
if idata.library_id is not idata.default_library:
|
||||||
query.library_id = idata.library_id
|
query.library_id = idata.library_id
|
||||||
set_current_query(query)
|
set_current_query(query)
|
||||||
|
@ -94,7 +94,7 @@ class DB:
|
|||||||
'manifest': None,
|
'manifest': None,
|
||||||
'cover_width': None,
|
'cover_width': None,
|
||||||
'cover_height': None,
|
'cover_height': None,
|
||||||
'last_read_position': None,
|
'last_read_position': {},
|
||||||
})
|
})
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -5,20 +5,29 @@ from __python__ import bound_methods, hash_literals
|
|||||||
import traceback
|
import traceback
|
||||||
from aes import GCM
|
from aes import GCM
|
||||||
from gettext import install, gettext as _
|
from gettext import install, gettext as _
|
||||||
|
from read_book.cfi import at_current, scroll_to as scroll_to_cfi
|
||||||
from read_book.globals import set_boss, set_current_spine_item, current_layout_mode, current_spine_item, set_layout_mode
|
from read_book.globals import set_boss, set_current_spine_item, current_layout_mode, current_spine_item, set_layout_mode
|
||||||
from read_book.mathjax import apply_mathjax
|
from read_book.mathjax import apply_mathjax
|
||||||
from read_book.resources import finalize_resources, unserialize_html
|
from read_book.resources import finalize_resources, unserialize_html
|
||||||
from read_book.flow_mode import flow_to_scroll_fraction, flow_onwheel, flow_onkeydown, layout as flow_layout
|
from read_book.flow_mode import (
|
||||||
from read_book.paged_mode import layout as paged_layout, scroll_to_fraction as paged_scroll_to_fraction, onwheel as paged_onwheel, onkeydown as paged_onkeydown, scroll_to_elem
|
flow_to_scroll_fraction, flow_onwheel, flow_onkeydown, layout as flow_layout
|
||||||
|
)
|
||||||
|
from read_book.paged_mode import (
|
||||||
|
layout as paged_layout, scroll_to_fraction as paged_scroll_to_fraction,
|
||||||
|
onwheel as paged_onwheel, onkeydown as paged_onkeydown, scroll_to_elem,
|
||||||
|
jump_to_cfi as paged_jump_to_cfi
|
||||||
|
)
|
||||||
from read_book.settings import apply_settings
|
from read_book.settings import apply_settings
|
||||||
from utils import debounce
|
from utils import debounce
|
||||||
|
|
||||||
FORCE_FLOW_MODE = False
|
FORCE_FLOW_MODE = False
|
||||||
|
|
||||||
class Boss:
|
class IframeBoss:
|
||||||
|
|
||||||
def __init__(self):
|
def __init__(self):
|
||||||
self.ready_sent = False
|
self.ready_sent = False
|
||||||
|
self.last_cfi = None
|
||||||
|
self.replace_history_on_next_cfi_update = True
|
||||||
self.encrypted_communications = False
|
self.encrypted_communications = False
|
||||||
window.addEventListener('message', self.handle_message, False)
|
window.addEventListener('message', self.handle_message, False)
|
||||||
window.addEventListener('load', def():
|
window.addEventListener('load', def():
|
||||||
@ -83,13 +92,16 @@ class Boss:
|
|||||||
self.handle_wheel = flow_onwheel
|
self.handle_wheel = flow_onwheel
|
||||||
self.handle_keydown = flow_onkeydown
|
self.handle_keydown = flow_onkeydown
|
||||||
self.to_scroll_fraction = flow_to_scroll_fraction
|
self.to_scroll_fraction = flow_to_scroll_fraction
|
||||||
|
self.jump_to_cfi = scroll_to_cfi
|
||||||
else:
|
else:
|
||||||
self.do_layout = paged_layout
|
self.do_layout = paged_layout
|
||||||
self.handle_wheel = paged_onwheel
|
self.handle_wheel = paged_onwheel
|
||||||
self.handle_keydown = paged_onkeydown
|
self.handle_keydown = paged_onkeydown
|
||||||
self.to_scroll_fraction = paged_scroll_to_fraction
|
self.to_scroll_fraction = paged_scroll_to_fraction
|
||||||
|
self.jump_to_cfi = paged_jump_to_cfi
|
||||||
apply_settings(data.settings)
|
apply_settings(data.settings)
|
||||||
set_current_spine_item({'name':data.name, 'is_first':index is 0, 'is_last':index is spine.length - 1, 'initial_position':data.initial_position})
|
set_current_spine_item({'name':data.name, 'is_first':index is 0, 'is_last':index is spine.length - 1, 'initial_position':data.initial_position})
|
||||||
|
self.last_cfi = None
|
||||||
root_data, self.mathjax = finalize_resources(self.book, data.name, data.resource_data)
|
root_data, self.mathjax = finalize_resources(self.book, data.name, data.resource_data)
|
||||||
unserialize_html(root_data, self.content_loaded)
|
unserialize_html(root_data, self.content_loaded)
|
||||||
|
|
||||||
@ -109,13 +121,26 @@ class Boss:
|
|||||||
csi = current_spine_item()
|
csi = current_spine_item()
|
||||||
if csi.initial_position:
|
if csi.initial_position:
|
||||||
ipos = csi.initial_position
|
ipos = csi.initial_position
|
||||||
|
self.replace_history_on_next_cfi_update = ipos.replace_history
|
||||||
if ipos.type is 'frac':
|
if ipos.type is 'frac':
|
||||||
self.to_scroll_fraction(ipos.frac)
|
self.to_scroll_fraction(ipos.frac)
|
||||||
elif ipos.type is 'anchor':
|
elif ipos.type is 'anchor':
|
||||||
self.scroll_to_anchor(ipos.anchor)
|
self.scroll_to_anchor(ipos.anchor)
|
||||||
|
elif ipos.type is 'cfi':
|
||||||
|
self.jump_to_cfi(ipos.cfi)
|
||||||
|
self.update_cfi()
|
||||||
|
|
||||||
def update_cfi(self):
|
def update_cfi(self):
|
||||||
pass # TODO: Update CFI
|
cfi = at_current()
|
||||||
|
if cfi:
|
||||||
|
spine = self.book.manifest.spine
|
||||||
|
index = spine.indexOf(current_spine_item().name)
|
||||||
|
if index > -1:
|
||||||
|
cfi = 'epubcfi(/{}{})'.format(2*(index+1), cfi)
|
||||||
|
if cfi != self.last_cfi:
|
||||||
|
self.last_cfi = cfi
|
||||||
|
self.send_message('update_cfi', cfi=cfi, replace_history=self.replace_history_on_next_cfi_update)
|
||||||
|
self.replace_history_on_next_cfi_update = True
|
||||||
|
|
||||||
def onresize(self):
|
def onresize(self):
|
||||||
if current_layout_mode() is not 'flow':
|
if current_layout_mode() is not 'flow':
|
||||||
@ -151,6 +176,7 @@ class Boss:
|
|||||||
if not name:
|
if not name:
|
||||||
name = current_spine_item().name
|
name = current_spine_item().name
|
||||||
if name is current_spine_item().name:
|
if name is current_spine_item().name:
|
||||||
|
self.replace_history_on_next_cfi_update = False
|
||||||
self.scroll_to_anchor(frag)
|
self.scroll_to_anchor(frag)
|
||||||
else:
|
else:
|
||||||
self.send_message('scroll_to_anchor', name=name, frag=frag)
|
self.send_message('scroll_to_anchor', name=name, frag=frag)
|
||||||
@ -168,4 +194,4 @@ class Boss:
|
|||||||
def init():
|
def init():
|
||||||
script = document.getElementById('bootstrap')
|
script = document.getElementById('bootstrap')
|
||||||
script.parentNode.removeChild(script) # free up some memory
|
script.parentNode.removeChild(script) # free up some memory
|
||||||
Boss()
|
IframeBoss()
|
||||||
|
@ -96,11 +96,20 @@ class ReadUI:
|
|||||||
div.lastChild.textContent = msg or ''
|
div.lastChild.textContent = msg or ''
|
||||||
|
|
||||||
def load_book(self, book_id, fmt, metadata):
|
def load_book(self, book_id, fmt, metadata):
|
||||||
|
self.base_url_data = {'book_id':book_id, 'fmt':fmt}
|
||||||
if self.db is None:
|
if self.db is None:
|
||||||
self.pending_load = [book_id, fmt, metadata]
|
self.pending_load = [book_id, fmt, metadata]
|
||||||
return
|
return
|
||||||
self.start_load(book_id, fmt, metadata)
|
self.start_load(book_id, fmt, metadata)
|
||||||
|
|
||||||
|
@property
|
||||||
|
def url_data(self):
|
||||||
|
ans = {'book_id':self.base_url_data.book_id, 'fmt': self.base_url_data.fmt}
|
||||||
|
bookpos = self.view.currently_showing.bookpos
|
||||||
|
if bookpos:
|
||||||
|
ans.bookpos = bookpos
|
||||||
|
return ans
|
||||||
|
|
||||||
def db_initialized(self, db):
|
def db_initialized(self, db):
|
||||||
self.db = db
|
self.db = db
|
||||||
if self.pending_load is not None:
|
if self.pending_load is not None:
|
||||||
|
@ -2,13 +2,14 @@
|
|||||||
# License: GPL v3 Copyright: 2016, Kovid Goyal <kovid at kovidgoyal.net>
|
# License: GPL v3 Copyright: 2016, Kovid Goyal <kovid at kovidgoyal.net>
|
||||||
from __python__ import bound_methods, hash_literals
|
from __python__ import bound_methods, hash_literals
|
||||||
|
|
||||||
from book_list.globals import get_session_data
|
from book_list.globals import get_session_data, get_boss
|
||||||
from dom import set_css
|
from dom import set_css
|
||||||
from elementmaker import E
|
from elementmaker import E
|
||||||
from gettext import gettext as _
|
from gettext import gettext as _
|
||||||
from read_book.globals import messenger, iframe_id
|
from read_book.globals import messenger, iframe_id
|
||||||
from read_book.resources import load_resources
|
from read_book.resources import load_resources
|
||||||
from read_book.overlay import Overlay
|
from read_book.overlay import Overlay
|
||||||
|
from utils import parse_url_params, username_key
|
||||||
|
|
||||||
LOADING_DOC = '''
|
LOADING_DOC = '''
|
||||||
<!DOCTYPE html>
|
<!DOCTYPE html>
|
||||||
@ -63,8 +64,9 @@ class View:
|
|||||||
'next_spine_item': self.on_next_spine_item,
|
'next_spine_item': self.on_next_spine_item,
|
||||||
'goto_doc_boundary': self.goto_doc_boundary,
|
'goto_doc_boundary': self.goto_doc_boundary,
|
||||||
'scroll_to_anchor': self.on_scroll_to_anchor,
|
'scroll_to_anchor': self.on_scroll_to_anchor,
|
||||||
|
'update_cfi': self.on_update_cfi,
|
||||||
}
|
}
|
||||||
self.currently_showing = {'spine':0, 'cfi':None}
|
self.currently_showing = {}
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def iframe(self):
|
def iframe(self):
|
||||||
@ -149,10 +151,28 @@ class View:
|
|||||||
self.book = book
|
self.book = book
|
||||||
self.show_loading(book.metadata.title)
|
self.show_loading(book.metadata.title)
|
||||||
self.ui.db.update_last_read_time(book)
|
self.ui.db.update_last_read_time(book)
|
||||||
# TODO: Check for last open position of book
|
pos = {'replace_history':True}
|
||||||
self.show_name(book.manifest.spine[1])
|
unkey = username_key(self.ui.interface_data.username)
|
||||||
|
name = book.manifest.spine[0]
|
||||||
|
cfi = None
|
||||||
|
q = parse_url_params()
|
||||||
|
if q.bookpos and q.bookpos.startswith('epubcfi(/'):
|
||||||
|
cfi = q.bookpos
|
||||||
|
elif book.last_read_position and book.last_read_position[unkey]:
|
||||||
|
cfi = book.last_read_position[unkey]
|
||||||
|
if cfi and cfi.startswith('epubcfi(/'):
|
||||||
|
cfi = cfi[len('epubcfi(/'):-1]
|
||||||
|
snum, rest = cfi.partition('/')[::2]
|
||||||
|
try:
|
||||||
|
snum = int(snum)
|
||||||
|
except Exception:
|
||||||
|
print('Invalid spine number in CFI:', snum)
|
||||||
|
if type(snum) == 'number':
|
||||||
|
name = book.manifest.spine[(int(snum) // 2) - 1] or name
|
||||||
|
pos.type, pos.cfi = 'cfi', '/' + rest
|
||||||
|
self.show_name(name, initial_position=pos)
|
||||||
|
|
||||||
def show_name(self, name, initial_position=None, cfi=None):
|
def show_name(self, name, initial_position=None):
|
||||||
if self.currently_showing.loading:
|
if self.currently_showing.loading:
|
||||||
return
|
return
|
||||||
sd = get_session_data()
|
sd = get_session_data()
|
||||||
@ -162,16 +182,21 @@ class View:
|
|||||||
'read_mode': sd.get('read_mode'),
|
'read_mode': sd.get('read_mode'),
|
||||||
'cols_per_screen': sd.get('cols_per_screen'),
|
'cols_per_screen': sd.get('cols_per_screen'),
|
||||||
}
|
}
|
||||||
self.currently_showing = {'name':name, 'cfi':cfi, 'settings':settings, 'initial_position':initial_position, 'loading':True}
|
initial_position = initial_position or {'replace_history':False}
|
||||||
|
self.currently_showing = {'name':name, 'settings':settings, 'initial_position':initial_position, 'loading':True}
|
||||||
|
spine = self.book.manifest.spine
|
||||||
|
idx = spine.indexOf(name)
|
||||||
|
if idx > -1:
|
||||||
|
self.currently_showing.bookpos = 'epubcfi(/{})'.format(2 * (idx +1))
|
||||||
self.set_margins(name is self.book.manifest.title_page_name)
|
self.set_margins(name is self.book.manifest.title_page_name)
|
||||||
load_resources(self.ui.db, self.book, name, self.loaded_resources, self.show_spine_item)
|
load_resources(self.ui.db, self.book, name, self.loaded_resources, self.show_spine_item)
|
||||||
|
|
||||||
def goto_doc_boundary(self, data):
|
def goto_doc_boundary(self, data):
|
||||||
name = self.book.manifest.spine[0 if data.start else self.book.manifest.spine.length - 1]
|
name = self.book.manifest.spine[0 if data.start else self.book.manifest.spine.length - 1]
|
||||||
self.show_name(name, initial_position={'type':'frac', 'frac':0 if data.start else 1})
|
self.show_name(name, initial_position={'type':'frac', 'frac':0 if data.start else 1, 'replace_history':False})
|
||||||
|
|
||||||
def on_scroll_to_anchor(self, data):
|
def on_scroll_to_anchor(self, data):
|
||||||
self.show_name(data.name, initial_position={'type':'anchor', 'anchor':data.frag})
|
self.show_name(data.name, initial_position={'type':'anchor', 'anchor':data.frag, 'replace_history':False})
|
||||||
|
|
||||||
def on_next_spine_item(self, data):
|
def on_next_spine_item(self, data):
|
||||||
spine = self.book.manifest.spine
|
spine = self.book.manifest.spine
|
||||||
@ -180,12 +205,21 @@ class View:
|
|||||||
if idx is 0:
|
if idx is 0:
|
||||||
return
|
return
|
||||||
idx = min(spine.length - 1, max(idx - 1, 0))
|
idx = min(spine.length - 1, max(idx - 1, 0))
|
||||||
self.show_name(spine[idx], initial_position={'type':'frac', 'frac':1})
|
self.show_name(spine[idx], initial_position={'type':'frac', 'frac':1, 'replace_history':True})
|
||||||
else:
|
else:
|
||||||
if idx is spine.length - 1:
|
if idx is spine.length - 1:
|
||||||
return
|
return
|
||||||
idx = max(0, min(spine.length - 1, idx + 1))
|
idx = max(0, min(spine.length - 1, idx + 1))
|
||||||
self.show_name(spine[idx])
|
self.show_name(spine[idx], initial_position={'type':'frac', 'frac':0, 'replace_history':True})
|
||||||
|
|
||||||
|
def on_update_cfi(self, data):
|
||||||
|
self.currently_showing.bookpos = data.cfi
|
||||||
|
get_boss().push_state(replace=data.replace_history)
|
||||||
|
unkey = username_key(self.ui.interface_data.username)
|
||||||
|
if not self.book.last_read_position:
|
||||||
|
self.book.last_read_position = {}
|
||||||
|
self.book.last_read_position[unkey] = data.cfi
|
||||||
|
self.ui.db.update_last_read_time(self.book)
|
||||||
|
|
||||||
def show_spine_item(self, resource_data):
|
def show_spine_item(self, resource_data):
|
||||||
self.loaded_resources = resource_data
|
self.loaded_resources = resource_data
|
||||||
|
@ -128,6 +128,9 @@ def viewport_to_document(x, y, doc):
|
|||||||
y += wy
|
y += wy
|
||||||
return x, y
|
return x, y
|
||||||
|
|
||||||
|
def username_key(username):
|
||||||
|
return ('u' if username else 'n') + username
|
||||||
|
|
||||||
if __name__ is '__main__':
|
if __name__ is '__main__':
|
||||||
print(fmt_sidx(10), fmt_sidx(1.2))
|
print(fmt_sidx(10), fmt_sidx(1.2))
|
||||||
print(list(map(human_readable, [1, 1024.0, 1025, 1024*1024*2.3])))
|
print(list(map(human_readable, [1, 1024.0, 1025, 1024*1024*2.3])))
|
||||||
|
Loading…
x
Reference in New Issue
Block a user