Squire v2.4.9

This commit is contained in:
the-djmaze 2026-10-20 23:32:32 +02:00
parent 8667af700d
commit 020927cc56

View file

@ -45,6 +45,7 @@
// source/Constants.ts // source/Constants.ts
var ELEMENT_NODE = 1; var ELEMENT_NODE = 1;
var TEXT_NODE = 3; var TEXT_NODE = 3;
var COMMENT_NODE = 8;
var DOCUMENT_FRAGMENT_NODE = 11; var DOCUMENT_FRAGMENT_NODE = 11;
var ZWS = "\u200B"; var ZWS = "\u200B";
var ua = navigator.userAgent; var ua = navigator.userAgent;
@ -55,7 +56,7 @@
var isWebKit = /WebKit\//.test(ua); var isWebKit = /WebKit\//.test(ua);
var ctrlKey = isMac || isIOS ? "Meta-" : "Ctrl-"; var ctrlKey = isMac || isIOS ? "Meta-" : "Ctrl-";
var cantFocusEmptyTextNodes = isWebKit; var cantFocusEmptyTextNodes = isWebKit;
var notWS = /[^ \t\r\n]/; var notWS = /[^ \t\r\n\u200B]/;
// source/node/Category.ts // source/node/Category.ts
var inlineNodeNames = /^(?:#text|A(?:BBR|CRONYM)?|B(?:R|D[IO])?|C(?:ITE|ODE)|D(?:ATA|EL|FN)|EM|FONT|HR|I(?:MG|NS)?|KBD|Q|R(?:P|T|UBY)|S(?:AMP|MALL|PAN|TR(?:IKE|ONG)|U[BP])?|TIME|U|VAR|WBR)$/; var inlineNodeNames = /^(?:#text|A(?:BBR|CRONYM)?|B(?:R|D[IO])?|C(?:ITE|ODE)|D(?:ATA|EL|FN)|EM|FONT|HR|I(?:MG|NS)?|KBD|Q|R(?:P|T|UBY)|S(?:AMP|MALL|PAN|TR(?:IKE|ONG)|U[BP])?|TIME|U|VAR|WBR)$/;
@ -71,6 +72,7 @@
var isLeaf = (node) => leafNodeNames.has(node.nodeName); var isLeaf = (node) => leafNodeNames.has(node.nodeName);
var getNodeCategory = (node) => { var getNodeCategory = (node) => {
switch (node.nodeType) { switch (node.nodeType) {
case COMMENT_NODE:
case TEXT_NODE: case TEXT_NODE:
return INLINE; return INLINE;
case ELEMENT_NODE: case ELEMENT_NODE:
@ -252,11 +254,12 @@
return nodeStartAfterStart && nodeEndBeforeEnd; return nodeStartAfterStart && nodeEndBeforeEnd;
} }
}; };
var canDescendInto = (node) => node instanceof Text || node.nodeType === ELEMENT_NODE && !isLeaf(node);
var moveRangeBoundariesDownTree = (range) => { var moveRangeBoundariesDownTree = (range) => {
let { startContainer, startOffset, endContainer, endOffset } = range; let { startContainer, startOffset, endContainer, endOffset } = range;
while (!(startContainer instanceof Text)) { while (!(startContainer instanceof Text)) {
let child = startContainer.childNodes[startOffset]; let child = startContainer.childNodes[startOffset];
if (!child || isLeaf(child)) { if (!child || !canDescendInto(child)) {
if (startOffset) { if (startOffset) {
child = startContainer.childNodes[startOffset - 1]; child = startContainer.childNodes[startOffset - 1];
if (child instanceof Text) { if (child instanceof Text) {
@ -278,7 +281,7 @@
if (endOffset) { if (endOffset) {
while (!(endContainer instanceof Text)) { while (!(endContainer instanceof Text)) {
const child = endContainer.childNodes[endOffset - 1]; const child = endContainer.childNodes[endOffset - 1];
if (!child || isLeaf(child)) { if (!child || !canDescendInto(child)) {
if (child && child.nodeName === "BR" && !isLineBreak(child)) { if (child && child.nodeName === "BR" && !isLineBreak(child)) {
--endOffset; --endOffset;
continue; continue;
@ -291,7 +294,7 @@
} else { } else {
while (!(endContainer instanceof Text)) { while (!(endContainer instanceof Text)) {
const child = endContainer.firstChild; const child = endContainer.firstChild;
if (!child || isLeaf(child)) { if (!child || !canDescendInto(child)) {
break; break;
} }
endContainer = child; endContainer = child;
@ -368,7 +371,7 @@
replace(classNames, family) { replace(classNames, family) {
return createElement("SPAN", { return createElement("SPAN", {
class: classNames.fontFamily, class: classNames.fontFamily,
style: "font-family:" + family style: { fontFamily: family }
}); });
} }
}, },
@ -377,7 +380,7 @@
replace(classNames, size) { replace(classNames, size) {
return createElement("SPAN", { return createElement("SPAN", {
class: classNames.fontSize, class: classNames.fontSize,
style: "font-size:" + size style: { fontSize: size }
}); });
} }
}, },
@ -557,6 +560,9 @@
}; };
var fixContainer = (container) => { var fixContainer = (container) => {
let wrapper = null; let wrapper = null;
if (/^(?:TABLE|TBODY|TR|TH|TD|P)/.test(container.nodeName)) {
return container;
}
[...container.childNodes].forEach((child) => { [...container.childNodes].forEach((child) => {
if (isInline(child)) { if (isInline(child)) {
wrapper || (wrapper = createElement("DIV")); wrapper || (wrapper = createElement("DIV"));
@ -566,10 +572,13 @@
wrapper = null; wrapper = null;
} }
}); });
wrapper && container.append(wrapper); wrapper && container.append(fixCursor(wrapper));
return container; return container;
}; };
var split = (node, offset, stopNode, root) => { var split = (node, offset, stopNode, root) => {
if (!stopNode.contains(node)) {
throw new Error("split: stopNode does not contain node");
}
if (node instanceof Text && node !== stopNode) { if (node instanceof Text && node !== stopNode) {
if (typeof offset !== "number") { if (typeof offset !== "number") {
throw new Error("Offset must be a number to split text node!"); throw new Error("Offset must be a number to split text node!");
@ -1020,7 +1029,7 @@
} }
moveRangeBoundariesDownTree(range); moveRangeBoundariesDownTree(range);
range.collapse(false); range.collapse(false);
const stopPoint = getClosest(range.endContainer, root, "BLOCKQUOTE") || root; let stopPoint = getClosest(range.endContainer, root, "BLOCKQUOTE") || root;
let block = getStartBlockOfRange(range, root); let block = getStartBlockOfRange(range, root);
let blockContentsAfterSplit = null; let blockContentsAfterSplit = null;
const firstBlockInFrag = getNextBlock(frag, frag); const firstBlockInFrag = getNextBlock(frag, frag);
@ -1054,11 +1063,15 @@
} }
} }
mergeWithBlock(container, firstBlockInFrag, range, root); mergeWithBlock(container, firstBlockInFrag, range, root);
offset = Array.from(container.parentNode.childNodes).indexOf( if (container === root) {
container range.setEnd(root, getLength(root));
) + 1; } else {
container = container.parentNode; offset = Array.from(container.parentNode.childNodes).indexOf(
range.setEnd(container, offset); container
) + 1;
container = container.parentNode;
range.setEnd(container, offset);
}
} }
if (getLength(frag)) { if (getLength(frag)) {
if (replaceBlock && block) { if (replaceBlock && block) {
@ -1066,6 +1079,13 @@
range.collapse(false); range.collapse(false);
detach(block); detach(block);
} }
if (!stopPoint.contains(range.endContainer)) {
if (!root.contains(range.endContainer)) {
range.setEnd(root, getLength(root));
range.collapse(false);
}
stopPoint = getClosest(range.endContainer, root, "BLOCKQUOTE") || root;
}
moveRangeBoundariesUpTree(range, stopPoint, stopPoint, root); moveRangeBoundariesUpTree(range, stopPoint, stopPoint, root);
let nodeAfterSplit = split( let nodeAfterSplit = split(
range.endContainer, range.endContainer,
@ -1149,29 +1169,25 @@
// source/Clipboard.ts // source/Clipboard.ts
var indexOf = Array.prototype.indexOf; var indexOf = Array.prototype.indexOf;
var extractRangeToClipboard = (event, range, root, removeRangeFromDocument, toCleanHTML, toPlainText, plainTextOnly) => { var extractRange = (range, root, removeRangeFromDocument, toCleanHTML, toPlainText) => {
const clipboardData = event.clipboardData;
if (!clipboardData) {
return false;
}
let text = toPlainText ? "" : getTextContentsOfRange(range); let text = toPlainText ? "" : getTextContentsOfRange(range);
const startBlock = getStartBlockOfRange(range, root); const startBlock = getStartBlockOfRange(range, root);
const endBlock = getEndBlockOfRange(range, root); const endBlock = getEndBlockOfRange(range, root);
let parent = range.commonAncestorContainer;
let copyRoot = root; let copyRoot = root;
if (startBlock === endBlock && startBlock?.contains(range.commonAncestorContainer)) { if (startBlock === endBlock && startBlock?.contains(parent)) {
copyRoot = startBlock; copyRoot = startBlock;
} }
let contents; let contents;
if (removeRangeFromDocument) { if (removeRangeFromDocument) {
contents = deleteContentsOfRange(range, root); contents = deleteContentsOfRange(range, root);
if (!parent.isConnected) {
parent = range.commonAncestorContainer;
}
} else { } else {
range = range.cloneRange();
moveRangeBoundariesDownTree(range);
moveRangeBoundariesUpTree(range, copyRoot, copyRoot, root);
contents = range.cloneContents(); contents = range.cloneContents();
} }
let parent = range.commonAncestorContainer; while (parent && !(parent instanceof Element)) {
if (parent instanceof Text) {
parent = parent.parentNode; parent = parent.parentNode;
} }
while (parent && parent !== copyRoot) { while (parent && parent !== copyRoot) {
@ -1183,7 +1199,7 @@
let html; let html;
if (contents.childNodes.length === 1 && contents.childNodes[0] instanceof Text) { if (contents.childNodes.length === 1 && contents.childNodes[0] instanceof Text) {
text = contents.childNodes[0].data.replace(/ /g, " "); text = contents.childNodes[0].data.replace(/ /g, " ");
plainTextOnly = true; html = void 0;
} else { } else {
const node = createElement("DIV"); const node = createElement("DIV");
node.append(contents); node.append(contents);
@ -1195,15 +1211,34 @@
if (toPlainText && html !== void 0) { if (toPlainText && html !== void 0) {
text = toPlainText(html); text = toPlainText(html);
} }
if (text === html) {
html = void 0;
}
if (isWin) { if (isWin) {
text = text.replace(/\r?\n/g, "\r\n"); text = text.replace(/\r?\n/g, "\r\n");
} }
if (!plainTextOnly && html && text !== html) { if (html) {
html = "<!-- squire -->" + html; html = "<!-- squire -->" + html;
}
return [text, html];
};
var extractRangeToClipboard = (event, range, root, removeRangeFromDocument, toCleanHTML, toPlainText, plainTextOnly) => {
const clipboardData = event.clipboardData;
if (!clipboardData) {
return false;
}
let [text, html] = extractRange(
range,
root,
removeRangeFromDocument,
toCleanHTML,
toPlainText
);
event.preventDefault();
if (!plainTextOnly && html) {
clipboardData.setData("text/html", html); clipboardData.setData("text/html", html);
} }
clipboardData.setData("text/plain", text); clipboardData.setData("text/plain", text);
event.preventDefault();
return true; return true;
}; };
var _onCut = function(event) { var _onCut = function(event) {
@ -1251,7 +1286,11 @@
var _onPaste = function(event) { var _onPaste = function(event) {
const clipboardData = event.clipboardData; const clipboardData = event.clipboardData;
const items = clipboardData.items; const items = clipboardData.items;
const choosePlain = this._isShiftDown; const choosePlain = this._isShiftDown || !!getNearest(
this.getSelection().commonAncestorContainer,
this._root,
"PRE"
);
let hasRTF = false; let hasRTF = false;
let hasImage = false; let hasImage = false;
let plainItem = null; let plainItem = null;
@ -1270,7 +1309,7 @@
hasImage = true; hasImage = true;
} }
} }
if (hasImage && !(hasRTF && htmlItem)) { if (hasImage && !(hasRTF && htmlItem) && !plainItem) {
event.preventDefault(); event.preventDefault();
this.fireEvent("pasteImage", { this.fireEvent("pasteImage", {
clipboardData clipboardData
@ -1284,42 +1323,122 @@
}); });
} else if (plainItem) { } else if (plainItem) {
plainItem.getAsString((text) => { plainItem.getAsString((text) => {
let isLink = false;
const range = this.getSelection(); const range = this.getSelection();
if (!range.collapsed && notWS.test(range.toString())) { if (!range.collapsed && notWS.test(range.toString())) {
const match = this.linkRegExp.exec(text); const match = this.linkRegExp.exec(text);
isLink = !!match && match[0].length === text.length; const isLink = !!match && match[0].length === text.length;
} if (isLink) {
if (isLink) { const href = match[1] ? /^(?:ht|f)tps?:/i.test(match[1]) ? match[1] : "http://" + match[1] : "mailto:" + match[0];
this.makeLink(text); this.makeLink(href);
} else { return;
this.insertPlainText(text, true); }
} }
this.insertPlainText(text, true);
}); });
} }
}; };
var _onDragStart = function() {
const range = this.getSelection();
if (range && !range.collapsed && this._root.contains(range.commonAncestorContainer)) {
this._dragRange = range.cloneRange();
} else {
this._dragRange = null;
}
};
var _onDragEnd = function() {
this._dragRange = null;
};
var getCaretRangeFromPoint = (x, y, root) => {
let range = null;
const doc = document;
if (doc.caretPositionFromPoint) {
const pos = doc.caretPositionFromPoint(x, y);
if (pos) {
range = document.createRange();
range.setStart(pos.offsetNode, pos.offset);
range.collapse(true);
}
} else if (doc.caretRangeFromPoint) {
range = doc.caretRangeFromPoint(x, y);
}
if (range && !root.contains(range.commonAncestorContainer)) {
return null;
}
return range;
};
var pointIsWithinRange = (point, source) => {
return point.compareBoundaryPoints(Range.START_TO_START, source) >= 0 && point.compareBoundaryPoints(Range.END_TO_END, source) <= 0;
};
var _onDrop = function(event) { var _onDrop = function(event) {
if (!event.dataTransfer) { const dataTransfer = event.dataTransfer;
if (!dataTransfer) {
return; return;
} }
const types = event.dataTransfer.types; const types = dataTransfer.types;
let l = types.length;
let hasPlain = false; let hasPlain = false;
let hasHTML = false; let hasHTML = false;
while (l--) { for (let i = 0, l = types.length; i < l; i += 1) {
switch (types[l]) { switch (types[i]) {
case "text/plain": case "text/plain":
hasPlain = true; hasPlain = true;
break; break;
case "text/html": case "text/html":
hasHTML = true; hasHTML = true;
break; break;
default:
return;
} }
} }
if (hasHTML || hasPlain && this.saveUndoState) { if (!hasPlain && !hasHTML) {
this.saveUndoState(); return;
}
event.preventDefault();
const root = this._root;
let dropRange = getCaretRangeFromPoint(event.clientX, event.clientY, root);
const dragRange = this._dragRange;
this._dragRange = null;
if (!dropRange) {
return;
}
let text, html;
if (dragRange) {
if (pointIsWithinRange(dropRange, dragRange)) {
return;
}
this._recordUndoState(dragRange, this._isInUndoState);
const bookmark = document.createComment("");
dropRange.insertNode(bookmark);
this._getRangeAndRemoveBookmark(dragRange);
[text, html] = extractRange(
dragRange,
root,
dataTransfer.dropEffect !== "copy",
this._config.willCutCopy,
this._config.toPlainText
);
bookmark.replaceWith(
createElement("INPUT", {
id: this.startSelectionId,
type: "hidden"
}),
createElement("INPUT", {
id: this.endSelectionId,
type: "hidden"
})
);
this._getRangeAndRemoveBookmark(dropRange);
resetNodeCategoryCache();
} else {
this.saveUndoState(dropRange);
if (hasHTML) {
html = dataTransfer.getData("text/html");
} else {
text = dataTransfer.getData("text/plain");
}
}
this.setSelection(dropRange);
if (html !== void 0) {
this.insertHTML(html, true);
} else if (text !== void 0) {
this.insertPlainText(text, true);
} }
}; };
@ -1639,8 +1758,9 @@
} }
key = modifiers + key; key = modifiers + key;
const range = this.getSelection(); const range = this.getSelection();
if (this._keyHandlers[key]) { const handler = this._keyHandlers[key];
this._keyHandlers[key](this, event, range); if (handler) {
handler(this, event, range);
} else if (!range.collapsed && !event.ctrlKey && !event.metaKey && key.length === 1) { } else if (!range.collapsed && !event.ctrlKey && !event.metaKey && key.length === 1) {
this.saveUndoState(range); this.saveUndoState(range);
deleteContentsOfRange(range, this._root); deleteContentsOfRange(range, this._root);
@ -1667,7 +1787,7 @@
do { do {
if (node.nodeName === "CODE") { if (node.nodeName === "CODE") {
let next = node.nextSibling; let next = node.nextSibling;
if (!(next instanceof Text)) { if (!(next instanceof Text) || next.length === 0) {
const textNode = document.createTextNode("\xA0"); const textNode = document.createTextNode("\xA0");
node.parentNode.insertBefore(textNode, next); node.parentNode.insertBefore(textNode, next);
next = textNode; next = textNode;
@ -1759,6 +1879,331 @@
self.redo(); self.redo();
}; };
// source/ImageResize.ts
var RESIZE_HANDLE_SIZE = 8;
var MIN_IMAGE_SIZE = 40;
var MAX_IMAGE_SIZE = 1200;
var handlePositions = [
{ pos: "nw", cursor: "nwse-resize" },
{ pos: "n", cursor: "ns-resize" },
{ pos: "ne", cursor: "nesw-resize" },
{ pos: "e", cursor: "ew-resize" },
{ pos: "se", cursor: "nwse-resize" },
{ pos: "s", cursor: "ns-resize" },
{ pos: "sw", cursor: "nesw-resize" },
{ pos: "w", cursor: "ew-resize" }
];
var keyHandlers2 = {
ArrowUp: (editor, range, root) => {
const block = getStartBlockOfRange(range, root);
if (block) {
const prev = getPreviousBlock(block, root);
if (prev) {
range.selectNodeContents(prev);
range.collapse(false);
editor.setSelection(range).focus();
}
}
},
ArrowDown: (editor, range, root) => {
const block = getStartBlockOfRange(range, root);
if (block) {
const next = getNextBlock(block, root);
if (next) {
range.selectNodeContents(next);
range.collapse(true);
editor.setSelection(range).focus();
}
}
},
Delete: (editor, range) => {
editor.replaceWithBlankLine(range);
},
Backspace: (editor, range) => {
editor.replaceWithBlankLine(range);
}
};
var ImageResizer = class {
constructor(root, editor) {
this._currentImage = null;
this._resizeContainer = null;
this._handles = null;
this._currentHandle = null;
this._startX = 0;
this._startY = 0;
this._startWidth = 0;
this._startHeight = 0;
this._maxWidth = MAX_IMAGE_SIZE;
this._originalRatio = 1;
this._editor = editor;
this._root = root;
document.addEventListener("click", this);
editor.addEventListener("drop", this);
}
destroy() {
this._deselectImage();
this._editor.removeEventListener("drop", this);
document.removeEventListener("click", this);
}
// EventListener interface implementation
handleEvent(event) {
switch (event.type) {
case "click":
this._onClick(event);
break;
case "pointerdown":
this._onPointerDown(event);
break;
case "pointermove":
this._onPointerMove(event);
break;
case "pointercancel":
case "pointerup":
this._onPointerUp(event);
break;
case "keydown":
this._onKeyDown(event);
break;
case "drop":
this._deselectImage();
break;
}
}
// ---
_onClick(event) {
const target = event.target;
if (target.nodeName === "IMG" && this._root.contains(target)) {
event.stopPropagation();
this._selectImage(target);
} else if (this._currentImage && this._handles && !this._handles.some((handle) => handle.element === target)) {
this._deselectImage();
}
}
_deselectImage() {
if (!this._currentImage) {
return;
}
document.removeEventListener("keydown", this, true);
if (this._currentHandle) {
this._onPointerUp({
preventDefault() {
},
target: this._currentHandle.element
});
}
if (this._handles) {
this._handles.forEach(
({ element }) => element.removeEventListener("pointerdown", this)
);
}
if (this._resizeContainer) {
this._resizeContainer.remove();
}
this._currentImage.removeAttribute("tabindex");
this._handles = null;
this._resizeContainer = null;
this._currentImage = null;
}
_selectImage(image) {
if (this._currentImage === image) {
return;
}
this._deselectImage();
this._root.blur();
const handles = handlePositions.map(({ pos, cursor }) => {
const offset = RESIZE_HANDLE_SIZE / 2;
let positionStyle = "";
switch (pos) {
case "nw":
positionStyle = `left: -${offset}px; top: -${offset}px;`;
break;
case "n":
positionStyle = `left: calc(50% - ${offset}px); top: -${offset}px;`;
break;
case "ne":
positionStyle = `right: -${offset}px; top: -${offset}px;`;
break;
case "e":
positionStyle = `right: -${offset}px; top: calc(50% - ${offset}px);`;
break;
case "se":
positionStyle = `right: -${offset}px; bottom: -${offset}px;`;
break;
case "s":
positionStyle = `left: calc(50% - ${offset}px); bottom: -${offset}px;`;
break;
case "sw":
positionStyle = `left: -${offset}px; bottom: -${offset}px;`;
break;
case "w":
positionStyle = `left: -${offset}px; top: calc(50% - ${offset}px);`;
break;
}
const handle = createElement("div", {
class: `squire-resize-handle squire-resize-handle-${pos}`,
style: `
position: absolute;
width: ${RESIZE_HANDLE_SIZE}px;
height: ${RESIZE_HANDLE_SIZE}px;
background: #0067b9;
border: 1px solid #fff;
cursor: ${cursor};
pointer-events: auto;
touch-action: none;
box-shadow: 0 1px 3px rgba(0,0,0,0.3);
${positionStyle}
`
});
handle.addEventListener("pointerdown", this);
return {
element: handle,
cursor,
position: pos
};
});
const resizeContainer = createElement(
"div",
{
class: "squire-image-resize-container",
style: "position: absolute; pointer-events: none; z-index: 1000;"
},
handles.map((handle) => handle.element)
);
this._currentImage = image;
this._resizeContainer = resizeContainer;
this._handles = handles;
this._root.appendChild(this._resizeContainer);
const naturalWidth = image.naturalWidth;
this._originalRatio = naturalWidth / image.naturalHeight;
this._maxWidth = Math.min(
naturalWidth * 2,
image.parentElement ? image.parentElement.offsetWidth : MAX_IMAGE_SIZE,
MAX_IMAGE_SIZE
);
this._positionResizeContainer();
image.tabIndex = -1;
image.focus();
document.addEventListener("keydown", this, true);
}
_positionResizeContainer() {
const resizeContainer = this._resizeContainer;
const root = this._root;
const currentImage = this._currentImage;
if (!resizeContainer || !currentImage) {
return;
}
const rootRect = root.getBoundingClientRect();
const imageRect = currentImage.getBoundingClientRect();
const top = imageRect.top - rootRect.top + root.scrollTop;
const left = imageRect.left - rootRect.left + root.scrollLeft;
const width = imageRect.width;
const height = imageRect.height;
resizeContainer.style.top = top + "px";
resizeContainer.style.left = left + "px";
resizeContainer.style.width = width + "px";
resizeContainer.style.height = height + "px";
}
_onPointerDown(event) {
if (this._currentHandle || !this._handles) {
return;
}
const target = event.target;
const currentHandle = this._handles.find((h) => h.element === target) || null;
if (!currentHandle) {
return;
}
const currentImage = this._currentImage;
if (!currentImage) {
return;
}
event.preventDefault();
event.stopPropagation();
target.addEventListener("pointermove", this);
target.addEventListener("pointerup", this);
target.addEventListener("pointercancel", this);
target.setPointerCapture(event.pointerId);
this._currentHandle = currentHandle;
this._startX = event.clientX;
this._startY = event.clientY;
const style = getComputedStyle(currentImage);
this._startWidth = parseFloat(style.width);
this._startHeight = parseFloat(style.height);
document.body.style.cursor = currentHandle.cursor;
}
_onPointerMove(event) {
event.preventDefault();
const currentHandle = this._currentHandle;
const currentImage = this._currentImage;
if (!currentHandle || !currentImage) {
return;
}
const deltaX = event.clientX - this._startX;
const deltaY = event.clientY - this._startY;
const maxWidth = this._maxWidth;
const originalRatio = this._originalRatio;
let newWidth = this._startWidth;
let newHeight = this._startHeight;
switch (currentHandle.position) {
case "sw":
case "nw":
case "w":
newWidth -= 2 * deltaX;
break;
case "se":
case "ne":
case "e":
newWidth += 2 * deltaX;
break;
case "n":
newHeight -= deltaY;
newWidth = newHeight * originalRatio;
break;
case "s":
newHeight += deltaY;
newWidth = newHeight * originalRatio;
break;
}
if (newWidth < MIN_IMAGE_SIZE) {
newWidth = MIN_IMAGE_SIZE;
} else if (newWidth > maxWidth) {
newWidth = maxWidth;
}
const currentImageStyle = currentImage.style;
currentImageStyle.width = newWidth + "px";
currentImageStyle.height = "auto";
this._positionResizeContainer();
}
_onPointerUp(event) {
event.preventDefault();
const target = event.target;
if (target) {
target.removeEventListener("pointermove", this);
target.removeEventListener("pointerup", this);
target.removeEventListener("pointercancel", this);
}
this._currentHandle = null;
document.body.style.cursor = "";
}
_onKeyDown(event) {
event.preventDefault();
event.stopPropagation();
const keyHandler = keyHandlers2[event.key];
if (!keyHandler) {
return;
}
const image = this._currentImage;
if (!image) {
return;
}
const editor = this._editor;
const root = this._root;
this._deselectImage();
const range = editor.getSelection();
range.selectNode(image);
keyHandler(editor, range, root);
}
};
// source/Editor.ts // source/Editor.ts
var Squire = class { var Squire = class {
constructor(root, config) { constructor(root, config) {
@ -1843,6 +2288,7 @@
this._mayHaveZWS = false; this._mayHaveZWS = false;
this._lastAnchorNode = null; this._lastAnchorNode = null;
this._lastFocusNode = null; this._lastFocusNode = null;
this._lastPathRange = null;
this._path = ""; this._path = "";
this._events = /* @__PURE__ */ new Map(); this._events = /* @__PURE__ */ new Map();
this._undoIndex = -1; this._undoIndex = -1;
@ -1861,6 +2307,9 @@
this.addEventListener("cut", _onCut); this.addEventListener("cut", _onCut);
this.addEventListener("copy", _onCopy); this.addEventListener("copy", _onCopy);
this.addEventListener("paste", _onPaste); this.addEventListener("paste", _onPaste);
this._dragRange = null;
this.addEventListener("dragstart", _onDragStart);
this.addEventListener("dragend", _onDragEnd);
this.addEventListener("drop", _onDrop); this.addEventListener("drop", _onDrop);
this.addEventListener( this.addEventListener(
"keydown", "keydown",
@ -1882,6 +2331,7 @@
"beforeinput", "beforeinput",
this._beforeInput this._beforeInput
); );
this._imageResizer = new ImageResizer(root, this);
this.setHTML(""); this.setHTML("");
} }
destroy() { destroy() {
@ -1889,6 +2339,7 @@
this.removeEventListener(type); this.removeEventListener(type);
}); });
this._mutation.disconnect(); this._mutation.disconnect();
this._imageResizer.destroy();
this._undoIndex = -1; this._undoIndex = -1;
this._undoStack = []; this._undoStack = [];
this._undoStackLength = 0; this._undoStackLength = 0;
@ -1963,7 +2414,7 @@
event.preventDefault(); event.preventDefault();
this.bold(); this.bold();
break; break;
case "formaItalic": case "formatItalic":
event.preventDefault(); event.preventDefault();
this.italic(); this.italic();
break; break;
@ -2157,9 +2608,11 @@
} }
_getRangeAndRemoveBookmark(range) { _getRangeAndRemoveBookmark(range) {
const root = this._root; const root = this._root;
const start = root.querySelector("#" + this.startSelectionId); const starts = root.querySelectorAll("#" + this.startSelectionId);
const end = root.querySelector("#" + this.endSelectionId); const ends = root.querySelectorAll("#" + this.endSelectionId);
if (start && end) { const start = starts[0];
const end = ends[0];
if (start && end && !!(start.compareDocumentPosition(end) & Node.DOCUMENT_POSITION_FOLLOWING)) {
let startContainer = start.parentNode; let startContainer = start.parentNode;
let endContainer = end.parentNode; let endContainer = end.parentNode;
const startOffset = Array.from(startContainer.childNodes).indexOf( const startOffset = Array.from(startContainer.childNodes).indexOf(
@ -2194,6 +2647,12 @@
} }
} }
} }
for (let i = 0; i < starts.length; i += 1) {
starts[i].remove();
}
for (let i = 0; i < ends.length; i += 1) {
ends[i].remove();
}
return range || null; return range || null;
} }
getSelection() { getSelection() {
@ -2279,10 +2738,23 @@
} }
_updatePathOnEvent() { _updatePathOnEvent() {
if (this._isFocused) { if (this._isFocused) {
this._updatePath(this.getSelection()); const lastPathRange = this._lastPathRange;
const selection = this.getSelection();
if (!lastPathRange || !lastPathRange.commonAncestorContainer.isConnected || selection.compareBoundaryPoints(
0,
// Range.START_TO_START,
lastPathRange
) !== 0 || selection.compareBoundaryPoints(
2,
//Range.END_TO_END
lastPathRange
) !== 0) {
this._updatePath(selection);
}
} }
} }
_updatePath(range, force) { _updatePath(range, force) {
this._lastPathRange = range.cloneRange();
const anchor = range.startContainer; const anchor = range.startContainer;
const focus = range.endContainer; const focus = range.endContainer;
let newPath; let newPath;
@ -2422,9 +2894,16 @@
return this; return this;
} }
saveUndoState(range) { saveUndoState(range) {
range || (range = this.getSelection()); let rangeIsFromSelection = false;
if (!range) {
range = this.getSelection();
rangeIsFromSelection = true;
}
this._recordUndoState(range, this._isInUndoState); this._recordUndoState(range, this._isInUndoState);
this._getRangeAndRemoveBookmark(range); this._getRangeAndRemoveBookmark(range);
if (rangeIsFromSelection) {
this.setSelection(range);
}
return this; return this;
} }
undo() { undo() {
@ -2484,14 +2963,26 @@
} }
getHTML(withBookmark) { getHTML(withBookmark) {
let range; let range;
if (withBookmark) { let html = "";
range = this.getSelection(); this.modifyDocument(() => {
this._saveRangeToBookmark(range); if (withBookmark) {
} range = this.getSelection();
const html = this._getRawHTML().replace(/\u200B/g, ""); this._saveRangeToBookmark(range);
if (withBookmark) { }
this._getRangeAndRemoveBookmark(range); const resizeContainer = this._root.querySelector(
} ".squire-image-resize-container"
);
if (resizeContainer) {
resizeContainer.remove();
}
html = this._getRawHTML().replace(/\u200B/g, "");
if (resizeContainer) {
this._root.append(resizeContainer);
}
if (withBookmark) {
this._getRangeAndRemoveBookmark(range);
}
});
return html; return html;
} }
setHTML(html) { setHTML(html) {
@ -2640,20 +3131,6 @@
insertPlainText(plainText, isPaste) { insertPlainText(plainText, isPaste) {
const range = this.getSelection(); const range = this.getSelection();
if (range.collapsed && getClosest(range.startContainer, this._root, "PRE")) { if (range.collapsed && getClosest(range.startContainer, this._root, "PRE")) {
const startContainer = range.startContainer;
let offset = range.startOffset;
let textNode;
if (!startContainer || !(startContainer instanceof Text)) {
const text = document.createTextNode("");
startContainer.insertBefore(
text,
startContainer.childNodes[offset]
);
textNode = text;
offset = 0;
} else {
textNode = startContainer;
}
let doInsert = true; let doInsert = true;
if (isPaste) { if (isPaste) {
const event = new CustomEvent("willPaste", { const event = new CustomEvent("willPaste", {
@ -2667,6 +3144,27 @@
doInsert = !event.defaultPrevented; doInsert = !event.defaultPrevented;
} }
if (doInsert) { if (doInsert) {
this.saveUndoState(range);
const startContainer = range.startContainer;
let offset = range.startOffset;
let textNode;
if (startContainer instanceof Text) {
textNode = startContainer;
} else {
let nodeBeforeCursor = offset ? startContainer.childNodes[offset - 1] : null;
if (nodeBeforeCursor instanceof Text) {
textNode = nodeBeforeCursor;
offset = textNode.length;
} else {
const text = document.createTextNode("");
startContainer.insertBefore(
text,
startContainer.childNodes[offset]
);
textNode = text;
offset = 0;
}
}
textNode.insertData(offset, plainText); textNode.insertData(offset, plainText);
range.setStart(textNode, offset + plainText.length); range.setStart(textNode, offset + plainText.length);
range.collapse(true); range.collapse(true);
@ -3038,8 +3536,11 @@
// --- Block formatting // --- Block formatting
_ensureBottomLine() { _ensureBottomLine() {
const root = this._root; const root = this._root;
const last = root.lastElementChild; let last = root;
if (!last || last.nodeName !== this._config.blockTag || !isBlock(last)) { do {
last = last.lastElementChild;
} while (last && isContainer(last));
if (!last || !isBlock(last)) {
root.append(this.createDefaultBlock()); root.append(this.createDefaultBlock());
} }
} }
@ -3076,10 +3577,11 @@
if (block && (parent = getClosest(block, root, "PRE"))) { if (block && (parent = getClosest(block, root, "PRE"))) {
moveRangeBoundariesDownTree(range); moveRangeBoundariesDownTree(range);
node = range.startContainer; node = range.startContainer;
const offset2 = range.startOffset; let offset2 = range.startOffset;
if (!(node instanceof Text)) { if (!(node instanceof Text)) {
node = document.createTextNode(""); node = document.createTextNode("");
parent.insertBefore(node, parent.firstChild); parent.insertBefore(node, parent.firstChild);
offset2 = 0;
} }
if (!lineBreakOnly && node instanceof Text && (node.data.charAt(offset2 - 1) === "\n" || rangeDoesStartAtBlockBoundary(range, root)) && (node.data.charAt(offset2) === "\n" || rangeDoesEndAtBlockBoundary(range, root))) { if (!lineBreakOnly && node instanceof Text && (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); node.deleteData(offset2 && offset2 - 1, offset2 ? 2 : 1);
@ -3101,7 +3603,9 @@
range.setStart(node, 0); range.setStart(node, 0);
} else { } else {
node.insertData(offset2, "\n"); node.insertData(offset2, "\n");
fixCursor(parent); if (!node.nextSibling) {
parent.append(createElement("BR"));
}
if (node.length === offset2 + 1) { if (node.length === offset2 + 1) {
range.setStartAfter(node); range.setStartAfter(node);
} else { } else {