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 _
|
||||
|
||||
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):
|
||||
if not query:
|
||||
return ''
|
||||
@ -15,7 +26,7 @@ def encode_query(query):
|
||||
val = query[k]
|
||||
if val is undefined or val is None:
|
||||
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
|
||||
return path
|
||||
|
||||
|
@ -42,10 +42,16 @@ class Boss:
|
||||
if not data.mode or data.mode is 'book_list':
|
||||
if data.panel is not self.ui.current_panel:
|
||||
self.ui.show_panel(data.panel, push_state=False)
|
||||
elif data.mode is 'read_book':
|
||||
self.current_mode = data.mode
|
||||
self.apply_mode()
|
||||
self.read_book(int(data.book_id), data.fmt)
|
||||
if data.mode is 'read_book':
|
||||
try:
|
||||
book_id = int(data.book_id)
|
||||
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():
|
||||
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
|
||||
@ -103,7 +109,6 @@ class Boss:
|
||||
def read_book(self, book_id, fmt, metadata):
|
||||
self.current_mode = 'read_book'
|
||||
self.apply_mode()
|
||||
self.push_state(extra_query_data={'book_id':book_id, 'fmt':fmt})
|
||||
self.read_ui.load_book(book_id, fmt, metadata)
|
||||
|
||||
def change_books(self, data):
|
||||
@ -131,6 +136,10 @@ class Boss:
|
||||
query.search = sq
|
||||
else:
|
||||
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:
|
||||
query.library_id = idata.library_id
|
||||
set_current_query(query)
|
||||
|
@ -94,7 +94,7 @@ class DB:
|
||||
'manifest': None,
|
||||
'cover_width': None,
|
||||
'cover_height': None,
|
||||
'last_read_position': None,
|
||||
'last_read_position': {},
|
||||
})
|
||||
)
|
||||
|
||||
|
@ -5,20 +5,29 @@ from __python__ import bound_methods, hash_literals
|
||||
import traceback
|
||||
from aes import GCM
|
||||
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.mathjax import apply_mathjax
|
||||
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.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
|
||||
from read_book.flow_mode import (
|
||||
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 utils import debounce
|
||||
|
||||
FORCE_FLOW_MODE = False
|
||||
|
||||
class Boss:
|
||||
class IframeBoss:
|
||||
|
||||
def __init__(self):
|
||||
self.ready_sent = False
|
||||
self.last_cfi = None
|
||||
self.replace_history_on_next_cfi_update = True
|
||||
self.encrypted_communications = False
|
||||
window.addEventListener('message', self.handle_message, False)
|
||||
window.addEventListener('load', def():
|
||||
@ -83,13 +92,16 @@ class Boss:
|
||||
self.handle_wheel = flow_onwheel
|
||||
self.handle_keydown = flow_onkeydown
|
||||
self.to_scroll_fraction = flow_to_scroll_fraction
|
||||
self.jump_to_cfi = scroll_to_cfi
|
||||
else:
|
||||
self.do_layout = paged_layout
|
||||
self.handle_wheel = paged_onwheel
|
||||
self.handle_keydown = paged_onkeydown
|
||||
self.to_scroll_fraction = paged_scroll_to_fraction
|
||||
self.jump_to_cfi = paged_jump_to_cfi
|
||||
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})
|
||||
self.last_cfi = None
|
||||
root_data, self.mathjax = finalize_resources(self.book, data.name, data.resource_data)
|
||||
unserialize_html(root_data, self.content_loaded)
|
||||
|
||||
@ -109,13 +121,26 @@ class Boss:
|
||||
csi = current_spine_item()
|
||||
if csi.initial_position:
|
||||
ipos = csi.initial_position
|
||||
self.replace_history_on_next_cfi_update = ipos.replace_history
|
||||
if ipos.type is 'frac':
|
||||
self.to_scroll_fraction(ipos.frac)
|
||||
elif ipos.type is '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):
|
||||
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):
|
||||
if current_layout_mode() is not 'flow':
|
||||
@ -151,6 +176,7 @@ class Boss:
|
||||
if not name:
|
||||
name = current_spine_item().name
|
||||
if name is current_spine_item().name:
|
||||
self.replace_history_on_next_cfi_update = False
|
||||
self.scroll_to_anchor(frag)
|
||||
else:
|
||||
self.send_message('scroll_to_anchor', name=name, frag=frag)
|
||||
@ -168,4 +194,4 @@ class Boss:
|
||||
def init():
|
||||
script = document.getElementById('bootstrap')
|
||||
script.parentNode.removeChild(script) # free up some memory
|
||||
Boss()
|
||||
IframeBoss()
|
||||
|
@ -96,11 +96,20 @@ class ReadUI:
|
||||
div.lastChild.textContent = msg or ''
|
||||
|
||||
def load_book(self, book_id, fmt, metadata):
|
||||
self.base_url_data = {'book_id':book_id, 'fmt':fmt}
|
||||
if self.db is None:
|
||||
self.pending_load = [book_id, fmt, metadata]
|
||||
return
|
||||
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):
|
||||
self.db = db
|
||||
if self.pending_load is not None:
|
||||
|
@ -2,13 +2,14 @@
|
||||
# License: GPL v3 Copyright: 2016, Kovid Goyal <kovid at kovidgoyal.net>
|
||||
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 elementmaker import E
|
||||
from gettext import gettext as _
|
||||
from read_book.globals import messenger, iframe_id
|
||||
from read_book.resources import load_resources
|
||||
from read_book.overlay import Overlay
|
||||
from utils import parse_url_params, username_key
|
||||
|
||||
LOADING_DOC = '''
|
||||
<!DOCTYPE html>
|
||||
@ -63,8 +64,9 @@ class View:
|
||||
'next_spine_item': self.on_next_spine_item,
|
||||
'goto_doc_boundary': self.goto_doc_boundary,
|
||||
'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
|
||||
def iframe(self):
|
||||
@ -149,10 +151,28 @@ class View:
|
||||
self.book = book
|
||||
self.show_loading(book.metadata.title)
|
||||
self.ui.db.update_last_read_time(book)
|
||||
# TODO: Check for last open position of book
|
||||
self.show_name(book.manifest.spine[1])
|
||||
pos = {'replace_history':True}
|
||||
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:
|
||||
return
|
||||
sd = get_session_data()
|
||||
@ -162,16 +182,21 @@ class View:
|
||||
'read_mode': sd.get('read_mode'),
|
||||
'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)
|
||||
load_resources(self.ui.db, self.book, name, self.loaded_resources, self.show_spine_item)
|
||||
|
||||
def goto_doc_boundary(self, data):
|
||||
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):
|
||||
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):
|
||||
spine = self.book.manifest.spine
|
||||
@ -180,12 +205,21 @@ class View:
|
||||
if idx is 0:
|
||||
return
|
||||
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:
|
||||
if idx is spine.length - 1:
|
||||
return
|
||||
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):
|
||||
self.loaded_resources = resource_data
|
||||
|
@ -128,6 +128,9 @@ def viewport_to_document(x, y, doc):
|
||||
y += wy
|
||||
return x, y
|
||||
|
||||
def username_key(username):
|
||||
return ('u' if username else 'n') + username
|
||||
|
||||
if __name__ is '__main__':
|
||||
print(fmt_sidx(10), fmt_sidx(1.2))
|
||||
print(list(map(human_readable, [1, 1024.0, 1025, 1024*1024*2.3])))
|
||||
|
Loading…
x
Reference in New Issue
Block a user