mirror of
https://github.com/kovidgoyal/calibre.git
synced 2025-11-17 12:03:02 -05:00
230 lines
6.9 KiB
Plaintext
230 lines
6.9 KiB
Plaintext
# vim:fileencoding=utf-8
|
|
# License: GPL v3 Copyright: 2015, Kovid Goyal <kovid at kovidgoyal.net>
|
|
from __python__ import hash_literals
|
|
|
|
from ajax import encode_query
|
|
from encodings import hexlify
|
|
|
|
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 request_full_screen(elem):
|
|
elem = elem or document.documentElement
|
|
if elem.requestFullScreen:
|
|
elem.requestFullScreen()
|
|
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 viewport_to_document(x, y, doc):
|
|
# Convert x, y from the viewport (window) co-ordinate system to the
|
|
# document (body) co-ordinate system
|
|
doc = doc or window.document
|
|
topdoc = window.document
|
|
while doc is not topdoc:
|
|
# We are in a frame
|
|
frame = doc.defaultView.frameElement
|
|
rect = frame.getBoundingClientRect()
|
|
x += rect.left
|
|
y += rect.top
|
|
doc = frame.ownerDocument
|
|
win = doc.defaultView
|
|
wx, wy = win.pageXOffset, win.pageYOffset
|
|
x += wx
|
|
y += wy
|
|
return x, y
|
|
|
|
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"'b|i|br|h1|h2|h3|h4|h5|h6|div|em|strong|span'.split('|')"
|
|
|
|
|
|
def safe_set_inner_html(elem, html):
|
|
elem.innerHTML = simple_markup(html)
|
|
|
|
|
|
if __name__ is '__main__':
|
|
from pythonize import strings
|
|
strings()
|
|
print(rating_to_stars(3, True))
|
|
print(fmt_sidx(10), fmt_sidx(1.2))
|
|
print(list(map(human_readable, [1, 1024.0, 1025, 1024*1024*2.3])))
|