separate basic and extended paste modes

- basic mode is the default, and includes only HTML elements that can be
added/edited easily with the default editor
- extended mode is enabled by holding down shift and includes a bunch of
other HTML elements
This commit is contained in:
Damien Elmes 2017-10-25 20:20:28 +10:00
parent 23e0034278
commit 1d3e5787a2
3 changed files with 43 additions and 20 deletions

View file

@ -646,8 +646,13 @@ to a cloze type first, via Edit>Change Note Type."""))
def doPaste(self, html, internal): def doPaste(self, html, internal):
if not internal: if not internal:
html = self._pastePreFilter(html) html = self._pastePreFilter(html)
self.web.eval("pasteHTML(%s, %s);" % ( extended = self.mw.app.queryKeyboardModifiers() & Qt.ShiftModifier
json.dumps(html), json.dumps(internal))) if extended:
extended = "true"
else:
extended = "false"
self.web.eval("pasteHTML(%s, %s, %s);" % (
json.dumps(html), json.dumps(internal), extended))
def doDrop(self, html, internal): def doDrop(self, html, internal):
self.web.evalWithCallback("makeDropTargetCurrent();", self.web.evalWithCallback("makeDropTargetCurrent();",

View file

@ -99,6 +99,8 @@ class AnkiWebView(QWebEngineView):
QShortcut(key, self, QShortcut(key, self,
context=Qt.WidgetWithChildrenShortcut, context=Qt.WidgetWithChildrenShortcut,
activated=fn) activated=fn)
QShortcut(QKeySequence("ctrl+shift+v"), self,
context=Qt.WidgetWithChildrenShortcut, activated=self.onPaste)
self.focusProxy().installEventFilter(self) self.focusProxy().installEventFilter(self)

View file

@ -300,18 +300,18 @@ function hideDupes() {
$("#dupes").hide(); $("#dupes").hide();
} }
var pasteHTML = function (html, internal) { var pasteHTML = function (html, internal, allowedTags) {
html = filterHTML(html, internal); html = filterHTML(html, internal, allowedTags);
setFormat("inserthtml", html); setFormat("inserthtml", html);
}; };
var filterHTML = function (html, internal) { var filterHTML = function (html, internal, extendedMode) {
// wrap it in <top> as we aren't allowed to change top level elements // wrap it in <top> as we aren't allowed to change top level elements
var top = $.parseHTML("<ankitop>" + html + "</ankitop>")[0]; var top = $.parseHTML("<ankitop>" + html + "</ankitop>")[0];
if (internal) { if (internal) {
filterInternalNode(top); filterInternalNode(top);
} else { } else {
filterNode(top); filterNode(top, extendedMode);
} }
var outHtml = top.innerHTML; var outHtml = top.innerHTML;
//console.log(`input html: ${html}`); //console.log(`input html: ${html}`);
@ -319,20 +319,31 @@ var filterHTML = function (html, internal) {
return outHtml; return outHtml;
}; };
var allowedTags = {}; var allowedTagsBasic = {};
var allowedTagsExtended = {};
var TAGS_WITHOUT_ATTRS = ["H1", "H2", "H3", "P", "DIV", "BR", "LI", "UL", var TAGS_WITHOUT_ATTRS = ["P", "DIV", "BR",
"OL", "B", "I", "U", "BLOCKQUOTE", "CODE", "EM", "B", "I", "U", "EM", "STRONG", "SUB", "SUP"];
"STRONG", "PRE", "SUB", "SUP", "TABLE", "DD", "DT", "DL"]; var i;
for (var i = 0; i < TAGS_WITHOUT_ATTRS.length; i++) { for (i = 0; i < TAGS_WITHOUT_ATTRS.length; i++) {
allowedTags[TAGS_WITHOUT_ATTRS[i]] = {"attrs": []}; allowedTagsBasic[TAGS_WITHOUT_ATTRS[i]] = {"attrs": []};
} }
allowedTags["A"] = {"attrs": ["HREF"]}; TAGS_WITHOUT_ATTRS = ["H1", "H2", "H3", "LI", "UL", "BLOCKQUOTE", "CODE",
allowedTags["TR"] = {"attrs": ["ROWSPAN"]}; "PRE", "TABLE", "DD", "DT", "DL"];
allowedTags["TD"] = {"attrs": ["COLSPAN", "ROWSPAN"]}; for (i = 0; i < TAGS_WITHOUT_ATTRS.length; i++) {
allowedTags["TH"] = {"attrs": ["COLSPAN", "ROWSPAN"]}; allowedTagsExtended[TAGS_WITHOUT_ATTRS[i]] = {"attrs": []};
allowedTags["IMG"] = {"attrs": ["SRC"]}; }
allowedTagsBasic["IMG"] = {"attrs": ["SRC"]};
allowedTagsExtended["A"] = {"attrs": ["HREF"]};
allowedTagsExtended["TR"] = {"attrs": ["ROWSPAN"]};
allowedTagsExtended["TD"] = {"attrs": ["COLSPAN", "ROWSPAN"]};
allowedTagsExtended["TH"] = {"attrs": ["COLSPAN", "ROWSPAN"]};
// add basic tags to extended
Object.assign(allowedTagsExtended, allowedTagsBasic);
// filtering from another field // filtering from another field
var filterInternalNode = function (node) { var filterInternalNode = function (node) {
@ -348,7 +359,7 @@ var filterInternalNode = function (node) {
}; };
// filtering from external sources // filtering from external sources
var filterNode = function (node) { var filterNode = function (node, extendedMode) {
// text node? // text node?
if (node.nodeType === 3) { if (node.nodeType === 3) {
return; return;
@ -363,14 +374,19 @@ var filterNode = function (node) {
nodes.push(node.childNodes[i]); nodes.push(node.childNodes[i]);
} }
for (i = 0; i < nodes.length; i++) { for (i = 0; i < nodes.length; i++) {
filterNode(nodes[i]); filterNode(nodes[i], extendedMode);
} }
if (node.tagName === "ANKITOP") { if (node.tagName === "ANKITOP") {
return; return;
} }
var tag = allowedTags[node.tagName]; var tag;
if (extendedMode) {
tag = allowedTagsExtended[node.tagName];
} else {
tag = allowedTagsBasic[node.tagName];
}
if (!tag) { if (!tag) {
if (!node.innerHTML) { if (!node.innerHTML) {
node.parentNode.removeChild(node); node.parentNode.removeChild(node);