experimental prewrap support
- add option to wrap html in implicit pre-wrap environment during editing and review - defaults to off - update paste filter to convert divs/Ps to newlines and non-breaking spaces to normal ones - catch enter key and write \n instead of creating a new div also: - remove extra caretToEnd() call that is no longer required - add dd/dt/dl to allowed tags
This commit is contained in:
parent
f7b3457ff0
commit
17bb179d06
@ -135,6 +135,10 @@ lapses=?, left=?, odue=?, odid=?, did=? where id = ?""",
|
||||
else:
|
||||
args = tuple()
|
||||
self._qa = self.col._renderQA(data, *args)
|
||||
if m.get("prewrap", False):
|
||||
wsdiv = "<div style='white-space:pre-wrap;'>{}</div>"
|
||||
self._qa['q'] = wsdiv.format(self._qa['q'])
|
||||
self._qa['a'] = wsdiv.format(self._qa['a'])
|
||||
return self._qa
|
||||
|
||||
def note(self, reload=False):
|
||||
|
116
aqt/editor.py
116
aqt/editor.py
@ -61,6 +61,8 @@ background-image: -webkit-gradient(linear, left top, left bottom,
|
||||
border-bottom: 3px solid #000;
|
||||
}
|
||||
|
||||
.prewrap { white-space: pre-wrap; }
|
||||
|
||||
#fields { margin-top: 35px; }
|
||||
|
||||
|
||||
@ -69,6 +71,7 @@ background-image: -webkit-gradient(linear, left top, left bottom,
|
||||
var currentField = null;
|
||||
var changeTimer = null;
|
||||
var dropTarget = null;
|
||||
var prewrapMode = false;
|
||||
|
||||
String.prototype.format = function() {
|
||||
var args = arguments;
|
||||
@ -93,6 +96,12 @@ function onKey() {
|
||||
currentField.blur();
|
||||
return;
|
||||
}
|
||||
// catch enter key in prewrap mode
|
||||
if (window.event.which == 13 && prewrapMode) {
|
||||
window.event.preventDefault();
|
||||
insertNewline();
|
||||
return;
|
||||
}
|
||||
clearChangeTimer();
|
||||
changeTimer = setTimeout(function () {
|
||||
updateButtonState();
|
||||
@ -100,6 +109,40 @@ function onKey() {
|
||||
}, 600);
|
||||
};
|
||||
|
||||
function insertNewline() {
|
||||
if (!inPreEnvironment()) {
|
||||
setFormat("insertText", "\\n");
|
||||
return;
|
||||
}
|
||||
|
||||
// in some cases inserting a newline will not show any changes,
|
||||
// as a trailing newline at the end of a block does not render
|
||||
// differently. so in such cases we note the height has not
|
||||
// changed and insert an extra newline.
|
||||
|
||||
var r = window.getSelection().getRangeAt(0);
|
||||
if (!r.collapsed) {
|
||||
// delete any currently selected text first, making
|
||||
// sure the delete is undoable
|
||||
setFormat("delete");
|
||||
}
|
||||
|
||||
var oldHeight = currentField.clientHeight;
|
||||
setFormat("inserthtml", "\\n");
|
||||
if (currentField.clientHeight == oldHeight) {
|
||||
setFormat("inserthtml", "\\n");
|
||||
}
|
||||
}
|
||||
|
||||
// is the cursor in an environment that respects whitespace?
|
||||
function inPreEnvironment() {
|
||||
var n = window.getSelection().anchorNode;
|
||||
if (n.nodeType == 3) {
|
||||
n = n.parentNode;
|
||||
}
|
||||
return window.getComputedStyle(n).whiteSpace.startsWith("pre");
|
||||
}
|
||||
|
||||
function checkForEmptyField() {
|
||||
if (currentField.innerHTML == "") {
|
||||
currentField.innerHTML = "<br>";
|
||||
@ -152,8 +195,6 @@ function onFocus(elem) {
|
||||
if (mouseDown) { return; }
|
||||
// do this twice so that there's no flicker on newer versions
|
||||
caretToEnd();
|
||||
// need to do this in a timeout for older qt versions
|
||||
setTimeout(function () { caretToEnd() }, 1);
|
||||
// scroll if bottom of element off the screen
|
||||
function pos(obj) {
|
||||
var cur = 0;
|
||||
@ -251,7 +292,7 @@ function wrap(front, back) {
|
||||
}
|
||||
};
|
||||
|
||||
function setFields(fields, focusTo) {
|
||||
function setFields(fields, focusTo, prewrap) {
|
||||
var txt = "";
|
||||
for (var i=0; i<fields.length; i++) {
|
||||
var n = fields[i][0];
|
||||
@ -274,6 +315,10 @@ function setFields(fields, focusTo) {
|
||||
$("#f"+focusTo).focus();
|
||||
}
|
||||
maybeDisableButtons();
|
||||
prewrapMode = prewrap;
|
||||
if (prewrap) {
|
||||
$(".field").addClass("prewrap");
|
||||
}
|
||||
};
|
||||
|
||||
function setBackgrounds(cols) {
|
||||
@ -319,7 +364,7 @@ var allowedTags = {};
|
||||
|
||||
var TAGS_WITHOUT_ATTRS = ["H1", "H2", "H3", "P", "DIV", "BR", "LI", "UL",
|
||||
"OL", "B", "I", "U", "BLOCKQUOTE", "CODE", "EM",
|
||||
"STRONG", "PRE", "SUB", "SUP", "TABLE"];
|
||||
"STRONG", "PRE", "SUB", "SUP", "TABLE", "DD", "DT", "DL"];
|
||||
for (var i = 0; i < TAGS_WITHOUT_ATTRS.length; i++) {
|
||||
allowedTags[TAGS_WITHOUT_ATTRS[i]] = {"attrs": []};
|
||||
}
|
||||
@ -330,9 +375,41 @@ allowedTags["TD"] = {"attrs": ["COLSPAN", "ROWSPAN"]};
|
||||
allowedTags["TH"] = {"attrs": ["COLSPAN", "ROWSPAN"]};
|
||||
allowedTags["IMG"] = {"attrs": ["SRC"]};
|
||||
|
||||
var blockRegex = /^(address|blockquote|br|center|div|dl|h[1-6]|hr|ol|p|pre|table|ul|dd|dt|li|tbody|td|tfoot|th|thead|tr)$/i;
|
||||
function isBlockLevel(n) {
|
||||
return blockRegex.test(n.nodeName);
|
||||
};
|
||||
|
||||
function isInlineElement(n) {
|
||||
return n && !isBlockLevel(n);
|
||||
}
|
||||
|
||||
function convertDivToNewline(node, isParagraph) {
|
||||
var html = node.innerHTML;
|
||||
if (isInlineElement(node.previousSibling) && html) {
|
||||
html = "\\n" + html;
|
||||
}
|
||||
if (isInlineElement(node.nextSibling)) {
|
||||
html += "\\n";
|
||||
}
|
||||
if (isParagraph) {
|
||||
html += "\\n";
|
||||
}
|
||||
node.outerHTML = html;
|
||||
};
|
||||
|
||||
var filterNode = function(node) {
|
||||
// if it's a text node, nothing to do
|
||||
// text node?
|
||||
if (node.nodeType == 3) {
|
||||
if (prewrapMode) {
|
||||
// collapse standard whitespace
|
||||
var val = node.nodeValue.replace(/^[ \\r\\n\\t]+$/g, " ");
|
||||
|
||||
// non-breaking spaces can be represented as normal spaces
|
||||
val = val.replace(/ |\u00a0/g, " ");
|
||||
|
||||
node.nodeValue = val;
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
@ -353,8 +430,18 @@ var filterNode = function(node) {
|
||||
|
||||
var tag = allowedTags[node.tagName];
|
||||
if (!tag) {
|
||||
node.outerHTML = node.innerHTML;
|
||||
} else {
|
||||
if (!node.innerHTML) {
|
||||
node.parentNode.removeChild(node);
|
||||
} else {
|
||||
node.outerHTML = node.innerHTML;
|
||||
}
|
||||
} else if (prewrapMode && node.tagName == "BR") {
|
||||
node.outerHTML = "\\n";
|
||||
} else if (prewrapMode && node.tagName == "DIV") {
|
||||
convertBlockToNewline(node, false);
|
||||
} else if (prewrapMode && node.tagName == "P") {
|
||||
convertBlockToNewline(node, true);
|
||||
} else {
|
||||
// allowed, filter out attributes
|
||||
var toRemove = [];
|
||||
for (var i = 0; i < node.attributes.length; i++) {
|
||||
@ -644,8 +731,8 @@ class Editor:
|
||||
data = []
|
||||
for fld, val in list(self.note.items()):
|
||||
data.append((fld, self.mw.col.media.escapeImages(val)))
|
||||
self.web.eval("setFields(%s, %d);" % (
|
||||
json.dumps(data), field))
|
||||
self.web.eval("setFields(%s, %d, %s);" % (
|
||||
json.dumps(data), field, json.dumps(self.prewrapMode())))
|
||||
self.web.eval("setFonts(%s);" % (
|
||||
json.dumps(self.fonts())))
|
||||
self.checkValid()
|
||||
@ -655,6 +742,8 @@ class Editor:
|
||||
self.web.setFocus()
|
||||
self.stealFocus = False
|
||||
|
||||
def prewrapMode(self):
|
||||
return self.note.model().get('prewrap', False)
|
||||
|
||||
def focus(self):
|
||||
self.web.setFocus()
|
||||
@ -962,9 +1051,10 @@ to a cloze type first, via Edit>Change Note Type."""))
|
||||
for node in doc(tag):
|
||||
node.decompose()
|
||||
|
||||
# convert p tags to divs
|
||||
for node in doc("p"):
|
||||
node.name = "div"
|
||||
if not self.prewrapMode():
|
||||
# convert p tags to divs
|
||||
for node in doc("p"):
|
||||
node.name = "div"
|
||||
|
||||
for tag in doc("img"):
|
||||
try:
|
||||
@ -1123,8 +1213,6 @@ class EditorWebView(AnkiWebView):
|
||||
|
||||
# normal text; convert it to HTML
|
||||
txt = html.escape(txt)
|
||||
txt = txt.replace("\n", "<br>")
|
||||
txt = txt.replace(" ", " ")
|
||||
return txt
|
||||
|
||||
def _processHtml(self, mime):
|
||||
|
@ -109,6 +109,7 @@ class Models(QDialog):
|
||||
frm.setupUi(d)
|
||||
frm.latexHeader.setText(self.model['latexPre'])
|
||||
frm.latexFooter.setText(self.model['latexPost'])
|
||||
frm.newStyleWhitespace.setChecked(self.model.get("prewrap", False))
|
||||
d.setWindowTitle(_("Options for %s") % self.model['name'])
|
||||
frm.buttonBox.helpRequested.connect(lambda: openHelp("latex"))
|
||||
restoreGeom(d, "modelopts")
|
||||
@ -116,6 +117,7 @@ class Models(QDialog):
|
||||
saveGeom(d, "modelopts")
|
||||
self.model['latexPre'] = str(frm.latexHeader.toPlainText())
|
||||
self.model['latexPost'] = str(frm.latexFooter.toPlainText())
|
||||
self.model['prewrap'] = frm.newStyleWhitespace.isChecked()
|
||||
|
||||
def saveModel(self):
|
||||
self.mm.save(self.model)
|
||||
|
@ -57,6 +57,33 @@
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
<widget class="QWidget" name="tab_2">
|
||||
<attribute name="title">
|
||||
<string>General</string>
|
||||
</attribute>
|
||||
<layout class="QVBoxLayout" name="verticalLayout_3">
|
||||
<item>
|
||||
<widget class="QCheckBox" name="newStyleWhitespace">
|
||||
<property name="text">
|
||||
<string>New style whitespace handling (EXPERIMENTAL)</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<spacer name="verticalSpacer">
|
||||
<property name="orientation">
|
||||
<enum>Qt::Vertical</enum>
|
||||
</property>
|
||||
<property name="sizeHint" stdset="0">
|
||||
<size>
|
||||
<width>20</width>
|
||||
<height>40</height>
|
||||
</size>
|
||||
</property>
|
||||
</spacer>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
|
Loading…
Reference in New Issue
Block a user