Remove WebKit from the ToC Editor

This commit is contained in:
Kovid Goyal 2019-06-25 13:38:54 +05:30
parent e3fd6f9bff
commit a304b67ce3
No known key found for this signature in database
GPG Key ID: 06BC317B515ACE7C
3 changed files with 129 additions and 147 deletions

53
resources/toc.js Normal file
View File

@ -0,0 +1,53 @@
/* vim:fileencoding=utf-8
*
* Copyright (C) 2019 Kovid Goyal <kovid at kovidgoyal.net>
*
* Distributed under terms of the GPLv3 license
*/
(function() {
"use strict";
var com_id = "COM_ID";
var com_counter = 0;
function onclick(event) {
// We dont want this event to trigger onclick on this element's parent
// block, if any.
event.stopPropagation();
var frac = window.pageYOffset/document.body.scrollHeight;
var loc = [];
var totals = [];
var block = event.currentTarget;
var parent = block;
while (parent && parent.tagName && parent.tagName.toLowerCase() !== 'body') {
totals.push(parent.parentNode.children.length);
var num = 0;
var sibling = parent.previousElementSibling;
while (sibling) {
num += 1;
sibling = sibling.previousElementSibling;
}
loc.push(num);
parent = parent.parentNode;
}
loc.reverse();
totals.reverse();
com_counter += 1;
window.calibre_toc_data = [block.tagName.toLowerCase(), block.id, loc, totals, frac];
document.title = com_id + '-' + com_counter;
}
function find_blocks() {
for (let elem of document.body.getElementsByTagName('*')) {
style = window.getComputedStyle(elem);
if (style.display === 'block' || style.display === 'flex-box' || style.display === 'box') {
elem.classList.add("calibre_toc_hover");
elem.onclick = onclick;
}
}
}
var style = document.createElement('style');
style.innerText = 'body { background-color: white }' + '.calibre_toc_hover:hover { cursor: pointer !important; border-top: solid 5px green !important }' + '::selection {background:#ffff00; color:#000;}';
document.documentElement.appendChild(style);
find_blocks();
})();

View File

@ -1,56 +0,0 @@
#!/usr/bin/env coffee
# vim:fileencoding=UTF-8:ts=4:sw=4:sta:et:sts=4:ai
###
Copyright 2013, Kovid Goyal <kovid at kovidgoyal.net>
Released under the GPLv3 License
###
if window?.calibre_utils
log = window.calibre_utils.log
class AnchorLocator
###
# Allow the user to click on any block level element to choose it as the
# location for an anchor.
###
constructor: () ->
if not this instanceof arguments.callee
throw new Error('AnchorLocator constructor called as function')
find_blocks: () =>
for elem in document.body.getElementsByTagName('*')
style = window.getComputedStyle(elem)
if style.display in ['block', 'flex-box', 'box']
elem.className += " calibre_toc_hover"
elem.onclick = this.onclick
onclick: (event) ->
# We dont want this event to trigger onclick on this element's parent
# block, if any.
event.stopPropagation()
frac = window.pageYOffset/document.body.scrollHeight
loc = []
totals = []
parent = this
while parent and parent.tagName.toLowerCase() != '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()
window.py_bridge.onclick(this, JSON.stringify(loc), JSON.stringify(totals), frac)
return false
calibre_anchor_locator = new AnchorLocator()
calibre_anchor_locator.find_blocks()

View File

