# vim:fileencoding=utf-8 # License: GPL v3 Copyright: 2018, Kovid Goyal 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()