From 020927cc564f57c4b2e520b509a5de343c4cabd8 Mon Sep 17 00:00:00 2001 From: the-djmaze <> Date: Tue, 20 Oct 2026 23:32:32 +0200 Subject: [PATCH] Squire v2.4.9 --- vendors/squire2/dist/squire-raw.js | 664 +++++++++++++++++++++++++---- 1 file changed, 584 insertions(+), 80 deletions(-) diff --git a/vendors/squire2/dist/squire-raw.js b/vendors/squire2/dist/squire-raw.js index de1705bbc..1a5b56854 100644 --- a/vendors/squire2/dist/squire-raw.js +++ b/vendors/squire2/dist/squire-raw.js @@ -45,6 +45,7 @@ // source/Constants.ts var ELEMENT_NODE = 1; var TEXT_NODE = 3; + var COMMENT_NODE = 8; var DOCUMENT_FRAGMENT_NODE = 11; var ZWS = "\u200B"; var ua = navigator.userAgent; @@ -55,7 +56,7 @@ var isWebKit = /WebKit\//.test(ua); var ctrlKey = isMac || isIOS ? "Meta-" : "Ctrl-"; var cantFocusEmptyTextNodes = isWebKit; - var notWS = /[^ \t\r\n]/; + var notWS = /[^ \t\r\n\u200B]/; // 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)$/; @@ -71,6 +72,7 @@ var isLeaf = (node) => leafNodeNames.has(node.nodeName); var getNodeCategory = (node) => { switch (node.nodeType) { + case COMMENT_NODE: case TEXT_NODE: return INLINE; case ELEMENT_NODE: @@ -252,11 +254,12 @@ return nodeStartAfterStart && nodeEndBeforeEnd; } }; + var canDescendInto = (node) => node instanceof Text || node.nodeType === ELEMENT_NODE && !isLeaf(node); var moveRangeBoundariesDownTree = (range) => { let { startContainer, startOffset, endContainer, endOffset } = range; while (!(startContainer instanceof Text)) { let child = startContainer.childNodes[startOffset]; - if (!child || isLeaf(child)) { + if (!child || !canDescendInto(child)) { if (startOffset) { child = startContainer.childNodes[startOffset - 1]; if (child instanceof Text) { @@ -278,7 +281,7 @@ if (endOffset) { while (!(endContainer instanceof Text)) { const child = endContainer.childNodes[endOffset - 1]; - if (!child || isLeaf(child)) { + if (!child || !canDescendInto(child)) { if (child && child.nodeName === "BR" && !isLineBreak(child)) { --endOffset; continue; @@ -291,7 +294,7 @@ } else { while (!(endContainer instanceof Text)) { const child = endContainer.firstChild; - if (!child || isLeaf(child)) { + if (!child || !canDescendInto(child)) { break; } endContainer = child; @@ -368,7 +371,7 @@ replace(classNames, family) { return createElement("SPAN", { class: classNames.fontFamily, - style: "font-family:" + family + style: { fontFamily: family } }); } }, @@ -377,7 +380,7 @@ replace(classNames, size) { return createElement("SPAN", { class: classNames.fontSize, - style: "font-size:" + size + style: { fontSize: size } }); } }, @@ -557,6 +560,9 @@ }; var fixContainer = (container) => { let wrapper = null; + if (/^(?:TABLE|TBODY|TR|TH|TD|P)/.test(container.nodeName)) { + return container; + } [...container.childNodes].forEach((child) => { if (isInline(child)) { wrapper || (wrapper = createElement("DIV")); @@ -566,10 +572,13 @@ wrapper = null; } }); - wrapper && container.append(wrapper); + wrapper && container.append(fixCursor(wrapper)); return container; }; 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 (typeof offset !== "number") { throw new Error("Offset must be a number to split text node!"); @@ -1020,7 +1029,7 @@ } moveRangeBoundariesDownTree(range); 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 blockContentsAfterSplit = null; const firstBlockInFrag = getNextBlock(frag, frag); @@ -1054,11 +1063,15 @@ } } mergeWithBlock(container, firstBlockInFrag, range, root); - offset = Array.from(container.parentNode.childNodes).indexOf( - container - ) + 1; - container = container.parentNode; - range.setEnd(container, offset); + if (container === root) { + range.setEnd(root, getLength(root)); + } else { + offset = Array.from(container.parentNode.childNodes).indexOf( + container + ) + 1; + container = container.parentNode; + range.setEnd(container, offset); + } } if (getLength(frag)) { if (replaceBlock && block) { @@ -1066,6 +1079,13 @@ range.collapse(false); 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); let nodeAfterSplit = split( range.endContainer, @@ -1149,29 +1169,25 @@ // source/Clipboard.ts var indexOf = Array.prototype.indexOf; - var extractRangeToClipboard = (event, range, root, removeRangeFromDocument, toCleanHTML, toPlainText, plainTextOnly) => { - const clipboardData = event.clipboardData; - if (!clipboardData) { - return false; - } + var extractRange = (range, root, removeRangeFromDocument, toCleanHTML, toPlainText) => { let text = toPlainText ? "" : getTextContentsOfRange(range); const startBlock = getStartBlockOfRange(range, root); const endBlock = getEndBlockOfRange(range, root); + let parent = range.commonAncestorContainer; let copyRoot = root; - if (startBlock === endBlock && startBlock?.contains(range.commonAncestorContainer)) { + if (startBlock === endBlock && startBlock?.contains(parent)) { copyRoot = startBlock; } let contents; if (removeRangeFromDocument) { contents = deleteContentsOfRange(range, root); + if (!parent.isConnected) { + parent = range.commonAncestorContainer; + } } else { - range = range.cloneRange(); - moveRangeBoundariesDownTree(range); - moveRangeBoundariesUpTree(range, copyRoot, copyRoot, root); contents = range.cloneContents(); } - let parent = range.commonAncestorContainer; - if (parent instanceof Text) { + while (parent && !(parent instanceof Element)) { parent = parent.parentNode; } while (parent && parent !== copyRoot) { @@ -1183,7 +1199,7 @@ let html; if (contents.childNodes.length === 1 && contents.childNodes[0] instanceof Text) { text = contents.childNodes[0].data.replace(/ /g, " "); - plainTextOnly = true; + html = void 0; } else { const node = createElement("DIV"); node.append(contents); @@ -1195,15 +1211,34 @@ if (toPlainText && html !== void 0) { text = toPlainText(html); } + if (text === html) { + html = void 0; + } if (isWin) { text = text.replace(/\r?\n/g, "\r\n"); } - if (!plainTextOnly && html && text !== html) { + if (html) { html = "" + 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/plain", text); - event.preventDefault(); return true; }; var _onCut = function(event) { @@ -1251,7 +1286,11 @@ var _onPaste = function(event) { const clipboardData = event.clipboardData; const items = clipboardData.items; - const choosePlain = this._isShiftDown; + const choosePlain = this._isShiftDown || !!getNearest( + this.getSelection().commonAncestorContainer, + this._root, + "PRE" + ); let hasRTF = false; let hasImage = false; let plainItem = null; @@ -1270,7 +1309,7 @@ hasImage = true; } } - if (hasImage && !(hasRTF && htmlItem)) { + if (hasImage && !(hasRTF && htmlItem) && !plainItem) { event.preventDefault(); this.fireEvent("pasteImage", { clipboardData @@ -1284,42 +1323,122 @@ }); } else if (plainItem) { plainItem.getAsString((text) => { - let isLink = false; const range = this.getSelection(); if (!range.collapsed && notWS.test(range.toString())) { const match = this.linkRegExp.exec(text); - isLink = !!match && match[0].length === text.length; - } - if (isLink) { - this.makeLink(text); - } else { - this.insertPlainText(text, true); + const isLink = !!match && match[0].length === text.length; + if (isLink) { + const href = match[1] ? /^(?:ht|f)tps?:/i.test(match[1]) ? match[1] : "http://" + match[1] : "mailto:" + match[0]; + this.makeLink(href); + return; + } } + 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) { - if (!event.dataTransfer) { + const dataTransfer = event.dataTransfer; + if (!dataTransfer) { return; } - const types = event.dataTransfer.types; - let l = types.length; + const types = dataTransfer.types; let hasPlain = false; let hasHTML = false; - while (l--) { - switch (types[l]) { + for (let i = 0, l = types.length; i < l; i += 1) { + switch (types[i]) { case "text/plain": hasPlain = true; break; case "text/html": hasHTML = true; break; - default: - return; } } - if (hasHTML || hasPlain && this.saveUndoState) { - this.saveUndoState(); + if (!hasPlain && !hasHTML) { + 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; const range = this.getSelection(); - if (this._keyHandlers[key]) { - this._keyHandlers[key](this, event, range); + const handler = this._keyHandlers[key]; + if (handler) { + handler(this, event, range); } else if (!range.collapsed && !event.ctrlKey && !event.metaKey && key.length === 1) { this.saveUndoState(range); deleteContentsOfRange(range, this._root); @@ -1667,7 +1787,7 @@ do { if (node.nodeName === "CODE") { let next = node.nextSibling; - if (!(next instanceof Text)) { + if (!(next instanceof Text) || next.length === 0) { const textNode = document.createTextNode("\xA0"); node.parentNode.insertBefore(textNode, next); next = textNode; @@ -1759,6 +1879,331 @@ 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 var Squire = class { constructor(root, config) { @@ -1843,6 +2288,7 @@ this._mayHaveZWS = false; this._lastAnchorNode = null; this._lastFocusNode = null; + this._lastPathRange = null; this._path = ""; this._events = /* @__PURE__ */ new Map(); this._undoIndex = -1; @@ -1861,6 +2307,9 @@ this.addEventListener("cut", _onCut); this.addEventListener("copy", _onCopy); this.addEventListener("paste", _onPaste); + this._dragRange = null; + this.addEventListener("dragstart", _onDragStart); + this.addEventListener("dragend", _onDragEnd); this.addEventListener("drop", _onDrop); this.addEventListener( "keydown", @@ -1882,6 +2331,7 @@ "beforeinput", this._beforeInput ); + this._imageResizer = new ImageResizer(root, this); this.setHTML(""); } destroy() { @@ -1889,6 +2339,7 @@ this.removeEventListener(type); }); this._mutation.disconnect(); + this._imageResizer.destroy(); this._undoIndex = -1; this._undoStack = []; this._undoStackLength = 0; @@ -1963,7 +2414,7 @@ event.preventDefault(); this.bold(); break; - case "formaItalic": + case "formatItalic": event.preventDefault(); this.italic(); break; @@ -2157,9 +2608,11 @@ } _getRangeAndRemoveBookmark(range) { const root = this._root; - const start = root.querySelector("#" + this.startSelectionId); - const end = root.querySelector("#" + this.endSelectionId); - if (start && end) { + const starts = root.querySelectorAll("#" + this.startSelectionId); + const ends = root.querySelectorAll("#" + this.endSelectionId); + const start = starts[0]; + const end = ends[0]; + if (start && end && !!(start.compareDocumentPosition(end) & Node.DOCUMENT_POSITION_FOLLOWING)) { let startContainer = start.parentNode; let endContainer = end.parentNode; 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; } getSelection() { @@ -2279,10 +2738,23 @@ } _updatePathOnEvent() { 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) { + this._lastPathRange = range.cloneRange(); const anchor = range.startContainer; const focus = range.endContainer; let newPath; @@ -2422,9 +2894,16 @@ return this; } saveUndoState(range) { - range || (range = this.getSelection()); + let rangeIsFromSelection = false; + if (!range) { + range = this.getSelection(); + rangeIsFromSelection = true; + } this._recordUndoState(range, this._isInUndoState); this._getRangeAndRemoveBookmark(range); + if (rangeIsFromSelection) { + this.setSelection(range); + } return this; } undo() { @@ -2484,14 +2963,26 @@ } getHTML(withBookmark) { let range; - if (withBookmark) { - range = this.getSelection(); - this._saveRangeToBookmark(range); - } - const html = this._getRawHTML().replace(/\u200B/g, ""); - if (withBookmark) { - this._getRangeAndRemoveBookmark(range); - } + let html = ""; + this.modifyDocument(() => { + if (withBookmark) { + range = this.getSelection(); + this._saveRangeToBookmark(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; } setHTML(html) { @@ -2640,20 +3131,6 @@ insertPlainText(plainText, isPaste) { const range = this.getSelection(); 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; if (isPaste) { const event = new CustomEvent("willPaste", { @@ -2667,6 +3144,27 @@ doInsert = !event.defaultPrevented; } 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); range.setStart(textNode, offset + plainText.length); range.collapse(true); @@ -3038,8 +3536,11 @@ // --- Block formatting _ensureBottomLine() { const root = this._root; - const last = root.lastElementChild; - if (!last || last.nodeName !== this._config.blockTag || !isBlock(last)) { + let last = root; + do { + last = last.lastElementChild; + } while (last && isContainer(last)); + if (!last || !isBlock(last)) { root.append(this.createDefaultBlock()); } } @@ -3076,10 +3577,11 @@ if (block && (parent = getClosest(block, root, "PRE"))) { moveRangeBoundariesDownTree(range); node = range.startContainer; - const offset2 = range.startOffset; + let offset2 = range.startOffset; if (!(node instanceof Text)) { node = document.createTextNode(""); 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))) { node.deleteData(offset2 && offset2 - 1, offset2 ? 2 : 1); @@ -3101,7 +3603,9 @@ range.setStart(node, 0); } else { node.insertData(offset2, "\n"); - fixCursor(parent); + if (!node.nextSibling) { + parent.append(createElement("BR")); + } if (node.length === offset2 + 1) { range.setStartAfter(node); } else {