@ -1,102 +1,95 @@
#!/usr/bin/env python2 #!/usr/bin/env python2
# vim:fileencoding=UTF-8:ts=4:sw=4:sta:et:sts=4:fdm=marker:ai # vim:fileencoding=UTF-8:ts=4:sw=4:sta:et:sts=4:fdm=marker:ai
# License: GPLv3 Copyright: 2013, Kovid Goyal <kovid at kovidgoyal.net>
from __future__ import absolute_import, division, print_function, unicode_literals from __future__ import absolute_import, division, print_function, unicode_literals
__license__ = 'GPL v3'
__copyright__ = '2013, Kovid Goyal <kovid at kovidgoyal.net>'
__docformat__ = 'restructuredtext en'
import json import json
from PyQt5.Qt import (QWidget, QGridLayout, QListWidget, QSize, Qt, QUrl, from PyQt5.Qt import (
pyqtSlot, pyqtSignal, QVBoxLayout, QFrame, QLabel, QFrame, QGridLayout, QIcon, QLabel, QLineEdit, QListWidget, QPushButton, QSize,
QLineEdit, QTimer, QPushButton, QIcon, QSplitter) QSplitter, Qt, QUrl, QVBoxLayout, QWidget, pyqtSignal
from PyQt5.QtWebKitWidgets import QWebView, QWebPage )
from PyQt5.QtWebKit import QWebElement from PyQt5.QtWebEngineWidgets import QWebEnginePage, QWebEngineScript, QWebEngineView
from calibre.ebooks.oeb.display.webview import load_html from calibre.gui2 import error_dialog, gprefs, question_dialog
from calibre.gui2 import error_dialog, question_dialog, gprefs, secure_web_page from calibre.gui2.webengine import secure_webengine
from calibre.utils.logging import default_log from calibre.utils.logging import default_log
from polyglot.builtins import native_string_type, range, unicode_type from calibre.utils.short_uuid import uuid4
from polyglot.binary import as_base64_unicode from polyglot.builtins import range, unicode_type
class Page(QWebPage): # {{{ class Page(QWebEnginePage): # {{{
elem_clicked = pyqtSignal(object, object, object, object, object) elem_clicked = pyqtSignal(object, object, object, object, object)
frag_shown = pyqtSignal(object)
def __init__(self): def __init__(self):
self.log = default_log self.log = default_log
QWebPage.__init__(self) self.current_frag = None
secure_web_page(self.settings()) self.com_id = unicode_type(uuid4())
self.js = None QWebEnginePage.__init__(self)
self.evaljs = self.mainFrame().evaluateJavaScript secure_webengine(self.settings(), for_viewer=True)
nam = self.networkAccessManager() self.titleChanged.connect(self.title_changed)
nam.setNetworkAccessible(nam.NotAccessible) self.loadFinished.connect(self.show_frag)
self.setLinkDelegationPolicy(self.DelegateAllLinks) s = QWebEngineScript()
s.setName('toc.js')
s.setInjectionPoint(QWebEngineScript.DocumentReady)
s.setRunsOnSubFrames(True)
s.setWorldId(QWebEngineScript.ApplicationWorld)
s.setSourceCode(P('toc.js', allow_user_override=False, data=True).decode('utf-8').replace('COM_ID', self.com_id))
self.scripts().insert(s)
def javaScriptConsoleMessage(self, msg, lineno, msgid): def javaScriptConsoleMessage(self, level, msg, lineno, msgid):
self.log('JS:', unicode_type(msg)) self.log('JS:', unicode_type(msg))
def javaScriptAlert(self, frame, msg): def javaScriptAlert(self, origin, msg):
self.log(unicode_type(msg)) self.log(unicode_type(msg))
@pyqtSlot(result=bool) def title_changed(self, title):
def shouldInterruptJavaScript(self): parts = title.split('-', 1)
return True if len(parts) == 2 and parts[0] == self.com_id:
self.runJavaScript(
'JSON.stringify(window.calibre_toc_data)',
QWebEngineScript.ApplicationWorld, self.onclick)
@pyqtSlot(QWebElement, native_string_type, native_string_type, float) def onclick(self, data):
def onclick(self, elem, loc, totals, frac): try:
elem_id = unicode_type(elem.attribute('id')) or None tag, elem_id, loc, totals, frac = json.loads(data)
tag = unicode_type(elem.tagName()).lower() except Exception:
self.elem_clicked.emit(tag, frac, elem_id, json.loads(unicode_type(loc)), json.loads(unicode_type(totals))) return
elem_id = elem_id or None
self.elem_clicked.emit(tag, frac, elem_id, loc, totals)
def show_frag(self, ok):
if ok and self.current_frag:
self.runJavaScript('''
document.location = '#non-existent-anchor';
document.location = '#' + {0};
'''.format(json.dumps(self.current_frag)))
self.current_frag = None
self.runJavaScript('window.pageYOffset/document.body.scrollHeight', QWebEngineScript.ApplicationWorld, self.frag_shown.emit)
def load_js(self):
if self.js is None:
from calibre.utils.resources import compiled_coffeescript
self.js = compiled_coffeescript('ebooks.oeb.display.utils')
self.js += compiled_coffeescript('ebooks.oeb.polish.choose')
if isinstance(self.js, bytes):
self.js = self.js.decode('utf-8')
self.mainFrame().addToJavaScriptWindowObject("py_bridge", self)
self.evaljs(self.js)
# }}} # }}}
class WebView(QWebView): # {{{ class WebView(QWebEngineView): # {{{
elem_clicked = pyqtSignal(object, object, object, object, object) elem_clicked = pyqtSignal(object, object, object, object, object)
frag_shown = pyqtSignal(object)
def __init__(self, parent): def __init__(self, parent):
QWebView.__init__(self, parent) QWebEngineView.__init__(self, parent)
self._page = Page() self._page = Page()
self._page.elem_clicked.connect(self.elem_clicked) self._page.elem_clicked.connect(self.elem_clicked)
self._page.frag_shown.connect(self.frag_shown)
self.setPage(self._page) self.setPage(self._page)
raw = '''
body { background-color: white }
.calibre_toc_hover:hover { cursor: pointer !important; border-top: solid 5px green !important }
'''
raw = '::selection {background:#ffff00; color:#000;}\n'+raw
data = 'data:text/css;charset=utf-8;base64,'
data += as_base64_unicode(raw)
self.settings().setUserStyleSheetUrl(QUrl(data))
def load_js(self): def load_path(self, path, frag=None):
self.page().load_js() self._page.current_frag = frag
self.setUrl(QUrl.fromLocalFile(path))
def sizeHint(self): def sizeHint(self):
return QSize(1500, 300) return QSize(1500, 300)
def show_frag(self, frag):
self.page().mainFrame().scrollToAnchor(frag)
@property
def scroll_frac(self):
try:
val = float(self.page().evaljs('window.pageYOffset/document.body.scrollHeight'))
except (TypeError, ValueError):
val = 0
return val
# }}} # }}}
@ -105,6 +98,8 @@ class ItemEdit(QWidget):
def __init__(self, parent, prefs=None): def __init__(self, parent, prefs=None):
QWidget.__init__(self, parent) QWidget.__init__(self, parent)
self.prefs = prefs or gprefs self.prefs = prefs or gprefs
self.pending_search = None
self.current_frag = None
self.setLayout(QVBoxLayout()) self.setLayout(QVBoxLayout())
self.la = la = QLabel('<b>'+_( self.la = la = QLabel('<b>'+_(
@ -126,6 +121,8 @@ class ItemEdit(QWidget):
w.setLayout(l) w.setLayout(l)
self.view = WebView(self) self.view = WebView(self)
self.view.elem_clicked.connect(self.elem_clicked) self.view.elem_clicked.connect(self.elem_clicked)
self.view.frag_shown.connect(self.update_dest_label, type=Qt.QueuedConnection)
self.view.loadFinished.connect(self.load_finished, type=Qt.QueuedConnection)
l.addWidget(self.view, 0, 0, 1, 3) l.addWidget(self.view, 0, 0, 1, 3)
sp.addWidget(w) sp.addWidget(w)
@ -178,6 +175,11 @@ class ItemEdit(QWidget):
if state is not None: if state is not None:
sp.restoreState(state) sp.restoreState(state)
def load_finished(self, ok):
if self.pending_search:
self.pending_search()
self.pending_search = None
def keyPressEvent(self, ev): def keyPressEvent(self, ev):
if ev.key() in (Qt.Key_Return, Qt.Key_Enter) and self.search_text.hasFocus(): if ev.key() in (Qt.Key_Return, Qt.Key_Enter) and self.search_text.hasFocus():
# Prevent pressing enter in the search box from triggering the dialog's accept() method # Prevent pressing enter in the search box from triggering the dialog's accept() method
@ -187,11 +189,14 @@ class ItemEdit(QWidget):
def find(self, forwards=True): def find(self, forwards=True):
text = unicode_type(self.search_text.text()).strip() text = unicode_type(self.search_text.text()).strip()
flags = QWebPage.FindFlags(0) if forwards else QWebPage.FindBackward flags = QWebEnginePage.FindFlags(0) if forwards else QWebEnginePage.FindBackward
self.find_data = text, flags, forwards
self.view.findText(text, flags, self.find_callback)
def find_callback(self, found):
d = self.dest_list d = self.dest_list
if d.count() == 1: text, flags, forwards = self.find_data
flags |= QWebPage.FindWrapsAroundDocument if not found and text:
if not self.view.findText(text, flags) and text:
if d.count() == 1: if d.count() == 1:
return error_dialog(self, _('No match found'), return error_dialog(self, _('No match found'),
_('No match found for: %s')%text, show=True) _('No match found for: %s')%text, show=True)
@ -223,7 +228,6 @@ class ItemEdit(QWidget):
def current_changed(self, item): def current_changed(self, item):
name = self.current_name = unicode_type(item.data(Qt.DisplayRole) or '') name = self.current_name = unicode_type(item.data(Qt.DisplayRole) or '')
self.current_frag = None
path = self.container.name_to_abspath(name) path = self.container.name_to_abspath(name)
# Ensure encoding map is populated # Ensure encoding map is populated
root = self.container.parsed(name) root = self.container.parsed(name)
@ -236,17 +240,10 @@ class ItemEdit(QWidget):
for x in reversed(nasty): for x in reversed(nasty):
body[0].insert(0, x) body[0].insert(0, x)
self.container.commit_item(name, keep_parsed=True) self.container.commit_item(name, keep_parsed=True)
encoding = self.container.encoding_map.get(name, None) or 'utf-8' self.view.load_path(path, self.current_frag)
self.current_frag = None
load_html(path, self.view, codec=encoding,
mime_type=self.container.mime_map[name])
self.view.load_js()
self.dest_label.setText(self.base_msg + '<br>' + _('File:') + ' ' + self.dest_label.setText(self.base_msg + '<br>' + _('File:') + ' ' +
name + '<br>' + _('Top of the file')) name + '<br>' + _('Top of the file'))
if hasattr(self, 'pending_search'):
f = self.pending_search
del self.pending_search
f()
def __call__(self, item, where): def __call__(self, item, where):
self.current_item, self.current_where = item, where self.current_item, self.current_where = item, where
@ -271,20 +268,9 @@ class ItemEdit(QWidget):
self.dest_list.setCurrentRow(dest_index) self.dest_list.setCurrentRow(dest_index)
self.dest_list.blockSignals(False) self.dest_list.blockSignals(False)
item = self.dest_list.item(dest_index) item = self.dest_list.item(dest_index)
self.current_changed(item)
if frag: if frag:
self.current_frag = frag self.current_frag = frag
QTimer.singleShot(1, self.show_frag) self.current_changed(item)
def show_frag(self):
self.view.show_frag(self.current_frag)
QTimer.singleShot(1, self.check_frag)
def check_frag(self):
pos = self.view.scroll_frac
if pos == 0:
self.current_frag = None
self.update_dest_label()
def get_loctext(self, frac): def get_loctext(self, frac):
frac = int(round(frac * 100)) frac = int(round(frac * 100))
@ -301,8 +287,7 @@ class ItemEdit(QWidget):
self.dest_label.setText(self.base_msg + '<br>' + self.dest_label.setText(self.base_msg + '<br>' +
_('File:') + ' ' + self.current_name + '<br>' + loctext) _('File:') + ' ' + self.current_name + '<br>' + loctext)
def update_dest_label(self): def update_dest_label(self, val):
val = self.view.scroll_frac
self.dest_label.setText(self.base_msg + '<br>' + self.dest_label.setText(self.base_msg + '<br>' +
_('File:') + ' ' + self.current_name + '<br>' + _('File:') + ' ' + self.current_name + '<br>' +
self.get_loctext(val)) self.get_loctext(val))