calibre/src/pyj/editor.pyj

222 lines
6.9 KiB
Plaintext

# vim:fileencoding=utf-8
# License: GPL v3 Copyright: 2018, Kovid Goyal <kovid at kovidgoyal.net>
from __python__ import bound_methods, hash_literals
from elementmaker import E
from fs_images import fix_fullscreen_svg_images
from live_css import get_matched_css, get_sourceline_address
from qt import from_python, to_python
FAKE_HOST = '__FAKE_HOST__'
FAKE_PROTOCOL = '__FAKE_PROTOCOL__'
def is_hidden(elem):
while elem:
if (elem.style and (elem.style.visibility is 'hidden' or elem.style.display is 'none')):
return True
elem = elem.parentNode
return False
def is_block(elem):
style = window.getComputedStyle(elem)
return style.display is 'block' or style.display is 'flex-box' or style.display is 'box'
def in_table(elem):
while elem:
tn = elem.tagName.toLowerCase() if elem.tagName else ''
if tn is 'table':
return True
elem = elem.parentNode
return False
def find_containing_block(elem):
while elem and elem.dataset.isBlock is not '1':
elem = elem.parentNode
return elem
def line_height():
ans = line_height.ans
if not ans:
ds = window.getComputedStyle(document.body)
try:
# will fail if line-height = "normal"
lh = float(ds.lineHeight)
except:
try:
lh = 1.2 * float(ds.fontSize)
except:
lh = 15
ans = line_height.ans = max(5, lh)
return ans
def scroll_to_node(node, sync_context):
if node is document.body:
window.scrollTo(0, 0)
else:
node.scrollIntoView()
if sync_context:
window.scrollBy(0, -sync_context * line_height())
state = {'blocks_found': False, 'in_split_mode': False}
def go_to_line(lnum, sync_context):
for node in document.querySelectorAll(f'[data-lnum="{lnum}"]'):
if is_hidden(node):
continue
scroll_to_node(node, sync_context)
break
@from_python
def go_to_sourceline_address(sourceline, tags, sync_context):
nodes = document.querySelectorAll(f'[data-lnum="{sourceline}"]')
for index in range(nodes.length):
node = nodes[index]
if index >= tags.length or node.tagName.toLowerCase() is not tags[index]:
break
if index == tags.length - 1 and not is_hidden(node):
return scroll_to_node(node, sync_context)
go_to_line(sourceline, sync_context)
def line_numbers():
found_body = False
ans = v'[]'
for node in document.getElementsByTagName('*'):
if not found_body and node.tagName.toLowerCase() is "body":
found_body = True
if found_body:
ans.push(node.dataset.lnum)
return ans
def find_blocks():
if state.blocks_found:
return
for elem in document.body.getElementsByTagName('*'):
if is_block(elem) and not in_table(elem):
elem.setAttribute('data-is-block', '1')
state.blocks_found = True
@from_python
def set_split_mode(enabled):
state.in_split_mode = enabled
document.body.dataset.inSplitMode = '1' if enabled else '0'
if enabled:
find_blocks()
def report_split(node):
loc = v'[]'
totals = v'[]'
parent = find_containing_block(node)
while parent and parent.tagName.toLowerCase() is not 'body':
totals.push(parent.parentNode.children.length)
num = 0
sibling = parent.previousElementSibling
while sibling:
num += 1
sibling = sibling.previousElementSibling
loc.push(num)
parent = parent.parentNode
loc.reverse()
totals.reverse()
to_python.request_split(loc, totals)
def onclick(event):
event.preventDefault()
if state.in_split_mode:
report_split(event.target)
else:
e = event.target
address = get_sourceline_address(e)
# Find the closest containing link, if any
href = tn = ''
while e and e is not document.body and e is not document and e is not document.documentElement and (tn is not 'a' or not href):
tn = e.tagName.toLowerCase() if e.tagName else ''
href = e.getAttribute('href')
e = e.parentNode
if to_python.request_sync:
to_python.request_sync(tn, href, address)
return False
@from_python
def go_to_anchor(anchor):
elem = document.getElementById(anchor)
if not elem:
elem = document.querySelector(f'[name="{anchor}"]')
if elem:
elem.scrollIntoView()
address = get_sourceline_address(elem)
if to_python.request_sync:
to_python.request_sync('', '', address)
@from_python
def live_css(editor_name, sourceline, tags):
all_properties = {}
ans = {'nodes':v'[]', 'computed_css':all_properties, 'editor_name': editor_name, 'sourceline': sourceline, 'tags': tags}
target = None
i = 0
for node in document.querySelectorAll(f'[data-lnum="{sourceline}"]'):
tn = node.tagName.toLowerCase() if node.tagName else ''
if tn is not tags[i]:
if to_python.live_css_data:
to_python.live_css_data(ans)
return
i += 1
target = node
if i >= tags.length:
break
ancestor_specificity = 0
while target and target.ownerDocument:
css = get_matched_css(target, ancestor_specificity is not 0, all_properties)
# We want to show the Matched CSS rules header even if no rules matched
if css.length > 0 or ancestor_specificity is 0:
tn = target.tagName.toLowerCase() if target.tagName else ''
ans.nodes.push({
'name': tn,
'css': css, 'ancestor_specificity': -ancestor_specificity,
'sourceline': target.getAttribute('data-lnum')
})
target = target.parentNode
ancestor_specificity += 1
if to_python.live_css_data:
to_python.live_css_data(ans)
def check_for_maths():
if document.body.getElementsByTagNameNS('http://www.w3.org/1998/Math/MathML', 'math').length > 0:
return True
for s in document.scripts:
if s.type is 'text/x-mathjax-config':
return True
return False
def load_mathjax():
script = E.script(type='text/javascript')
script.async = True
script.src = f'{FAKE_PROTOCOL}://{FAKE_HOST}/calibre_internal-mathjax/startup.js'
document.head.appendChild(script)
if document.body:
settings = JSON.parse(window.navigator.userAgent.split('|')[1])
css = '[data-in-split-mode="1"] [data-is-block="1"]:hover { cursor: pointer !important; border-top: solid 5px green !important }'
if settings.os is 'macos':
# See settings.pyj for reason for webkit-hyphenate-character
css += '\n* { -webkit-hyphenate-character: "-" !important }\n'
document.body.addEventListener('click', onclick, True)
document.documentElement.appendChild(E.style(type='text/css', css))
fix_fullscreen_svg_images()
if check_for_maths():
load_mathjax()