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
# 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
__license__ = 'GPL v3'
__copyright__ = '2013, Kovid Goyal <kovid at kovidgoyal.net>'
__docformat__ = 'restructuredtext en'
import json
from PyQt5.Qt import (QWidget, QGridLayout, QListWidget, QSize, Qt, QUrl,
pyqtSlot, pyqtSignal, QVBoxLayout, QFrame, QLabel,
QLineEdit, QTimer, QPushButton, QIcon, QSplitter)
from PyQt5.QtWebKitWidgets import QWebView, QWebPage
from PyQt5.QtWebKit import QWebElement
from PyQt5.Qt import (
QFrame, QGridLayout, QIcon, QLabel, QLineEdit, QListWidget, QPushButton, QSize,
QSplitter, Qt, QUrl, QVBoxLayout, QWidget, pyqtSignal
)
from PyQt5.QtWebEngineWidgets import QWebEnginePage, QWebEngineScript, QWebEngineView
from calibre.ebooks.oeb.display.webview import load_html
from calibre.gui2 import error_dialog, question_dialog, gprefs, secure_web_page
from calibre.gui2 import error_dialog, gprefs, question_dialog
from calibre.gui2.webengine import secure_webengine
from calibre.utils.logging import default_log
from polyglot.builtins import native_string_type, range, unicode_type
from polyglot.binary import as_base64_unicode
from calibre.utils.short_uuid import uuid4
from polyglot.builtins import range, unicode_type
class Page(QWebPage): # {{{
class Page(QWebEnginePage): # {{{
elem_clicked = pyqtSignal(object, object, object, object, object)
frag_shown = pyqtSignal(object)
def __init__(self):
self.log = default_log
QWebPage.__init__(self)
secure_web_page(self.settings())
self.js = None
self.evaljs = self.mainFrame().evaluateJavaScript
nam = self.networkAccessManager()
nam.setNetworkAccessible(nam.NotAccessible)
self.setLinkDelegationPolicy(self.DelegateAllLinks)
self.current_frag = None
self.com_id = unicode_type(uuid4())
QWebEnginePage.__init__(self)
secure_webengine(self.settings(), for_viewer=True)
self.titleChanged.connect(self.title_changed)
self.loadFinished.connect(self.show_frag)
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))
def javaScriptAlert(self, frame, msg):
def javaScriptAlert(self, origin, msg):
self.log(unicode_type(msg))
@pyqtSlot(result=bool)
def shouldInterruptJavaScript(self):
return True
def title_changed(self, title):
parts = title.split('-', 1)
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, elem, loc, totals, frac):
elem_id = unicode_type(elem.attribute('id')) or None
tag = unicode_type(elem.tagName()).lower()
self.elem_clicked.emit(tag, frac, elem_id, json.loads(unicode_type(loc)), json.loads(unicode_type(totals)))
def onclick(self, data):
try:
tag, elem_id, loc, totals, frac = json.loads(data)
except Exception:
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)
frag_shown = pyqtSignal(object)
def __init__(self, parent):
QWebView.__init__(self, parent)
QWebEngineView.__init__(self, parent)
self._page = Page()
self._page.elem_clicked.connect(self.elem_clicked)
self._page.frag_shown.connect(self.frag_shown)
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):
self.page().load_js()
def load_path(self, path, frag=None):
self._page.current_frag = frag
self.setUrl(QUrl.fromLocalFile(path))
def sizeHint(self):
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):
QWidget.__init__(self, parent)
self.prefs = prefs or gprefs
self.pending_search = None
self.current_frag = None
self.setLayout(QVBoxLayout())
self.la = la = QLabel('<b>'+_(
@ -126,6 +121,8 @@ class ItemEdit(QWidget):
w.setLayout(l)
self.view = WebView(self)
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)
sp.addWidget(w)
@ -178,6 +175,11 @@ class ItemEdit(QWidget):
if state is not None:
sp.restoreState(state)
def load_finished(self, ok):
if self.pending_search:
self.pending_search()
self.pending_search = None
def keyPressEvent(self, ev):
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
@ -187,11 +189,14 @@ class ItemEdit(QWidget):
def find(self, forwards=True):
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
if d.count() == 1:
flags |= QWebPage.FindWrapsAroundDocument
if not self.view.findText(text, flags) and text:
text, flags, forwards = self.find_data
if not found and text:
if d.count() == 1:
return error_dialog(self, _('No match found'),
_('No match found for: %s')%text, show=True)
@ -223,7 +228,6 @@ class ItemEdit(QWidget):
def current_changed(self, item):
name = self.current_name = unicode_type(item.data(Qt.DisplayRole) or '')
self.current_frag = None
path = self.container.name_to_abspath(name)
# Ensure encoding map is populated
root = self.container.parsed(name)
@ -236,17 +240,10 @@ class ItemEdit(QWidget):
for x in reversed(nasty):
body[0].insert(0, x)
self.container.commit_item(name, keep_parsed=True)
encoding = self.container.encoding_map.get(name, None) or 'utf-8'
load_html(path, self.view, codec=encoding,
mime_type=self.container.mime_map[name])
self.view.load_js()
self.view.load_path(path, self.current_frag)
self.current_frag = None
self.dest_label.setText(self.base_msg + '<br>' + _('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):
self.current_item, self.current_where = item, where
@ -271,20 +268,9 @@ class ItemEdit(QWidget):
self.dest_list.setCurrentRow(dest_index)
self.dest_list.blockSignals(False)
item = self.dest_list.item(dest_index)
self.current_changed(item)
if frag:
self.current_frag = frag
QTimer.singleShot(1, self.show_frag)
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()
self.current_changed(item)
def get_loctext(self, frac):
frac = int(round(frac * 100))
@ -301,8 +287,7 @@ class ItemEdit(QWidget):
self.dest_label.setText(self.base_msg + '<br>' +
_('File:') + ' ' + self.current_name + '<br>' + loctext)
def update_dest_label(self):
val = self.view.scroll_frac
def update_dest_label(self, val):
self.dest_label.setText(self.base_msg + '<br>' +
_('File:') + ' ' + self.current_name + '<br>' +
self.get_loctext(val))