# vim:fileencoding=utf-8 # License: GPL v3 Copyright: 2015, Kovid Goyal from __python__ import hash_literals from ajax import encode_query from encodings import hexlify from book_list.theme import get_font_family is_ios = v'!!navigator.platform && /iPad|iPhone|iPod/.test(navigator.platform)' ios_major_version = 0 if !is_ios and v'!!navigator.platform' and window? and window.navigator.platform is 'MacIntel' and window.navigator.maxTouchPoints > 1: # iPad Safari in desktop mode https://stackoverflow.com/questions/57765958/how-to-detect-ipad-and-ipad-os-version-in-ios-13-and-up is_ios = True if is_ios: v = v'navigator.appVersion.match(/OS (\d+)/)' if v and v[1]: ios_major_version = parseInt(v[1], 10) or 0 def default_context_menu_should_be_allowed(evt): if evt.target and evt.target.tagName and evt.target.tagName.toLowerCase() in ('input', 'textarea'): return True return False def debounce(func, wait, immediate=False): # Returns a function, that, as long as it continues to be invoked, will not # be triggered. The function will be called after it stops being called for # wait milliseconds. If `immediate` is True, trigger the function on the # leading edge, instead of the trailing. timeout = None return def debounce_inner(): # noqa: unused-local nonlocal timeout context, args = this, arguments def later(): nonlocal timeout timeout = None if not immediate: func.apply(context, args) call_now = immediate and not timeout window.clearTimeout(timeout) timeout = window.setTimeout(later, wait) if call_now: func.apply(context, args) if Object.assign: copy_hash = def (obj): return Object.assign({}, obj) else: copy_hash = def (obj): return {k:obj[k] for k in Object.keys(obj)} def parse_url_params(url=None, allow_multiple=False): cache = parse_url_params.cache url = url or window.location.href if cache[url]: return copy_hash(parse_url_params.cache[url]) qs = url.indexOf('#') ans = {} if qs < 0: cache[url] = ans return copy_hash(ans) q = url.slice(qs + 1, (url.length + 1)) if not q: cache[url] = ans return copy_hash(ans) pairs = q.replace(/\+/g, " ").split("&") for pair in pairs: key, val = pair.partition('=')[::2] key, val = decodeURIComponent(key), decodeURIComponent(val) if allow_multiple: if ans[key] is undefined: ans[key] = v'[]' ans[key].append(val) else: ans[key] = val cache[url] = ans return copy_hash(ans) parse_url_params.cache = {} def encode_query_with_path(query, path): path = path or window.location.pathname return path + encode_query(query, '#') def full_screen_supported(elem): elem = elem or document.documentElement if elem.requestFullScreen or elem.webkitRequestFullScreen or elem.mozRequestFullScreen: return True return False def request_full_screen(elem): elem = elem or document.documentElement options = {'navigationUI': 'hide'} if elem.requestFullScreen: elem.requestFullScreen(options) elif elem.webkitRequestFullScreen: elem.webkitRequestFullScreen() elif elem.mozRequestFullScreen: elem.mozRequestFullScreen() def full_screen_element(): return document.fullscreenElement or document.webkitFullscreenElement or document.mozFullScreenElement or document.msFullscreenElement _roman = list(zip( [1000,900,500,400,100,90,50,40,10,9,5,4,1], ["M","CM","D","CD","C","XC","L","XL","X","IX","V","IV","I"] )) def roman(num): if num <= 0 or num >= 4000 or int(num) is not num: return num + '' result = [] for d, r in _roman: while num >= d: result.append(r) num -= d return result.join('') def fmt_sidx(val, fmt='{:.2f}', use_roman=True): if val is undefined or val is None or val is '': return '1' if int(val) is float(val): if use_roman: return roman(val) return int(val) + '' return fmt.format(float(val)) def rating_to_stars(value, allow_half_stars=False, star='★', half='⯨'): r = max(0, min(int(value or 0), 10)) if allow_half_stars: ans = star.repeat(r // 2) if r % 2: ans += half else: ans = star.repeat(int(r/2.0)) return ans def human_readable(size, sep=' '): divisor, suffix = 1, "B" for i, candidate in enumerate(('B', 'KB', 'MB', 'GB', 'TB', 'PB', 'EB')): if size < (1 << ((i + 1) * 10)): divisor, suffix = (1 << (i * 10)), candidate break size = (float(size)/divisor) + '' pos = size.find(".") if pos > -1: size = size[:pos + 2] if size.endswith('.0'): size = size[:-2] return size + sep + suffix def document_height(): html = document.documentElement return max(document.body.scrollHeight, document.body.offsetHeight, html.clientHeight, html.scrollHeight, html.offsetHeight) def document_width(): html = document.documentElement return max(document.body.scrollWidth, document.body.offsetWidth, html.clientWidth, html.scrollWidth, html.offsetWidth) _data_ns = None def data_ns(name): nonlocal _data_ns if _data_ns is None: rand = Uint8Array(12) window.crypto.getRandomValues(rand) _data_ns = 'data-' + hexlify(rand) + '-' return _data_ns + name def get_elem_data(elem, name, defval): ans = elem.getAttribute(data_ns(name)) if ans is None: return defval ? None return JSON.parse(ans) def set_elem_data(elem, name, val): elem.setAttribute(data_ns(name), JSON.stringify(val)) def username_key(username): return ('u' if username else 'n') + username def html_escape(text): repl = { '&': "&", '"': """, '<': "<", '>': ">" } return String.prototype.replace.call(text, /[&"<>]/g, def (c): return repl[c];) def uniq(vals): # Remove all duplicates from vals, while preserving order ans = v'[]' seen = {} for x in vals: if not seen[x]: seen[x] = True ans.push(x) return ans def conditional_timeout(elem_id, timeout, func): def ct_impl(): elem = document.getElementById(elem_id) if elem: func.call(elem) window.setTimeout(ct_impl, timeout) def simple_markup(html): html = (html or '').replace(/\uffff/g, '').replace( /<\s*(\/?[a-zA-Z1-6]+)[^>]*>/g, def (match, tag): tag = tag.toLowerCase() is_closing = '/' if tag[0] is '/' else '' if is_closing: tag = tag[1:] if simple_markup.allowed_tags.indexOf(tag) < 0: tag = 'span' return f'\uffff{is_closing}{tag}\uffff' ) div = document.createElement('b') div.textContent = html html = div.innerHTML return html.replace(/\uffff(\/?[a-z1-6]+)\uffff/g, '<$1>') simple_markup.allowed_tags = v"'a|b|i|br|hr|h1|h2|h3|h4|h5|h6|div|em|strong|span'.split('|')" def safe_set_inner_html(elem, html): elem.innerHTML = simple_markup(html) return elem def sandboxed_html(html, style, sandbox): ans = document.createElement('iframe') ans.setAttribute('sandbox', sandbox or '') ans.setAttribute('seamless', '') ans.style.width = '100%' html = html or '' css = 'html, body { margin: 0; padding: 0; font-family: __FONT__ } p:first-child { margin-top: 0; padding-top: 0; -webkit-margin-before: 0 }'.replace('__FONT__', get_font_family()) css += style or '' final_html = f'{html}' # Microsoft Edge does not support srcdoc not does it work using a data URI. ans.srcdoc = final_html return ans