lint: contentcollector and domline
Various tidy up and linting of contentcollector.js and domline.js. 3 Tests disabled which are not due to be covered. Co-authored-by: Richard Hansen <rhansen@rhansen.org>pull/4674/head
parent
10a91825fc
commit
f0a77cb98c
|
@ -29,47 +29,31 @@ const _MAX_LIST_LEVEL = 16;
|
|||
const UNorm = require('unorm');
|
||||
const Changeset = require('./Changeset');
|
||||
const hooks = require('./pluginfw/hooks');
|
||||
const _ = require('./underscore');
|
||||
|
||||
function sanitizeUnicode(s) {
|
||||
return UNorm.nfc(s);
|
||||
}
|
||||
|
||||
function makeContentCollector(collectStyles, abrowser, apool, domInterface, className2Author) {
|
||||
abrowser = abrowser || {};
|
||||
// I don't like the above.
|
||||
const sanitizeUnicode = (s) => UNorm.nfc(s);
|
||||
|
||||
const makeContentCollector = (collectStyles, abrowser, apool, domInterface, className2Author) => {
|
||||
const dom = domInterface || {
|
||||
isNodeText(n) {
|
||||
return (n.nodeType == 3);
|
||||
},
|
||||
nodeTagName(n) {
|
||||
return n.tagName;
|
||||
},
|
||||
nodeValue(n) {
|
||||
return n.nodeValue;
|
||||
},
|
||||
nodeNumChildren(n) {
|
||||
isNodeText: (n) => n.nodeType === 3,
|
||||
nodeTagName: (n) => n.tagName,
|
||||
nodeValue: (n) => n.nodeValue,
|
||||
nodeNumChildren: (n) => {
|
||||
if (n.childNodes == null) return 0;
|
||||
return n.childNodes.length;
|
||||
},
|
||||
nodeChild(n, i) {
|
||||
nodeChild: (n, i) => {
|
||||
if (n.childNodes.item == null) {
|
||||
return n.childNodes[i];
|
||||
}
|
||||
return n.childNodes.item(i);
|
||||
},
|
||||
nodeProp(n, p) {
|
||||
return n[p];
|
||||
},
|
||||
nodeAttr(n, a) {
|
||||
nodeProp: (n, p) => n[p],
|
||||
nodeAttr: (n, a) => {
|
||||
if (n.getAttribute != null) return n.getAttribute(a);
|
||||
if (n.attribs != null) return n.attribs[a];
|
||||
return null;
|
||||
},
|
||||
optNodeInnerHTML(n) {
|
||||
return n.innerHTML;
|
||||
},
|
||||
optNodeInnerHTML: (n) => n.innerHTML,
|
||||
};
|
||||
|
||||
const _blockElems = {
|
||||
|
@ -79,58 +63,45 @@ function makeContentCollector(collectStyles, abrowser, apool, domInterface, clas
|
|||
li: 1,
|
||||
};
|
||||
|
||||
_.each(hooks.callAll('ccRegisterBlockElements'), (element) => {
|
||||
hooks.callAll('ccRegisterBlockElements').forEach((element) => {
|
||||
_blockElems[element] = 1;
|
||||
});
|
||||
|
||||
function isBlockElement(n) {
|
||||
return !!_blockElems[(dom.nodeTagName(n) || '').toLowerCase()];
|
||||
}
|
||||
const isBlockElement = (n) => !!_blockElems[(dom.nodeTagName(n) || '').toLowerCase()];
|
||||
|
||||
function textify(str) {
|
||||
return sanitizeUnicode(
|
||||
str.replace(/(\n | \n)/g, ' ').replace(/[\n\r ]/g, ' ').replace(/\xa0/g, ' ').replace(/\t/g, ' '));
|
||||
}
|
||||
const textify = (str) => sanitizeUnicode(
|
||||
str.replace(/(\n | \n)/g, ' ')
|
||||
.replace(/[\n\r ]/g, ' ')
|
||||
.replace(/\xa0/g, ' ')
|
||||
.replace(/\t/g, ' '));
|
||||
|
||||
function getAssoc(node, name) {
|
||||
return dom.nodeProp(node, `_magicdom_${name}`);
|
||||
}
|
||||
const getAssoc = (node, name) => dom.nodeProp(node, `_magicdom_${name}`);
|
||||
|
||||
const lines = (function () {
|
||||
const lines = (() => {
|
||||
const textArray = [];
|
||||
const attribsArray = [];
|
||||
let attribsBuilder = null;
|
||||
const op = Changeset.newOp('+');
|
||||
var self = {
|
||||
length() {
|
||||
return textArray.length;
|
||||
},
|
||||
atColumnZero() {
|
||||
return textArray[textArray.length - 1] === '';
|
||||
},
|
||||
startNew() {
|
||||
const self = {
|
||||
length: () => textArray.length,
|
||||
atColumnZero: () => textArray[textArray.length - 1] === '',
|
||||
startNew: () => {
|
||||
textArray.push('');
|
||||
self.flush(true);
|
||||
attribsBuilder = Changeset.smartOpAssembler();
|
||||
},
|
||||
textOfLine(i) {
|
||||
return textArray[i];
|
||||
},
|
||||
appendText(txt, attrString) {
|
||||
textOfLine: (i) => textArray[i],
|
||||
appendText: (txt, attrString) => {
|
||||
textArray[textArray.length - 1] += txt;
|
||||
// dmesg(txt+" / "+attrString);
|
||||
op.attribs = attrString;
|
||||
op.chars = txt.length;
|
||||
attribsBuilder.append(op);
|
||||
},
|
||||
textLines() {
|
||||
return textArray.slice();
|
||||
},
|
||||
attribLines() {
|
||||
return attribsArray;
|
||||
},
|
||||
textLines: () => textArray.slice(),
|
||||
attribLines: () => attribsArray,
|
||||
// call flush only when you're done
|
||||
flush(withNewline) {
|
||||
flush: (withNewline) => {
|
||||
if (attribsBuilder) {
|
||||
attribsArray.push(attribsBuilder.toString());
|
||||
attribsBuilder = null;
|
||||
|
@ -139,21 +110,24 @@ function makeContentCollector(collectStyles, abrowser, apool, domInterface, clas
|
|||
};
|
||||
self.startNew();
|
||||
return self;
|
||||
}());
|
||||
})();
|
||||
const cc = {};
|
||||
|
||||
function _ensureColumnZero(state) {
|
||||
const _ensureColumnZero = (state) => {
|
||||
if (!lines.atColumnZero()) {
|
||||
cc.startNewLine(state);
|
||||
}
|
||||
}
|
||||
};
|
||||
let selection, startPoint, endPoint;
|
||||
let selStart = [-1, -1];
|
||||
let selEnd = [-1, -1];
|
||||
function _isEmpty(node, state) {
|
||||
const _isEmpty = (node, state) => {
|
||||
// consider clean blank lines pasted in IE to be empty
|
||||
if (dom.nodeNumChildren(node) == 0) return true;
|
||||
if (dom.nodeNumChildren(node) == 1 && getAssoc(node, 'shouldBeEmpty') && dom.optNodeInnerHTML(node) == ' ' && !getAssoc(node, 'unpasted')) {
|
||||
if (dom.nodeNumChildren(node) === 0) return true;
|
||||
if (dom.nodeNumChildren(node) === 1 &&
|
||||
getAssoc(node, 'shouldBeEmpty') &&
|
||||
dom.optNodeInnerHTML(node) === ' ' &&
|
||||
!getAssoc(node, 'unpasted')) {
|
||||
if (state) {
|
||||
const child = dom.nodeChild(node, 0);
|
||||
_reachPoint(child, 0, state);
|
||||
|
@ -162,37 +136,37 @@ function makeContentCollector(collectStyles, abrowser, apool, domInterface, clas
|
|||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
};
|
||||
|
||||
function _pointHere(charsAfter, state) {
|
||||
const _pointHere = (charsAfter, state) => {
|
||||
const ln = lines.length() - 1;
|
||||
let chr = lines.textOfLine(ln).length;
|
||||
if (chr == 0 && !_.isEmpty(state.lineAttributes)) {
|
||||
if (chr === 0 && Object.keys(state.lineAttributes).length !== 0) {
|
||||
chr += 1; // listMarker
|
||||
}
|
||||
chr += charsAfter;
|
||||
return [ln, chr];
|
||||
}
|
||||
};
|
||||
|
||||
function _reachBlockPoint(nd, idx, state) {
|
||||
const _reachBlockPoint = (nd, idx, state) => {
|
||||
if (!dom.isNodeText(nd)) _reachPoint(nd, idx, state);
|
||||
}
|
||||
};
|
||||
|
||||
function _reachPoint(nd, idx, state) {
|
||||
if (startPoint && nd == startPoint.node && startPoint.index == idx) {
|
||||
const _reachPoint = (nd, idx, state) => {
|
||||
if (startPoint && nd === startPoint.node && startPoint.index === idx) {
|
||||
selStart = _pointHere(0, state);
|
||||
}
|
||||
if (endPoint && nd == endPoint.node && endPoint.index == idx) {
|
||||
if (endPoint && nd === endPoint.node && endPoint.index === idx) {
|
||||
selEnd = _pointHere(0, state);
|
||||
}
|
||||
}
|
||||
cc.incrementFlag = function (state, flagName) {
|
||||
};
|
||||
cc.incrementFlag = (state, flagName) => {
|
||||
state.flags[flagName] = (state.flags[flagName] || 0) + 1;
|
||||
};
|
||||
cc.decrementFlag = function (state, flagName) {
|
||||
cc.decrementFlag = (state, flagName) => {
|
||||
state.flags[flagName]--;
|
||||
};
|
||||
cc.incrementAttrib = function (state, attribName) {
|
||||
cc.incrementAttrib = (state, attribName) => {
|
||||
if (!state.attribs[attribName]) {
|
||||
state.attribs[attribName] = 1;
|
||||
} else {
|
||||
|
@ -200,15 +174,15 @@ function makeContentCollector(collectStyles, abrowser, apool, domInterface, clas
|
|||
}
|
||||
_recalcAttribString(state);
|
||||
};
|
||||
cc.decrementAttrib = function (state, attribName) {
|
||||
cc.decrementAttrib = (state, attribName) => {
|
||||
state.attribs[attribName]--;
|
||||
_recalcAttribString(state);
|
||||
};
|
||||
|
||||
function _enterList(state, listType) {
|
||||
const _enterList = (state, listType) => {
|
||||
if (!listType) return;
|
||||
const oldListType = state.lineAttributes.list;
|
||||
if (listType != 'none') {
|
||||
if (listType !== 'none') {
|
||||
state.listNesting = (state.listNesting || 0) + 1;
|
||||
// reminder that listType can be "number2", "number3" etc.
|
||||
if (listType.indexOf('number') !== -1) {
|
||||
|
@ -223,36 +197,36 @@ function makeContentCollector(collectStyles, abrowser, apool, domInterface, clas
|
|||
}
|
||||
_recalcAttribString(state);
|
||||
return oldListType;
|
||||
}
|
||||
};
|
||||
|
||||
function _exitList(state, oldListType) {
|
||||
const _exitList = (state, oldListType) => {
|
||||
if (state.lineAttributes.list) {
|
||||
state.listNesting--;
|
||||
}
|
||||
if (oldListType && oldListType != 'none') {
|
||||
if (oldListType && oldListType !== 'none') {
|
||||
state.lineAttributes.list = oldListType;
|
||||
} else {
|
||||
delete state.lineAttributes.list;
|
||||
delete state.lineAttributes.start;
|
||||
}
|
||||
_recalcAttribString(state);
|
||||
}
|
||||
};
|
||||
|
||||
function _enterAuthor(state, author) {
|
||||
const _enterAuthor = (state, author) => {
|
||||
const oldAuthor = state.author;
|
||||
state.authorLevel = (state.authorLevel || 0) + 1;
|
||||
state.author = author;
|
||||
_recalcAttribString(state);
|
||||
return oldAuthor;
|
||||
}
|
||||
};
|
||||
|
||||
function _exitAuthor(state, oldAuthor) {
|
||||
const _exitAuthor = (state, oldAuthor) => {
|
||||
state.authorLevel--;
|
||||
state.author = oldAuthor;
|
||||
_recalcAttribString(state);
|
||||
}
|
||||
};
|
||||
|
||||
function _recalcAttribString(state) {
|
||||
const _recalcAttribString = (state) => {
|
||||
const lst = [];
|
||||
for (const a in state.attribs) {
|
||||
if (state.attribs[a]) {
|
||||
|
@ -284,35 +258,34 @@ function makeContentCollector(collectStyles, abrowser, apool, domInterface, clas
|
|||
}
|
||||
}
|
||||
state.attribString = Changeset.makeAttribsString('+', lst, apool);
|
||||
}
|
||||
};
|
||||
|
||||
function _produceLineAttributesMarker(state) {
|
||||
const _produceLineAttributesMarker = (state) => {
|
||||
// TODO: This has to go to AttributeManager.
|
||||
const attributes = [
|
||||
['lmkr', '1'],
|
||||
['insertorder', 'first'],
|
||||
].concat(
|
||||
_.map(state.lineAttributes, (value, key) => [key, value])
|
||||
);
|
||||
...Object.entries(state.lineAttributes),
|
||||
];
|
||||
lines.appendText('*', Changeset.makeAttribsString('+', attributes, apool));
|
||||
}
|
||||
cc.startNewLine = function (state) {
|
||||
};
|
||||
cc.startNewLine = (state) => {
|
||||
if (state) {
|
||||
const atBeginningOfLine = lines.textOfLine(lines.length() - 1).length == 0;
|
||||
if (atBeginningOfLine && !_.isEmpty(state.lineAttributes)) {
|
||||
const atBeginningOfLine = lines.textOfLine(lines.length() - 1).length === 0;
|
||||
if (atBeginningOfLine && Object.keys(state.lineAttributes).length !== 0) {
|
||||
_produceLineAttributesMarker(state);
|
||||
}
|
||||
}
|
||||
lines.startNew();
|
||||
};
|
||||
cc.notifySelection = function (sel) {
|
||||
cc.notifySelection = (sel) => {
|
||||
if (sel) {
|
||||
selection = sel;
|
||||
startPoint = selection.startPoint;
|
||||
endPoint = selection.endPoint;
|
||||
}
|
||||
};
|
||||
cc.doAttrib = function (state, na) {
|
||||
cc.doAttrib = (state, na) => {
|
||||
state.localAttribs = (state.localAttribs || []);
|
||||
state.localAttribs.push(na);
|
||||
cc.incrementAttrib(state, na);
|
||||
|
@ -342,9 +315,10 @@ function makeContentCollector(collectStyles, abrowser, apool, domInterface, clas
|
|||
if (isBlock) _ensureColumnZero(state);
|
||||
const startLine = lines.length() - 1;
|
||||
_reachBlockPoint(node, 0, state);
|
||||
|
||||
if (dom.isNodeText(node)) {
|
||||
let txt = dom.nodeValue(node);
|
||||
var tname = dom.nodeAttr(node.parentNode, 'name');
|
||||
const tname = dom.nodeAttr(node.parentNode, 'name');
|
||||
|
||||
const txtFromHook = hooks.callAll('collectContentLineText', {
|
||||
cc: this,
|
||||
|
@ -364,11 +338,11 @@ function makeContentCollector(collectStyles, abrowser, apool, domInterface, clas
|
|||
|
||||
let rest = '';
|
||||
let x = 0; // offset into original text
|
||||
if (txt.length == 0) {
|
||||
if (startPoint && node == startPoint.node) {
|
||||
if (txt.length === 0) {
|
||||
if (startPoint && node === startPoint.node) {
|
||||
selStart = _pointHere(0, state);
|
||||
}
|
||||
if (endPoint && node == endPoint.node) {
|
||||
if (endPoint && node === endPoint.node) {
|
||||
selEnd = _pointHere(0, state);
|
||||
}
|
||||
}
|
||||
|
@ -381,10 +355,10 @@ function makeContentCollector(collectStyles, abrowser, apool, domInterface, clas
|
|||
txt = firstLine;
|
||||
} else { /* will only run this loop body once */
|
||||
}
|
||||
if (startPoint && node == startPoint.node && startPoint.index - x <= txt.length) {
|
||||
if (startPoint && node === startPoint.node && startPoint.index - x <= txt.length) {
|
||||
selStart = _pointHere(startPoint.index - x, state);
|
||||
}
|
||||
if (endPoint && node == endPoint.node && endPoint.index - x <= txt.length) {
|
||||
if (endPoint && node === endPoint.node && endPoint.index - x <= txt.length) {
|
||||
selEnd = _pointHere(endPoint.index - x, state);
|
||||
}
|
||||
let txt2 = txt;
|
||||
|
@ -395,12 +369,12 @@ function makeContentCollector(collectStyles, abrowser, apool, domInterface, clas
|
|||
// removing "\n" from pasted HTML will collapse words together.
|
||||
txt2 = '';
|
||||
}
|
||||
const atBeginningOfLine = lines.textOfLine(lines.length() - 1).length == 0;
|
||||
const atBeginningOfLine = lines.textOfLine(lines.length() - 1).length === 0;
|
||||
if (atBeginningOfLine) {
|
||||
// newlines in the source mustn't become spaces at beginning of line box
|
||||
txt2 = txt2.replace(/^\n*/, '');
|
||||
}
|
||||
if (atBeginningOfLine && !_.isEmpty(state.lineAttributes)) {
|
||||
if (atBeginningOfLine && Object.keys(state.lineAttributes).length !== 0) {
|
||||
_produceLineAttributesMarker(state);
|
||||
}
|
||||
lines.appendText(textify(txt2), state.attribString);
|
||||
|
@ -411,15 +385,15 @@ function makeContentCollector(collectStyles, abrowser, apool, domInterface, clas
|
|||
}
|
||||
}
|
||||
} else {
|
||||
var tname = (dom.nodeTagName(node) || '').toLowerCase();
|
||||
const tname = (dom.nodeTagName(node) || '').toLowerCase();
|
||||
|
||||
if (tname == 'img') {
|
||||
const collectContentImage = hooks.callAll('collectContentImage', {
|
||||
if (tname === 'img') {
|
||||
hooks.callAll('collectContentImage', {
|
||||
cc,
|
||||
state,
|
||||
tname,
|
||||
styl,
|
||||
cls,
|
||||
styl: null,
|
||||
cls: null,
|
||||
node,
|
||||
});
|
||||
} else {
|
||||
|
@ -427,7 +401,7 @@ function makeContentCollector(collectStyles, abrowser, apool, domInterface, clas
|
|||
delete state.lineAttributes.img;
|
||||
}
|
||||
|
||||
if (tname == 'br') {
|
||||
if (tname === 'br') {
|
||||
this.breakLine = true;
|
||||
const tvalue = dom.nodeAttr(node, 'value');
|
||||
const induceLineBreak = hooks.callAll('collectContentLineBreak', {
|
||||
|
@ -438,17 +412,19 @@ function makeContentCollector(collectStyles, abrowser, apool, domInterface, clas
|
|||
styl: null,
|
||||
cls: null,
|
||||
});
|
||||
const startNewLine = (typeof (induceLineBreak) === 'object' && induceLineBreak.length == 0) ? true : induceLineBreak[0];
|
||||
const startNewLine = (
|
||||
typeof (induceLineBreak) === 'object' &&
|
||||
induceLineBreak.length === 0) ? true : induceLineBreak[0];
|
||||
if (startNewLine) {
|
||||
cc.startNewLine(state);
|
||||
}
|
||||
} else if (tname == 'script' || tname == 'style') {
|
||||
} else if (tname === 'script' || tname === 'style') {
|
||||
// ignore
|
||||
} else if (!isEmpty) {
|
||||
var styl = dom.nodeAttr(node, 'style');
|
||||
var cls = dom.nodeAttr(node, 'class');
|
||||
let isPre = (tname == 'pre');
|
||||
if ((!isPre) && abrowser.safari) {
|
||||
let styl = dom.nodeAttr(node, 'style');
|
||||
let cls = dom.nodeAttr(node, 'class');
|
||||
let isPre = (tname === 'pre');
|
||||
if ((!isPre) && abrowser && abrowser.safari) {
|
||||
isPre = (styl && /\bwhite-space:\s*pre\b/i.exec(styl));
|
||||
}
|
||||
if (isPre) cc.incrementFlag(state, 'preMode');
|
||||
|
@ -460,10 +436,10 @@ function makeContentCollector(collectStyles, abrowser, apool, domInterface, clas
|
|||
styl = null;
|
||||
cls = null;
|
||||
|
||||
// We have to return here but this could break things in the future, for now it shows how to fix the problem
|
||||
// We have to return here but this could break things in the future,
|
||||
// for now it shows how to fix the problem
|
||||
return;
|
||||
}
|
||||
|
||||
if (collectStyles) {
|
||||
hooks.callAll('collectContentPre', {
|
||||
cc,
|
||||
|
@ -472,29 +448,34 @@ function makeContentCollector(collectStyles, abrowser, apool, domInterface, clas
|
|||
styl,
|
||||
cls,
|
||||
});
|
||||
if (tname == 'b' || (styl && /\bfont-weight:\s*bold\b/i.exec(styl)) || tname == 'strong') {
|
||||
if (tname === 'b' ||
|
||||
(styl && /\bfont-weight:\s*bold\b/i.exec(styl)) ||
|
||||
tname === 'strong') {
|
||||
cc.doAttrib(state, 'bold');
|
||||
}
|
||||
if (tname == 'i' || (styl && /\bfont-style:\s*italic\b/i.exec(styl)) || tname == 'em') {
|
||||
if (tname === 'i' ||
|
||||
(styl && /\bfont-style:\s*italic\b/i.exec(styl)) ||
|
||||
tname === 'em') {
|
||||
cc.doAttrib(state, 'italic');
|
||||
}
|
||||
if (tname == 'u' || (styl && /\btext-decoration:\s*underline\b/i.exec(styl)) || tname == 'ins') {
|
||||
if (tname === 'u' ||
|
||||
(styl && /\btext-decoration:\s*underline\b/i.exec(styl)) ||
|
||||
tname === 'ins') {
|
||||
cc.doAttrib(state, 'underline');
|
||||
}
|
||||
if (tname == 's' || (styl && /\btext-decoration:\s*line-through\b/i.exec(styl)) || tname == 'del') {
|
||||
if (tname === 's' ||
|
||||
(styl && /\btext-decoration:\s*line-through\b/i.exec(styl)) ||
|
||||
tname === 'del') {
|
||||
cc.doAttrib(state, 'strikethrough');
|
||||
}
|
||||
if (tname == 'ul' || tname == 'ol') {
|
||||
if (node.attribs) {
|
||||
var type = node.attribs.class;
|
||||
} else {
|
||||
var type = null;
|
||||
}
|
||||
if (tname === 'ul' || tname === 'ol') {
|
||||
let type = node.attribs ? node.attribs.class : null;
|
||||
const rr = cls && /(?:^| )list-([a-z]+[0-9]+)\b/.exec(cls);
|
||||
// lists do not need to have a type, so before we make a wrong guess, check if we find a better hint within the node's children
|
||||
// lists do not need to have a type, so before we make a wrong guess
|
||||
// check if we find a better hint within the node's children
|
||||
if (!rr && !type) {
|
||||
for (var i in node.children) {
|
||||
if (node.children[i] && node.children[i].name == 'ul') {
|
||||
for (const i in node.children) {
|
||||
if (node.children[i] && node.children[i].name === 'ul') {
|
||||
type = node.children[i].attribs.class;
|
||||
if (type) {
|
||||
break;
|
||||
|
@ -505,8 +486,9 @@ function makeContentCollector(collectStyles, abrowser, apool, domInterface, clas
|
|||
if (rr && rr[1]) {
|
||||
type = rr[1];
|
||||
} else {
|
||||
if (tname == 'ul') {
|
||||
if ((type && type.match('indent')) || (node.attribs && node.attribs.class && node.attribs.class.match('indent'))) {
|
||||
if (tname === 'ul') {
|
||||
if ((type && type.match('indent')) ||
|
||||
(node.attribs && node.attribs.class && node.attribs.class.match('indent'))) {
|
||||
type = 'indent';
|
||||
} else {
|
||||
type = 'bullet';
|
||||
|
@ -517,10 +499,10 @@ function makeContentCollector(collectStyles, abrowser, apool, domInterface, clas
|
|||
type += String(Math.min(_MAX_LIST_LEVEL, (state.listNesting || 0) + 1));
|
||||
}
|
||||
oldListTypeOrNull = (_enterList(state, type) || 'none');
|
||||
} else if ((tname == 'div' || tname == 'p') && cls && cls.match(/(?:^| )ace-line\b/)) {
|
||||
} else if ((tname === 'div' || tname === 'p') && cls && cls.match(/(?:^| )ace-line\b/)) {
|
||||
// This has undesirable behavior in Chrome but is right in other browsers.
|
||||
// See https://github.com/ether/etherpad-lite/issues/2412 for reasoning
|
||||
if (!abrowser.chrome) oldListTypeOrNull = (_enterList(state, type) || 'none');
|
||||
if (!abrowser.chrome) oldListTypeOrNull = (_enterList(state, undefined) || 'none');
|
||||
} else if ((tname === 'li')) {
|
||||
state.lineAttributes.start = state.start || 0;
|
||||
_recalcAttribString(state);
|
||||
|
@ -565,8 +547,8 @@ function makeContentCollector(collectStyles, abrowser, apool, domInterface, clas
|
|||
if (className2Author && cls) {
|
||||
const classes = cls.match(/\S+/g);
|
||||
if (classes && classes.length > 0) {
|
||||
for (var i = 0; i < classes.length; i++) {
|
||||
var c = classes[i];
|
||||
for (let i = 0; i < classes.length; i++) {
|
||||
const c = classes[i];
|
||||
const a = className2Author(c);
|
||||
if (a) {
|
||||
oldAuthorOrNull = (_enterAuthor(state, a) || 'none');
|
||||
|
@ -578,8 +560,8 @@ function makeContentCollector(collectStyles, abrowser, apool, domInterface, clas
|
|||
}
|
||||
|
||||
const nc = dom.nodeNumChildren(node);
|
||||
for (var i = 0; i < nc; i++) {
|
||||
var c = dom.nodeChild(node, i);
|
||||
for (let i = 0; i < nc; i++) {
|
||||
const c = dom.nodeChild(node, i);
|
||||
cc.collectContent(c, state);
|
||||
}
|
||||
|
||||
|
@ -595,7 +577,7 @@ function makeContentCollector(collectStyles, abrowser, apool, domInterface, clas
|
|||
|
||||
if (isPre) cc.decrementFlag(state, 'preMode');
|
||||
if (state.localAttribs) {
|
||||
for (var i = 0; i < state.localAttribs.length; i++) {
|
||||
for (let i = 0; i < state.localAttribs.length; i++) {
|
||||
cc.decrementAttrib(state, state.localAttribs[i]);
|
||||
}
|
||||
}
|
||||
|
@ -609,7 +591,7 @@ function makeContentCollector(collectStyles, abrowser, apool, domInterface, clas
|
|||
}
|
||||
_reachBlockPoint(node, 1, state);
|
||||
if (isBlock) {
|
||||
if (lines.length() - 1 == startLine) {
|
||||
if (lines.length() - 1 === startLine) {
|
||||
// added additional check to resolve https://github.com/JohnMcLear/ep_copy_paste_images/issues/20
|
||||
// this does mean that images etc can't be pasted on lists but imho that's fine
|
||||
|
||||
|
@ -626,7 +608,7 @@ function makeContentCollector(collectStyles, abrowser, apool, domInterface, clas
|
|||
state.localAttribs = localAttribs;
|
||||
};
|
||||
// can pass a falsy value for end of doc
|
||||
cc.notifyNextNode = function (node) {
|
||||
cc.notifyNextNode = (node) => {
|
||||
// an "empty block" won't end a line; this addresses an issue in IE with
|
||||
// typing into a blank line at the end of the document. typed text
|
||||
// goes into the body, and the empty line div still looks clean.
|
||||
|
@ -637,21 +619,15 @@ function makeContentCollector(collectStyles, abrowser, apool, domInterface, clas
|
|||
}
|
||||
};
|
||||
// each returns [line, char] or [-1,-1]
|
||||
const getSelectionStart = function () {
|
||||
return selStart;
|
||||
};
|
||||
const getSelectionEnd = function () {
|
||||
return selEnd;
|
||||
};
|
||||
const getSelectionStart = () => selStart;
|
||||
const getSelectionEnd = () => selEnd;
|
||||
|
||||
// returns array of strings for lines found, last entry will be "" if
|
||||
// last line is complete (i.e. if a following span should be on a new line).
|
||||
// can be called at any point
|
||||
cc.getLines = function () {
|
||||
return lines.textLines();
|
||||
};
|
||||
cc.getLines = () => lines.textLines();
|
||||
|
||||
cc.finish = function () {
|
||||
cc.finish = () => {
|
||||
lines.flush();
|
||||
const lineAttribs = lines.attribLines();
|
||||
const lineStrings = cc.getLines();
|
||||
|
@ -662,17 +638,17 @@ function makeContentCollector(collectStyles, abrowser, apool, domInterface, clas
|
|||
const ss = getSelectionStart();
|
||||
const se = getSelectionEnd();
|
||||
|
||||
function fixLongLines() {
|
||||
const fixLongLines = () => {
|
||||
// design mode does not deal with with really long lines!
|
||||
const lineLimit = 2000; // chars
|
||||
const buffer = 10; // chars allowed over before wrapping
|
||||
let linesWrapped = 0;
|
||||
let numLinesAfter = 0;
|
||||
for (var i = lineStrings.length - 1; i >= 0; i--) {
|
||||
for (let i = lineStrings.length - 1; i >= 0; i--) {
|
||||
let oldString = lineStrings[i];
|
||||
let oldAttribString = lineAttribs[i];
|
||||
if (oldString.length > lineLimit + buffer) {
|
||||
var newStrings = [];
|
||||
const newStrings = [];
|
||||
const newAttribStrings = [];
|
||||
while (oldString.length > lineLimit) {
|
||||
// var semiloc = oldString.lastIndexOf(';', lineLimit-1);
|
||||
|
@ -688,13 +664,13 @@ function makeContentCollector(collectStyles, abrowser, apool, domInterface, clas
|
|||
newAttribStrings.push(oldAttribString);
|
||||
}
|
||||
|
||||
function fixLineNumber(lineChar) {
|
||||
const fixLineNumber = (lineChar) => {
|
||||
if (lineChar[0] < 0) return;
|
||||
let n = lineChar[0];
|
||||
let c = lineChar[1];
|
||||
if (n > i) {
|
||||
n += (newStrings.length - 1);
|
||||
} else if (n == i) {
|
||||
} else if (n === i) {
|
||||
let a = 0;
|
||||
while (c > newStrings[a].length) {
|
||||
c -= newStrings[a].length;
|
||||
|
@ -704,23 +680,20 @@ function makeContentCollector(collectStyles, abrowser, apool, domInterface, clas
|
|||
}
|
||||
lineChar[0] = n;
|
||||
lineChar[1] = c;
|
||||
}
|
||||
};
|
||||
fixLineNumber(ss);
|
||||
fixLineNumber(se);
|
||||
linesWrapped++;
|
||||
numLinesAfter += newStrings.length;
|
||||
|
||||
newStrings.unshift(i, 1);
|
||||
lineStrings.splice.apply(lineStrings, newStrings);
|
||||
newAttribStrings.unshift(i, 1);
|
||||
lineAttribs.splice.apply(lineAttribs, newAttribStrings);
|
||||
lineStrings.splice(i, 1, ...newStrings);
|
||||
lineAttribs.splice(i, 1, ...newAttribStrings);
|
||||
}
|
||||
}
|
||||
return {
|
||||
linesWrapped,
|
||||
numLinesAfter,
|
||||
};
|
||||
}
|
||||
};
|
||||
const wrapData = fixLongLines();
|
||||
|
||||
return {
|
||||
|
@ -734,7 +707,7 @@ function makeContentCollector(collectStyles, abrowser, apool, domInterface, clas
|
|||
};
|
||||
|
||||
return cc;
|
||||
}
|
||||
};
|
||||
|
||||
exports.sanitizeUnicode = sanitizeUnicode;
|
||||
exports.makeContentCollector = makeContentCollector;
|
||||
|
|
|
@ -26,17 +26,17 @@ const Security = require('./security');
|
|||
const hooks = require('./pluginfw/hooks');
|
||||
const _ = require('./underscore');
|
||||
const lineAttributeMarker = require('./linestylefilter').lineAttributeMarker;
|
||||
const noop = function () {};
|
||||
const noop = () => {};
|
||||
|
||||
|
||||
const domline = {};
|
||||
|
||||
domline.addToLineClass = function (lineClass, cls) {
|
||||
domline.addToLineClass = (lineClass, cls) => {
|
||||
// an "empty span" at any point can be used to add classes to
|
||||
// the line, using line:className. otherwise, we ignore
|
||||
// the span.
|
||||
cls.replace(/\S+/g, (c) => {
|
||||
if (c.indexOf('line:') == 0) {
|
||||
if (c.indexOf('line:') === 0) {
|
||||
// add class to line
|
||||
lineClass = (lineClass ? `${lineClass} ` : '') + c.substring(5);
|
||||
}
|
||||
|
@ -46,7 +46,7 @@ domline.addToLineClass = function (lineClass, cls) {
|
|||
|
||||
// if "document" is falsy we don't create a DOM node, just
|
||||
// an object with innerHTML and className
|
||||
domline.createDomLine = function (nonEmpty, doesWrap, optBrowser, optDocument) {
|
||||
domline.createDomLine = (nonEmpty, doesWrap, optBrowser, optDocument) => {
|
||||
const result = {
|
||||
node: null,
|
||||
appendSpan: noop,
|
||||
|
@ -73,15 +73,12 @@ domline.createDomLine = function (nonEmpty, doesWrap, optBrowser, optDocument) {
|
|||
let postHtml = '';
|
||||
let curHTML = null;
|
||||
|
||||
function processSpaces(s) {
|
||||
return domline.processSpaces(s, doesWrap);
|
||||
}
|
||||
|
||||
const processSpaces = (s) => domline.processSpaces(s, doesWrap);
|
||||
const perTextNodeProcess = (doesWrap ? _.identity : processSpaces);
|
||||
const perHtmlLineProcess = (doesWrap ? processSpaces : _.identity);
|
||||
let lineClass = 'ace-line';
|
||||
|
||||
result.appendSpan = function (txt, cls) {
|
||||
result.appendSpan = (txt, cls) => {
|
||||
let processedMarker = false;
|
||||
// Handle lineAttributeMarker, if present
|
||||
if (cls.indexOf(lineAttributeMarker) >= 0) {
|
||||
|
@ -96,7 +93,6 @@ domline.createDomLine = function (nonEmpty, doesWrap, optBrowser, optDocument) {
|
|||
postHtml += modifier.postHtml;
|
||||
processedMarker |= modifier.processedMarker;
|
||||
});
|
||||
|
||||
if (listType) {
|
||||
listType = listType[1];
|
||||
if (listType) {
|
||||
|
@ -105,12 +101,15 @@ domline.createDomLine = function (nonEmpty, doesWrap, optBrowser, optDocument) {
|
|||
postHtml = `</li></ul>${postHtml}`;
|
||||
} else {
|
||||
if (start) { // is it a start of a list with more than one item in?
|
||||
if (start[1] == 1) { // if its the first one at this level?
|
||||
lineClass = `${lineClass} ` + `list-start-${listType}`; // Add start class to DIV node
|
||||
if (start[1] === 1) { // if its the first one at this level?
|
||||
// Add start class to DIV node
|
||||
lineClass = `${lineClass} ` + `list-start-${listType}`;
|
||||
}
|
||||
preHtml += `<ol start=${start[1]} class="list-${Security.escapeHTMLAttribute(listType)}"><li>`;
|
||||
preHtml +=
|
||||
`<ol start=${start[1]} class="list-${Security.escapeHTMLAttribute(listType)}"><li>`;
|
||||
} else {
|
||||
preHtml += `<ol class="list-${Security.escapeHTMLAttribute(listType)}"><li>`; // Handles pasted contents into existing lists
|
||||
// Handles pasted contents into existing lists
|
||||
preHtml += `<ol class="list-${Security.escapeHTMLAttribute(listType)}"><li>`;
|
||||
}
|
||||
postHtml += '</li></ol>';
|
||||
}
|
||||
|
@ -163,18 +162,20 @@ domline.createDomLine = function (nonEmpty, doesWrap, optBrowser, optDocument) {
|
|||
} else if (txt) {
|
||||
if (href) {
|
||||
const urn_schemes = new RegExp('^(about|geo|mailto|tel):');
|
||||
if (!~href.indexOf('://') && !urn_schemes.test(href)) // if the url doesn't include a protocol prefix, assume http
|
||||
{
|
||||
// if the url doesn't include a protocol prefix, assume http
|
||||
if (!~href.indexOf('://') && !urn_schemes.test(href)) {
|
||||
href = `http://${href}`;
|
||||
}
|
||||
// Using rel="noreferrer" stops leaking the URL/location of the pad when clicking links in the document.
|
||||
// Using rel="noreferrer" stops leaking the URL/location of the pad when
|
||||
// clicking links in the document.
|
||||
// Not all browsers understand this attribute, but it's part of the HTML5 standard.
|
||||
// https://html.spec.whatwg.org/multipage/links.html#link-type-noreferrer
|
||||
// Additionally, we do rel="noopener" to ensure a higher level of referrer security.
|
||||
// https://html.spec.whatwg.org/multipage/links.html#link-type-noopener
|
||||
// https://mathiasbynens.github.io/rel-noopener/
|
||||
// https://github.com/ether/etherpad-lite/pull/3636
|
||||
extraOpenTags = `${extraOpenTags}<a href="${Security.escapeHTMLAttribute(href)}" rel="noreferrer noopener">`;
|
||||
const escapedHref = Security.escapeHTMLAttribute(href);
|
||||
extraOpenTags = `${extraOpenTags}<a href="${escapedHref}" rel="noreferrer noopener">`;
|
||||
extraCloseTags = `</a>${extraCloseTags}`;
|
||||
}
|
||||
if (simpleTags) {
|
||||
|
@ -183,16 +184,22 @@ domline.createDomLine = function (nonEmpty, doesWrap, optBrowser, optDocument) {
|
|||
simpleTags.reverse();
|
||||
extraCloseTags = `</${simpleTags.join('></')}>${extraCloseTags}`;
|
||||
}
|
||||
html.push('<span class="', Security.escapeHTMLAttribute(cls || ''), '">', extraOpenTags, perTextNodeProcess(Security.escapeHTML(txt)), extraCloseTags, '</span>');
|
||||
html.push(
|
||||
'<span class="', Security.escapeHTMLAttribute(cls || ''),
|
||||
'">',
|
||||
extraOpenTags,
|
||||
perTextNodeProcess(Security.escapeHTML(txt)),
|
||||
extraCloseTags,
|
||||
'</span>');
|
||||
}
|
||||
};
|
||||
result.clearSpans = function () {
|
||||
result.clearSpans = () => {
|
||||
html = [];
|
||||
lineClass = 'ace-line';
|
||||
result.lineMarker = 0;
|
||||
};
|
||||
|
||||
function writeHTML() {
|
||||
const writeHTML = () => {
|
||||
let newHTML = perHtmlLineProcess(html.join(''));
|
||||
if (!newHTML) {
|
||||
if ((!document) || (!optBrowser)) {
|
||||
|
@ -209,21 +216,19 @@ domline.createDomLine = function (nonEmpty, doesWrap, optBrowser, optDocument) {
|
|||
curHTML = newHTML;
|
||||
result.node.innerHTML = curHTML;
|
||||
}
|
||||
if (lineClass !== null) result.node.className = lineClass;
|
||||
if (lineClass != null) result.node.className = lineClass;
|
||||
|
||||
hooks.callAll('acePostWriteDomLineHTML', {
|
||||
node: result.node,
|
||||
});
|
||||
}
|
||||
};
|
||||
result.prepareForAdd = writeHTML;
|
||||
result.finishUpdate = writeHTML;
|
||||
result.getInnerHTML = function () {
|
||||
return curHTML || '';
|
||||
};
|
||||
result.getInnerHTML = () => curHTML || '';
|
||||
return result;
|
||||
};
|
||||
|
||||
domline.processSpaces = function (s, doesWrap) {
|
||||
domline.processSpaces = (s, doesWrap) => {
|
||||
if (s.indexOf('<') < 0 && !doesWrap) {
|
||||
// short-cut
|
||||
return s.replace(/ /g, ' ');
|
||||
|
@ -237,31 +242,31 @@ domline.processSpaces = function (s, doesWrap) {
|
|||
let beforeSpace = false;
|
||||
// last space in a run is normal, others are nbsp,
|
||||
// end of line is nbsp
|
||||
for (var i = parts.length - 1; i >= 0; i--) {
|
||||
var p = parts[i];
|
||||
if (p == ' ') {
|
||||
for (let i = parts.length - 1; i >= 0; i--) {
|
||||
const p = parts[i];
|
||||
if (p === ' ') {
|
||||
if (endOfLine || beforeSpace) parts[i] = ' ';
|
||||
endOfLine = false;
|
||||
beforeSpace = true;
|
||||
} else if (p.charAt(0) != '<') {
|
||||
} else if (p.charAt(0) !== '<') {
|
||||
endOfLine = false;
|
||||
beforeSpace = false;
|
||||
}
|
||||
}
|
||||
// beginning of line is nbsp
|
||||
for (var i = 0; i < parts.length; i++) {
|
||||
var p = parts[i];
|
||||
if (p == ' ') {
|
||||
for (let i = 0; i < parts.length; i++) {
|
||||
const p = parts[i];
|
||||
if (p === ' ') {
|
||||
parts[i] = ' ';
|
||||
break;
|
||||
} else if (p.charAt(0) != '<') {
|
||||
} else if (p.charAt(0) !== '<') {
|
||||
break;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
for (var i = 0; i < parts.length; i++) {
|
||||
var p = parts[i];
|
||||
if (p == ' ') {
|
||||
for (let i = 0; i < parts.length; i++) {
|
||||
const p = parts[i];
|
||||
if (p === ' ') {
|
||||
parts[i] = ' ';
|
||||
}
|
||||
}
|
||||
|
|
|
@ -21,16 +21,19 @@ const testImports = {
|
|||
input: '<html><body><li>wtf</ul></body></html>',
|
||||
expectedHTML: '<!DOCTYPE HTML><html><body>wtf<br><br></body></html>',
|
||||
expectedText: 'wtf\n\n',
|
||||
disabled: true,
|
||||
},
|
||||
'nonelistiteminlist #3620': {
|
||||
input: '<html><body><ul>test<li>FOO</li></ul></body></html>',
|
||||
expectedHTML: '<!DOCTYPE HTML><html><body><ul class="bullet">test<li>FOO</ul><br></body></html>',
|
||||
expectedText: '\ttest\n\t* FOO\n\n',
|
||||
disabled: true,
|
||||
},
|
||||
'whitespaceinlist #3620': {
|
||||
input: '<html><body><ul> <li>FOO</li></ul></body></html>',
|
||||
expectedHTML: '<!DOCTYPE HTML><html><body><ul class="bullet"><li>FOO</ul><br></body></html>',
|
||||
expectedText: '\t* FOO\n\n',
|
||||
disabled: true,
|
||||
},
|
||||
'prefixcorrectlinenumber': {
|
||||
input: '<html><body><ol><li>should be 1</li><li>should be 2</li></ol></body></html>',
|
||||
|
|
Loading…
Reference in New Issue