/* Copyright © 2011-2015 by Neil Jenkins. MIT Licensed. */
/* eslint max-len: 0 */
/**
TODO: modifyBlocks function doesn't work very good.
For example you have: UL > LI > [cursor here in text]
Then create blockquote at cursor, the result is: BLOCKQUOTE > UL > LI
not UL > LI > BLOCKQUOTE
*/
(() => {
// source/node/TreeIterator.ts
var SHOW_ELEMENT = 1;
var SHOW_TEXT = 4;
var SHOW_ELEMENT_OR_TEXT = 5;
var filterAccept = NodeFilter.FILTER_ACCEPT;
TreeWalker.prototype.previousPONode = function() {
let current = this.currentNode;
let node = current.lastChild;
while (!node && current) {
if (current === this.root) {
break;
}
node = this.previousSibling();
if (!node) {
current = this.parentNode();
}
}
node && (this.currentNode = node);
return node;
};
var createTreeWalker = (root, whatToShow, filter) => document.createTreeWalker(root, whatToShow, filter ? {
acceptNode: (node) => filter(node) ? filterAccept : NodeFilter.FILTER_SKIP
} : null);
// source/Constants.ts
var ELEMENT_NODE = 1;
var TEXT_NODE = 3;
var DOCUMENT_FRAGMENT_NODE = 11;
var ZWS = "\u200B";
var ua = navigator.userAgent;
var isMac = /Mac OS X/.test(ua);
var isIOS = /iP(?:ad|hone)/.test(ua) || (isMac && !!navigator.maxTouchPoints);
var isAndroid = /Android/.test(ua);
var isWebKit = /WebKit\//.test(ua);
var ctrlKey = isMac || isIOS ? "Meta-" : "Ctrl-";
var cantFocusEmptyTextNodes = isWebKit;
var notWS = /[^ \t\r\n]/;
var indexOf = (array, value) => Array.prototype.indexOf.call(array, value);
// source/node/Category.ts
var inlineNodeNames = /^(?:#text|A|ABBR|ACRONYM|B|BR|BD[IO]|CITE|CODE|DATA|DEL|DFN|EM|FONT|HR|I|IMG|INPUT|INS|KBD|Q|RP|RT|RUBY|S|SAMP|SMALL|SPAN|STR(IKE|ONG)|SU[BP]|TIME|U|VAR|WBR)$/;
var leafNodeNames = new Set(["BR", "HR", "IMG"]);
var listNodeNames = new Set(["OL", "UL"]);
var UNKNOWN = 0;
var INLINE = 1;
var BLOCK = 2;
var CONTAINER = 3;
var cache = new WeakMap();
var isLeaf = (node) => isElement(node) && leafNodeNames.has(node.nodeName);
var getNodeCategory = (node) => {
switch (node.nodeType) {
case TEXT_NODE:
return INLINE;
case ELEMENT_NODE:
case DOCUMENT_FRAGMENT_NODE:
if (cache.has(node)) {
return cache.get(node);
}
break;
default:
return UNKNOWN;
}
let nodeCategory = Array.prototype.every.call(node.childNodes, isInline) ? (inlineNodeNames.test(node.nodeName) ? INLINE : BLOCK) : CONTAINER;
cache.set(node, nodeCategory);
return nodeCategory;
};
var isInline = (node) => getNodeCategory(node) === INLINE;
var isBlock = (node) => getNodeCategory(node) === BLOCK;
var isContainer = (node) => getNodeCategory(node) === CONTAINER;
// source/node/Node.ts
var createElement = (tag, props, children) => {
const el = document.createElement(tag);
if (props instanceof Array) {
children = props;
props = null;
}
setAttributes(el, props);
children && el.append(...children);
return el;
};
var areAlike = (node, node2) => !isLeaf(node) && (node.nodeType === node2.nodeType && node.nodeName === node2.nodeName && node.nodeName !== "A" && node.className === node2.className && node.style?.cssText === node2.style?.cssText);
var hasTagAttributes = (node, tag, attributes) => node.nodeName === tag && Object.entries(attributes || {}).every(([k,v]) => node.getAttribute(k) === v);
var getNearest = (node, root, tag, attributes) => {
while (node && node !== root) {
if (hasTagAttributes(node, tag, attributes)) {
return node;
}
node = node.parentNode;
}
return null;
};
var getNodeBeforeOffset = (node, offset) => {
let children = node.childNodes;
while (offset && isElement(node)) {
node = children[offset - 1];
children = node.childNodes;
offset = children.length;
}
return node;
};
var getNodeAfterOffset = (node, offset) => {
if (isElement(node)) {
const children = node.childNodes;
if (offset < children.length) {
node = children[offset];
} else {
while (node && !node.nextSibling) {
node = node.parentNode;
}
node && (node = node.nextSibling);
}
}
return node;
};
var getLength = (node) => isElement(node) || node instanceof DocumentFragment ? node.childNodes.length : node.length || 0;
var empty = (node) => {
const frag = document.createDocumentFragment(), childNodes = node.childNodes;
childNodes && frag.append(...childNodes);
return frag;
};
var detach = (node) => node.parentNode?.removeChild(node);
var replaceWith = (node, node2) => node.parentNode?.replaceChild(node2, node);
var getClosest = (node, root, selector) => {
node = (node && !node.closest) ? node.parentElement : node;
node = node?.closest(selector);
return (node && root.contains(node)) ? node : null;
};
var isElement = (node) => node instanceof Element;
var isTextNode = (node) => node instanceof Text;
var isBrElement = (node) => "BR" === node?.nodeName;
var setAttributes = (node, props) => {
props && Object.entries(props).forEach(([k, v]) => {
if (null == v) {
node.removeAttribute(k);
} else if ("style" === k && typeof v === "object") {
Object.entries(v).forEach(([k2, v2]) => node.style[k2] = v2);
} else {
node.setAttribute(k, v);
}
});
};
// source/node/Whitespace.ts
var notWSTextNode = (node) => isElement(node) ? isBrElement(node) : notWS.test(node.data);
var isLineBreak = (br, isLBIfEmptyBlock) => {
let block = br.parentNode;
while (isInline(block)) {
block = block.parentNode;
}
const walker = createTreeWalker(
block,
SHOW_ELEMENT_OR_TEXT,
notWSTextNode
);
walker.currentNode = br;
return !!walker.nextNode() || (isLBIfEmptyBlock && !walker.previousNode());
};
var removeZWS = (root, keepNode) => {
const walker = createTreeWalker(root, SHOW_TEXT);
let textNode;
let index;
while (textNode = walker.nextNode()) {
while ((index = textNode.data.indexOf(ZWS)) > -1 && (!keepNode || textNode.parentNode !== keepNode)) {
if (textNode.length === 1) {
do {
let parent = textNode.parentNode;
textNode.remove();
textNode = parent;
walker.currentNode = parent;
} while (isInline(textNode) && !getLength(textNode));
break;
} else {
textNode.deleteData(index, 1);
}
}
}
};
// source/range/Boundaries.ts
var START_TO_START = 0;
var START_TO_END = 1;
var END_TO_END = 2;
var END_TO_START = 3;
var isNodeContainedInRange = (range, node, partial = true) => {
const nodeRange = document.createRange();
nodeRange.selectNode(node);
return partial
? range.compareBoundaryPoints(END_TO_START, nodeRange) < 0
&& range.compareBoundaryPoints(START_TO_END, nodeRange) > 0
: range.compareBoundaryPoints(START_TO_START, nodeRange) < 1
&& range.compareBoundaryPoints(END_TO_END, nodeRange) > -1;
};
var moveRangeBoundariesDownTree = range => {
let { startContainer, startOffset, endContainer, endOffset } = range;
while (!isTextNode(startContainer)) {
let child = startContainer.childNodes[startOffset];
if (!child || isLeaf(child)) {
if (startOffset) {
child = startContainer.childNodes[startOffset - 1];
if (isTextNode(child)) {
let prev;
while (!child.length && (prev = child.previousSibling) && isTextNode(prev)) {
child.remove();
child = prev;
}
startContainer = child;
startOffset = child.data.length;
}
}
break;
}
startContainer = child;
startOffset = 0;
}
if (endOffset) {
while (!isTextNode(endContainer)) {
const child = endContainer.childNodes[endOffset - 1];
if (!child || isLeaf(child)) {
if (isBrElement(child) && !isLineBreak(child)) {
--endOffset;
continue;
}
break;
}
endContainer = child;
endOffset = getLength(endContainer);
}
} else {
while (!isTextNode(endContainer)) {
const child = endContainer.firstChild;
if (!child || isLeaf(child)) {
break;
}
endContainer = child;
}
}
range.setStart(startContainer, startOffset);
range.setEnd(endContainer, endOffset);
};
var moveRangeBoundariesUpTree = (range, startMax, endMax, root) => {
let startContainer = range.startContainer;
let startOffset = range.startOffset;
let endContainer = range.endContainer;
let endOffset = range.endOffset;
let parent;
if (!startMax) {
startMax = range.commonAncestorContainer;
}
if (!endMax) {
endMax = startMax;
}
while (!startOffset && startContainer !== startMax && startContainer !== root) {
parent = startContainer.parentNode;
startOffset = indexOf(parent.childNodes, startContainer);
startContainer = parent;
}
while (endContainer !== endMax && endContainer !== root) {
if (!isTextNode(endContainer) && isBrElement(endContainer.childNodes[endOffset]) && !isLineBreak(endContainer.childNodes[endOffset])) {
++endOffset;
}
if (endOffset !== getLength(endContainer)) {
break;
}
parent = endContainer.parentNode;
endOffset = indexOf(parent.childNodes, endContainer) + 1;
endContainer = parent;
}
range.setStart(startContainer, startOffset);
range.setEnd(endContainer, endOffset);
};
var moveRangeBoundaryOutOf = (range, tag, root) => {
let parent = getClosest(range.endContainer, root, tag);
if (parent && (parent = parent.parentNode)) {
const clone = range.cloneRange();
moveRangeBoundariesUpTree(clone, parent, parent, root);
if (clone.endContainer === parent) {
range.setStart(clone.endContainer, clone.endOffset);
range.setEnd(clone.endContainer, clone.endOffset);
}
}
return range;
};
// source/node/MergeSplit.ts
var fixCursor = (node) => {
let fixer = null;
if (!isTextNode(node)) {
if (isInline(node)) {
let child = node.firstChild;
if (cantFocusEmptyTextNodes) {
while (isTextNode(child) && !child.data) {
node.removeChild(child);
child = node.firstChild;
}
}
if (!child) {
fixer = document.createTextNode(cantFocusEmptyTextNodes ? ZWS : "");
}
} else if (isElement(node) && !node.querySelector("BR")) {
fixer = createElement("BR");
}
if (fixer) {
try {
node.appendChild(fixer);
} catch (error) {
didError({
name: 'Squire: fixCursor – ' + error,
message: 'Parent: ' + node.nodeName + '/' + node.innerHTML +
' appendChild: ' + fixer.nodeName
});
}
}
}
return node;
};
var fixContainer = (container, root) => {
let wrapper;
[...container.childNodes].forEach((child) => {
const isBR = isBrElement(child);
if (!isBR && child.parentNode == root && isInline(child)) {
wrapper = wrapper || createElement("DIV");
wrapper.append(child);
} else if (isBR || wrapper) {
wrapper = wrapper || createElement("DIV");
fixCursor(wrapper);
child[isBR ? "replaceWith" : "before"](wrapper);
wrapper = null;
}
isContainer(child) && fixContainer(child, root);
});
wrapper && container.append(fixCursor(wrapper));
return container;
};
var split = (node, offset, stopNode, root) => {
if (isTextNode(node) && node !== stopNode) {
if (typeof offset !== "number") {
throw new Error("Offset must be a number to split text node!");
}
if (!node.parentNode) {
throw new Error("Cannot split text node with no parent!");
}
return split(node.parentNode, node.splitText(offset), stopNode, root);
}
let nodeAfterSplit = typeof offset === "number" ? offset < node.childNodes.length ? node.childNodes[offset] : null : offset;
const parent = node.parentNode;
if (!parent || node === stopNode || !isElement(node)) {
return nodeAfterSplit;
}
const clone = node.cloneNode(false);
while (nodeAfterSplit) {
const next = nodeAfterSplit.nextSibling;
clone.append(nodeAfterSplit);
nodeAfterSplit = next;
}
if (node instanceof HTMLOListElement && getClosest(node, root, "BLOCKQUOTE")) {
clone.start = (+node.start || 1) + node.childNodes.length - 1;
}
fixCursor(node);
fixCursor(clone);
node.after(clone);
return split(parent, clone, stopNode, root);
};
var _mergeInlines = (node, fakeRange) => {
const children = node.childNodes;
let l = children.length;
let frags = [];
while (l--) {
const child = children[l];
const prev = l && children[l - 1];
if (prev && isInline(child) && areAlike(child, prev)/* && !leafNodeNames.has(child.nodeName)*/) {
if (fakeRange.startContainer === child) {
fakeRange.startContainer = prev;
fakeRange.startOffset += getLength(prev);
}
if (fakeRange.endContainer === child) {
fakeRange.endContainer = prev;
fakeRange.endOffset += getLength(prev);
}
if (fakeRange.startContainer === node) {
if (fakeRange.startOffset > l) {
--fakeRange.startOffset;
} else if (fakeRange.startOffset === l) {
fakeRange.startContainer = prev;
fakeRange.startOffset = getLength(prev);
}
}
if (fakeRange.endContainer === node) {
if (fakeRange.endOffset > l) {
--fakeRange.endOffset;
} else if (fakeRange.endOffset === l) {
fakeRange.endContainer = prev;
fakeRange.endOffset = getLength(prev);
}
}
detach(child);
if (isTextNode(child)) {
prev.appendData(child.data);
} else {
frags.unshift(empty(child));
}
} else if (isElement(child)) {
child.append(...frags);
frags = [];
_mergeInlines(child, fakeRange);
}
}
};
var mergeInlines = (node, range) => {
node = isTextNode(node) ? node.parentNode : node;
if (isElement(node)) {
const fakeRange = {
startContainer: range.startContainer,
startOffset: range.startOffset,
endContainer: range.endContainer,
endOffset: range.endOffset
};
_mergeInlines(node, fakeRange);
range.setStart(fakeRange.startContainer, fakeRange.startOffset);
range.setEnd(fakeRange.endContainer, fakeRange.endOffset);
}
};
var mergeWithBlock = (block, next, range, root) => {
let container = next;
let parent;
let offset;
while ((parent = container.parentNode) && parent !== root && isElement(parent) && parent.childNodes.length === 1) {
container = parent;
}
detach(container);
offset = block.childNodes.length;
const last = block.lastChild;
if (isBrElement(last)) {
last.remove();
--offset;
}
block.append(empty(next));
range.setStart(block, offset);
range.collapse(true);
mergeInlines(block, range);
};
var mergeContainers = (node, root) => {
const prev = node.previousSibling;
const first = node.firstChild;
const isListItem = node.nodeName === "LI";
if (isListItem && (!first || !listNodeNames.has(first.nodeName))) {
return;
}
if (prev && areAlike(prev, node)) {
if (!isContainer(prev)) {
if (!isListItem) {
return;
}
const block = createElement("DIV");
block.append(empty(prev));
prev.append(block);
}
detach(node);
const needsFix = !isContainer(node);
prev.append(empty(node));
needsFix && fixContainer(prev, root);
first && mergeContainers(first, root);
} else if (isListItem) {
const block = createElement("DIV");
node.insertBefore(block, first);
fixCursor(block);
}
};
// source/Clean.ts
var styleToSemantic = {
fontWeight: {
regexp: /^bold|^700/i,
replace: () => createElement("B")
},
fontStyle: {
regexp: /^italic/i,
replace: () => createElement("I")
},
fontFamily: {
regexp: notWS,
replace: (family) => createElement("SPAN", {
style: "font-family:" + family
})
},
fontSize: {
regexp: notWS,
replace: (size) => createElement("SPAN", {
style: "font-size:" + size
})
},
textDecoration: {
regexp: /^underline/i,
replace: () => createElement("U")
}
/*
textDecoration: {
regexp: /^line-through/i,
replace: doc => createElement("S")
}
*/
};
var replaceStyles = (node) => {
const style = node.style;
let newTreeBottom;
let newTreeTop;
Object.entries(styleToSemantic).forEach(([attr, converter]) => {
const css = style[attr];
if (css && converter.regexp.test(css)) {
const el = converter.replace(css);
if (el.nodeName !== node.nodeName || el.className !== node.className) {
if (!newTreeTop) {
newTreeTop = el;
}
if (newTreeBottom) {
newTreeBottom.append(el);
}
newTreeBottom = el;
node.style.removeProperty(attr);
}
}
});
if (newTreeTop && newTreeBottom) {
newTreeBottom.append(empty(node));
if (node.style.cssText) {
node.append(newTreeTop);
} else {
replaceWith(node, newTreeTop);
}
}
return newTreeBottom || node;
};
var replaceWithTag = (tag) => (node) => {
const el = createElement(tag);
Array.prototype.forEach.call(node.attributes, (attr) => el.setAttribute(attr.name, attr.value));
replaceWith(node, el);
el.append(empty(node));
return el;
};
var fontSizes = {
1: "x-small",
2: "small",
3: "medium",
4: "large",
5: "x-large",
6: "xx-large",
7: "xxx-large",
"-1": "smaller",
"+1": "larger"
};
var stylesRewriters = {
STRONG: replaceWithTag("B"),
EM: replaceWithTag("I"),
INS: replaceWithTag("U"),
STRIKE: replaceWithTag("S"),
SPAN: replaceStyles,
FONT: (node) => {
const face = node.face;
const size = node.size;
let color = node.color;
let newTag = createElement("SPAN");
let css = newTag.style;
newTag.style.cssText = node.style.cssText;
if (face) {
css.fontFamily = face;
}
if (size) {
css.fontSize = fontSizes[size];
}
if (color && /^#?([\dA-F]{3}){1,2}$/i.test(color)) {
if (color.charAt(0) !== "#") {
color = "#" + color;
}
css.color = color;
}
replaceWith(node, newTag);
newTag.append(empty(node));
return newTag;
},
TT: (node) => {
const el = createElement("SPAN", {
style: 'font-family:menlo,consolas,"courier new",monospace'
});
replaceWith(node, el);
el.append(empty(node));
return el;
}
};
var allowedBlock = /^(?:A(?:DDRESS|RTICLE|SIDE|UDIO)|BLOCKQUOTE|CAPTION|D(?:[DLT]|IV)|F(?:IGURE|IGCAPTION|OOTER)|H[1-6]|HEADER|L(?:ABEL|EGEND|I)|O(?:L|UTPUT)|P(?:RE)?|SECTION|T(?:ABLE|BODY|D|FOOT|H|HEAD|R)|COL(?:GROUP)?|UL)$/;
var blacklist = new Set(["HEAD", "META", "STYLE"]);
var cleanTree = (node, preserveWS) => {
const children = node.childNodes;
let nonInlineParent = node;
while (isInline(nonInlineParent)) {
nonInlineParent = nonInlineParent.parentNode;
}
/*
const walker = createTreeWalker(
nonInlineParent,
SHOW_ELEMENT_OR_TEXT
);
*/
let i = children.length;
while (i--) {
let child = children[i];
const nodeName = child.nodeName;
if (isElement(child)) {
const childLength = child.childNodes.length;
if (stylesRewriters[nodeName]) {
child = stylesRewriters[nodeName](child);
} else if (blacklist.has(nodeName)) {
child.remove();
continue;
} else if (!allowedBlock.test(nodeName) && !isInline(child)) {
i += childLength;
replaceWith(child, empty(child));
continue;
}
if (childLength) {
cleanTree(child, preserveWS || (nodeName === "PRE"));
}
/*
} else {
if (isTextNode(child)) {
let data = child.data;
const startsWithWS = !notWS.test(data.charAt(0));
const endsWithWS = !notWS.test(data.charAt(data.length - 1));
if (preserveWS || (!startsWithWS && !endsWithWS)) {
continue;
}
if (startsWithWS) {
walker.currentNode = child;
let sibling;
while (sibling = walker.previousPONode()) {
if (sibling.nodeName === "IMG" || isTextNode(sibling) && notWS.test(sibling.data)) {
break;
}
if (!isInline(sibling)) {
sibling = null;
break;
}
}
data = data.replace(/^[ \t\r\n]+/g, sibling ? " " : "");
}
if (endsWithWS) {
walker.currentNode = child;
let sibling;
while (sibling = walker.nextNode()) {
if (sibling.nodeName === "IMG" || (isTextNode(sibling) && notWS.test(sibling.data))) {
break;
}
if (!isInline(sibling)) {
sibling = null;
break;
}
}
data = data.replace(/[ \t\r\n]+$/g, sibling ? " " : "");
}
if (data) {
child.data = data;
continue;
}
}
node.removeChild(child);
*/
}
}
return node;
};
var removeEmptyInlines = (node) => {
const children = node.childNodes;
let l = children.length;
while (l--) {
const child = children[l];
if (isElement(child) && !isLeaf(child)) {
removeEmptyInlines(child);
if (!child.firstChild && isInline(child)) {
child.remove();
}
} else if (!child.data && isTextNode(child)) {
node.removeChild(child);
}
}
};
var cleanupBRs = (node, root, keepForBlankLine) => {
const brs = node.querySelectorAll("BR");
let l = brs.length;
while (l--) {
const br = brs[l];
const parent = br.parentNode;
if (parent) {
if (!isLineBreak(br, keepForBlankLine)) {
detach(br);
} else if (!isInline(parent)) {
fixContainer(parent, root);
}
}
}
};
var escapeHTML = (text) => text.replaceAll("&", "&").replaceAll("<", "<").replaceAll(">", ">").replaceAll('"', """);
// source/node/Block.ts
var getBlockWalker = (node, root) => {
const walker = createTreeWalker(root, SHOW_ELEMENT, isBlock);
walker.currentNode = node;
return walker;
};
var getPreviousBlock = (node, root) => {
node = getBlockWalker(node, root).previousNode();
return node !== root ? node : null;
};
var getNextBlock = (node, root) => {
node = getBlockWalker(node, root).nextNode();
return node !== root ? node : null;
};
var isEmptyBlock = (block) => !block.textContent && !block.querySelector("IMG");
// source/range/Block.ts
var getStartBlockOfRange = (range, root) => {
const container = range.startContainer;
let block;
if (isInline(container)) {
block = getPreviousBlock(container, root);
} else if (container !== root && container instanceof HTMLElement && isBlock(container)) {
block = container;
} else {
block = getNextBlock(getNodeBeforeOffset(container, range.startOffset), root);
}
return block && isNodeContainedInRange(range, block) ? block : null;
};
var getEndBlockOfRange = (range, root) => {
const container = range.endContainer;
let block;
if (isInline(container)) {
block = getPreviousBlock(container, root);
} else if (container !== root && container instanceof HTMLElement && isBlock(container)) {
block = container;
} else {
let node = getNodeAfterOffset(container, range.endOffset);
if (!node || !root.contains(node)) {
node = root;
let child;
while (child = node.lastChild) {
node = child;
}
}
block = getPreviousBlock(node, root);
}
return block && isNodeContainedInRange(range, block) ? block : null;
};
var createContentWalker = (root) => createTreeWalker(
root,
SHOW_ELEMENT_OR_TEXT,
(node) => isTextNode(node) ? notWS.test(node.data) : node.nodeName === "IMG"
);
var rangeDoesStartAtBlockBoundary = (range, root) => {
const startContainer = range.startContainer;
const startOffset = range.startOffset;
let nodeAfterCursor;
if (isTextNode(startContainer)) {
let i = startOffset;
while (i--) {
if (startContainer.data.charAt(i) !== ZWS) {
return false;
}
}
nodeAfterCursor = startContainer;
} else {
nodeAfterCursor = getNodeAfterOffset(startContainer, startOffset);
if (!nodeAfterCursor || !root.contains(nodeAfterCursor)) {
nodeAfterCursor = getNodeBeforeOffset(startContainer, startOffset);
if (isTextNode(nodeAfterCursor) && nodeAfterCursor.length) {
return false;
}
}
}
const block = getStartBlockOfRange(range, root);
if (block) {
const contentWalker = createContentWalker(block);
contentWalker.currentNode = nodeAfterCursor;
return !contentWalker.previousNode();
}
};
var rangeDoesEndAtBlockBoundary = (range, root) => {
const endContainer = range.endContainer;
const endOffset = range.endOffset;
let currentNode;
if (isTextNode(endContainer)) {
const text = endContainer.data;
const length = text.length;
for (let i = endOffset; i < length; ++i) {
if (text.charAt(i) !== ZWS) {
return false;
}
}
currentNode = endContainer;
} else {
currentNode = getNodeBeforeOffset(endContainer, endOffset);
}
const block = getEndBlockOfRange(range, root);
if (block) {
const contentWalker = createContentWalker(block);
contentWalker.currentNode = currentNode;
return !contentWalker.nextNode();
}
};
var expandRangeToBlockBoundaries = (range, root) => {
const start = getStartBlockOfRange(range, root);
const end = getEndBlockOfRange(range, root);
if (start && end) {
range.setStart(start, 0);
range.setEnd(end, end.childNodes.length);
}
};
// source/range/InsertDelete.ts
var createRange = (startContainer, startOffset, endContainer, endOffset) => {
const range = document.createRange();
range.setStart(startContainer, startOffset);
if (endContainer && typeof endOffset === "number") {
range.setEnd(endContainer, endOffset);
} else {
range.setEnd(startContainer, startOffset);
}
return range;
};
var insertNodeInRange = (range, node) => {
let { startContainer, startOffset, endContainer, endOffset } = range;
let children;
if (isTextNode(startContainer)) {
const parent = startContainer.parentNode;
children = parent.childNodes;
if (startOffset === startContainer.length) {
startOffset = indexOf(children, startContainer) + 1;
if (range.collapsed) {
endContainer = parent;
endOffset = startOffset;
}
} else {
if (startOffset) {
const afterSplit = startContainer.splitText(startOffset);
if (endContainer === startContainer) {
endOffset -= startOffset;
endContainer = afterSplit;
} else if (endContainer === parent) {
++endOffset;
}
startContainer = afterSplit;
}
startOffset = indexOf(children, startContainer);
}
startContainer = parent;
} else {
children = startContainer.childNodes;
}
const childCount = children.length;
if (startOffset === childCount) {
startContainer.append(node);
} else {
startContainer.insertBefore(node, children[startOffset]);
}
if (startContainer === endContainer) {
endOffset += children.length - childCount;
}
range.setStart(startContainer, startOffset);
range.setEnd(endContainer, endOffset);
};
var extractContentsOfRange = (range, common, root) => {
common = common || range.commonAncestorContainer;
if (isTextNode(common)) {
common = common.parentNode;
}
let endNode = split(range.endContainer, range.endOffset, common, root),
frag = range.extractContents(),
startContainer = common,
startOffset = endNode ? indexOf(common.childNodes, endNode) : common.childNodes.length,
after = common.childNodes[startOffset],
before = after?.previousSibling,
beforeText, afterText;
if (isTextNode(before) && isTextNode(after)) {
startContainer = before;
startOffset = before.length;
beforeText = before.data;
afterText = after.data;
if (beforeText.charAt(beforeText.length - 1) === ' ' && afterText.charAt(0) === ' ') {
afterText = NBSP + afterText.slice(1);
}
before.appendData(afterText);
detach(after);
}
range.setStart(startContainer, startOffset);
range.collapse(true);
fixCursor(common);
return frag;
};
var getAdjacentInlineNode = (iterator, method, node) => {
iterator.currentNode = node;
let nextNode;
while (nextNode = iterator[method]()) {
if (isTextNode(nextNode) || isLeaf(nextNode)) {
return nextNode;
}
if (!isInline(nextNode)) {
return null;
}
}
return null;
};
var deleteContentsOfRange = (range, root) => {
const startBlock = getStartBlockOfRange(range, root);
let endBlock = getEndBlockOfRange(range, root);
const needsMerge = startBlock !== endBlock;
if (startBlock && endBlock) {
moveRangeBoundariesDownTree(range);
moveRangeBoundariesUpTree(range, startBlock, endBlock, root);
}
const frag = extractContentsOfRange(range, null, root);
moveRangeBoundariesDownTree(range);
if (needsMerge) {
endBlock = getEndBlockOfRange(range, root);
if (startBlock && endBlock && startBlock !== endBlock) {
mergeWithBlock(startBlock, endBlock, range, root);
}
}
if (startBlock) {
fixCursor(startBlock);
}
const child = root.firstChild;
if (!child || isBrElement(child)) {
fixCursor(root);
root.firstChild && range.selectNodeContents(root.firstChild);
}
range.collapse(true);
const startContainer = range.startContainer;
const startOffset = range.startOffset;
const iterator = createTreeWalker(root, SHOW_ELEMENT_OR_TEXT);
let afterNode = startContainer;
let afterOffset = startOffset;
if (!isTextNode(afterNode) || afterOffset === afterNode.data.length) {
afterNode = getAdjacentInlineNode(iterator, "nextNode", afterNode);
afterOffset = 0;
}
let beforeNode = startContainer;
let beforeOffset = startOffset - 1;
if (!isTextNode(beforeNode) || beforeOffset === -1) {
beforeNode = getAdjacentInlineNode(
iterator,
"previousPONode",
afterNode || (isTextNode(startContainer) ? startContainer : startContainer.childNodes[startOffset] || startContainer)
);
if (isTextNode(beforeNode)) {
beforeOffset = beforeNode.data.length;
}
}
let node = null;
let offset = 0;
if (isTextNode(afterNode) && afterNode.data.charAt(afterOffset) === " " && rangeDoesStartAtBlockBoundary(range, root)) {
node = afterNode;
offset = afterOffset;
} else if (isTextNode(beforeNode) && beforeNode.data.charAt(beforeOffset) === " ") {
if (isTextNode(afterNode) && afterNode.data.charAt(afterOffset) === " " || rangeDoesEndAtBlockBoundary(range, root)) {
node = beforeNode;
offset = beforeOffset;
}
}
node && node.replaceData(offset, 1, "\xA0");
range.setStart(startContainer, startOffset);
range.collapse(true);
return frag;
};
var insertTreeFragmentIntoRange = (range, frag, root) => {
const firstInFragIsInline = frag.firstChild && isInline(frag.firstChild);
let node, blockContentsAfterSplit;
fixContainer(frag, root);
node = frag;
while (node = getNextBlock(node, root)) {
fixCursor(node);
}
if (!range.collapsed) {
deleteContentsOfRange(range, root);
}
moveRangeBoundariesDownTree(range);
range.collapse();
const stopPoint = getClosest(range.endContainer, root, "BLOCKQUOTE") || root;
let block = getStartBlockOfRange(range, root);
const firstBlockInFrag = getNextBlock(frag, frag);
const replaceBlock = !firstInFragIsInline && !!block && isEmptyBlock(block);
if (block && firstBlockInFrag && !replaceBlock &&
!getClosest(firstBlockInFrag, frag, "PRE,TABLE")) {
moveRangeBoundariesUpTree(range, block, block, root);
range.collapse(true);
let container = range.endContainer;
let offset = range.endOffset;
cleanupBRs(block, root, false);
if (isInline(container)) {
const nodeAfterSplit = split(
container,
offset,
getPreviousBlock(container, root) || root,
root
);
container = nodeAfterSplit.parentNode;
offset = indexOf(container.childNodes, nodeAfterSplit);
}
if (
/*isBlock(container) && */
offset !== getLength(container)
) {
blockContentsAfterSplit = document.createDocumentFragment();
while (node = container.childNodes[offset]) {
blockContentsAfterSplit.append(node);
}
}
mergeWithBlock(container, firstBlockInFrag, range, root);
offset = indexOf(container.parentNode.childNodes, container) + 1;
container = container.parentNode;
range.setEnd(container, offset);
}
if (getLength(frag)) {
if (replaceBlock && block) {
range.setEndBefore(block);
range.collapse();
detach(block);
}
moveRangeBoundariesUpTree(range, stopPoint, stopPoint, root);
let nodeAfterSplit = split(
range.endContainer,
range.endOffset,
stopPoint,
root
);
const nodeBeforeSplit = nodeAfterSplit ? nodeAfterSplit.previousSibling : stopPoint.lastChild;
stopPoint.insertBefore(frag, nodeAfterSplit);
if (nodeAfterSplit) {
range.setEndBefore(nodeAfterSplit);
} else {
range.setEnd(stopPoint, getLength(stopPoint));
}
block = getEndBlockOfRange(range, root);
moveRangeBoundariesDownTree(range);
const container = range.endContainer;
const offset = range.endOffset;
if (nodeAfterSplit && isContainer(nodeAfterSplit)) {
mergeContainers(nodeAfterSplit, root);
}
nodeAfterSplit = nodeBeforeSplit?.nextSibling;
if (nodeAfterSplit && isContainer(nodeAfterSplit)) {
mergeContainers(nodeAfterSplit, root);
}
range.setEnd(container, offset);
}
if (blockContentsAfterSplit && block) {
const tempRange = range.cloneRange();
mergeWithBlock(block, blockContentsAfterSplit, tempRange, root);
range.setEnd(tempRange.endContainer, tempRange.endOffset);
}
moveRangeBoundariesDownTree(range);
};
// source/range/Contents.ts
/*
var getTextContentsOfRange = (range) => {
if (range.collapsed) {
return "";
}
const startContainer = range.startContainer;
const endContainer = range.endContainer;
const filter = (node2) => isNodeContainedInRange(range, node2, true);
const walker = createTreeWalker(
range.commonAncestorContainer,
SHOW_ELEMENT_OR_TEXT,
filter
);
walker.currentNode = startContainer;
let node = startContainer;
let textContent = "";
let addedTextInBlock = false;
let value;
if (!isElement(node) && !isTextNode(node) || !filter(node)) {
node = walker.nextNode();
}
while (node) {
if (isTextNode(node)) {
value = node.data;
if (value && /\S/.test(value)) {
if (node === endContainer) {
value = value.slice(0, range.endOffset);
}
if (node === startContainer) {
value = value.slice(range.startOffset);
}
textContent += value;
addedTextInBlock = true;
}
} else if (isBrElement(node) || addedTextInBlock && !isInline(node)) {
textContent += "\n";
addedTextInBlock = false;
}
node = walker.nextNode();
}
textContent = textContent.replace(/\xA0/g, " ");
return textContent;
};
*/
// source/Clipboard.ts
var extractRangeToClipboard = (event, range, root, cut) => {
if (event.clipboardData) {
let startBlock = getStartBlockOfRange(range, root),
endBlock = getEndBlockOfRange(range, root),
copyRoot = ((startBlock === endBlock) && startBlock) || root,
contents, parent, newContents;
if (cut) {
contents = deleteContentsOfRange(range, root);
} else {
range = range.cloneRange();
moveRangeBoundariesDownTree(range);
moveRangeBoundariesUpTree(range, copyRoot, copyRoot, root);
contents = range.cloneContents();
}
parent = range.commonAncestorContainer;
if (isTextNode(parent)) {
parent = parent.parentNode;
}
while (parent && parent !== copyRoot) {
newContents = parent.cloneNode(false);
newContents.append(contents);
contents = newContents;
parent = parent.parentNode;
}
let clipboardData = event.clipboardData;
let body = document.body;
let node = createElement("div");
let html, text;
node.append(contents);
html = node.innerHTML;
cleanupBRs(node, root, true);
node.setAttribute("style",
'position:fixed;overflow:hidden;bottom:100%;right:100%;');
body.append(node);
text = (node.innerText || node.textContent).replace(NBSP, ' '); // Replace nbsp with regular space
node.remove();
if (text !== html) {
clipboardData.setData("text/html", html);
}
clipboardData.setData("text/plain", text);
event.preventDefault();
return true;
}
};
var _onCut = function(event) {
const range = this.getSelection();
const root = this._root;
if (range.collapsed) {
event.preventDefault();
return;
}
this.saveUndoState(range);
if (!extractRangeToClipboard(event, range, root, true)) {
setTimeout(() => {
try {
this._ensureBottomLine();
} catch (error) {
didError(error);
}
}, 0);
}
this.setSelection(range);
};
var _onCopy = function(event) {
extractRangeToClipboard(
event,
this.getSelection(),
this._root
);
};
var _onPaste = function(event) {
const clipboardData = event.clipboardData;
const items = clipboardData?.items;
let imageItem = null;
let plainItem = null;
let htmlItem = null;
let self = this;
let type;
if (items) {
[...items].forEach(item => {
type = item.type;
if (type === "text/html") {
htmlItem = item;
} else if (type === "text/plain" || type === "text/uri-list") {
plainItem = item;
} else if (item.kind === "file" && /^image\/(png|jpeg|webp)/.test(type)) {
imageItem = item;
}
});
if (htmlItem || plainItem || imageItem) {
event.preventDefault();
if (imageItem) {
let reader = new FileReader();
reader.onload = (event) => {
let img = createElement("img", {src: event.target.result}),
canvas = createElement("canvas"),
ctx = canvas.getContext('2d');
img.onload = ()=>{
ctx.drawImage(img, 0, 0);
let width = img.width, height = img.height;
if (width > height) {
if (width > 1024) {
height = height * 1024 / width;
width = 1024;
}
} else if (height > 1024) {
width = width * 1024 / height;
height = 1024;
}
canvas.width = width;
canvas.height = height;
ctx.drawImage(img, 0, 0, width, height);
self.insertHTML('
', true);
};
}
reader.readAsDataURL(imageItem.getAsFile());
} else if (htmlItem && (!self.isShiftDown || !plainItem)) {
htmlItem.getAsString(html => self.insertHTML(html, true));
} else if (plainItem) {
plainItem.getAsString(text => self.insertPlainText(text, true));
}
}
}
};
// source/keyboard/KeyHelpers.ts
var afterDelete = (self, range) => {
try {
range = range || self.getSelection();
let node = range.startContainer;
if (isTextNode(node)) {
node = node.parentNode;
}
let parent = node;
while (isInline(parent) && (!parent.textContent || parent.textContent === ZWS)) {
node = parent;
parent = node.parentNode;
}
if (node !== parent) {
range.setStart(
parent,
indexOf(parent.childNodes, node)
);
range.collapse(true);
node.remove();
if (!isBlock(parent)) {
parent = getPreviousBlock(parent, self._root) || parent;
}
fixCursor(parent);
moveRangeBoundariesDownTree(range);
}
if (node === self._root && (node = node.firstChild) && isBrElement(node)) {
detach(node);
}
self._ensureBottomLine();
self.setRange(range);
} catch (error) {
didError(error);
}
};
// source/keyboard/Backspace.ts
var Backspace = (self, event, range) => {
const root = self._root;
self._removeZWS();
self.saveUndoState(range);
if (!range.collapsed) {
event.preventDefault();
deleteContentsOfRange(range, root);
afterDelete(self, range);
} else if (rangeDoesStartAtBlockBoundary(range, root)) {
event.preventDefault();
let current = getStartBlockOfRange(range, root);
let previous;
if (!current) {
return;
}
fixContainer(current.parentNode, root);
previous = getPreviousBlock(current, root);
if (previous) {
if (!previous.isContentEditable) {
detachUneditableNode(previous, root);
return;
}
mergeWithBlock(previous, current, range, root);
current = previous.parentNode;
while (current !== root && !current.nextSibling) {
current = current.parentNode;
}
if (current !== root && (current = current.nextSibling)) {
mergeContainers(current, root);
}
self.setSelection(range);
} else if (current) {
if (decreaseLevel(self, range, current)) {
return;
}
self.setRange(range);
}
} else {
self.setSelection(range);
setTimeout(() => afterDelete(self), 0);
}
};
// source/keyboard/Delete.ts
var Delete = (self, event, range) => {
const root = self._root;
let current;
let next;
let originalRange;
let cursorContainer;
let cursorOffset;
let nodeAfterCursor;
self._removeZWS();
self.saveUndoState(range);
if (!range.collapsed) {
event.preventDefault();
deleteContentsOfRange(range, root);
afterDelete(self, range);
} else if (rangeDoesEndAtBlockBoundary(range, root)) {
event.preventDefault();
if (current = getStartBlockOfRange(range, root)) {
fixContainer(current.parentNode, root);
if (next = getNextBlock(current, root)) {
if (!next.isContentEditable) {
detachUneditableNode(next, root);
return;
}
mergeWithBlock(current, next, range, root);
next = current.parentNode;
while (next !== root && !next.nextSibling) {
next = next.parentNode;
}
if (next !== root && (next = next.nextSibling)) {
mergeContainers(next, root);
}
self.setRange(range);
}
}
} else {
originalRange = range.cloneRange();
moveRangeBoundariesUpTree(range, root, root, root);
cursorContainer = range.endContainer;
cursorOffset = range.endOffset;
if (isElement(cursorContainer)) {
nodeAfterCursor = cursorContainer.childNodes[cursorOffset];
if (nodeAfterCursor?.nodeName === "IMG") {
event.preventDefault();
detach(nodeAfterCursor);
moveRangeBoundariesDownTree(range);
afterDelete(self, range);
return;
}
}
self.setSelection(originalRange);
setTimeout(() => afterDelete(self), 0);
}
};
// source/keyboard/Tab.ts
var Tab = (self, event, range) => {
self._removeZWS();
range.collapsed
&& rangeDoesStartAtBlockBoundary(range, self._root)
&& getClosest(range.startContainer, self._root, "UL,OL,BLOCKQUOTE")
&& self.changeIndentationLevel("increase")
&& event.preventDefault();
};
var ShiftTab = (self, event, range) => {
self._removeZWS();
range.collapsed
&& rangeDoesStartAtBlockBoundary(range, self._root)
&& decreaseLevel(self, range, range.startContainer)
&& event.preventDefault();
};
// source/keyboard/Space.ts
var Space = (self, event, range) => {
/*
var _a;
let node;
const root = self._root;
self._recordUndoState(range);
self._getRangeAndRemoveBookmark(range);
if (!range.collapsed) {
deleteContentsOfRange(range, root);
self._ensureBottomLine();
self.setSelection(range);
self._updatePath(range, true);
} else if (rangeDoesEndAtBlockBoundary(range, root)) {
const block = getStartBlockOfRange(range, root);
if (block && block.nodeName !== "PRE") {
const text = block.textContent?.trimEnd().replace(ZWS, "");
if (text === "*" || text === "1.") {
event.preventDefault();
const walker = createTreeWalker(block, SHOW_TEXT);
let textNode;
while (textNode = walker.nextNode()) {
textNode.data = cantFocusEmptyTextNodes ? ZWS : "";
}
if (text === "*") {
self.makeUnorderedList();
} else {
self.makeOrderedList();
}
return;
}
}
}
node = range.endContainer;
if (range.endOffset === getLength(node)) {
do {
if (node.nodeName === "A") {
range.setStartAfter(node);
break;
}
} while (!node.nextSibling && (node = node.parentNode) && node !== root);
}
if (self._config.addLinks) {
const linkRange = range.cloneRange();
moveRangeBoundariesDownTree(linkRange);
const textNode = linkRange.startContainer;
const offset = linkRange.startOffset;
setTimeout(() => {
linkifyText(self, textNode, offset);
}, 0);
}
self.setSelection(range);
*/
const root = self._root;
self._recordUndoState(range);
self._config.addLinks && addLinks(range.startContainer, root);
self._getRangeAndRemoveBookmark(range);
/*
let node = range.endContainer;
if (range.collapsed && range.endOffset === getLength(node)) {
do {
if (node.nodeName === "A") {
range.setStartAfter(node);
break;
}
} while (!node.nextSibling && (node = node.parentNode) && node !== root);
}
*/
if (!range.collapsed) {
deleteContentsOfRange(range, root);
self._ensureBottomLine();
}
self.setRange(range);
};
// source/keyboard/KeyHandlers.ts
var _onKey = function(event) {
if (event.defaultPrevented) {
return;
}
let key = event.key,
range = this.getSelection(),
root = this._root;
if (key !== "Backspace" && key !== "Delete") {
if (event.shiftKey) {
key = "Shift-" + key;
}
if (event[osKey]) { key = ctrlKey + key; }
}
if (this._keyHandlers[key]) {
this._keyHandlers[key](this, event, range);
} else if (!range.collapsed && !event.isComposing && !event[osKey] && key.length === 1) {
this.saveUndoState(range);
deleteContentsOfRange(range, root);
this._ensureBottomLine();
this.setRange(range);
} else if (range.collapsed && range.startContainer === root && root.children.length > 0) {
const nextElement = root.children[range.startOffset];
if (nextElement && !isBlock(nextElement)) {
range = createRange(root.insertBefore(
this.createDefaultBlock(), nextElement
), 0);
if (isBrElement(nextElement)) {
root.removeChild(nextElement);
}
const restore = this._willRestoreSelection;
this.setSelection(range);
this._willRestoreSelection = restore;
}
}
};
var mapKeyToFormat = (tag, remove) => {
return (self, event) => {
event.preventDefault();
self.toggleTag(tag, remove);
};
};
var mapKeyTo = (method) => (self, event) => {
event.preventDefault();
self[method]();
};
var toggleList = (type, methodIfNotInList) => (self, event) => {
event.preventDefault();
let parent = self.getSelectionClosest("UL,OL");
if (type == parent?.nodeName) {
self.removeList();
} else {
self[methodIfNotInList]();
}
};
var changeIndentationLevel = (direction) => (self, event) => {
event.preventDefault();
self.changeIndentationLevel(direction);
};
var keyHandlers = {
Tab,
"Shift-Tab": ShiftTab,
Space,
ArrowLeft(self) {
self._removeZWS();
},
ArrowRight(self) {
self._removeZWS()
},
[ctrlKey + "b"]: mapKeyToFormat("B"),
[ctrlKey + "i"]: mapKeyToFormat("I"),
[ctrlKey + "u"]: mapKeyToFormat("U"),
[ctrlKey + "Shift-7"]: mapKeyToFormat("S"),
[ctrlKey + "Shift-5"]: mapKeyToFormat("SUB", "SUP"),
[ctrlKey + "Shift-6"]: mapKeyToFormat("SUP", "SUB"),
[ctrlKey + "Shift-8"]: toggleList("UL", "makeUnorderedList"),
[ctrlKey + "Shift-9"]: toggleList("OL", "makeOrderedList"),
[ctrlKey + "["]: changeIndentationLevel("decrease"),
[ctrlKey + "]"]: changeIndentationLevel("increase"),
[ctrlKey + "d"]: mapKeyTo("toggleCode"),
// [ctrlKey + "z"]: mapKeyTo("undo"), // historyUndo
[ctrlKey + "y"]: mapKeyTo("redo"), // historyRedo
[ctrlKey + "Shift-Z"]: mapKeyTo("redo"),
["Redo"]: mapKeyTo("redo")
};
var blockTag = "DIV";
var DOCUMENT_POSITION_PRECEDING = 2;
var NBSP = '\u00A0';
var win = document.defaultView;
var osKey = isMac ? "metaKey" : "ctrlKey";
/*
typeToBitArray = {
1: 1,
2: 2,
3: 4,
8: 128,
9: 256,
11: 1024
},
*/
var didError = error => console.error(error);
var detachUneditableNode = (node, root) => {
let parent;
while (parent = node.parentNode) {
if (parent === root || parent.isContentEditable) {
break;
}
node = parent;
}
detach(node);
};
var mergeObjects = (base, extras, mayOverride) => {
base = base || {};
extras && Object.entries(extras).forEach(([prop,value])=>{
if (mayOverride || !(prop in base)) {
base[prop] = (value?.constructor === Object) ?
mergeObjects(base[prop], value, mayOverride) :
value;
}
});
return base;
};
var createBookmarkNodes = () => [
createElement("INPUT", {
id: startSelectionId,
type: "hidden"
}),
createElement("INPUT", {
id: endSelectionId,
type: "hidden"
})
];
var getListSelection = (range, root) => {
let list = range.commonAncestorContainer;
let startLi = range.startContainer;
let endLi = range.endContainer;
while (list && list !== root && !listNodeNames.has(list.nodeName)) {
list = list.parentNode;
}
if (!list || list === root) {
return null;
}
if (startLi === list) {
startLi = startLi.childNodes[range.startOffset];
}
if (endLi === list) {
endLi = endLi.childNodes[range.endOffset];
}
while (startLi && startLi.parentNode !== list) {
startLi = startLi.parentNode;
}
while (endLi && endLi.parentNode !== list) {
endLi = endLi.parentNode;
}
return [list, startLi, endLi];
};
var setDirection = (self, frag, dir) => {
let walker = getBlockWalker(frag, self._root),
node;
while (node = walker.nextNode()) {
if (node.nodeName === "LI") {
node.parentNode.setAttribute("dir", dir);
break;
}
node.setAttribute("dir", dir);
}
return frag;
};
var decreaseLevel = (self, range, node) =>
getClosest(node, self._root, "UL,OL,BLOCKQUOTE") && self.changeIndentationLevel("decrease");
var addLinks = (frag, root) => {
let walker = createTreeWalker(frag, SHOW_TEXT, node => !getClosest(node, root, "A"));
let node, data, parent, match, index, endIndex, child;
while (node = walker.nextNode()) {
data = node.data;
parent = node.parentNode;
while (match = linkRegExp.exec(data)) {
index = match.index;
endIndex = index + match[0].length;
if (index) {
child = document.createTextNode(data.slice(0, index));
parent.insertBefore(child, node);
}
child = createElement("A", {
href: match[1]
? (match[2] ? match[1] : "https://" + match[1])
: "mailto:" + match[0]
}, [data.slice(index, endIndex)]);
parent.insertBefore(child, node);
node.data = data = data.slice(endIndex);
}
}
};
keyHandlers[ctrlKey + "b"] = mapKeyToFormat("B");
keyHandlers[ctrlKey + "i"] = mapKeyToFormat("I");
keyHandlers[ctrlKey + "u"] = mapKeyToFormat("U");
keyHandlers[ctrlKey + "Shift-7"] = mapKeyToFormat("S");
keyHandlers[ctrlKey + "Shift-5"] = mapKeyToFormat("SUB", "SUP");
keyHandlers[ctrlKey + "Shift-6"] = mapKeyToFormat("SUP", "SUB");
keyHandlers[ctrlKey + "Shift-8"] = toggleList("UL", "makeUnorderedList");
keyHandlers[ctrlKey + "Shift-9"] = toggleList("OL", "makeOrderedList");
keyHandlers[ctrlKey + "["] = changeIndentationLevel("decrease");
keyHandlers[ctrlKey + "]"] = changeIndentationLevel("increase");
keyHandlers[ctrlKey + "d"] = mapKeyTo("toggleCode");
keyHandlers[ctrlKey + "y"] = mapKeyTo("redo");
keyHandlers[ctrlKey + "Shift-Z"] = mapKeyTo("redo");
keyHandlers["Redo"] = mapKeyTo("redo");
class EditStack extends Array
{
constructor(squire) {
super();
this.squire = squire;
this.index = -1;
this.inUndoState = false;
this.limit = 0; // -1 means no limit
}
clear() {
this.index = -1;
this.length = 0;
}
stateChanged(/*canUndo, canRedo*/) {
this.squire.fireEvent("undoStateChange", {
canUndo: this.index > 0,
canRedo: this.index + 1 < this.length
});
this.squire.fireEvent("input");
}
docWasChanged() {
if (this.inUndoState) {
this.inUndoState = false;
this.stateChanged(/*true, false*/);
} else
this.squire.fireEvent("input");
}
/**
* Leaves bookmark.
*/
recordUndoState(range, replace) {
if (!this.inUndoState || replace) {
let undoIndex = this.index;
let undoLimit = this.limit;
let squire = this.squire;
replace || ++undoIndex;
undoIndex = Math.max(0, undoIndex);
this.length = Math.min(undoIndex + 1, this.length);
range && squire._saveRangeToBookmark(range);
const html = squire._getRawHTML();
if (undoLimit > 0 && undoIndex > undoLimit) {
this.splice(0, undoIndex - undoLimit);
undoIndex = undoLimit;
}
this[undoIndex] = html;
this.index = undoIndex;
this.inUndoState = true;
}
}
saveUndoState(range) {
let squire = this.squire;
range = range || squire.getSelection();
this.recordUndoState(range, true);
squire._getRangeAndRemoveBookmark(range);
}
undo() {
let squire = this.squire;
if (this.index > 0 || !this.inUndoState) {
this.recordUndoState(squire.getSelection());
const undoIndex = this.index - 1;
this.index = undoIndex;
squire._setRawHTML(this[undoIndex]);
let range = squire._getRangeAndRemoveBookmark();
if (range) {
squire.setSelection(range);
}
this.stateChanged(/*undoIndex > 0, true*/);
}
}
redo() {
let squire = this.squire,
undoIndex = this.index + 1;
if (undoIndex < this.length && this.inUndoState) {
this.index = undoIndex;
squire._setRawHTML(this[undoIndex]);
let range = squire._getRangeAndRemoveBookmark();
if (range) {
squire.setSelection(range);
}
this.stateChanged(/*true, undoIndex + 1 < this.length*/);
}
}
}
// source/Editor.ts
var customEvents = new Set([
"pathChange",
"select",
"input",
"pasteImage",
"undoStateChange"
]);
var startSelectionId = "squire-selection-start";
var endSelectionId = "squire-selection-end";
var tagAfterSplit = {
DT: "DD",
DD: "DT",
LI: "LI",
PRE: "PRE"
};
var linkRegExp = /\b(?:((https?:\/\/)?(?:www\d{0,3}\.|[a-z0-9][a-z0-9.-]*\.[a-z]{2,}\/)(?:[^\s()<>]+|\([^\s()<>]+\))+(?:[^\s?&`!()[\]{};:'".,<>«»“”‘’]|\([^\s()<>]+\)))|([\w\-.%+]+@(?:[\w-]+\.)+[a-z]{2,}\b(?:\?[^&?\s]+=[^\s?&`!()[\]{};:'".,<>«»“”‘’]+(?:&[^&?\s]+=[^\s?&`!()[\]{};:'".,<>«»“”‘’]+)*)?))/i;
var Squire = class {
constructor(root, config) {
this._root = root;
this.setConfig(config);
this._isFocused = false;
this._lastSelection = null;
this._willRestoreSelection = false;
this._mayHaveZWS = false;
this._path = "";
this._pathRange = null;
this._events = new Map();
this.editStack = new EditStack(this);
this._ignoreChange = false;
this.addEventListener("selectionchange", () => this._isFocused && this._updatePath(this.getSelection()))
.addEventListener("blur", () => this._willRestoreSelection = true)
.addEventListener("pointerdown mousedown touchstart", () => this._willRestoreSelection = false)
.addEventListener("focus", () => this._willRestoreSelection && this.setSelection(this._lastSelection))
.addEventListener("cut", _onCut)
.addEventListener("copy", _onCopy)
.addEventListener("paste", _onPaste)
.addEventListener("drop", (event) => {
let types = event.dataTransfer.types;
if (types.includes("text/plain") || types.includes("text/html")) {
this.saveUndoState();
}
})
.addEventListener("keydown keyup", (event) => this.isShiftDown = event.shiftKey)
.addEventListener("keydown", _onKey)
.addEventListener("pointerup keyup mouseup touchend", () => this.getSelection())
.addEventListener("beforeinput", this._beforeInput);
this._keyHandlers = Object.create(keyHandlers);
this._mutation = new MutationObserver(() => this._docWasChanged());
this._mutation.observe(root, {
childList: true,
attributes: true,
characterData: true,
subtree: true
});
root.setAttribute("contenteditable", "true");
this.setHTML("");
this._beforeInputTypes = {
insertText: (event) => {
if (isAndroid && event.data && event.data.includes("\n")) {
event.preventDefault();
}
},
insertLineBreak: (event) => {
event.preventDefault();
this.splitBlock(true);
},
insertParagraph: (event) => {
event.preventDefault();
this.splitBlock(false);
},
insertOrderedList: (event) => {
event.preventDefault();
this.makeOrderedList();
},
insertUnoderedList: (event) => {
event.preventDefault();
this.makeUnorderedList();
},
historyUndo: (event) => {
event.preventDefault();
this.undo();
},
historyRedo: (event) => {
event.preventDefault();
},
formatRemove: (event) => {
event.preventDefault();
this.setStyle();
},
formatSetBlockTextDirection: (event) => {
event.preventDefault();
let dir = event.data;
this.setTextDirection(dir === "null" ? null : dir);
},
formatBackColor: (event) => {
event.preventDefault();
this.setStyle({ backgroundColor: event.data });
},
formatFontColor: (event) => {
event.preventDefault();
this.setStyle({ color: event.data });
},
formatFontName: (event) => {
event.preventDefault();
this.setStyle({ fontFamily: event.data });
},
/*
formatIndent: event => {
event.preventDefault();
this.changeIndentationLevel("increase");
},
formatOutdent: event => {
event.preventDefault();
this.changeIndentationLevel("decrease");
},
this.saveUndoState();
},
*/
deleteContentBackward: (event) => {
Backspace(this, event, this.getSelection());
},
deleteContentForward: (event) => {
Delete(this, event, this.getSelection());
}
};
}
_beforeInput(event) {
let type = event.isComposing ? "" : event.inputType;
switch (type) {
case "formatBold":
case "formatItalic":
case "formatUnderline":
case "formatStrikeThrough":
case "formatSuperscript":
case "formatSubscript":
event.preventDefault();
this[type.slice(6).toLowerCase()]();
break;
case "formatJustifyFull":
case "formatJustifyCenter":
case "formatJustifyRight":
case "formatJustifyLeft": {
event.preventDefault();
let alignment = type.slice(13).toLowerCase();
this.setStyle({textAlign:alignment === "full" ? "justify" : alignment});
break;
}
default:
this._beforeInputTypes[type]?.(event);
}
}
// --- Events
handleEvent(event) {
this.fireEvent(event.type, event);
}
fireEvent(type, detail) {
let handlers = this._events.get(type);
if (/^(?:focus|blur)/.test(type)) {
const isFocused = this._root === document.activeElement;
if (type === "focus") {
if (!isFocused || this._isFocused) {
return this;
}
this._isFocused = true;
} else {
if (isFocused || !this._isFocused) {
return this;
}
this._isFocused = false;
}
}
if (handlers) {
const event = detail instanceof Event ? detail : new CustomEvent(type, {
detail
});
handlers = handlers.slice();
for (const handler of handlers) {
try {
handler.handleEvent ? handler.handleEvent(event) : handler.call(this, event);
} catch (error) {
error.details = 'Squire: fireEvent error. Event type: ' + type;
didError(error);
}
}
}
return this;
}
addEventListener(types, fn) {
if (!fn) {
didError({
name: 'Squire: addEventListener with null or undefined fn',
message: 'Event type: ' + types
});
return this;
}
types.split(/\s+/).forEach((type) => {
let handlers = this._events.get(type);
let target = this._root;
if (!handlers) {
handlers = [];
this._events.set(type, handlers);
customEvents.has(type) || (type === "selectionchange" ? document : target).addEventListener(type, this, {capture:true,passive:"touchstart"===type});
}
handlers.push(fn);
});
return this;
}
removeEventListener(type, fn) {
const handlers = this._events.get(type);
if (handlers) {
if (fn) {
let l = handlers.length;
while (l--) {
if (handlers[l] === fn) {
handlers.splice(l, 1);
}
}
} else {
handlers.length = 0;
}
if (!handlers.length) {
this._events.delete(type);
customEvents.has(type) || (type === "selectionchange" ? document : this._root).removeEventListener(type, this, true);
}
}
return this;
}
// --- Focus
focus() {
this._root.focus({ preventScroll: true });
return this;
}
blur() {
this._root.blur();
return this;
}
// ---
_removeZWS() {
if (this._mayHaveZWS) {
removeZWS(this._root);
this._mayHaveZWS = false;
}
}
// ---
_saveRangeToBookmark(range) {
let [startNode, endNode] = createBookmarkNodes(),
temp;
insertNodeInRange(range, startNode);
range.collapse();
insertNodeInRange(range, endNode);
if (startNode.compareDocumentPosition(endNode) & DOCUMENT_POSITION_PRECEDING) {
startNode.id = endSelectionId;
endNode.id = startSelectionId;
temp = startNode;
startNode = endNode;
endNode = temp;
}
range.setStartAfter(startNode);
range.setEndBefore(endNode);
}
_getRangeAndRemoveBookmark(range) {
const root = this._root;
const start = root.querySelector("#" + startSelectionId);
const end = root.querySelector("#" + endSelectionId);
if (start && end) {
let startContainer = start.parentNode;
let endContainer = end.parentNode;
const startOffset = indexOf(startContainer.childNodes, start);
let endOffset = indexOf(endContainer.childNodes, end);
if (startContainer === endContainer) {
--endOffset;
}
detach(start);
detach(end);
range = range || document.createRange();
range.setStart(startContainer, startOffset);
range.setEnd(endContainer, endOffset);
mergeInlines(startContainer, range);
if (startContainer !== endContainer) {
mergeInlines(endContainer, range);
}
if (range.collapsed) {
startContainer = range.startContainer;
if (isTextNode(startContainer)) {
endContainer = startContainer.childNodes[range.startOffset];
if (!endContainer || !isTextNode(endContainer)) {
endContainer = startContainer.childNodes[range.startOffset - 1];
}
if (isTextNode(endContainer)) {
range.setStart(endContainer, 0);
range.collapse(true);
}
}
}
}
return range || null;
}
getSelection() {
const sel = win.getSelection();
const root = this._root;
let range;
if (this._isFocused && sel?.rangeCount) {
range = sel.getRangeAt(0).cloneRange();
const startContainer = range.startContainer;
const endContainer = range.endContainer;
if (startContainer && isLeaf(startContainer)) {
range.setStartBefore(startContainer);
}
if (endContainer && isLeaf(endContainer)) {
range.setEndBefore(endContainer);
}
}
if (range && root.contains(range.commonAncestorContainer)) {
this._lastSelection = range;
} else {
range = this._lastSelection;
if (!document.contains(range.commonAncestorContainer)) {
range = null;
}
}
return range || createRange(root.firstChild, 0);
}
setSelection(range) {
this._lastSelection = range;
if (this._isFocused) {
const selection = win.getSelection();
if (selection) {
selection.setBaseAndExtent(
range.startContainer,
range.startOffset,
range.endContainer,
range.endOffset
);
}
} else {
this._willRestoreSelection = true;
}
return this;
}
// ---
// --- Path
getPath() {
return this._path;
}
_updatePath(range, force) {
const anchor = range.startContainer,
focus = range.endContainer;
if (force || anchor !== this._pathRange.startContainer || focus !== this._pathRange.endContainer) {
this._pathRange = range.cloneRange();
let node = anchor === focus ? focus : null,
newPath = (anchor && focus) ? (node ? this._getPath(focus) : "(selection)") : "";
if (this._path !== newPath) {
this._path = newPath;
this.fireEvent("pathChange", {
path: newPath,
element: (!node || isElement(node)) ? node : node.parentElement
});
}
}
this.fireEvent(range.collapsed ? "cursor" : "select", {
range
});
}
_getPath(node) {
const root = this._root;
let path = "", style;
if (node && node !== root) {
path = this._getPath(node.parentNode, root);
if (isElement(node)) {
path += (path ? ">" : "") + node.nodeName;
if (node.id) {
path += "#" + node.id;
}
if (node.dir) {
path += "[dir=" + node.dir + "]";
}
if (style = node.style.cssText) {
path += "[style=" + style + "]";
}
}
}
return path;
}
// --- History
_docWasChanged() {
cache = new WeakMap();
this._mayHaveZWS = cantFocusEmptyTextNodes;
if (this._ignoreChange) {
this._ignoreChange = false;
} else {
this.editStack.docWasChanged();
}
}
/**
* Leaves bookmark.
*/
_recordUndoState(range, replace) {
this.editStack.recordUndoState(range, replace);
}
saveUndoState(range) {
this.editStack.saveUndoState(range);
}
undo() {
this.editStack.undo();
}
redo() {
this.editStack.redo();
}
// --- Get and set data
getRoot() {
return this._root;
}
_getRawHTML() {
return this._root.innerHTML;
}
_setRawHTML(html) {
if (html !== undefined) {
const root = this._root;
let node = root;
root.innerHTML = html;
do {
fixCursor(node);
} while (node = getNextBlock(node, root));
this._ignoreChange = true;
}
}
getHTML(withBookMark) {
let html, range;
if (withBookMark) {
range = this.getSelection();
this._saveRangeToBookmark(range);
}
html = this._getRawHTML().replace(/\u200B/g, "");
withBookMark && this._getRangeAndRemoveBookmark(range);
return html;
}
setHTML(html) {
const root = this._root,
frag = this._config.sanitizeToDOMFragment(html, false);
cleanTree(frag);
cleanupBRs(frag, root, false);
fixContainer(frag, root);
let node, walker = getBlockWalker(frag, root);
while ((node = walker.nextNode()) && node !== root) {
fixCursor(node);
}
this._ignoreChange = true;
if (root.replaceChildren) {
root.replaceChildren(frag);
} else {
while (root.lastChild)
detach(root.lastChild);
root.append(frag);
}
fixCursor(root);
this.editStack.clear();
const range = this._getRangeAndRemoveBookmark() || createRange(root.firstElementChild || root, 0);
this.saveUndoState(range);
this.setRange(range);
return this;
}
/**
* Insert HTML at the cursor location. If the selection is not collapsed
* insertTreeFragmentIntoRange will delete the selection so that it is
* replaced by the html being inserted.
*/
insertHTML(html, isPaste) {
let range = this.getSelection();
if (isPaste) {
let startFragmentIndex = html.indexOf(""),
endFragmentIndex = html.lastIndexOf("");
if (startFragmentIndex > -1 && endFragmentIndex > -1) {
html = html.slice(startFragmentIndex + 20, endFragmentIndex);
}
}
let frag = this._config.sanitizeToDOMFragment(html, isPaste);
this.saveUndoState(range);
try {
let root = this._root, node = frag;
addLinks(frag, frag);
cleanTree(frag);
cleanupBRs(frag, root, false);
removeEmptyInlines(frag);
frag.normalize();
while (node = getNextBlock(node, frag)) {
fixCursor(node);
}
insertTreeFragmentIntoRange(range, frag, root);
range.collapse();
moveRangeBoundaryOutOf(range, "A", root);
this._ensureBottomLine();
this.setRange(range);
isPaste && this.focus();
} catch (error) {
didError(error);
}
return this;
}
insertElement(el, range) {
range = range || this.getSelection();
range.collapse(true);
if (isInline(el)) {
insertNodeInRange(range, el);
range.setStartAfter(el);
} else {
const root = this._root;
let splitNode = getStartBlockOfRange(range, root) || root;
let nodeAfterSplit;
while (splitNode !== root && !splitNode.nextSibling) {
splitNode = splitNode.parentNode;
}
if (splitNode !== root) {
const parent = splitNode.parentNode;
nodeAfterSplit = split(parent, splitNode.nextSibling, root, root);
}
if (nodeAfterSplit) {
nodeAfterSplit.before(el);
} else {
root.append(el);
nodeAfterSplit = this.createDefaultBlock();
root.append(nodeAfterSplit);
}
range.setStart(nodeAfterSplit, 0);
range.setEnd(nodeAfterSplit, 0);
moveRangeBoundariesDownTree(range);
}
this.focus();
this.setSelection(range);
this._updatePath(range);
return this;
}
insertImage(src, attributes) {
const img = createElement("IMG", mergeObjects({
src: src
}, attributes, true));
this.insertElement(img);
return img;
}
insertPlainText(plainText, isPaste) {
const range = this.getSelection();
if (range.collapsed && getClosest(range.startContainer, this._root, "PRE")) {
let node = range.startContainer;
let offset = range.startOffset;
let text;
if (!isTextNode(node)) {
text = document.createTextNode("");
node?.childNodes[offset].before(text);
node = text;
offset = 0;
}
node.insertData(offset, plainText);
range.setStart(node, offset + plainText.length);
range.collapse(true);
this.setSelection(range);
return this;
}
const lines = plainText.split(/\r?\n/),
closeBlock = "" + blockTag + ">",
openBlock = "<" + blockTag + ">";
lines.forEach((line, i) => {
line = escapeHTML(line).replace(/ (?=(?: |$))/g, NBSP);
lines[i] = i ? openBlock + (line || "
") + closeBlock : line;
});
return this.insertHTML(lines.join(""), isPaste);
}
// --- Inline formatting
/**
* Extracts the font-family and font-size (if any) of the element
* holding the cursor. If there's a selection, returns an empty object.
*/
getFontInfo(range) {
const fontInfo = {
color: void 0,
backgroundColor: void 0,
family: void 0,
size: void 0
};
range = range || this.getSelection();
let seenAttributes = 0;
let element = range.commonAncestorContainer, style, attr;
if (range.collapsed || isTextNode(element)) {
if (isTextNode(element)) {
element = element.parentNode;
}
while (seenAttributes < 4 && element) {
if (style = element.style) {
if (!fontInfo.color && (attr = style.color)) {
fontInfo.color = attr;
++seenAttributes;
}
if (!fontInfo.backgroundColor && (attr = style.backgroundColor)) {
fontInfo.backgroundColor = attr;
++seenAttributes;
}
if (!fontInfo.family && (attr = style.fontFamily)) {
fontInfo.family = attr;
++seenAttributes;
}
if (!fontInfo.size && (attr = style.fontSize)) {
fontInfo.size = attr;
++seenAttributes;
}
}
element = element.parentNode;
}
}
return fontInfo;
}
/**
* Looks for matching tag and attributes, so won't work if
* instead of etc.
*/
hasFormat(tag, attributes, range) {
tag = tag.toUpperCase();
range = range || this.getSelection();
if (!range.collapsed && isTextNode(range.startContainer) && range.startOffset === range.startContainer.length && range.startContainer.nextSibling) {
range.setStartBefore(range.startContainer.nextSibling);
}
if (!range.collapsed && isTextNode(range.endContainer) && range.endOffset === 0 && range.endContainer.previousSibling) {
range.setEndAfter(range.endContainer.previousSibling);
}
const root = this._root;
const common = range.commonAncestorContainer;
if (getNearest(common, root, tag, attributes)) {
return true;
}
if (isTextNode(common)) {
return false;
}
const walker = createTreeWalker(common, SHOW_TEXT, (node2) => isNodeContainedInRange(range, node2));
let seenNode = false;
let node;
while (node = walker.nextNode()) {
if (!getNearest(node, root, tag, attributes)) {
return false;
}
seenNode = true;
}
return seenNode;
}
changeFormat(add, remove, range, partial) {
range = range || this.getSelection();
this.saveUndoState(range);
if (remove) {
range = this._removeFormat(
remove.tag.toUpperCase(),
remove.attributes || {},
range,
partial
);
}
if (add) {
range = this._addFormat(
add.tag.toUpperCase(),
add.attributes || {},
range
);
}
this.setRange(range);
return this.focus();
}
_addFormat(tag, attributes, range) {
const root = this._root;
let node;
if (range.collapsed) {
const el = fixCursor(createElement(tag, attributes));
insertNodeInRange(range, el);
range.setStart(el.firstChild, el.firstChild.length);
range.collapse(true);
let block = el;
while (isInline(block)) {
block = block.parentNode;
}
removeZWS(block, el);
} else {
const filter = (node) => (isTextNode(node) || isBrElement(node) || node.nodeName === "IMG") && isNodeContainedInRange(range, node);
const walker = createTreeWalker(
range.commonAncestorContainer,
SHOW_ELEMENT_OR_TEXT,
filter
);
let { startContainer, startOffset, endContainer, endOffset } = range;
walker.currentNode = startContainer;
if (!isElement(startContainer) && !isTextNode(startContainer) || !filter(startContainer)) {
startContainer = walker.nextNode();
startOffset = 0;
}
if (startContainer) {
do {
node = walker.currentNode;
if (!getNearest(node, root, tag, attributes)) {
if (node === endContainer && node.length > endOffset) {
node.splitText(endOffset);
}
if (node === startContainer && startOffset) {
node = node.splitText(startOffset);
if (endContainer === startContainer) {
endContainer = node;
endOffset -= startOffset;
}
startContainer = node;
startOffset = 0;
}
const el = createElement(tag, attributes);
replaceWith(node, el);
el.append(node);
}
} while (walker.nextNode());
if (!isTextNode(endContainer)) {
if (isTextNode(node)) {
endContainer = node;
endOffset = node.length;
} else {
endContainer = node.parentNode;
endOffset = 1;
}
}
range = createRange(
startContainer,
startOffset,
endContainer,
endOffset
);
}
}
return range;
}
_removeFormat(tag, attributes, range, partial) {
this._saveRangeToBookmark(range);
let fixer;
if (range.collapsed) {
this._mayHaveZWS = cantFocusEmptyTextNodes;
fixer = document.createTextNode(cantFocusEmptyTextNodes ? ZWS : "");
insertNodeInRange(range, fixer);
}
let root = range.commonAncestorContainer;
while (isInline(root)) {
root = root.parentNode;
}
const { startContainer, startOffset, endContainer, endOffset } = range;
const toWrap = [];
const examineNode = (node, exemplar) => {
if (isNodeContainedInRange(range, node, false)) {
return;
}
let isText = isTextNode(node);
let child;
let next;
if (!isNodeContainedInRange(range, node)) {
if (node.nodeName !== "INPUT" && (!isText || node.data)) {
toWrap.push([exemplar, node]);
}
return;
}
if (isText) {
if (node === endContainer && endOffset !== node.length) {
toWrap.push([exemplar, node.splitText(endOffset)]);
}
if (node === startContainer && startOffset) {
node.splitText(startOffset);
toWrap.push([exemplar, node]);
}
} else {
for (child = node.firstChild; child; child = next) {
next = child.nextSibling;
examineNode(child, exemplar);
}
}
};
const formatTags = Array.prototype.filter.call(
root.getElementsByTagName(tag),
(el) => isNodeContainedInRange(range, el, true) && hasTagAttributes(el, tag, attributes)
);
partial || formatTags.forEach((node) => examineNode(node, node));
toWrap.forEach(([el, node]) => {
el = el.cloneNode(false);
replaceWith(node, el);
el.append(node);
});
formatTags.forEach((el) => replaceWith(el, empty(el)));
if (cantFocusEmptyTextNodes && fixer) {
fixer = fixer.parentNode;
let block = fixer;
while (block && isInline(block)) {
block = block.parentNode;
}
if (block) {
removeZWS(block, fixer);
}
}
this._getRangeAndRemoveBookmark(range);
fixer && range.collapse();
mergeInlines(root, range);
return range;
}
// ---
bold() {
this.toggleTag("B");
}
italic() {
this.toggleTag("I");
}
underline() {
this.toggleTag("U");
}
strikethrough() {
this.toggleTag("S");
}
subscript() {
this.toggleTag("SUB", "SUP");
}
superscript() {
this.toggleTag("SUP", "SUB");
}
// ---
makeLink(url, attributes) {
const range = this.getSelection();
if (range.collapsed) {
insertNodeInRange(
range,
document.createTextNode(url.replace(/^[^:]*:\/*/, ""))
);
}
attributes = mergeObjects(
mergeObjects({
href: url
}, attributes, true),
null,
false
);
return this.changeFormat(
{
tag: "A",
attributes
},
{
tag: "A"
},
range
);
}
removeLink() {
return this.changeFormat(
null,
{
tag: "A"
},
this.getSelection(),
true
);
}
// --- Block formatting
_ensureBottomLine() {
const root = this._root;
const last = root.lastElementChild;
if (!last || last.nodeName !== blockTag || !isBlock(last)) {
root.append(this.createDefaultBlock());
}
}
createDefaultBlock(children) {
return fixCursor(
createElement(blockTag, null, children)
);
}
splitBlock(lineBreakOnly, range) {
range = range || this.getSelection();
const root = this._root;
let block;
let parent;
let node;
let nodeAfterSplit;
this.editStack.inUndoState && this._docWasChanged();
this._recordUndoState(range);
this._removeZWS();
this._getRangeAndRemoveBookmark(range);
if (!range.collapsed) {
deleteContentsOfRange(range, root);
}
if (this._config.addLinks) {
moveRangeBoundariesDownTree(range);
setTimeout(() => {
addLinks(range.startContainer, root);
}, 0);
}
block = getStartBlockOfRange(range, root);
if (block && (parent = getClosest(block, root, "PRE"))) {
moveRangeBoundariesDownTree(range);
node = range.startContainer;
const offset2 = range.startOffset;
if (!isTextNode(node)) {
node = document.createTextNode("");
parent.insertBefore(node, parent.firstChild);
}
if (!lineBreakOnly && isTextNode(node) && (node.data.charAt(offset2 - 1) === "\n" || rangeDoesStartAtBlockBoundary(range, root)) && (node.data.charAt(offset2) === "\n" || rangeDoesEndAtBlockBoundary(range, root))) {
node.deleteData(offset2 && offset2 - 1, offset2 ? 2 : 1);
nodeAfterSplit = split(
node,
offset2 && offset2 - 1,
root,
root
);
node = nodeAfterSplit.previousSibling;
if (!node.textContent) {
detach(node);
}
node = this.createDefaultBlock();
nodeAfterSplit.before(node);
if (!nodeAfterSplit.textContent) {
detach(nodeAfterSplit);
}
range.setStart(node, 0);
} else {
node.insertData(offset2, "\n");
fixCursor(parent);
if (node.length === offset2 + 1) {
range.setStartAfter(node);
} else {
range.setStart(node, offset2 + 1);
}
}
range.collapse(true);
this.setRange(range);
this._docWasChanged();
return;
}
if (!block || lineBreakOnly || /^T[HD]$/.test(block.nodeName)) {
moveRangeBoundaryOutOf(range, "A", root);
insertNodeInRange(range, createElement("BR"));
range.collapse();
this.setRange(range);
return;
}
block = getClosest(block, root, "LI") || block;
if (isEmptyBlock(block) && (parent = getClosest(block, root, "UL,OL,BLOCKQUOTE"))) {
return "BLOCKQUOTE" === parent.nodeName
? this.modifyBlocks((/* frag */) => this.createDefaultBlock(createBookmarkNodes()), range)
: this.decreaseListLevel(range);
}
node = range.startContainer;
const offset = range.startOffset;
let splitTag = tagAfterSplit[block.nodeName] || blockTag;
nodeAfterSplit = split(
node,
offset,
block.parentNode,
root
);
if (!hasTagAttributes(nodeAfterSplit, splitTag)) {
block = createElement(splitTag);
if (nodeAfterSplit.dir) {
block.dir = nodeAfterSplit.dir;
}
replaceWith(nodeAfterSplit, block);
block.append(empty(nodeAfterSplit));
nodeAfterSplit = block;
}
removeZWS(block);
removeEmptyInlines(block);
fixCursor(block);
while (isElement(nodeAfterSplit)) {
let child = nodeAfterSplit.firstChild;
let next;
if (nodeAfterSplit.nodeName === "A" && (!nodeAfterSplit.textContent || nodeAfterSplit.textContent === ZWS)) {
child = document.createTextNode("");
replaceWith(nodeAfterSplit, child);
nodeAfterSplit = child;
break;
}
while (isTextNode(child) && !child.data) {
next = child.nextSibling;
if (!next || isBrElement(next)) {
break;
}
detach(child);
child = next;
}
if (!child || isBrElement(child) || isTextNode(child)) {
break;
}
nodeAfterSplit = child;
}
range = createRange(nodeAfterSplit, 0);
this.setRange(range);
}
modifyBlocks(modify, range) {
range = range || this.getSelection();
this._recordUndoState(range, true);
const root = this._root;
expandRangeToBlockBoundaries(range, root);
moveRangeBoundariesUpTree(range, root, root, root);
const frag = extractContentsOfRange(range, root, root);
insertNodeInRange(range, modify.call(this, frag));
if (range.endOffset < range.endContainer.childNodes.length) {
mergeContainers(
range.endContainer.childNodes[range.endOffset],
root
);
}
mergeContainers(
range.startContainer.childNodes[range.startOffset],
root
);
this._getRangeAndRemoveBookmark(range);
this.setRange(range);
return this;
}
// ---
setTextDirection(direction) {
return this.modifyBlocks(frag => setDirection(this, frag, direction)).focus();
}
increaseListLevel(range) {
range = range || this.getSelection();
const root = this._root;
const listSelection = getListSelection(range, root);
if (listSelection) {
let [list, startLi, endLi] = listSelection;
if (startLi && startLi !== list.firstChild) {
this._recordUndoState(range, true);
const type = list.nodeName;
let newParent = startLi.previousSibling;
let next;
if (newParent.nodeName !== type) {
newParent = createElement(type);
startLi.before(newParent);
}
do {
next = startLi === endLi ? null : startLi.nextSibling;
newParent.append(startLi);
} while (startLi = next);
next = newParent.nextSibling;
next && mergeContainers(next, root);
this._getRangeAndRemoveBookmark(range);
this.setRange(range);
}
}
return this.focus();
}
decreaseListLevel(range) {
range = range || this.getSelection();
const root = this._root;
const listSelection = getListSelection(range, root);
if (listSelection) {
let list = listSelection[0];
let startLi = listSelection[1] || list.firstChild;
let endLi = listSelection[2] || list.lastChild;
let next, insertBefore;
this._recordUndoState(range, true);
if (startLi) {
let newParent = list.parentNode;
insertBefore = !endLi.nextSibling ?
list.nextSibling :
split(list, endLi.nextSibling, newParent, root);
if (newParent !== root && newParent.nodeName === "LI") {
newParent = newParent.parentNode;
while (insertBefore) {
next = insertBefore.nextSibling;
endLi.append(insertBefore);
insertBefore = next;
}
insertBefore = list.parentNode.nextSibling;
}
const makeNotList = !/^[OU]L$/.test(newParent.nodeName);
do {
next = startLi === endLi ? null : startLi.nextSibling;
startLi.remove();
if (makeNotList && startLi.nodeName === "LI") {
startLi = this.createDefaultBlock([empty(startLi)]);
}
newParent.insertBefore(startLi, insertBefore);
} while (startLi = next);
}
list.firstChild || detach(list);
insertBefore && mergeContainers(insertBefore, root);
this._getRangeAndRemoveBookmark(range);
this.setRange(range);
}
return this.focus();
}
_makeList(frag, type) {
let walker = getBlockWalker(frag, this._root),
node, tag, prev, newLi;
while (node = walker.nextNode()) {
if (node.parentNode.nodeName === "LI") {
node = node.parentNode;
walker.currentNode = node.lastChild;
}
if (node.nodeName !== "LI") {
newLi = createElement("LI");
if (node.dir) {
newLi.dir = node.dir;
}
if ((prev = node.previousSibling) && prev.nodeName === type) {
prev.append(newLi);
detach(node);
} else {
replaceWith(node, createElement(type, null, [newLi]));
}
newLi.append(empty(node));
walker.currentNode = newLi;
} else {
node = node.parentNode;
tag = node.nodeName;
if (tag !== type && listNodeNames.has(tag)) {
replaceWith(node,
createElement(type, null, [empty(node)])
);
}
}
}
return frag;
}
makeUnorderedList() {
return this.modifyBlocks((frag) => this._makeList(frag, "UL")).focus();
}
makeOrderedList() {
return this.modifyBlocks((frag) => this._makeList(frag, "OL")).focus();
}
removeList() {
return this.modifyBlocks((frag) => {
const root = this._root;
frag.querySelectorAll("LI").forEach((item) => {
if (isBlock(item)) {
replaceWith(item, this.createDefaultBlock([empty(item)]));
} else {
fixContainer(item, root);
replaceWith(item, empty(item));
}
});
frag.querySelectorAll("UL, OL").forEach((list) => {
const listFrag = empty(list);
fixContainer(listFrag, root);
replaceWith(list, listFrag);
});
return frag;
}).focus();
}
// ---
increaseQuoteLevel(range) {
return this.modifyBlocks(
(frag) => createElement(
"BLOCKQUOTE",
null,
[frag]
),
range
).focus();
}
decreaseQuoteLevel(range) {
return this.modifyBlocks((frag) => {
Array.prototype.filter.call(
frag.querySelectorAll("blockquote"),
(el) => !getClosest(el.parentNode, frag, "BLOCKQUOTE")
).forEach(
(el) => replaceWith(el, empty(el))
);
return frag;
}, range).focus();
}
// ---
code() {
const range = this.getSelection();
if (range.collapsed || isContainer(range.commonAncestorContainer)) {
return this.modifyBlocks((frag) => {
const root = this._root;
const output = document.createDocumentFragment();
let walker = getBlockWalker(frag, root);
let node;
while (node = walker.nextNode()) {
node.querySelectorAll("BR").forEach(br => {
if (!isLineBreak(br, false)) {
detach(br);
} else {
replaceWith(br, document.createTextNode("\n"));
}
});
node.querySelectorAll("CODE").forEach(el => detach(el));
if (output.childNodes.length) {
output.append(document.createTextNode("\n"));
}
output.append(empty(node));
}
walker = createTreeWalker(output, SHOW_TEXT);
while (node = walker.nextNode()) {
node.data = node.data.replace(NBSP, " "); // nbsp -> sp
}
output.normalize();
return fixCursor(
createElement("PRE", null, [
output
])
);
}, range).focus();
}
return this.changeFormat({ tag: "CODE" }, null, range);
}
removeCode() {
const range = this.getSelection();
const ancestor = range.commonAncestorContainer;
const inPre = getClosest(ancestor, this._root, "PRE");
if (inPre) {
return this.modifyBlocks((frag) => {
const root = this._root;
const pres = frag.querySelectorAll("PRE");
let l = pres.length;
let pre, walker, node, value, contents, index;
while (l--) {
pre = pres[l];
walker = createTreeWalker(pre, SHOW_TEXT);
while (node = walker.nextNode()) {
value = node.data;
value = value.replace(/ (?=)/g, NBSP); // sp -> nbsp
contents = document.createDocumentFragment();
while ((index = value.indexOf("\n")) > -1) {
contents.append(
document.createTextNode(value.slice(0, index))
);
contents.append(createElement("BR"));
value = value.slice(index + 1);
}
node.before(contents);
node.data = value;
}
fixContainer(pre, root);
replaceWith(pre, empty(pre));
}
return frag;
}, range).focus();
}
return this.changeFormat(null, { tag: "CODE" }, range);
}
toggleCode() {
return (this.hasFormat("PRE") || this.hasFormat("CODE")) ? this.removeCode() : this.code();
}
// SnappyMail
changeIndentationLevel(direction) {
let parent = this.getSelectionClosest("UL,OL,BLOCKQUOTE");
if (parent || "increase" === direction) {
direction += (!parent || "BLOCKQUOTE" === parent.nodeName) ? "Quote" : "List";
return this[direction + "Level"]();
}
}
getSelectionClosest(selector) {
return getClosest(this.getSelection().commonAncestorContainer, this._root, selector);
}
setAttribute(name, value) {
let range = this.getSelection();
let start = range?.startContainer || {};
let end = range?.endContainer || {};
if ("dir" == name || (isTextNode(start) && 0 === range.startOffset && start === end && end.length === range.endOffset)) {
this._recordUndoState(range);
setAttributes(start.parentNode, { [name]: value });
this._docWasChanged();
} else if (null == value) {
this._recordUndoState(range);
let node = getClosest(range.commonAncestorContainer, this._root, "*");
range.collapsed ? setAttributes(node, { [name]: value }) : node.querySelectorAll("*").forEach((el) => setAttributes(el, { [name]: value }));
this._docWasChanged();
} else {
this.changeFormat({
tag: "SPAN",
attributes: { [name]: value }
}, null, range);
}
return this.focus();
}
setStyle(style) {
this.setAttribute("style", style);
}
toggleTag(name, remove) {
let range = this.getSelection();
if (this.hasFormat(name, null, range)) {
this.changeFormat(null, { tag: name }, range);
} else {
this.changeFormat({ tag: name }, remove ? { tag: remove } : null, range);
}
}
setRange(range) {
this.setSelection(range);
this._updatePath(range, true);
}
setConfig(config) {
this._config = mergeObjects({
addLinks: true
}, config, true);
return this;
}
}
// source/Legacy.ts
win.Squire = Squire;
})();