import cledit from './cleditCore';
const styleElts = [];
function createStyleSheet(document) {
const styleElt = document.createElement('style');
styleElt.type = 'text/css';
styleElt.innerHTML = '.cledit-section * { display: inline; }';
document.head.appendChild(styleElt);
styleElts.push(styleElt);
}
function Highlighter(editor) {
cledit.Utils.createEventHooks(this);
if (!styleElts.cl_some(styleElt => document.head.contains(styleElt))) {
createStyleSheet(document);
}
const contentElt = editor.$contentElt;
this.isComposing = 0;
let sectionList = [];
let insertBeforeSection;
const useBr = cledit.Utils.isWebkit;
const trailingNodeTag = 'div';
const hiddenLfInnerHtml = '
\n';
const lfHtml = `${useBr ? hiddenLfInnerHtml : '\n'}`;
this.fixContent = (modifiedSections, removedSections, noContentFix) => {
modifiedSections.cl_each((section) => {
section.forceHighlighting = true;
if (!noContentFix) {
if (useBr) {
section.elt.getElementsByClassName('hd-lf')
.cl_each(lfElt => lfElt.parentNode.removeChild(lfElt));
section.elt.getElementsByTagName('br')
.cl_each(brElt => brElt.parentNode.replaceChild(document.createTextNode('\n'), brElt));
}
if (section.elt.textContent.slice(-1) !== '\n') {
section.elt.appendChild(document.createTextNode('\n'));
}
}
});
};
this.addTrailingNode = () => {
this.trailingNode = document.createElement(trailingNodeTag);
contentElt.appendChild(this.trailingNode);
};
class Section {
constructor(text) {
this.text = text.text === undefined ? text : text.text;
this.data = text.data;
}
setElement(elt) {
this.elt = elt;
elt.section = this;
}
}
this.parseSections = (content, isInit) => {
if (this.isComposing && !this.cancelComposition) {
return sectionList;
}
this.cancelComposition = false;
const newSectionList = (editor.options.sectionParser
? editor.options.sectionParser(content)
: [content])
.cl_map(sectionText => new Section(sectionText));
let modifiedSections = [];
let sectionsToRemove = [];
insertBeforeSection = undefined;
if (isInit) {
// Render everything if isInit
sectionsToRemove = sectionList;
sectionList = newSectionList;
modifiedSections = newSectionList;
} else {
// Find modified section starting from top
let leftIndex = sectionList.length;
sectionList.cl_some((section, index) => {
const newSection = newSectionList[index];
if (index >= newSectionList.length ||
section.forceHighlighting ||
// Check text modification
section.text !== newSection.text ||
// Check that section has not been detached or moved
section.elt.parentNode !== contentElt ||
// Check also the content since nodes can be injected in sections via copy/paste
section.elt.textContent !== newSection.text
) {
leftIndex = index;
return true;
}
return false;
});
// Find modified section starting from bottom
let rightIndex = -sectionList.length;
sectionList.slice().reverse().cl_some((section, index) => {
const newSection = newSectionList[newSectionList.length - index - 1];
if (index >= newSectionList.length ||
section.forceHighlighting ||
// Check modified
section.text !== newSection.text ||
// Check that section has not been detached or moved
section.elt.parentNode !== contentElt ||
// Check also the content since nodes can be injected in sections via copy/paste
section.elt.textContent !== newSection.text
) {
rightIndex = -index;
return true;
}
return false;
});
if (leftIndex - rightIndex > sectionList.length) {
// Prevent overlap
rightIndex = leftIndex - sectionList.length;
}
const leftSections = sectionList.slice(0, leftIndex);
modifiedSections = newSectionList.slice(leftIndex, newSectionList.length + rightIndex);
const rightSections = sectionList.slice(sectionList.length + rightIndex, sectionList.length);
[insertBeforeSection] = rightSections;
sectionsToRemove = sectionList.slice(leftIndex, sectionList.length + rightIndex);
sectionList = leftSections.concat(modifiedSections).concat(rightSections);
}
const highlight = (section) => {
const html = editor.options.sectionHighlighter(section).replace(/\n/g, lfHtml);
const sectionElt = document.createElement('div');
sectionElt.className = 'cledit-section';
sectionElt.innerHTML = html;
section.setElement(sectionElt);
this.$trigger('sectionHighlighted', section);
};
const newSectionEltList = document.createDocumentFragment();
modifiedSections.cl_each((section) => {
section.forceHighlighting = false;
highlight(section);
newSectionEltList.appendChild(section.elt);
});
editor.watcher.noWatch(() => {
if (isInit) {
contentElt.innerHTML = '';
contentElt.appendChild(newSectionEltList);
this.addTrailingNode();
return;
}
// Remove outdated sections
sectionsToRemove.cl_each((section) => {
// section may be already removed
if (section.elt.parentNode === contentElt) {
contentElt.removeChild(section.elt);
}
// To detect sections that come back with built-in undo
section.elt.section = undefined;
});
if (insertBeforeSection !== undefined) {
contentElt.insertBefore(newSectionEltList, insertBeforeSection.elt);
} else {
contentElt.appendChild(newSectionEltList);
}
// Remove unauthorized nodes (text nodes outside of sections or
// duplicated sections via copy/paste)
let childNode = contentElt.firstChild;
while (childNode) {
const nextNode = childNode.nextSibling;
if (!childNode.section) {
contentElt.removeChild(childNode);
}
childNode = nextNode;
}
this.addTrailingNode();
this.$trigger('highlighted');
if (editor.selectionMgr.hasFocus()) {
editor.selectionMgr.restoreSelection();
editor.selectionMgr.updateCursorCoordinates();
}
});
return sectionList;
};
}
cledit.Highlighter = Highlighter;