2021-01-14 11:00:14 +01:00
|
|
|
'use strict';
|
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');
|
2012-01-16 05:16:11 +01:00
|
|
|
|
2021-01-22 21:41:14 +01:00
|
|
|
const sanitizeUnicode = (s) => UNorm.nfc(s);
|
2011-03-26 14:10:41 +01:00
|
|
|
|
2021-01-21 08:30:29 +01:00
|
|
|
const makeContentCollector = (collectStyles, abrowser, apool, className2Author) => {
|
2021-01-25 00:03:13 +01:00
|
|
|
// This file is used both in browsers and with cheerio in Node.js (for importing HTML). Cheerio's
|
|
|
|
// Node-like objects are not 100% API compatible with the DOM Node specification; this `dom`
|
|
|
|
// object abstracts away the differences.
|
2021-01-21 08:30:29 +01:00
|
|
|
const dom = {
|
2021-01-25 00:03:13 +01:00
|
|
|
// .nodeType works with DOM and cheerio 0.22.0. Note: Cheerio 0.22.0 does not provide the
|
|
|
|
// Node.*_NODE constants, so they cannot be used here.
|
2021-01-24 08:07:33 +01:00
|
|
|
isElementNode: (n) => n.nodeType === 1, // Node.ELEMENT_NODE
|
2021-01-25 00:20:19 +01:00
|
|
|
isTextNode: (n) => n.nodeType === 3, // Node.TEXT_NODE
|
2021-01-25 00:03:13 +01:00
|
|
|
// .tagName works with DOM and cheerio 0.22.0, but:
|
|
|
|
// * With DOM, .tagName is an uppercase string.
|
|
|
|
// * With cheerio 0.22.0, .tagName is a lowercase string.
|
2021-01-21 08:27:15 +01:00
|
|
|
// For consistency, this function always returns a lowercase string.
|
2021-01-25 00:20:19 +01:00
|
|
|
tagName: (n) => n.tagName && n.tagName.toLowerCase(),
|
2021-01-25 00:03:13 +01:00
|
|
|
// .nodeValue works with DOM and cheerio 0.22.0.
|
2021-01-22 21:41:14 +01:00
|
|
|
nodeValue: (n) => n.nodeValue,
|
2021-01-25 00:03:13 +01:00
|
|
|
// Returns the number of Node children (n.childNodes.length), not the number of Element children
|
|
|
|
// (n.children.length in DOM).
|
2021-01-25 00:20:19 +01:00
|
|
|
numChildNodes: (n) => {
|
2021-01-25 00:03:13 +01:00
|
|
|
// .childNodes.length works with DOM and cheerio 0.22.0, except in cheerio the .childNodes
|
|
|
|
// property does not exist on text nodes (and maybe other non-element nodes).
|
2020-11-23 19:24:19 +01:00
|
|
|
if (n.childNodes == null) return 0;
|
2011-03-26 14:10:41 +01:00
|
|
|
return n.childNodes.length;
|
|
|
|
},
|
2021-01-25 00:03:13 +01:00
|
|
|
// Returns the i'th Node child (n.childNodes[i]), not the i'th Element child (n.children[i] in
|
|
|
|
// DOM).
|
2021-01-25 00:20:19 +01:00
|
|
|
childNode: (n, i) => {
|
2020-11-23 19:24:19 +01:00
|
|
|
if (n.childNodes.item == null) {
|
2021-01-25 00:03:13 +01:00
|
|
|
// .childNodes[] works with DOM and cheerio 0.22.0.
|
2014-11-25 18:26:09 +01:00
|
|
|
return n.childNodes[i];
|
|
|
|
}
|
2021-01-25 00:03:13 +01:00
|
|
|
// .childNodes.item() works with DOM but not with cheerio 0.22.0.
|
2011-03-26 14:10:41 +01:00
|
|
|
return n.childNodes.item(i);
|
|
|
|
},
|
2021-01-22 21:41:14 +01:00
|
|
|
nodeProp: (n, p) => n[p],
|
2021-01-25 00:20:19 +01:00
|
|
|
getAttribute: (n, a) => {
|
2021-01-25 00:03:13 +01:00
|
|
|
// .getAttribute() works with DOM but not with cheerio 0.22.0.
|
2020-11-23 19:24:19 +01:00
|
|
|
if (n.getAttribute != null) return n.getAttribute(a);
|
2021-01-25 00:03:13 +01:00
|
|
|
// .attribs[] works with cheerio 0.22.0 but not with DOM.
|
2020-11-23 19:24:19 +01:00
|
|
|
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
|
|
|
},
|
2021-01-25 00:03:13 +01:00
|
|
|
// .innerHTML works with DOM but not with cheerio 0.22.0. Cheerio's Element-like objects have no
|
|
|
|
// equivalent. (Cheerio objects have an .html() method, but that isn't accessible here.)
|
2021-01-25 00:20:19 +01:00
|
|
|
innerHTML: (n) => n.innerHTML,
|
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
|
|
|
};
|
|
|
|
|
2021-01-22 21:41:14 +01:00
|
|
|
hooks.callAll('ccRegisterBlockElements').forEach((element) => {
|
2015-01-27 20:16:36 +01:00
|
|
|
_blockElems[element] = 1;
|
|
|
|
});
|
|
|
|
|
2021-01-25 00:20:19 +01:00
|
|
|
const isBlockElement = (n) => !!_blockElems[dom.tagName(n) || ''];
|
2011-07-07 19:59:34 +02:00
|
|
|
|
2021-01-22 21:41:14 +01:00
|
|
|
const textify = (str) => sanitizeUnicode(
|
|
|
|
str.replace(/(\n | \n)/g, ' ')
|
|
|
|
.replace(/[\n\r ]/g, ' ')
|
|
|
|
.replace(/\xa0/g, ' ')
|
|
|
|
.replace(/\t/g, ' '));
|
2011-07-07 19:59:34 +02:00
|
|
|
|
2021-01-22 21:41:14 +01:00
|
|
|
const getAssoc = (node, name) => dom.nodeProp(node, `_magicdom_${name}`);
|
2011-03-26 14:10:41 +01:00
|
|
|
|
2021-01-22 21:41:14 +01:00
|
|
|
const lines = (() => {
|
2020-11-23 19:24:19 +01:00
|
|
|
const textArray = [];
|
|
|
|
const attribsArray = [];
|
|
|
|
let attribsBuilder = null;
|
|
|
|
const op = Changeset.newOp('+');
|
2021-01-22 21:41:14 +01:00
|
|
|
const self = {
|
|
|
|
length: () => textArray.length,
|
|
|
|
atColumnZero: () => textArray[textArray.length - 1] === '',
|
|
|
|
startNew: () => {
|
2020-11-23 19:24:19 +01:00
|
|
|
textArray.push('');
|
2011-03-26 14:10:41 +01:00
|
|
|
self.flush(true);
|
|
|
|
attribsBuilder = Changeset.smartOpAssembler();
|
|
|
|
},
|
2021-01-22 21:41:14 +01:00
|
|
|
textOfLine: (i) => textArray[i],
|
|
|
|
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);
|
|
|
|
},
|
2021-01-22 21:41:14 +01:00
|
|
|
textLines: () => textArray.slice(),
|
|
|
|
attribLines: () => attribsArray,
|
2011-03-26 14:10:41 +01:00
|
|
|
// call flush only when you're done
|
2021-01-22 21:41:14 +01:00
|
|
|
flush: (withNewline) => {
|
2020-11-23 19:24:19 +01:00
|
|
|
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;
|
2021-01-22 21:41:14 +01:00
|
|
|
})();
|
2020-11-23 19:24:19 +01:00
|
|
|
const cc = {};
|
2011-07-07 19:59:34 +02:00
|
|
|
|
2021-01-22 21:41:14 +01:00
|
|
|
const _ensureColumnZero = (state) => {
|
2020-11-23 19:24:19 +01:00
|
|
|
if (!lines.atColumnZero()) {
|
2011-03-26 14:10:41 +01:00
|
|
|
cc.startNewLine(state);
|
|
|
|
}
|
2021-01-22 21:41:14 +01:00
|
|
|
};
|
2020-11-23 19:24:19 +01:00
|
|
|
let selection, startPoint, endPoint;
|
|
|
|
let selStart = [-1, -1];
|
|
|
|
let selEnd = [-1, -1];
|
2021-01-22 21:41:14 +01:00
|
|
|
const _isEmpty = (node, state) => {
|
2011-03-26 14:10:41 +01:00
|
|
|
// consider clean blank lines pasted in IE to be empty
|
2021-01-25 00:20:19 +01:00
|
|
|
if (dom.numChildNodes(node) === 0) return true;
|
|
|
|
if (dom.numChildNodes(node) === 1 &&
|
2021-01-22 21:41:14 +01:00
|
|
|
getAssoc(node, 'shouldBeEmpty') &&
|
2021-01-25 00:20:19 +01:00
|
|
|
dom.innerHTML(node) === ' ' &&
|
2021-01-22 21:41:14 +01:00
|
|
|
!getAssoc(node, 'unpasted')) {
|
2020-11-23 19:24:19 +01:00
|
|
|
if (state) {
|
2021-01-25 00:20:19 +01:00
|
|
|
const child = dom.childNode(node, 0);
|
2011-03-26 14:10:41 +01:00
|
|
|
_reachPoint(child, 0, state);
|
|
|
|
_reachPoint(child, 1, state);
|
|
|
|
}
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
return false;
|
2021-01-22 21:41:14 +01:00
|
|
|
};
|
2011-07-07 19:59:34 +02:00
|
|
|
|
2021-01-22 21:41:14 +01:00
|
|
|
const _pointHere = (charsAfter, state) => {
|
2020-11-23 19:24:19 +01:00
|
|
|
const ln = lines.length() - 1;
|
|
|
|
let chr = lines.textOfLine(ln).length;
|
2021-01-22 21:41:14 +01:00
|
|
|
if (chr === 0 && Object.keys(state.lineAttributes).length !== 0) {
|
2011-03-26 14:10:41 +01:00
|
|
|
chr += 1; // listMarker
|
|
|
|
}
|
|
|
|
chr += charsAfter;
|
|
|
|
return [ln, chr];
|
2021-01-22 21:41:14 +01:00
|
|
|
};
|
2011-07-07 19:59:34 +02:00
|
|
|
|
2021-01-22 21:41:14 +01:00
|
|
|
const _reachBlockPoint = (nd, idx, state) => {
|
2021-01-25 00:20:19 +01:00
|
|
|
if (!dom.isTextNode(nd)) _reachPoint(nd, idx, state);
|
2021-01-22 21:41:14 +01:00
|
|
|
};
|
2011-07-07 19:59:34 +02:00
|
|
|
|
2021-01-22 21:41:14 +01:00
|
|
|
const _reachPoint = (nd, idx, state) => {
|
|
|
|
if (startPoint && nd === startPoint.node && startPoint.index === idx) {
|
2011-03-26 14:10:41 +01:00
|
|
|
selStart = _pointHere(0, state);
|
|
|
|
}
|
2021-01-22 21:41:14 +01:00
|
|
|
if (endPoint && nd === endPoint.node && endPoint.index === idx) {
|
2011-03-26 14:10:41 +01:00
|
|
|
selEnd = _pointHere(0, state);
|
|
|
|
}
|
2021-01-22 21:41:14 +01:00
|
|
|
};
|
|
|
|
cc.incrementFlag = (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
|
|
|
};
|
2021-01-22 21:41:14 +01:00
|
|
|
cc.decrementFlag = (state, flagName) => {
|
2011-03-26 14:10:41 +01:00
|
|
|
state.flags[flagName]--;
|
2020-11-23 19:24:19 +01:00
|
|
|
};
|
2021-01-22 21:41:14 +01:00
|
|
|
cc.incrementAttrib = (state, attribName) => {
|
2020-11-23 19:24:19 +01:00
|
|
|
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
|
|
|
};
|
2021-01-22 21:41:14 +01:00
|
|
|
cc.decrementAttrib = (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
|
|
|
|
2021-01-22 21:41:14 +01:00
|
|
|
const _enterList = (state, listType) => {
|
2020-11-23 19:24:19 +01:00
|
|
|
if (!listType) return;
|
|
|
|
const oldListType = state.lineAttributes.list;
|
2021-01-22 21:41:14 +01:00
|
|
|
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;
|
2021-01-22 21:41:14 +01:00
|
|
|
};
|
2011-07-07 19:59:34 +02:00
|
|
|
|
2021-01-22 21:41:14 +01:00
|
|
|
const _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--;
|
|
|
|
}
|
2021-01-22 21:41:14 +01: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);
|
2021-01-22 21:41:14 +01:00
|
|
|
};
|
2011-07-07 19:59:34 +02:00
|
|
|
|
2021-01-22 21:41:14 +01:00
|
|
|
const _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;
|
2021-01-22 21:41:14 +01:00
|
|
|
};
|
2011-07-07 19:59:34 +02:00
|
|
|
|
2021-01-22 21:41:14 +01:00
|
|
|
const _exitAuthor = (state, oldAuthor) => {
|
2011-03-26 14:10:41 +01:00
|
|
|
state.authorLevel--;
|
|
|
|
state.author = oldAuthor;
|
|
|
|
_recalcAttribString(state);
|
2021-01-22 21:41:14 +01:00
|
|
|
};
|
2011-07-07 19:59:34 +02:00
|
|
|
|
2021-01-22 21:41:14 +01:00
|
|
|
const _recalcAttribString = (state) => {
|
2020-11-23 19:24:19 +01:00
|
|
|
const lst = [];
|
2021-01-21 05:50:33 +01:00
|
|
|
for (const [a, count] of Object.entries(state.attribs)) {
|
2021-01-21 08:35:44 +01:00
|
|
|
if (!count) continue;
|
|
|
|
// 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
|
|
|
|
const ATTRIBUTE_SPLIT_STRING = '::';
|
2019-04-16 00:34:29 +02:00
|
|
|
|
2021-01-21 08:35:44 +01:00
|
|
|
// see if attributeString is splittable
|
|
|
|
const attributeSplits = a.split(ATTRIBUTE_SPLIT_STRING);
|
|
|
|
if (attributeSplits.length > 1) {
|
|
|
|
// the attribute name follows the convention key::value
|
|
|
|
// so save it as a key value attribute
|
|
|
|
lst.push([attributeSplits[0], attributeSplits[1]]);
|
|
|
|
} else {
|
|
|
|
// the "normal" case, the attribute is just a switch
|
|
|
|
// so set it true
|
|
|
|
lst.push([a, 'true']);
|
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);
|
2021-01-22 21:41:14 +01:00
|
|
|
};
|
2011-07-07 19:59:34 +02:00
|
|
|
|
2021-01-22 21:41:14 +01:00
|
|
|
const _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'],
|
2021-01-22 21:41:14 +01:00
|
|
|
...Object.entries(state.lineAttributes),
|
|
|
|
];
|
2020-11-23 19:24:19 +01:00
|
|
|
lines.appendText('*', Changeset.makeAttribsString('+', attributes, apool));
|
2021-01-22 21:41:14 +01:00
|
|
|
};
|
|
|
|
cc.startNewLine = (state) => {
|
2020-11-23 19:24:19 +01:00
|
|
|
if (state) {
|
2021-01-22 21:41:14 +01:00
|
|
|
const atBeginningOfLine = lines.textOfLine(lines.length() - 1).length === 0;
|
|
|
|
if (atBeginningOfLine && Object.keys(state.lineAttributes).length !== 0) {
|
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
|
|
|
};
|
2021-01-22 21:41:14 +01:00
|
|
|
cc.notifySelection = (sel) => {
|
2020-11-23 19:24:19 +01:00
|
|
|
if (sel) {
|
2011-03-26 14:10:41 +01:00
|
|
|
selection = sel;
|
|
|
|
startPoint = selection.startPoint;
|
|
|
|
endPoint = selection.endPoint;
|
|
|
|
}
|
|
|
|
};
|
2021-01-22 21:41:14 +01:00
|
|
|
cc.doAttrib = (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);
|
2021-01-22 21:41:14 +01:00
|
|
|
|
2021-01-25 00:20:19 +01:00
|
|
|
if (dom.isTextNode(node)) {
|
|
|
|
const tname = dom.getAttribute(node.parentNode, 'name');
|
2021-01-22 08:06:06 +01:00
|
|
|
const context = {cc: this, state, tname, node, text: dom.nodeValue(node)};
|
|
|
|
// Hook functions may either return a string (deprecated) or modify context.text. If any hook
|
|
|
|
// function modifies context.text then all returned strings are ignored. If no hook functions
|
|
|
|
// modify context.text, the first hook function to return a string wins.
|
|
|
|
const [hookTxt] =
|
|
|
|
hooks.callAll('collectContentLineText', context).filter((s) => typeof s === 'string');
|
|
|
|
let txt = context.text === dom.nodeValue(node) && hookTxt != null ? hookTxt : context.text;
|
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
|
2021-01-22 21:41:14 +01:00
|
|
|
if (txt.length === 0) {
|
|
|
|
if (startPoint && node === startPoint.node) {
|
2011-03-26 14:10:41 +01:00
|
|
|
selStart = _pointHere(0, state);
|
|
|
|
}
|
2021-01-22 21:41:14 +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
|
|
|
}
|
2021-01-22 21:41:14 +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);
|
|
|
|
}
|
2021-01-22 21:41:14 +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
|
|
|
}
|
2021-01-22 21:41:14 +01:00
|
|
|
const atBeginningOfLine = lines.textOfLine(lines.length() - 1).length === 0;
|
2020-11-23 19:24:19 +01:00
|
|
|
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*/, '');
|
|
|
|
}
|
2021-01-22 21:41:14 +01:00
|
|
|
if (atBeginningOfLine && Object.keys(state.lineAttributes).length !== 0) {
|
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);
|
|
|
|
}
|
|
|
|
}
|
2021-01-24 08:07:33 +01:00
|
|
|
} else if (dom.isElementNode(node)) {
|
2021-01-25 00:20:19 +01:00
|
|
|
const tname = dom.tagName(node) || '';
|
2020-11-23 19:24:19 +01:00
|
|
|
|
2021-01-22 21:41:14 +01:00
|
|
|
if (tname === 'img') {
|
|
|
|
hooks.callAll('collectContentImage', {
|
2020-11-23 19:24:19 +01:00
|
|
|
cc,
|
|
|
|
state,
|
|
|
|
tname,
|
2021-01-22 21:41:14 +01:00
|
|
|
styl: null,
|
|
|
|
cls: null,
|
2020-11-23 19:24:19 +01:00
|
|
|
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
|
|
|
|
2021-01-22 21:41:14 +01:00
|
|
|
if (tname === 'br') {
|
2012-09-08 19:03:13 +02:00
|
|
|
this.breakLine = true;
|
2021-01-25 00:20:19 +01:00
|
|
|
const tvalue = dom.getAttribute(node, 'value');
|
2021-01-21 06:06:43 +01:00
|
|
|
const [startNewLine = true] = 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
|
|
|
if (startNewLine) {
|
2012-09-08 19:03:13 +02:00
|
|
|
cc.startNewLine(state);
|
2013-12-05 08:41:29 +01:00
|
|
|
}
|
2021-01-22 21:41:14 +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) {
|
2021-01-25 00:20:19 +01:00
|
|
|
let styl = dom.getAttribute(node, 'style');
|
|
|
|
let cls = dom.getAttribute(node, 'class');
|
2021-01-22 21:41:14 +01:00
|
|
|
let isPre = (tname === 'pre');
|
|
|
|
if ((!isPre) && abrowser && 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;
|
|
|
|
|
2021-01-22 21:41:14 +01:00
|
|
|
// We have to return here but this could break things in the future,
|
|
|
|
// for now it shows how to fix the problem
|
2020-03-29 14:09:33 +02:00
|
|
|
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
|
|
|
});
|
2021-01-22 21:41:14 +01:00
|
|
|
if (tname === 'b' ||
|
|
|
|
(styl && /\bfont-weight:\s*bold\b/i.exec(styl)) ||
|
|
|
|
tname === 'strong') {
|
2020-11-23 19:24:19 +01:00
|
|
|
cc.doAttrib(state, 'bold');
|
2011-03-26 14:10:41 +01:00
|
|
|
}
|
2021-01-22 21:41:14 +01:00
|
|
|
if (tname === 'i' ||
|
|
|
|
(styl && /\bfont-style:\s*italic\b/i.exec(styl)) ||
|
|
|
|
tname === 'em') {
|
2020-11-23 19:24:19 +01:00
|
|
|
cc.doAttrib(state, 'italic');
|
2011-03-26 14:10:41 +01:00
|
|
|
}
|
2021-01-22 21:41:14 +01:00
|
|
|
if (tname === 'u' ||
|
|
|
|
(styl && /\btext-decoration:\s*underline\b/i.exec(styl)) ||
|
|
|
|
tname === 'ins') {
|
2020-11-23 19:24:19 +01:00
|
|
|
cc.doAttrib(state, 'underline');
|
2011-03-26 14:10:41 +01:00
|
|
|
}
|
2021-01-22 21:41:14 +01:00
|
|
|
if (tname === 's' ||
|
|
|
|
(styl && /\btext-decoration:\s*line-through\b/i.exec(styl)) ||
|
|
|
|
tname === 'del') {
|
2020-11-23 19:24:19 +01:00
|
|
|
cc.doAttrib(state, 'strikethrough');
|
2011-03-26 14:10:41 +01:00
|
|
|
}
|
2021-01-22 21:41:14 +01:00
|
|
|
if (tname === 'ul' || tname === 'ol') {
|
2021-01-25 00:20:19 +01:00
|
|
|
let type = dom.getAttribute(node, 'class');
|
2020-11-23 19:24:19 +01:00
|
|
|
const rr = cls && /(?:^| )list-([a-z]+[0-9]+)\b/.exec(cls);
|
2021-01-22 21:41:14 +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) {
|
2021-01-25 00:50:50 +01:00
|
|
|
for (let i = 0; i < dom.numChildNodes(node); i++) {
|
|
|
|
const child = dom.childNode(node, i);
|
2021-01-21 08:46:22 +01:00
|
|
|
if (dom.tagName(child) !== 'ul') continue;
|
2021-01-21 08:35:44 +01:00
|
|
|
type = dom.getAttribute(child, '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 {
|
2021-01-22 21:41:14 +01:00
|
|
|
if (tname === 'ul') {
|
2021-01-25 00:20:19 +01:00
|
|
|
const cls = dom.getAttribute(node, 'class');
|
2021-01-21 06:30:55 +01:00
|
|
|
if ((type && type.match('indent')) || (cls && cls.match('indent'))) {
|
2020-11-23 19:24:19 +01:00
|
|
|
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');
|
2021-01-22 21:41:14 +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
|
2021-01-22 21:41:14 +01:00
|
|
|
if (!abrowser.chrome) oldListTypeOrNull = (_enterList(state, undefined) || 'none');
|
2021-01-25 01:27:00 +01:00
|
|
|
} else if (tname === 'li') {
|
2020-11-23 19:24:19 +01:00
|
|
|
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
|
|
|
|
*/
|
2021-01-21 08:46:22 +01:00
|
|
|
if (node.parentNode && dom.tagName(node.parentNode) !== '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.
|
2021-01-21 08:46:22 +01:00
|
|
|
if (node.parentNode && dom.tagName(node.parentNode) === '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) {
|
2021-01-22 21:41:14 +01:00
|
|
|
for (let i = 0; i < classes.length; i++) {
|
|
|
|
const 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;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2021-01-25 00:20:19 +01:00
|
|
|
const nc = dom.numChildNodes(node);
|
2021-01-22 21:41:14 +01:00
|
|
|
for (let i = 0; i < nc; i++) {
|
2021-01-25 00:20:19 +01:00
|
|
|
const c = dom.childNode(node, i);
|
2011-03-26 14:10:41 +01:00
|
|
|
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) {
|
2021-01-22 21:41:14 +01:00
|
|
|
for (let 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-12-19 00:13:02 +01:00
|
|
|
_reachBlockPoint(node, 1, state);
|
2020-11-23 19:24:19 +01:00
|
|
|
if (isBlock) {
|
2021-01-22 21:41:14 +01:00
|
|
|
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);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
state.localAttribs = localAttribs;
|
|
|
|
};
|
|
|
|
// can pass a falsy value for end of doc
|
2021-01-22 21:41:14 +01:00
|
|
|
cc.notifyNextNode = (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]
|
2021-01-22 21:41:14 +01:00
|
|
|
const getSelectionStart = () => selStart;
|
|
|
|
const getSelectionEnd = () => 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
|
2021-01-22 21:41:14 +01:00
|
|
|
cc.getLines = () => lines.textLines();
|
2011-03-26 14:10:41 +01:00
|
|
|
|
2021-01-22 21:41:14 +01:00
|
|
|
cc.finish = () => {
|
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
|
|
|
|
2021-01-22 21:41:14 +01:00
|
|
|
const 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;
|
2021-01-22 21:41:14 +01:00
|
|
|
for (let i = lineStrings.length - 1; i >= 0; i--) {
|
2020-11-23 19:24:19 +01:00
|
|
|
let oldString = lineStrings[i];
|
|
|
|
let oldAttribString = lineAttribs[i];
|
|
|
|
if (oldString.length > lineLimit + buffer) {
|
2021-01-22 21:41:14 +01:00
|
|
|
const 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);
|
|
|
|
}
|
|
|
|
|
2021-01-22 21:41:14 +01:00
|
|
|
const 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);
|
2021-01-22 21:41:14 +01:00
|
|
|
} else if (n === i) {
|
2020-11-23 19:24:19 +01:00
|
|
|
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;
|
2021-01-22 21:41:14 +01:00
|
|
|
};
|
2011-07-07 19:59:34 +02:00
|
|
|
fixLineNumber(ss);
|
|
|
|
fixLineNumber(se);
|
|
|
|
linesWrapped++;
|
|
|
|
numLinesAfter += newStrings.length;
|
2021-01-22 21:41:14 +01:00
|
|
|
lineStrings.splice(i, 1, ...newStrings);
|
|
|
|
lineAttribs.splice(i, 1, ...newAttribStrings);
|
2011-07-07 19:59:34 +02:00
|
|
|
}
|
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
|
|
|
};
|
2021-01-22 21:41:14 +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;
|
2021-01-22 21:41:14 +01:00
|
|
|
};
|
2012-01-16 02:23:48 +01:00
|
|
|
|
|
|
|
exports.sanitizeUnicode = sanitizeUnicode;
|
|
|
|
exports.makeContentCollector = makeContentCollector;
|