mirror of
https://github.com/kovidgoyal/calibre.git
synced 2025-07-08 10:44:09 -04:00
Browser viewer: Show footnotes in a popup window
Works similar to the desktop calibre viewer, except that the popup window is over the text instead of at the bottom.
This commit is contained in:
parent
9ed99e752f
commit
c690588ed0
@ -5,8 +5,6 @@ particular order.
|
|||||||
New features for the in-browser viewer
|
New features for the in-browser viewer
|
||||||
----------------------------------------
|
----------------------------------------
|
||||||
|
|
||||||
- Footnote popups
|
|
||||||
|
|
||||||
- Bookmarks and more generally, annotations such as highlighting text and
|
- Bookmarks and more generally, annotations such as highlighting text and
|
||||||
adding comments
|
adding comments
|
||||||
|
|
||||||
@ -17,7 +15,7 @@ New features for the in-browser viewer
|
|||||||
New features for the server generally
|
New features for the server generally
|
||||||
---------------------------------------
|
---------------------------------------
|
||||||
|
|
||||||
- Create a UI for making changes to the library such as editing metadta,
|
- Create a UI for making changes to the library such as editing metadata,
|
||||||
adding/deleting books, converting, sending by email, etc.
|
adding/deleting books, converting, sending by email, etc.
|
||||||
|
|
||||||
- Add a way to search the set of locally available books stored in offline
|
- Add a way to search the set of locally available books stored in offline
|
||||||
|
@ -7,15 +7,17 @@ from read_book.comm import IframeClient
|
|||||||
from read_book.resources import finalize_resources, unserialize_html
|
from read_book.resources import finalize_resources, unserialize_html
|
||||||
|
|
||||||
|
|
||||||
|
block_names = dict.fromkeys(v"['p', 'div', 'li', 'td', 'h1', 'h2', 'h2', 'h3', 'h4', 'h5', 'h6', 'body']", True).as_object()
|
||||||
|
block_display_styles = dict.fromkeys(v"['block', 'list-item', 'table-cell', 'table']", True).as_object()
|
||||||
|
|
||||||
def elem_roles(elem):
|
def elem_roles(elem):
|
||||||
return {k.toLowerCase(): True for k in (elem.getAttribute('role') or '').split(' ')}
|
return {k.toLowerCase(): True for k in (elem.getAttribute('role') or '').split(' ')}
|
||||||
|
|
||||||
|
|
||||||
def get_containing_block(node):
|
def get_containing_block(node):
|
||||||
while node and node.tagName and get_containing_block.block_names[node.tagName.toLowerCase()] is not True:
|
while node and node.tagName and block_names[node.tagName.toLowerCase()] is not True:
|
||||||
node = node.parentNode
|
node = node.parentNode
|
||||||
return node
|
return node
|
||||||
get_containing_block.block_names = dict.fromkeys(v"['p', 'div', 'li', 'td', 'h1', 'h2', 'h2', 'h3', 'h4', 'h5', 'h6', 'body']", True).as_object()
|
|
||||||
|
|
||||||
|
|
||||||
def is_footnote_link(a, dest_name, dest_frag, src_name, link_to_map):
|
def is_footnote_link(a, dest_name, dest_frag, src_name, link_to_map):
|
||||||
@ -74,6 +76,97 @@ is_footnote_link.inline_displays = {'inline': True, 'inline-block': True}
|
|||||||
is_footnote_link.vert_aligns = {'sub': True, 'super': True, 'top': True, 'bottom': True}
|
is_footnote_link.vert_aligns = {'sub': True, 'super': True, 'top': True, 'bottom': True}
|
||||||
|
|
||||||
|
|
||||||
|
def is_epub_footnote(node):
|
||||||
|
roles = elem_roles(node)
|
||||||
|
if roles['doc-note'] or roles['doc-footnote'] or roles['doc-rearnote']:
|
||||||
|
return True
|
||||||
|
return False
|
||||||
|
|
||||||
|
|
||||||
|
def get_note_container(node):
|
||||||
|
while node and block_names[node.tagName.toLowerCase()] is not True and block_display_styles[window.getComputedStyle(node).display] is not True:
|
||||||
|
node = node.parentNode
|
||||||
|
return node
|
||||||
|
|
||||||
|
|
||||||
|
def get_parents_and_self(node):
|
||||||
|
ans = v'[]'
|
||||||
|
while node and node is not document.body:
|
||||||
|
ans.push(node)
|
||||||
|
node = node.parentNode
|
||||||
|
return ans
|
||||||
|
|
||||||
|
|
||||||
|
def get_page_break(node):
|
||||||
|
style = window.getComputedStyle(node)
|
||||||
|
return {
|
||||||
|
'before': get_page_break.on[style.getPropertyValue('page-break-before')] is True,
|
||||||
|
'after': get_page_break.on[style.getPropertyValue('page-break-after')] is True,
|
||||||
|
}
|
||||||
|
get_page_break.on = {'always': True, 'left': True, 'right': True}
|
||||||
|
|
||||||
|
|
||||||
|
def hide_children(node):
|
||||||
|
for child in node.childNodes:
|
||||||
|
if child.nodeType is Node.ELEMENT_NODE:
|
||||||
|
if child.do_not_hide:
|
||||||
|
hide_children(child)
|
||||||
|
v'delete child.do_not_hide'
|
||||||
|
else:
|
||||||
|
child.style.display = 'none'
|
||||||
|
|
||||||
|
|
||||||
|
def unhide_tree(elem):
|
||||||
|
elem.do_not_hide = True
|
||||||
|
for c in elem.getElementsByTagName('*'):
|
||||||
|
c.do_not_hide = True
|
||||||
|
|
||||||
|
|
||||||
|
ok_list_types = {'disc': True, 'circle': True, 'square': True}
|
||||||
|
|
||||||
|
|
||||||
|
def show_footnote(target, known_anchors):
|
||||||
|
if not target:
|
||||||
|
return
|
||||||
|
start_elem = document.getElementById(target)
|
||||||
|
if not start_elem:
|
||||||
|
return
|
||||||
|
start_elem = get_note_container(start_elem)
|
||||||
|
for elem in get_parents_and_self(start_elem):
|
||||||
|
elem.do_not_hide = True
|
||||||
|
style = window.getComputedStyle(elem)
|
||||||
|
if style.display is 'list-item' and ok_list_types[style.listStyleType] is not True:
|
||||||
|
# We cannot display list numbers since they will be
|
||||||
|
# incorrect as we are removing siblings of this element.
|
||||||
|
elem.style.listStyleType = 'none'
|
||||||
|
if is_epub_footnote(start_elem):
|
||||||
|
unhide_tree(start_elem)
|
||||||
|
else:
|
||||||
|
# Try to detect natural boundaries based on markup for this note
|
||||||
|
found_note_start = False
|
||||||
|
for elem in document.body.getElementsByTagName('*'):
|
||||||
|
if found_note_start:
|
||||||
|
eid = elem.getAttribute('id')
|
||||||
|
if eid is not target and known_anchors[eid] and get_note_container(elem) is not start_elem:
|
||||||
|
# console.log('Breaking footnote on anchor: ' + elem.getAttribute('id'))
|
||||||
|
v'delete get_note_container(elem).do_not_hide'
|
||||||
|
break
|
||||||
|
pb = get_page_break(elem)
|
||||||
|
if pb.before:
|
||||||
|
# console.log('Breaking footnote on page break before')
|
||||||
|
break
|
||||||
|
if pb.after:
|
||||||
|
unhide_tree(elem)
|
||||||
|
# console.log('Breaking footnote on page break after')
|
||||||
|
break
|
||||||
|
elem.do_not_hide = True
|
||||||
|
else if elem is start_elem:
|
||||||
|
found_note_start = True
|
||||||
|
|
||||||
|
hide_children(document.body)
|
||||||
|
window.location.hash = '#' + target
|
||||||
|
|
||||||
|
|
||||||
class PopupIframeBoss:
|
class PopupIframeBoss:
|
||||||
|
|
||||||
def __init__(self):
|
def __init__(self):
|
||||||
@ -106,7 +199,7 @@ class PopupIframeBoss:
|
|||||||
for name in self.blob_url_map:
|
for name in self.blob_url_map:
|
||||||
window.URL.revokeObjectURL(self.blob_url_map[name])
|
window.URL.revokeObjectURL(self.blob_url_map[name])
|
||||||
root_data, self.mathjax, self.blob_url_map = finalize_resources(self.book, data.name, data.resource_data)
|
root_data, self.mathjax, self.blob_url_map = finalize_resources(self.book, data.name, data.resource_data)
|
||||||
self.resource_urls = unserialize_html(root_data, self.content_loaded)
|
self.resource_urls = unserialize_html(root_data, self.content_loaded, self.show_only_footnote)
|
||||||
|
|
||||||
def on_clear(self, data):
|
def on_clear(self, data):
|
||||||
clear(document.head)
|
clear(document.head)
|
||||||
@ -115,5 +208,12 @@ class PopupIframeBoss:
|
|||||||
self.name = None
|
self.name = None
|
||||||
self.frag = None
|
self.frag = None
|
||||||
|
|
||||||
|
def show_only_footnote(self):
|
||||||
|
known_anchors = {}
|
||||||
|
ltm = self.book.manifest.link_to_map?[self.name]
|
||||||
|
if ltm:
|
||||||
|
known_anchors = {k:True for k in Object.keys(ltm)}
|
||||||
|
show_footnote(self.frag, known_anchors)
|
||||||
|
|
||||||
def content_loaded(self):
|
def content_loaded(self):
|
||||||
self.comm.send_message('content_loaded', height=document.documentElement.scrollHeight + 25)
|
self.comm.send_message('content_loaded', height=document.documentElement.scrollHeight + 25)
|
||||||
|
@ -220,7 +220,7 @@ def process_stack(stack, tag_map, ns_map, load_required, onload, resource_urls):
|
|||||||
for v'var i = node.length - 1; i >= 1; i--': # noqa: unused-local
|
for v'var i = node.length - 1; i >= 1; i--': # noqa: unused-local
|
||||||
stack.push(v'[node[i], elem]')
|
stack.push(v'[node[i], elem]')
|
||||||
|
|
||||||
def unserialize_html(serialized_data, proceed):
|
def unserialize_html(serialized_data, proceed, postprocess_dom):
|
||||||
tag_map = serialized_data.tag_map
|
tag_map = serialized_data.tag_map
|
||||||
tree = serialized_data.tree
|
tree = serialized_data.tree
|
||||||
ns_map = serialized_data.ns_map
|
ns_map = serialized_data.ns_map
|
||||||
@ -258,6 +258,8 @@ def unserialize_html(serialized_data, proceed):
|
|||||||
for v'var i = body.length - 1; i >= 1; i--': # noqa: unused-local
|
for v'var i = body.length - 1; i >= 1; i--': # noqa: unused-local
|
||||||
stack.push(v'[body[i], document.body]')
|
stack.push(v'[body[i], document.body]')
|
||||||
process_stack(stack, tag_map, ns_map, load_required, onload, resource_urls)
|
process_stack(stack, tag_map, ns_map, load_required, onload, resource_urls)
|
||||||
|
if postprocess_dom:
|
||||||
|
postprocess_dom()
|
||||||
ev = document.createEvent('Event')
|
ev = document.createEvent('Event')
|
||||||
ev.initEvent('DOMContentLoaded', True, True)
|
ev.initEvent('DOMContentLoaded', True, True)
|
||||||
document.dispatchEvent(ev)
|
document.dispatchEvent(ev)
|
||||||
|
Loading…
x
Reference in New Issue
Block a user