lint: contentcollector

This commit is contained in:
John McLear 2021-01-14 11:24:59 +00:00
parent 183b765df0
commit 638bd9942e

View file

@ -31,45 +31,31 @@ 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 = function (
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 = {
@ -84,54 +70,41 @@ function makeContentCollector(collectStyles, abrowser, apool, domInterface, clas
_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;
@ -140,21 +113,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);
@ -163,37 +139,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 && !_.isEmpty(state.lineAttributes)) {
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 {
@ -201,15 +177,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) {
@ -224,36 +200,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]) {
@ -285,9 +261,9 @@ 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'],
@ -296,24 +272,24 @@ function makeContentCollector(collectStyles, abrowser, apool, domInterface, clas
_.map(state.lineAttributes, (value, key) => [key, value])
);
lines.appendText('*', Changeset.makeAttribsString('+', attributes, apool));
}
cc.startNewLine = function (state) {
};
cc.startNewLine = (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)) {
_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);
@ -343,9 +319,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,
@ -365,11 +342,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);
}
}
@ -382,10 +359,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;
@ -396,7 +373,7 @@ 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*/, '');
@ -412,10 +389,13 @@ function makeContentCollector(collectStyles, abrowser, apool, domInterface, clas
}
}
} else {
var tname = (dom.nodeTagName(node) || '').toLowerCase();
// Not a text node..
const tname = (dom.nodeTagName(node) || '').toLowerCase();
const styl = dom.nodeAttr(node, 'style');
const cls = dom.nodeAttr(node, 'class');
if (tname == 'img') {
const collectContentImage = hooks.callAll('collectContentImage', {
if (tname === 'img') {
hooks.callAll('collectContentImage', {
cc,
state,
tname,
@ -425,8 +405,7 @@ function makeContentCollector(collectStyles, abrowser, apool, domInterface, clas
});
// cc.doAttrib(state, 'image:foo.png');
state.lineAttributes.img = 'foo.png';
} else if (tname == 'br') {
} else if (tname === 'br') {
this.breakLine = true;
const tvalue = dom.nodeAttr(node, 'value');
const induceLineBreak = hooks.callAll('collectContentLineBreak', {
@ -437,16 +416,18 @@ 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');
let styl = dom.nodeAttr(node, 'style');
let cls = dom.nodeAttr(node, 'class');
let isPre = (tname === 'pre');
if ((!isPre) && abrowser.safari) {
isPre = (styl && /\bwhite-space:\s*pre\b/i.exec(styl));
}
@ -459,7 +440,8 @@ 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;
}
@ -471,29 +453,39 @@ 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') {
let type;
if (tname === 'ul' || tname === 'ol') {
if (node.attribs) {
var type = node.attribs.class;
type = node.attribs.class;
} else {
var type = null;
type = 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;
@ -504,8 +496,14 @@ 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';
@ -516,7 +514,7 @@ 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');
@ -564,8 +562,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');
@ -577,8 +575,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);
}
@ -594,7 +592,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]);
}
}
@ -608,7 +606,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
@ -625,7 +623,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.
@ -636,21 +634,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();
@ -661,17 +653,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);
@ -687,13 +679,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;
@ -703,13 +695,14 @@ 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);
// Still to fix linting issue below.
lineStrings.splice.apply(lineStrings, newStrings);
newAttribStrings.unshift(i, 1);
lineAttribs.splice.apply(lineAttribs, newAttribStrings);
@ -719,7 +712,7 @@ function makeContentCollector(collectStyles, abrowser, apool, domInterface, clas
linesWrapped,
numLinesAfter,
};
}
};
const wrapData = fixLongLines();
return {
@ -733,7 +726,7 @@ function makeContentCollector(collectStyles, abrowser, apool, domInterface, clas
};
return cc;
}
};
exports.sanitizeUnicode = sanitizeUnicode;
exports.makeContentCollector = makeContentCollector;