2011-12-04 16:33:56 +01:00
|
|
|
/**
|
2019-04-16 00:34:29 +02:00
|
|
|
* This code is mostly from the old Etherpad. Please help us to comment this code.
|
2011-12-04 16:33:56 +01:00
|
|
|
* This helps other people to understand this code better and helps them to improve it.
|
|
|
|
* TL;DR COMMENTS ON THIS FILE ARE HIGHLY APPRECIATED
|
|
|
|
*/
|
|
|
|
|
2011-03-26 14:10:41 +01:00
|
|
|
// THIS FILE IS ALSO AN APPJET MODULE: etherpad.collab.ace.contentcollector
|
|
|
|
// %APPJET%: import("etherpad.collab.ace.easysync2.Changeset");
|
|
|
|
// %APPJET%: import("etherpad.admin.plugins");
|
|
|
|
/**
|
|
|
|
* Copyright 2009 Google Inc.
|
|
|
|
*
|
|
|
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
|
|
* you may not use this file except in compliance with the License.
|
|
|
|
* You may obtain a copy of the License at
|
|
|
|
*
|
|
|
|
* http://www.apache.org/licenses/LICENSE-2.0
|
|
|
|
*
|
|
|
|
* Unless required by applicable law or agreed to in writing, software
|
|
|
|
* distributed under the License is distributed on an "AS-IS" BASIS,
|
|
|
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
|
|
* See the License for the specific language governing permissions and
|
|
|
|
* limitations under the License.
|
|
|
|
*/
|
|
|
|
|
2020-11-23 19:24:19 +01:00
|
|
|
const _MAX_LIST_LEVEL = 16;
|
2011-03-26 14:10:41 +01:00
|
|
|
|
2020-11-23 19:24:19 +01:00
|
|
|
const UNorm = require('unorm');
|
|
|
|
const Changeset = require('./Changeset');
|
|
|
|
const hooks = require('./pluginfw/hooks');
|
|
|
|
const _ = require('./underscore');
|
2012-01-16 05:16:11 +01:00
|
|
|
|
2020-11-21 19:37:57 +01:00
|
|
|
function sanitizeUnicode(s) {
|
2014-11-27 01:45:22 +01:00
|
|
|
return UNorm.nfc(s);
|
2011-03-26 14:10:41 +01:00
|
|
|
}
|
|
|
|
|
2020-11-21 19:37:57 +01:00
|
|
|
function makeContentCollector(collectStyles, abrowser, apool, domInterface, className2Author) {
|
2015-01-21 15:25:24 +01:00
|
|
|
abrowser = abrowser || {};
|
2015-01-21 03:55:03 +01:00
|
|
|
// I don't like the above.
|
2011-03-26 14:10:41 +01:00
|
|
|
|
2020-11-23 19:24:19 +01:00
|
|
|
const dom = domInterface || {
|
|
|
|
isNodeText(n) {
|
2011-03-26 14:10:41 +01:00
|
|
|
return (n.nodeType == 3);
|
|
|
|
},
|
2020-11-23 19:24:19 +01:00
|
|
|
nodeTagName(n) {
|
2011-03-26 14:10:41 +01:00
|
|
|
return n.tagName;
|
|
|
|
},
|
2020-11-23 19:24:19 +01:00
|
|
|
nodeValue(n) {
|
2011-03-26 14:10:41 +01:00
|
|
|
return n.nodeValue;
|
|
|
|
},
|
2020-11-23 19:24:19 +01:00
|
|
|
nodeNumChildren(n) {
|
|
|
|
if (n.childNodes == null) return 0;
|
2011-03-26 14:10:41 +01:00
|
|
|
return n.childNodes.length;
|
|
|
|
},
|
2020-11-23 19:24:19 +01:00
|
|
|
nodeChild(n, i) {
|
|
|
|
if (n.childNodes.item == null) {
|
2014-11-25 18:26:09 +01:00
|
|
|
return n.childNodes[i];
|
|
|
|
}
|
2011-03-26 14:10:41 +01:00
|
|
|
return n.childNodes.item(i);
|
|
|
|
},
|
2020-11-23 19:24:19 +01:00
|
|
|
nodeProp(n, p) {
|
2011-03-26 14:10:41 +01:00
|
|
|
return n[p];
|
|
|
|
},
|
2020-11-23 19:24:19 +01:00
|
|
|
nodeAttr(n, a) {
|
|
|
|
if (n.getAttribute != null) return n.getAttribute(a);
|
|
|
|
if (n.attribs != null) return n.attribs[a];
|
2015-04-28 14:31:07 +02:00
|
|
|
return null;
|
2011-03-26 14:10:41 +01:00
|
|
|
},
|
2020-11-23 19:24:19 +01:00
|
|
|
optNodeInnerHTML(n) {
|
2011-03-26 14:10:41 +01:00
|
|
|
return n.innerHTML;
|
2020-11-23 19:24:19 +01:00
|
|
|
},
|
2011-03-26 14:10:41 +01:00
|
|
|
};
|
|
|
|
|
2020-11-23 19:24:19 +01:00
|
|
|
const _blockElems = {
|
|
|
|
div: 1,
|
|
|
|
p: 1,
|
|
|
|
pre: 1,
|
|
|
|
li: 1,
|
2011-07-07 19:59:34 +02:00
|
|
|
};
|
|
|
|
|
2020-11-23 19:24:19 +01:00
|
|
|
_.each(hooks.callAll('ccRegisterBlockElements'), (element) => {
|
2015-01-27 20:16:36 +01:00
|
|
|
_blockElems[element] = 1;
|
|
|
|
});
|
|
|
|
|
2020-11-21 19:37:57 +01:00
|
|
|
function isBlockElement(n) {
|
2020-11-23 19:24:19 +01:00
|
|
|
return !!_blockElems[(dom.nodeTagName(n) || '').toLowerCase()];
|
2011-03-26 14:10:41 +01:00
|
|
|
}
|
2011-07-07 19:59:34 +02:00
|
|
|
|
2020-11-21 19:37:57 +01:00
|
|
|
function textify(str) {
|
2011-03-26 14:10:41 +01:00
|
|
|
return sanitizeUnicode(
|
2020-11-23 19:24:19 +01:00
|
|
|
str.replace(/(\n | \n)/g, ' ').replace(/[\n\r ]/g, ' ').replace(/\xa0/g, ' ').replace(/\t/g, ' '));
|
2011-03-26 14:10:41 +01:00
|
|
|
}
|
2011-07-07 19:59:34 +02:00
|
|
|
|
2020-11-21 19:37:57 +01:00
|
|
|
function getAssoc(node, name) {
|
2020-11-23 19:24:19 +01:00
|
|
|
return dom.nodeProp(node, `_magicdom_${name}`);
|
2011-03-26 14:10:41 +01:00
|
|
|
}
|
|
|
|
|
2020-11-23 19:24:19 +01:00
|
|
|
const lines = (function () {
|
|
|
|
const textArray = [];
|
|
|
|
const attribsArray = [];
|
|
|
|
let attribsBuilder = null;
|
|
|
|
const op = Changeset.newOp('+');
|
2011-03-26 14:10:41 +01:00
|
|
|
var self = {
|
2020-11-23 19:24:19 +01:00
|
|
|
length() {
|
2011-07-07 19:59:34 +02:00
|
|
|
return textArray.length;
|
|
|
|
},
|
2020-11-23 19:24:19 +01:00
|
|
|
atColumnZero() {
|
|
|
|
return textArray[textArray.length - 1] === '';
|
2011-03-26 14:10:41 +01:00
|
|
|
},
|
2020-11-23 19:24:19 +01:00
|
|
|
startNew() {
|
|
|
|
textArray.push('');
|
2011-03-26 14:10:41 +01:00
|
|
|
self.flush(true);
|
|
|
|
attribsBuilder = Changeset.smartOpAssembler();
|
|
|
|
},
|
2020-11-23 19:24:19 +01:00
|
|
|
textOfLine(i) {
|
2011-07-07 19:59:34 +02:00
|
|
|
return textArray[i];
|
|
|
|
},
|
2020-11-23 19:24:19 +01:00
|
|
|
appendText(txt, attrString) {
|
2011-07-07 19:59:34 +02:00
|
|
|
textArray[textArray.length - 1] += txt;
|
2020-11-23 19:24:19 +01:00
|
|
|
// dmesg(txt+" / "+attrString);
|
2011-03-26 14:10:41 +01:00
|
|
|
op.attribs = attrString;
|
|
|
|
op.chars = txt.length;
|
|
|
|
attribsBuilder.append(op);
|
|
|
|
},
|
2020-11-23 19:24:19 +01:00
|
|
|
textLines() {
|
2011-07-07 19:59:34 +02:00
|
|
|
return textArray.slice();
|
|
|
|
},
|
2020-11-23 19:24:19 +01:00
|
|
|
attribLines() {
|
2011-07-07 19:59:34 +02:00
|
|
|
return attribsArray;
|
|
|
|
},
|
2011-03-26 14:10:41 +01:00
|
|
|
// call flush only when you're done
|
2020-11-23 19:24:19 +01:00
|
|
|
flush(withNewline) {
|
|
|
|
if (attribsBuilder) {
|
2011-03-26 14:10:41 +01:00
|
|
|
attribsArray.push(attribsBuilder.toString());
|
|
|
|
attribsBuilder = null;
|
|
|
|
}
|
2020-11-23 19:24:19 +01:00
|
|
|
},
|
2011-03-26 14:10:41 +01:00
|
|
|
};
|
|
|
|
self.startNew();
|
|
|
|
return self;
|
|
|
|
}());
|
2020-11-23 19:24:19 +01:00
|
|
|
const cc = {};
|
2011-07-07 19:59:34 +02:00
|
|
|
|
2020-11-21 19:37:57 +01:00
|
|
|
function _ensureColumnZero(state) {
|
2020-11-23 19:24:19 +01:00
|
|
|
if (!lines.atColumnZero()) {
|
2011-03-26 14:10:41 +01:00
|
|
|
cc.startNewLine(state);
|
|
|
|
}
|
|
|
|
}
|
2020-11-23 19:24:19 +01:00
|
|
|
let selection, startPoint, endPoint;
|
|
|
|
let selStart = [-1, -1];
|
|
|
|
let selEnd = [-1, -1];
|
2020-11-21 19:37:57 +01:00
|
|
|
function _isEmpty(node, state) {
|
2011-03-26 14:10:41 +01:00
|
|
|
// consider clean blank lines pasted in IE to be empty
|
|
|
|
if (dom.nodeNumChildren(node) == 0) return true;
|
2020-11-23 19:24:19 +01:00
|
|
|
if (dom.nodeNumChildren(node) == 1 && getAssoc(node, 'shouldBeEmpty') && dom.optNodeInnerHTML(node) == ' ' && !getAssoc(node, 'unpasted')) {
|
|
|
|
if (state) {
|
|
|
|
const child = dom.nodeChild(node, 0);
|
2011-03-26 14:10:41 +01:00
|
|
|
_reachPoint(child, 0, state);
|
|
|
|
_reachPoint(child, 1, state);
|
|
|
|
}
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
return false;
|
|
|
|
}
|
2011-07-07 19:59:34 +02:00
|
|
|
|
2020-11-21 19:37:57 +01:00
|
|
|
function _pointHere(charsAfter, state) {
|
2020-11-23 19:24:19 +01:00
|
|
|
const ln = lines.length() - 1;
|
|
|
|
let chr = lines.textOfLine(ln).length;
|
|
|
|
if (chr == 0 && !_.isEmpty(state.lineAttributes)) {
|
2011-03-26 14:10:41 +01:00
|
|
|
chr += 1; // listMarker
|
|
|
|
}
|
|
|
|
chr += charsAfter;
|
|
|
|
return [ln, chr];
|
|
|
|
}
|
2011-07-07 19:59:34 +02:00
|
|
|
|
2020-11-21 19:37:57 +01:00
|
|
|
function _reachBlockPoint(nd, idx, state) {
|
2011-07-07 19:59:34 +02:00
|
|
|
if (!dom.isNodeText(nd)) _reachPoint(nd, idx, state);
|
2011-03-26 14:10:41 +01:00
|
|
|
}
|
2011-07-07 19:59:34 +02:00
|
|
|
|
2020-11-21 19:37:57 +01:00
|
|
|
function _reachPoint(nd, idx, state) {
|
2020-11-23 19:24:19 +01:00
|
|
|
if (startPoint && nd == startPoint.node && startPoint.index == idx) {
|
2011-03-26 14:10:41 +01:00
|
|
|
selStart = _pointHere(0, state);
|
|
|
|
}
|
2020-11-23 19:24:19 +01:00
|
|
|
if (endPoint && nd == endPoint.node && endPoint.index == idx) {
|
2011-03-26 14:10:41 +01:00
|
|
|
selEnd = _pointHere(0, state);
|
|
|
|
}
|
|
|
|
}
|
2020-11-23 19:24:19 +01:00
|
|
|
cc.incrementFlag = function (state, flagName) {
|
2011-07-07 19:59:34 +02:00
|
|
|
state.flags[flagName] = (state.flags[flagName] || 0) + 1;
|
2020-11-23 19:24:19 +01:00
|
|
|
};
|
|
|
|
cc.decrementFlag = function (state, flagName) {
|
2011-03-26 14:10:41 +01:00
|
|
|
state.flags[flagName]--;
|
2020-11-23 19:24:19 +01:00
|
|
|
};
|
|
|
|
cc.incrementAttrib = function (state, attribName) {
|
|
|
|
if (!state.attribs[attribName]) {
|
2011-03-26 14:10:41 +01:00
|
|
|
state.attribs[attribName] = 1;
|
2020-11-23 19:24:19 +01:00
|
|
|
} else {
|
2011-03-26 14:10:41 +01:00
|
|
|
state.attribs[attribName]++;
|
|
|
|
}
|
|
|
|
_recalcAttribString(state);
|
2020-11-23 19:24:19 +01:00
|
|
|
};
|
|
|
|
cc.decrementAttrib = function (state, attribName) {
|
2011-03-26 14:10:41 +01:00
|
|
|
state.attribs[attribName]--;
|
|
|
|
_recalcAttribString(state);
|
2020-11-23 19:24:19 +01:00
|
|
|
};
|
2011-07-07 19:59:34 +02:00
|
|
|
|
2020-11-21 19:37:57 +01:00
|
|
|
function _enterList(state, listType) {
|
2020-11-23 19:24:19 +01:00
|
|
|
if (!listType) return;
|
|
|
|
const oldListType = state.lineAttributes.list;
|
|
|
|
if (listType != 'none') {
|
2011-07-07 19:59:34 +02:00
|
|
|
state.listNesting = (state.listNesting || 0) + 1;
|
2020-06-05 21:54:16 +02:00
|
|
|
// reminder that listType can be "number2", "number3" etc.
|
2020-11-23 19:24:19 +01:00
|
|
|
if (listType.indexOf('number') !== -1) {
|
2020-06-05 21:54:16 +02:00
|
|
|
state.start = (state.start || 0) + 1;
|
|
|
|
}
|
2011-03-26 14:10:41 +01:00
|
|
|
}
|
2019-04-16 00:34:29 +02:00
|
|
|
|
2020-11-23 19:24:19 +01:00
|
|
|
if (listType === 'none' || !listType) {
|
|
|
|
delete state.lineAttributes.list;
|
|
|
|
} else {
|
|
|
|
state.lineAttributes.list = listType;
|
2012-04-07 02:12:42 +02:00
|
|
|
}
|
2011-03-26 14:10:41 +01:00
|
|
|
_recalcAttribString(state);
|
|
|
|
return oldListType;
|
|
|
|
}
|
2011-07-07 19:59:34 +02:00
|
|
|
|
2020-11-21 19:37:57 +01:00
|
|
|
function _exitList(state, oldListType) {
|
2020-11-23 19:24:19 +01:00
|
|
|
if (state.lineAttributes.list) {
|
2011-03-26 14:10:41 +01:00
|
|
|
state.listNesting--;
|
|
|
|
}
|
2020-06-05 21:54:16 +02:00
|
|
|
if (oldListType && oldListType != 'none') {
|
2020-11-23 19:24:19 +01:00
|
|
|
state.lineAttributes.list = oldListType;
|
|
|
|
} else {
|
|
|
|
delete state.lineAttributes.list;
|
|
|
|
delete state.lineAttributes.start;
|
2020-06-05 21:54:16 +02:00
|
|
|
}
|
2011-03-26 14:10:41 +01:00
|
|
|
_recalcAttribString(state);
|
|
|
|
}
|
2011-07-07 19:59:34 +02:00
|
|
|
|
2020-11-21 19:37:57 +01:00
|
|
|
function _enterAuthor(state, author) {
|
2020-11-23 19:24:19 +01:00
|
|
|
const oldAuthor = state.author;
|
2011-07-07 19:59:34 +02:00
|
|
|
state.authorLevel = (state.authorLevel || 0) + 1;
|
2011-03-26 14:10:41 +01:00
|
|
|
state.author = author;
|
|
|
|
_recalcAttribString(state);
|
|
|
|
return oldAuthor;
|
|
|
|
}
|
2011-07-07 19:59:34 +02:00
|
|
|
|
2020-11-21 19:37:57 +01:00
|
|
|
function _exitAuthor(state, oldAuthor) {
|
2011-03-26 14:10:41 +01:00
|
|
|
state.authorLevel--;
|
|
|
|
state.author = oldAuthor;
|
|
|
|
_recalcAttribString(state);
|
|
|
|
}
|
2011-07-07 19:59:34 +02:00
|
|
|
|
2020-11-21 19:37:57 +01:00
|
|
|
function _recalcAttribString(state) {
|
2020-11-23 19:24:19 +01:00
|
|
|
const lst = [];
|
|
|
|
for (const a in state.attribs) {
|
|
|
|
if (state.attribs[a]) {
|
2015-03-26 18:49:35 +01:00
|
|
|
// The following splitting of the attribute name is a workaround
|
|
|
|
// to enable the content collector to store key-value attributes
|
|
|
|
// see https://github.com/ether/etherpad-lite/issues/2567 for more information
|
|
|
|
// in long term the contentcollector should be refactored to get rid of this workaround
|
2020-11-23 19:24:19 +01:00
|
|
|
const ATTRIBUTE_SPLIT_STRING = '::';
|
2019-04-16 00:34:29 +02:00
|
|
|
|
2015-03-26 18:49:35 +01:00
|
|
|
// see if attributeString is splittable
|
2020-11-23 19:24:19 +01:00
|
|
|
const attributeSplits = a.split(ATTRIBUTE_SPLIT_STRING);
|
2015-03-26 18:49:35 +01:00
|
|
|
if (attributeSplits.length > 1) {
|
2020-11-23 19:24:19 +01:00
|
|
|
// the attribute name follows the convention key::value
|
|
|
|
// so save it as a key value attribute
|
|
|
|
lst.push([attributeSplits[0], attributeSplits[1]]);
|
2015-03-26 18:49:35 +01:00
|
|
|
} else {
|
2020-11-23 19:24:19 +01:00
|
|
|
// the "normal" case, the attribute is just a switch
|
|
|
|
// so set it true
|
|
|
|
lst.push([a, 'true']);
|
2015-03-26 18:49:35 +01:00
|
|
|
}
|
2011-03-26 14:10:41 +01:00
|
|
|
}
|
|
|
|
}
|
2020-11-23 19:24:19 +01:00
|
|
|
if (state.authorLevel > 0) {
|
|
|
|
const authorAttrib = ['author', state.author];
|
|
|
|
if (apool.putAttrib(authorAttrib, true) >= 0) {
|
2011-03-26 14:10:41 +01:00
|
|
|
// require that author already be in pool
|
|
|
|
// (don't add authors from other documents, etc.)
|
|
|
|
lst.push(authorAttrib);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
state.attribString = Changeset.makeAttribsString('+', lst, apool);
|
|
|
|
}
|
2011-07-07 19:59:34 +02:00
|
|
|
|
2020-11-21 19:37:57 +01:00
|
|
|
function _produceLineAttributesMarker(state) {
|
2012-04-07 02:12:42 +02:00
|
|
|
// TODO: This has to go to AttributeManager.
|
2020-11-23 19:24:19 +01:00
|
|
|
const attributes = [
|
2012-04-07 02:12:42 +02:00
|
|
|
['lmkr', '1'],
|
2020-11-23 19:24:19 +01:00
|
|
|
['insertorder', 'first'],
|
2012-04-07 02:12:42 +02:00
|
|
|
].concat(
|
2020-11-23 19:24:19 +01:00
|
|
|
_.map(state.lineAttributes, (value, key) => [key, value]),
|
2012-04-07 02:12:42 +02:00
|
|
|
);
|
2020-11-23 19:24:19 +01:00
|
|
|
lines.appendText('*', Changeset.makeAttribsString('+', attributes, apool));
|
2011-03-26 14:10:41 +01:00
|
|
|
}
|
2020-11-23 19:24:19 +01:00
|
|
|
cc.startNewLine = function (state) {
|
|
|
|
if (state) {
|
|
|
|
const atBeginningOfLine = lines.textOfLine(lines.length() - 1).length == 0;
|
|
|
|
if (atBeginningOfLine && !_.isEmpty(state.lineAttributes)) {
|
2012-04-07 02:12:42 +02:00
|
|
|
_produceLineAttributesMarker(state);
|
2011-03-26 14:10:41 +01:00
|
|
|
}
|
|
|
|
}
|
|
|
|
lines.startNew();
|
2020-11-23 19:24:19 +01:00
|
|
|
};
|
|
|
|
cc.notifySelection = function (sel) {
|
|
|
|
if (sel) {
|
2011-03-26 14:10:41 +01:00
|
|
|
selection = sel;
|
|
|
|
startPoint = selection.startPoint;
|
|
|
|
endPoint = selection.endPoint;
|
|
|
|
}
|
|
|
|
};
|
2020-11-23 19:24:19 +01:00
|
|
|
cc.doAttrib = function (state, na) {
|
2011-03-26 14:10:41 +01:00
|
|
|
state.localAttribs = (state.localAttribs || []);
|
|
|
|
state.localAttribs.push(na);
|
|
|
|
cc.incrementAttrib(state, na);
|
|
|
|
};
|
2020-11-23 19:24:19 +01:00
|
|
|
cc.collectContent = function (node, state) {
|
|
|
|
if (!state) {
|
2011-07-07 19:59:34 +02:00
|
|
|
state = {
|
2020-11-23 19:24:19 +01:00
|
|
|
flags: { /* name -> nesting counter*/
|
2011-07-07 19:59:34 +02:00
|
|
|
},
|
|
|
|
localAttribs: null,
|
2020-11-23 19:24:19 +01:00
|
|
|
attribs: { /* name -> nesting counter*/
|
2011-07-07 19:59:34 +02:00
|
|
|
},
|
2012-04-07 02:12:42 +02:00
|
|
|
attribString: '',
|
|
|
|
// lineAttributes maintain a map from attributes to attribute values set on a line
|
|
|
|
lineAttributes: {
|
|
|
|
/*
|
|
|
|
example:
|
|
|
|
'list': 'bullet1',
|
|
|
|
*/
|
2020-11-23 19:24:19 +01:00
|
|
|
},
|
2011-07-07 19:59:34 +02:00
|
|
|
};
|
2011-03-26 14:10:41 +01:00
|
|
|
}
|
2020-11-23 19:24:19 +01:00
|
|
|
const localAttribs = state.localAttribs;
|
2011-03-26 14:10:41 +01:00
|
|
|
state.localAttribs = null;
|
2020-11-23 19:24:19 +01:00
|
|
|
const isBlock = isBlockElement(node);
|
|
|
|
const isEmpty = _isEmpty(node, state);
|
2011-03-26 14:10:41 +01:00
|
|
|
if (isBlock) _ensureColumnZero(state);
|
2020-11-23 19:24:19 +01:00
|
|
|
const startLine = lines.length() - 1;
|
2011-03-26 14:10:41 +01:00
|
|
|
_reachBlockPoint(node, 0, state);
|
2020-11-23 19:24:19 +01:00
|
|
|
if (dom.isNodeText(node)) {
|
|
|
|
let txt = dom.nodeValue(node);
|
|
|
|
var tname = dom.nodeAttr(node.parentNode, 'name');
|
2012-09-12 00:02:53 +02:00
|
|
|
|
2020-11-23 19:24:19 +01:00
|
|
|
const txtFromHook = hooks.callAll('collectContentLineText', {
|
2012-09-08 19:03:13 +02:00
|
|
|
cc: this,
|
2020-11-23 19:24:19 +01:00
|
|
|
state,
|
|
|
|
tname,
|
|
|
|
node,
|
|
|
|
text: txt,
|
2012-09-08 19:03:13 +02:00
|
|
|
styl: null,
|
2020-11-23 19:24:19 +01:00
|
|
|
cls: null,
|
2019-04-16 00:34:29 +02:00
|
|
|
});
|
2020-06-05 21:54:16 +02:00
|
|
|
|
2020-11-23 19:24:19 +01:00
|
|
|
if (typeof (txtFromHook) === 'object') {
|
|
|
|
txt = dom.nodeValue(node);
|
|
|
|
} else if (txtFromHook) {
|
|
|
|
txt = txtFromHook;
|
2020-06-05 21:54:16 +02:00
|
|
|
}
|
2012-09-11 23:21:14 +02:00
|
|
|
|
2020-11-23 19:24:19 +01:00
|
|
|
let rest = '';
|
|
|
|
let x = 0; // offset into original text
|
|
|
|
if (txt.length == 0) {
|
|
|
|
if (startPoint && node == startPoint.node) {
|
2011-03-26 14:10:41 +01:00
|
|
|
selStart = _pointHere(0, state);
|
|
|
|
}
|
2020-11-23 19:24:19 +01:00
|
|
|
if (endPoint && node == endPoint.node) {
|
2011-03-26 14:10:41 +01:00
|
|
|
selEnd = _pointHere(0, state);
|
2013-12-05 08:41:29 +01:00
|
|
|
}
|
2011-03-26 14:10:41 +01:00
|
|
|
}
|
2020-11-23 19:24:19 +01:00
|
|
|
while (txt.length > 0) {
|
|
|
|
let consumed = 0;
|
|
|
|
if (state.flags.preMode) {
|
|
|
|
const firstLine = txt.split('\n', 1)[0];
|
2011-07-07 19:59:34 +02:00
|
|
|
consumed = firstLine.length + 1;
|
2011-03-26 14:10:41 +01:00
|
|
|
rest = txt.substring(consumed);
|
|
|
|
txt = firstLine;
|
2020-11-23 19:24:19 +01:00
|
|
|
} else { /* will only run this loop body once */
|
2011-03-26 14:10:41 +01:00
|
|
|
}
|
2020-11-23 19:24:19 +01:00
|
|
|
if (startPoint && node == startPoint.node && startPoint.index - x <= txt.length) {
|
2011-07-07 19:59:34 +02:00
|
|
|
selStart = _pointHere(startPoint.index - x, state);
|
|
|
|
}
|
2020-11-23 19:24:19 +01:00
|
|
|
if (endPoint && node == endPoint.node && endPoint.index - x <= txt.length) {
|
2011-07-07 19:59:34 +02:00
|
|
|
selEnd = _pointHere(endPoint.index - x, state);
|
2011-03-26 14:10:41 +01:00
|
|
|
}
|
2020-11-23 19:24:19 +01:00
|
|
|
let txt2 = txt;
|
|
|
|
if ((!state.flags.preMode) && /^[\r\n]*$/.exec(txt)) {
|
2011-03-26 14:10:41 +01:00
|
|
|
// prevents textnodes containing just "\n" from being significant
|
|
|
|
// in safari when pasting text, now that we convert them to
|
|
|
|
// spaces instead of removing them, because in other cases
|
|
|
|
// removing "\n" from pasted HTML will collapse words together.
|
2020-11-23 19:24:19 +01:00
|
|
|
txt2 = '';
|
2011-03-26 14:10:41 +01:00
|
|
|
}
|
2020-11-23 19:24:19 +01:00
|
|
|
const atBeginningOfLine = lines.textOfLine(lines.length() - 1).length == 0;
|
|
|
|
if (atBeginningOfLine) {
|
2011-03-26 14:10:41 +01:00
|
|
|
// newlines in the source mustn't become spaces at beginning of line box
|
|
|
|
txt2 = txt2.replace(/^\n*/, '');
|
|
|
|
}
|
2020-11-23 19:24:19 +01:00
|
|
|
if (atBeginningOfLine && !_.isEmpty(state.lineAttributes)) {
|
2012-04-07 02:12:42 +02:00
|
|
|
_produceLineAttributesMarker(state);
|
2011-03-26 14:10:41 +01:00
|
|
|
}
|
|
|
|
lines.appendText(textify(txt2), state.attribString);
|
|
|
|
x += consumed;
|
|
|
|
txt = rest;
|
2020-11-23 19:24:19 +01:00
|
|
|
if (txt.length > 0) {
|
2011-03-26 14:10:41 +01:00
|
|
|
cc.startNewLine(state);
|
|
|
|
}
|
|
|
|
}
|
2020-11-23 19:24:19 +01:00
|
|
|
} else {
|
|
|
|
var tname = (dom.nodeTagName(node) || '').toLowerCase();
|
|
|
|
|
|
|
|
if (tname == 'img') {
|
|
|
|
const collectContentImage = hooks.callAll('collectContentImage', {
|
|
|
|
cc,
|
|
|
|
state,
|
|
|
|
tname,
|
|
|
|
styl,
|
|
|
|
cls,
|
|
|
|
node,
|
2015-01-26 03:32:58 +01:00
|
|
|
});
|
2020-11-23 19:24:19 +01:00
|
|
|
} else {
|
2015-01-28 20:09:47 +01:00
|
|
|
// THIS SEEMS VERY HACKY! -- Please submit a better fix!
|
2020-11-23 19:24:19 +01:00
|
|
|
delete state.lineAttributes.img;
|
2015-01-26 02:44:40 +01:00
|
|
|
}
|
2015-01-28 20:09:47 +01:00
|
|
|
|
2020-11-23 19:24:19 +01:00
|
|
|
if (tname == 'br') {
|
2012-09-08 19:03:13 +02:00
|
|
|
this.breakLine = true;
|
2020-11-23 19:24:19 +01:00
|
|
|
const tvalue = dom.nodeAttr(node, 'value');
|
|
|
|
const induceLineBreak = hooks.callAll('collectContentLineBreak', {
|
2012-09-08 19:03:13 +02:00
|
|
|
cc: this,
|
2020-11-23 19:24:19 +01:00
|
|
|
state,
|
|
|
|
tname,
|
|
|
|
tvalue,
|
2012-09-08 19:03:13 +02:00
|
|
|
styl: null,
|
2020-11-23 19:24:19 +01:00
|
|
|
cls: null,
|
2019-04-16 00:34:29 +02:00
|
|
|
});
|
2020-11-23 19:24:19 +01:00
|
|
|
const startNewLine = (typeof (induceLineBreak) === 'object' && induceLineBreak.length == 0) ? true : induceLineBreak[0];
|
|
|
|
if (startNewLine) {
|
2012-09-08 19:03:13 +02:00
|
|
|
cc.startNewLine(state);
|
2013-12-05 08:41:29 +01:00
|
|
|
}
|
2020-11-23 19:24:19 +01:00
|
|
|
} else if (tname == 'script' || tname == 'style') {
|
2011-03-26 14:10:41 +01:00
|
|
|
// ignore
|
2020-11-23 19:24:19 +01:00
|
|
|
} else if (!isEmpty) {
|
|
|
|
var styl = dom.nodeAttr(node, 'style');
|
|
|
|
var cls = dom.nodeAttr(node, 'class');
|
|
|
|
let isPre = (tname == 'pre');
|
|
|
|
if ((!isPre) && abrowser.safari) {
|
2011-03-26 14:10:41 +01:00
|
|
|
isPre = (styl && /\bwhite-space:\s*pre\b/i.exec(styl));
|
|
|
|
}
|
|
|
|
if (isPre) cc.incrementFlag(state, 'preMode');
|
2020-11-23 19:24:19 +01:00
|
|
|
let oldListTypeOrNull = null;
|
|
|
|
let oldAuthorOrNull = null;
|
2020-03-29 14:09:33 +02:00
|
|
|
|
|
|
|
// LibreOffice Writer puts in weird items during import or copy/paste, we should drop them.
|
2020-11-23 19:24:19 +01:00
|
|
|
if (cls === 'Numbering_20_Symbols' || cls === 'Bullet_20_Symbols') {
|
2020-03-29 14:09:33 +02:00
|
|
|
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
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
2020-11-23 19:24:19 +01:00
|
|
|
if (collectStyles) {
|
2012-03-01 19:22:02 +01:00
|
|
|
hooks.callAll('collectContentPre', {
|
2020-11-23 19:24:19 +01:00
|
|
|
cc,
|
|
|
|
state,
|
|
|
|
tname,
|
|
|
|
styl,
|
|
|
|
cls,
|
2011-07-07 19:59:34 +02:00
|
|
|
});
|
2020-11-23 19:24:19 +01:00
|
|
|
if (tname == 'b' || (styl && /\bfont-weight:\s*bold\b/i.exec(styl)) || tname == 'strong') {
|
|
|
|
cc.doAttrib(state, 'bold');
|
2011-03-26 14:10:41 +01:00
|
|
|
}
|
2020-11-23 19:24:19 +01:00
|
|
|
if (tname == 'i' || (styl && /\bfont-style:\s*italic\b/i.exec(styl)) || tname == 'em') {
|
|
|
|
cc.doAttrib(state, 'italic');
|
2011-03-26 14:10:41 +01:00
|
|
|
}
|
2020-11-23 19:24:19 +01:00
|
|
|
if (tname == 'u' || (styl && /\btext-decoration:\s*underline\b/i.exec(styl)) || tname == 'ins') {
|
|
|
|
cc.doAttrib(state, 'underline');
|
2011-03-26 14:10:41 +01:00
|
|
|
}
|
2020-11-23 19:24:19 +01:00
|
|
|
if (tname == 's' || (styl && /\btext-decoration:\s*line-through\b/i.exec(styl)) || tname == 'del') {
|
|
|
|
cc.doAttrib(state, 'strikethrough');
|
2011-03-26 14:10:41 +01:00
|
|
|
}
|
2020-11-23 19:24:19 +01:00
|
|
|
if (tname == 'ul' || tname == 'ol') {
|
|
|
|
if (node.attribs) {
|
2014-12-31 15:16:10 +01:00
|
|
|
var type = node.attribs.class;
|
2020-11-23 19:24:19 +01:00
|
|
|
} else {
|
2014-12-31 15:16:10 +01:00
|
|
|
var type = null;
|
|
|
|
}
|
2020-11-23 19:24:19 +01:00
|
|
|
const rr = cls && /(?:^| )list-([a-z]+[0-9]+)\b/.exec(cls);
|
2014-12-29 16:12:07 +01:00
|
|
|
// 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
|
2020-11-23 19:24:19 +01:00
|
|
|
if (!rr && !type) {
|
|
|
|
for (var i in node.children) {
|
|
|
|
if (node.children[i] && node.children[i].name == 'ul') {
|
|
|
|
type = node.children[i].attribs.class;
|
|
|
|
if (type) {
|
|
|
|
break;
|
2014-12-29 16:12:07 +01:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
2020-11-23 19:24:19 +01:00
|
|
|
if (rr && rr[1]) {
|
|
|
|
type = rr[1];
|
2014-12-29 16:12:07 +01:00
|
|
|
} else {
|
2020-11-23 19:24:19 +01:00
|
|
|
if (tname == 'ul') {
|
|
|
|
if ((type && type.match('indent')) || (node.attribs && node.attribs.class && node.attribs.class.match('indent'))) {
|
|
|
|
type = 'indent';
|
2015-01-09 02:04:03 +01:00
|
|
|
} else {
|
2020-11-23 19:24:19 +01:00
|
|
|
type = 'bullet';
|
2015-01-09 02:04:03 +01:00
|
|
|
}
|
|
|
|
} else {
|
2020-11-23 19:24:19 +01:00
|
|
|
type = 'number';
|
2015-01-09 02:04:03 +01:00
|
|
|
}
|
2020-11-23 19:24:19 +01:00
|
|
|
type += String(Math.min(_MAX_LIST_LEVEL, (state.listNesting || 0) + 1));
|
2014-12-29 16:12:07 +01:00
|
|
|
}
|
2011-03-26 14:10:41 +01:00
|
|
|
oldListTypeOrNull = (_enterList(state, type) || 'none');
|
2020-11-23 19:24:19 +01:00
|
|
|
} else if ((tname == 'div' || tname == 'p') && cls && cls.match(/(?:^| )ace-line\b/)) {
|
2015-01-23 02:47:12 +01:00
|
|
|
// This has undesirable behavior in Chrome but is right in other browsers.
|
|
|
|
// See https://github.com/ether/etherpad-lite/issues/2412 for reasoning
|
2020-11-23 19:24:19 +01:00
|
|
|
if (!abrowser.chrome) oldListTypeOrNull = (_enterList(state, type) || 'none');
|
|
|
|
} else if ((tname === 'li')) {
|
|
|
|
state.lineAttributes.start = state.start || 0;
|
2020-06-05 21:54:16 +02:00
|
|
|
_recalcAttribString(state);
|
2020-11-23 19:24:19 +01:00
|
|
|
if (state.lineAttributes.list.indexOf('number') !== -1) {
|
2020-06-05 21:54:16 +02:00
|
|
|
/*
|
|
|
|
Nested OLs are not --> <ol><li>1</li><ol>nested</ol></ol>
|
|
|
|
They are --> <ol><li>1</li><li><ol><li>nested</li></ol></li></ol>
|
|
|
|
Note how the <ol> item has to be inside a <li>
|
|
|
|
Because of this we don't increment the start number
|
|
|
|
*/
|
2020-11-23 19:24:19 +01:00
|
|
|
if (node.parent && node.parent.name !== 'ol') {
|
2020-06-05 21:54:16 +02:00
|
|
|
/*
|
|
|
|
TODO: start number has to increment based on indentLevel(numberX)
|
|
|
|
This means we have to build an object IE
|
|
|
|
{
|
|
|
|
1: 4
|
|
|
|
2: 3
|
|
|
|
3: 5
|
|
|
|
}
|
|
|
|
But the browser seems to handle it fine using CSS.. Why can't we do the same
|
|
|
|
with exports? We can.. But let's leave this comment in because it might be useful
|
|
|
|
in the future..
|
|
|
|
*/
|
|
|
|
state.start++; // not if it's parent is an OL or UL.
|
|
|
|
}
|
|
|
|
}
|
|
|
|
// UL list items never modify the start value.
|
2020-11-23 19:24:19 +01:00
|
|
|
if (node.parent && node.parent.name === 'ul') {
|
2020-06-05 21:54:16 +02:00
|
|
|
state.start++;
|
|
|
|
// TODO, this is hacky.
|
|
|
|
// Because if the first item is an UL it will increment a list no?
|
|
|
|
// A much more graceful way would be to say, ul increases if it's within an OL
|
|
|
|
// But I don't know a way to do that because we're only aware of the previous Line
|
|
|
|
// As the concept of parent's doesn't exist when processing each domline...
|
|
|
|
}
|
2020-11-23 19:24:19 +01:00
|
|
|
} else {
|
2020-06-05 21:54:16 +02:00
|
|
|
// Below needs more testin if it's neccesary as _exitList should take care of this.
|
|
|
|
// delete state.start;
|
|
|
|
// delete state.listNesting;
|
|
|
|
// _recalcAttribString(state);
|
|
|
|
}
|
2020-11-23 19:24:19 +01:00
|
|
|
if (className2Author && cls) {
|
|
|
|
const classes = cls.match(/\S+/g);
|
|
|
|
if (classes && classes.length > 0) {
|
|
|
|
for (var i = 0; i < classes.length; i++) {
|
2011-03-26 14:10:41 +01:00
|
|
|
var c = classes[i];
|
2020-11-23 19:24:19 +01:00
|
|
|
const a = className2Author(c);
|
|
|
|
if (a) {
|
2011-03-26 14:10:41 +01:00
|
|
|
oldAuthorOrNull = (_enterAuthor(state, a) || 'none');
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-11-23 19:24:19 +01:00
|
|
|
const nc = dom.nodeNumChildren(node);
|
|
|
|
for (var i = 0; i < nc; i++) {
|
2011-03-26 14:10:41 +01:00
|
|
|
var c = dom.nodeChild(node, i);
|
|
|
|
cc.collectContent(c, state);
|
|
|
|
}
|
|
|
|
|
2020-11-23 19:24:19 +01:00
|
|
|
if (collectStyles) {
|
2012-03-01 19:22:02 +01:00
|
|
|
hooks.callAll('collectContentPost', {
|
2020-11-23 19:24:19 +01:00
|
|
|
cc,
|
|
|
|
state,
|
|
|
|
tname,
|
|
|
|
styl,
|
|
|
|
cls,
|
2011-07-07 19:59:34 +02:00
|
|
|
});
|
2011-03-26 14:10:41 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
if (isPre) cc.decrementFlag(state, 'preMode');
|
2020-11-23 19:24:19 +01:00
|
|
|
if (state.localAttribs) {
|
|
|
|
for (var i = 0; i < state.localAttribs.length; i++) {
|
2011-03-26 14:10:41 +01:00
|
|
|
cc.decrementAttrib(state, state.localAttribs[i]);
|
|
|
|
}
|
|
|
|
}
|
2020-11-23 19:24:19 +01:00
|
|
|
if (oldListTypeOrNull) {
|
2011-03-26 14:10:41 +01:00
|
|
|
_exitList(state, oldListTypeOrNull);
|
|
|
|
}
|
2020-11-23 19:24:19 +01:00
|
|
|
if (oldAuthorOrNull) {
|
2011-03-26 14:10:41 +01:00
|
|
|
_exitAuthor(state, oldAuthorOrNull);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
2020-11-23 19:24:19 +01:00
|
|
|
if (!abrowser.msie) {
|
2011-03-26 14:10:41 +01:00
|
|
|
_reachBlockPoint(node, 1, state);
|
|
|
|
}
|
2020-11-23 19:24:19 +01:00
|
|
|
if (isBlock) {
|
|
|
|
if (lines.length() - 1 == startLine) {
|
2020-05-29 17:53:08 +02:00
|
|
|
// 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
|
2020-06-05 21:54:16 +02:00
|
|
|
|
|
|
|
// If we're doing an export event we need to start a new lines
|
|
|
|
// Export events don't have window available.
|
|
|
|
// commented out to solve #2412 - https://github.com/ether/etherpad-lite/issues/2412
|
2020-11-23 19:24:19 +01:00
|
|
|
if ((state.lineAttributes && !state.lineAttributes.list) || typeof window === 'undefined') {
|
2020-05-29 17:53:08 +02:00
|
|
|
cc.startNewLine(state);
|
|
|
|
}
|
2020-11-23 19:24:19 +01:00
|
|
|
} else {
|
2011-03-26 14:10:41 +01:00
|
|
|
_ensureColumnZero(state);
|
|
|
|
}
|
|
|
|
}
|
2020-11-23 19:24:19 +01:00
|
|
|
if (abrowser.msie) {
|
2011-03-26 14:10:41 +01:00
|
|
|
// in IE, a point immediately after a DIV appears on the next line
|
|
|
|
_reachBlockPoint(node, 1, state);
|
|
|
|
}
|
|
|
|
state.localAttribs = localAttribs;
|
|
|
|
};
|
|
|
|
// can pass a falsy value for end of doc
|
2020-11-23 19:24:19 +01:00
|
|
|
cc.notifyNextNode = function (node) {
|
2011-03-26 14:10:41 +01:00
|
|
|
// 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.
|
|
|
|
// it is incorporated as dirty by the rule that a dirty region has
|
|
|
|
// to end a line.
|
2020-11-23 19:24:19 +01:00
|
|
|
if ((!node) || (isBlockElement(node) && !_isEmpty(node))) {
|
2011-03-26 14:10:41 +01:00
|
|
|
_ensureColumnZero(null);
|
|
|
|
}
|
|
|
|
};
|
|
|
|
// each returns [line, char] or [-1,-1]
|
2020-11-23 19:24:19 +01:00
|
|
|
const getSelectionStart = function () {
|
|
|
|
return selStart;
|
|
|
|
};
|
|
|
|
const getSelectionEnd = function () {
|
|
|
|
return selEnd;
|
|
|
|
};
|
2011-03-26 14:10:41 +01:00
|
|
|
|
|
|
|
// 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
|
2020-11-23 19:24:19 +01:00
|
|
|
cc.getLines = function () {
|
2011-07-07 19:59:34 +02:00
|
|
|
return lines.textLines();
|
|
|
|
};
|
2011-03-26 14:10:41 +01:00
|
|
|
|
2020-11-23 19:24:19 +01:00
|
|
|
cc.finish = function () {
|
2011-03-26 14:10:41 +01:00
|
|
|
lines.flush();
|
2020-11-23 19:24:19 +01:00
|
|
|
const lineAttribs = lines.attribLines();
|
|
|
|
const lineStrings = cc.getLines();
|
2011-03-26 14:10:41 +01:00
|
|
|
|
|
|
|
lineStrings.length--;
|
|
|
|
lineAttribs.length--;
|
|
|
|
|
2020-11-23 19:24:19 +01:00
|
|
|
const ss = getSelectionStart();
|
|
|
|
const se = getSelectionEnd();
|
2011-03-26 14:10:41 +01:00
|
|
|
|
2020-11-21 19:37:57 +01:00
|
|
|
function fixLongLines() {
|
2011-03-26 14:10:41 +01:00
|
|
|
// design mode does not deal with with really long lines!
|
2020-11-23 19:24:19 +01:00
|
|
|
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--) {
|
|
|
|
let oldString = lineStrings[i];
|
|
|
|
let oldAttribString = lineAttribs[i];
|
|
|
|
if (oldString.length > lineLimit + buffer) {
|
2011-07-07 19:59:34 +02:00
|
|
|
var newStrings = [];
|
2020-11-23 19:24:19 +01:00
|
|
|
const newAttribStrings = [];
|
|
|
|
while (oldString.length > lineLimit) {
|
|
|
|
// var semiloc = oldString.lastIndexOf(';', lineLimit-1);
|
|
|
|
// var lengthToTake = (semiloc >= 0 ? (semiloc+1) : lineLimit);
|
|
|
|
const lengthToTake = lineLimit;
|
2011-07-07 19:59:34 +02:00
|
|
|
newStrings.push(oldString.substring(0, lengthToTake));
|
|
|
|
oldString = oldString.substring(lengthToTake);
|
|
|
|
newAttribStrings.push(Changeset.subattribution(oldAttribString, 0, lengthToTake));
|
|
|
|
oldAttribString = Changeset.subattribution(oldAttribString, lengthToTake);
|
|
|
|
}
|
2020-11-23 19:24:19 +01:00
|
|
|
if (oldString.length > 0) {
|
2011-07-07 19:59:34 +02:00
|
|
|
newStrings.push(oldString);
|
|
|
|
newAttribStrings.push(oldAttribString);
|
|
|
|
}
|
|
|
|
|
2020-11-21 19:37:57 +01:00
|
|
|
function fixLineNumber(lineChar) {
|
2011-07-07 19:59:34 +02:00
|
|
|
if (lineChar[0] < 0) return;
|
2020-11-23 19:24:19 +01:00
|
|
|
let n = lineChar[0];
|
|
|
|
let c = lineChar[1];
|
|
|
|
if (n > i) {
|
2011-07-07 19:59:34 +02:00
|
|
|
n += (newStrings.length - 1);
|
2020-11-23 19:24:19 +01:00
|
|
|
} else if (n == i) {
|
|
|
|
let a = 0;
|
|
|
|
while (c > newStrings[a].length) {
|
2011-07-07 19:59:34 +02:00
|
|
|
c -= newStrings[a].length;
|
|
|
|
a++;
|
|
|
|
}
|
|
|
|
n += a;
|
|
|
|
}
|
|
|
|
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);
|
|
|
|
}
|
2011-03-26 14:10:41 +01:00
|
|
|
}
|
2011-07-07 19:59:34 +02:00
|
|
|
return {
|
2020-11-23 19:24:19 +01:00
|
|
|
linesWrapped,
|
|
|
|
numLinesAfter,
|
2011-07-07 19:59:34 +02:00
|
|
|
};
|
2011-03-26 14:10:41 +01:00
|
|
|
}
|
2020-11-23 19:24:19 +01:00
|
|
|
const wrapData = fixLongLines();
|
2011-03-26 14:10:41 +01:00
|
|
|
|
2011-07-07 19:59:34 +02:00
|
|
|
return {
|
|
|
|
selStart: ss,
|
|
|
|
selEnd: se,
|
|
|
|
linesWrapped: wrapData.linesWrapped,
|
|
|
|
numLinesAfter: wrapData.numLinesAfter,
|
|
|
|
lines: lineStrings,
|
2020-11-23 19:24:19 +01:00
|
|
|
lineAttribs,
|
2011-07-07 19:59:34 +02:00
|
|
|
};
|
2020-11-23 19:24:19 +01:00
|
|
|
};
|
2011-03-26 14:10:41 +01:00
|
|
|
|
|
|
|
return cc;
|
|
|
|
}
|
2012-01-16 02:23:48 +01:00
|
|
|
|
|
|
|
exports.sanitizeUnicode = sanitizeUnicode;
|
|
|
|
exports.makeContentCollector = makeContentCollector;
|