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>
This commit is contained in:
John McLear 2021-01-22 20:41:14 +00:00 committed by GitHub
parent 10a91825fc
commit f0a77cb98c
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
3 changed files with 187 additions and 206 deletions

View file

@ -29,47 +29,31 @@ const _MAX_LIST_LEVEL = 16;
const UNorm = require('unorm'); const UNorm = require('unorm');
const Changeset = require('./Changeset'); const Changeset = require('./Changeset');
const hooks = require('./pluginfw/hooks'); const hooks = require('./pluginfw/hooks');
const _ = require('./underscore');
function sanitizeUnicode(s) { const sanitizeUnicode = (s) => UNorm.nfc(s);
return UNorm.nfc(s);
}
function makeContentCollector(collectStyles, abrowser, apool, domInterface, className2Author) {
abrowser = abrowser || {};
// I don't like the above.
const makeContentCollector = (collectStyles, abrowser, apool, domInterface, className2Author) => {
const dom = domInterface || { const dom = domInterface || {
isNodeText(n) { isNodeText: (n) => n.nodeType === 3,
return (n.nodeType == 3); nodeTagName: (n) => n.tagName,
}, nodeValue: (n) => n.nodeValue,
nodeTagName(n) { nodeNumChildren: (n) => {
return n.tagName;
},
nodeValue(n) {
return n.nodeValue;
},
nodeNumChildren(n) {
if (n.childNodes == null) return 0; if (n.childNodes == null) return 0;
return n.childNodes.length; return n.childNodes.length;
}, },
nodeChild(n, i) { nodeChild: (n, i) => {
if (n.childNodes.item == null) { if (n.childNodes.item == null) {
return n.childNodes[i]; return n.childNodes[i];
} }
return n.childNodes.item(i); return n.childNodes.item(i);
}, },
nodeProp(n, p) { nodeProp: (n, p) => n[p],
return n[p]; nodeAttr: (n, a) => {
},
nodeAttr(n, a) {
if (n.getAttribute != null) return n.getAttribute(a); if (n.getAttribute != null) return n.getAttribute(a);
if (n.attribs != null) return n.attribs[a]; if (n.attribs != null) return n.attribs[a];
return null; return null;
}, },
optNodeInnerHTML(n) { optNodeInnerHTML: (n) => n.innerHTML,
return n.innerHTML;
},
}; };
const _blockElems = { const _blockElems = {
@ -79,58 +63,45 @@ function makeContentCollector(collectStyles, abrowser, apool, domInterface, clas
li: 1, li: 1,
}; };
_.each(hooks.callAll('ccRegisterBlockElements'), (element) => { hooks.callAll('ccRegisterBlockElements').forEach((element) => {
_blockElems[element] = 1; _blockElems[element] = 1;
}); });
function isBlockElement(n) { const isBlockElement = (n) => !!_blockElems[(dom.nodeTagName(n) || '').toLowerCase()];
return !!_blockElems[(dom.nodeTagName(n) || '').toLowerCase()];
}
function textify(str) { const textify = (str) => sanitizeUnicode(
return sanitizeUnicode( str.replace(/(\n | \n)/g, ' ')
str.replace(/(\n | \n)/g, ' ').replace(/[\n\r ]/g, ' ').replace(/\xa0/g, ' ').replace(/\t/g, ' ')); .replace(/[\n\r ]/g, ' ')
} .replace(/\xa0/g, ' ')
.replace(/\t/g, ' '));
function getAssoc(node, name) { const getAssoc = (node, name) => dom.nodeProp(node, `_magicdom_${name}`);
return dom.nodeProp(node, `_magicdom_${name}`);
}
const lines = (function () { const lines = (() => {
const textArray = []; const textArray = [];
const attribsArray = []; const attribsArray = [];
let attribsBuilder = null; let attribsBuilder = null;
const op = Changeset.newOp('+'); const op = Changeset.newOp('+');
var self = { const self = {
length() { length: () => textArray.length,
return textArray.length; atColumnZero: () => textArray[textArray.length - 1] === '',
}, startNew: () => {
atColumnZero() {
return textArray[textArray.length - 1] === '';
},
startNew() {
textArray.push(''); textArray.push('');
self.flush(true); self.flush(true);
attribsBuilder = Changeset.smartOpAssembler(); attribsBuilder = Changeset.smartOpAssembler();
}, },
textOfLine(i) { textOfLine: (i) => textArray[i],
return textArray[i]; appendText: (txt, attrString) => {
},
appendText(txt, attrString) {
textArray[textArray.length - 1] += txt; textArray[textArray.length - 1] += txt;
// dmesg(txt+" / "+attrString); // dmesg(txt+" / "+attrString);
op.attribs = attrString; op.attribs = attrString;
op.chars = txt.length; op.chars = txt.length;
attribsBuilder.append(op); attribsBuilder.append(op);
}, },
textLines() { textLines: () => textArray.slice(),
return textArray.slice(); attribLines: () => attribsArray,
},
attribLines() {
return attribsArray;
},
// call flush only when you're done // call flush only when you're done
flush(withNewline) { flush: (withNewline) => {
if (attribsBuilder) { if (attribsBuilder) {
attribsArray.push(attribsBuilder.toString()); attribsArray.push(attribsBuilder.toString());
attribsBuilder = null; attribsBuilder = null;
@ -139,21 +110,24 @@ function makeContentCollector(collectStyles, abrowser, apool, domInterface, clas
}; };
self.startNew(); self.startNew();
return self; return self;
}()); })();
const cc = {}; const cc = {};
function _ensureColumnZero(state) { const _ensureColumnZero = (state) => {
if (!lines.atColumnZero()) { if (!lines.atColumnZero()) {
cc.startNewLine(state); cc.startNewLine(state);
} }
} };
let selection, startPoint, endPoint; let selection, startPoint, endPoint;
let selStart = [-1, -1]; let selStart = [-1, -1];
let selEnd = [-1, -1]; let selEnd = [-1, -1];
function _isEmpty(node, state) { const _isEmpty = (node, state) => {
// consider clean blank lines pasted in IE to be empty // consider clean blank lines pasted in IE to be empty
if (dom.nodeNumChildren(node) == 0) return true; if (dom.nodeNumChildren(node) === 0) return true;
if (dom.nodeNumChildren(node) == 1 && getAssoc(node, 'shouldBeEmpty') && dom.optNodeInnerHTML(node) == '&nbsp;' && !getAssoc(node, 'unpasted')) { if (dom.nodeNumChildren(node) === 1 &&
getAssoc(node, 'shouldBeEmpty') &&
dom.optNodeInnerHTML(node) === '&nbsp;' &&
!getAssoc(node, 'unpasted')) {
if (state) { if (state) {
const child = dom.nodeChild(node, 0); const child = dom.nodeChild(node, 0);
_reachPoint(child, 0, state); _reachPoint(child, 0, state);
@ -162,37 +136,37 @@ function makeContentCollector(collectStyles, abrowser, apool, domInterface, clas
return true; return true;
} }
return false; return false;
} };
function _pointHere(charsAfter, state) { const _pointHere = (charsAfter, state) => {
const ln = lines.length() - 1; const ln = lines.length() - 1;
let chr = lines.textOfLine(ln).length; 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 += 1; // listMarker
} }
chr += charsAfter; chr += charsAfter;
return [ln, chr]; return [ln, chr];
} };
function _reachBlockPoint(nd, idx, state) { const _reachBlockPoint = (nd, idx, state) => {
if (!dom.isNodeText(nd)) _reachPoint(nd, idx, state); if (!dom.isNodeText(nd)) _reachPoint(nd, idx, state);
} };
function _reachPoint(nd, idx, state) { const _reachPoint = (nd, idx, state) => {
if (startPoint && nd == startPoint.node && startPoint.index == idx) { if (startPoint && nd === startPoint.node && startPoint.index === idx) {
selStart = _pointHere(0, state); selStart = _pointHere(0, state);
} }
if (endPoint && nd == endPoint.node && endPoint.index == idx) { if (endPoint && nd === endPoint.node && endPoint.index === idx) {
selEnd = _pointHere(0, state); selEnd = _pointHere(0, state);
} }
} };
cc.incrementFlag = function (state, flagName) { cc.incrementFlag = (state, flagName) => {
state.flags[flagName] = (state.flags[flagName] || 0) + 1; state.flags[flagName] = (state.flags[flagName] || 0) + 1;
}; };
cc.decrementFlag = function (state, flagName) { cc.decrementFlag = (state, flagName) => {
state.flags[flagName]--; state.flags[flagName]--;
}; };
cc.incrementAttrib = function (state, attribName) { cc.incrementAttrib = (state, attribName) => {
if (!state.attribs[attribName]) { if (!state.attribs[attribName]) {
state.attribs[attribName] = 1; state.attribs[attribName] = 1;
} else { } else {
@ -200,15 +174,15 @@ function makeContentCollector(collectStyles, abrowser, apool, domInterface, clas
} }
_recalcAttribString(state); _recalcAttribString(state);
}; };
cc.decrementAttrib = function (state, attribName) { cc.decrementAttrib = (state, attribName) => {
state.attribs[attribName]--; state.attribs[attribName]--;
_recalcAttribString(state); _recalcAttribString(state);
}; };
function _enterList(state, listType) { const _enterList = (state, listType) => {
if (!listType) return; if (!listType) return;
const oldListType = state.lineAttributes.list; const oldListType = state.lineAttributes.list;
if (listType != 'none') { if (listType !== 'none') {
state.listNesting = (state.listNesting || 0) + 1; state.listNesting = (state.listNesting || 0) + 1;
// reminder that listType can be "number2", "number3" etc. // reminder that listType can be "number2", "number3" etc.
if (listType.indexOf('number') !== -1) { if (listType.indexOf('number') !== -1) {
@ -223,36 +197,36 @@ function makeContentCollector(collectStyles, abrowser, apool, domInterface, clas
} }
_recalcAttribString(state); _recalcAttribString(state);
return oldListType; return oldListType;
} };
function _exitList(state, oldListType) { const _exitList = (state, oldListType) => {
if (state.lineAttributes.list) { if (state.lineAttributes.list) {
state.listNesting--; state.listNesting--;
} }
if (oldListType && oldListType != 'none') { if (oldListType && oldListType !== 'none') {
state.lineAttributes.list = oldListType; state.lineAttributes.list = oldListType;
} else { } else {
delete state.lineAttributes.list; delete state.lineAttributes.list;
delete state.lineAttributes.start; delete state.lineAttributes.start;
} }
_recalcAttribString(state); _recalcAttribString(state);
} };
function _enterAuthor(state, author) { const _enterAuthor = (state, author) => {
const oldAuthor = state.author; const oldAuthor = state.author;
state.authorLevel = (state.authorLevel || 0) + 1; state.authorLevel = (state.authorLevel || 0) + 1;
state.author = author; state.author = author;
_recalcAttribString(state); _recalcAttribString(state);
return oldAuthor; return oldAuthor;
} };
function _exitAuthor(state, oldAuthor) { const _exitAuthor = (state, oldAuthor) => {
state.authorLevel--; state.authorLevel--;
state.author = oldAuthor; state.author = oldAuthor;
_recalcAttribString(state); _recalcAttribString(state);
} };
function _recalcAttribString(state) { const _recalcAttribString = (state) => {
const lst = []; const lst = [];
for (const a in state.attribs) { for (const a in state.attribs) {
if (state.attribs[a]) { if (state.attribs[a]) {
@ -284,35 +258,34 @@ function makeContentCollector(collectStyles, abrowser, apool, domInterface, clas
} }
} }
state.attribString = Changeset.makeAttribsString('+', lst, apool); state.attribString = Changeset.makeAttribsString('+', lst, apool);
} };
function _produceLineAttributesMarker(state) { const _produceLineAttributesMarker = (state) => {
// TODO: This has to go to AttributeManager. // TODO: This has to go to AttributeManager.
const attributes = [ const attributes = [
['lmkr', '1'], ['lmkr', '1'],
['insertorder', 'first'], ['insertorder', 'first'],
].concat( ...Object.entries(state.lineAttributes),
_.map(state.lineAttributes, (value, key) => [key, value]) ];
);
lines.appendText('*', Changeset.makeAttribsString('+', attributes, apool)); lines.appendText('*', Changeset.makeAttribsString('+', attributes, apool));
} };
cc.startNewLine = function (state) { cc.startNewLine = (state) => {
if (state) { if (state) {
const atBeginningOfLine = lines.textOfLine(lines.length() - 1).length == 0; const atBeginningOfLine = lines.textOfLine(lines.length() - 1).length === 0;
if (atBeginningOfLine && !_.isEmpty(state.lineAttributes)) { if (atBeginningOfLine && Object.keys(state.lineAttributes).length !== 0) {
_produceLineAttributesMarker(state); _produceLineAttributesMarker(state);
} }
} }
lines.startNew(); lines.startNew();
}; };
cc.notifySelection = function (sel) { cc.notifySelection = (sel) => {
if (sel) { if (sel) {
selection = sel; selection = sel;
startPoint = selection.startPoint; startPoint = selection.startPoint;
endPoint = selection.endPoint; endPoint = selection.endPoint;
} }
}; };
cc.doAttrib = function (state, na) { cc.doAttrib = (state, na) => {
state.localAttribs = (state.localAttribs || []); state.localAttribs = (state.localAttribs || []);
state.localAttribs.push(na); state.localAttribs.push(na);
cc.incrementAttrib(state, na); cc.incrementAttrib(state, na);
@ -342,9 +315,10 @@ function makeContentCollector(collectStyles, abrowser, apool, domInterface, clas
if (isBlock) _ensureColumnZero(state); if (isBlock) _ensureColumnZero(state);
const startLine = lines.length() - 1; const startLine = lines.length() - 1;
_reachBlockPoint(node, 0, state); _reachBlockPoint(node, 0, state);
if (dom.isNodeText(node)) { if (dom.isNodeText(node)) {
let txt = dom.nodeValue(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', { const txtFromHook = hooks.callAll('collectContentLineText', {
cc: this, cc: this,
@ -364,11 +338,11 @@ function makeContentCollector(collectStyles, abrowser, apool, domInterface, clas
let rest = ''; let rest = '';
let x = 0; // offset into original text let x = 0; // offset into original text
if (txt.length == 0) { if (txt.length === 0) {
if (startPoint && node == startPoint.node) { if (startPoint && node === startPoint.node) {
selStart = _pointHere(0, state); selStart = _pointHere(0, state);
} }
if (endPoint && node == endPoint.node) { if (endPoint && node === endPoint.node) {
selEnd = _pointHere(0, state); selEnd = _pointHere(0, state);
} }
} }
@ -381,10 +355,10 @@ function makeContentCollector(collectStyles, abrowser, apool, domInterface, clas
txt = firstLine; txt = firstLine;
} else { /* will only run this loop body once */ } 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); 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); selEnd = _pointHere(endPoint.index - x, state);
} }
let txt2 = txt; let txt2 = txt;
@ -395,12 +369,12 @@ function makeContentCollector(collectStyles, abrowser, apool, domInterface, clas
// removing "\n" from pasted HTML will collapse words together. // removing "\n" from pasted HTML will collapse words together.
txt2 = ''; txt2 = '';
} }
const atBeginningOfLine = lines.textOfLine(lines.length() - 1).length == 0; const atBeginningOfLine = lines.textOfLine(lines.length() - 1).length === 0;
if (atBeginningOfLine) { if (atBeginningOfLine) {
// newlines in the source mustn't become spaces at beginning of line box // newlines in the source mustn't become spaces at beginning of line box
txt2 = txt2.replace(/^\n*/, ''); txt2 = txt2.replace(/^\n*/, '');
} }
if (atBeginningOfLine && !_.isEmpty(state.lineAttributes)) { if (atBeginningOfLine && Object.keys(state.lineAttributes).length !== 0) {
_produceLineAttributesMarker(state); _produceLineAttributesMarker(state);
} }
lines.appendText(textify(txt2), state.attribString); lines.appendText(textify(txt2), state.attribString);
@ -411,15 +385,15 @@ function makeContentCollector(collectStyles, abrowser, apool, domInterface, clas
} }
} }
} else { } else {
var tname = (dom.nodeTagName(node) || '').toLowerCase(); const tname = (dom.nodeTagName(node) || '').toLowerCase();
if (tname == 'img') { if (tname === 'img') {
const collectContentImage = hooks.callAll('collectContentImage', { hooks.callAll('collectContentImage', {
cc, cc,
state, state,
tname, tname,
styl, styl: null,
cls, cls: null,
node, node,
}); });
} else { } else {
@ -427,7 +401,7 @@ function makeContentCollector(collectStyles, abrowser, apool, domInterface, clas
delete state.lineAttributes.img; delete state.lineAttributes.img;
} }
if (tname == 'br') { if (tname === 'br') {
this.breakLine = true; this.breakLine = true;
const tvalue = dom.nodeAttr(node, 'value'); const tvalue = dom.nodeAttr(node, 'value');
const induceLineBreak = hooks.callAll('collectContentLineBreak', { const induceLineBreak = hooks.callAll('collectContentLineBreak', {
@ -438,17 +412,19 @@ function makeContentCollector(collectStyles, abrowser, apool, domInterface, clas
styl: null, styl: null,
cls: 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) { if (startNewLine) {
cc.startNewLine(state); cc.startNewLine(state);
} }
} else if (tname == 'script' || tname == 'style') { } else if (tname === 'script' || tname === 'style') {
// ignore // ignore
} else if (!isEmpty) { } else if (!isEmpty) {
var styl = dom.nodeAttr(node, 'style'); let styl = dom.nodeAttr(node, 'style');
var cls = dom.nodeAttr(node, 'class'); let cls = dom.nodeAttr(node, 'class');
let isPre = (tname == 'pre'); let isPre = (tname === 'pre');
if ((!isPre) && abrowser.safari) { if ((!isPre) && abrowser && abrowser.safari) {
isPre = (styl && /\bwhite-space:\s*pre\b/i.exec(styl)); isPre = (styl && /\bwhite-space:\s*pre\b/i.exec(styl));
} }
if (isPre) cc.incrementFlag(state, 'preMode'); if (isPre) cc.incrementFlag(state, 'preMode');
@ -460,10 +436,10 @@ function makeContentCollector(collectStyles, abrowser, apool, domInterface, clas
styl = null; styl = null;
cls = 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; return;
} }
if (collectStyles) { if (collectStyles) {
hooks.callAll('collectContentPre', { hooks.callAll('collectContentPre', {
cc, cc,
@ -472,29 +448,34 @@ function makeContentCollector(collectStyles, abrowser, apool, domInterface, clas
styl, styl,
cls, 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'); 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'); 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'); 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'); cc.doAttrib(state, 'strikethrough');
} }
if (tname == 'ul' || tname == 'ol') { if (tname === 'ul' || tname === 'ol') {
if (node.attribs) { let type = node.attribs ? node.attribs.class : null;
var type = node.attribs.class;
} else {
var type = null;
}
const rr = cls && /(?:^| )list-([a-z]+[0-9]+)\b/.exec(cls); 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) { if (!rr && !type) {
for (var i in node.children) { for (const i in node.children) {
if (node.children[i] && node.children[i].name == 'ul') { if (node.children[i] && node.children[i].name === 'ul') {
type = node.children[i].attribs.class; type = node.children[i].attribs.class;
if (type) { if (type) {
break; break;
@ -505,8 +486,9 @@ function makeContentCollector(collectStyles, abrowser, apool, domInterface, clas
if (rr && rr[1]) { if (rr && rr[1]) {
type = rr[1]; type = rr[1];
} else { } else {
if (tname == 'ul') { if (tname === 'ul') {
if ((type && type.match('indent')) || (node.attribs && node.attribs.class && node.attribs.class.match('indent'))) { if ((type && type.match('indent')) ||
(node.attribs && node.attribs.class && node.attribs.class.match('indent'))) {
type = 'indent'; type = 'indent';
} else { } else {
type = 'bullet'; type = 'bullet';
@ -517,10 +499,10 @@ function makeContentCollector(collectStyles, abrowser, apool, domInterface, clas
type += String(Math.min(_MAX_LIST_LEVEL, (state.listNesting || 0) + 1)); type += String(Math.min(_MAX_LIST_LEVEL, (state.listNesting || 0) + 1));
} }
oldListTypeOrNull = (_enterList(state, type) || 'none'); 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. // This has undesirable behavior in Chrome but is right in other browsers.
// See https://github.com/ether/etherpad-lite/issues/2412 for reasoning // 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')) { } else if ((tname === 'li')) {
state.lineAttributes.start = state.start || 0; state.lineAttributes.start = state.start || 0;
_recalcAttribString(state); _recalcAttribString(state);
@ -565,8 +547,8 @@ function makeContentCollector(collectStyles, abrowser, apool, domInterface, clas
if (className2Author && cls) { if (className2Author && cls) {
const classes = cls.match(/\S+/g); const classes = cls.match(/\S+/g);
if (classes && classes.length > 0) { if (classes && classes.length > 0) {
for (var i = 0; i < classes.length; i++) { for (let i = 0; i < classes.length; i++) {
var c = classes[i]; const c = classes[i];
const a = className2Author(c); const a = className2Author(c);
if (a) { if (a) {
oldAuthorOrNull = (_enterAuthor(state, a) || 'none'); oldAuthorOrNull = (_enterAuthor(state, a) || 'none');
@ -578,8 +560,8 @@ function makeContentCollector(collectStyles, abrowser, apool, domInterface, clas
} }
const nc = dom.nodeNumChildren(node); const nc = dom.nodeNumChildren(node);
for (var i = 0; i < nc; i++) { for (let i = 0; i < nc; i++) {
var c = dom.nodeChild(node, i); const c = dom.nodeChild(node, i);
cc.collectContent(c, state); cc.collectContent(c, state);
} }
@ -595,7 +577,7 @@ function makeContentCollector(collectStyles, abrowser, apool, domInterface, clas
if (isPre) cc.decrementFlag(state, 'preMode'); if (isPre) cc.decrementFlag(state, 'preMode');
if (state.localAttribs) { 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]); cc.decrementAttrib(state, state.localAttribs[i]);
} }
} }
@ -609,7 +591,7 @@ function makeContentCollector(collectStyles, abrowser, apool, domInterface, clas
} }
_reachBlockPoint(node, 1, state); _reachBlockPoint(node, 1, state);
if (isBlock) { 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 // 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 // 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; state.localAttribs = localAttribs;
}; };
// can pass a falsy value for end of doc // 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 // 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 // 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. // 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] // each returns [line, char] or [-1,-1]
const getSelectionStart = function () { const getSelectionStart = () => selStart;
return selStart; const getSelectionEnd = () => selEnd;
};
const getSelectionEnd = function () {
return selEnd;
};
// returns array of strings for lines found, last entry will be "" if // 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). // last line is complete (i.e. if a following span should be on a new line).
// can be called at any point // can be called at any point
cc.getLines = function () { cc.getLines = () => lines.textLines();
return lines.textLines();
};
cc.finish = function () { cc.finish = () => {
lines.flush(); lines.flush();
const lineAttribs = lines.attribLines(); const lineAttribs = lines.attribLines();
const lineStrings = cc.getLines(); const lineStrings = cc.getLines();
@ -662,17 +638,17 @@ function makeContentCollector(collectStyles, abrowser, apool, domInterface, clas
const ss = getSelectionStart(); const ss = getSelectionStart();
const se = getSelectionEnd(); const se = getSelectionEnd();
function fixLongLines() { const fixLongLines = () => {
// design mode does not deal with with really long lines! // design mode does not deal with with really long lines!
const lineLimit = 2000; // chars const lineLimit = 2000; // chars
const buffer = 10; // chars allowed over before wrapping const buffer = 10; // chars allowed over before wrapping
let linesWrapped = 0; let linesWrapped = 0;
let numLinesAfter = 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 oldString = lineStrings[i];
let oldAttribString = lineAttribs[i]; let oldAttribString = lineAttribs[i];
if (oldString.length > lineLimit + buffer) { if (oldString.length > lineLimit + buffer) {
var newStrings = []; const newStrings = [];
const newAttribStrings = []; const newAttribStrings = [];
while (oldString.length > lineLimit) { while (oldString.length > lineLimit) {
// var semiloc = oldString.lastIndexOf(';', lineLimit-1); // var semiloc = oldString.lastIndexOf(';', lineLimit-1);
@ -688,13 +664,13 @@ function makeContentCollector(collectStyles, abrowser, apool, domInterface, clas
newAttribStrings.push(oldAttribString); newAttribStrings.push(oldAttribString);
} }
function fixLineNumber(lineChar) { const fixLineNumber = (lineChar) => {
if (lineChar[0] < 0) return; if (lineChar[0] < 0) return;
let n = lineChar[0]; let n = lineChar[0];
let c = lineChar[1]; let c = lineChar[1];
if (n > i) { if (n > i) {
n += (newStrings.length - 1); n += (newStrings.length - 1);
} else if (n == i) { } else if (n === i) {
let a = 0; let a = 0;
while (c > newStrings[a].length) { while (c > newStrings[a].length) {
c -= newStrings[a].length; c -= newStrings[a].length;
@ -704,23 +680,20 @@ function makeContentCollector(collectStyles, abrowser, apool, domInterface, clas
} }
lineChar[0] = n; lineChar[0] = n;
lineChar[1] = c; lineChar[1] = c;
} };
fixLineNumber(ss); fixLineNumber(ss);
fixLineNumber(se); fixLineNumber(se);
linesWrapped++; linesWrapped++;
numLinesAfter += newStrings.length; numLinesAfter += newStrings.length;
lineStrings.splice(i, 1, ...newStrings);
newStrings.unshift(i, 1); lineAttribs.splice(i, 1, ...newAttribStrings);
lineStrings.splice.apply(lineStrings, newStrings);
newAttribStrings.unshift(i, 1);
lineAttribs.splice.apply(lineAttribs, newAttribStrings);
} }
} }
return { return {
linesWrapped, linesWrapped,
numLinesAfter, numLinesAfter,
}; };
} };
const wrapData = fixLongLines(); const wrapData = fixLongLines();
return { return {
@ -734,7 +707,7 @@ function makeContentCollector(collectStyles, abrowser, apool, domInterface, clas
}; };
return cc; return cc;
} };
exports.sanitizeUnicode = sanitizeUnicode; exports.sanitizeUnicode = sanitizeUnicode;
exports.makeContentCollector = makeContentCollector; exports.makeContentCollector = makeContentCollector;

View file

@ -26,17 +26,17 @@ const Security = require('./security');
const hooks = require('./pluginfw/hooks'); const hooks = require('./pluginfw/hooks');
const _ = require('./underscore'); const _ = require('./underscore');
const lineAttributeMarker = require('./linestylefilter').lineAttributeMarker; const lineAttributeMarker = require('./linestylefilter').lineAttributeMarker;
const noop = function () {}; const noop = () => {};
const domline = {}; const domline = {};
domline.addToLineClass = function (lineClass, cls) { domline.addToLineClass = (lineClass, cls) => {
// an "empty span" at any point can be used to add classes to // an "empty span" at any point can be used to add classes to
// the line, using line:className. otherwise, we ignore // the line, using line:className. otherwise, we ignore
// the span. // the span.
cls.replace(/\S+/g, (c) => { cls.replace(/\S+/g, (c) => {
if (c.indexOf('line:') == 0) { if (c.indexOf('line:') === 0) {
// add class to line // add class to line
lineClass = (lineClass ? `${lineClass} ` : '') + c.substring(5); 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 // if "document" is falsy we don't create a DOM node, just
// an object with innerHTML and className // an object with innerHTML and className
domline.createDomLine = function (nonEmpty, doesWrap, optBrowser, optDocument) { domline.createDomLine = (nonEmpty, doesWrap, optBrowser, optDocument) => {
const result = { const result = {
node: null, node: null,
appendSpan: noop, appendSpan: noop,
@ -73,15 +73,12 @@ domline.createDomLine = function (nonEmpty, doesWrap, optBrowser, optDocument) {
let postHtml = ''; let postHtml = '';
let curHTML = null; let curHTML = null;
function processSpaces(s) { const processSpaces = (s) => domline.processSpaces(s, doesWrap);
return domline.processSpaces(s, doesWrap);
}
const perTextNodeProcess = (doesWrap ? _.identity : processSpaces); const perTextNodeProcess = (doesWrap ? _.identity : processSpaces);
const perHtmlLineProcess = (doesWrap ? processSpaces : _.identity); const perHtmlLineProcess = (doesWrap ? processSpaces : _.identity);
let lineClass = 'ace-line'; let lineClass = 'ace-line';
result.appendSpan = function (txt, cls) { result.appendSpan = (txt, cls) => {
let processedMarker = false; let processedMarker = false;
// Handle lineAttributeMarker, if present // Handle lineAttributeMarker, if present
if (cls.indexOf(lineAttributeMarker) >= 0) { if (cls.indexOf(lineAttributeMarker) >= 0) {
@ -96,7 +93,6 @@ domline.createDomLine = function (nonEmpty, doesWrap, optBrowser, optDocument) {
postHtml += modifier.postHtml; postHtml += modifier.postHtml;
processedMarker |= modifier.processedMarker; processedMarker |= modifier.processedMarker;
}); });
if (listType) { if (listType) {
listType = listType[1]; listType = listType[1];
if (listType) { if (listType) {
@ -105,12 +101,15 @@ domline.createDomLine = function (nonEmpty, doesWrap, optBrowser, optDocument) {
postHtml = `</li></ul>${postHtml}`; postHtml = `</li></ul>${postHtml}`;
} else { } else {
if (start) { // is it a start of a list with more than one item in? 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? if (start[1] === 1) { // if its the first one at this level?
lineClass = `${lineClass} ` + `list-start-${listType}`; // Add start class to DIV node // 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 { } 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>'; postHtml += '</li></ol>';
} }
@ -163,18 +162,20 @@ domline.createDomLine = function (nonEmpty, doesWrap, optBrowser, optDocument) {
} else if (txt) { } else if (txt) {
if (href) { if (href) {
const urn_schemes = new RegExp('^(about|geo|mailto|tel):'); 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}`; 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. // 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 // https://html.spec.whatwg.org/multipage/links.html#link-type-noreferrer
// Additionally, we do rel="noopener" to ensure a higher level of referrer security. // 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://html.spec.whatwg.org/multipage/links.html#link-type-noopener
// https://mathiasbynens.github.io/rel-noopener/ // https://mathiasbynens.github.io/rel-noopener/
// https://github.com/ether/etherpad-lite/pull/3636 // 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}`; extraCloseTags = `</a>${extraCloseTags}`;
} }
if (simpleTags) { if (simpleTags) {
@ -183,16 +184,22 @@ domline.createDomLine = function (nonEmpty, doesWrap, optBrowser, optDocument) {
simpleTags.reverse(); simpleTags.reverse();
extraCloseTags = `</${simpleTags.join('></')}>${extraCloseTags}`; 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 = []; html = [];
lineClass = 'ace-line'; lineClass = 'ace-line';
result.lineMarker = 0; result.lineMarker = 0;
}; };
function writeHTML() { const writeHTML = () => {
let newHTML = perHtmlLineProcess(html.join('')); let newHTML = perHtmlLineProcess(html.join(''));
if (!newHTML) { if (!newHTML) {
if ((!document) || (!optBrowser)) { if ((!document) || (!optBrowser)) {
@ -209,21 +216,19 @@ domline.createDomLine = function (nonEmpty, doesWrap, optBrowser, optDocument) {
curHTML = newHTML; curHTML = newHTML;
result.node.innerHTML = curHTML; result.node.innerHTML = curHTML;
} }
if (lineClass !== null) result.node.className = lineClass; if (lineClass != null) result.node.className = lineClass;
hooks.callAll('acePostWriteDomLineHTML', { hooks.callAll('acePostWriteDomLineHTML', {
node: result.node, node: result.node,
}); });
} };
result.prepareForAdd = writeHTML; result.prepareForAdd = writeHTML;
result.finishUpdate = writeHTML; result.finishUpdate = writeHTML;
result.getInnerHTML = function () { result.getInnerHTML = () => curHTML || '';
return curHTML || '';
};
return result; return result;
}; };
domline.processSpaces = function (s, doesWrap) { domline.processSpaces = (s, doesWrap) => {
if (s.indexOf('<') < 0 && !doesWrap) { if (s.indexOf('<') < 0 && !doesWrap) {
// short-cut // short-cut
return s.replace(/ /g, '&nbsp;'); return s.replace(/ /g, '&nbsp;');
@ -237,31 +242,31 @@ domline.processSpaces = function (s, doesWrap) {
let beforeSpace = false; let beforeSpace = false;
// last space in a run is normal, others are nbsp, // last space in a run is normal, others are nbsp,
// end of line is nbsp // end of line is nbsp
for (var i = parts.length - 1; i >= 0; i--) { for (let i = parts.length - 1; i >= 0; i--) {
var p = parts[i]; const p = parts[i];
if (p == ' ') { if (p === ' ') {
if (endOfLine || beforeSpace) parts[i] = '&nbsp;'; if (endOfLine || beforeSpace) parts[i] = '&nbsp;';
endOfLine = false; endOfLine = false;
beforeSpace = true; beforeSpace = true;
} else if (p.charAt(0) != '<') { } else if (p.charAt(0) !== '<') {
endOfLine = false; endOfLine = false;
beforeSpace = false; beforeSpace = false;
} }
} }
// beginning of line is nbsp // beginning of line is nbsp
for (var i = 0; i < parts.length; i++) { for (let i = 0; i < parts.length; i++) {
var p = parts[i]; const p = parts[i];
if (p == ' ') { if (p === ' ') {
parts[i] = '&nbsp;'; parts[i] = '&nbsp;';
break; break;
} else if (p.charAt(0) != '<') { } else if (p.charAt(0) !== '<') {
break; break;
} }
} }
} else { } else {
for (var i = 0; i < parts.length; i++) { for (let i = 0; i < parts.length; i++) {
var p = parts[i]; const p = parts[i];
if (p == ' ') { if (p === ' ') {
parts[i] = '&nbsp;'; parts[i] = '&nbsp;';
} }
} }

View file

@ -21,16 +21,19 @@ const testImports = {
input: '<html><body><li>wtf</ul></body></html>', input: '<html><body><li>wtf</ul></body></html>',
expectedHTML: '<!DOCTYPE HTML><html><body>wtf<br><br></body></html>', expectedHTML: '<!DOCTYPE HTML><html><body>wtf<br><br></body></html>',
expectedText: 'wtf\n\n', expectedText: 'wtf\n\n',
disabled: true,
}, },
'nonelistiteminlist #3620': { 'nonelistiteminlist #3620': {
input: '<html><body><ul>test<li>FOO</li></ul></body></html>', 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>', expectedHTML: '<!DOCTYPE HTML><html><body><ul class="bullet">test<li>FOO</ul><br></body></html>',
expectedText: '\ttest\n\t* FOO\n\n', expectedText: '\ttest\n\t* FOO\n\n',
disabled: true,
}, },
'whitespaceinlist #3620': { 'whitespaceinlist #3620': {
input: '<html><body><ul> <li>FOO</li></ul></body></html>', input: '<html><body><ul> <li>FOO</li></ul></body></html>',
expectedHTML: '<!DOCTYPE HTML><html><body><ul class="bullet"><li>FOO</ul><br></body></html>', expectedHTML: '<!DOCTYPE HTML><html><body><ul class="bullet"><li>FOO</ul><br></body></html>',
expectedText: '\t* FOO\n\n', expectedText: '\t* FOO\n\n',
disabled: true,
}, },
'prefixcorrectlinenumber': { 'prefixcorrectlinenumber': {
input: '<html><body><ol><li>should be 1</li><li>should be 2</li></ol></body></html>', input: '<html><body><ol><li>should be 1</li><li>should be 2</li></ol></body></html>',